package systems import ( "log/slog" "openquell/assets" "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 (system *ObstacleSystem) Update() error { playerobserver := observers.GetPlayerObserver(system.World) gameobserver := observers.GetGameObserver(system.World) obstacleobserver := observers.GetObstacleObserver(system.World) if gameobserver.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 playerobserver.Entities { 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 { // slog.Debug("bumped into obstacle", "obstacle", obstacle) if CheckObstacleSide(playervelocity, obsvelocity.Direction) { // 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", "originalpos", playerposition, "newpos", newpos, "obspos", obsposition) obsvelocity.Set(playervelocity) playervelocity.Change(Stop) playerposition.Set(newpos) } } } // check if current obstacle bumped into another obstacle for foreign_obstacle := range obstacleobserver.Entities { 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() } } // FIXME: this is the same loop as in player_system, unite the // two, just iterate over all entities with pos,vel,render, dammit if obsvelocity.Moving() { ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(obsposition, obsvelocity, true) if ok { intersects, newpos := tilepos.Intersects(obsposition, obsvelocity) if intersects { // slog.Debug("collision with foreign obstacle detected", "tile", // tilepos, "obs", obsposition, "new", newpos) obsposition.Set(newpos) obsvelocity.ResetDirectionAndStop() } } obsposition.Move(obsvelocity) } } for _, entity := range EntitiesToRemove { slog.Debug("remove player") system.World.RemoveEntity(entity) playerobserver.RemoveEntity(entity) } if len(playerobserver.Entities) == 0 { // lost timer := gameobserver.StopTimer if !timer.Running { timer.Start(LEVEL_END_WAIT) } gameobserver.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) } } func (system *ObstacleSystem) AddParticle(position *components.Position) { particleobserver := observers.GetParticleObserver(system.World) ptmapper := generic.NewMap3[ components.Position, components.Particle, components.Timer, ](system.World) entity := ptmapper.New() pos, particle, timer := ptmapper.Get(entity) particleobserver.AddEntity(entity) particle.Index = assets.Tiles['*'].Particle particle.Tiles = assets.Tiles['*'].Tiles pos.Update( position.X-(16), // FIXME: use global tilesize! position.Y-(16), 64, ) timer.Start(PARTICLE_LOOPWAIT) } // return true if obstacle weapon points into the direction the player is moving func CheckObstacleSide(playervelocity *Velocity, obsdirection int) bool { movingdirection := playervelocity.InvertDirection() if movingdirection == Stop || movingdirection == obsdirection { slog.Debug("Damaging obstacle collision", "playerdirection", util.DirectionStr(playervelocity.Direction), "obsdirection", util.DirectionStr(obsdirection), "movingdirection", util.DirectionStr(movingdirection), ) return true } return false }