openquell/systems/player_system.go

295 lines
7.3 KiB
Go
Raw Permalink Normal View History

2024-02-10 19:45:06 +01:00
package systems
2024-02-09 20:20:13 +01:00
import (
"fmt"
"log/slog"
2024-02-27 17:28:18 +01:00
"openquell/components"
2024-02-09 20:20:13 +01:00
. "openquell/components"
. "openquell/config"
"openquell/grid"
2024-02-27 17:28:18 +01:00
"openquell/observers"
"openquell/util"
2024-02-09 20:20:13 +01:00
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
2024-02-09 20:20:13 +01:00
"github.com/mlange-42/arche/ecs"
"github.com/mlange-42/arche/generic"
2024-04-16 19:10:32 +02:00
input "github.com/quasilyte/ebitengine-input"
2024-02-09 20:20:13 +01:00
)
type PlayerSystem struct {
World *ecs.World
Selector *generic.Filter4[Position, Velocity, Player, Renderable]
GridContainer *grid.GridContainer
Width, Height int
2024-04-16 19:10:32 +02:00
Input *input.Handler
2024-02-09 20:20:13 +01:00
}
2024-04-16 19:10:32 +02:00
func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer,
width, height int, handler *input.Handler) System {
2024-02-09 20:20:13 +01:00
system := &PlayerSystem{
Selector: generic.NewFilter4[Position, Velocity, Player, Renderable](),
GridContainer: gridcontainer,
World: world,
Width: width,
Height: height,
2024-04-16 19:10:32 +02:00
Input: handler,
2024-02-09 20:20:13 +01:00
}
return system
}
func PlayerBumpEdgeResponder(
pos *components.Position,
vel *components.Velocity,
newpos *components.Position) {
pos.Set(newpos)
}
func PlayerBumpWallResponder(
pos *components.Position,
vel *components.Velocity,
newpos *components.Position) {
slog.Debug("(2) PlayerBumpWallResponder", "old", pos.String(), "new", newpos.String())
pos.Set(newpos)
vel.Change(Stop)
}
2024-02-09 20:20:13 +01:00
func (system PlayerSystem) Update() error {
2024-02-27 17:28:18 +01:00
var EntitiesToRemove []ecs.Entity
// check if we need to switch player[s]
system.SwitchPlayers()
// check player movements etc
query := system.Selector.Query(system.World)
count := query.Count()
for query.Next() {
playerposition, velocity, player, _ := query.Get()
if !player.IsPrimary {
continue
}
// check if the user alters or initiates movement, only
// changes player direction
system.CheckMovement(playerposition, velocity, player)
2024-02-27 17:28:18 +01:00
// check if player collides with walls or edges
if velocity.Moving() {
slog.Debug("(2) checking grid collision")
system.GridContainer.Grid.CheckGridCollision(
playerposition, velocity, PlayerBumpEdgeResponder, PlayerBumpWallResponder)
}
2024-02-27 17:28:18 +01:00
if count > 1 {
// check if player collides with another player, fuse them if any
EntitiesToRemove = system.CheckPlayerCollision(playerposition, velocity, query.Entity())
if len(EntitiesToRemove) > 0 {
slog.Debug("other player collision")
}
} else {
// only 1 player left or one is max
EntitiesToRemove = system.CheckPlayerLooping(
playerposition, velocity, player, query.Entity())
if len(EntitiesToRemove) > 0 {
slog.Debug("player loops", "entities", EntitiesToRemove)
}
}
if !velocity.Moving() {
// disable loop detection
player.LoopCount = 0
2024-02-27 17:28:18 +01:00
}
}
// 2nd pass: move player after obstacle or collectible updates
query = system.Selector.Query(system.World)
for query.Next() {
playerposition, velocity, _, _ := query.Get()
oldpos := playerposition.String()
if velocity.Moving() {
playerposition.Move(velocity)
slog.Debug("(4) moving player", "old", oldpos, "new", playerposition.String())
}
2024-02-27 17:28:18 +01:00
}
// we may have lost players, remove them here
for _, entity := range EntitiesToRemove {
slog.Debug("remove player", "ent", entity.IsZero())
system.World.RemoveEntity(entity)
}
return nil
}
func (system *PlayerSystem) Draw(screen *ebiten.Image) {
// write the movable tiles
op := &ebiten.DrawImageOptions{}
query := system.Selector.Query(system.World)
for query.Next() {
pos, _, _, sprite := query.Get()
op.GeoM.Reset()
op.GeoM.Translate(float64(pos.X), float64(pos.Y))
screen.DrawImage(sprite.Image, op)
if util.DebugEnabled() {
ebitenutil.DebugPrintAt(screen, pos.SmallString(), pos.X, pos.Y+16) // print player pos
}
2024-02-27 17:28:18 +01:00
}
}
func (system *PlayerSystem) SwitchPlayers() {
// first check if we need to switch player
switchable := false
2024-02-09 20:20:13 +01:00
query := system.Selector.Query(system.World)
count := query.Count()
for query.Next() {
_, _, player, _ := query.Get()
if !player.IsPrimary {
switchable = true
}
}
2024-02-09 20:20:13 +01:00
if switchable {
query := system.Selector.Query(system.World)
for query.Next() {
_, _, player, render := query.Get()
if count == 1 && !player.IsPrimary {
// there's only one player left, make it the primary one
player.IsPrimary = true
render.Image = player.SwitchSprite()
} else {
// many players, switch when requested
2024-04-16 19:10:32 +02:00
if system.Input.ActionIsJustPressed(SwitchPlayer) {
slog.Debug("switch players")
if player.IsPrimary {
player.IsPrimary = false
render.Image = player.SwitchSprite()
} else {
player.IsPrimary = true
render.Image = player.SwitchSprite()
}
}
}
}
}
2024-02-27 17:28:18 +01:00
}
func (system *PlayerSystem) CheckMovement(
position *components.Position,
velocity *components.Velocity,
player *components.Player) {
moved := false
observer := observers.GetGameObserver(system.World)
2024-02-27 17:28:18 +01:00
if !velocity.Moving() {
switch {
2024-04-16 19:10:32 +02:00
case system.Input.ActionIsJustPressed(MoveRight):
2024-02-27 17:28:18 +01:00
velocity.Change(East)
moved = true
2024-04-16 19:10:32 +02:00
case system.Input.ActionIsJustPressed(MoveLeft):
2024-02-27 17:28:18 +01:00
velocity.Change(West)
moved = true
2024-04-16 19:10:32 +02:00
case system.Input.ActionIsPressed(MoveDown):
2024-02-27 17:28:18 +01:00
velocity.Change(South)
moved = true
2024-04-16 19:10:32 +02:00
case system.Input.ActionIsJustPressed(MoveUp):
2024-02-27 17:28:18 +01:00
velocity.Change(North)
moved = true
}
if moved {
// will be reset every time the user initiates player movement
player.LoopPos.Set(position)
player.LoopCount = 0
observer.AddMove()
2024-02-09 20:20:13 +01:00
}
} else {
if util.DebugEnabled() {
fmt.Println("------------------------")
}
slog.Debug("(1) player is at", "current", position.String())
2024-02-27 17:28:18 +01:00
}
}
2024-02-09 20:20:13 +01:00
2024-02-27 17:28:18 +01:00
func (system *PlayerSystem) CheckPlayerCollision(
position *components.Position,
velocity *components.Velocity,
player ecs.Entity) []ecs.Entity {
2024-02-27 17:28:18 +01:00
observer := observers.GetGameObserver(system.World)
posID := ecs.ComponentID[components.Position](system.World)
EntitiesToRemove := []ecs.Entity{}
2024-02-10 19:45:06 +01:00
2024-02-27 17:28:18 +01:00
for _, otherplayer := range observer.GetPlayers() {
if !system.World.Alive(otherplayer) {
continue
}
2024-02-10 19:45:06 +01:00
2024-02-27 17:28:18 +01:00
if otherplayer == player {
continue
}
2024-02-10 19:45:06 +01:00
2024-02-27 17:28:18 +01:00
otherposition := (*Position)(system.World.Get(otherplayer, posID))
2024-02-10 19:45:06 +01:00
2024-02-27 17:28:18 +01:00
ok, _ := otherposition.Intersects(position, velocity)
if ok {
// keep displaying it until the two fully intersect
EntitiesToRemove = append(EntitiesToRemove, otherplayer)
}
2024-02-10 19:45:06 +01:00
}
2024-02-27 17:28:18 +01:00
// FIXME: add an animation highlighting the fusion
return EntitiesToRemove
2024-02-10 19:45:06 +01:00
}
func (system *PlayerSystem) CheckPlayerLooping(
position *components.Position,
velocity *components.Velocity,
player *components.Player,
entity ecs.Entity) []ecs.Entity {
if player.LoopPos.Rect == nil {
// hasn't moved
return nil
}
EntitiesToRemove := []ecs.Entity{}
ok, _ := player.LoopPos.Intersects(position, velocity)
if ok {
// Fatal: loop detected with last player
player.LoopStart = true
} else {
// no intersection with old pos anymore
if player.LoopStart {
// loop detection active
player.LoopCount++
max := system.Width
if velocity.Direction == North || velocity.Direction == South {
max = system.Height
}
max += system.GridContainer.Grid.Tilesize * 5
// we haved moved one time around the whole screen, loose
if (player.LoopCount * velocity.Speed) > max {
slog.Debug("loop?", "loopcount", player.LoopCount, "speed", velocity.Speed, "max", max)
EntitiesToRemove = append(EntitiesToRemove, entity)
}
}
}
return EntitiesToRemove
}