From 6209360f990b11f933889c2905882c18fcd03dba Mon Sep 17 00:00:00 2001 From: Causeless Date: Sun, 25 Oct 2026 19:35:31 +0000 Subject: [PATCH 1/3] Kept the terrain layers' draw depths when they're cloned or re-created SceneLayer's copy didn't take m_ZOrder along, and SLTerrain::LoadData Destroy()s and re-Create()s its color layers when the terrain is made from placed objects, which resets m_ZOrder. Either way the terrain background layer ended up drawn at depth 0 like the foreground, so a primitive drawn at a depth between the two was hidden behind the background instead of showing on top of it. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ Source/Entities/SLTerrain.cpp | 2 ++ Source/Entities/SceneLayer.cpp | 1 + 3 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b036f1c72..51bf4fbd76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -309,6 +309,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fixed AI actors refusing to fire the Coalition Missile Launcher at anything beyond point-blank range, because the AI took the range of its short-lived dummy round instead of the missile's. +- Fixed the terrain background layer drawing at the terrain foreground's depth, which hid primitives drawn at a depth between `DrawDepth.Default` and `DrawDepth.TerrainBackground` behind it instead of showing them on top of the background. +
Removed diff --git a/Source/Entities/SLTerrain.cpp b/Source/Entities/SLTerrain.cpp index 142889f003..27f58d58da 100644 --- a/Source/Entities/SLTerrain.cpp +++ b/Source/Entities/SLTerrain.cpp @@ -278,9 +278,11 @@ int SLTerrain::LoadData() { } else { m_FGColorLayer->Destroy(); m_FGColorLayer->Create(create_bitmap_ex(8, m_MainBitmap->w, m_MainBitmap->h), true, m_Offset, m_WrapX, m_WrapY, m_ScrollInfo); + m_FGColorLayer->SetZOrder(c_DefaultDrawDepth); m_BGColorLayer->Destroy(); m_BGColorLayer->Create(create_bitmap_ex(8, m_MainBitmap->w, m_MainBitmap->h), true, m_Offset, m_WrapX, m_WrapY, m_ScrollInfo); + m_BGColorLayer->SetZOrder(c_TerrainBGDepth); TexturizeTerrain(); diff --git a/Source/Entities/SceneLayer.cpp b/Source/Entities/SceneLayer.cpp index 9e85a2ac9c..4ec7f9011a 100644 --- a/Source/Entities/SceneLayer.cpp +++ b/Source/Entities/SceneLayer.cpp @@ -93,6 +93,7 @@ int SceneLayerImpl::Create(const SceneLayerImpl& m_BitmapFile = reference.m_BitmapFile; m_DrawMasked = reference.m_DrawMasked; + m_ZOrder = reference.m_ZOrder; m_WrapX = reference.m_WrapX; m_WrapY = reference.m_WrapY; m_OriginOffset = reference.m_OriginOffset; From aea8f881f88e3653e5dcf6ad5c875fb7a0cac0a4 Mon Sep 17 00:00:00 2001 From: Causeless Date: Sun, 25 Oct 2026 19:35:40 +0000 Subject: [PATCH 2/3] Added Lua bindings for players' control schemes UInputMan:GetControlScheme(player) returns the player's InputScheme, which has its Device, its Preset and GetMappingName(inputElement), so scripts can tell players which keys or buttons to press. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 ++++ Source/Lua/LuaBindingRegisterDefinitions.h | 2 ++ Source/Lua/LuaBindingsManagers.cpp | 1 + Source/Lua/LuaBindingsSystem.cpp | 20 ++++++++++++++++++++ Source/Managers/LuaMan.cpp | 1 + 5 files changed, 28 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 51bf4fbd76..5a2a75c0a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -165,6 +165,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - New `Activity` INI property `HumansCanSpectate = 0/1` for Activities that human players can choose to just watch. The setup screen starts out with every team given to the CPU, and the game can be started without any human players on a team, in which case player one watches from the first CPU team. Spectating is optional: players can still put themselves on a team to play it as usual. Disabled by default. +- New `UInputMan` Lua function `GetControlScheme(player)`, which returns that player's `InputScheme`. + `InputScheme` has the Lua properties `Device` (R) and `Preset` (R), and the function `GetMappingName(inputElement)`, which gives the name of whatever the input is bound to, such as "Mouse Left". + `Enum` binding for `InputScheme.InputPreset`: `NoPreset = 0, PresetArrowKeys = 1, PresetWASDKeys = 2, PresetMouseWASDKeys = 3, PresetGenericDPad = 4, PresetGenericDualAnalog = 5, PresetGamepadSNES = 6, PresetGamepadDS4 = 7, PresetGamepadXbox360 = 8, InputPresetCount = 9`. +
Changed diff --git a/Source/Lua/LuaBindingRegisterDefinitions.h b/Source/Lua/LuaBindingRegisterDefinitions.h index 91be1b3c54..2acf4feda2 100644 --- a/Source/Lua/LuaBindingRegisterDefinitions.h +++ b/Source/Lua/LuaBindingRegisterDefinitions.h @@ -11,6 +11,7 @@ namespace RTE { PER_LUA_BINDING(Box) \ PER_LUA_BINDING(Controller) \ PER_LUA_BINDING(DataModule) \ + PER_LUA_BINDING(InputScheme) \ PER_LUA_BINDING(Timer) \ PER_LUA_BINDING(Vector) \ PER_LUA_BINDING(PathRequest) \ @@ -179,6 +180,7 @@ namespace RTE { LuaBindingRegisterFunctionDeclarationForType(Box); LuaBindingRegisterFunctionDeclarationForType(Controller); LuaBindingRegisterFunctionDeclarationForType(DataModule); + LuaBindingRegisterFunctionDeclarationForType(InputScheme); LuaBindingRegisterFunctionDeclarationForType(Timer); LuaBindingRegisterFunctionDeclarationForType(Vector); LuaBindingRegisterFunctionDeclarationForType(PathRequest); diff --git a/Source/Lua/LuaBindingsManagers.cpp b/Source/Lua/LuaBindingsManagers.cpp index 55d60d8bfc..9a6e20e3d1 100644 --- a/Source/Lua/LuaBindingsManagers.cpp +++ b/Source/Lua/LuaBindingsManagers.cpp @@ -428,6 +428,7 @@ LuaBindingRegisterFunctionDefinitionForType(ManagerLuaBindings, UInputMan) { .property("FlagShiftState", &UInputMan::FlagShiftState) .def("GetInputDevice", &UInputMan::GetInputDevice) + .def("GetControlScheme", &UInputMan::GetControlScheme) .def("ElementPressed", &UInputMan::ElementPressed) .def("ElementReleased", &UInputMan::ElementReleased) .def("ElementHeld", &UInputMan::ElementHeld) diff --git a/Source/Lua/LuaBindingsSystem.cpp b/Source/Lua/LuaBindingsSystem.cpp index 19aa29ac4d..99dd6c65bc 100644 --- a/Source/Lua/LuaBindingsSystem.cpp +++ b/Source/Lua/LuaBindingsSystem.cpp @@ -134,6 +134,26 @@ LuaBindingRegisterFunctionDefinitionForType(SystemLuaBindings, DataModule) { .def_readwrite("Presets", &DataModule::m_EntityList, luabind::return_stl_iterator); } +LuaBindingRegisterFunctionDefinitionForType(SystemLuaBindings, InputScheme) { + return luabind::class_("InputScheme") + + .property("Device", &InputScheme::GetDevice) + .property("Preset", &InputScheme::GetPreset) + + .def("GetMappingName", &InputScheme::GetMappingName) + + .enum_("InputPreset")[luabind::value("NoPreset", InputScheme::InputPreset::NoPreset), + luabind::value("PresetArrowKeys", InputScheme::InputPreset::PresetArrowKeys), + luabind::value("PresetWASDKeys", InputScheme::InputPreset::PresetWASDKeys), + luabind::value("PresetMouseWASDKeys", InputScheme::InputPreset::PresetMouseWASDKeys), + luabind::value("PresetGenericDPad", InputScheme::InputPreset::PresetGenericDPad), + luabind::value("PresetGenericDualAnalog", InputScheme::InputPreset::PresetGenericDualAnalog), + luabind::value("PresetGamepadSNES", InputScheme::InputPreset::PresetGamepadSNES), + luabind::value("PresetGamepadDS4", InputScheme::InputPreset::PresetGamepadDS4), + luabind::value("PresetGamepadXbox360", InputScheme::InputPreset::PresetGamepadXbox360), + luabind::value("InputPresetCount", InputScheme::InputPreset::InputPresetCount)]; +} + LuaBindingRegisterFunctionDefinitionForType(SystemLuaBindings, Timer) { return luabind::class_("Timer") diff --git a/Source/Managers/LuaMan.cpp b/Source/Managers/LuaMan.cpp index 88bab187f0..dd4429da46 100644 --- a/Source/Managers/LuaMan.cpp +++ b/Source/Managers/LuaMan.cpp @@ -175,6 +175,7 @@ void LuaStateWrapper::Initialize() { RegisterLuaBindingsOfType(EntityLuaBindings, SLBackground), RegisterLuaBindingsOfAbstractType(EntityLuaBindings, Deployment), RegisterLuaBindingsOfType(SystemLuaBindings, DataModule), + RegisterLuaBindingsOfType(SystemLuaBindings, InputScheme), RegisterLuaBindingsOfType(ActivityLuaBindings, Activity), RegisterLuaBindingsOfAbstractType(ActivityLuaBindings, GameActivity), RegisterLuaBindingsOfAbstractType(EntityLuaBindings, GlobalScript), From 134190c3bc5fca419b8de5d6e78df534d472da3f Mon Sep 17 00:00:00 2001 From: Causeless Date: Sun, 25 Oct 2026 19:35:51 +0000 Subject: [PATCH 3/3] Moved the tutorial mission to a GAScripted and removed GATutorial The tutorial's logic is now Missions.rte/Activities/Tutorial.lua. Its screens and room signs used to be blitted into the terrain's background layer; they're now bitmap primitives drawn just in front of it, so they still show behind actors and the terrain foreground. The fight trigger uses the scene's AIAttack area, which matched the box hardcoded before, and the activity requires the Tutorial Bunker's areas instead of checking the scene's name. A few things work differently: the CPU brain is no longer created when the scene lacks one (the Tutorial Bunker always has its Dummy Controller), and the game no longer returns to the menu by itself 54 s after a win, as scripted activities stop updating once the game is over. Settings.ini files written before this all have DefaultActivityType = GATutorial, which is now read as GAScripted. Fixes #188 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 4 + Data/Base.rte/Activities.ini | 8 +- Data/Missions.rte/Activities/Tutorial.lua | 386 ++++++++++ RTEA.vcxproj | 2 - RTEA.vcxproj.filters | 6 - Source/Activities/GATutorial.cpp | 874 ---------------------- Source/Activities/GATutorial.h | 208 ----- Source/Activities/meson.build | 1 - Source/Managers/ActivityMan.cpp | 4 +- Source/Managers/SettingsMan.cpp | 8 +- Source/Menus/MetagameGUI.cpp | 1 - Source/Menus/ScenarioGUI.cpp | 2 +- 12 files changed, 407 insertions(+), 1097 deletions(-) create mode 100644 Data/Missions.rte/Activities/Tutorial.lua delete mode 100644 Source/Activities/GATutorial.cpp delete mode 100644 Source/Activities/GATutorial.h diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a2a75c0a4..efcc14391f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -253,6 +253,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Numbers saved with `Activity:SaveNumber()` are now kept as doubles instead of floats, and floating point numbers in INI and save files are written out exactly instead of being rounded to 6 digits. +- The tutorial mission is now a `GAScripted` activity, `Missions.rte/Activities/Tutorial.lua`, instead of being hardcoded in the engine. Its screens and room signs are drawn as bitmap primitives on the bunker's back wall instead of into the terrain. +
Fixed @@ -334,6 +336,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Deprecated `LimbPath` properties `SlowTravelSpeed`, `NormalTravelSpeed` and `FastTravelSpeed`. For the sake of backwards compatibility they will not crash the game and `NormalTravelSpeed` is a valid synonym for the new `TravelSpeed`. +- Removed the `GATutorial` activity type, which only the tutorial mission used. `Settings.ini` files with `DefaultActivityType = GATutorial` are read as `GAScripted`. +
## [Release v6.2.2] - 2024/02/24 diff --git a/Data/Base.rte/Activities.ini b/Data/Base.rte/Activities.ini index ce172ef4a6..744a543d2f 100644 --- a/Data/Base.rte/Activities.ini +++ b/Data/Base.rte/Activities.ini @@ -5,9 +5,12 @@ // Switch enabled state follows in the same order. -AddActivity = GATutorial +AddActivity = GAScripted PresetName = Tutorial Mission + Description = A tutorial for learning how to play Cortex Command. A good place to start! SceneName = Tutorial Bunker + ScriptPath = Missions.rte/Activities/Tutorial.lua + LuaClassName = TutorialMission InCampaignStage = 1 TeamOfPlayer1 = 0 TeamOfPlayer2 = 1 @@ -20,6 +23,9 @@ AddActivity = GATutorial RequireClearPathToOrbitSwitchEnabled = 0 FogOfWarSwitchEnabled = 0 DeployUnitsSwitchEnabled = 0 + AddRequiredArea = Tutorial Bunker + AddRequiredArea = LZ Team 1 + AddRequiredArea = AIAttack AddActivity = GAScripted diff --git a/Data/Missions.rte/Activities/Tutorial.lua b/Data/Missions.rte/Activities/Tutorial.lua new file mode 100644 index 0000000000..ad363a7683 --- /dev/null +++ b/Data/Missions.rte/Activities/Tutorial.lua @@ -0,0 +1,386 @@ +local imagePath = "Missions.rte/Objects/Tutorial/"; + +-- The screens and room signs are drawn onto the bunker's back wall, so they show in front of the terrain background layer but behind the terrain foreground and actors. +local wallDrawDepth = DrawDepth.TerrainBackground - 1; + +local screenHalfSize = Vector(23, 20); +local signHalfSize = Vector(24, 24); + +local screenOffImage = imagePath .. "ScreenStatic000.png"; +local screenStaticLittleImage = imagePath .. "ScreenStatic001.png"; +local screenStaticLargeImage = imagePath .. "ScreenStatic002.png"; + +local function TutorialStep(text, duration, imageName, frameCount, frameDuration) + local step = { text = text, duration = duration, frameDuration = frameDuration or 250, frames = {} }; + for frame = 0, frameCount - 1 do + step.frames[frame + 1] = imagePath .. imageName .. string.format("%03d", frame) .. ".png"; + end + return step; +end + +function TutorialMission:StartActivity(isNewGame) + self.tutorialPlayer = Activity.PLAYER_1; + + for team = Activity.TEAM_1, Activity.MAXTEAMCOUNT - 1 do + if self:TeamActive(team) and team ~= self.CPUTeam then + self:SetTeamFunds(self:GetStartingGold(), team); + end + end + self:SetLZArea(Activity.TEAM_1, SceneMan.Scene:GetArea("LZ Team 1")); + + for player = Activity.PLAYER_1, Activity.MAXPLAYERCOUNT - 1 do + if self:PlayerActive(player) and self:PlayerHuman(player) then + -- Expand all modules to show the goods + self:GetBuyGUI(player):SetModuleExpanded(0, true); + -- No need for the LZ to follow the brain, the scene-set one is enough + self:SetBrainLZWidth(player, 0); + + if self:GetPlayerBrain(player) then + self.tutorialPlayer = player; + else + ConsoleMan:PrintString("ERROR: Can't find brain for tutorial game mode!"); + end + end + end + + self.CPUBrain = MovableMan:GetUnassignedBrain(self.CPUTeam) or MovableMan:GetFirstBrainActor(self.CPUTeam); + self.CPUActorCount = self:CountCPUActors(); + + -- Keep everyone in place, the practice bodies included, and disable all enemy AIs so they don't attack prematurely + for team = Activity.TEAM_1, Activity.MAXTEAMCOUNT - 1 do + self:SetTeamAIMode(team, Actor.AIMODE_SENTRY); + end + self:DisableAIs(true, self.CPUTeam); + self.fightTrigger = SceneMan.Scene:GetArea("AIAttack"); + self.fightStarted = false; + + self.roomSigns = { + { pos = Vector(744, 695), name = "TutEntryA" }, + { pos = Vector(888, 695), name = "TutEntryB" }, + { pos = Vector(888, 599), name = "TutEntryC" }, + { pos = Vector(888, 431), name = "TutEntryD" } + }; + self:SetupAreas(); + + self.areaTimer = Timer(); + self.stepTimer = Timer(); + self.currentArea = 1; + self.previousArea = nil; + self.currentStep = 1; + self.currentRoom = self.areas[self.currentArea].room; + + MusicMan:PlayDynamicSong("Generic Battle Music"); +end + +function TutorialMission:PauseActivity(pause) + -- Re-setup the areas with any updated control mappings that the player might have made in the menu + if not pause then + self:SetupAreas(); + end +end + +function TutorialMission:EndActivity() + -- UpdateActivity stops running once the game is over, so clear the markers it would otherwise keep re-adding + self:ClearObjectivePoints(); + + local playerWon = false; + for player = Activity.PLAYER_1, Activity.MAXPLAYERCOUNT - 1 do + if self:PlayerActive(player) and self:PlayerHuman(player) then + local screen = self:ScreenOfPlayer(player); + FrameMan:ClearScreenText(screen); + if self:GetTeamOfPlayer(player) == self.WinnerTeam then + playerWon = true; + FrameMan:SetScreenText("You destroyed the dummy CPU!\nPress [SPACE] or [START] to continue", screen, 0, -1, false); + else + FrameMan:SetScreenText("Your brain has been destroyed!", screen, 0, -1, false); + end + end + end + + -- Temp fix so music doesn't start playing if ending the Activity when changing resolution through the in-game settings. + if not self:IsPaused() then + if playerWon then + MusicMan:PlayDynamicSong("Generic Victory Music", "Default", true); + else + MusicMan:PlayDynamicSong("Generic Defeat Music", "Default", true); + end + MusicMan:PlayDynamicSong("Generic Ambient Music"); + end +end + +function TutorialMission:UpdateActivity() + -- Clear all objective markers, they get re-added each frame + self:ClearObjectivePoints(); + + for player = Activity.PLAYER_1, Activity.MAXPLAYERCOUNT - 1 do + if self:PlayerActive(player) and self:PlayerHuman(player) and self.ActivityState ~= Activity.OVER then + local team = self:GetTeamOfPlayer(player); + local brain = self:GetPlayerBrain(player); + if not brain or not MovableMan:IsActor(brain) then + self:SetPlayerBrain(nil, player); + FrameMan:SetScreenText("Your brain has been destroyed!", self:ScreenOfPlayer(player), 333, -1, false); + + -- Now see if all brains of this player's team are dead, and if so, end the game + if not MovableMan:GetFirstBrainActor(team) then + self.WinnerTeam = self:OtherTeam(team); + ActivityMan:EndActivity(); + end + self:ResetMessageTimer(player); + elseif self.fightStarted then + self:AddObjectivePoint("Protect!", brain.Pos + Vector(0, 10), team, GameActivity.ARROWUP); + if MovableMan:IsActor(self.CPUBrain) then + self:AddObjectivePoint("Destroy!", self.CPUBrain.Pos + Vector(0, 12), team, GameActivity.ARROWUP); + end + end + end + end + + self:UpdateTutorialAreas(); + self:DrawTutorialAreas(); + + local controlledActor = self:GetControlledActor(self.tutorialPlayer); + if not self.fightStarted and ((controlledActor and self.fightTrigger:IsInside(controlledActor.Pos)) or self:CountCPUActors() < self.CPUActorCount) then + local screen = self:ScreenOfPlayer(self.tutorialPlayer); + self:ResetMessageTimer(self.tutorialPlayer); + FrameMan:ClearScreenText(screen); + FrameMan:SetScreenText("DEFEND YOUR BRAIN AGAINST THE INCOMING FORCES!", screen, 500, 8000, true); + self:SetTeamAIMode(self.CPUTeam, Actor.AIMODE_BRAINHUNT); + self:DisableAIs(false, self.CPUTeam); + self.fightStarted = true; + end + + if self.ActivityState ~= Activity.OVER and not MovableMan:IsActor(self.CPUBrain) then + self.WinnerTeam = Activity.TEAM_1; + ActivityMan:EndActivity(); + end +end + +function TutorialMission:SetTeamAIMode(team, aiMode) + for _, actors in ipairs({ MovableMan.Actors, MovableMan.AddedActors }) do + for actor in actors do + if actor.Team == team and (IsAHuman(actor) or IsACrab(actor)) and not actor:IsPlayerControlled() then + actor.AIMode = aiMode; + end + end + end +end + +function TutorialMission:CountCPUActors() + local count = 0; + for actor in MovableMan.Actors do + if actor.Team == self.CPUTeam then + count = count + 1; + end + end + for actor in MovableMan.AddedActors do + if actor.Team == self.CPUTeam then + count = count + 1; + end + end + return count; +end + +function TutorialMission:UpdateTutorialAreas() + -- Switch to a new area when the player's actor enters its trigger box + local controlledActor = self:GetControlledActor(self.tutorialPlayer); + if controlledActor then + for areaIndex, area in ipairs(self.areas) do + if areaIndex ~= self.currentArea and area.triggerBox:IsWithinBox(controlledActor.Pos) then + self.previousArea = self.currentArea; + self.currentArea = areaIndex; + self.currentStep = 1; + self.areaTimer:Reset(); + self.stepTimer:Reset(); + end + end + end + + -- Cycle through the steps of the current area, looping around to the first + local steps = self.areas[self.currentArea].steps; + if self.stepTimer:IsPastRealMS(steps[self.currentStep].duration) then + self.currentStep = self.currentStep % #steps + 1; + self.stepTimer:Reset(); + end + + self.currentRoom = self.areas[self.currentArea].room or self.currentRoom; +end + +function TutorialMission:DrawTutorialAreas() + local primitives = {}; + local areaTime = self.areaTimer.ElapsedRealTimeMS; + + -- The current area's screen turns on through static, the previous area's turns off through static, and all others stay off + for areaIndex, area in ipairs(self.areas) do + local image = screenOffImage; + if areaIndex == self.currentArea then + if areaTime > 200 then + local step = area.steps[self.currentStep]; + image = step.frames[math.floor(self.stepTimer.ElapsedRealTimeMS / step.frameDuration) % #step.frames + 1]; + else + image = areaTime > 100 and screenStaticLargeImage or screenStaticLittleImage; + end + elseif areaIndex == self.previousArea and areaTime <= 200 then + image = areaTime > 100 and screenStaticLittleImage or screenStaticLargeImage; + end + table.insert(primitives, BitmapPrimitive(-1, area.screenPos + screenHalfSize, image, 0, false, false, wallDrawDepth)); + end + + -- Light up the signs of the rooms reached so far, and blink the next room's sign + for roomIndex, sign in ipairs(self.roomSigns) do + local lit = roomIndex <= self.currentRoom or (roomIndex == self.currentRoom + 1 and self.areaTimer:AlternateReal(200)); + local image = imagePath .. sign.name .. (lit and "001.png" or "000.png"); + table.insert(primitives, BitmapPrimitive(-1, sign.pos + signHalfSize, image, 0, false, false, wallDrawDepth)); + end + + PrimitiveMan:DrawPrimitives(DrawBlendMode.NoBlend, 0, primitives); + + -- Reveal the current step's text one character at a time, then blink dots after it + local area = self.areas[self.currentArea]; + local text = area.steps[self.currentStep].text; + local stepDuration = area.steps[self.currentStep].duration; + local revealPeriod = math.min((#text + 3) * 30, stepDuration * 0.85); + local revealed = math.min(self.stepTimer.ElapsedRealTimeMS / revealPeriod, 1); + local revealText = text:sub(1, math.floor((#text + 3) * revealed)); + if revealed == 1 then + local timePhase = areaTime % 1200; + revealText = revealText .. (timePhase > 900 and "..." or (timePhase > 600 and ".. " or (timePhase > 300 and ". " or " "))); + end + PrimitiveMan:DrawTextPrimitive(area.screenPos + area.textOffset, revealText, true, 1); +end + +function TutorialMission:SetupAreas() + local controlScheme = UInputMan:GetControlScheme(self.tutorialPlayer); + local function MappingName(inputElement) + return controlScheme:GetMappingName(inputElement); + end + + local prevName = MappingName(InputElements.INPUT_PREV); + local nextName = MappingName(InputElements.INPUT_NEXT); + local jumpName = MappingName(InputElements.INPUT_JUMP); + local proneName = MappingName(InputElements.INPUT_PRONE); + + local pieName = MappingName(InputElements.INPUT_PIEMENU_ANALOG); + if pieName == "" then + pieName = MappingName(InputElements.INPUT_PIEMENU_DIGITAL); + end + local fireName = MappingName(InputElements.INPUT_FIRE); + + -- If no preset, adjust the pie menu and fire names when using the defaults on a gamepad, otherwise they'll show up as unhelpful axis names + if controlScheme.Device >= InputDevice.DEVICE_GAMEPAD_1 and controlScheme.Preset == InputScheme.NoPreset then + pieName = "Pie Menu Trigger"; + fireName = "Fire Trigger"; + end + + local textBelowScreen = Vector(screenHalfSize.X, screenHalfSize.Y * 2 + 4); + local textAboveScreen = Vector(screenHalfSize.X, -16); + + -- The room field is the room sign this area lights up to; areas without one keep the current room + self.areas = { + { + -- Brain chamber + triggerBox = Box(Vector(631, 608), 137, 155), + screenPos = Vector(673, 688), + textOffset = textBelowScreen, + room = 1, + steps = { + TutorialStep("Welcome to Cortex Command", 4000, "CCLogo", 2), + TutorialStep("Above is your disembodied brain", 4000, "ArrowUp", 2), + TutorialStep("With it, you can remotely control other bodies", 4000, "BodyZap", 2), + TutorialStep("Switch to one now by using [" .. prevName .. "] or [" .. nextName .. "]", 4000, "BodyZap", 2), + TutorialStep("If you haven't set up your controls yet, hit [Esc] and do so", 8000, "Joystick", 2) + } + }, + { + -- Body storage + triggerBox = Box(Vector(911, 662), 397, 98), + screenPos = Vector(961, 688), + textOffset = textBelowScreen, + room = 2, + steps = { + TutorialStep("Here are some dummy bodies for practice", 4000, "BodyHop", 2), + TutorialStep("Quickly switch control between them left and right with [" .. prevName .. "] and [" .. nextName .. "]", 6000, "BodyZap", 2), + TutorialStep("Or hold down either [" .. prevName .. "] or [" .. nextName .. "] to get a selection cursor", 6000, "BodyZap", 2), + TutorialStep("Switch to the leftmost body and walk it out of the room with [" .. MappingName(InputElements.INPUT_L_LEFT) .. "]", 8000, "ArrowLeft", 2) + } + }, + { + -- Shaft + triggerBox = Box(Vector(772, 385), 135, 380), + screenPos = Vector(817, 688), + textOffset = textBelowScreen, + steps = { + TutorialStep("Use [" .. jumpName .. "] to activate jetpack", 4000, "ArrowUp", 2), + TutorialStep("Fire jetpack in bursts for better control", 4000, "BodyJetpack", 2), + TutorialStep("Adjust the jet direction by aiming or looking", 4000, "BodyJetpack", 2), + TutorialStep("Jump height is affected by the body's total weight", 4000, "BodyJetpack", 2), + TutorialStep("So the more you carry, the less you can jump", 8000, "BodyJetpack", 2) + } + }, + { + -- Obstacle course + triggerBox = Box(Vector(915, 492), 395, 167), + screenPos = Vector(961, 592), + textOffset = textBelowScreen, + room = 3, + steps = { + TutorialStep("Obstacle course", 2000, "BodyHop", 2), + TutorialStep("Climb obstacles by holding [" .. MappingName(InputElements.INPUT_L_RIGHT) .. "]", 4000, "ArrowRight", 2), + TutorialStep("Even climb ladders by simply moving toward them", 8000, "BodyClimb", 2), + TutorialStep("You can also go prone and crawl with [" .. proneName .. "]", 6000, "BodyCrawl", 2), + TutorialStep("In tight spaces, you may need to angle your head down to get through", 8000, "BodyCrawl", 2) + } + }, + { + -- Firing range + triggerBox = Box(Vector(913, 394), 389, 97), + screenPos = Vector(961, 424), + textOffset = textBelowScreen, + room = 4, + steps = { + TutorialStep("Firing range", 2000, "FireTarget", 1), + TutorialStep("Pick up the weapon by first standing over it", 4000, "ArrowRight", 2), + TutorialStep("And then hold down [" .. pieName .. "]", 4000, "MenuPickUp", 1, 500), + TutorialStep("Point up to 'Pick Up'", 4000, "MenuPickUp", 2, 500), + TutorialStep("Release [" .. pieName .. "] to complete the command", 4000, "MenuPickUp", 1), + TutorialStep("If you aim continuously toward your target", 4000, "BodyAim", 3), + TutorialStep("It improves your accuracy and view distance", 4000, "BodyAim", 3), + TutorialStep("Use [" .. fireName .. "] to Fire!", 4000, "BodyFire", 2), + TutorialStep("Reload manually with [" .. pieName .. "] + up", 8000, "BodyFire", 2) + } + }, + { + -- Rooftop + triggerBox = Box(Vector(732, 176), 356, 210), + screenPos = Vector(961, 316), + textOffset = textAboveScreen, + steps = { + TutorialStep("Pick up the digging tool", 4000, "MenuPickUp", 2, 500), + TutorialStep("Use it on the dirt here", 8000, "DigPile", 2, 750), + TutorialStep("If you dig up gold, it is added to your team's funds", 4000, "Funds", 2, 250), + TutorialStep("Funds can be spent in the Buy Menu", 4000, "Funds", 1, 333), + TutorialStep("Which is opened through the Command Menu", 4000, "MenuBuyMenu", 1, 500), + TutorialStep("Hold [" .. pieName .. "] and point up and left to 'Buy Menu'", 6000, "MenuBuyMenu", 2, 500), + TutorialStep("The Buy Menu works like a shopping cart", 6000, "BuyMenuCargo", 1, 500), + TutorialStep("Add to the Cargo list the items you want delivered", 6000, "BuyMenuCargo", 2, 500), + TutorialStep("Then use the BUY button, or click outside the menu", 4000, "BuyMenuBuy", 2, 500), + TutorialStep("Finally select a flat area where you want the goods delivered", 8000, "BuyMenuBuy", 1, 500), + TutorialStep("Next, you can go explore to the west to try flying and climbing in the wild", 6000, "ArrowLeft", 2), + TutorialStep("Or, go to the east to learn about squads!", 8000, "ArrowRight", 2) + } + }, + { + -- Roof east + triggerBox = Box(Vector(1100, 176), 200, 210), + screenPos = Vector(1201, 316), + textOffset = textAboveScreen, + steps = { + TutorialStep("Hold [" .. pieName .. "] and point down and right to 'Form Squad'", 4000, "MenuTeam", 2, 500), + TutorialStep("Adjust selection circle to select nearby bodies", 4000, "TeamSelect", 4, 500), + TutorialStep("All selected units will follow you, and engage on their own", 4000, "TeamFollow", 2, 500), + TutorialStep("Units with similar weapons will fire in unison with the leader", 4000, "TeamFollow", 2, 500), + TutorialStep("Hold [" .. pieName .. "] and point down and right again to disband squad", 4000, "MenuTeam", 2, 500), + TutorialStep("Next, you can head east for a TRIAL BATTLE!", 8000, "ArrowRight", 2) + } + } + }; +end diff --git a/RTEA.vcxproj b/RTEA.vcxproj index 731c2ad830..e335a651bd 100644 --- a/RTEA.vcxproj +++ b/RTEA.vcxproj @@ -935,7 +935,6 @@ - @@ -1463,7 +1462,6 @@ - diff --git a/RTEA.vcxproj.filters b/RTEA.vcxproj.filters index e5f13d5197..6bf1935141 100644 --- a/RTEA.vcxproj.filters +++ b/RTEA.vcxproj.filters @@ -388,9 +388,6 @@ Activities - - Activities - Activities @@ -985,9 +982,6 @@ Activities - - Activities - Activities diff --git a/Source/Activities/GATutorial.cpp b/Source/Activities/GATutorial.cpp deleted file mode 100644 index b4cfa88e45..0000000000 --- a/Source/Activities/GATutorial.cpp +++ /dev/null @@ -1,874 +0,0 @@ -#include "GATutorial.h" - -#include "SceneMan.h" -#include "PresetMan.h" -#include "MovableMan.h" -#include "FrameMan.h" -#include "UInputMan.h" -#include "ConsoleMan.h" -#include "AudioMan.h" -#include "AHuman.h" -#include "ACrab.h" -#include "SLTerrain.h" -#include "Controller.h" -#include "Scene.h" -#include "Actor.h" - -#include "GUI.h" -#include "GUIFont.h" -#include "AllegroBitmap.h" -#include "BuyMenuGUI.h" -#include "MusicMan.h" -#include "SceneEditorGUI.h" - -#define MAPNAME(element) g_UInputMan.GetControlScheme(m_TutorialPlayer)->GetMappingName(element) - -using namespace RTE; - -ConcreteClassInfo(GATutorial, GameActivity, 0); - -GATutorial::GATutorial() { - Clear(); -} - -GATutorial::~GATutorial() { - Destroy(true); -} - -GATutorial::TutStep::TutStep(std::string text, int stepDuration, const std::string& screensPath, int frameCount, int frameDuration) : - m_Text(std::move(text)), - m_Duration(stepDuration), - m_FrameDuration(frameDuration) - { - - if (!screensPath.empty()) { - ContentFile(screensPath.c_str()).GetAsAnimation(m_pScreens, frameCount); - } -} - -void GATutorial::Clear() { - m_TutorialPlayer = Players::PlayerOne; - - for (int area = 0; area < AREACOUNT; ++area) { - m_TriggerBoxes[area].Reset(); - m_ScreenPositions[area].Reset(); - m_ScreenStates[area] = SCREENOFF; - m_TextOffsets[area].Reset(); - m_TutAreaSteps[area].clear(); - } - for (int room = 0; room < ROOMCOUNT; ++room) { - m_RoomSignPositions[room].Reset(); - m_aapRoomSigns[room][UNLIT] = 0; - m_aapRoomSigns[room][LIT] = 0; - } - m_AreaTimer.Reset(); - m_StepTimer.Reset(); - m_CurrentArea = BRAINCHAMBER; - m_PrevArea = BRAINCHAMBER; - m_ScreenChange = false; - m_CurrentStep = 0; - m_CurrentFrame = 0; - m_CurrentRoom = ROOM0; - - for (int stage = 0; stage < FIGHTSTAGECOUNT; ++stage) - m_FightTriggers[stage].Reset(); - - m_EnemyCount = 0; - m_CurrentFightStage = NOFIGHT; - m_pCPUBrain = 0; -} - -int GATutorial::Create() { - if (GameActivity::Create() < 0) - return -1; - - m_Description = "A tutorial for learning how to play Cortex Command. A good place to start!"; - - return 0; -} - -int GATutorial::Create(const GATutorial& reference) { - if (GameActivity::Create(reference) < 0) - return -1; - - for (int area = 0; area < AREACOUNT; ++area) { - m_TriggerBoxes[area] = reference.m_TriggerBoxes[area]; - m_ScreenPositions[area] = reference.m_ScreenPositions[area]; - m_ScreenStates[area] = reference.m_ScreenStates[area]; - m_TextOffsets[area] = reference.m_TextOffsets[area]; - m_TutAreaSteps[area] = reference.m_TutAreaSteps[area]; - } - for (int room = 0; room < ROOMCOUNT; ++room) { - m_RoomSignPositions[room] = reference.m_RoomSignPositions[room]; - } - m_AreaTimer.Reset(); - m_StepTimer.Reset(); - m_CurrentArea = reference.m_CurrentArea; - m_PrevArea = reference.m_PrevArea; - m_CurrentStep = reference.m_CurrentStep; - m_CurrentFrame = reference.m_CurrentFrame; - m_CurrentRoom = reference.m_CurrentRoom; - - for (int stage = 0; stage < FIGHTSTAGECOUNT; ++stage) - m_FightTriggers[stage] = reference.m_FightTriggers[stage]; - - m_EnemyCount = reference.m_EnemyCount; - m_CurrentFightStage = reference.m_CurrentFightStage; - // DOn't, owned and need to make deep copy in that case - // m_pCPUBrain; - - return 0; -} - -int GATutorial::ReadProperty(const std::string_view& propName, Reader& reader) { - StartPropertyList(return GameActivity::ReadProperty(propName, reader)); - /* - MatchProperty("SpawnIntervalEasiest", { reader >> m_SpawnIntervalEasiest; }); - MatchProperty("SpawnIntervalHardest", { reader >> m_SpawnIntervalHardest; }); - MatchProperty("AddAttackerSpawn", - Actor *pNewSpawn = dynamic_cast(g_PresetMan.ReadReflectedPreset(reader)); - if (pNewSpawn) { m_AttackerSpawns.push_back(pNewSpawn); } ); - */ - EndPropertyList; -} - -int GATutorial::Save(Writer& writer) const { - GameActivity::Save(writer); - return 0; -} - -void GATutorial::Destroy(bool notInherited) { - if (!notInherited) - GameActivity::Destroy(); - Clear(); -} - -bool GATutorial::SceneIsCompatible(Scene* pScene, int teams) { - if (!GameActivity::SceneIsCompatible(pScene, teams)) - return false; - - // Quick and dirty hardcoded hack.. only this scene is compatible anyway - if (pScene->GetPresetName() == "Tutorial Bunker") - return true; - - return false; -} - -int GATutorial::Start() { - int error = GameActivity::Start(); - - //////////////////////////////// - // Set up teams - - // Team 2 is always CPU - SetCPUTeam(Teams::TeamTwo); - - for (int team = Teams::TeamOne; team < Teams::MaxTeamCount; ++team) { - if (!m_TeamActive[team]) - continue; - - if (team == Teams::TeamOne) { - // See if there are specified landing zone areas defined in the scene - char str[64]; - std::snprintf(str, sizeof(str), "LZ Team %d", team + 1); - Scene::Area* pArea = g_SceneMan.GetScene()->GetArea(str); - // pArea = pArea ? pArea : g_SceneMan.GetScene()->GetArea("Landing Zone"); - // If area is defined, save a copy so we can lock the LZ selection to within its boxes - if (pArea && !pArea->HasNoArea()) - SetLZArea(team, *pArea); - } - - // If this is a CPU controlled team, then try to find a brain for them, or place one - if (team == m_CPUTeam && !m_pCPUBrain) { - // TODO: Make special CPU brain actor which will only appear in CPU brain fights, and have to be placed in the scenes - m_pCPUBrain = g_MovableMan.GetUnassignedBrain(team); - m_pCPUBrain = m_pCPUBrain ? m_pCPUBrain : g_MovableMan.GetFirstBrainActor(team); // Try getting our assigned brain - if (!m_pCPUBrain) { - // Couldn't find an available brain in the scene, so make one and place it - m_pCPUBrain = dynamic_cast(g_PresetMan.GetEntityPreset("Actor", "Brain Case")->Clone()); - if (m_pCPUBrain) { - Vector brainPos; - // Place opposite of the other team's brain - Actor* pOtherBrain = g_MovableMan.GetFirstOtherBrainActor(team); - if (pOtherBrain) { - brainPos = pOtherBrain->GetPos(); - brainPos.m_X += g_SceneMan.GetSceneWidth() / 2; - brainPos.m_Y = g_SceneMan.GetSceneHeight() - 200; - g_SceneMan.WrapPosition(brainPos); - } - // No brain found on other team... then just place somewhere in the ground, spaced out - else { - if (team == Teams::TeamOne) - brainPos.SetXY((float)g_SceneMan.GetSceneWidth() * 0.25, (float)g_SceneMan.GetSceneHeight() * 0.75); - if (team == Teams::TeamTwo) - brainPos.SetXY((float)g_SceneMan.GetSceneWidth() * 0.75, (float)g_SceneMan.GetSceneHeight() * 0.75); - } - m_pCPUBrain->SetPos(brainPos); - m_pCPUBrain->SetTeam(team); - // Transfer ownership here - g_MovableMan.AddActor(m_pCPUBrain); - pOtherBrain = 0; - } - } - m_EnemyCount = g_MovableMan.GetTeamRoster(m_CPUTeam)->size(); - } - // Give the player some scratch - else - m_TeamFunds[team] = this->GetStartingGold(); - } - - /////////////////////////////////////// - // Set up players - - for (int player = Players::PlayerOne; player < Players::MaxPlayerCount; ++player) { - if (!(m_IsActive[player] && m_IsHuman[player])) - continue; - - // Expand all modules to show the goods - m_pBuyGUI[player]->SetModuleExpanded(0); - - // No need for the LZ to follow the brain, the scene-set one is enough - SetBrainLZWidth(player, 0); - - // If we can't find an unassigned brain in the scene to give each player, then force to go into editing mode to place one - if (!m_Brain[player]) { - g_ConsoleMan.PrintString("ERROR: Can't find brain for tutorial game mode!"); - } - // Set the found brain to be the selected actor at start - else { - m_TutorialPlayer = player; - } - /* - if (m_ActivityState == ActivityState::Editing) - g_FrameMan.SetScreenText((player % 2 == 0) ? "Place your brain vault and build your bunker around it..." : "...then select \"DONE\" from the pie menu!", ScreenOfPlayer(player), 0); - else if (m_ActivityState == ActivityState::Running) - g_FrameMan.SetScreenText((player % 2 == 0) ? "Mine Gold and buy more firepower with the funds..." : "...then smash the competing brain to claim victory!", ScreenOfPlayer(player), 0); - */ - } - - ///////////////////////////////////////////// - // SET UP TUTORIAL - - // COMMON SCREENS - std::vector apScreens = ContentFile("Missions.rte/Objects/Tutorial/ScreenStatic.png").GetAsAnimation(3); - m_apCommonScreens[SCREENOFF] = apScreens[0]; - m_apCommonScreens[STATICLITTLE] = apScreens[1]; - m_apCommonScreens[STATICLARGE] = apScreens[2]; - - // ROOM SIGNS - ContentFile signFile; - for (int room = 0; room < ROOMCOUNT; ++room) { - if (room == ROOM0) - signFile.SetDataPath("Missions.rte/Objects/Tutorial/TutEntryA.png"); - else if (room == ROOM1) - signFile.SetDataPath("Missions.rte/Objects/Tutorial/TutEntryB.png"); - else if (room == ROOM2) - signFile.SetDataPath("Missions.rte/Objects/Tutorial/TutEntryC.png"); - else if (room == ROOM3) - signFile.SetDataPath("Missions.rte/Objects/Tutorial/TutEntryD.png"); - std::vector apSigns = signFile.GetAsAnimation(2); - m_aapRoomSigns[room][UNLIT] = apSigns[0]; - m_aapRoomSigns[room][LIT] = apSigns[1]; - } - - m_RoomSignPositions[ROOM0].SetXY(744, 695); - m_RoomSignPositions[ROOM1].SetXY(888, 695); - m_RoomSignPositions[ROOM2].SetXY(888, 599); - m_RoomSignPositions[ROOM3].SetXY(888, 431); - m_CurrentRoom = ROOM3; - - ////////////////////////////////// - // Setup all the Tutorial Areas and their text instructions etc - - SetupAreas(); - - // Disable all enemy AIs so they dont attack prematurely - DisableAIs(true, Teams::TeamTwo); - - ////////////////////////////////// - // FIGHT TRIGGERS - - m_FightTriggers[DEFENDING].Create(Vector(1330, 0), 770, 400); - - // m_FightTriggerEast.Create(Vector(526, 0), 52, 840); - // m_FightTriggerWest.Create(Vector(1336, 0), 52, 840); - - // Start music - g_MusicMan.PlayDynamicSong("Generic Battle Music"); - return error; -} - -void GATutorial::SetPaused(bool pause) { - GameActivity::SetPaused(pause); - - // Re-setup the ares with any updated control mappings that the player might have made in teh menu - if (!pause) - SetupAreas(); -} - -void GATutorial::End() { - GameActivity::End(); - - bool playerWon = false; - // Show appropriate end game messages - for (int player = Players::PlayerOne; player < Players::MaxPlayerCount; ++player) { - if (!(m_IsActive[player] && m_IsHuman[player])) - continue; - - if (m_Team[player] == m_WinnerTeam) { - playerWon = true; - // Set the winner's observation view to his controlled actors instead of his brain - if (m_ControlledActor[player] && g_MovableMan.IsActor(m_ControlledActor[player])) - m_ObservationTarget[player] = m_ControlledActor[player]->GetPos(); - } - } - - // Temp fix so music doesn't start playing if ending the Activity when changing resolution through the in-game settings. - if (!m_Paused) { - // Play the appropriate tune on player win/lose - if (playerWon) { - g_MusicMan.PlayDynamicSong("Generic Victory Music", "Default", true); - g_MusicMan.PlayDynamicSong("Generic Ambient Music"); - } else { - g_MusicMan.PlayDynamicSong("Generic Defeat Music", "Default", true); - g_MusicMan.PlayDynamicSong("Generic Ambient Music"); - } - } -} - -/* -void GATutorial::UpdateEditing() -{ - GameActivity::UpdateEditing(); -} -*/ - -void GATutorial::Update() { - // Avoid game logic when we're editing - if (m_ActivityState == ActivityState::Editing) { - UpdateEditing(); - return; - } - - GameActivity::Update(); - - // Clear all objective markers, they get re-added each frame - ClearObjectivePoints(); - - /////////////////////////////////////////// - // Iterate through all human players - - for (int player = Players::PlayerOne; player < Players::MaxPlayerCount; ++player) { - if (!(m_IsActive[player] && m_IsHuman[player])) - continue; - // The current player's team - int team = m_Team[player]; - if (team == Teams::NoTeam) - continue; - - // Make sure the game is not already ending - if (m_ActivityState != ActivityState::Over) { - // Check if any player's brain is dead - if (!g_MovableMan.IsActor(m_Brain[player])) { - m_Brain[player] = 0; - g_FrameMan.SetScreenText("Your brain has been destroyed!", ScreenOfPlayer(player), 333); - - // Now see if all brains are dead of this player's team, and if so, end the game - if (!g_MovableMan.GetFirstBrainActor(team)) { - m_WinnerTeam = OtherTeam(team); - End(); - } - - m_MessageTimer[player].Reset(); - } - // Mark the player brain to be protected when the fight happens - else if (m_CurrentFightStage >= DEFENDING) { - // Update the observation target to the brain, so that if/when it dies, the view flies to it in observation mode - // SetObservationTarget(m_Brain[player]->GetPos(), player); - // Mark the player's brain to be protected by his team - AddObjectivePoint("Protect!", m_Brain[player]->GetPos() + Vector(0, 10), team, GameActivity::ARROWUP); - // Mark the CPU brain for desctruction too - if (g_MovableMan.IsActor(m_pCPUBrain)) - AddObjectivePoint("Destroy!", m_pCPUBrain->GetPos() + Vector(0, 12), team, GameActivity::ARROWUP); - } - } - // Game over, show the appropriate messages until a certain time - else if (!m_GameOverTimer.IsPastSimMS(m_GameOverPeriod)) { - // TODO: make more appropriate messages here for run out of funds endings - if (m_Team[player] == m_WinnerTeam) - g_FrameMan.SetScreenText("You destroyed the dummy CPU!\nPress [SPACE] or [START] to continue", ScreenOfPlayer(player)); - else - g_FrameMan.SetScreenText("Your brain has been destroyed!", ScreenOfPlayer(player)); - } - } - - /////////////////////////////////////////// - // Iterate through all teams - - for (int team = Teams::TeamOne; team < Teams::MaxTeamCount; ++team) { - if (!m_TeamActive[team]) - continue; - ///////////////////////////////////////// - // Attacking CPU team logic - /* - if (team == m_CPUTeam) - { - // Spawn the CPU team's attacking forces - if (m_SpawnTimer.IsPastSimMS(m_SpawnInterval) && m_ActivityState != ActivityState::Over) - { - if (!m_AttackerSpawns.empty()) - { - int whichSpawn = std::floor(m_AttackerSpawns.size() * RandomNum()); - Actor *pSpawn = dynamic_cast(m_AttackerSpawns[whichSpawn]->Clone()); - if (pSpawn) - { - Vector landingZone; - Actor *pEnemyBrain = g_MovableMan.GetFirstOtherBrainActor(team); - - // Don't land right on top of player team's base - if (pEnemyBrain) - { - // Get player team's base pos - landingZone = pEnemyBrain->GetPos(); - // Get the opposite side - landingZone.m_X += g_SceneMan.GetSceneWidth() / 2; - // Now give the zone width - landingZone.m_X += (g_SceneMan.GetSceneWidth() / 2) * 0.75 * NormalRand(); - // Wrap - g_SceneMan.WrapPosition(landingZone); - } - else - { - landingZone.m_X = g_SceneMan.GetSceneWidth() * RandomNum(); - } - Vector dropStart(landingZone.m_X, -50); - pSpawn->SetPos(dropStart); - pSpawn->SetTeam(team); - pSpawn->SetControllerMode(Controller::CIM_AI); - // Let the spawn into the world, passing ownership - g_MovableMan.AddActor(pSpawn); - pSpawn = 0; - } - } - m_SpawnTimer.Reset(); - } - } - */ - /////////////////////////////////////////// - // Check for victory conditions - - // Make sure the game is not already ending - if (m_ActivityState != ActivityState::Over && team != m_CPUTeam) { - // TODO: Gotto have budget restrictions in this activity! - /* - // Check for bankruptcy - // TODO Don't hardcode the rocket cost! - if (m_TeamFunds[team] < 0)//&& Only brain is left of actors) - { - for (int player = Players::PlayerOne; player < Players::MaxPlayerCount; ++player) - { - if (!(m_IsActive[player] && m_IsHuman[player])) - continue; - - if (m_Team[player] == team) - g_FrameMan.SetScreenText("Your team can't afford any more transportation!", ScreenOfPlayer(player)); - else - { - g_FrameMan.SetScreenText("Your competition is bankrupt!", ScreenOfPlayer(player)); - m_WinnerTeam = m_Team[player]; - } - m_MessageTimer[player].Reset(); - } - End(); - } - */ - } - } - - /////////////////////////////////////////// - // TUTORIAL LOGIC - - // Detect the player going into new areas - if (m_ControlledActor[m_TutorialPlayer]) { - for (int area = 0; area < AREACOUNT; ++area) { - // Switch if within the trigger box of a new area - if (area != m_CurrentArea && m_TriggerBoxes[area].IsWithinBox(m_ControlledActor[m_TutorialPlayer]->GetPos())) { - // Change to the new area - m_PrevArea = m_CurrentArea; - m_CurrentArea = (TutorialArea)area; - m_CurrentStep = 0; - m_ScreenChange = true; - m_AreaTimer.Reset(); - m_StepTimer.Reset(); - } - } - /* - // Fight stage triggers - for (int stage = 0; stage < FIGHTSTAGECOUNT; ++stage) - { - - // Switch if within the trigger box of a new area - if (area != m_CurrentArea && m_TriggerBoxes[area].IsWithinBox(m_ControlledActor[m_TutorialPlayer]->GetPos())) - { - - if (m_FightTriggers[stage].Reset(); - } - */ - } - - // Cycle through the steps of the current area - if (m_StepTimer.IsPastRealMS(m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Duration)) { - // Go to next step, looping around to the first if necessary - if (++m_CurrentStep == m_TutAreaSteps[m_CurrentArea].size()) - m_CurrentStep = 0; - m_ScreenChange = true; - // Start timing the new step - m_StepTimer.Reset(); - } - - // Only mess with animation if there are more than one frame for this step and the frameduration is set to something - int frameCount = m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_pScreens.size(); - if (frameCount > 0 && m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_FrameDuration > 0) { - int newFrame = ((long)m_StepTimer.GetElapsedRealTimeMS() / m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_FrameDuration) % frameCount; - // Clamp the frame - newFrame = newFrame < 0 ? 0 : (newFrame >= frameCount ? frameCount - 1 : newFrame); - if (newFrame != m_CurrentFrame) { - m_CurrentFrame = newFrame; - m_ScreenChange = true; - } - } - - /* Draw this manually over the current screen in DrawGUI - // Take over control of screen messages - m_MessageTimer[m_TutorialPlayer].Reset(); - // Display the text of the current step - // g_FrameMan.ClearScreenText(); - g_FrameMan.SetScreenText(m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Text, 0, 500, -1, true);//, m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Duration); - */ - // Draw the correct current screens - BITMAP* pScreen = 0; - - // Turn ON the screen of the CURRENT area, animating the static-y frames and drawing them to the scene - // Then show the current step image - if (m_ScreenStates[m_CurrentArea] != SHOWINGSTEP || m_ScreenChange) { - // Figure out if to draw static or the step screen - m_ScreenStates[m_CurrentArea] = m_AreaTimer.IsPastRealMS(200) ? SHOWINGSTEP : (m_AreaTimer.IsPastRealMS(100) ? STATICLARGE : STATICLITTLE); - - pScreen = 0; - // Showing step image or a static screen? - if (m_ScreenStates[m_CurrentArea] == SHOWINGSTEP) { - if (m_CurrentFrame < frameCount) - pScreen = m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_pScreens[m_CurrentFrame]; - } else - pScreen = m_apCommonScreens[(int)(m_ScreenStates[m_CurrentArea])]; - - // Draw to the scene bg layer - if (pScreen) - blit(pScreen, g_SceneMan.GetTerrain()->GetBGColorBitmap(), 0, 0, m_ScreenPositions[m_CurrentArea].GetFloorIntX(), m_ScreenPositions[m_CurrentArea].GetFloorIntY(), pScreen->w, pScreen->h); - - m_ScreenChange = false; - } - - // Turn OFF the screen of all the other areas, animating the static-y frames and drawing them to the scene - for (int area = 0; area < AREACOUNT; ++area) { - pScreen = 0; - // Turn off all other areas - if (area != m_CurrentArea && m_ScreenStates[area] != SCREENOFF) { - m_ScreenStates[area] = m_AreaTimer.IsPastRealMS(200) ? SCREENOFF : (m_AreaTimer.IsPastRealMS(100) ? STATICLITTLE : STATICLARGE); - pScreen = m_apCommonScreens[(int)(m_ScreenStates[area])]; - if (pScreen) - blit(pScreen, g_SceneMan.GetTerrain()->GetBGColorBitmap(), 0, 0, m_ScreenPositions[area].GetFloorIntX(), m_ScreenPositions[area].GetFloorIntY(), pScreen->w, pScreen->h); - } - } - - //////////////////////// - // ROOM SIGNS - - // Translate current area to a room - TutorialRoom prevRoom = m_CurrentRoom; - if (m_CurrentArea == BRAINCHAMBER) - m_CurrentRoom = ROOM0; - else if (m_CurrentArea == BODYSTORAGE) - m_CurrentRoom = ROOM1; - else if (m_CurrentArea == OBSTACLECOURSE) - m_CurrentRoom = ROOM2; - else if (m_CurrentArea == FIRINGRANGE) - m_CurrentRoom = ROOM3; - - // Draw the correct currently lit or blinking signs - BITMAP* pSign = 0; - if (prevRoom != m_CurrentRoom) { - pSign = m_aapRoomSigns[ROOM0][m_CurrentRoom >= ROOM0 ? LIT : UNLIT]; - blit(pSign, g_SceneMan.GetTerrain()->GetBGColorBitmap(), 0, 0, m_RoomSignPositions[ROOM0].GetFloorIntX(), m_RoomSignPositions[ROOM0].GetFloorIntY(), pSign->w, pSign->h); - pSign = m_aapRoomSigns[ROOM1][m_CurrentRoom >= ROOM1 ? LIT : UNLIT]; - blit(pSign, g_SceneMan.GetTerrain()->GetBGColorBitmap(), 0, 0, m_RoomSignPositions[ROOM1].GetFloorIntX(), m_RoomSignPositions[ROOM1].GetFloorIntY(), pSign->w, pSign->h); - pSign = m_aapRoomSigns[ROOM2][m_CurrentRoom >= ROOM2 ? LIT : UNLIT]; - blit(pSign, g_SceneMan.GetTerrain()->GetBGColorBitmap(), 0, 0, m_RoomSignPositions[ROOM2].GetFloorIntX(), m_RoomSignPositions[ROOM2].GetFloorIntY(), pSign->w, pSign->h); - pSign = m_aapRoomSigns[ROOM3][m_CurrentRoom >= ROOM3 ? LIT : UNLIT]; - blit(pSign, g_SceneMan.GetTerrain()->GetBGColorBitmap(), 0, 0, m_RoomSignPositions[ROOM3].GetFloorIntX(), m_RoomSignPositions[ROOM3].GetFloorIntY(), pSign->w, pSign->h); - } - // Blink the next room's sign - if (m_CurrentRoom < ROOM3) { - pSign = m_aapRoomSigns[m_CurrentRoom + 1][m_AreaTimer.AlternateReal(200) ? LIT : UNLIT]; - blit(pSign, g_SceneMan.GetTerrain()->GetBGColorBitmap(), 0, 0, m_RoomSignPositions[m_CurrentRoom + 1].GetFloorIntX(), m_RoomSignPositions[m_CurrentRoom + 1].GetFloorIntY(), pSign->w, pSign->h); - } - - //////////////////////// - // FIGHT LOGIC - - // Triggered defending stage - if (m_CurrentFightStage == NOFIGHT && ((m_ControlledActor[m_TutorialPlayer] && m_FightTriggers[DEFENDING].IsWithinBox(m_ControlledActor[m_TutorialPlayer]->GetPos())) || g_MovableMan.GetTeamRoster(m_CPUTeam)->size() < m_EnemyCount)) { - // Take over control of screen messages - m_MessageTimer[m_TutorialPlayer].Reset(); - // Display the text of the current step - g_FrameMan.ClearScreenText(ScreenOfPlayer(m_TutorialPlayer)); - g_FrameMan.SetScreenText("DEFEND YOUR BRAIN AGAINST THE INCOMING FORCES!", ScreenOfPlayer(m_TutorialPlayer), 500, 8000, true); - // This will make all the enemy team AI's go into brain hunt mode - GameActivity::InitAIs(); - DisableAIs(false, Teams::TeamTwo); - - // Advance the stage - m_CurrentFightStage = DEFENDING; - } - - /////////////////////////////////////////// - // Check for victory conditions - - // Check if the CPU brain is dead, if we're playing against the CPU - if (!g_MovableMan.IsActor(m_pCPUBrain) && m_ActivityState != ActivityState::Over) { - m_pCPUBrain = 0; - // Proclaim player winner and end - m_WinnerTeam = Teams::TeamOne; - // Finito! - End(); - } - - // After a while of game over and we won, exit to the campaign menu automatically - if (m_ActivityState == ActivityState::Over && m_WinnerTeam == Teams::TeamOne) { - if (m_GameOverTimer.IsPastSimMS(m_GameOverPeriod)) { - g_FrameMan.ClearScreenText(ScreenOfPlayer(m_TutorialPlayer)); - g_FrameMan.SetScreenText("Press [SPACE] or [START] to continue!", ScreenOfPlayer(m_TutorialPlayer), 750); - } - - if (m_GameOverTimer.IsPastSimMS(54000) || g_UInputMan.AnyStartPress()) { - g_ActivityMan.EndActivity(); - g_ActivityMan.SetInActivity(false); - } - } -} - -void GATutorial::DrawGUI(BITMAP* pTargetBitmap, const Vector& targetPos, int which) { - GameActivity::DrawGUI(pTargetBitmap, targetPos, which); - - if (IsRunning()) // && (m_AreaTimer.AlternateReal(500) || m_AreaTimer.AlternateReal(250) || m_AreaTimer.AlternateReal(125))) - { - AllegroBitmap pBitmapInt(pTargetBitmap); - Vector screenTextPos = m_ScreenPositions[m_CurrentArea] + m_TextOffsets[m_CurrentArea] - targetPos; - // How long the revealing of the text period will be, clamped, and plus three for the last three dots added later - float revealPeriod = (m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Text.size() + 3) * 30; - if (revealPeriod > m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Duration * 0.85) - revealPeriod = m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Duration * 0.85; - // The normalized reveal control - float revealed = m_StepTimer.GetElapsedRealTimeMS() / revealPeriod; - if (revealed > 1.0) - revealed = 1.0; - std::string revealText = m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Text.substr(0, (m_TutAreaSteps[m_CurrentArea][m_CurrentStep].m_Text.size() + 3) * revealed); - // Dot blinking logic - if (revealed == 1.0) { - int timePhase = (int)m_AreaTimer.GetElapsedRealTimeMS() % 1200; - revealText = revealText + (timePhase > 900 ? "..." : (timePhase > 600 ? ".. " : (timePhase > 300 ? ". " : " "))); - } - g_FrameMan.GetSmallFont()->DrawAligned(&pBitmapInt, screenTextPos.m_X, screenTextPos.m_Y, revealText, GUIFont::Centre); - } -} - -void GATutorial::Draw(BITMAP* pTargetBitmap, const Vector& targetPos) { - GameActivity::Draw(pTargetBitmap, targetPos); -} - -void GATutorial::InitAIs() { - Actor* pActor = 0; - Actor* pFirstActor = 0; - - for (int team = Teams::TeamOne; team < Teams::MaxTeamCount; ++team) { - if (!m_TeamActive[team]) - continue; - // Get the first one - pFirstActor = pActor = g_MovableMan.GetNextTeamActor(team); - - do { - // Set up AI controller if currently not player controlled - if (pActor && !pActor->GetController()->IsPlayerControlled()) { - pActor->SetControllerMode(Controller::CIM_AI); - - // If human, set appropriate AI mode - if (dynamic_cast(pActor) || dynamic_cast(pActor)) { - // Sentry default - if (team == m_CPUTeam) - pActor->SetAIMode(AHuman::AIMODE_SENTRY); - else if (team >= 0) - pActor->SetAIMode(AHuman::AIMODE_SENTRY); - // Let the non team actors be (the wildlife) - else - ; - } - } - - // Next! - pActor = g_MovableMan.GetNextTeamActor(team, pActor); - } while (pActor && pActor != pFirstActor); - } -} - -void GATutorial::SetupAreas() { - int device = g_UInputMan.GetControlScheme(m_TutorialPlayer)->GetDevice(); - int preset = g_UInputMan.GetControlScheme(m_TutorialPlayer)->GetPreset(); - - std::string JumpName = MAPNAME(INPUT_JUMP); - std::string ProneName = MAPNAME(INPUT_PRONE); - - // If no preset, adjust the pie menu and fire names when using the defaults on a gamepad.. otherwise it'll show up as an unhelpful "Joystick" - std::string PieName = MAPNAME(INPUT_PIEMENU_ANALOG); - if (PieName == "") { - PieName = MAPNAME(INPUT_PIEMENU_DIGITAL); - } - - std::string FireName = MAPNAME(INPUT_FIRE); - if (device >= DEVICE_GAMEPAD_1 && preset == InputScheme::InputPreset::NoPreset) { - PieName = "Pie Menu Trigger"; - FireName = "Fire Trigger"; - } - - // BRAINCHAMBER - // Set up the trigger area - m_TriggerBoxes[BRAINCHAMBER].SetCorner(Vector(631, 608)); - m_TriggerBoxes[BRAINCHAMBER].SetWidth(137); - m_TriggerBoxes[BRAINCHAMBER].SetHeight(155); - // Screen position - m_ScreenPositions[BRAINCHAMBER].SetXY(673, 688); - // Text offset from screen position - m_TextOffsets[BRAINCHAMBER].SetXY(m_apCommonScreens[0]->w / 2, m_apCommonScreens[0]->h + 4); - // Set up the steps - m_TutAreaSteps[BRAINCHAMBER].clear(); - m_TutAreaSteps[BRAINCHAMBER].push_back(TutStep("Welcome to Cortex Command", 4000, "Missions.rte/Objects/Tutorial/CCLogo.png", 2)); - m_TutAreaSteps[BRAINCHAMBER].push_back(TutStep("Above is your disembodied brain", 4000, "Missions.rte/Objects/Tutorial/ArrowUp.png", 2)); - m_TutAreaSteps[BRAINCHAMBER].push_back(TutStep("With it, you can remotely control other bodies", 4000, "Missions.rte/Objects/Tutorial/BodyZap.png", 2)); - m_TutAreaSteps[BRAINCHAMBER].push_back(TutStep("Switch to one now by using [" + MAPNAME(INPUT_PREV) + "] or [" + MAPNAME(INPUT_NEXT) + "]", 4000, "Missions.rte/Objects/Tutorial/BodyZap.png", 2)); - m_TutAreaSteps[BRAINCHAMBER].push_back(TutStep("If you haven't set up your controls yet, hit [Esc] and do so", 8000, "Missions.rte/Objects/Tutorial/Joystick.png", 2)); - - // BODYSTORAGE - // Set up the trigger area - m_TriggerBoxes[BODYSTORAGE].SetCorner(Vector(911, 662)); - m_TriggerBoxes[BODYSTORAGE].SetWidth(397); - m_TriggerBoxes[BODYSTORAGE].SetHeight(98); - // Screen position - m_ScreenPositions[BODYSTORAGE].SetXY(961, 688); - // Text offset from screen position - m_TextOffsets[BODYSTORAGE].SetXY(m_apCommonScreens[0]->w / 2, m_apCommonScreens[0]->h + 4); - // Set up the steps - m_TutAreaSteps[BODYSTORAGE].clear(); - m_TutAreaSteps[BODYSTORAGE].push_back(TutStep("Here are some dummy bodies for practice", 4000, "Missions.rte/Objects/Tutorial/BodyHop.png", 2)); - m_TutAreaSteps[BODYSTORAGE].push_back(TutStep("Quickly switch control between them left and right with [" + MAPNAME(INPUT_PREV) + "] and [" + MAPNAME(INPUT_NEXT) + "]", 6000, "Missions.rte/Objects/Tutorial/BodyZap.png", 2)); - m_TutAreaSteps[BODYSTORAGE].push_back(TutStep("Or hold down either [" + MAPNAME(INPUT_PREV) + "] or [" + MAPNAME(INPUT_NEXT) + "] to get a selection cursor", 6000, "Missions.rte/Objects/Tutorial/BodyZap.png", 2)); - m_TutAreaSteps[BODYSTORAGE].push_back(TutStep("Switch to the leftmost body and walk it out of the room with [" + MAPNAME(INPUT_L_LEFT) + "]", 8000, "Missions.rte/Objects/Tutorial/ArrowLeft.png", 2)); - - // SHAFT - // Set up the trigger area - m_TriggerBoxes[SHAFT].SetCorner(Vector(772, 385)); - m_TriggerBoxes[SHAFT].SetWidth(135); - m_TriggerBoxes[SHAFT].SetHeight(380); - // Screen position - m_ScreenPositions[SHAFT].SetXY(817, 688); - // Text offset from screen position - m_TextOffsets[SHAFT].SetXY(m_apCommonScreens[0]->w / 2, m_apCommonScreens[0]->h + 4); - // Set up the steps - m_TutAreaSteps[SHAFT].clear(); - m_TutAreaSteps[SHAFT].push_back(TutStep("Use [" + JumpName + "] to activate jetpack", 4000, "Missions.rte/Objects/Tutorial/ArrowUp.png", 2)); - m_TutAreaSteps[SHAFT].push_back(TutStep("Fire jetpack in bursts for better control", 4000, "Missions.rte/Objects/Tutorial/BodyJetpack.png", 2)); - m_TutAreaSteps[SHAFT].push_back(TutStep("Adjust the jet direction by aiming or looking", 4000, "Missions.rte/Objects/Tutorial/BodyJetpack.png", 2)); - m_TutAreaSteps[SHAFT].push_back(TutStep("Jump height is affected by the body's total weight", 4000, "Missions.rte/Objects/Tutorial/BodyJetpack.png", 2)); - m_TutAreaSteps[SHAFT].push_back(TutStep("So the more you carry, the less you can jump", 8000, "Missions.rte/Objects/Tutorial/BodyJetpack.png", 2)); - - // OBSTACLECOURSE - // Set up the trigger area - m_TriggerBoxes[OBSTACLECOURSE].SetCorner(Vector(915, 492)); - m_TriggerBoxes[OBSTACLECOURSE].SetWidth(395); - m_TriggerBoxes[OBSTACLECOURSE].SetHeight(167); - // Screen position - m_ScreenPositions[OBSTACLECOURSE].SetXY(961, 592); - // Text offset from screen position - m_TextOffsets[OBSTACLECOURSE].SetXY(m_apCommonScreens[0]->w / 2, m_apCommonScreens[0]->h + 4); - // Set up the steps - m_TutAreaSteps[OBSTACLECOURSE].clear(); - m_TutAreaSteps[OBSTACLECOURSE].push_back(TutStep("Obstacle course", 2000, "Missions.rte/Objects/Tutorial/BodyHop.png", 2)); - m_TutAreaSteps[OBSTACLECOURSE].push_back(TutStep("Climb obstacles by holding [" + MAPNAME(INPUT_L_RIGHT) + "]", 4000, "Missions.rte/Objects/Tutorial/ArrowRight.png", 2)); - m_TutAreaSteps[OBSTACLECOURSE].push_back(TutStep("Even climb ladders by simply moving toward them", 8000, "Missions.rte/Objects/Tutorial/BodyClimb.png", 2)); - m_TutAreaSteps[OBSTACLECOURSE].push_back(TutStep("You can also go prone and crawl with [" + ProneName + "]", 6000, "Missions.rte/Objects/Tutorial/BodyCrawl.png", 2)); - m_TutAreaSteps[OBSTACLECOURSE].push_back(TutStep("In tight spaces, you may need to angle your head down to get through", 8000, "Missions.rte/Objects/Tutorial/BodyCrawl.png", 2)); - - // FIRINGRANGE - // Set up the trigger area - m_TriggerBoxes[FIRINGRANGE].SetCorner(Vector(913, 394)); - m_TriggerBoxes[FIRINGRANGE].SetWidth(389); - m_TriggerBoxes[FIRINGRANGE].SetHeight(97); - // Screen position - m_ScreenPositions[FIRINGRANGE].SetXY(961, 424); - // Text offset from screen position - m_TextOffsets[FIRINGRANGE].SetXY(m_apCommonScreens[0]->w / 2, m_apCommonScreens[0]->h + 4); - // Set up the steps - m_TutAreaSteps[FIRINGRANGE].clear(); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("Firing range", 2000, "Missions.rte/Objects/Tutorial/FireTarget.png", 1)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("Pick up the weapon by first standing over it", 4000, "Missions.rte/Objects/Tutorial/ArrowRight.png", 2)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("And then hold down [" + PieName + "]", 4000, "Missions.rte/Objects/Tutorial/MenuPickUp.png", 1, 500)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("Point up to 'Pick Up'", 4000, "Missions.rte/Objects/Tutorial/MenuPickUp.png", 2, 500)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("Release [" + PieName + "] to complete the command", 4000, "Missions.rte/Objects/Tutorial/MenuPickUp.png", 1)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("If you aim continuously toward your target", 4000, "Missions.rte/Objects/Tutorial/BodyAim.png", 3)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("It improves your accuracy and view distance", 4000, "Missions.rte/Objects/Tutorial/BodyAim.png", 3)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("Use [" + FireName + "] to Fire!", 4000, "Missions.rte/Objects/Tutorial/BodyFire.png", 2)); - m_TutAreaSteps[FIRINGRANGE].push_back(TutStep("Reload manually with [" + PieName + "] + up", 8000, "Missions.rte/Objects/Tutorial/BodyFire.png", 2)); - - // ROOFTOP - // Set up the trigger area - m_TriggerBoxes[ROOFTOP].SetCorner(Vector(732, 176)); - m_TriggerBoxes[ROOFTOP].SetWidth(356); - m_TriggerBoxes[ROOFTOP].SetHeight(210); - // Screen position - m_ScreenPositions[ROOFTOP].SetXY(961, 316); - // Text offset from screen position - m_TextOffsets[ROOFTOP].SetXY(m_apCommonScreens[0]->w / 2, -16); - // Set up the steps - m_TutAreaSteps[ROOFTOP].clear(); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Pick up the digging tool", 4000, "Missions.rte/Objects/Tutorial/MenuPickUp.png", 2, 500)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Use it on the dirt here", 8000, "Missions.rte/Objects/Tutorial/DigPile.png", 2, 750)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("If you dig up gold, it is added to your team's funds", 4000, "Missions.rte/Objects/Tutorial/Funds.png", 2, 250)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Funds can be spent in the Buy Menu", 4000, "Missions.rte/Objects/Tutorial/Funds.png", 1, 333)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Which is opened through the Command Menu", 4000, "Missions.rte/Objects/Tutorial/MenuBuyMenu.png", 1, 500)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Hold [" + PieName + "] and point up and left to 'Buy Menu'", 6000, "Missions.rte/Objects/Tutorial/MenuBuyMenu.png", 2, 500)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("The Buy Menu works like a shopping cart", 6000, "Missions.rte/Objects/Tutorial/BuyMenuCargo.png", 1, 500)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Add to the Cargo list the items you want delivered", 6000, "Missions.rte/Objects/Tutorial/BuyMenuCargo.png", 2, 500)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Then use the BUY button, or click outside the menu", 4000, "Missions.rte/Objects/Tutorial/BuyMenuBuy.png", 2, 500)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Finally select a flat area where you want the goods delivered", 8000, "Missions.rte/Objects/Tutorial/BuyMenuBuy.png", 1, 500)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Next, you can go explore to the west to try flying and climbing in the wild", 6000, "Missions.rte/Objects/Tutorial/ArrowLeft.png", 2)); - m_TutAreaSteps[ROOFTOP].push_back(TutStep("Or, go to the east to learn about squads!", 8000, "Missions.rte/Objects/Tutorial/ArrowRight.png", 2)); - - // ROOFEAST - // Set up the trigger area - m_TriggerBoxes[ROOFEAST].SetCorner(Vector(1100, 176)); - m_TriggerBoxes[ROOFEAST].SetWidth(200); - m_TriggerBoxes[ROOFEAST].SetHeight(210); - // Screen position - m_ScreenPositions[ROOFEAST].SetXY(1201, 316); - // Text offset from screen position - m_TextOffsets[ROOFEAST].SetXY(m_apCommonScreens[0]->w / 2, -16); - // Set up the steps - m_TutAreaSteps[ROOFEAST].clear(); - m_TutAreaSteps[ROOFEAST].push_back(TutStep("Hold [" + PieName + "] and point down and right to 'Form Squad'", 4000, "Missions.rte/Objects/Tutorial/MenuTeam.png", 2, 500)); - m_TutAreaSteps[ROOFEAST].push_back(TutStep("Adjust selection circle to select nearby bodies", 4000, "Missions.rte/Objects/Tutorial/TeamSelect.png", 4, 500)); - m_TutAreaSteps[ROOFEAST].push_back(TutStep("All selected units will follow you, and engage on their own", 4000, "Missions.rte/Objects/Tutorial/TeamFollow.png", 2, 500)); - m_TutAreaSteps[ROOFEAST].push_back(TutStep("Units with similar weapons will fire in unison with the leader", 4000, "Missions.rte/Objects/Tutorial/TeamFollow.png", 2, 500)); - m_TutAreaSteps[ROOFEAST].push_back(TutStep("Hold [" + PieName + "] and point down and right again to disband squad", 4000, "Missions.rte/Objects/Tutorial/MenuTeam.png", 2, 500)); - m_TutAreaSteps[ROOFEAST].push_back(TutStep("Next, you can head east for a TRIAL BATTLE!", 8000, "Missions.rte/Objects/Tutorial/ArrowRight.png", 2)); - - m_AreaTimer.Reset(); - m_StepTimer.Reset(); -} diff --git a/Source/Activities/GATutorial.h b/Source/Activities/GATutorial.h deleted file mode 100644 index 0eb45f0958..0000000000 --- a/Source/Activities/GATutorial.h +++ /dev/null @@ -1,208 +0,0 @@ -#pragma once - -/// Header file for the ActivityMan class. -/// @author Daniel Tabar -/// data@datarealms.com -/// http://www.datarealms.com -/// Inclusions of header files -#include "GameActivity.h" -#include "Box.h" - -namespace RTE { - - class Actor; - - /// Tutorial mission with lots of special triggering logic. - class GATutorial : public GameActivity { - - /// Public member variable, method and friend function declarations - public: - // Concrete allocation and cloning definitions - EntityAllocation(GATutorial); - SerializableOverrideMethods; - ClassInfoGetters; - - /// Constructor method used to instantiate a GATutorial object in system - /// memory. Create() should be called before using the object. - GATutorial(); - - /// Destructor method used to clean up a GATutorial object before deletion - /// from system memory. - ~GATutorial() override; - - /// Makes the GATutorial object ready for use. - /// @return An error return value signaling sucess or any particular failure. - /// Anything below 0 is an error signal. - int Create() override; - - /// Creates a GATutorial to be identical to another, by deep copy. - /// @param reference A reference to the GATutorial to deep copy. - /// @return An error return value signaling sucess or any particular failure. - /// Anything below 0 is an error signal. - int Create(const GATutorial& reference); - - /// Resets the entire GATutorial, including its inherited members, to their - /// default settings or values. - void Reset() override { - Clear(); - Activity::Reset(); - } - - /// Destroys and resets (through Clear()) the GATutorial object. - /// @param notInherited Whether to only destroy the members defined in this derived class, or (default: false) - /// to destroy all inherited members also. - void Destroy(bool notInherited = false) override; - - /// Tells if a particular Scene supports this specific Activity on it. - /// Usually that means certain Area:s need to be defined in the Scene. - /// @param pScene The Scene to check if it supports this Activiy. Ownership IS NOT TRANSFERRED! - /// @param teams How many teams we're checking for. Some scenes may support and activity (default: -1) - /// but only for a limited number of teams. If -1, not applicable. - /// @return Whether the Scene has the right stuff. - bool SceneIsCompatible(Scene* pScene, int teams = -1) override; - - /// Officially starts the game accroding to parameters previously set. - /// @return An error return value signaling sucess or any particular failure. - /// Anything below 0 is an error signal. - int Start() override; - - /// Pauses and unpauses the game. - /// @param pause Whether to pause the game or not. (default: true) - void SetPaused(bool pause = true) override; - - /// Forces the current game's end. - void End() override; - - /// Updates the state of this ActivityMan. Supposed to be done every frame - /// before drawing. - void Update() override; - - /// Draws the currently active GUI of a screen to a BITMAP of choice. - /// @param pTargetBitmap A pointer to a screen-sized BITMAP to draw on. - /// @param targetPos The absolute position of the target bitmap's upper left corner in the scene. (default: Vector()) - /// @param which Which screen's GUI to draw onto the bitmap. (default: 0) - void DrawGUI(BITMAP* pTargetBitmap, const Vector& targetPos = Vector(), int which = 0) override; - - /// Draws this ActivityMan's current graphical representation to a - /// BITMAP of choice. This includes all game-related graphics. - /// @param pTargetBitmap A pointer to a BITMAP to draw on. - /// @param targetPos The absolute position of the target bitmap's upper left corner in the scene. (default: Vector()) - void Draw(BITMAP* pTargetBitmap, const Vector& targetPos = Vector()) override; - - /// Protected member variable and method declarations - protected: - /// Goes through all Actor:s currently in the MovableMan and sets each - /// one not controlled by a player to be AI controlled and AIMode setting - /// based on team and CPU team. - void InitAIs() override; - - /// Sets up or resets the Tutorial Areas to show the current control - /// mappings etc. - void SetupAreas(); - - enum TutorialArea { - BRAINCHAMBER = 0, - BODYSTORAGE, - SHAFT, - OBSTACLECOURSE, - FIRINGRANGE, - ROOFTOP, - ROOFEAST, - AREACOUNT - }; - - enum TutorialRoom { - ROOM0 = 0, - ROOM1, - ROOM2, - ROOM3, - ROOMCOUNT - }; - - enum LitState { - UNLIT = 0, - LIT, - LITSTATECOUNT - }; - - enum ScreenState { - SCREENOFF = 0, - STATICLITTLE, - STATICLARGE, - SHOWINGSTEP, - SREENSTATECOUNT - }; - - enum FightStage { - NOFIGHT = 0, - DEFENDING, - ATTACK, - FIGHTSTAGECOUNT - }; - - struct TutStep { - // Text of this step - std::string m_Text; - // Duration of the whole step - int m_Duration; - // BITMAPs not owned here - std::vector m_pScreens; - // The duration of one frame - int m_FrameDuration; - - TutStep(std::string text, int stepDuration, const std::string& screensPath = "", int frameCount = 1, int frameDuration = 250); - }; - - // Member variables - static Entity::ClassInfo m_sClass; - - // The player which is actually playing the tut - int m_TutorialPlayer; - // The areas that trigger specific sets of steps to be shown - Box m_TriggerBoxes[AREACOUNT]; - // Positions of the screens for each area - Vector m_ScreenPositions[AREACOUNT]; - // The current state of the all the different areas' screens - ScreenState m_ScreenStates[AREACOUNT]; - // Offsets of the center of the text line from the screen position - Vector m_TextOffsets[AREACOUNT]; - // Screen bitmaps common to all areas.. off, static etc - BITMAP* m_apCommonScreens[STATICLARGE + 1]; - // The steps themselves; cycles through for each area - std::vector m_TutAreaSteps[AREACOUNT]; - // Positions of the numbered room signs - Vector m_RoomSignPositions[ROOMCOUNT]; - // Room sign bitmaps, unlit and lit - BITMAP* m_aapRoomSigns[ROOMCOUNT][LITSTATECOUNT]; - // The timer which keeps track of how long each area has been showing - Timer m_AreaTimer; - // The timer which keeps track of how long each step should be shown - Timer m_StepTimer; - // Which are the player-controlled actor is within - TutorialArea m_CurrentArea; - // Which are the player-controlled actor was in last - TutorialArea m_PrevArea; - // If teh screen has just changed and needs to be redrawn - bool m_ScreenChange; - // Which tutorial step of the current area currently being played back - unsigned int m_CurrentStep; - // Which frame of the current step's animation are we on? - int m_CurrentFrame; - // Current room - TutorialRoom m_CurrentRoom; - // Trigger box for the subsequent fight - Box m_FightTriggers[FIGHTSTAGECOUNT]; - unsigned int m_EnemyCount; //!< The amount of enemy actors at the start of the activity. - // The current fight stage - FightStage m_CurrentFightStage; - // The CPU opponent brain; not owned! - Actor* m_pCPUBrain; - - /// Private member variable and method declarations - private: - /// Clears all the member variables of this Activity, effectively - /// resetting the members of this abstraction level only. - void Clear(); - }; - -} // namespace RTE diff --git a/Source/Activities/meson.build b/Source/Activities/meson.build index 3249d1bbc3..82564d1f15 100644 --- a/Source/Activities/meson.build +++ b/Source/Activities/meson.build @@ -6,7 +6,6 @@ sources += files( 'EditorActivity.cpp', 'GameActivity.cpp', 'GAScripted.cpp', -'GATutorial.cpp', 'GibEditor.cpp', #'MultiplayerGame.cpp', #'MultiplayerServerLobby.cpp', diff --git a/Source/Managers/ActivityMan.cpp b/Source/Managers/ActivityMan.cpp index a66c97ee7c..b7a2db796b 100644 --- a/Source/Managers/ActivityMan.cpp +++ b/Source/Managers/ActivityMan.cpp @@ -52,7 +52,7 @@ ActivityMan::~ActivityMan() { } void ActivityMan::Clear() { - m_DefaultActivityType = "GATutorial"; + m_DefaultActivityType = "GAScripted"; m_DefaultActivityName = "Tutorial Mission"; m_Activity = nullptr; m_StartActivity = nullptr; @@ -409,7 +409,7 @@ void ActivityMan::SetStartActivity(Activity* newActivity) { } void ActivityMan::SetStartTutorialActivity() { - SetStartActivity(dynamic_cast(g_PresetMan.GetEntityPreset("GATutorial", "Tutorial Mission")->Clone())); + SetStartActivity(dynamic_cast(g_PresetMan.GetEntityPreset("GAScripted", "Tutorial Mission")->Clone())); if (GameActivity* gameActivity = dynamic_cast(GetStartActivity())) { gameActivity->SetStartingGold(10000); } diff --git a/Source/Managers/SettingsMan.cpp b/Source/Managers/SettingsMan.cpp index f0bfb48b43..3df5f14651 100644 --- a/Source/Managers/SettingsMan.cpp +++ b/Source/Managers/SettingsMan.cpp @@ -164,7 +164,13 @@ int SettingsMan::ReadProperty(const std::string_view& propName, Reader& reader) MatchProperty("DefaultShakePerUnitOfRecoilEnergy", { reader >> g_CameraMan.m_DefaultShakePerUnitOfRecoilEnergy; }); MatchProperty("DefaultShakeFromRecoilMaximum", { reader >> g_CameraMan.m_DefaultShakeFromRecoilMaximum; }); MatchProperty("LaunchIntoActivity", { reader >> g_ActivityMan.m_LaunchIntoActivity; }); - MatchProperty("DefaultActivityType", { reader >> g_ActivityMan.m_DefaultActivityType; }); + MatchProperty("DefaultActivityType", { + reader >> g_ActivityMan.m_DefaultActivityType; + // The tutorial was its own Activity type before it became a GAScripted. + if (g_ActivityMan.m_DefaultActivityType == "GATutorial") { + g_ActivityMan.m_DefaultActivityType = "GAScripted"; + } + }); MatchProperty("DefaultActivityName", { reader >> g_ActivityMan.m_DefaultActivityName; }); MatchProperty("DefaultSceneName", { reader >> g_SceneMan.m_DefaultSceneName; }); MatchProperty("DisableLuaJIT", { reader >> m_DisableLuaJIT; }); diff --git a/Source/Menus/MetagameGUI.cpp b/Source/Menus/MetagameGUI.cpp index 4d9606b115..60aae53d96 100644 --- a/Source/Menus/MetagameGUI.cpp +++ b/Source/Menus/MetagameGUI.cpp @@ -32,7 +32,6 @@ #include "MOSprite.h" #include "HeldDevice.h" #include "AHuman.h" -#include "GATutorial.h" #include "GAScripted.h" #include "BaseEditor.h" #include "Scene.h" diff --git a/Source/Menus/ScenarioGUI.cpp b/Source/Menus/ScenarioGUI.cpp index 09e5f81dd9..78363c3a91 100644 --- a/Source/Menus/ScenarioGUI.cpp +++ b/Source/Menus/ScenarioGUI.cpp @@ -235,7 +235,7 @@ void ScenarioGUI::FetchActivitiesAndScenesLists() { // Add to the activity selection ComboBox and attach the activity pointer, not passing in ownership. m_ActivitySelectComboBox->AddItem(presetActivity->GetPresetName(), "", nullptr, presetActivity); - if (prevSelectedActivityIndex < 0 && presetActivity->GetClassName() == "GATutorial") { + if (prevSelectedActivityIndex < 0 && presetActivity->GetPresetName() == "Tutorial Mission") { prevSelectedActivityIndex = index; prevSelectedScene = nullptr; }