From 3dcbdaa56e2acf7968b7ad9cb1a58d19ada6bf81 Mon Sep 17 00:00:00 2001 From: JovialFeline Date: Wed, 15 Oct 2025 11:47:41 -0400 Subject: [PATCH] Add grace period & polish to Situation Critical --- mods/ra/fluent/campaign.ftl | 2 +- mods/ra/fluent/lua.ftl | 4 +- mods/ra/maps/situation-critical/map.yaml | 87 ++--- mods/ra/maps/situation-critical/rules.yaml | 38 ++- .../situation-critical/situation-critical.lua | 308 ++++++++++++------ 5 files changed, 302 insertions(+), 137 deletions(-) diff --git a/mods/ra/fluent/campaign.ftl b/mods/ra/fluent/campaign.ftl index 12121fa439..7ff6bb3a00 100644 --- a/mods/ra/fluent/campaign.ftl +++ b/mods/ra/fluent/campaign.ftl @@ -41,7 +41,7 @@ actor-5tnk = actor-5tnk-husk-name = Husk (Super Tank) -## mousetrap +## mousetrap, situation-critical actor-gas-name = Gas ## sarin-gas-2-down-under diff --git a/mods/ra/fluent/lua.ftl b/mods/ra/fluent/lua.ftl index d24623dd3f..4367ac8148 100644 --- a/mods/ra/fluent/lua.ftl +++ b/mods/ra/fluent/lua.ftl @@ -341,6 +341,9 @@ destroy-bio-weapons-lab-missile-silos = Destroy the bio-weapons lab and the miss destroy-all-sam-sites-strategic-bombers = Destroy all SAM sites on the island. Our strategic bombers will finish the rest. +## situation-critical, soviet-10 +run-for-it = RUN FOR IT! + ## situation-critical, soviet-soldier-volkov-n-chitzkoi volkov-survive = Volkov must survive. @@ -393,7 +396,6 @@ destroy-stolen-convoy-truck = Destroy the stolen convoy truck defend-forward-command-center = Defend our forward Command Center. ## soviet-10 -run-for-it = RUN FOR IT! convoy-commander = Convoy commander we-are-surrounded = We are surrounded! corridor-closes-in = Corridor closes in { $time } diff --git a/mods/ra/maps/situation-critical/map.yaml b/mods/ra/maps/situation-critical/map.yaml index 21f6cbb263..f37ce3d7f0 100644 --- a/mods/ra/maps/situation-critical/map.yaml +++ b/mods/ra/maps/situation-critical/map.yaml @@ -24,10 +24,10 @@ Players: OwnsWorld: True NonCombatant: True Faction: england - PlayerReference@Creeps: - Name: Creeps - NonCombatant: True - Faction: england + PlayerReference@Ukraine: + Name: Ukraine + Faction: allies + Color: FF8800 PlayerReference@Turkey: Name: Turkey Faction: allies @@ -363,8 +363,8 @@ Actors: TeamOne1: e1 Location: 47,54 Owner: Turkey - Facing: 124 - SubCell: 3 + Facing: 128 + SubCell: 4 TeamOne2: e2 Location: 46,54 Owner: Turkey @@ -376,12 +376,12 @@ Actors: TeamTwo1: e1 Location: 51,71 Owner: Turkey - Facing: 124 - SubCell: 4 + Facing: 128 + SubCell: 5 TeamTwo2: e1 Location: 51,71 Owner: Turkey - Facing: 124 + Facing: 128 SubCell: 1 TeamTwo3: e2 Location: 50,70 @@ -390,52 +390,52 @@ Actors: TeamThree1: e1 Location: 68,79 Owner: Turkey - Facing: 252 + Facing: 256 SubCell: 1 TeamThree2: e1 Location: 68,79 Owner: Turkey - Facing: 252 - SubCell: 4 + Facing: 256 + SubCell: 5 TeamThree3: e2 Location: 68,79 Owner: Turkey - Facing: 252 + Facing: 256 SubCell: 5 TeamFour1: e1 Location: 66,44 Owner: Turkey - Facing: 636 + Facing: 640 SubCell: 1 TeamFour2: e1 Location: 66,43 Owner: Turkey - Facing: 636 - SubCell: 3 + Facing: 640 + SubCell: 4 TeamFour3: e2 Location: 66,44 Owner: Turkey - Facing: 636 + Facing: 640 SubCell: 2 TeamFive1: e1 Location: 75,58 Owner: Turkey - Facing: 508 - SubCell: 4 + Facing: 512 + SubCell: 5 TeamFive2: e2 Location: 75,58 Owner: Turkey - Facing: 508 - SubCell: 3 + Facing: 512 + SubCell: 4 TeamFive3: e1 Location: 75,58 Owner: Turkey - Facing: 508 + Facing: 512 SubCell: 1 Shok1: shok Location: 54,50 Owner: Turkey - Facing: 380 + Facing: 384 SubCell: 1 Shok2: shok Location: 51,62 @@ -444,72 +444,72 @@ Actors: Shok3: shok Location: 63,68 Owner: Turkey - Facing: 380 + Facing: 384 SubCell: 1 Shok4: shok Location: 69,52 Owner: Turkey - Facing: 636 + Facing: 640 SubCell: 0 Actor123: ss Location: 35,34 Owner: USSR - Facing: 764 + Facing: 768 Actor124: ss Location: 36,35 Owner: USSR - Facing: 764 + Facing: 768 Actor125: ss Location: 35,36 Owner: USSR - Facing: 764 + Facing: 768 MSub1: msub Location: 32,35 Owner: USSR - Facing: 764 + Facing: 768 MSub2: msub Location: 31,36 Owner: USSR - Facing: 764 + Facing: 768 MSub3: msub Location: 31,34 Owner: USSR - Facing: 764 + Facing: 768 MSub4: msub Location: 30,35 Owner: USSR - Facing: 764 + Facing: 768 Actor129: ss Location: 71,36 Owner: Turkey - Facing: 124 + Facing: 128 Actor130: ss Location: 81,60 Owner: Turkey - Facing: 892 + Facing: 896 Actor131: ss Location: 59,83 Owner: Turkey - Facing: 252 + Facing: 256 Actor132: ss Location: 34,57 Owner: Turkey - Facing: 124 + Facing: 128 Actor133: ss Location: 36,81 Owner: Turkey Actor134: ss Location: 74,87 Owner: Turkey - Facing: 892 + Facing: 896 Actor135: ss Location: 91,39 Owner: Turkey - Facing: 380 + Facing: 384 Actor137: ss Location: 34,35 Owner: USSR - Facing: 764 + Facing: 768 DefaultCameraPosition: waypoint Location: 32,35 Owner: Neutral @@ -561,6 +561,15 @@ Actors: TacticalNuke3: waypoint Location: 57,61 Owner: Neutral + GasEntry: waypoint + Location: 60,57 + Owner: Neutral + LaunchCameraMark: waypoint + Location: 59,57 + Owner: Neutral + TacticalNukeCenter: waypoint + Location: 62,54 + Owner: Neutral Rules: ra|rules/campaign-rules.yaml, ra|rules/campaign-tooltips.yaml, ra|rules/campaign-palettes.yaml, rules.yaml diff --git a/mods/ra/maps/situation-critical/rules.yaml b/mods/ra/maps/situation-critical/rules.yaml index 709c0cc7a3..3305ffbd8c 100644 --- a/mods/ra/maps/situation-critical/rules.yaml +++ b/mods/ra/maps/situation-critical/rules.yaml @@ -5,6 +5,8 @@ World: WinVideo: flare.vqa LossVideo: nukestok.vqa Briefing: A radical faction of our forces has stolen a biological weapon, threatening to use it on Allied emplacements. Since the weapon is highly unstable, it could destroy us all if released. \n\nThe facility must be destroyed, but the weapon must be neutralized first. Destroy the island's defenses, then use Volkov to assist our scientist in reaching the bio-research center. If either are killed before the weapon is neutralized, the mission is a failure.\n\nOnce the scientist completes his mission, destroy the base. + TimeLimitManager: + SkipTimerExpiredNotification: true VOLK: Inherits: GNRL @@ -50,15 +52,29 @@ LST.Reinforcement: RenderSprites: Image: lst Interactable: + Cargo: + BetweenUnloadDelay: 10 MSLO: Power: Amount: 0 -WithColoredOverlay@IDISABLE: - -NukePower: + NukePower: + DisplayTimerRelationships: Neutral + ExternalCondition@NODEMO: + Condition: demolish-disabled + Demolishable: + RequiresCondition: !demolish-disabled + DamageMultiplier@NODEMO: + Modifier: 0 + RequiresCondition: demolish-disabled SAM: -WithColoredOverlay@IDISABLE: + RevealsShroud: + Range: 2c0 + RevealsShroud@GAPGEN: + Range: 2c0 BADR.Bomber: Aircraft: @@ -68,16 +84,36 @@ BADR.Bomber: RenderSprites: Image: U2 +FLARE: + Tooltip: + Name: actor-gas-name + powerproxy.parabombs: AirstrikePower: DisplayBeacon: False +CAMERA.Bomber: + Inherits: CAMERA + RevealsShroud: + Range: 12c0 + DELPHI: Tooltip: Name: actor-scientist-name Infiltrates: Types: ScientistInfiltrate + ExternalCondition: + Condition: can-be-targeted + Targetable: + RequiresCondition: can-be-targeted BIO: Targetable: TargetTypes: GroundActor, Structure, C4, DetonateAttack, ScientistInfiltrate + ExternalCondition@NODEMO: + Condition: demolish-disabled + Demolishable: + RequiresCondition: !demolish-disabled + DamageMultiplier@NODEMO: + Modifier: 0 + RequiresCondition: demolish-disabled diff --git a/mods/ra/maps/situation-critical/situation-critical.lua b/mods/ra/maps/situation-critical/situation-critical.lua index 63bdf6090f..e5ce746f89 100644 --- a/mods/ra/maps/situation-critical/situation-critical.lua +++ b/mods/ra/maps/situation-critical/situation-critical.lua @@ -6,17 +6,38 @@ the License, or (at your option) any later version. For more information, see COPYING. ]] -MissleSubs = { MSub1, MSub2, MSub3, MSub4 } -VolkovEntryPath = { LSTEntry.Location, LZ.Location } -VolkovandFriend = { "volk", "delphi" } -InsertionTransport = "lst.reinforcement" -SamSites = { Sam1, Sam2, Sam3, Sam4, Sam5, Sam6, Sam7, Sam8, Sam9, Sam10, Sam11, Sam12 } -PrimaryTargets = { BioLab, Silo1, Silo2 } -TimerTicks = DateTime.Minutes(8) -Shocktroopers = { Shok1, Shok2, Shok3, Shok4 } +local USSR = Player.GetPlayer("USSR") +local Turkey = Player.GetPlayer("Turkey") +local Ukraine = Player.GetPlayer("Ukraine") -InnerPatrolPaths = +local MissileSubs = { MSub1, MSub2, MSub3, MSub4 } +local VolkovEntryPath = { LSTEntry.Location, LZ.Location } +local VolkovAndFriend = { "delphi", "volk" } +local InsertionTransport = "lst.reinforcement" +local SamSites = { Sam1, Sam2, Sam3, Sam4, Sam5, Sam6, Sam7, Sam8, Sam9, Sam10, Sam11, Sam12 } +local PrimaryTargets = { BioLab, Silo1, Silo2 } +local Shocktroopers = { Shok1, Shok2, Shok3, Shok4 } + +local GuardLab = AddPrimaryObjective(Turkey, "") +local KillPower = AddPrimaryObjective(USSR, "kill-power") +local InfiltrateLab = AddPrimaryObjective(USSR, "infiltrate-bio-weapons-lab-scientist") +local DestroyFacility = AddPrimaryObjective(USSR, "destroy-bio-weapons-lab-missile-silos") +local KillSams = AddSecondaryObjective(USSR, "destroy-all-sam-sites-strategic-bombers") +local VolkovSurvive = AddPrimaryObjective(USSR, "volkov-survive") + +local LabInfiltrated = false +local VolkovArrived = false +local LaunchStarted = false +local BombersCalled = false + +local LowPowerDuration = 0 +local LowPowerThreshold = DateTime.Seconds(2) +local TimerTicks = DateTime.Minutes(8) +local TimerColor = Turkey.Color + +---@type cpos[][] +local InnerPatrolPaths = { { InnerPatrol2.Location, InnerPatrol3.Location, InnerPatrol4.Location, InnerPatrol1.Location }, { InnerPatrol3.Location, InnerPatrol2.Location, InnerPatrol1.Location, InnerPatrol4.Location }, @@ -24,7 +45,8 @@ InnerPatrolPaths = { InnerPatrol1.Location, InnerPatrol4.Location, InnerPatrol3.Location, InnerPatrol2.Location } } -OuterPatrols = +---@type actor[][] +local OuterPatrols = { { TeamOne1, TeamOne2, TeamOne3 }, { TeamTwo1, TeamTwo2, TeamTwo3 }, @@ -33,7 +55,8 @@ OuterPatrols = { TeamFive1, TeamFive2, TeamFive3 } } -OuterPatrolPaths = +---@type cpos[][] +local OuterPatrolPaths = { { OuterPatrol1.Location, OuterPatrol2.Location, OuterPatrol3.Location, OuterPatrol4.Location, OuterPatrol5.Location, OuterPatrol6.Location, OuterPatrol7.Location }, { OuterPatrol5.Location, OuterPatrol4.Location, OuterPatrol3.Location, OuterPatrol2.Location, OuterPatrol1.Location, OuterPatrol7.Location, OuterPatrol6.Location }, @@ -42,7 +65,42 @@ OuterPatrolPaths = { OuterPatrol3.Location, OuterPatrol2.Location, OuterPatrol1.Location, OuterPatrol7.Location, OuterPatrol6.Location, OuterPatrol5.Location, OuterPatrol4.Location } } -GroupPatrol = function(units, waypoints, delay) +local function IsVolkovNearBlast() + -- Within this radius, most normal infantry are left at 20% HP or less. + local cyborgsInDanger = Map.ActorsInCircle(TacticalNukeCenter.CenterPosition, WDist.FromCells(12), function(a) + return a.Type == "volk" + end) + + return #cyborgsInDanger == 1 +end + +local function ClearCountdown() + UserInterface.SetMissionText("") + DateTime.TimeLimit = 0 + TimerTicks = 0 +end + +---@param duration integer +local function SetRunText(duration) + local text = UserInterface.GetFluentMessage("run-for-it") + local seconds = math.floor(duration / DateTime.Seconds(1)) + Media.PlaySoundNotification(USSR, "AlertBleep") + + for i = 0, seconds - 1, 1 do + local color = Ukraine.Color + if i % 2 ~= 0 then + color = HSLColor.White + end + + Trigger.AfterDelay(DateTime.Seconds(i), function() UserInterface.SetMissionText(text, color) end) + end + Trigger.AfterDelay(duration, function() UserInterface.SetMissionText("") end) +end + +---@param units actor[] +---@param path cpos[] +---@param delay integer +local function GroupPatrol(units, path, delay) local i = 1 local stop = false @@ -51,30 +109,36 @@ GroupPatrol = function(units, waypoints, delay) if stop then return end - if unit.Location == waypoints[i] then - local bool = Utils.All(units, function(actor) return actor.IsIdle end) - if bool then - stop = true - i = i + 1 - if i > #waypoints then - i = 1 - end - Trigger.AfterDelay(delay, function() stop = false end) + + if unit.Location ~= path[i] then + unit.AttackMove(path[i]) + return + end + + local bool = Utils.All(units, function(actor) return actor.IsIdle or actor.IsDead end) + if bool then + stop = true + i = i + 1 + if i > #path then + i = 1 end - else - unit.AttackMove(waypoints[i]) + Trigger.AfterDelay(delay, function() stop = false end) end end) end) end -StartPatrols = function() +local function StartPatrols() for i = 1, 5 do GroupPatrol(OuterPatrols[i], OuterPatrolPaths[i], DateTime.Seconds(3)) end for i = 1, 4 do - Trigger.AfterDelay(DateTime.Seconds(3* (i - 1)), function() + Trigger.AfterDelay(DateTime.Seconds(3 * (i - 1)), function() + if Shocktroopers[i].IsDead then + return + end + Trigger.OnIdle(Shocktroopers[i], function() Shocktroopers[i].Patrol(InnerPatrolPaths[i]) end) @@ -82,8 +146,104 @@ StartPatrols = function() end end -LabInfiltrated = false -SetupTriggers = function() +local function LaunchMissiles() + if LaunchStarted then + return + end + + LaunchStarted = true + UserInterface.SetMissionText(UserInterface.GetFluentMessage("too-late"), USSR.Color) + Actor.Create("camera", true, { Owner = USSR, Location = LaunchCameraMark.Location }) + + -- Edge case that seems impossible in RA1. + if Silo1.IsDead and Silo2.IsDead and not BioLab.IsDead then + Actor.Create("flare", true, { Owner = Ukraine, Location = GasEntry.Location }) + BioLab.GrantCondition("demolish-disabled") + return + end + + Camera.Position = LaunchCameraMark.CenterPosition + local delay = 15 + + Utils.Do({ Silo1, Silo2 }, function(silo) + Trigger.AfterDelay(delay, function() + if silo.IsDead then + return + end + + silo.ActivateNukePower(DefaultCameraPosition.Location) + end) + + if not silo.IsDead then + silo.GrantCondition("demolish-disabled") + delay = delay + 15 + end + end) +end + +local function SendInBombers() + if LaunchStarted or not LabInfiltrated or not USSR.IsObjectiveCompleted(KillSams) then + return + end + + BombersCalled = true + ClearCountdown() + local bombDelay = DateTime.Seconds(2) + + Trigger.AfterDelay(DateTime.Seconds(1), function() + Actor.Create("camera.bomber", true, { Owner = USSR, Location = TacticalNukeCenter.Location }) + end) + + if IsVolkovNearBlast() then + bombDelay = DateTime.Seconds(8) + SetRunText(bombDelay + DateTime.Seconds(1)) + end + + Trigger.AfterDelay(bombDelay, function() + local proxy = Actor.Create("powerproxy.parabombs", false, { Owner = USSR }) + proxy.TargetAirstrike(TacticalNuke1.CenterPosition, Angle.SouthWest) + proxy.TargetAirstrike(TacticalNuke2.CenterPosition, Angle.SouthWest) + proxy.TargetAirstrike(TacticalNuke3.CenterPosition, Angle.SouthWest) + proxy.Destroy() + end) +end + +local function SendInVolkov() + if VolkovArrived or LowPowerDuration < LowPowerThreshold then + return + end + + VolkovArrived = true + USSR.MarkCompletedObjective(KillPower) + Media.PlaySpeechNotification(USSR, "ReinforcementsArrived") + Beacon.New(USSR, LZ.CenterPosition, DateTime.Seconds(6)) + local teamVolkov = Reinforcements.ReinforceWithTransport(USSR, InsertionTransport, VolkovAndFriend, VolkovEntryPath, { VolkovEntryPath[1] })[2] + local scientist, volkov = teamVolkov[1], teamVolkov[2] + + Trigger.OnKilled(volkov, function() + USSR.MarkFailedObjective(VolkovSurvive) + end) + + Trigger.OnAddedToWorld(volkov, function() + Media.DisplayMessage(UserInterface.GetFluentMessage("software-update-failed-manual-targets"), UserInterface.GetFluentMessage("volkov")) + end) + + Trigger.OnAddedToWorld(scientist, function(b) + Trigger.OnKilled(b, function() + if not LabInfiltrated then + USSR.MarkFailedObjective(InfiltrateLab) + end + end) + + Trigger.AfterDelay(DateTime.Seconds(5), function() + if not scientist.IsDead then + scientist.GrantCondition("can-be-targeted") + end + end) + end) +end + +local function SetupStartTriggers() Trigger.OnAllKilled(SamSites, function() USSR.MarkCompletedObjective(KillSams) SendInBombers() @@ -105,94 +265,52 @@ SetupTriggers = function() end) Trigger.OnAllKilled(PrimaryTargets, function() + if LaunchStarted then + return + end + + if not BombersCalled then + ClearCountdown() + end + USSR.MarkCompletedObjective(DestroyFacility) - USSR.MarkCompletedObjective(VolkovSurvive) + + Trigger.AfterDelay(DateTime.Seconds(2), function() + USSR.MarkCompletedObjective(VolkovSurvive) + end) end) - Trigger.OnAllKilled(MissleSubs, function() + Trigger.OnAllKilled(MissileSubs, function() if not VolkovArrived then USSR.MarkFailedObjective(KillPower) end end) end -SendInBombers = function() - if LabInfiltrated and USSR.IsObjectiveCompleted(KillSams) then - local proxy = Actor.Create("powerproxy.parabombs", false, { Owner = USSR }) - proxy.TargetAirstrike(TacticalNuke1.CenterPosition, Angle.SouthWest) - proxy.TargetAirstrike(TacticalNuke2.CenterPosition, Angle.SouthWest) - proxy.TargetAirstrike(TacticalNuke3.CenterPosition, Angle.SouthWest) - proxy.Destroy() - end -end - - -SendInVolkov = function() - if not VolkovArrived then - USSR.MarkCompletedObjective(KillPower) - Media.PlaySpeechNotification(USSR, "ReinforcementsArrived") - local teamVolkov = Reinforcements.ReinforceWithTransport(USSR, InsertionTransport, VolkovandFriend, VolkovEntryPath, { VolkovEntryPath[1] })[2] - VolkovArrived = true - Trigger.OnKilled(teamVolkov[1], function() - USSR.MarkFailedObjective(VolkovSurvive) - end) - Trigger.OnAddedToWorld(teamVolkov[1], function(a) - Media.DisplayMessage(UserInterface.GetFluentMessage("software-update-failed-manual-targets"), UserInterface.GetFluentMessage("volkov")) - end) - - Trigger.OnAddedToWorld(teamVolkov[2], function(b) - Trigger.OnKilled(b, function() - if not LabInfiltrated then - USSR.MarkFailedObjective(InfiltrateLab) - end - end) - end) - end -end - -Ticked = TimerTicks Tick = function() if Turkey.PowerState ~= "Normal" then + LowPowerDuration = LowPowerDuration + 1 SendInVolkov() + else + LowPowerDuration = 0 end - if Ticked > 0 then - if (Ticked % DateTime.Seconds(1)) == 0 then - Timer = UserInterface.GetFluentMessage("missiles-launch-in", { ["time"] = Utils.FormatTime(Ticked) }) - UserInterface.SetMissionText(Timer, TimerColor) + if TimerTicks > 0 then + if (TimerTicks % DateTime.Seconds(1)) == 0 then + local text = UserInterface.GetFluentMessage("missiles-launch-in", { ["time"] = Utils.FormatTime(TimerTicks) }) + UserInterface.SetMissionText(text, TimerColor) end - Ticked = Ticked - 1 - elseif Ticked == 0 then - UserInterface.SetMissionText(UserInterface.GetFluentMessage("too-late"), USSR.Color) - Turkey.MarkCompletedObjective(LaunchMissles) + TimerTicks = TimerTicks - 1 + elseif TimerTicks == 0 and not BombersCalled and not USSR.IsObjectiveCompleted(DestroyFacility) then + Turkey.MarkCompletedObjective(GuardLab) + LaunchMissiles() end end WorldLoaded = function() - USSR = Player.GetPlayer("USSR") - Turkey = Player.GetPlayer("Turkey") - InitObjectives(USSR) - - LaunchMissles = AddPrimaryObjective(Turkey, "") - KillPower = AddPrimaryObjective(USSR, "kill-power") - InfiltrateLab = AddPrimaryObjective(USSR, "infiltrate-bio-weapons-lab-scientist") - DestroyFacility = AddPrimaryObjective(USSR, "destroy-bio-weapons-lab-missile-silos") - KillSams = AddSecondaryObjective(USSR, "destroy-all-sam-sites-strategic-bombers") - VolkovSurvive = AddPrimaryObjective(USSR, "volkov-survive") - - Trigger.AfterDelay(DateTime.Minutes(3), function() - Media.PlaySpeechNotification(USSR, "WarningFiveMinutesRemaining") - end) - Trigger.AfterDelay(DateTime.Minutes(5), function() - Media.PlaySpeechNotification(USSR, "WarningThreeMinutesRemaining") - end) - Trigger.AfterDelay(DateTime.Minutes(7), function() - Media.PlaySpeechNotification(USSR, "WarningOneMinuteRemaining") - end) - StartPatrols() - SetupTriggers() + SetupStartTriggers() Camera.Position = DefaultCameraPosition.CenterPosition - TimerColor = Turkey.Color + DateTime.TimeLimit = TimerTicks end