package main import ( "github.com/veandco/go-sdl2/sdl" "image/color" "math/rand" ) type Player struct { local bool playerColors PlayerColors keyMap KeyMap joyMap JoyMap joyStick *sdl.Joystick camera *sdl.Rect energy uint16 ammunition uint16 shields uint16 digCooldown uint8 shootCooldown uint8 repairCooldown uint8 rechargeCooldown uint8 movementCooldown uint8 reloadCooldown uint8 gameObject *GameObject window *sdl.Window logicalSurface *sdl.Surface playSurface *sdl.Surface HUDSurface *sdl.Surface playSurfaceRect *sdl.Rect HUDSurfaceRect *sdl.Rect playSurfaceTargetRect *sdl.Rect HUDSurfaceTargetRect *sdl.Rect totalHandleEventsTime uint64 totalRenderTime uint64 totalFrameTime uint64 totalScalingTime uint64 frameCount uint64 } func (player *Player) track(camera *sdl.Rect) { camera.X = player.gameObject.baseRect.X - 37 camera.Y = player.gameObject.baseRect.Y - 38 } func (player *Player) render(camera *sdl.Rect, surface *sdl.Surface) { // Common part: Rendering player player.gameObject.baseRect = sdl.Rect{X: player.gameObject.baseRect.X, Y: player.gameObject.baseRect.Y, W: 7, H: 7} if player.shields > 0 && player.shields <= MaxShields { player.gameObject.render(camera, surface) } } func (player *Player) digBlock(posX, posY int32, gameMap *GameMap, isShooting bool) uint8 { collisionAtDigSite := gameMap.checkCollision(posX, posY) if collisionAtDigSite == 0 { return 0 } else if collisionAtDigSite == 1 && (player.digCooldown == 0 || isShooting) { gameMap.tiles[posX][posY] = 0 player.digCooldown = DiggingCooldown return 1 } return 2 } func (player *Player) tick(isShooting bool, bullets *[]*Bullet, format *sdl.PixelFormat) { if player.digCooldown > 0 { player.digCooldown-- } if player.shootCooldown > 0 { player.shootCooldown-- } if player.repairCooldown > 0 { player.repairCooldown-- } if player.rechargeCooldown > 0 { player.rechargeCooldown-- } if player.movementCooldown > 0 { player.movementCooldown-- } if player.reloadCooldown > 0 { player.reloadCooldown-- } else if player.ammunition < MaxAmmunition && !isShooting && player.energy > ReloadCost { player.ammunition++ player.reloadCooldown = ReloadCooldown player.energy -= ReloadCost } if player.shields <= 0 { player.shields = MaxShields + 1 player.explode(bullets) } } func (player *Player) tryMove(gameMap *GameMap, isShooting bool, players *[]*Player) (moved bool) { if player.movementCooldown > 0 { return false } ranOutOfEnergy := (isShooting && player.energy <= DiggingCost+ShootDiggingCostBonus) || player.energy <= DiggingCost if ranOutOfEnergy { isShooting = false } shouldPenalizeDigging := false stopped := false switch player.gameObject.orientation { case 0: // Up collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X, Y: player.gameObject.baseRect.Y - 1, W: 5, H: 7, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for x := player.gameObject.baseRect.X; x < player.gameObject.baseRect.X+5 && !stopped; x++ { for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+7; y++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.Y-- } case 1: // Right collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X + 1, Y: player.gameObject.baseRect.Y, W: 7, H: 5, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+6 && !stopped; y++ { for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8; x++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.X++ } case 2: // Down collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X, Y: player.gameObject.baseRect.Y + 1, W: 5, H: 7, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for x := player.gameObject.baseRect.X; x < player.gameObject.baseRect.X+5 && !stopped; x++ { for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.Y++ } case 3: // Left collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X - 1, Y: player.gameObject.baseRect.Y, W: 7, H: 5, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+6 && !stopped; y++ { for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6; x++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.X-- } case 4: // Up-Right collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X + 1, Y: player.gameObject.baseRect.Y - 1, W: 7, H: 7, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8 && !stopped; x++ { for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+6; y++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.X++ player.gameObject.baseRect.Y-- } case 5: // Up-Left collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X - 1, Y: player.gameObject.baseRect.Y - 1, W: 7, H: 7, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6 && !stopped; x++ { for y := player.gameObject.baseRect.Y - 1; y < player.gameObject.baseRect.Y+6; y++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.X-- player.gameObject.baseRect.Y-- } case 6: // Down-Right collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X + 1, Y: player.gameObject.baseRect.Y + 1, W: 7, H: 7, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for x := player.gameObject.baseRect.X + 1; x < player.gameObject.baseRect.X+8 && !stopped; x++ { for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.X++ player.gameObject.baseRect.Y++ } case 7: // Down-Left collisionRect := sdl.Rect{ X: player.gameObject.baseRect.X - 1, Y: player.gameObject.baseRect.Y + 1, W: 7, H: 7, } for _, opponent := range *players { if opponent != player && opponent.gameObject.baseRect.HasIntersection(&collisionRect) { return false } } for x := player.gameObject.baseRect.X - 1; x < player.gameObject.baseRect.X+6 && !stopped; x++ { for y := player.gameObject.baseRect.Y + 1; y < player.gameObject.baseRect.Y+8; y++ { digResult := player.digBlock(x, y, gameMap, isShooting) if digResult == 1 { shouldPenalizeDigging = true if !isShooting { stopped = true break } } else if digResult != 0 { stopped = true break } } } if !stopped { moved = true player.gameObject.baseRect.X-- player.gameObject.baseRect.Y++ } } // Penalties and cooldown handling if shouldPenalizeDigging { if isShooting && player.ammunition < MaxAmmunition && rand.Intn(2) == 0 { player.ammunition++ } if ranOutOfEnergy { player.digCooldown = DiggingCooldownNoEnergy } player.energy -= DiggingCost if isShooting { player.energy -= ShootDiggingCostBonus } } if moved { if ranOutOfEnergy { player.movementCooldown = MovementCooldownNoEnergy } else { player.movementCooldown = MovementCooldown } } return } func (player *Player) getRGBAColor(colorIndex uint8, format *sdl.PixelFormat) uint32 { var selectedColor color.Color switch colorIndex { case 0: selectedColor = player.playerColors.tracks case 1: selectedColor = player.playerColors.body case 2: selectedColor = player.playerColors.cannon } var r, g, b, a uint8 if selectedColor != nil { rt, gt, bt, at := selectedColor.RGBA() r, g, b, a = uint8(rt), uint8(gt), uint8(bt), uint8(at) } return sdl.MapRGBA(format, r, g, b, a) } func (player *Player) shoot(super bool, bullets *[]*Bullet) { if (super && (player.energy <= SuperShotCost || player.ammunition < MaxAmmunition)) || (!super && (player.energy <= NormalShotCost || player.ammunition < 1)) { return } if player.shootCooldown == 0 { var shootX, shootY int32 switch player.gameObject.orientation { case 0: // Up shootY = player.gameObject.baseRect.Y - 1 shootX = player.gameObject.baseRect.X + 2 break case 1: // Right shootY = player.gameObject.baseRect.Y + 3 shootX = player.gameObject.baseRect.X + 8 break case 2: // Down shootX = player.gameObject.baseRect.X + 2 shootY = player.gameObject.baseRect.Y + 8 break case 3: // Left shootY = player.gameObject.baseRect.Y + 3 shootX = player.gameObject.baseRect.X - 2 break case 4: // Up-Right shootY = player.gameObject.baseRect.Y shootX = player.gameObject.baseRect.X + 5 break case 5: // Up-Left shootY = player.gameObject.baseRect.Y shootX = player.gameObject.baseRect.X - 1 break case 6: // Down-Right shootY = player.gameObject.baseRect.Y + 5 shootX = player.gameObject.baseRect.X + 5 break case 7: // Down-Left shootY = player.gameObject.baseRect.Y + 5 shootX = player.gameObject.baseRect.X - 1 break } // Set bullet color bulletColor := player.playerColors.body // Create and add the bullet *bullets = append(*bullets, &Bullet{ posX: shootX, posY: shootY, direction: player.gameObject.orientation, color: bulletColor, super: super, }) // Set cooldown and decrease energy player.shootCooldown = ShootCooldown if super { player.energy -= SuperShotCost player.ammunition = 0 } else { player.energy -= NormalShotCost player.ammunition-- } } } func (player *Player) explode(bullets *[]*Bullet) { // Set bullet color bulletColor := player.playerColors.body for x := player.gameObject.baseRect.X - BlastRadius; x < BlastRadius*2+1; x++ { for y := player.gameObject.baseRect.Y - BlastRadius; y < BlastRadius*2+1; y++ { // Create and add the bullet *bullets = append(*bullets, &Bullet{ posX: x, posY: y, direction: uint8(rand.Intn(8)), color: bulletColor, super: true, }) } } } func createPlayers(amount uint8, playerColors []PlayerColors, keyMaps []KeyMap, joyMaps []JoyMap, gameMap *GameMap, players *[]*Player) { joyStickCount := sdl.NumJoysticks() if amount > uint8(len(keyMaps)+len(joyMaps)) || amount > uint8(len(keyMaps)+joyStickCount) { panic("Too many players, not enough inputs") } if amount >= uint8(len(playerColors)) { panic("Too many players, not enough colors") } addedKeyboardPlayers := 0 for i := uint8(0); i < amount; i++ { var keyMap KeyMap var joyMap JoyMap var joyStick *sdl.Joystick if (DoAllKeymapsPlayers && i <= uint8(len(keyMaps))) || (DoKeymapPlayer && i == 0) || (uint8(joyStickCount) <= i) { keyMap = keyMaps[addedKeyboardPlayers] addedKeyboardPlayers++ } else { joyStickIndex := i - uint8(addedKeyboardPlayers) joyMap = joyMaps[joyStickIndex] joyStick = sdl.JoystickOpen(int(joyStickIndex)) } createPlayer( playerColors[i], keyMap, joyMap, joyStick, gameMap, players, ) } } func closeThings(players *[]*Player) { for _, player := range *players { if player.joyStick != nil { player.joyStick.Close() } if player.window != nil { player.window.Destroy() } if player.logicalSurface != nil { player.logicalSurface.Free() } if player.playSurface != nil { player.playSurface.Free() } if player.HUDSurface != nil { player.HUDSurface.Free() } } } func createPlayer(playerColors PlayerColors, keyMap KeyMap, joyMap JoyMap, joyStick *sdl.Joystick, gameMap *GameMap, players *[]*Player) { coordsAreValid := false var posX, posY int32 maxTries := 1000 for !coordsAreValid && maxTries >= 0 { maxTries-- posX = int32(16 + rand.Intn(int(gameMap.width-43))) posY = int32(16 + rand.Intn(int(gameMap.height-43))) coordsAreValid = true for _, player := range *players { distance := (player.gameObject.baseRect.X-posX)*(player.gameObject.baseRect.X-posX) + (player.gameObject.baseRect.Y-posY)*(player.gameObject.baseRect.Y-posY) if distance < 300*300 { // Check if distance is less than 300 units coordsAreValid = false break } } if posX < 16 || posX > gameMap.width-36-7 || posY < 16 || posY > gameMap.height-36-7 { coordsAreValid = false break } } if maxTries < 0 { panic("Could not place all players, increase map size") } gameObject := &GameObject{} gameObject.baseRect = sdl.Rect{ X: posX, Y: posY, W: 7, H: 7, } gameObject.addColor(playerColors.tracks) gameObject.addColor(playerColors.body) gameObject.addColor(playerColors.cannon) gameObject.orientation = 0 // Up gameObject.addColoredRect(0, 1, 1, 6, 0) gameObject.addColoredRect(4, 1, 1, 6, 0) gameObject.addColoredRect(1, 2, 3, 4, 1) gameObject.addColoredRect(2, 0, 1, 4, 2) gameObject.orientation = 1 // Right gameObject.addColoredRect(1, 1, 6, 1, 0) gameObject.addColoredRect(1, 5, 6, 1, 0) gameObject.addColoredRect(2, 2, 4, 3, 1) gameObject.addColoredRect(4, 3, 4, 1, 2) gameObject.orientation = 2 // Down gameObject.addColoredRect(0, 1, 1, 6, 0) gameObject.addColoredRect(4, 1, 1, 6, 0) gameObject.addColoredRect(1, 2, 3, 4, 1) gameObject.addColoredRect(2, 4, 1, 4, 2) gameObject.orientation = 3 // Left gameObject.addColoredRect(1, 1, 6, 1, 0) gameObject.addColoredRect(1, 5, 6, 1, 0) gameObject.addColoredRect(2, 2, 4, 3, 1) gameObject.addColoredRect(0, 3, 4, 1, 2) gameObject.orientation = 4 // Up-Right gameObject.addColoredRect(3, 0, 1, 1, 0) gameObject.addColoredRect(2, 1, 1, 1, 0) gameObject.addColoredRect(1, 2, 1, 1, 0) gameObject.addColoredRect(0, 3, 1, 1, 0) gameObject.addColoredRect(6, 3, 1, 1, 0) gameObject.addColoredRect(5, 4, 1, 1, 0) gameObject.addColoredRect(4, 5, 1, 1, 0) gameObject.addColoredRect(3, 6, 1, 1, 0) gameObject.addColoredRect(3, 1, 1, 1, 1) gameObject.addColoredRect(2, 2, 2, 1, 1) gameObject.addColoredRect(1, 3, 2, 1, 1) gameObject.addColoredRect(4, 3, 2, 1, 1) gameObject.addColoredRect(2, 4, 3, 1, 1) gameObject.addColoredRect(3, 5, 1, 1, 1) gameObject.addColoredRect(5, 1, 1, 1, 2) gameObject.addColoredRect(4, 2, 1, 1, 2) gameObject.addColoredRect(3, 3, 1, 1, 2) // Up-Left orientation (Y-axis reflection) gameObject.orientation = 5 // Up-Left gameObject.addColoredRect(3, 0, 1, 1, 0) gameObject.addColoredRect(4, 1, 1, 1, 0) gameObject.addColoredRect(5, 2, 1, 1, 0) gameObject.addColoredRect(6, 3, 1, 1, 0) gameObject.addColoredRect(0, 3, 1, 1, 0) gameObject.addColoredRect(1, 4, 1, 1, 0) gameObject.addColoredRect(2, 5, 1, 1, 0) gameObject.addColoredRect(3, 6, 1, 1, 0) gameObject.addColoredRect(3, 1, 1, 1, 1) gameObject.addColoredRect(3, 2, 2, 1, 1) gameObject.addColoredRect(4, 3, 2, 1, 1) gameObject.addColoredRect(1, 3, 2, 1, 1) gameObject.addColoredRect(2, 4, 3, 1, 1) gameObject.addColoredRect(3, 5, 1, 1, 1) gameObject.addColoredRect(1, 1, 1, 1, 2) gameObject.addColoredRect(2, 2, 1, 1, 2) gameObject.addColoredRect(3, 3, 1, 1, 2) // Down-Right orientation (X-axis reflection) gameObject.orientation = 6 // Down-Right gameObject.addColoredRect(3, 6, 1, 1, 0) gameObject.addColoredRect(2, 5, 1, 1, 0) gameObject.addColoredRect(1, 4, 1, 1, 0) gameObject.addColoredRect(0, 3, 1, 1, 0) gameObject.addColoredRect(6, 3, 1, 1, 0) gameObject.addColoredRect(5, 2, 1, 1, 0) gameObject.addColoredRect(4, 1, 1, 1, 0) gameObject.addColoredRect(3, 0, 1, 1, 0) gameObject.addColoredRect(3, 5, 1, 1, 1) gameObject.addColoredRect(2, 4, 2, 1, 1) gameObject.addColoredRect(1, 3, 2, 1, 1) gameObject.addColoredRect(4, 3, 2, 1, 1) gameObject.addColoredRect(2, 2, 3, 1, 1) gameObject.addColoredRect(3, 1, 1, 1, 1) gameObject.addColoredRect(5, 5, 1, 1, 2) gameObject.addColoredRect(4, 4, 1, 1, 2) gameObject.addColoredRect(3, 3, 1, 1, 2) // Down-Left orientation (XY reflection) gameObject.orientation = 7 // Down-Left gameObject.addColoredRect(3, 6, 1, 1, 0) gameObject.addColoredRect(4, 5, 1, 1, 0) gameObject.addColoredRect(5, 4, 1, 1, 0) gameObject.addColoredRect(6, 3, 1, 1, 0) gameObject.addColoredRect(0, 3, 1, 1, 0) gameObject.addColoredRect(1, 2, 1, 1, 0) gameObject.addColoredRect(2, 1, 1, 1, 0) gameObject.addColoredRect(3, 0, 1, 1, 0) gameObject.addColoredRect(3, 1, 1, 1, 1) gameObject.addColoredRect(2, 2, 3, 1, 1) gameObject.addColoredRect(1, 3, 2, 1, 1) gameObject.addColoredRect(4, 3, 2, 1, 1) gameObject.addColoredRect(3, 4, 2, 1, 1) gameObject.addColoredRect(3, 5, 1, 1, 1) gameObject.addColoredRect(1, 5, 1, 1, 2) gameObject.addColoredRect(2, 4, 1, 1, 2) gameObject.addColoredRect(3, 3, 1, 1, 2) gameObject.orientation = 0 *players = append(*players, &Player{ playerColors: playerColors, keyMap: keyMap, joyMap: joyMap, joyStick: joyStick, shields: MaxShields, energy: MaxEnergy, gameObject: gameObject, local: true, }) }