doctorlectro/system/movement.go

280 lines
8.9 KiB
Go

package system
import (
"fmt"
"image"
"code.rocketnine.space/tslocum/doctorlectro/asset"
"code.rocketnine.space/tslocum/doctorlectro/component"
"code.rocketnine.space/tslocum/doctorlectro/world"
"code.rocketnine.space/tslocum/gohan"
"github.com/hajimehoshi/ebiten/v2"
)
const walkThreshold = 4.00
type MovementSystem struct {
Position *component.Position
Velocity *component.Velocity
Sprite *component.Sprite
}
func NewMovementSystem() *MovementSystem {
return &MovementSystem{}
}
func (s *MovementSystem) Update(e gohan.Entity) error {
if world.GameOver && e == world.Player {
return nil
}
if world.GameOverTicks != 0 {
return nil
}
position := s.Position
if position.Body != nil {
pos := position.Body.Position()
vel := position.Body.Velocity()
position.X, position.Y = pos.X, pos.Y
if e == world.Player {
const threshold = 1
if world.PlayerNormal.Y <= -threshold {
world.PlayerDirection = world.MagnetizeDown
} else if world.MagnetActive && world.PlayerNormal.Y >= threshold {
world.PlayerDirection = world.MagnetizeUp
} else if world.MagnetActive && world.PlayerNormal.X <= -threshold {
world.PlayerDirection = world.MagnetizeRight
} else if world.MagnetActive && world.PlayerNormal.X >= threshold {
world.PlayerDirection = world.MagnetizeLeft
} else if world.MagnetActive && false {
if world.MagnetDirection == world.MagnetizeDown || world.MagnetDirection == world.MagnetizeLeft || world.MagnetDirection == world.MagnetizeUp || world.MagnetDirection == world.MagnetizeRight {
world.PlayerDirection = world.MagnetDirection
} else if world.MagnetDirection == world.MagnetizeDownLeft {
if world.PlayerNormal.X != 0 {
world.PlayerDirection = world.MagnetizeLeft
} else {
world.PlayerDirection = world.MagnetizeDown
}
} else if world.MagnetDirection == world.MagnetizeDownRight {
if world.PlayerNormal.X != 0 {
world.PlayerDirection = world.MagnetizeRight
} else {
world.PlayerDirection = world.MagnetizeDown
}
} else if world.MagnetDirection == world.MagnetizeUpLeft {
if world.PlayerNormal.X != 0 {
world.PlayerDirection = world.MagnetizeLeft
} else {
world.PlayerDirection = world.MagnetizeUp
}
} else if world.MagnetDirection == world.MagnetizeUpRight {
if world.PlayerNormal.X != 0 {
world.PlayerDirection = world.MagnetizeRight
} else {
world.PlayerDirection = world.MagnetizeUp
}
} else {
panic(world.MagnetDirection)
}
}
// Reset to MagnetizeDown values.
s.Sprite.HorizontalFlip = !world.LastWalkDirL
s.Sprite.VerticalFlip = false
s.Sprite.OffsetX = -8
s.Sprite.OffsetY = -18
magnetizeUp := func() {
s.Sprite.HorizontalFlip = !world.LastWalkDirL
s.Sprite.VerticalFlip = true
s.Sprite.OffsetY = 2
}
idleFrames := asset.PlayerIdleFrames
walkFrames := asset.PlayerWalkFrames
switch world.PlayerDirection {
case world.MagnetizeDown:
// Already set.
case world.MagnetizeLeft:
if vel.X <= walkThreshold || true {
s.Sprite.HorizontalFlip = false
s.Sprite.VerticalFlip = world.LastWalkDirU
idleFrames = asset.PlayerIdleFramesRot90
walkFrames = asset.PlayerWalkFramesRot90
s.Sprite.OffsetX = -8
s.Sprite.OffsetY = -8
} else if world.MagnetActive && (world.MagnetDirection == world.MagnetizeUpLeft || world.MagnetDirection == world.MagnetizeUpRight) {
magnetizeUp()
} else {
// Magnetize down.
}
case world.MagnetizeUp:
magnetizeUp()
case world.MagnetizeRight:
if vel.X >= -walkThreshold || true {
s.Sprite.HorizontalFlip = true
s.Sprite.VerticalFlip = world.LastWalkDirU
idleFrames = asset.PlayerIdleFramesRot90
walkFrames = asset.PlayerWalkFramesRot90
s.Sprite.OffsetX = -8
s.Sprite.OffsetY = -8
} else if world.MagnetActive && (world.MagnetDirection == world.MagnetizeUpLeft || world.MagnetDirection == world.MagnetizeUpRight) {
magnetizeUp()
} else {
// Magnetize down.
}
default:
panic("DEFAULT")
}
world.PlayerIdleFrames = idleFrames
world.PlayerWalkFrames = walkFrames
moveX := vel.X < -walkThreshold || vel.X > walkThreshold
moveY := vel.Y < -walkThreshold || vel.Y > walkThreshold
walking := (ebiten.IsKeyPressed(ebiten.KeyW) && moveY) || (ebiten.IsKeyPressed(ebiten.KeyS) && moveY) || (ebiten.IsKeyPressed(ebiten.KeyA) && moveX) || (ebiten.IsKeyPressed(ebiten.KeyD) && moveX)
if walking {
s.Sprite.Frames = walkFrames
} else {
s.Sprite.Frames = idleFrames
}
s.Sprite.Image = s.Sprite.Frames[s.Sprite.Frame]
normalize := func(f float64) float64 {
if f > 0 && f < 0.1 {
f = 0.1
} else if f < 0 && f > -0.1 {
f = -0.1
}
return f
}
mapScreenWidth := 60 * TileWidth
mapScreenHeight := 34 * TileWidth
targetX, targetY := 16+(int(position.X-world.PlayerWidth/2)/mapScreenWidth)*mapScreenWidth, 16+(int(position.Y-world.PlayerWidth/2)/mapScreenHeight)*mapScreenHeight
const targetThreshold = 2.0
const targetDiv = 42.0
deltaX, deltaY := world.CamX-world.CamTargetX, world.CamY-world.CamTargetY
world.CamTargetX, world.CamTargetY = float64(targetX), float64(targetY)
if (world.CamX == 0 && world.CamY == 0) || (deltaX > targetThreshold && deltaX < targetThreshold) || (deltaY > targetThreshold && deltaY < targetThreshold) {
world.CamX, world.CamY = world.CamTargetX, world.CamTargetY
} else {
world.CamX += normalize((world.CamTargetX - world.CamX) / targetDiv)
world.CamY += normalize((world.CamTargetY - world.CamY) / targetDiv)
}
// Remove activated trigger.
if world.RemoveTrigger != -1 {
i := world.RemoveTrigger
if world.TriggerActions[i] != world.TriggerData {
world.TriggerEntities[i].With(func(s *component.Sprite) {
s.Image = world.TileImages[world.TileScreenOff]
})
}
world.TriggerRects = append(world.TriggerRects[:i], world.TriggerRects[i+1:]...)
world.TriggerActions = append(world.TriggerActions[:i], world.TriggerActions[i+1:]...)
world.TriggerEntities = append(world.TriggerEntities[:i], world.TriggerEntities[i+1:]...)
world.RemoveTrigger = -1
}
// Check trigger rects.
playerRect := world.BBToLevelRect(world.PlayerShape.BB())
for i := range world.TriggerRects {
if playerRect.Overlaps(world.TriggerRects[i]) {
action := world.TriggerActions[i]
var message string
switch action {
case world.TriggerJump:
world.CanJump = true
message = "<J> TO JUMP."
case world.TriggerMagnetize:
world.CanMagnetize = true
message = "<SPACE> TO MAGNETIZE."
case world.TriggerFire:
world.CanFire = true
message = "<K> TO FIRE."
case world.TriggerSwitch:
world.OpenSwitch()
message = "SECURITY BEAM DEACTIVATED."
case world.TriggerData:
message = "ALL SYSTEMS ARE OPERATIONAL.\n\nKEEP AWAY FROM MAGNETIC FIELDS."
default:
panic(fmt.Sprintf("unknown trigger action %d", action))
}
// Notify player of new ability.
if action == world.TriggerSwitch {
world.SetMessage(fmt.Sprintf("ACCESS GRANTED!\n\n%s", message))
} else if action == world.TriggerData {
world.SetMessage(fmt.Sprintf("SUPERWEAPON CONSOLE\n\n%s", message))
} else {
world.SetMessage(fmt.Sprintf("SYSTEM UPGRADED!\n\n%s", message))
}
// Remove trigger during next tick.
world.RemoveTrigger = i
return nil
}
}
if world.MagnetActive && playerRect.Overlaps(world.DataRect) {
asset.SoundAlarm.Rewind()
asset.SoundAlarm.Play()
world.GameOver = true
world.GameOverTicks = 1
}
}
} else if s.Velocity != nil {
position.X, position.Y = position.X+s.Velocity.X, position.Y+s.Velocity.Y
bulletRect := image.Rect(int(position.X), int(position.Y), int(position.X+4), int(position.Y+4))
// Hit destructible.
for i := range world.DestructibleRects {
if world.DestructibleRects[i].Overlaps(bulletRect) {
asset.SoundExplode.Rewind()
asset.SoundExplode.Play()
world.DestructibleEntities[i].Remove()
for _, shape := range world.DestructibleShapes[i] {
world.Space.RemoveShape(shape)
}
world.DestructibleRects = append(world.DestructibleRects[:i], world.DestructibleRects[i+1:]...)
world.DestructibleEntities = append(world.DestructibleEntities[:i], world.DestructibleEntities[i+1:]...)
world.DestructibleShapes = append(world.DestructibleShapes[:i], world.DestructibleShapes[i+1:]...)
e.Remove()
return nil
}
}
// Hit beam.
for i := range world.BeamRects {
if world.BeamRects[i].Overlaps(bulletRect) {
asset.SoundHit.Rewind()
asset.SoundHit.Play()
e.Remove()
return nil
}
}
// Hit wall.
for i := range world.MetalRects {
if world.MetalRects[i].Overlaps(bulletRect) {
asset.SoundHit.Rewind()
asset.SoundHit.Play()
e.Remove()
return nil
}
}
}
return nil
}
func (_ *MovementSystem) Draw(_ gohan.Entity, _ *ebiten.Image) error {
return gohan.ErrUnregister
}