GOingTunneling/player.go

723 lines
19 KiB
Go

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,
})
}