| 
									
										
										
										
											2024-02-10 19:45:06 +01:00
										 |  |  | package systems | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 	"log/slog" | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 	"openquell/components" | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 	. "openquell/components" | 
					
						
							|  |  |  | 	. "openquell/config" | 
					
						
							| 
									
										
										
										
											2024-02-23 18:47:15 +01:00
										 |  |  | 	"openquell/grid" | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 	"openquell/observers" | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/hajimehoshi/ebiten/v2" | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 	"github.com/hajimehoshi/ebiten/v2/inpututil" | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 	"github.com/mlange-42/arche/ecs" | 
					
						
							|  |  |  | 	"github.com/mlange-42/arche/generic" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | type PlayerSystem struct { | 
					
						
							| 
									
										
										
										
											2024-02-23 18:47:15 +01:00
										 |  |  | 	World         *ecs.World | 
					
						
							| 
									
										
										
										
											2024-02-26 12:56:12 +01:00
										 |  |  | 	Selector      *generic.Filter4[Position, Velocity, Player, Renderable] | 
					
						
							| 
									
										
										
										
											2024-02-23 18:47:15 +01:00
										 |  |  | 	GridContainer *grid.GridContainer | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 	Width, Height int | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | func NewPlayerSystem(world *ecs.World, gridcontainer *grid.GridContainer, width, height int) System { | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 	system := &PlayerSystem{ | 
					
						
							| 
									
										
										
										
											2024-02-26 12:56:12 +01:00
										 |  |  | 		Selector:      generic.NewFilter4[Position, Velocity, Player, Renderable](), | 
					
						
							| 
									
										
										
										
											2024-02-23 18:47:15 +01:00
										 |  |  | 		GridContainer: gridcontainer, | 
					
						
							|  |  |  | 		World:         world, | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 		Width:         width, | 
					
						
							|  |  |  | 		Height:        height, | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return system | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 		system.CheckMovement(playerposition, velocity, player) | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		// check if player collides with walls or edges | 
					
						
							|  |  |  | 		system.CheckGridCollision(playerposition, velocity) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if count > 1 { | 
					
						
							|  |  |  | 			// check if player collides with another player, fuse them if any | 
					
						
							|  |  |  | 			EntitiesToRemove = system.CheckPlayerCollision(playerposition, velocity, query.Entity()) | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 		} else { | 
					
						
							|  |  |  | 			// only 1 player left or one is max | 
					
						
							|  |  |  | 			EntitiesToRemove = system.CheckPlayerLooping( | 
					
						
							|  |  |  | 				playerposition, velocity, player, query.Entity()) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		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() | 
					
						
							|  |  |  | 		playerposition.Move(velocity) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// 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) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (system *PlayerSystem) SwitchPlayers() { | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 	// first check if we need to switch player | 
					
						
							|  |  |  | 	switchable := false | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 	query := system.Selector.Query(system.World) | 
					
						
							| 
									
										
										
										
											2024-02-27 16:52:20 +01:00
										 |  |  | 	count := query.Count() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 	for query.Next() { | 
					
						
							| 
									
										
										
										
											2024-02-26 12:56:12 +01:00
										 |  |  | 		_, _, player, _ := query.Get() | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 		if !player.IsPrimary { | 
					
						
							|  |  |  | 			switchable = true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 	if switchable { | 
					
						
							|  |  |  | 		query := system.Selector.Query(system.World) | 
					
						
							|  |  |  | 		for query.Next() { | 
					
						
							| 
									
										
										
										
											2024-02-26 12:56:12 +01:00
										 |  |  | 			_, _, player, render := query.Get() | 
					
						
							| 
									
										
										
										
											2024-02-27 16:52:20 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			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 | 
					
						
							|  |  |  | 				if inpututil.IsKeyJustPressed(ebiten.KeyTab) { | 
					
						
							|  |  |  | 					slog.Debug("switch players") | 
					
						
							|  |  |  | 					if player.IsPrimary { | 
					
						
							|  |  |  | 						player.IsPrimary = false | 
					
						
							|  |  |  | 						render.Image = player.SwitchSprite() | 
					
						
							|  |  |  | 					} else { | 
					
						
							|  |  |  | 						player.IsPrimary = true | 
					
						
							|  |  |  | 						render.Image = player.SwitchSprite() | 
					
						
							|  |  |  | 					} | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2024-02-22 19:40:49 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | func (system *PlayerSystem) CheckMovement( | 
					
						
							|  |  |  | 	position *components.Position, | 
					
						
							|  |  |  | 	velocity *components.Velocity, | 
					
						
							|  |  |  | 	player *components.Player) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	moved := false | 
					
						
							| 
									
										
										
										
											2024-02-28 19:58:05 +01:00
										 |  |  | 	observer := observers.GetGameObserver(system.World) | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 	if !velocity.Moving() { | 
					
						
							|  |  |  | 		switch { | 
					
						
							| 
									
										
										
										
											2024-02-28 13:15:45 +01:00
										 |  |  | 		case inpututil.IsKeyJustPressed(ebiten.KeyRight): | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 			velocity.Change(East) | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 			moved = true | 
					
						
							| 
									
										
										
										
											2024-02-28 13:15:45 +01:00
										 |  |  | 		case inpututil.IsKeyJustPressed(ebiten.KeyLeft): | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 			velocity.Change(West) | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 			moved = true | 
					
						
							| 
									
										
										
										
											2024-02-28 13:15:45 +01:00
										 |  |  | 		case inpututil.IsKeyJustPressed(ebiten.KeyDown): | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 			velocity.Change(South) | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 			moved = true | 
					
						
							| 
									
										
										
										
											2024-02-28 13:15:45 +01:00
										 |  |  | 		case inpututil.IsKeyJustPressed(ebiten.KeyUp): | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 			velocity.Change(North) | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +01:00
										 |  |  | 			moved = true | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if moved { | 
					
						
							|  |  |  | 			// will be reset every time the user initiates player movement | 
					
						
							|  |  |  | 			player.LoopPos.Set(position) | 
					
						
							|  |  |  | 			player.LoopCount = 0 | 
					
						
							| 
									
										
										
										
											2024-02-28 19:58:05 +01:00
										 |  |  | 			observer.AddMove() | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											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) CheckGridCollision( | 
					
						
							|  |  |  | 	playerposition *components.Position, | 
					
						
							|  |  |  | 	velocity *components.Velocity) { | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if velocity.Moving() { | 
					
						
							|  |  |  | 		ok, newpos := system.GridContainer.Grid.BumpEdge(playerposition, velocity) | 
					
						
							|  |  |  | 		if ok { | 
					
						
							|  |  |  | 			//slog.Debug("falling off the edge", "newpos", newpos) | 
					
						
							|  |  |  | 			playerposition.Set(newpos) | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			ok, tilepos := system.GridContainer.Grid.GetSolidNeighborPosition(playerposition, velocity, true) | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 			if ok { | 
					
						
							| 
									
										
										
										
											2024-02-28 19:58:05 +01:00
										 |  |  | 				// slog.Debug("HaveSolidNeighbor", "ok", ok, "tilepos", | 
					
						
							|  |  |  | 				// 	tilepos.Point(), "playerpos", playerposition) | 
					
						
							| 
									
										
										
										
											2024-02-27 17:28:18 +01:00
										 |  |  | 				intersects, newpos := tilepos.Intersects(playerposition, velocity) | 
					
						
							|  |  |  | 				if intersects { | 
					
						
							|  |  |  | 					playerposition.Set(newpos) | 
					
						
							|  |  |  | 					velocity.Change(Stop) | 
					
						
							| 
									
										
										
										
											2024-02-09 20:20:13 +01:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											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-21 12:50:17 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											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
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2024-02-27 18:20:00 +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 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 			// we haved moved one time around the whole screen, loose | 
					
						
							|  |  |  | 			if (player.LoopCount * velocity.Speed) > max { | 
					
						
							|  |  |  | 				EntitiesToRemove = append(EntitiesToRemove, entity) | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return EntitiesToRemove | 
					
						
							|  |  |  | } |