diff --git a/TowerForge/TowerForge.xcodeproj/project.pbxproj b/TowerForge/TowerForge.xcodeproj/project.pbxproj index 3b302a95..f71f7d3b 100644 --- a/TowerForge/TowerForge.xcodeproj/project.pbxproj +++ b/TowerForge/TowerForge.xcodeproj/project.pbxproj @@ -84,6 +84,7 @@ 523C29272BBCFA13004C6EAC /* FirebaseDatabaseSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 523C29262BBCFA13004C6EAC /* FirebaseDatabaseSwift */; }; 523C29282BBCFD93004C6EAC /* Nosifer-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 5240D0952BB04E57004F1486 /* Nosifer-Regular.ttf */; }; 523C29302BBD0916004C6EAC /* GameWaitingRoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523C292F2BBD0916004C6EAC /* GameWaitingRoomViewController.swift */; }; + 523E5C4C2BC53F70007444DA /* WaveSpawnEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 523E5C4B2BC53F70007444DA /* WaveSpawnEvent.swift */; }; 5240D08F2BAE6D0A004F1486 /* Point.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5240D08E2BAE6D0A004F1486 /* Point.swift */; }; 5240D0912BAF3453004F1486 /* Life.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5240D0902BAF3453004F1486 /* Life.swift */; }; 5240D0A02BB330B5004F1486 /* GameMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5240D09F2BB330B4004F1486 /* GameMode.swift */; }; @@ -137,6 +138,8 @@ 52A794172BBC4F690083C976 /* GamePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A794162BBC4F690083C976 /* GamePlayer.swift */; }; 52A794192BBC630F0083C976 /* RoomData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A794182BBC630F0083C976 /* RoomData.swift */; }; 52A7941B2BBC726E0083C976 /* GameRoomViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7941A2BBC726E0083C976 /* GameRoomViewController.swift */; }; + 52DD8F952BC5208900D96BAB /* SurvivalGameMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD8F942BC5208800D96BAB /* SurvivalGameMode.swift */; }; + 52DD8F992BC52F8500D96BAB /* LevelPopupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DD8F982BC52F8400D96BAB /* LevelPopupViewController.swift */; }; 52DF5FA82BA32B2300135367 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52DF5FA72BA32B2300135367 /* AppDelegate.swift */; }; 52DF5FAA2BA32B2300135367 /* GameScene.sks in Resources */ = {isa = PBXBuildFile; fileRef = 52DF5FA92BA32B2300135367 /* GameScene.sks */; }; 52DF5FAC2BA32B2300135367 /* Actions.sks in Resources */ = {isa = PBXBuildFile; fileRef = 52DF5FAB2BA32B2300135367 /* Actions.sks */; }; @@ -294,6 +297,7 @@ 523AA3BC2BB88ED10041E60D /* WizardBall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardBall.swift; sourceTree = ""; }; 523AA3BE2BB88FBF0041E60D /* WizardUnit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WizardUnit.swift; sourceTree = ""; }; 523C292F2BBD0916004C6EAC /* GameWaitingRoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameWaitingRoomViewController.swift; sourceTree = ""; }; + 523E5C4B2BC53F70007444DA /* WaveSpawnEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaveSpawnEvent.swift; sourceTree = ""; }; 5240D08E2BAE6D0A004F1486 /* Point.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Point.swift; sourceTree = ""; }; 5240D0902BAF3453004F1486 /* Life.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Life.swift; sourceTree = ""; }; 5240D0952BB04E57004F1486 /* Nosifer-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Nosifer-Regular.ttf"; sourceTree = ""; }; @@ -346,6 +350,8 @@ 52A794162BBC4F690083C976 /* GamePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GamePlayer.swift; sourceTree = ""; }; 52A794182BBC630F0083C976 /* RoomData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomData.swift; sourceTree = ""; }; 52A7941A2BBC726E0083C976 /* GameRoomViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameRoomViewController.swift; sourceTree = ""; }; + 52DD8F942BC5208800D96BAB /* SurvivalGameMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurvivalGameMode.swift; sourceTree = ""; }; + 52DD8F982BC52F8400D96BAB /* LevelPopupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LevelPopupViewController.swift; sourceTree = ""; }; 52DF5FA42BA32B2300135367 /* TowerForge.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TowerForge.app; sourceTree = BUILT_PRODUCTS_DIR; }; 52DF5FA72BA32B2300135367 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52DF5FA92BA32B2300135367 /* GameScene.sks */ = {isa = PBXFileReference; lastKnownFileType = file.sks; path = GameScene.sks; sourceTree = ""; }; @@ -587,6 +593,7 @@ 5240D09F2BB330B4004F1486 /* GameMode.swift */, 5240D0A12BB33183004F1486 /* DeathMatchMode.swift */, 5240D0AA2BB3340F004F1486 /* CaptureTheFlagMode.swift */, + 52DD8F942BC5208800D96BAB /* SurvivalGameMode.swift */, 527A07732BB3D8E800CD9D08 /* GameModeFactory.swift */, ); path = GameModes; @@ -652,6 +659,7 @@ 523C292F2BBD0916004C6EAC /* GameWaitingRoomViewController.swift */, 5299D13B2BC3670E003EF746 /* LoginViewController.swift */, 5299D13D2BC36E61003EF746 /* RegisterViewController.swift */, + 52DD8F982BC52F8400D96BAB /* LevelPopupViewController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -697,6 +705,7 @@ 52A7940E2BBC476B0083C976 /* Networking */ = { isa = PBXGroup; children = ( + 52A794072BBC35E30083C976 /* Constants */, 5299D13F2BC3AA27003EF746 /* RankingNetwork */, 3CFA72E52BC039740081337F /* Utils */, 3CBECF822BBDC36A005EF39B /* GameNetwork */, @@ -753,7 +762,6 @@ 5240D0972BB0522A004F1486 /* Info.plist */, BA443D432BAD98CD009F0FFB /* AppMain */, 52A7940E2BBC476B0083C976 /* Networking */, - 52A794072BBC35E30083C976 /* Constants */, 52A794002BBC2E940083C976 /* Firebase */, BAFFB92F2BB0A01700D8301F /* Commons */, 5295A2052BAA0208005018A8 /* Nodes */, @@ -1014,6 +1022,7 @@ isa = PBXGroup; children = ( 3CE951682BAEB719008B2785 /* RequestSpawnEvent.swift */, + 523E5C4B2BC53F70007444DA /* WaveSpawnEvent.swift */, ); path = PlayerEvents; sourceTree = ""; @@ -1331,6 +1340,7 @@ BAFFB9552BB342FE00D8301F /* Storable.swift in Sources */, 52578B8C2BA627B200B4D76C /* Team.swift in Sources */, BAFFB9532BB342F100D8301F /* StorageManager.swift in Sources */, + 52DD8F992BC52F8500D96BAB /* LevelPopupViewController.swift in Sources */, BAFFB9682BB9981700D8301F /* TotalGamesAchievement.swift in Sources */, BAFFB9732BBAC14500D8301F /* AchievementManager.swift in Sources */, 3CBECF8E2BBE9EAC005EF39B /* RemoteSpawnEvent.swift in Sources */, @@ -1348,6 +1358,7 @@ 5200624E2BA8D597000DBA30 /* AiComponent.swift in Sources */, 3C9955C22BA5838900D33FA5 /* EventOutput.swift in Sources */, BAFFB96C2BB9AB2400D8301F /* LocalDatabase.swift in Sources */, + 523E5C4C2BC53F70007444DA /* WaveSpawnEvent.swift in Sources */, 5240D0912BAF3453004F1486 /* Life.swift in Sources */, 52A794192BBC630F0083C976 /* RoomData.swift in Sources */, 52DF5FAE2BA32B2300135367 /* GameScene.swift in Sources */, @@ -1426,6 +1437,7 @@ 5240D08F2BAE6D0A004F1486 /* Point.swift in Sources */, 3C769A742BA591BD00F454F9 /* SpawnSystem.swift in Sources */, 52A7941B2BBC726E0083C976 /* GameRoomViewController.swift in Sources */, + 52DD8F952BC5208900D96BAB /* SurvivalGameMode.swift in Sources */, 527A07862BB411FB00CD9D08 /* GameOverScene.swift in Sources */, 3C9955B12BA4ACA100D33FA5 /* Bullet.swift in Sources */, 3CA829C62BB719A500D8E72A /* ButtonRenderStage.swift in Sources */, diff --git a/TowerForge/TowerForge.xcodeproj/project.xcworkspace/xcuserdata/macbookpro.xcuserdatad/UserInterfaceState.xcuserstate b/TowerForge/TowerForge.xcodeproj/project.xcworkspace/xcuserdata/macbookpro.xcuserdatad/UserInterfaceState.xcuserstate index ceaf41e0..9c1e716c 100644 Binary files a/TowerForge/TowerForge.xcodeproj/project.xcworkspace/xcuserdata/macbookpro.xcuserdatad/UserInterfaceState.xcuserstate and b/TowerForge/TowerForge.xcodeproj/project.xcworkspace/xcuserdata/macbookpro.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard index adf727f7..8c44e23b 100644 --- a/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard +++ b/TowerForge/TowerForge/AppMain/Storyboards/Base.lproj/Main.storyboard @@ -286,61 +286,13 @@ - - - - - - - - - - - - - - - - - - - - - + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + @@ -485,6 +559,7 @@ Match + @@ -689,16 +764,96 @@ Match + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/TowerForge/TowerForge/Commons/Constants/Constants.swift b/TowerForge/TowerForge/Commons/Constants/Constants.swift index 2538c16f..60cd3e29 100644 --- a/TowerForge/TowerForge/Commons/Constants/Constants.swift +++ b/TowerForge/TowerForge/Commons/Constants/Constants.swift @@ -29,3 +29,31 @@ class Constants { static var SOUND_EFFECTS_VOLUME: Float = 0.8 } + +struct PositionConstants { + // Death Match Mode Properties Positions + static let DEATH_MATCH_POINT_OWN = CGPoint(x: 300, y: 50) + static let DEATH_MATCH_POINT_OPP = CGPoint(x: 300, y: 110) + + // Capture the Flag Mode Properties + static let CTF_POINT_OWN = CGPoint(x: 300, y: 50) + static let CTF_POINT_OPP = CGPoint(x: 300, y: 110) + + static let SUBTITLE_LABEL_OFFSET = CGPoint(x: 0, y: -30) +} + +struct SizeConstants { + // Death Match Mode Properties Size + static let DEATH_MATCH_POINT_SIZE = CGSize(width: 50, height: 50) + + // Capture the Flag Mode Properties Size + static let CTF_POINT_SIZE = CGSize(width: 50, height: 50) +} + +struct FontConstants { + static let GAME_FONT_SIZE: CGFloat = 40.0 +} + +struct GameModeSettingsConstants { + // static let LEVEL_ONE_WAVE +} diff --git a/TowerForge/TowerForge/GameModule/Components/BaseComponents/LabelComponent.swift b/TowerForge/TowerForge/GameModule/Components/BaseComponents/LabelComponent.swift index 0024781c..5210a610 100644 --- a/TowerForge/TowerForge/GameModule/Components/BaseComponents/LabelComponent.swift +++ b/TowerForge/TowerForge/GameModule/Components/BaseComponents/LabelComponent.swift @@ -8,23 +8,25 @@ import SpriteKit class LabelComponent: TFComponent { - var text: String + var title: String + var subtitle: String? var name: String var fontColor: UIColor = .white var fontName: String = "HelveticaNeue-Bold" - var fontSize: CGFloat = 60 + var fontSize: CGFloat = FontConstants.GAME_FONT_SIZE var zPosition: CGFloat = 0 var displacement: CGVector = .zero var horizontalAlignment: AlignmentMode = .center var verticalAlignment: AlignmentMode = .center - init(text: String, name: String) { - self.text = text + init(text: String, name: String, subtitle: String? = nil) { + self.title = text + self.subtitle = subtitle self.name = name super.init() } func changeText(_ text: String) { - self.text = text + self.title = text } } diff --git a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Death.swift b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Death.swift index b3dfa651..e7b3c415 100644 --- a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Death.swift +++ b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Death.swift @@ -8,17 +8,20 @@ import Foundation class Death: TFEntity { - static let position = CGPoint(x: 300, y: 100) - static let size = CGSize(width: 100, height: 100) - init() { + init(position: CGPoint, player: Player) { super.init() - let spriteComponent = SpriteComponent(textureNames: ["Skull"], size: Death.size, animatableKey: "death") + let spriteComponent = SpriteComponent(textureNames: ["Skull"], + size: SizeConstants.DEATH_MATCH_POINT_SIZE, + animatableKey: "death") self.addComponent(spriteComponent) - self.addComponent(LabelComponent(text: String(0), name: "killCount")) - self.addComponent(HomeComponent(initialLifeCount: Team.lifeCount, pointInterval: Team.pointsInterval)) - self.addComponent(PositionComponent(position: Death.position)) - self.addComponent(PlayerComponent(player: .ownPlayer)) + self.addComponent(LabelComponent(text: String(0), + name: "killCount", + subtitle: player == .ownPlayer ? "Your Kill" : "Opponent Kill")) + self.addComponent(HomeComponent(initialLifeCount: Team.lifeCount, + pointInterval: Team.pointsInterval)) + self.addComponent(PositionComponent(position: position)) + self.addComponent(PlayerComponent(player: player)) spriteComponent.staticOnScreen = true } diff --git a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift index e1f7acf2..2b3fe6f8 100644 --- a/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift +++ b/TowerForge/TowerForge/GameModule/Entities/GameEntities/Life.swift @@ -8,17 +8,18 @@ import Foundation class Life: TFEntity { - static let position = CGPoint(x: 300, y: 100) - static let size = CGSize(width: 100, height: 100) - init(initialLife: Int) { + init(initialLife: Int, position: CGPoint, player: Player) { super.init() - let spriteComponent = SpriteComponent(textureNames: ["Life"], size: Life.size, animatableKey: "life") - + let spriteComponent = SpriteComponent(textureNames: ["Life"], + size: SizeConstants.CTF_POINT_SIZE, + animatableKey: "life") self.addComponent(spriteComponent) self.addComponent(HomeComponent(initialLifeCount: Team.lifeCount, pointInterval: Team.pointsInterval)) - self.addComponent(LabelComponent(text: String(initialLife), name: "life")) - self.addComponent(PositionComponent(position: Life.position)) - self.addComponent(PlayerComponent(player: .ownPlayer)) + self.addComponent(LabelComponent(text: String(initialLife), + name: "life", + subtitle: player == .ownPlayer ? "Your Life" : "Opponent Life")) + self.addComponent(PositionComponent(position: position)) + self.addComponent(PlayerComponent(player: player)) spriteComponent.staticOnScreen = true } diff --git a/TowerForge/TowerForge/GameModule/Events/PlayerEvents/WaveSpawnEvent.swift b/TowerForge/TowerForge/GameModule/Events/PlayerEvents/WaveSpawnEvent.swift new file mode 100644 index 00000000..eaeddc09 --- /dev/null +++ b/TowerForge/TowerForge/GameModule/Events/PlayerEvents/WaveSpawnEvent.swift @@ -0,0 +1,37 @@ +// +// RequestSpawnEvent.swift +// TowerForge +// +// Created by Vanessa Mae on 23/3/24. +// + +import Foundation + +struct WaveSpawnEvent: TFEvent { + let timestamp: TimeInterval + let position: CGPoint + let entityType: (PlayerSpawnable & TFEntity).Type + let player: Player + + /// TODO: might need to change this to something other than throwing fatal error, + /// because it could cause accidental application crash, for example iterating through + /// a collection of TFEvents. + 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 { + if let homeSystem = target.system(ofType: HomeSystem.self) { + homeSystem.waveSpawn(at: position, ofType: entityType, for: player) + } + return EventOutput() + } +} diff --git a/TowerForge/TowerForge/GameModule/GameModes/CaptureTheFlagMode.swift b/TowerForge/TowerForge/GameModule/GameModes/CaptureTheFlagMode.swift index f8729e84..148e3a32 100644 --- a/TowerForge/TowerForge/GameModule/GameModes/CaptureTheFlagMode.swift +++ b/TowerForge/TowerForge/GameModule/GameModes/CaptureTheFlagMode.swift @@ -19,7 +19,12 @@ class CaptureTheFlagMode: GameMode { var currentOpponentLife: Int init(initialLife: Int, eventManager: EventManager) { - self.gameProps.append(LifeProp(initialLife: initialLife)) + self.gameProps.append(LifeProp(initialLife: initialLife, + position: PositionConstants.CTF_POINT_OWN, + player: .ownPlayer)) + self.gameProps.append(LifeProp(initialLife: initialLife, + position: PositionConstants.CTF_POINT_OPP, + player: .oppositePlayer)) self.currentOwnLife = initialLife self.currentOpponentLife = initialLife self.eventManager = eventManager @@ -34,28 +39,14 @@ class CaptureTheFlagMode: GameMode { } } } - func updateGame() { + func updateGame(deltaTime: TimeInterval) { if self.currentOwnLife <= 0 { gameState = .LOSE } else if self.currentOpponentLife <= 0 { gameState = .WIN } } - func startGame() { - gameState = GameState.PLAYING - - } - - func resumeGame() { - gameState = GameState.PLAYING - } - - func pauseGame() { - gameState = GameState.PAUSED - } - - func winGame() { - gameState = GameState.WIN + func getGameResults() -> [GameResult] { + [GameResult(variable: "Life left", value: String(self.currentOwnLife))] } - } diff --git a/TowerForge/TowerForge/GameModule/GameModes/DeathMatchMode.swift b/TowerForge/TowerForge/GameModule/GameModes/DeathMatchMode.swift index 3ab05f72..cf3f1b02 100644 --- a/TowerForge/TowerForge/GameModule/GameModes/DeathMatchMode.swift +++ b/TowerForge/TowerForge/GameModule/GameModes/DeathMatchMode.swift @@ -11,7 +11,11 @@ class DeathMatchMode: GameMode { var eventManager: EventManager var modeName: String = "Death Match Mode" var modeDescription: String = "Kill as many units within the time limit" - var gameProps: [any GameProp] = [DeathProp(), PointProp(initialPoint: 0)] + var gameProps: [any GameProp] = [DeathProp(position: PositionConstants.DEATH_MATCH_POINT_OWN, + player: .ownPlayer), + DeathProp(position: PositionConstants.DEATH_MATCH_POINT_OPP, + player: .oppositePlayer), + PointProp(initialPoint: 0)] var timer = TimerProp(timeLength: TimeInterval(60)) var gameState = GameState.IDLE var currentOwnKillCounter: Int @@ -33,7 +37,7 @@ class DeathMatchMode: GameMode { } } } - func updateGame() { + func updateGame(deltaTime: TimeInterval) { if timer.time < 0 { if currentOwnKillCounter > currentOpponentKillCounter { gameState = .WIN @@ -44,20 +48,11 @@ class DeathMatchMode: GameMode { } } } - func startGame() { - gameState = GameState.PLAYING - } - - func resumeGame() { - gameState = GameState.PLAYING - } - - func pauseGame() { - gameState = GameState.PAUSED - } - - func winGame() { - gameState = GameState.WIN + func getGameResults() -> [GameResult] { + let result: [GameResult] = [GameResult(variable: "Total Kill", + value: String(self.currentOwnKillCounter)), + GameResult(variable: "Opponent Kill", value: String(self.currentOpponentKillCounter))] + return result } } diff --git a/TowerForge/TowerForge/GameModule/GameModes/GameMode.swift b/TowerForge/TowerForge/GameModule/GameModes/GameMode.swift index 65fb9236..7cff385f 100644 --- a/TowerForge/TowerForge/GameModule/GameModes/GameMode.swift +++ b/TowerForge/TowerForge/GameModule/GameModes/GameMode.swift @@ -7,15 +7,17 @@ import Foundation +struct GameResult { + var variable: String + var value: String +} + protocol GameMode { var modeName: String { get } var modeDescription: String { get } var gameProps: [any GameProp] { get } var gameState: GameState { get set } var eventManager: EventManager { get set } - func updateGame() - func startGame() - func pauseGame() - func winGame() - func resumeGame() + func updateGame(deltaTime: TimeInterval) + func getGameResults() -> [GameResult] } diff --git a/TowerForge/TowerForge/GameModule/GameModes/GameModeFactory.swift b/TowerForge/TowerForge/GameModule/GameModes/GameModeFactory.swift index 1058fd3b..837d4a8b 100644 --- a/TowerForge/TowerForge/GameModule/GameModes/GameModeFactory.swift +++ b/TowerForge/TowerForge/GameModule/GameModes/GameModeFactory.swift @@ -10,16 +10,24 @@ import Foundation enum Mode { case deathMatch case captureTheFlag + case survivalLevelOne + case survivalLevelTwo + case survivalLevelThree } class GameModeFactory { - static func createGameMode(mode: Mode, eventManager: EventManager) -> GameMode { switch mode { case .captureTheFlag: return CaptureTheFlagMode(initialLife: 5, eventManager: eventManager) case .deathMatch: return DeathMatchMode(eventManager: eventManager) + case .survivalLevelOne: + return SurvivalGameMode(eventManager: eventManager, maxLevel: 1) + case .survivalLevelTwo: + return SurvivalGameMode(eventManager: eventManager, maxLevel: 2) + case .survivalLevelThree: + return SurvivalGameMode(eventManager: eventManager, maxLevel: 3) } } } diff --git a/TowerForge/TowerForge/GameModule/GameModes/SurvivalGameMode.swift b/TowerForge/TowerForge/GameModule/GameModes/SurvivalGameMode.swift new file mode 100644 index 00000000..b3549374 --- /dev/null +++ b/TowerForge/TowerForge/GameModule/GameModes/SurvivalGameMode.swift @@ -0,0 +1,88 @@ +// +// SurvivalGameMode.swift +// TowerForge +// +// Created by MacBook Pro on 09/04/24. +// + +import Foundation +import UIKit + +class SurvivalGameMode: GameMode { + var modeName: String = "Survival Game Mode" + + var modeDescription: String = "Don't let the enemy invade your base!" + + var gameProps: [any GameProp] = [PointProp(initialPoint: 0)] + + var gameState = GameState.IDLE + + var eventManager: EventManager + + private var currentOwnLife = 1 + + private var currentLevel: Int = 1 + private var maxLevel: Int + + private var nextWaveTime = TimeInterval(50) + private var waveTimeInterval = TimeInterval(50) + + init(eventManager: EventManager, maxLevel: Int) { + self.eventManager = eventManager + self.maxLevel = maxLevel + + let timer = TimerProp(timeLength: waveTimeInterval * Double(maxLevel)) + self.gameProps.append(timer) + + eventManager.registerHandler(forEvent: LifeEvent.self) { event in + if let lifeEvent = event as? LifeEvent { + // Check if the event reduces life + if lifeEvent.player == .oppositePlayer && lifeEvent.lifeDecrease > 0 { + print("Triggered?") + self.currentOwnLife -= lifeEvent.lifeDecrease + } + } + } + } + + func updateGame(deltaTime: TimeInterval) { + nextWaveTime -= deltaTime + if nextWaveTime < 0 && self.currentLevel <= self.maxLevel { + nextWaveTime = waveTimeInterval + self.currentLevel += 1 + self.generateWaveSpawns(enemyCount: 5 * self.currentLevel) + } + if self.currentLevel > self.maxLevel { + gameState = .WIN + } + if self.currentOwnLife <= 0 { + gameState = .LOSE + } + } + + func getGameResults() -> [GameResult] { + [GameResult(variable: "Finished Waves", value: String(currentLevel))] + } + + private func generateWaveSpawns(enemyCount: Int) { + for _ in 0.. CGPoint { + let randomY = CGFloat.random(in: 200...UIScreen.main.bounds.height) + // TODO: Change the GameWorld to a constant + return CGPoint(x: GameWorld.worldSize.width, y: randomY) + + } + +} diff --git a/TowerForge/TowerForge/GameModule/GameProps/DeathProp.swift b/TowerForge/TowerForge/GameModule/GameProps/DeathProp.swift index f1ff616d..cbcdb0ff 100644 --- a/TowerForge/TowerForge/GameModule/GameProps/DeathProp.swift +++ b/TowerForge/TowerForge/GameModule/GameProps/DeathProp.swift @@ -10,7 +10,8 @@ import Foundation class DeathProp: GameProp { var renderableEntity: Death - init() { - self.renderableEntity = Death() + init(position: CGPoint, player: Player) { + self.renderableEntity = Death(position: position, player: player) + } } diff --git a/TowerForge/TowerForge/GameModule/GameProps/LifeProp.swift b/TowerForge/TowerForge/GameModule/GameProps/LifeProp.swift index ee215761..050d6018 100644 --- a/TowerForge/TowerForge/GameModule/GameProps/LifeProp.swift +++ b/TowerForge/TowerForge/GameModule/GameProps/LifeProp.swift @@ -10,7 +10,7 @@ import Foundation class LifeProp: GameProp { var renderableEntity: Life - init(initialLife: Int) { - self.renderableEntity = Life(initialLife: initialLife) + init(initialLife: Int, position: CGPoint, player: Player) { + self.renderableEntity = Life(initialLife: initialLife, position: position, player: player) } } diff --git a/TowerForge/TowerForge/GameModule/GameWorld.swift b/TowerForge/TowerForge/GameModule/GameWorld.swift index 924e34a5..14170ce9 100644 --- a/TowerForge/TowerForge/GameModule/GameWorld.swift +++ b/TowerForge/TowerForge/GameModule/GameWorld.swift @@ -42,10 +42,11 @@ class GameWorld { func update(deltaTime: TimeInterval) { gameEngine.updateGame(deltaTime: deltaTime) - gameMode.updateGame() + gameMode.updateGame(deltaTime: deltaTime) if checkGameEnded() { renderer?.renderMessage("You win") - delegate?.showGameOverScene(isWin: gameMode.gameState == .WIN ? true : false) + print(gameMode.gameState) + delegate?.showGameOverScene(isWin: gameMode.gameState == .WIN ? true : false, results: gameMode.getGameResults()) } selectionNode.update() renderer?.render() diff --git a/TowerForge/TowerForge/GameModule/Systems/LevelSystems/AiSystem.swift b/TowerForge/TowerForge/GameModule/Systems/LevelSystems/AiSystem.swift index 152fe2e3..b65f766c 100644 --- a/TowerForge/TowerForge/GameModule/Systems/LevelSystems/AiSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/LevelSystems/AiSystem.swift @@ -36,8 +36,10 @@ class AiSystem: TFSystem { continue } - let newEvent = RequestSpawnEvent(ofType: unitType, timestamp: CACurrentMediaTime(), - position: aiComponent.spawnLocation, player: aiPlayer) + let newEvent = RequestSpawnEvent(ofType: unitType, + timestamp: CACurrentMediaTime(), + position: aiComponent.spawnLocation, + player: aiPlayer) event = event.concurrentlyWith(newEvent) } eventManager.add(event) diff --git a/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift b/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift index 9aa77331..e1ed045b 100644 --- a/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift +++ b/TowerForge/TowerForge/GameModule/Systems/LevelSystems/HomeSystem.swift @@ -74,6 +74,29 @@ class HomeSystem: TFSystem { eventManager.add(spawnEvent) } + func waveSpawn(at position: CGPoint, ofType type: T.Type, for player: Player) { + // Get HomeComponent for the player + let playerHomeComponentArr = entityManager.components(ofType: HomeComponent.self).filter(({ + $0.entity?.component(ofType: PlayerComponent.self)?.player == player + })) + guard !playerHomeComponentArr.isEmpty, position.y >= gridDelegate.UNIT_SELECTION_NODE_HEIGHT else { + return + } + + let snapPosition = getSnapPosition(at: position, for: player, with: type) + + // If player is not playing in multiplayer, spawn as normal + guard let currentPlayer = eventManager.currentPlayer else { + let spawnEvent = SpawnEvent(ofType: type, timestamp: CACurrentMediaTime(), + position: snapPosition, player: player) + return eventManager.add(spawnEvent) + } + + // Report spawn to which will propogate to all players evenly + let spawnEvent = RemoteSpawnEvent(ofType: type, location: snapPosition, player: currentPlayer) + eventManager.add(spawnEvent) + } + private func getSnapPosition(at requestedPosition: CGPoint, for player: Player, with type: T.Type) -> CGPoint { // Normalise to own player position diff --git a/TowerForge/TowerForge/GameViewController.swift b/TowerForge/TowerForge/GameViewController.swift index 44009cc8..b067c7bc 100644 --- a/TowerForge/TowerForge/GameViewController.swift +++ b/TowerForge/TowerForge/GameViewController.swift @@ -72,8 +72,8 @@ extension GameViewController: SceneManagerDelegate { func showLevelScene() { // TODO : to implement after Keith is done } - func showGameOverScene(isWin: Bool) { - let gameOverScene = GameOverScene(win: isWin) + func showGameOverScene(isWin: Bool, results: [GameResult]) { + let gameOverScene = GameOverScene(win: isWin, results: results) gameOverScene.sceneManagerDelegate = self showScene(scene: gameOverScene) } diff --git a/TowerForge/TowerForge/Constants/Constant.swift b/TowerForge/TowerForge/Networking/Constants/Constant.swift similarity index 100% rename from TowerForge/TowerForge/Constants/Constant.swift rename to TowerForge/TowerForge/Networking/Constants/Constant.swift diff --git a/TowerForge/TowerForge/Scenes/GameOverScene.swift b/TowerForge/TowerForge/Scenes/GameOverScene.swift index 1b633e5c..064050a6 100644 --- a/TowerForge/TowerForge/Scenes/GameOverScene.swift +++ b/TowerForge/TowerForge/Scenes/GameOverScene.swift @@ -11,9 +11,11 @@ import SpriteKit class GameOverScene: SKScene { var sceneManagerDelegate: SceneManagerDelegate? var win: Bool + var results: [GameResult] - init(win: Bool) { + init(win: Bool, results: [GameResult]) { self.win = win + self.results = results super.init(size: CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) } @@ -31,6 +33,24 @@ class GameOverScene: SKScene { label.fontColor = .darkGray label.zPosition = 1_000 addChild(label.node) + setupResultsUI() + } + + func setupResultsUI() { + var yOffset: CGFloat = 80.0 + let labelFontSize: CGFloat = 16.0 + let labelFontName = "Nosifer-Regular" + + for result in results { + let resultLabel = TFLabelNode(text: "\(result.variable): \(result.value)") + resultLabel.fontSize = labelFontSize + resultLabel.fontName = labelFontName + resultLabel.fontColor = .darkGray + resultLabel.position = CGPoint(x: 50, y: size.height - yOffset) + addChild(resultLabel.node) + + yOffset += 30.0 + } } @available(*, unavailable) diff --git a/TowerForge/TowerForge/Scenes/Rendering/RenderStages/LabelRenderStage.swift b/TowerForge/TowerForge/Scenes/Rendering/RenderStages/LabelRenderStage.swift index d5a9b821..a8ddc0ec 100644 --- a/TowerForge/TowerForge/Scenes/Rendering/RenderStages/LabelRenderStage.swift +++ b/TowerForge/TowerForge/Scenes/Rendering/RenderStages/LabelRenderStage.swift @@ -28,11 +28,11 @@ class LabelRenderStage: RenderStage { let labelComponent = entity.component(ofType: LabelComponent.self) else { return } - labelNode.text = labelComponent.text + labelNode.text = labelComponent.title } private func createLabelNode(with labelComponent: LabelComponent) -> TFLabelNode { - let labelNode = TFLabelNode(text: labelComponent.text) + let labelNode = TFLabelNode(text: labelComponent.title) labelNode.fontColor = labelComponent.fontColor labelNode.fontName = labelComponent.fontName labelNode.fontSize = labelComponent.fontSize @@ -40,6 +40,18 @@ class LabelRenderStage: RenderStage { labelNode.verticalAlignmentMode = labelComponent.verticalAlignment labelNode.name = LabelRenderStage.name + if let subtitle = labelComponent.subtitle { + let subtitleNode = TFLabelNode(text: subtitle) + subtitleNode.fontColor = labelComponent.fontColor + subtitleNode.fontName = labelComponent.fontName + subtitleNode.fontSize = labelComponent.fontSize * 0.2 + subtitleNode.horizontalAlignmentMode = labelComponent.horizontalAlignment + subtitleNode.verticalAlignmentMode = labelComponent.verticalAlignment + subtitleNode.position = PositionConstants.SUBTITLE_LABEL_OFFSET + subtitleNode.name = "\(LabelRenderStage.name)-subtitle" + labelNode.add(child: subtitleNode) + } + return labelNode } } diff --git a/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift b/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift index 6a3e8f84..ecf73491 100644 --- a/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift +++ b/TowerForge/TowerForge/Scenes/Rendering/RenderStages/PlayerRenderStage.swift @@ -13,6 +13,11 @@ class PlayerRenderStage: RenderStage { return } + // prevent HUD labels from being scaled + if entity.hasComponent(ofType: LabelComponent.self) { + return + } + if playerComponent.player == .oppositePlayer { node.xScale *= -1 } diff --git a/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift b/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift index 85190c0a..df255327 100644 --- a/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift +++ b/TowerForge/TowerForge/Scenes/Rendering/Renderer.swift @@ -45,19 +45,19 @@ class Renderer { return } let labelNode = TFLabelNode(text: message) - let label = SKLabelNode(text: message) - labelNode.node = label - label.name = "message" - label.fontSize = 50.0 - label.fontName = "Nosifer-Regular" - label.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2) + labelNode.name = "message" + labelNode.fontSize = 50.0 + labelNode.fontName = "Nosifer-Regular" + labelNode.position = CGPoint(x: 0, y: 0) + labelNode.zPosition = 1_000 let fadeInAction = SKAction.fadeIn(withDuration: 0.5) let waitAction = SKAction.wait(forDuration: 1.0) let fadeOutAction = SKAction.fadeOut(withDuration: 0.5) let removeAction = SKAction.removeFromParent() let sequence = SKAction.sequence([fadeInAction, waitAction, fadeOutAction, removeAction]) - label.run(sequence) + + labelNode.run(sequence) scene.add(node: labelNode, staticOnScreen: true) } diff --git a/TowerForge/TowerForge/Scenes/SceneDelegates/SceneManagerDelegate.swift b/TowerForge/TowerForge/Scenes/SceneDelegates/SceneManagerDelegate.swift index 4abd76e9..04e5f2c4 100644 --- a/TowerForge/TowerForge/Scenes/SceneDelegates/SceneManagerDelegate.swift +++ b/TowerForge/TowerForge/Scenes/SceneDelegates/SceneManagerDelegate.swift @@ -9,7 +9,7 @@ import Foundation protocol SceneManagerDelegate: AnyObject { func showMenuScene() - func showGameOverScene(isWin: Bool) + func showGameOverScene(isWin: Bool, results: [GameResult]) func showLevelScene() func showGameLevelScene(level: Int) } diff --git a/TowerForge/TowerForge/TFCore/TFLabelNode.swift b/TowerForge/TowerForge/TFCore/TFLabelNode.swift index 0a5e75c9..2a1c9704 100644 --- a/TowerForge/TowerForge/TFCore/TFLabelNode.swift +++ b/TowerForge/TowerForge/TFCore/TFLabelNode.swift @@ -105,4 +105,8 @@ class TFLabelNode: TFNode { } return labelNode } + + func run(_ action: SKAction) { + labelNode.run(action) + } } diff --git a/TowerForge/TowerForge/ViewControllers/GameModeViewController.swift b/TowerForge/TowerForge/ViewControllers/GameModeViewController.swift index 00997439..ab54a98d 100644 --- a/TowerForge/TowerForge/ViewControllers/GameModeViewController.swift +++ b/TowerForge/TowerForge/ViewControllers/GameModeViewController.swift @@ -14,7 +14,15 @@ class GameModeViewController: UIViewController { @IBOutlet private var rankingButton: UIButton! @IBOutlet private var loginButtonLabel: UILabel! + var selectedGameMode = Mode.captureTheFlag + + let levelModes: [Int: Mode] = [ + 1: .survivalLevelOne, + 2: .survivalLevelTwo, + 3: .survivalLevelThree + ] + private let authenticationProvider = AuthenticationProvider() override func viewDidLoad() { super.viewDidLoad() @@ -32,7 +40,11 @@ class GameModeViewController: UIViewController { authenticationProvider.removeObserver(self) } - @IBAction func onRankingPressed(_ sender: Any) { + @IBAction private func onRankingPressed(_ sender: Any) { + } + @IBAction private func survivalMatchPressed(_ sender: Any) { + print("PRESSED?") + LevelPopupViewController.showDialogBox(parentVC: self) } @IBAction private func deathMatchButtonPressed(_ sender: UIButton) { selectedGameMode = .deathMatch @@ -93,3 +105,13 @@ extension GameModeViewController: AuthenticationDelegate { rankingButton.isHidden = true } } + +extension GameModeViewController: LevelPopupDelegate { + func handleLevel(level: Int) { + if let levelMode = levelModes[level] { + selectedGameMode = levelMode + navigateToGameViewController() + } + } + +} diff --git a/TowerForge/TowerForge/ViewControllers/LevelPopupViewController.swift b/TowerForge/TowerForge/ViewControllers/LevelPopupViewController.swift new file mode 100644 index 00000000..41f5a558 --- /dev/null +++ b/TowerForge/TowerForge/ViewControllers/LevelPopupViewController.swift @@ -0,0 +1,45 @@ +// +// LevelPopupViewController.swift +// TowerForge +// +// Created by MacBook Pro on 09/04/24. +// + +import Foundation +import UIKit + +protocol LevelPopupDelegate { + func handleLevel(level: Int) +} + +class LevelPopupViewController: UIViewController { + + var delegate: LevelPopupDelegate? + + @IBAction func onLevelOnePressed(_ sender: Any) { + self.delegate?.handleLevel(level: 1) + self.dismiss(animated: true) + } + @IBAction func onLevelTwoPressed(_ sender: Any) { + self.delegate?.handleLevel(level: 2) + self.dismiss(animated: true) + } + @IBAction func onLevelThreePressed(_ sender: Any) { + self.delegate?.handleLevel(level: 3) + self.dismiss(animated: true) + } + + @IBAction func onClosePressed(_ sender: Any) { + self.dismiss(animated: true) + } + static func showDialogBox(parentVC: UIViewController) { + if let levelPopupViewController = UIStoryboard(name: "Main", bundle: nil) + .instantiateViewController(withIdentifier: "LevelPopupViewController") + as? LevelPopupViewController { + levelPopupViewController.modalTransitionStyle = .crossDissolve + levelPopupViewController.modalPresentationStyle = .fullScreen + levelPopupViewController.delegate = parentVC as? LevelPopupDelegate + parentVC.present(levelPopupViewController, animated: true) + } + } +}