Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
317 lines
9.4 KiB
Lua
317 lines
9.4 KiB
Lua
--[[
|
|
Copyright (c) The OpenRA Developers and Contributors
|
|
This file is part of OpenRA, which is free software. It is made
|
|
available to you under the terms of the GNU General Public License
|
|
as published by the Free Software Foundation, either version 3 of
|
|
the License, or (at your option) any later version. For more
|
|
information, see COPYING.
|
|
]]
|
|
|
|
local USSR = Player.GetPlayer("USSR")
|
|
local Turkey = Player.GetPlayer("Turkey")
|
|
local Ukraine = Player.GetPlayer("Ukraine")
|
|
|
|
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 },
|
|
{ InnerPatrol4.Location, InnerPatrol1.Location, InnerPatrol2.Location, InnerPatrol3.Location },
|
|
{ InnerPatrol1.Location, InnerPatrol4.Location, InnerPatrol3.Location, InnerPatrol2.Location }
|
|
}
|
|
|
|
---@type actor[][]
|
|
local OuterPatrols =
|
|
{
|
|
{ TeamOne1, TeamOne2, TeamOne3 },
|
|
{ TeamTwo1, TeamTwo2, TeamTwo3 },
|
|
{ TeamThree1, TeamThree2, TeamThree3 },
|
|
{ TeamFour1, TeamFour2, TeamFour3 },
|
|
{ TeamFive1, TeamFive2, TeamFive3 }
|
|
}
|
|
|
|
---@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 },
|
|
{ OuterPatrol6.Location, OuterPatrol7.Location, OuterPatrol1.Location, OuterPatrol2.Location, OuterPatrol3.Location, OuterPatrol4.Location, OuterPatrol5.Location },
|
|
{ OuterPatrol3.Location, OuterPatrol4.Location, OuterPatrol5.Location, OuterPatrol6.Location, OuterPatrol7.Location, OuterPatrol1.Location, OuterPatrol2.Location },
|
|
{ OuterPatrol3.Location, OuterPatrol2.Location, OuterPatrol1.Location, OuterPatrol7.Location, OuterPatrol6.Location, OuterPatrol5.Location, OuterPatrol4.Location }
|
|
}
|
|
|
|
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
|
|
|
|
Utils.Do(units, function(unit)
|
|
Trigger.OnIdle(unit, function()
|
|
if stop then
|
|
return
|
|
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
|
|
Trigger.AfterDelay(delay, function() stop = false end)
|
|
end
|
|
end)
|
|
end)
|
|
end
|
|
|
|
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()
|
|
if Shocktroopers[i].IsDead then
|
|
return
|
|
end
|
|
|
|
Trigger.OnIdle(Shocktroopers[i], function()
|
|
Shocktroopers[i].Patrol(InnerPatrolPaths[i])
|
|
end)
|
|
end)
|
|
end
|
|
end
|
|
|
|
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()
|
|
end)
|
|
|
|
Trigger.OnInfiltrated(BioLab, function()
|
|
Media.DisplayMessage(UserInterface.GetFluentMessage("plans-stolen-erase-data"), UserInterface.GetFluentMessage("scientist"))
|
|
Trigger.AfterDelay(DateTime.Seconds(5), function()
|
|
USSR.MarkCompletedObjective(InfiltrateLab)
|
|
LabInfiltrated = true
|
|
SendInBombers()
|
|
end)
|
|
end)
|
|
|
|
Trigger.OnKilled(BioLab, function()
|
|
if not LabInfiltrated then
|
|
USSR.MarkFailedObjective(InfiltrateLab)
|
|
end
|
|
end)
|
|
|
|
Trigger.OnAllKilled(PrimaryTargets, function()
|
|
if LaunchStarted then
|
|
return
|
|
end
|
|
|
|
if not BombersCalled then
|
|
ClearCountdown()
|
|
end
|
|
|
|
USSR.MarkCompletedObjective(DestroyFacility)
|
|
|
|
Trigger.AfterDelay(DateTime.Seconds(2), function()
|
|
USSR.MarkCompletedObjective(VolkovSurvive)
|
|
end)
|
|
end)
|
|
|
|
Trigger.OnAllKilled(MissileSubs, function()
|
|
if not VolkovArrived then
|
|
USSR.MarkFailedObjective(KillPower)
|
|
end
|
|
end)
|
|
end
|
|
|
|
Tick = function()
|
|
if Turkey.PowerState ~= "Normal" then
|
|
LowPowerDuration = LowPowerDuration + 1
|
|
SendInVolkov()
|
|
else
|
|
LowPowerDuration = 0
|
|
end
|
|
|
|
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
|
|
TimerTicks = TimerTicks - 1
|
|
elseif TimerTicks == 0 and not BombersCalled and not USSR.IsObjectiveCompleted(DestroyFacility) then
|
|
Turkey.MarkCompletedObjective(GuardLab)
|
|
LaunchMissiles()
|
|
end
|
|
end
|
|
|
|
WorldLoaded = function()
|
|
InitObjectives(USSR)
|
|
StartPatrols()
|
|
SetupStartTriggers()
|
|
Camera.Position = DefaultCameraPosition.CenterPosition
|
|
DateTime.TimeLimit = TimerTicks
|
|
end
|