Files
OpenRA/mods/ra/maps/negotiations/negotiations.lua
2024-05-03 18:24:47 +03:00

1266 lines
31 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.
]]
BadGuy = Player.GetPlayer("BadGuy")
England = Player.GetPlayer("England")
Greece = Player.GetPlayer("Greece")
GoodGuy = Player.GetPlayer("GoodGuy")
Neutral = Player.GetPlayer("Neutral")
USSR = Player.GetPlayer("USSR")
ForestTeam = { ForestPatroller1, ForestPatroller2, ForestPatroller3, ForestPatroller4, ForestPatroller5, ForestPatroller6, ForestPatroller7, ForestPatroller8 }
BlockerTeam = { BlockerGuard1, BlockerGuard2, BlockerTank }
SoutheastTurretGuards = { SoutheastTurretRifle1, SoutheastTurretRifle2, SoutheastTurretRifle3 }
Hostages = { Hostage1, Hostage2, Hostage3, Hostage4, Hostage5 }
TimerColor = HSLColor.White
TimeRemaining = 0
TanyaType = "e7.noautotarget"
if Difficulty == "easy" then
TanyaType = "e7"
end
WorldLoaded = function()
Camera.Position = TanyaDrop.CenterPosition
SpawnPlayerCamera(TanyaDrop.Location, DateTime.Seconds(20))
StartSoldier.Move(WestRoadRally.Location)
if Difficulty == "hard" then
StartSoldier.CallFunc(ReinforceStartSoldiers)
end
VillageBridge = GetBridge()
ReinforceHardTeams()
PrepareBadGuy()
ReinforceTanya()
CreateInitialTriggers()
CreateInitialObjectives()
LinkCooperativeObjectives()
CheckSovietDestruction()
end
CreateInitialObjectives = function()
InitObjectives(Greece)
if Difficulty == "hard" then
SaveAllHostages = AddPrimaryObjective(Greece, "keep-all-hostages-alive")
end
FreeTheHostages = AddPrimaryObjective(Greece, "free-hostages")
SaveAllHostages = SaveAllHostages or AddSecondaryObjective(Greece, "keep-all-hostages-alive")
SurviveUntilEnd = AddPrimaryObjective(England, "")
StopAllies = AddPrimaryObjective(USSR, "")
end
--[[
Link Greece's win and victory notification to England's own victory.
To reduce message spam, the creation of some primary objectives is delayed.
That is done by making this a co-op mission between Greece and England
(owner of the hostages). Because both must achieve their objectives, Greece
can finish all primaries without immediately triggering victory.
]]
LinkCooperativeObjectives = function()
local greeceActor = Greece.GetActorsByType("player")[1]
Trigger.Clear(greeceActor, "OnPlayerWon")
Trigger.OnPlayerWon(England, function()
USSR.MarkFailedObjective(StopAllies)
Trigger.AfterDelay(DateTime.Seconds(1), function()
Media.PlaySpeechNotification(Greece, "MissionAccomplished")
end)
end)
Trigger.AfterDelay(1, function()
-- Triggers any time Greece finishes all CURRENT primary objectives.
Trigger.OnPlayerWon(Greece, function()
local sovietsDestroyed = DestroySoviets ~= nil and Greece.IsObjectiveCompleted(DestroySoviets)
if sovietsDestroyed and EscortFinished then
England.MarkCompletedObjective(SurviveUntilEnd)
end
end)
end)
end
CheckSovietDestruction = function()
local destroyed = USSR.HasNoRequiredUnits() and BadGuy.HasNoRequiredUnits()
if not destroyed then
Trigger.AfterDelay(DateTime.Seconds(1), CheckSovietDestruction)
return
end
-- Cover unlikely case that all Soviets are destroyed with hostages in tow.
DestroySoviets = DestroySoviets or AddPrimaryObjective(Greece, "destroy-soviet-units-infrastructure")
Greece.MarkCompletedObjective(DestroySoviets)
if EscortFinished and not Greece.IsObjectiveFailed(SaveAllHostages) then
Greece.MarkCompletedObjective(SaveAllHostages)
end
end
CheckAlliedDestruction = function()
if #Greece.GetGroundAttackers() == 0 then
MarkAlliedDefeat()
end
end
MarkAlliedDefeat = function(objective, delay)
if AlliesDefeated then
return
end
StopCountdown()
AlliesDefeated = true
delay = delay or DateTime.Seconds(1)
Trigger.AfterDelay(delay, function()
if not Greece.IsObjectiveCompleted(FreeTheHostages) then
Greece.MarkFailedObjective(SaveAllHostages)
end
if objective then
Greece.MarkFailedObjective(objective)
return
end
local mainObjectives = { FreeTheHostages, EscortTheHostages, DestroySoviets }
Utils.Do(mainObjectives, function(o)
if not Greece.IsObjectiveCompleted(o) then
Greece.MarkFailedObjective(o)
end
end)
end)
end
CheckEscortObjective = function()
if EscortFinished then
return
end
local maximum = Utils.Where(Hostages, IsAlive)
if Church.PassengerCount < #maximum then
return
end
OnHostagesEscorted()
end
CreateInitialTriggers = function()
PrepareChurch()
PrepareHostages()
PrepareReveals()
PrepareSovietGuards()
PrepareForestEncounter()
PrepareChronoTanks()
PrepareBlockers()
Trigger.OnKilled(ForwardCommand, OnForwardCommandKilled)
local sams = { WestSam, EastSam }
Trigger.OnAllKilled(sams, ReinforceLongbows)
local guideHouseAttackers = { GuideRifleWest, GuideRifleEast }
Trigger.OnAllKilled(guideHouseAttackers, OnGuideRiflesKilled)
Trigger.OnEnteredProximityTrigger(TanyaDrop.CenterPosition, WDist.FromCells(4), function(actor, id)
if not EscortFinished or actor.Type ~= TanyaType then
return
end
Trigger.RemoveProximityTrigger(id)
SignalAllies()
end)
local prisonApproached = false
Trigger.OnEnteredProximityTrigger(Prison.CenterPosition, WDist.FromCells(8), function(actor, id)
if prisonApproached or not IsGuideOrAllies(actor) then
return
end
prisonApproached = true
Trigger.RemoveProximityTrigger(id)
ReinforceGuardHouse(actor.Type)
end)
local prisonBarrels = { PrisonBarrel1, PrisonBarrel2, PrisonBarrel3, PrisonBarrel4 }
Trigger.OnAllKilled(prisonBarrels, function()
local targets = { Prison, Executioner }
Utils.Do(targets, function(target)
if not target.IsDead then
target.Kill("FireDeath")
end
end)
end)
end
PrepareHostages = function()
ScheduleExecutions()
Trigger.OnKilled(Executioner, FreeHostages)
Trigger.OnAllKilled(Hostages, OnAllHostagesKilled)
Utils.Do(Hostages, function(hostage)
Trigger.OnKilled(hostage, OnHostageKilled)
end)
-- Keep hostages wandering, but only inside the pen.
Trigger.OnEnteredProximityTrigger(Executioner.CenterPosition, WDist.FromCells(2), function(actor, id)
if Executioner.IsDead then
Trigger.RemoveProximityTrigger(id)
return
end
if actor.Owner ~= Neutral then
return
end
actor.Stop()
actor.Move(HostageCenter.Location)
end)
end
Tick = function()
if TimeRemaining < 1 then
return
end
TimeRemaining = TimeRemaining - 1
if TimeRemaining % DateTime.Seconds(1) ~= 0 then
return
end
local text = UserInterface.Translate("hostage-dies-in", { ["time"] = Utils.FormatTime(TimeRemaining) })
UserInterface.SetMissionText(text, TimerColor)
end
PrepareCountdown = function(interval)
local text = UserInterface.Translate("hostage-dies-in", { ["time"] = Utils.FormatTime(interval) })
UserInterface.SetMissionText(text, TimerColor)
DateTime.TimeLimit = 1
Trigger.OnTimerExpired(function()
local survivors = Utils.Where(Hostages, IsAlive)
if #survivors > 1 then
ResetCountdown(interval)
return
end
UserInterface.SetMissionText("")
end)
end
ResetCountdown = function(interval)
DateTime.TimeLimit = interval
TimeRemaining = interval
TimerColor = HSLColor.White
Trigger.AfterDelay(interval - DateTime.Minutes(2), function()
TimerColor = USSR.Color
end)
end
StopCountdown = function()
DateTime.TimeLimit = 0
TimeRemaining = 0
UserInterface.SetMissionText("")
end
ScheduleExecutions = function()
local intervals =
{
easy = 5,
normal = 4,
hard = 3,
}
-- -1 tick avoids the redundant x-minute warning speech upon timer refresh.
local interval = DateTime.Minutes(intervals[Difficulty]) - 1
local delay = 0
PrepareCountdown(interval)
Utils.Do(Hostages, function(hostage)
delay = delay + interval
Trigger.AfterDelay(delay, function()
ExecuteHostage(hostage)
end)
end)
end
ExecuteHostage = function(hostage)
if hostage.IsDead then
local survivors = Utils.Where(Hostages, IsAlive)
if #survivors > 0 then
hostage = Utils.Random(survivors)
end
end
if Executioner.IsDead or hostage.IsDead then
return
end
Media.PlaySoundNotification(Greece, "HostageShot")
hostage.Kill("DefaultDeath")
end
FreeHostages = function()
StopCountdown()
ReinforceNorthChinook()
Greece.MarkCompletedObjective(FreeTheHostages)
Trigger.AfterDelay(DateTime.Seconds(3), function()
EscortTheHostages = AddPrimaryObjective(Greece, "get-hostages-to-church")
end)
local liveHostages = Utils.Where(Hostages, IsAlive)
Utils.Do(liveHostages, function(hostage)
hostage.Owner = England
FollowTanya(hostage)
end)
local guide = GetGuide()
if guide then
guide.Stop()
FollowTanya(guide)
end
end
FollowTanya = function(actor)
-- Hostages' panic can make them forget orders; OnIdle will refresh them.
-- For the same reason, the Guide elsewhere uses OnIdle for his actions.
Trigger.OnIdle(actor, function()
if Tanya.IsDead then
return
end
actor.Guard(Tanya)
end)
end
PrepareForestEncounter = function()
ForestTeamReachedHouse = false
local patrolPassed = false
local forestFoot = Trigger.OnEnteredFootprint({ ForestSafetyCheck.Location }, function(actor, id)
if patrolPassed or actor.Owner ~= USSR then
return
end
patrolPassed = true
Trigger.RemoveFootprintTrigger(id)
GuideToVillage()
end)
Trigger.OnAllKilled(ForestTeam, function()
if patrolPassed then
return
end
patrolPassed = true
Trigger.RemoveFootprintTrigger(forestFoot)
GuideToVillage(nil, true)
end)
Trigger.OnEnteredFootprint({ ForestCenter.Location }, function(actor, id)
if not IsGuide(actor) then
return
end
Trigger.RemoveFootprintTrigger(id)
Trigger.AfterDelay(0, function()
GuideToHideout(actor)
end)
end)
Trigger.OnExitedFootprint({ ForestHideout.Location }, function(actor, id)
if Executioner.IsDead or actor.IsDead or not IsGuide(actor) then
return
end
Trigger.RemoveFootprintTrigger(id)
Media.DisplayMessage(UserInterface.Translate("guide-follow-me"), actor.TooltipName)
end)
Trigger.OnEnteredFootprint({ ForestSouthwest.Location }, function(actor, id)
if actor.Type ~= "dog" then
return
end
Trigger.RemoveFootprintTrigger(id)
Trigger.AfterDelay(DateTime.Seconds(1), function()
if not actor.IsDead then
Media.PlaySoundNotification(Greece, "DogWhine")
end
end)
end)
Trigger.OnEnteredFootprint({ GuideHouseReveal.Location }, function(actor, id)
if actor.Type ~= "dog" then
return
end
ForestTeamReachedHouse = true
Trigger.RemoveFootprintTrigger(id)
end)
end
PrepareChronoTanks = function()
local sides =
{
westRoad =
{
cells = { CPos.New(41, 65), CPos.New(42, 65), CPos.New(43, 65), CPos.New(44, 65), CPos.New(45, 65), CPos.New(46, 65) },
spawns = { ChronoEntryWest1.Location, ChronoEntryWest2.Location }
},
eastRoad =
{
cells = { CPos.New(66, 70), CPos.New(66, 71), CPos.New(67, 71), CPos.New(68, 71), CPos.New(69, 71), CPos.New(70, 71), CPos.New(71, 71), CPos.New(72, 71), CPos.New(72, 71), CPos.New(73, 71) },
spawns = { ChronoEntryEast1.Location, ChronoEntryEast2.Location }
}
}
Utils.Do(sides, function(side)
Trigger.OnEnteredFootprint(side.cells, function(actor, id)
local signaled = SignalForReinforcements and Greece.IsObjectiveCompleted(SignalForReinforcements)
if not signaled or actor.Owner ~= Greece then
return
end
Trigger.RemoveFootprintTrigger(id)
ReinforceChronoTanks(side.spawns)
end)
end)
end
PrepareSovietGuards = function()
Utils.Do(BadGuy.GetGroundAttackers(), function(soviet)
Trigger.OnDamaged(soviet, function(_, attacker)
if attacker.Type == "dtrk" or attacker.Owner ~= Greece then
return
end
Trigger.Clear(soviet, "OnDamaged")
IdleHunt(soviet)
end)
end)
local beachAlerted = false
local beachGroup = { SouthBeachGuard1, SouthBeachGuard2, SouthBeachGuard3, SouthBeachGuard4 }
Trigger.OnAllKilled(beachGroup, SovietSouthBeachCamera.Destroy)
Trigger.OnAnyKilled(beachGroup, function()
if beachAlerted or Tanya.IsDead then
return
end
beachAlerted = true
GroupIdleHunt(beachGroup)
Utils.Do(beachGroup, function(actor)
if actor.IsDead then
return
end
-- Get attack dogs sprinting if they aren't already.
actor.Attack(Tanya)
end)
end)
local others =
{
{ RoadTurretRifle1, RoadTurretRifle2, RoadTurretWest, RoadTurretEast },
{ GuideRifleWest, GuideRifleEast },
{ FarmGuard1, FarmGuard2, FarmGuard3, FarmGuard4 },
SoutheastTurretGuards,
ForestTeam,
BlockerTeam
}
Utils.Do(others, function(group)
GroupHuntOnDamaged(group, Greece)
end)
end
PrepareMainReveals = function()
local mainReveals =
{
roadTurrets =
{
cells = { CPos.New(36, 71), CPos.New(36, 72), CPos.New(36, 73), CPos.New(37, 73), CPos.New(38, 73), CPos.New(39, 73), CPos.New(40, 73), CPos.New(41, 73), CPos.New(42, 73), CPos.New(43, 73), CPos.New(44, 73), CPos.New(45, 73), CPos.New(46, 73), CPos.New(47, 73) },
location = RoadTurretReveal.Location
},
southBeachBarrels =
{
cells = { CPos.New(51, 75), CPos.New(51, 76), CPos.New(51, 77), CPos.New(51, 78), CPos.New(51, 79) },
location = SouthBeachReveal.Location,
onTriggered = SovietStartCamera.Destroy
},
southeastTurret =
{
cells = { CPos.New(68, 82), CPos.New(68, 83), CPos.New(68, 84), CPos.New(68, 85), CPos.New(68, 86), CPos.New(68, 87), CPos.New(68, 88), CPos.New(68, 89) },
location = SoutheastTurret.Location,
onTriggered = function()
local dogs = Map.ActorsWithTag("TurretDog")
GroupAttackMove(dogs, SoutheastTurret.Location + CVec.New(0, 1))
end
},
samFence =
{
cells = { CPos.New(69, 74), CPos.New(70, 74), CPos.New(71, 74), CPos.New(72, 74), CPos.New(73, 74), CPos.New(74, 74) },
location = SamFenceReveal.Location,
onTriggered = ReinforceVillagePatrol
},
riverHouse =
{
cells = { CPos.New(80, 79), CPos.New(80, 80), CPos.New(80, 81), CPos.New(80, 82), CPos.New(80, 83) },
location = GuideHouse.Location,
type = "camera.paradrop",
onTriggered = function()
ReinforceVillagePatrol()
OrderGuideRifles()
end
},
forest =
{
cells = { CPos.New(85, 62), CPos.New(86, 62), CPos.New(87, 62), CPos.New(88, 62), CPos.New(89, 62), CPos.New(90, 62), CPos.New(91, 62), CPos.New(92, 62), CPos.New(93, 62), CPos.New(94, 62) },
location = ForestCenter.Location,
duration = DateTime.Seconds(20),
onTriggered = function()
OrderForestPatrol(DateTime.Seconds(3))
SpawnPlayerCamera(ForestPatrolStart.Location, DateTime.Seconds(6), "camera")
end
},
forestTeamBackAtForest =
{
location = ForestCenter.Location,
type = "camera.tiny",
condition = function(actor)
return ForestTeamReachedHouse and actor.Type == "dog"
end
},
forestTeamBackAtStart =
{
location = ForestPatrolStart.Location,
type = "camera.small",
condition = function(actor)
return ForestTeamReachedHouse and actor.Type == "dog"
end
},
forestTeamAtBridge =
{
location = VillageBridgeNortheast.Location,
type = "camera.small",
condition = function(actor)
return ForestTeamReachedHouse and actor.Type == "dog"
end
}
}
Utils.Do(mainReveals, function(reveal)
local triggered = false
local isCorrect = reveal.condition or function(actor)
return IsGuideOrAllies(actor)
end
Trigger.OnEnteredFootprint(reveal.cells or { reveal.location }, function(actor, id)
if triggered or not isCorrect(actor) then
return
end
triggered = true
Trigger.RemoveFootprintTrigger(id)
if reveal.location then
SpawnPlayerCamera(reveal.location, reveal.duration, reveal.type)
end
if reveal.onTriggered then
reveal.onTriggered()
end
end)
end)
end
PrepareBlockers= function()
local houseReached = false
Trigger.OnEnteredProximityTrigger(BlockerReturnProximity.CenterPosition, WDist.FromCells(2), function(actor, id)
if not Executioner.IsDead or actor.Owner ~= Greece then
return
end
Trigger.RemoveProximityTrigger(id)
BlockersToBridgePatrol()
end)
Trigger.OnEnteredProximityTrigger(GuideHouseReveal.CenterPosition, WDist.FromCells(2), function(actor, id)
if BlockerTank.IsDead then
Trigger.RemoveProximityTrigger(id)
return
end
if not Executioner.IsDead or actor ~= BlockerTank then
return
end
houseReached = true
Trigger.RemoveProximityTrigger(id)
end)
-- For BlockerTeam returning north from the church/Guide house area.
Trigger.OnEnteredProximityTrigger(SouthRiver.CenterPosition, WDist.FromCells(3), function(actor, id)
if BlockerTank.IsDead then
Trigger.RemoveProximityTrigger(id)
return
end
if not houseReached or not Executioner.IsDead or actor ~= BlockerTank then
return
end
Trigger.RemoveProximityTrigger(id)
SpawnPlayerCamera(SouthRiver.Location, DateTime.Seconds(6), "camera.tiny")
SpawnPlayerCamera(ForestCenter.Location, DateTime.Seconds(20))
end)
end
PrepareReveals = function()
PrepareMainReveals()
Trigger.OnEnteredProximityTrigger(Prison.CenterPosition, WDist.FromCells(10), function(actor, id)
if not IsGuideOrAllies(actor) then
return
end
Trigger.RemoveProximityTrigger(id)
SpawnPlayerCamera(PrisonReveal.Location, DateTime.Seconds(20))
BlockersToGuideHouse()
local delay = DateTime.Seconds(1)
if actor.Type == TanyaType then
delay = 5
end
Trigger.AfterDelay(delay, OrderGeneralRetreat)
end)
Trigger.OnKilled(SovietDemoTruck, function()
if not SovietDemoTruck.IsIdle then
return
end
SpawnPlayerCamera(SovietDemoTruck.Location, DateTime.Seconds(1), "camera.paradrop")
end)
local otherExplosions =
{
{
actors = { SouthBeachPump1, SouthBeachPump2 },
origin = BeachExplosionReveal.Location
},
{
actors = { PrisonPump, PrisonBarrel1, PrisonBarrel2, PrisonBarrel3, PrisonBarrel4 },
origin = PrisonPump.Location
},
{
actors = { DemoPump1, DemoPump2, DemoPump3, DemoHouse },
origin = DemoBarrel3.Location
},
{
actors = { NorthBeachPump2 },
origin = NorthBeachPump2.Location
},
{
actors = { LakePump1, LakePump2 },
origin = LakeExplosionReveal.Location
},
}
Utils.Do(otherExplosions, function(group)
PrepareExplosionReveal(group.actors, group.origin, DateTime.Seconds(1))
end)
end
PrepareExplosionReveal = function(actors, origin, duration)
local revealed = false
Utils.Do(actors, function(actor)
Trigger.OnDamaged(actor, function(_, attacker)
if revealed or (attacker.Type ~= "barl" and attacker.Type ~= "brl3") then
return
end
revealed = true
SpawnPlayerCamera(origin, duration, "camera.small")
end)
end)
end
PrepareChurch = function()
Trigger.OnKilled(Church, OnChurchKilled)
Trigger.OnPassengerEntered(Church, CheckEscortObjective)
Trigger.OnEnteredProximityTrigger(Church.CenterPosition, WDist.FromCells(4), function(actor)
local civilian = IsHostage(actor) or IsGuide(actor)
if civilian then
EnterChurch(actor)
end
end)
end
EnterChurch = function(actor)
if Church.IsDead then
return
end
actor.Stop()
actor.Move(ChurchRally.Location)
if IsGuide(actor) then
actor.Infiltrate(Church)
return
end
actor.EnterTransport(Church)
end
StartFireSale = function(player)
-- Short delay in case the Soviets' Tech Center must change owner first.
Trigger.AfterDelay(1, function()
local buildings = Utils.Where(player.GetActors(), function(actor)
return actor.HasProperty("StartBuildingRepairs")
end)
if #buildings == 0 then
return
end
Utils.Do(buildings, function(building)
building.Sell()
end)
Trigger.OnAllRemovedFromWorld(buildings, function()
GroupIdleHunt(player.GetGroundAttackers())
end)
end)
end
SignalAllies = function()
Greece.MarkCompletedObjective(SignalForReinforcements)
SpawnFlare(TanyaDrop.Location, DateTime.Minutes(4))
Trigger.AfterDelay(DateTime.Seconds(1), ReinforceAllies)
if General.IsDead then
OrderBadGuyHunters()
return
end
Trigger.OnKilled(General, OrderBadGuyHunters)
end
GroupAttackMove = function(actors, location, closeEnough)
Utils.Do(actors, function(actor)
if actor.IsDead then
return
end
actor.AttackMove(location, closeEnough or 0)
end)
end
GroupIdleHunt = function(actors)
Utils.Do(actors, IdleHunt)
end
-- Patrol along path. Pause at each location until others arrive.
-- At final location, call onFinished(actors) or loop and restart.
GroupTightPatrol = function(actors, path, looped, pauseTime, onFinished)
pauseTime = pauseTime or 0
local paused = false
local goal = 1
local arrived = { }
local patrollers = Utils.Where(actors, IsAlive)
for id = 1, #patrollers do
Trigger.OnIdle(patrollers[id], function(actor)
if paused then
return
end
-- Move each unit to the goal until their arrival or death.
if not arrived[id] then
actor.AttackMove(path[goal], 2)
actor.CallFunc(function()
arrived[id] = true
end)
return
end
if not AreAllIdleOrDead(patrollers) then
return
end
-- All live actors have arrived and are idle.
paused = true
Trigger.AfterDelay(pauseTime, function()
paused = false
end)
arrived = { }
local nextLocation, patrolFinished = NextPatrolLocation(goal, #path, looped)
if patrolFinished then
ClearTightPatrol(patrollers, onFinished)
return
end
goal = nextLocation
end)
end
end
-- Fetch the next goal in a patrol path, or signal the end of a one-way patrol.
NextPatrolLocation = function(current, final, looped)
if current < final then
return current + 1, false
end
if looped then
return 1, false
end
return current, true
end
-- Clear a group's TightPatrol and give orders queued for that patrol's finish.
ClearTightPatrol = function(actors, onFinished)
Utils.Do(actors, function(actor)
if actor.IsDead then
return
end
Trigger.Clear(actor, "OnIdle")
end)
if not onFinished then
return
end
Trigger.AfterDelay(1, function()
onFinished(actors)
end)
end
GroupHuntOnDamaged = function(actors, targetPlayer)
local alerted = false
Utils.Do(actors, function(actor)
if actor.IsDead then
return
end
Trigger.OnDamaged(actor, function(_, attacker)
if alerted or attacker.Type == "dtrk" or attacker.Owner ~= targetPlayer then
return
end
alerted = true
Utils.Do(actors, function(hunter)
if hunter.IsDead or not hunter.HasProperty("Hunt") then
return
end
-- If the hunter is patrolling, their patrol will be aborted.
hunter.Stop()
Trigger.Clear(hunter, "OnIdle")
Trigger.AfterDelay(1, function()
IdleHunt(hunter)
end)
end)
end)
end)
end
OrderForestPatrol = function(delay)
local path =
{
ForestNorthwest.Location,
ForestSouthwest.Location,
SouthRiver.Location,
GuideHouseReveal.Location,
ForestCenter.Location,
ForestPatrolStart.Location,
VillageBridgeNortheast.Location,
VillageNortheast.Location,
PrisonReveal.Location,
BadGuyRally.Location
}
Trigger.AfterDelay(delay, function()
GroupTightPatrol(ForestTeam, path, false)
end)
end
BlockersToGuideHouse = function()
local path =
{
ForestNorthwest.Location,
SouthRiver.Location,
GuideHouseReveal.Location
}
GroupTightPatrol(BlockerTeam, path, false)
end
BlockersToBridgePatrol = function()
local path =
{
SouthRiver.Location,
ForestSouthwest.Location,
ForestNorthwest.Location,
ForestPatrolStart.Location,
VillageBridgeNortheast.Location,
ForestPatrolStart.Location,
ForestNorthwest.Location,
ForestSouthwest.Location
}
GroupTightPatrol(BlockerTeam, path, true)
end
SpawnGuide = function()
Trigger.AfterDelay(15, function()
if GuideHouse.IsDead then
return
end
local guide = Actor.Create("c1", true, { Owner = GoodGuy, Location = GuideHouse.Location, SubCell = 4, Facing = Angle.South } )
GuideToForest(guide)
end)
end
GuideToForest = function(guide)
guide.Move(GuideHouse.Location + CVec.New(0, 1))
guide.CallFunc(function()
Media.DisplayMessage(UserInterface.Translate("guide-thank-you"), guide.TooltipName)
end)
guide.Move(SouthRiver.Location)
guide.Move(ForestSouthwest.Location)
guide.Move(ForestCenter.Location)
end
GuideToHideout = function(guide)
if IsGroupDead(ForestTeam) then
-- If this team dies, the Guide will skip ahead to GuideToVillage.
return
end
guide.CallFunc(function()
Media.DisplayMessage(UserInterface.Translate("guide-patrol-coming"), guide.TooltipName)
end)
guide.Wait(DateTime.Seconds(2))
guide.CallFunc(function()
if IsGroupDead(ForestTeam) then
return
end
Media.DisplayMessage(UserInterface.Translate("guide-come-this-way"), guide.TooltipName)
guide.Move(ForestHideout.Location)
end)
end
GuideToVillage = function(guide, patrolKilled)
guide = guide or GetGuide()
if not guide then
return
end
guide.Stop()
Media.PlaySoundNotification(Greece, "GuideOkay")
Media.DisplayMessage(UserInterface.Translate("guide-safe-to-move"), guide.TooltipName)
if not patrolKilled then
guide.Wait(DateTime.Seconds(1))
end
if VillageBridge.IsDead then
GuideToBackdoor(guide)
return
else
Trigger.OnKilled(VillageBridge, function()
GuideToBackdoor(guide)
end)
end
guide.Move(ForestNorthwest.Location)
guide.Move(VillageBridgeNortheast.Location)
Trigger.OnIdle(guide, function()
guide.AttackMove(PrisonIntersection.Location)
guide.CallFunc(function()
GuideToHostages(guide)
end)
end)
end
GuideToBackdoor = function(guide)
if guide.IsDead or Greece.IsObjectiveCompleted(FreeTheHostages) then
return
end
Trigger.Clear(guide, "OnIdle")
Trigger.AfterDelay(1, function()
if guide.IsDead then
return
end
guide.Stop()
Trigger.OnIdle(guide, function()
guide.AttackMove(GuideBackdoorGoal.Location)
guide.CallFunc(function()
GuideToHostages(guide)
end)
end)
end)
end
GuideToHostages = function(guide)
if Executioner.IsDead or PrisonBarrel2.IsDead or PrisonBarrel3.IsDead then
return
end
-- To compensate for the guards' better speed/range with minimal changes,
-- the Guide is made untargetable until actors are in position (or dead).
local ghost = guide.GrantCondition("untargetable")
Trigger.Clear(guide, "OnIdle")
Trigger.OnEnteredProximityTrigger(PrisonBarrel3.CenterPosition, WDist.FromCells(2), function(actor, id)
if Executioner.IsDead or PrisonBarrel2.IsDead or guide.IsDead then
Trigger.RemoveProximityTrigger(id)
return
end
if actor.Type ~= "e1" or guide.Location ~= GuideBarrelGoal.Location then
return
end
Trigger.RemoveProximityTrigger(id)
guide.Stop()
guide.Attack(PrisonBarrel2)
end)
guide.Move(GuideBarrelGoal.Location)
local revokeTargets = Utils.Where({ Executioner, PrisonBarrel1, PrisonBarrel2, PrisonBarrel3, PrisonBarrel4 }, IsAlive)
Trigger.OnAnyKilled(revokeTargets, function()
if guide.IsDead then
return
end
guide.RevokeCondition(ghost)
end)
end
OrderGeneralRetreat = function()
if General.IsDead or not General.IsIdle then
return
end
local reinforcementTimer = DateTime.Seconds(30)
General.Move(BadGuyRally.Location)
General.Move(GeneralRally.Location + CVec.New(0, 3))
General.CallFunc(function()
local camera = SpawnPlayerCamera(GeneralRally.Location, reinforcementTimer)
Trigger.OnKilled(General, function()
RemoveActor(camera)
end)
end)
General.Move(GeneralRally.Location)
General.CallFunc(function()
Trigger.AfterDelay(reinforcementTimer, ReinforceBadGuy)
end)
end
OrderGuideRifles = function()
local rifles = { GuideRifleWest, GuideRifleEast }
local offset = 0
Utils.Do(rifles, function(rifle)
Trigger.AfterDelay(offset, function()
if rifle.IsDead or GuideHouse.IsDead then
return
end
rifle.Attack(GuideHouse, true, true)
end)
offset = offset + 8
end)
end
OrderBadGuyHunters = function()
local blockers = Utils.Where(BlockerTeam, IsAlive)
ClearTightPatrol(blockers, function(actors)
GroupAttackMove(actors, WestRoadRally.Location, 2)
GroupIdleHunt(actors)
end)
local others = { SovietDemoTruck, DemoFlamer1, DemoFlamer2, ForwardCliffGuard2 }
Utils.Do(others, function(other)
if other.IsDead then
return
end
other.AttackMove(WestRoadRally.Location)
IdleHunt(other)
end)
end
AreAllIdleOrDead = function(actors)
return Utils.All(actors, function(actor)
return actor.IsIdle or actor.IsDead
end)
end
GetBridge = function()
local bridges = Neutral.GetActorsByType("bridge1")
return bridges[1]
end
GetGuide = function()
local guides = GoodGuy.GetActorsByType("c1")
return guides[1]
end
IsAlive = function(actor)
return not actor.IsDead
end
IsGroupDead = function(actors)
return Utils.All(actors, function(actor)
return actor.IsDead
end)
end
IsGuide = function(actor)
return actor.Type == "c1"
end
IsGuideOrAllies = function(actor)
return actor.Owner == Greece or IsGuide(actor)
end
IsHostage = function(actor)
return actor.Owner == England
end
SpawnPlayerCamera = function(location, duration, type, delay)
duration = duration or DateTime.Seconds(6)
type = type or "camera"
return SpawnMiscActor(type, GoodGuy, location, duration, delay)
end
SpawnFlare = function(location, duration, delay)
return SpawnMiscActor("flare", England, location, duration, delay)
end
-- Spawn a supporting actor with optional delay and self-removal.
SpawnMiscActor = function(type, owner, location, duration, delay)
local actor = Actor.Create(type, false, { Owner = owner, Location = location or CPos.New(0, 0) })
if delay then
Trigger.AfterDelay(delay, function()
actor.IsInWorld = true
end)
else
actor.IsInWorld = true
end
if duration and duration > 0 then
RemoveActor(actor, duration)
end
return actor
end
RemoveActor = function(actor, delay)
Trigger.AfterDelay(delay or 0, function()
if not actor or not actor.IsInWorld then
return
end
actor.Destroy()
end)
end
OnHostageKilled = function()
if Greece.GetObjectiveType(SaveAllHostages) == "Primary" then
MarkAlliedDefeat(SaveAllHostages)
return
end
Greece.MarkFailedObjective(SaveAllHostages)
CheckEscortObjective()
end
OnAllHostagesKilled = function()
Media.PlaySoundNotification(Greece, "AlertBleep")
Media.DisplayMessage(UserInterface.Translate("all-hostages-killed"))
MarkAlliedDefeat(EscortTheHostages or FreeTheHostages)
end
OnHostagesEscorted = function()
EscortFinished = true
Media.PlaySpeechNotification(Greece, "ObjectiveMet")
Greece.MarkCompletedObjective(EscortTheHostages)
local delay = DateTime.Seconds(3)
if DestroySoviets then
delay = 5
if not Greece.IsObjectiveFailed(SaveAllHostages) then
Greece.MarkCompletedObjective(SaveAllHostages)
end
end
Trigger.AfterDelay(delay, function()
DestroySoviets = DestroySoviets or AddPrimaryObjective(Greece, "destroy-soviet-units-infrastructure")
SignalForReinforcements = AddSecondaryObjective(Greece, "signal-for-reinforcements")
end)
end
OnChurchKilled = function()
Media.PlaySoundNotification(Greece, "AlertBleep")
Media.DisplayMessage(UserInterface.Translate("church-destroyed"))
MarkAlliedDefeat(EscortTheHostages, DateTime.Seconds(2))
end
OnForwardCommandKilled = function()
if not BadGuyReinforced then
PrepareTechSale()
end
StartFireSale(USSR)
local forwardBaseGuards = { ForwardRifle1, ForwardRifle2, ForwardGrenadier, ForwardCliffGuard1, ForwardCliffGuard2, TechTank }
GroupIdleHunt(forwardBaseGuards)
end
OnGuideRiflesKilled = function()
if not GuideHouse.IsDead then
SpawnGuide()
end
end
OnTanyaKilled = function(_, killer)
CheckAlliedDestruction()
if killer.Type == "v2rl" and IsAlive(killer) then
SpawnPlayerCamera(killer.Location, DateTime.Minutes(1), "camera.tiny")
end
if not EscortFinished then
local liveHostages = Utils.Where(Hostages, IsAlive)
Utils.Do(liveHostages, function(hostage)
hostage.Panic()
end)
end
end