diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index caf2198d..af266cf9 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -47,6 +47,9 @@ 3CAC4A712BB6AB3100A5D22E /* TFLabelNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CAC4A702BB6AB3100A5D22E /* TFLabelNode.swift */; }; 3CAC4A732BB6B61C00A5D22E /* PlayerRenderStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CAC4A722BB6B61C00A5D22E /* PlayerRenderStage.swift */; }; 3CAC4A752BB6BDD500A5D22E /* HealthRenderStage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CAC4A742BB6BDD500A5D22E /* HealthRenderStage.swift */; }; + 3CBE72F92BC8D63500CC446A /* RemoteKillEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBE72F82BC8D63500CC446A /* RemoteKillEvent.swift */; }; + 3CBE72FF2BC8D66C00CC446A /* RemotePowerupEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBE72FE2BC8D66C00CC446A /* RemotePowerupEvent.swift */; }; + 3CBE73012BC8D69A00CC446A /* RemoteLifeEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBE73002BC8D69A00CC446A /* RemoteLifeEvent.swift */; }; 3CBECF892BBE9797005EF39B /* TFNetworkCoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBECF882BBE9797005EF39B /* TFNetworkCoder.swift */; }; 3CBECF8C2BBE9A41005EF39B /* TFRemoteEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBECF8B2BBE9A41005EF39B /* TFRemoteEvent.swift */; }; 3CBECF8E2BBE9EAC005EF39B /* RemoteSpawnEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CBECF8D2BBE9EAC005EF39B /* RemoteSpawnEvent.swift */; }; @@ -288,6 +291,9 @@ 3CAC4A702BB6AB3100A5D22E /* TFLabelNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFLabelNode.swift; sourceTree = ""; }; 3CAC4A722BB6B61C00A5D22E /* PlayerRenderStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerRenderStage.swift; sourceTree = ""; }; 3CAC4A742BB6BDD500A5D22E /* HealthRenderStage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HealthRenderStage.swift; sourceTree = ""; }; + 3CBE72F82BC8D63500CC446A /* RemoteKillEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteKillEvent.swift; sourceTree = ""; }; + 3CBE72FE2BC8D66C00CC446A /* RemotePowerupEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemotePowerupEvent.swift; sourceTree = ""; }; + 3CBE73002BC8D69A00CC446A /* RemoteLifeEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteLifeEvent.swift; sourceTree = ""; }; 3CBECF882BBE9797005EF39B /* TFNetworkCoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFNetworkCoder.swift; sourceTree = ""; }; 3CBECF8B2BBE9A41005EF39B /* TFRemoteEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TFRemoteEvent.swift; sourceTree = ""; }; 3CBECF8D2BBE9EAC005EF39B /* RemoteSpawnEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteSpawnEvent.swift; sourceTree = ""; }; @@ -573,7 +579,10 @@ children = ( 3CBECF8B2BBE9A41005EF39B /* TFRemoteEvent.swift */, 3CBECF8D2BBE9EAC005EF39B /* RemoteSpawnEvent.swift */, + 3CBE72F82BC8D63500CC446A /* RemoteKillEvent.swift */, + 3CBE73002BC8D69A00CC446A /* RemoteLifeEvent.swift */, 3CD37AA62BBEC5EF00222D8A /* BaseRemoteEvent.swift */, + 3CBE72FE2BC8D66C00CC446A /* RemotePowerupEvent.swift */, ); path = RemoteEvents; sourceTree = ""; @@ -1478,6 +1487,7 @@ 5240D0912BAF3453004F1486 /* Life.swift in Sources */, 52A794192BBC630F0083C976 /* RoomData.swift in Sources */, BA82C7632BCBBB2A000515A0 /* Double+Extensions.swift in Sources */, + 3CBE72F92BC8D63500CC446A /* RemoteKillEvent.swift in Sources */, 52DF5FAE2BA32B2300135367 /* GameScene.swift in Sources */, 52578B822BA61AAF00B4D76C /* PositionComponent.swift in Sources */, 9BC60BC82BB9BE6D001A6737 /* DisabledEvent.swift in Sources */, @@ -1571,6 +1581,7 @@ 52A7941B2BBC726E0083C976 /* GameRoomViewController.swift in Sources */, 52DD8F952BC5208900D96BAB /* SurvivalGameMode.swift in Sources */, 527A07862BB411FB00CD9D08 /* GameOverScene.swift in Sources */, + 3CBE72FF2BC8D66C00CC446A /* RemotePowerupEvent.swift in Sources */, 3C9955B12BA4ACA100D33FA5 /* Bullet.swift in Sources */, 3CA829C62BB719A500D8E72A /* ButtonRenderStage.swift in Sources */, 3CE9514F2BAC8936008B2785 /* TFRenderer.swift in Sources */, @@ -1616,6 +1627,7 @@ 3CD37AA32BBEC0F900222D8A /* FirebaseRemoteEventPublisher.swift in Sources */, 3CAC4A6B2BB6992F00A5D22E /* TFNode.swift in Sources */, BA82C7732BCBF657000515A0 /* MetadataManager.swift in Sources */, + 3CBE73012BC8D69A00CC446A /* RemoteLifeEvent.swift in Sources */, 3CAC4A6D2BB6A13B00A5D22E /* PositionRenderStage.swift in Sources */, 3CE951672BAEAB0E008B2785 /* ContactSystem.swift in Sources */, 529190E32BBFB59B001D8821 /* StatePopupNode.swift in Sources */, diff --git a/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift b/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift index a356a84a..19f4d1b8 100644 --- a/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/ConcurrentEvent.swift @@ -9,14 +9,12 @@ import Foundation struct ConcurrentEvent: TFEvent { var timestamp: TimeInterval - var entityId: UUID private let event1: TFEvent private let event2: TFEvent init(_ event1: TFEvent, _ event2: TFEvent) { self.timestamp = event1.timestamp - self.entityId = event1.entityId self.event1 = event1 self.event2 = event2 } diff --git a/TowerForge/TowerForge/GameModule/Events/EventManager.swift b/TowerForge/TowerForge/GameModule/Events/EventManager.swift index 6a732228..a6217a55 100644 --- a/TowerForge/TowerForge/GameModule/Events/EventManager.swift +++ b/TowerForge/TowerForge/GameModule/Events/EventManager.swift @@ -14,8 +14,9 @@ class EventManager { var eventHandler: [TFEventTypeWrapper: [EventHandler]] private(set) var remoteEventManager: RemoteEventManager? private(set) var currentPlayer: GamePlayer? + private(set) var isHost = true - init(roomId: RoomId? = nil, currentPlayer: GamePlayer? = nil) { + init(roomId: RoomId? = nil, isHost: Bool = true, currentPlayer: GamePlayer? = nil) { eventTransformations = [] eventQueue = [] eventHandler = [:] @@ -25,6 +26,7 @@ class EventManager { let subscriber = FirebaseRemoteEventSubscriber(roomId: roomId, eventManager: self) self.remoteEventManager = RemoteEventManager(publisher: publisher, subscriber: subscriber) self.currentPlayer = currentPlayer + self.isHost = isHost } } diff --git a/TowerForge/TowerForge/GameModule/Events/GameEvents/DisabledEvent.swift b/TowerForge/TowerForge/GameModule/Events/GameEvents/DisabledEvent.swift index c30e82d9..0274dfc8 100644 --- a/TowerForge/TowerForge/GameModule/Events/GameEvents/DisabledEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/GameEvents/DisabledEvent.swift @@ -9,10 +9,8 @@ import Foundation struct DisabledEvent: TFEvent { let timestamp: TimeInterval - let entityId: UUID - init(on entityId: UUID = UUID(), at timestamp: TimeInterval = .zero) { - self.entityId = entityId + init(at timestamp: TimeInterval = .zero) { self.timestamp = timestamp } diff --git a/TowerForge/TowerForge/GameModule/Events/GameEvents/LifeEvent.swift b/TowerForge/TowerForge/GameModule/Events/GameEvents/LifeEvent.swift index 3984813d..7f9bc908 100644 --- a/TowerForge/TowerForge/GameModule/Events/GameEvents/LifeEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/GameEvents/LifeEvent.swift @@ -9,13 +9,11 @@ import Foundation struct LifeEvent: TFEvent { let timestamp: TimeInterval - let entityId: UUID let lifeDecrease: Int let player: Player - init(on entityId: UUID, at timestamp: TimeInterval, reduceBy: Int, player: Player) { + init(at timestamp: TimeInterval, reduceBy: Int, player: Player) { self.timestamp = timestamp - self.entityId = entityId self.lifeDecrease = reduceBy self.player = player } diff --git a/TowerForge/TowerForge/GameModule/Events/TFEvent.swift b/TowerForge/TowerForge/GameModule/Events/TFEvent.swift index 2cdb3ee4..7c493bc2 100644 --- a/TowerForge/TowerForge/GameModule/Events/TFEvent.swift +++ b/TowerForge/TowerForge/GameModule/Events/TFEvent.swift @@ -25,7 +25,6 @@ extension TFEventTypeWrapper: Hashable { protocol TFEvent { var timestamp: TimeInterval { get } - var entityId: UUID { get } func execute(in target: EventTarget) -> EventOutput } diff --git a/TowerForge/TowerForge/GameModule/GameEngine.swift b/TowerForge/TowerForge/GameModule/GameEngine.swift index 005029d4..7b89b036 100644 --- a/TowerForge/TowerForge/GameModule/GameEngine.swift +++ b/TowerForge/TowerForge/GameModule/GameEngine.swift @@ -47,10 +47,10 @@ class GameEngine: AbstractGameEngine { init(entityManager: EntityManager = EntityManager(), systemManager: SystemManager = SystemManager(), - roomId: RoomId? = nil, currentPlayer: GamePlayer? = nil) { + roomId: RoomId? = nil, isHost: Bool = true, currentPlayer: GamePlayer? = nil) { self.entityManager = entityManager self.systemManager = systemManager - self.eventManager = EventManager(roomId: roomId, currentPlayer: currentPlayer) + self.eventManager = EventManager(roomId: roomId, isHost: isHost, currentPlayer: currentPlayer) self.setupTeam() self.setupGame() } diff --git a/TowerForge/TowerForge/GameModule/GameWorld.swift b/TowerForge/TowerForge/GameModule/GameWorld.swift index ff3893ea..d49f511e 100644 --- a/TowerForge/TowerForge/GameModule/GameWorld.swift +++ b/TowerForge/TowerForge/GameModule/GameWorld.swift @@ -12,7 +12,6 @@ class GameWorld { // Need to ensure that width is a multiple of 1024 - unit selection node height static let worldSize = CGSize(width: 2_472, height: 1_024) - private unowned var scene: GameScene? private var gameEngine: AbstractGameEngine private var gameMode: GameMode private var selectionNode: UnitSelectionNode @@ -22,20 +21,20 @@ class GameWorld { private let worldBounds: CGRect private var popup: StatePopupNode private var statisticsEngine = StatisticsEngine() + + unowned var scene: GameScene? { didSet { setUpScene() } } unowned var delegate: SceneManagerDelegate? unowned var statePopupDelegate: StatePopupDelegate? { didSet { popup.delegate = statePopupDelegate } } - init(scene: GameScene?, screenSize: CGRect, mode: Mode, - gameRoom: GameRoom? = nil, currentPlayer: GamePlayer? = nil) { - self.scene = scene + init(screenSize: CGRect, mode: Mode, roomId: RoomId? = nil, + isHost: Bool = true, currentPlayer: GamePlayer? = nil) { worldBounds = CGRect(origin: screenSize.origin, size: GameWorld.worldSize) - gameEngine = GameEngine(roomId: gameRoom?.roomId, currentPlayer: currentPlayer) + gameEngine = GameEngine(roomId: roomId, isHost: isHost, currentPlayer: currentPlayer) gameMode = GameModeFactory.createGameMode(mode: mode, eventManager: gameEngine.eventManager) selectionNode = UnitSelectionNode() powerUpSelectionNode = PowerUpSelectionNode(eventManager: gameEngine.eventManager) grid = Grid(screenSize: worldBounds) popup = StatePopupNode() - renderer = TFRenderer(target: self, scene: scene) setUp() } @@ -69,12 +68,12 @@ class GameWorld { } private func setUpScene() { + renderer = TFRenderer(target: self, scene: scene) scene?.setBounds(worldBounds) if let scene = self.scene { grid.generateTileMap(scene: scene) } renderer?.renderMessage("Game Starts") - } private func setUpGameEngine() { diff --git a/TowerForge/TowerForge/GameModule/PowerUps/InvulnerabilityPowerUp.swift b/TowerForge/TowerForge/GameModule/PowerUps/InvulnerabilityPowerUp.swift index 32d368e2..24920d19 100644 --- a/TowerForge/TowerForge/GameModule/PowerUps/InvulnerabilityPowerUp.swift +++ b/TowerForge/TowerForge/GameModule/PowerUps/InvulnerabilityPowerUp.swift @@ -20,6 +20,6 @@ class InvulnerabilityPowerUp: EventTransformation { return event } - return DisabledEvent(on: damageEvent.entityId, at: damageEvent.timestamp) + return DisabledEvent() } } diff --git a/TowerForge/TowerForge/GameModule/Systems/GameSystems/HealthSystem.swift b/TowerForge/TowerForge/GameModule/Systems/GameSystems/HealthSystem.swift index 15bb92bd..70d2f6e8 100644 --- a/TowerForge/TowerForge/GameModule/Systems/GameSystems/HealthSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/GameSystems/HealthSystem.swift @@ -27,8 +27,18 @@ class HealthSystem: TFSystem { healthComponent.adjustHealth(amount: hp) - if healthComponent.currentHealth <= 0 { + guard healthComponent.currentHealth <= 0 else { + return + } + + guard let currentPlayer = eventManager.currentPlayer else { eventManager.add(KillEvent(on: currentEntity.id, at: CACurrentMediaTime(), player: playerComponent.player)) + return + } + + if eventManager.isHost { + let remoteRemoveEvent = RemoteKillEvent(id: entityId, player: playerComponent.player, source: currentPlayer) + eventManager.add(remoteRemoveEvent) } } } diff --git a/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift b/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift index c1f1eda1..d25fb533 100644 --- a/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/GameSystems/PositionSystem.swift @@ -38,11 +38,19 @@ class PositionSystem: TFSystem { guard let playerComponent = entity.component(ofType: PlayerComponent.self) else { return } - // TODO: Might need some change - if entity is BaseUnit { - eventManager.add(LifeEvent(on: entity.id, at: CACurrentMediaTime(), - reduceBy: 1, player: playerComponent.player)) - } + eventManager.add(RemoveEvent(on: entity.id, at: CACurrentMediaTime())) + + guard entity is BaseUnit else { + return + } + + guard let player = eventManager.currentPlayer else { + return eventManager.add(LifeEvent(at: CACurrentMediaTime(), reduceBy: 1, player: playerComponent.player)) + } + + if eventManager.isHost { + eventManager.add(RemoteLifeEvent(reduceBy: 1, player: playerComponent.player, source: player)) + } } } diff --git a/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift b/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift index e1ed045b..eeac0189 100644 --- a/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift @@ -69,7 +69,7 @@ class HomeSystem: TFSystem { return eventManager.add(spawnEvent) } - // Report spawn to which will propogate to all players evenly + // Report spawn to network which will propogate to all players evenly let spawnEvent = RemoteSpawnEvent(ofType: type, location: snapPosition, player: currentPlayer) eventManager.add(spawnEvent) } diff --git a/TowerForge/TowerForge/GameViewController.swift b/TowerForge/TowerForge/GameViewController.swift index 903206dd..27fa9bf3 100644 --- a/TowerForge/TowerForge/GameViewController.swift +++ b/TowerForge/TowerForge/GameViewController.swift @@ -13,7 +13,8 @@ class GameViewController: UIViewController { private var gameRankProvider: GameRankProvider? var gameMode: Mode? var isPaused = false - var gameRoom: GameRoom? + var roomId: RoomId? + var isHost = true var currentPlayer: GamePlayer? @IBOutlet private var gamePopupButton: UIButton! @@ -24,6 +25,8 @@ class GameViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() + self.navigationController?.isNavigationBarHidden = true + AchievementManager.incrementTotalGamesStarted() AudioManager.shared.playBackground() showGameLevelScene() @@ -38,6 +41,7 @@ class GameViewController: UIViewController { override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) + self.navigationController?.isNavigationBarHidden = false AudioManager.shared.pauseBackground() gameWorld = nil } @@ -55,9 +59,10 @@ class GameViewController: UIViewController { } private func setUpGameWorld(scene: GameScene) { - self.gameWorld = GameWorld(scene: scene, screenSize: self.view.frame, + self.gameWorld = GameWorld(screenSize: self.view.frame, mode: self.gameMode ?? .captureTheFlag, - gameRoom: gameRoom, currentPlayer: currentPlayer) + roomId: roomId, isHost: isHost, currentPlayer: currentPlayer) + self.gameWorld?.scene = scene self.gameWorld?.delegate = self self.gameWorld?.statePopupDelegate = self } @@ -75,13 +80,14 @@ extension GameViewController: SceneUpdateDelegate { extension GameViewController: SceneManagerDelegate { func showMenuScene() { - guard let mainMenuViewController = - self.storyboard?.instantiateViewController(withIdentifier: "MainMenuViewController") - as? MainMenuViewController else { - return - } - - self.present(mainMenuViewController, animated: true, completion: nil) + if let targetVC = navigationController?.viewControllers.first(where: { $0 is MainMenuViewController }) { + navigationController?.popToViewController(targetVC, animated: true) + return + } + if let targetVC = storyboard? + .instantiateViewController(withIdentifier: "MainMenuViewController") as? MainMenuViewController { + present(targetVC, animated: true) + } } func showLevelScene() { // TODO : to implement after Keith is done diff --git a/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteKillEvent.swift b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteKillEvent.swift new file mode 100644 index 00000000..81db0f18 --- /dev/null +++ b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteKillEvent.swift @@ -0,0 +1,32 @@ +// +// RemoteKillEvent.swift +// TowerForge +// +// Created by Zheng Ze on 12/4/24. +// + +import Foundation + +class RemoteKillEvent: TFRemoteEvent { + let type: String + let timeStamp: TimeInterval + let source: UserPlayerId + + let id: UUID + let targetIsSource: Bool + + init(id: UUID, player: Player, source: GamePlayer) { + self.type = String(describing: RemoteKillEvent.self) + self.timeStamp = Date().timeIntervalSince1970 + self.source = source.userPlayerId + + self.id = id + self.targetIsSource = player == .ownPlayer + } + + func unpack(into eventManager: EventManager, for gamePlayer: UserPlayerId) { + let player: Player = (gamePlayer == source) == targetIsSource ? .ownPlayer : .oppositePlayer // XOR + let killEvent = KillEvent(on: id, at: timeStamp, player: player) + eventManager.add(killEvent) + } +} diff --git a/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteLifeEvent.swift b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteLifeEvent.swift new file mode 100644 index 00000000..9dc9b589 --- /dev/null +++ b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteLifeEvent.swift @@ -0,0 +1,32 @@ +// +// RemoteLifeEvent.swift +// TowerForge +// +// Created by Zheng Ze on 12/4/24. +// + +import Foundation + +class RemoteLifeEvent: TFRemoteEvent { + let type: String + let timeStamp: TimeInterval + let source: UserPlayerId + + let reduceBy: Int + let targetIsSource: Bool + + init(reduceBy: Int, player: Player, source: GamePlayer) { + self.type = String(describing: RemoteLifeEvent.self) + self.timeStamp = Date().timeIntervalSince1970 + self.source = source.userPlayerId + + self.reduceBy = reduceBy + self.targetIsSource = player == .ownPlayer + } + + func unpack(into eventManager: EventManager, for gamePlayer: UserPlayerId) { + let player: Player = (gamePlayer == source) == targetIsSource ? .ownPlayer : .oppositePlayer + let lifeEvent = LifeEvent(at: timeStamp, reduceBy: reduceBy, player: player) + eventManager.add(lifeEvent) + } +} diff --git a/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemotePowerupEvent.swift b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemotePowerupEvent.swift new file mode 100644 index 00000000..72331390 --- /dev/null +++ b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemotePowerupEvent.swift @@ -0,0 +1,35 @@ +// +// RemotePowerupEvent.swift +// TowerForge +// +// Created by Zheng Ze on 12/4/24. +// + +import Foundation + +class RemotePowerupEvent: TFRemoteEvent { + let type: String + let timeStamp: TimeInterval + let source: UserPlayerId + + let powerup: String + let targetIsSource: Bool + + init(powerup: PowerUp, player: Player, source: GamePlayer) { + self.type = String(describing: RemotePowerupEvent.self) + self.timeStamp = Date().timeIntervalSince1970 + self.source = source.userPlayerId + + self.powerup = powerup.rawValue + self.targetIsSource = player == .ownPlayer + } + + func unpack(into eventManager: EventManager, for gamePlayer: UserPlayerId) { + guard let type = Bundle.main.classNamed("TowerForge.\(powerup)PowerUp") as? any EventTransformation.Type else { + return + } + let player: Player = (gamePlayer == source) == targetIsSource ? .ownPlayer : .oppositePlayer +// let powerup = type.init(player: player) +// eventManager.addTransformation(eventTransformation: powerup) + } +} diff --git a/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteSpawnEvent.swift b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteSpawnEvent.swift index 3fac1bea..4c8dc7bb 100644 --- a/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteSpawnEvent.swift +++ b/TowerForge/TowerForge/Networking/GameNetwork/RemoteEvents/RemoteSpawnEvent.swift @@ -19,7 +19,7 @@ class RemoteSpawnEvent: TFRemoteEvent { init(ofType type: T.Type, location: CGPoint, player: GamePlayer) { self.type = String(describing: RemoteSpawnEvent.self) self.timeStamp = Date().timeIntervalSince1970 - self.source = player.userPlayerId ?? "" + self.source = player.userPlayerId self.spawnType = String(describing: type) self.spawnLocation = location diff --git a/TowerForge/TowerForge/Networking/GamePlayer.swift b/TowerForge/TowerForge/Networking/GamePlayer.swift index 4ef1c7e2..94f4a8b6 100644 --- a/TowerForge/TowerForge/Networking/GamePlayer.swift +++ b/TowerForge/TowerForge/Networking/GamePlayer.swift @@ -8,10 +8,10 @@ import Foundation class GamePlayer: Codable { - var userPlayerId: UserPlayerId? + var userPlayerId: UserPlayerId let userName: String - init(userPlayerId: UserPlayerId? = nil, userName: String) { + init(userPlayerId: UserPlayerId = UUID().uuidString, userName: String) { self.userName = userName self.userPlayerId = userPlayerId } diff --git a/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift b/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift index 9ba26f76..5fa62836 100644 --- a/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift +++ b/TowerForge/TowerForge/Networking/RoomNetwork/GameRoom.swift @@ -12,6 +12,7 @@ class GameRoom { private(set) var roomName: String private(set) var roomState: RoomState? private(set) var roomId: RoomId? + private(set) var host: UserPlayerId? private(set) var playerOne: GamePlayer? private(set) var playerTwo: GamePlayer? @@ -40,6 +41,7 @@ class GameRoom { // If the room name is available, try to join the room if isAvailable { self.roomId = id + self.makeRoomChangeListener() completion(true) } else { completion(false) @@ -54,6 +56,7 @@ class GameRoom { } deinit { + print("deinit") removeAllListeners() } @@ -73,25 +76,37 @@ class GameRoom { } func joinRoom(player: GamePlayer, completion: @escaping (Bool) -> Void) { - self.isRoomFull(roomName) { isFull in + let delegate = { (isFull: Bool) -> Void in if isFull { completion(false) + return } - } - if playerOne == nil { - playerOne = player - } else { - playerTwo = player - } - let playerRef = roomRef.child(roomName).child("players").childByAutoId() - playerRef.setValue(player.userName) - player.userPlayerId = playerRef.key - self.isRoomFull(roomName) { isFull in - if isFull { - self.updateRoomState(roomState: .waitingForBothConfirmation) + if self.playerOne == nil { + self.playerOne = player + } else { + self.playerTwo = player } + + let onAddCompleted = { (error: Error?, _: DatabaseReference) -> Void in + if let error = error { + print("Error getting data: \(error.localizedDescription)") + completion(false) + return + } + self.attemptSetHost(player: player) + self.isRoomFull(self.roomName) { isFull in + if isFull { + self.updateRoomState(roomState: .waitingForBothConfirmation) + } + completion(true) // Successfully joined the room + } + } + + let playerRef = self.roomRef.child(self.roomName).child("players").child(player.userPlayerId) + playerRef.setValue(player.userName, withCompletionBlock: onAddCompleted) } - completion(true) // Successfully joined the room + + self.isRoomFull(roomName, completion: delegate) } // Logic to start the game when players are ready @@ -134,36 +149,41 @@ class GameRoom { } func leaveRoom(player: GamePlayer, completion: @escaping (Bool) -> Void) { - let roomPlayersRef = roomRef.child(roomName).child("players") + let roomDataRef = roomRef.child(roomName) + let delegate = { (snapshot: DataSnapshot) -> Void in + let playerSnapshot = snapshot.childSnapshot(forPath: "players") + let hostSnapshot = snapshot.childSnapshot(forPath: "host") + guard let playerData = playerSnapshot.value as? [String: Any] else { + completion(false) + return + } - roomPlayersRef.observeSingleEvent(of: .value) { snapshot, _ in - if let playerData = snapshot.value as? [String: Any] { - // Check if the leaving player is in the room - guard let playerKey = playerData.first(where: { $0.key == player.userPlayerId })?.key else { - // The player is not in the room, cannot leave - completion(false) - return - } + // Check if the leaving player is in the room + guard let playerKey = playerData.first(where: { $0.key == player.userPlayerId })?.key else { + // The player is not in the room, cannot leave + completion(false) + return + } - // Remove the player from the room in the database - roomPlayersRef.child(playerKey).removeValue() + // Remove the player from the room in the database + playerSnapshot.ref.child(playerKey).removeValue() - // Update local player data - if self.playerOne?.userPlayerId == player.userPlayerId { - self.playerOne = nil - } else if self.playerTwo?.userPlayerId == player.userPlayerId { - self.playerTwo = nil - } - if playerData.count == 1 { - // Delete the room from the database - self.roomRef.child(self.roomName).removeValue() - } + guard playerData.count > 1 else { + // Delete the room from the database + roomDataRef.removeValue() completion(true) // Successfully left the room - } else { - // No player data found in the room, cannot leave - completion(false) + return } + + // Set other player as host if leaving player was host + if let host = hostSnapshot.value as? String, host == player.userPlayerId, + let otherPlayerId = playerData.first(where: { $0.key != player.userPlayerId })?.key { + roomDataRef.updateChildValues(["host": otherPlayerId]) + } + completion(true) } + + roomDataRef.observeSingleEvent(of: .value, with: delegate) } func getPlayerNum(gamePlayer: GamePlayer) -> Player? { @@ -177,15 +197,16 @@ class GameRoom { } private func isRoomFull(_ roomName: String, completion: @escaping (Bool) -> Void) { - let roomRef = FirebaseDatabaseReference(.Rooms).child(roomName).child("players") - roomRef.getData { error, snapshot in + let playerRef = FirebaseDatabaseReference(.Rooms).child(roomName).child("players") + playerRef.getData { error, snapshot in if let error = error { print("Error getting data: \(error.localizedDescription)") completion(false) return } - guard let snap = snapshot, snap.exists() else { + // Need to access child snapshot here else it captures roomId and roomState and not just the childs + guard let snap = snapshot?.childSnapshot(forPath: "players"), snap.exists() else { completion(false) return } @@ -222,6 +243,8 @@ class GameRoom { return } if let playersData = snapshotValue["players"] as? [String: Any] { + self?.playerOne = nil + self?.playerTwo = nil for (playerKey, playerData) in playersData { guard let playerData = playerData as? String else { continue @@ -237,26 +260,10 @@ class GameRoom { if let roomStateData = snapshotValue["roomState"] as? String { self?.roomState = RoomState.fromString(roomStateData) } - self?.gameRoomDelegate?.onRoomChange() - } - } - - private func makeRoomDeletionListener() { - roomRef.child(roomName).child("players").observe(.childRemoved) { [weak self] snapshot in - let playerKey = snapshot.key - // Check if the player is playerOne - if let playerOne = self?.playerOne, playerOne.userPlayerId == playerKey { - self?.playerOne = nil - self?.gameRoomDelegate?.onRoomChange() - return - } - - // Check if the player is playerTwo - if let playerTwo = self?.playerTwo, playerTwo.userPlayerId == playerKey { - self?.playerTwo = nil - self?.gameRoomDelegate?.onRoomChange() - return + if let hostData = snapshotValue["host"] as? UserPlayerId { + self?.host = hostData } + self?.gameRoomDelegate?.onRoomChange() } } @@ -270,8 +277,6 @@ class GameRoom { } self.postRoomDataToFirebase { _, id in - self.makeRoomChangeListener() - self.makeRoomDeletionListener() completion(true, id) } } @@ -281,4 +286,20 @@ class GameRoom { private func removeAllListeners() { roomRef.child(roomName).removeAllObservers() } + + private func attemptSetHost(player: GamePlayer) { + let delegate = { (error: Error?, snapshot: DataSnapshot?) -> Void in + if let error = error { + print("Error getting data: \(error.localizedDescription)") + return + } + + guard let snap = snapshot, !snap.childSnapshot(forPath: "host").exists() else { + return + } + + snap.ref.updateChildValues(["host": player.userPlayerId]) + } + FirebaseDatabaseReference(.Rooms).child(roomName).getData(completion: delegate) + } } diff --git a/TowerForge/TowerForge/Nodes/PowerUpNode.swift b/TowerForge/TowerForge/Nodes/PowerUpNode.swift index 9120fce6..660d8887 100644 --- a/TowerForge/TowerForge/Nodes/PowerUpNode.swift +++ b/TowerForge/TowerForge/Nodes/PowerUpNode.swift @@ -7,7 +7,7 @@ import SpriteKit -enum PowerUp { +enum PowerUp: String { case invulnerability var imageName: String { diff --git a/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift b/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift index 7ee3b3ea..134fd701 100644 --- a/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift +++ b/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift @@ -17,10 +17,8 @@ class PlayerRenderStage: RenderStage { func render() { let entities = renderer.target.entities(with: PlayerComponent.self) - for entity in entities { - if !renderedNodes.contains(entity.id) { - transform(for: entity) - } + for entity in entities where !renderedNodes.contains(entity.id) { + transform(for: entity) } } diff --git a/TowerForge/TowerForge/ViewControllers/GameWaitingRoomViewController.swift b/TowerForge/TowerForge/ViewControllers/GameWaitingRoomViewController.swift index 543dd948..bef43812 100644 --- a/TowerForge/TowerForge/ViewControllers/GameWaitingRoomViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/GameWaitingRoomViewController.swift @@ -26,6 +26,15 @@ class GameWaitingRoomViewController: UIViewController { updatePlayerList() } + override func prepare(for segue: UIStoryboardSegue, sender: Any?) { + if let destVC = segue.destination as? GameViewController { + destVC.currentPlayer = currentPlayer + destVC.roomId = gameRoom?.roomId + destVC.isHost = currentPlayer?.userPlayerId == gameRoom?.host + gameRoom?.deleteRoom() + } + } + @IBAction private func onStartButtonPressed(_ sender: Any) { gameRoom?.updatePlayerReady { _ in self.startButton.isHidden = true @@ -61,14 +70,7 @@ class GameWaitingRoomViewController: UIViewController { } if gameRoom?.roomState == .gameOnGoing { - guard let gameViewController = storyboard?.instantiateViewController(withIdentifier: "GameViewController") - as? GameViewController else { - return - } - gameViewController.currentPlayer = currentPlayer - gameViewController.gameRoom = gameRoom - self.present(gameViewController, animated: true) - gameRoom?.deleteRoom() + performSegue(withIdentifier: "segueToGame", sender: self) } } diff --git a/TowerForge/TowerForge/ViewControllers/MainMenuViewController.swift b/TowerForge/TowerForge/ViewControllers/MainMenuViewController.swift index 7ff3d400..58acddf0 100644 --- a/TowerForge/TowerForge/ViewControllers/MainMenuViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/MainMenuViewController.swift @@ -11,6 +11,7 @@ import UIKit class MainMenuViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + self.navigationController?.isNavigationBarHidden = false AudioManager.shared.playMainMusic() } }