--[[ 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.GetFluentMessage("hostage-dies-in", { ["time"] = Utils.FormatTime(TimeRemaining) }) UserInterface.SetMissionText(text, TimerColor) end PrepareCountdown = function(interval) local text = UserInterface.GetFluentMessage("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.GetFluentMessage("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.GetFluentMessage("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.GetFluentMessage("guide-patrol-coming"), guide.TooltipName) end) guide.Wait(DateTime.Seconds(2)) guide.CallFunc(function() if IsGroupDead(ForestTeam) then return end Media.DisplayMessage(UserInterface.GetFluentMessage("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.GetFluentMessage("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.GetFluentMessage("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.GetFluentMessage("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