package systems import ( "log/slog" "openquell/components" . "openquell/components" . "openquell/config" "openquell/grid" "openquell/observers" "openquell/util" "github.com/hajimehoshi/ebiten/v2" "github.com/mlange-42/arche/ecs" "github.com/mlange-42/arche/generic" ) type ObstacleSystem struct { World *ecs.World Selector *generic.Filter4[Position, Velocity, Obstacle, Renderable] GridContainer *grid.GridContainer } func NewObstacleSystem(world *ecs.World, gridcontainer *grid.GridContainer) System { system := &ObstacleSystem{ Selector: generic.NewFilter4[Position, Velocity, Obstacle, Renderable](), World: world, GridContainer: gridcontainer, } return system } func ObstacleBumpEdgeResponder( pos *components.Position, vel *components.Velocity, newpos *components.Position) { pos.Set(newpos) } func ObstacleBumpWallResponder( pos *components.Position, vel *components.Velocity, newpos *components.Position) { pos.Set(newpos) vel.ResetDirectionAndStop() } func (system *ObstacleSystem) Update() error { observer := observers.GetGameObserver(system.World) if observer.Lost { return nil } posID := ecs.ComponentID[components.Position](system.World) veloID := ecs.ComponentID[components.Velocity](system.World) EntitiesToRemove := []ecs.Entity{} query := system.Selector.Query(system.World) for query.Next() { obsposition, obsvelocity, _, _ := query.Get() // check if one player has bumped into current obstacle for _, player := range observer.GetPlayers() { if !system.World.Alive(player) { continue } playerposition := (*Position)(system.World.Get(player, posID)) playervelocity := (*Velocity)(system.World.Get(player, veloID)) ok, newpos := obsposition.Intersects(playerposition, playervelocity) if ok { if CheckObstacleSide(playervelocity, obsvelocity) { // player died EntitiesToRemove = append(EntitiesToRemove, player) } else { // bumped into nonlethal obstacle side, stop the // player, set the obstacle in motion, if possible //slog.Debug("bump not die", "playervelo", playervelocity) obsvelocity.Set(playervelocity) //slog.Debug("bump not die", "obsvelo", obsvelocity) playervelocity.Change(Stop) playerposition.Set(newpos) } } } // check if current obstacle bumped into another obstacle for _, foreign_obstacle := range observer.GetObstacles() { if foreign_obstacle == query.Entity() { // don't check obstacle against itself continue } foreign_obstacle_position := (*Position)(system.World.Get(foreign_obstacle, posID)) ok, newpos := foreign_obstacle_position.Intersects(obsposition, obsvelocity) if ok { //slog.Debug("bumped into foreign obstacle", "obstacle", foreign_obstacle) obsposition.Set(newpos) obsvelocity.ResetDirectionAndStop() } } // check if [moving] obstacle collides with walls or edges system.GridContainer.Grid.CheckGridCollision( obsposition, obsvelocity, ObstacleBumpEdgeResponder, ObstacleBumpWallResponder) if obsvelocity.Moving() { obsposition.Move(obsvelocity) } } for _, entity := range EntitiesToRemove { slog.Debug("remove player") system.World.RemoveEntity(entity) } if len(observer.GetPlayers()) == 0 { // lost timer := observer.StopTimer if !timer.Running { timer.Start(LEVEL_END_WAIT) } observer.Gameover() } return nil } func (system *ObstacleSystem) 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) } } // return true if obstacle weapon points into the direction the player is moving // OR if the weapon points towards a non-moving player func CheckObstacleSide(playervelocity *Velocity, obsvelocity *Velocity) bool { movingdirection := playervelocity.InvertDirection() if obsvelocity.PointingAt == All || movingdirection == obsvelocity.PointingAt { slog.Debug("Damaging obstacle collision", "playerdirection", util.DirectionStr(playervelocity.Direction), "obsdirection", util.DirectionStr(obsvelocity.PointingAt), "movingdirection", util.DirectionStr(movingdirection), ) return true } return false }