diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 47a4b966..b6dbedca 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -39,6 +39,7 @@ 3CE951632BAE037C008B2785 /* AiSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951622BAE037C008B2785 /* AiSystem.swift */; }; 3CE951652BAE0A04008B2785 /* HomeSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951642BAE0A04008B2785 /* HomeSystem.swift */; }; 3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951662BAEAB0E008B2785 /* ContactSystem.swift */; }; + 3CE951692BAEB719008B2785 /* RequestSpawnEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */; }; 5200624E2BA8D597000DBA30 /* AiComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5200624D2BA8D597000DBA30 /* AiComponent.swift */; }; 520062522BA8DA09000DBA30 /* UnitGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520062512BA8DA09000DBA30 /* UnitGenerator.swift */; }; 520062562BA8E026000DBA30 /* PlayerSpawnable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520062552BA8E026000DBA30 /* PlayerSpawnable.swift */; }; @@ -134,6 +135,7 @@ 3CE951622BAE037C008B2785 /* AiSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AiSystem.swift; sourceTree = ""; }; 3CE951642BAE0A04008B2785 /* HomeSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeSystem.swift; sourceTree = ""; }; 3CE951662BAEAB0E008B2785 /* ContactSystem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSystem.swift; sourceTree = ""; }; + 3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestSpawnEvent.swift; sourceTree = ""; }; 5200624D2BA8D597000DBA30 /* AiComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AiComponent.swift; sourceTree = ""; }; 520062512BA8DA09000DBA30 /* UnitGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnitGenerator.swift; sourceTree = ""; }; 520062552BA8E026000DBA30 /* PlayerSpawnable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerSpawnable.swift; sourceTree = ""; }; @@ -258,6 +260,7 @@ 3C9955CB2BA5889800D33FA5 /* MoveEvent.swift */, 3C9955C72BA5865C00D33FA5 /* ConcurrentEvent.swift */, BA443D3E2BAD9774009F0FFB /* RemoveEvent.swift */, + 3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */, ); path = "Implemented Events"; sourceTree = ""; @@ -705,6 +708,7 @@ BA443D3F2BAD9774009F0FFB /* RemoveEvent.swift in Sources */, 5295A2072BAA02FD005018A8 /* TFButton.swift in Sources */, 3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */, + 3CE951692BAEB719008B2785 /* RequestSpawnEvent.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TowerForge/TowerForge/GameWorld.swift b/TowerForge/TowerForge/GameWorld.swift index 21b56d77..23f7900f 100644 --- a/TowerForge/TowerForge/GameWorld.swift +++ b/TowerForge/TowerForge/GameWorld.swift @@ -22,7 +22,7 @@ class GameWorld { systemManager = SystemManager() eventManager = EventManager() selectionNode = UnitSelectionNode() - grid = Grid(entityManager: entityManager, screenSize: screenSize) + grid = Grid(eventManager: eventManager, screenSize: screenSize) if let scene = self.scene { grid.generateTileMap(scene: scene) } @@ -42,6 +42,7 @@ class GameWorld { func spawnUnit(at location: CGPoint) { selectionNode.unitNodeDidSpawn(location) } + func setupTeam() { let ownTeam = Team(player: .ownPlayer) let oppositeTeam = Team(player: .oppositePlayer) @@ -63,9 +64,12 @@ class GameWorld { systemManager.add(system: RemoveSystem(entityManager: entityManager, eventManager: eventManager)) systemManager.add(system: SpawnSystem(entityManager: entityManager, eventManager: eventManager)) systemManager.add(system: ShootingSystem(entityManager: entityManager, eventManager: eventManager)) - systemManager.add(system: HomeSystem(entityManager: entityManager)) + systemManager.add(system: HomeSystem(entityManager: entityManager, eventManager: eventManager)) systemManager.add(system: AiSystem(entityManager: entityManager, eventManager: eventManager)) systemManager.add(system: ContactSystem(entityManager: entityManager, eventManager: eventManager)) + + // Temporary until we have different gamemodes + systemManager.system(ofType: AiSystem.self)?.aiPlayers.append(.oppositePlayer) } private func setUpSelectionNode() { diff --git a/TowerForge/TowerForge/Grid.swift b/TowerForge/TowerForge/Grid.swift index c7e74ef9..852e3913 100644 --- a/TowerForge/TowerForge/Grid.swift +++ b/TowerForge/TowerForge/Grid.swift @@ -10,13 +10,13 @@ import SpriteKit class Grid: UnitSelectionNodeDelegate { let DEFAULT_NO_OF_ROWS = 8 - private var entityManager: EntityManager + private var eventManager: EventManager private var noOfRows: Int private var width: CGFloat private var height: CGFloat - init(entityManager: EntityManager, screenSize: CGRect) { - self.entityManager = entityManager + init(eventManager: EventManager, screenSize: CGRect) { + self.eventManager = eventManager self.noOfRows = DEFAULT_NO_OF_ROWS self.width = screenSize.width self.height = screenSize.height @@ -24,8 +24,8 @@ class Grid: UnitSelectionNodeDelegate { func unitSelectionNodeDidSpawn(ofType type: T.Type, position: CGPoint) { let snapPosition = CGPoint(x: position.x, y: snapYPosition(yPosition: position.y)) - let unit = UnitGenerator.spawn(ofType: type, at: snapPosition, player: Player.ownPlayer) - entityManager.add(unit) + eventManager.add(RequestSpawnEvent(ofType: type, timestamp: CACurrentMediaTime(), + position: snapPosition, player: .ownPlayer)) } func generateTileMap(scene: SKScene) { diff --git a/TowerForge/TowerForge/LevelManager/Components/AiComponent.swift b/TowerForge/TowerForge/LevelManager/Components/AiComponent.swift index cff3ad83..5dbe87d5 100644 --- a/TowerForge/TowerForge/LevelManager/Components/AiComponent.swift +++ b/TowerForge/TowerForge/LevelManager/Components/AiComponent.swift @@ -9,28 +9,38 @@ import Foundation import UIKit class AiComponent: TFComponent { - private var chosenUnit: (TFEntity & PlayerSpawnable).Type? + private var spawnableEntities: [(TFEntity & PlayerSpawnable).Type] + private(set) var delayUnitNextSpawn: TimeInterval - override init() { - self.chosenUnit = SpawnableEntities.playerSpawnableEntities.randomElement() - super.init() + var chosenUnit: (TFEntity & PlayerSpawnable).Type? { + spawnableEntities.randomElement() } - func spawn() -> TFEvent? { - guard let homeComponent = entity?.component(ofType: HomeComponent.self), - let chosenUnit = chosenUnit else { - return nil - } + var spawnLocation: CGPoint { let randomY = CGFloat.random(in: 0...UIScreen.main.bounds.height) + return CGPoint(x: UIScreen.main.bounds.width, y: randomY) + } + + init(spawnableEntities: [(TFEntity & PlayerSpawnable).Type] = SpawnableEntities.playerSpawnableEntities) { + self.spawnableEntities = spawnableEntities + self.delayUnitNextSpawn = AiComponent.getNextInterval() + super.init() + } + + override func update(deltaTime: TimeInterval) { + delayUnitNextSpawn -= deltaTime + } - guard homeComponent.points >= chosenUnit.cost else { - return nil + func spawn() -> Bool { + guard delayUnitNextSpawn <= 0 else { + return false } - homeComponent.decreasePoints(chosenUnit.cost) - // Re-randomize the unit - self.chosenUnit = SpawnableEntities.playerSpawnableEntities.randomElement() - return SpawnEvent(ofType: chosenUnit, timestamp: CACurrentMediaTime(), - position: CGPoint(x: UIScreen.main.bounds.width, y: randomY), player: .oppositePlayer) + delayUnitNextSpawn = AiComponent.getNextInterval() + return true + } + + private static func getNextInterval() -> TimeInterval { + Double.random(in: 1...10) } } diff --git a/TowerForge/TowerForge/LevelManager/Events/Implemented Events/MoveEvent.swift b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/MoveEvent.swift index 408939bb..a603d5ae 100644 --- a/TowerForge/TowerForge/LevelManager/Events/Implemented Events/MoveEvent.swift +++ b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/MoveEvent.swift @@ -16,6 +16,6 @@ struct MoveEvent: TFEvent { return nil } movementSystem.handleMovement(for: entityId, with: displacement) - return EventOutput() + return nil } } diff --git a/TowerForge/TowerForge/LevelManager/Events/Implemented Events/RemoveEvent.swift b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/RemoveEvent.swift index 2de0101b..2193ca18 100644 --- a/TowerForge/TowerForge/LevelManager/Events/Implemented Events/RemoveEvent.swift +++ b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/RemoveEvent.swift @@ -15,6 +15,6 @@ struct RemoveEvent: TFEvent { } removeSystem.handleRemove(for: entityId) - return EventOutput() + return nil } } diff --git a/TowerForge/TowerForge/LevelManager/Events/Implemented Events/RequestSpawnEvent.swift b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/RequestSpawnEvent.swift new file mode 100644 index 00000000..76b100d8 --- /dev/null +++ b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/RequestSpawnEvent.swift @@ -0,0 +1,35 @@ +// +// RequestSpawnEvent.swift +// TowerForge +// +// Created by Zheng Ze on 23/3/24. +// + +import Foundation + +struct RequestSpawnEvent: TFEvent { + let timestamp: TimeInterval + let position: CGPoint + let entityType: (PlayerSpawnable & TFEntity).Type + let player: Player + + var entityId: UUID { + fatalError("entityId is not to be used here") + } + + init(ofType type: T.Type, timestamp: TimeInterval, + position: CGPoint, player: Player) { + self.timestamp = timestamp + self.position = position + self.entityType = type + self.player = player + } + + func execute(in target: any EventTarget) -> EventOutput? { + guard let homeSystem = target.system(ofType: HomeSystem.self) else { + return nil + } + homeSystem.attemptSpawn(at: position, ofType: entityType, for: player) + return nil + } +} diff --git a/TowerForge/TowerForge/LevelManager/Events/Implemented Events/SpawnEvent.swift b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/SpawnEvent.swift index 772cf97b..fb2024bc 100644 --- a/TowerForge/TowerForge/LevelManager/Events/Implemented Events/SpawnEvent.swift +++ b/TowerForge/TowerForge/LevelManager/Events/Implemented Events/SpawnEvent.swift @@ -16,6 +16,6 @@ struct SpawnEvent: TFEvent { return nil } spawnSystem.handleSpawn(with: entity) - return EventOutput() + return nil } } diff --git a/TowerForge/TowerForge/LevelManager/Systems/AiSystem.swift b/TowerForge/TowerForge/LevelManager/Systems/AiSystem.swift index 1aeb0510..4064eb79 100644 --- a/TowerForge/TowerForge/LevelManager/Systems/AiSystem.swift +++ b/TowerForge/TowerForge/LevelManager/Systems/AiSystem.swift @@ -5,12 +5,13 @@ // Created by Zheng Ze on 23/3/24. // -import Foundation +import SpriteKit class AiSystem: TFSystem { var isActive = true weak var entityManager: EntityManager? weak var eventManager: EventManager? + var aiPlayers: [Player] = [] init(entityManager: EntityManager, eventManager: EventManager) { self.entityManager = entityManager @@ -22,12 +23,36 @@ class AiSystem: TFSystem { return } - let aiComponents = entityManager.components(ofType: AiComponent.self) - for aiComponent in aiComponents { - guard let event = aiComponent.spawn() else { + var aiComponents: [Player: AiComponent] = [:] + for aiComponent in entityManager.components(ofType: AiComponent.self) { + guard let player = aiComponent.entity?.component(ofType: PlayerComponent.self)?.player else { continue } - eventManager.add(event) + aiComponents[player] = aiComponent + aiComponent.update(deltaTime: time) } + + var event: TFEvent? + + for aiPlayer in aiPlayers { + guard let aiComponent = aiComponents[aiPlayer], aiComponent.spawn(), + let unitType = aiComponent.chosenUnit else { + continue + } + + let newEvent = RequestSpawnEvent(ofType: unitType, timestamp: CACurrentMediaTime(), + position: aiComponent.spawnLocation, player: aiPlayer) + + guard var event = event else { + event = newEvent + continue + } + event = event.concurrentlyWith(newEvent) + } + + guard let event = event else { + return + } + eventManager.add(event) } } diff --git a/TowerForge/TowerForge/LevelManager/Systems/HomeSystem.swift b/TowerForge/TowerForge/LevelManager/Systems/HomeSystem.swift index 0e3a7af3..9a0dde28 100644 --- a/TowerForge/TowerForge/LevelManager/Systems/HomeSystem.swift +++ b/TowerForge/TowerForge/LevelManager/Systems/HomeSystem.swift @@ -6,14 +6,16 @@ // import Foundation +import SpriteKit class HomeSystem: TFSystem { var isActive = true weak var entityManager: EntityManager? weak var eventManager: EventManager? - init(entityManager: EntityManager) { + init(entityManager: EntityManager, eventManager: EventManager) { self.entityManager = entityManager + self.eventManager = eventManager } func update(within time: CGFloat) { @@ -26,4 +28,28 @@ class HomeSystem: TFSystem { homeComponent.update(deltaTime: time) } } + + func attemptSpawn(at position: CGPoint, ofType type: T.Type, for player: Player) { + guard let entityManager = entityManager else { + return + } + + // Get HomeComponent for the player + let playerHomeComponentArr = entityManager.components(ofType: HomeComponent.self).filter(({ + $0.entity?.component(ofType: PlayerComponent.self)?.player == player + })) + guard !playerHomeComponentArr.isEmpty else { + return + } + let playerHomeComponent = playerHomeComponentArr[0] + + // Check if they have enough points to spawn + guard playerHomeComponent.points >= type.cost else { + return + } + + playerHomeComponent.decreasePoints(type.cost) + let spawnEvent = SpawnEvent(ofType: type, timestamp: CACurrentMediaTime(), position: position, player: player) + eventManager?.add(spawnEvent) + } }