From b1fd3924862e91f545aafdc30fe938d92ef72319 Mon Sep 17 00:00:00 2001 From: yamismo Date: Thu, 20 Apr 2023 20:00:07 +0200 Subject: [PATCH] Add GDI Covert Operations - Twist of Fate - scg41ea --- mods/cnc/languages/lua/en.ftl | 12 +- mods/cnc/maps/twist-of-fate/map.bin | Bin 0 -> 20497 bytes mods/cnc/maps/twist-of-fate/map.png | Bin 0 -> 12117 bytes mods/cnc/maps/twist-of-fate/map.yaml | 1147 +++++++++++++++++ mods/cnc/maps/twist-of-fate/rules.yaml | 107 ++ .../maps/twist-of-fate/twist-of-fate-AI.lua | 650 ++++++++++ mods/cnc/maps/twist-of-fate/twist-of-fate.lua | 329 +++++ mods/cnc/maps/twist-of-fate/weapons.yaml | 3 + mods/cnc/missions.yaml | 1 + 9 files changed, 2248 insertions(+), 1 deletion(-) create mode 100644 mods/cnc/maps/twist-of-fate/map.bin create mode 100644 mods/cnc/maps/twist-of-fate/map.png create mode 100644 mods/cnc/maps/twist-of-fate/map.yaml create mode 100644 mods/cnc/maps/twist-of-fate/rules.yaml create mode 100644 mods/cnc/maps/twist-of-fate/twist-of-fate-AI.lua create mode 100644 mods/cnc/maps/twist-of-fate/twist-of-fate.lua create mode 100644 mods/cnc/maps/twist-of-fate/weapons.yaml diff --git a/mods/cnc/languages/lua/en.ftl b/mods/cnc/languages/lua/en.ftl index 928fdfad7a..4d02df461e 100644 --- a/mods/cnc/languages/lua/en.ftl +++ b/mods/cnc/languages/lua/en.ftl @@ -25,7 +25,7 @@ kill-creatures = Kill all creatures in the area. ## gdi01 establish-beachhead = Establish a beachhead. -## gdi01, gdi02, gdi03, gdi04c, gdi05abc +## gdi01, gdi02, gdi03, gdi04c, gdi05abc, twist-of-fate eliminate-nod = Eliminate all Nod forces in the area. ## gdi02, nod09 @@ -150,3 +150,13 @@ nod-soldier = Nod Soldier civilians-runs = Hey, those civilians... where are they going? destroy-ion-cannon-advise = The GDI are preparing their ion cannon. Don't let them get used to it. village-destruction-warning = Be careful, commander. The GDI won't stand still while we burn the entire village. + +## twist-of-fate +clear-path = Repel the ambush and clear the way + for our MCV. +recover-old-base = Capture the Construction Yard in our recon + post to regain control. +air-strikes-intel-report = Nod airstrikes are being directed by a Communications Center, located northwest. We may gain useful information from its capture. +capture-nod-communications-center = Capture the Nod Communications Center to + the northwest. +communications-center-captured-sams-located = Our engineers located Nod SAM sites. They also disarmed an unusual trap on the Construction Yard to the south. diff --git a/mods/cnc/maps/twist-of-fate/map.bin b/mods/cnc/maps/twist-of-fate/map.bin new file mode 100644 index 0000000000000000000000000000000000000000..49b034bcca5ba2f130d4cbfb3f9cd9015a3ba5b1 GIT binary patch literal 20497 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 bM{Yi^TlS-}P>K53$Ns8aY)?b}j}!j~d`dK# literal 0 HcmV?d00001 diff --git a/mods/cnc/maps/twist-of-fate/map.png b/mods/cnc/maps/twist-of-fate/map.png new file mode 100644 index 0000000000000000000000000000000000000000..048999e5279ec89801037a4fc39cfa0b0dc94ef6 GIT binary patch literal 12117 zcmcI~g;x~q`?rb;(%m2(($bAfgCIz^l$5}dOXt$vjV>M1NQcDIAziz`(%sVWuFv

jrER0e!O%6 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 )^^Vr>fI_(*kpLzP}PkkV7cKl1!%C9>e zK~&~KHC}vL?630rK*mV>niTXRhRQGh*rK=hW+9d$RE~h&KI`4s_0mbewC&@q&pU{j z%3aan5L;(=hROhO*Vn~U#`(lbSL>#dqvdebT2cc&+#_zca0g?V%ike7{0%glR3nhF7}Fl|NjsUA>D?d!jmhW# ztWXy0q_LnW|MZqH0x)x3;p9VQ!D>L0xFjA4&te)yed^MsGDx_$5WcW0Nj=^?>boPN z)AUJm-v2;EXIJPNAowkv19A03!~NS?cCqCAQEak~j3|LiWZccS*Uh26^HI97 zdEaf$!nc8$n*E2qQ8Wp~}(?rJ4mW6jQ z)uFf5Xu==8xUAmME)jaCE{CLFjB0HVOJ<%9mPxne#^LZg>O;^)Qdhlv=G z83A`$qP3~!_q!K@e|FiE3-8)oX-XpNgbM?BlX3eB>f&juKcg?# z0U{Cvfa!f&3#Dv5<)+u56zBy3;S+952SCCzlE2ZWFNI zfGMyX+>(8UH)(2OX#P9vsHTwoi(wJEKw6rGJ0(4FUB2f-uiB7mUGMx# zBSqq+iyS_{n9wHa2qThlb7>&?clo0&XewMqN9a@dLTO++8Xs&A*I%2xbZyJRzc**& z4?A&K!hGrYC}!0m&G}Y1TFDdjt{)4#-e?atYmD2nIW08%mLtol|Ja!cACUTLHpRj2 zR!8~#>g6#ESW)%|WlKC~{vjh#)*L!hvW=gwA<+GLATT$JEHX0R&}dNf?5gL%f!`4o zL{gO2O;%P-&wr{{G^m84$JfzjMElA8_AQZ~Yv%}=0@@o4%+&0yWOqtx3FQw4qrc0= zu<;VgX7!Ueq+d_q@4kK_V*N1?7^}_odrK!E9O98yCAlQ~j@bZ*pomimGF zS~g_pIxWh`E;g0z36AFnizTY|;8J%JRQk>ioBLHOOnb#CPXrCfO3N_IRr=HXF;};* z2{}7nD2)qM$Gw$O#}J8FcQh*9+1?b;`U~xsjuhT(6_B1Z{g(i`m03L5v-)Sb1W=hab5`VngIJ|uLIlU>H+e6QtQ8^hYpJR0;U z*1E*i{AptXp{Js?==rfiMLt0{jMBQB!p@Uc$8%WeEnftsU1=&@qyrV!jNH!;aC&`h zGpN_hu-r335}CZl$F;~Y7cLxF-Rt8TT-eS*CPXbURHg+>%L2w<`-_In```ok! z?~UC#9?HRK$0z{`AFGk#w$&xO5o9>~J~&|g3)(W?{$KOZ^sZkOorr(pVA7GNkWcWj zoJRA9NP&+DTaK}jNR51!s_b{CtUk3FVh4M9wX*32sIw^S_iK)u!!Gv8HKR7p<=6#N!{` zR^c`HCz>FkYy;8;42xtMd2eKZ3g($F^RTc}+D_Bz-ZS??GPp?)9dP0(_^x*tn(kO{ z8BrS~?%W^kt!DAK#w<*aMtjOUo%UG*a-@CeV7DlUq{|-;KEM6wMYmjJTh|YFk`(}k z6bn3Rw<>}-o<8?=u42-Fd&NtlT}a)tfmX#&3Gfn(SCnCZf5`{p+>*RDq4gx=>F0M{ zxQ|SXqU&$aRR|e3i?3q{0QfFC4-S)#B~_1+EmQZRZXl|%Q%Wq_`|4T4wNX;wp)_e> zpD)7(H)F^&>X^w=jG_SH$w?L?_#2}HcB4R;;`X^N?~@Xd7=mk&y4R?c_ zrueUO?-_&0-Q02yMyf=R{!ZAJJoKuz9Vy(`(sL+|;YiQm#p!{fTX7U7)`g}_YF}NT z*cQ_1Ul2i;M44;KZNbPx`kgkPHTA0ncldUtl{-cMeuph&m^hIp-D3wI5wbm?(ORU? zSdN-bGA_l4E~~$q+2r08ZCfDwBqp5kG@O)k+PHUgw-otqV{#opWQh59JDAgD=!(#8 zQL;qxod2)YQx 0 then + apache.Hunt() + else + if not hpad.IsDead then + apache.Stop() + apache.ReturnToBase(hpad) + end + end + Trigger.AfterDelay(Utils.RandomInteger(25,50), function() + ApacheGuard(apache, hpad, topleft, botright, false) + end) +end + +ApacheRebuild = function(hpad, topleft, botright) + if hpad.IsDead and hpad.Owner == Nod then + return + end + if CheckProduction() then + hpad.Build({ "heli" }, function(h) + ApacheGuard(h[1], hpad, topleft, botright, true) + end) + else + Trigger.AfterDelay(250, function() + ApacheRebuild(hpad, topleft, botright) + end) + end +end + +GuardBuilding = function(building) + Trigger.OnDamaged(building, function(_, atk, _) + if atk.Type ~= "player" and not atk.IsDead and atk.Owner == GDI then + Utils.Do(BaseDefenseTeam, function(guard) + if not guard.IsDead and not building.IsDead then + if guard.Stance == "Defend" then + guard.Stop() + guard.Stance = "AttackAnything" + guard.AttackMove(atk.Location, 3) + Trigger.OnIdle(guard, function() + guard.AttackMove(CPos.New(10,48)) + guard.Stance = "Defend" + Trigger.ClearAll(guard) + end) + end + end + end) + end + end) +end + +MoveAsGroup = function(team, path, i, loop) + if i == 1 and not loop then + Utils.Do(team, function(u) + Trigger.OnDamaged(u, function(_, atk, _) + if atk.Owner == GDI then + Trigger.AfterDelay(2, function() + Utils.Do(team, function(u) + if not u.IsDead then + Trigger.Clear(u, "OnDamaged") + IdleHunt(u) + end + end) + end) + end + end) + end) + end + Utils.Do(team, function(a) + if not a.IsDead then + a.Stance = "AttackAnything" + a.AttackMove(path[i], 3) + Trigger.OnIdle(a, function() + Trigger.Clear(a, "OnIdle") + a.Stance = "Defend" + local ii = 0 + local regrouped = false + Utils.Do(team, function(a) + if a.IsDead or a.Stance == "Defend" then + ii = ii + 1 + if ii == #team then + regrouped = true + end + end + end) + if regrouped then + if i == #path then + if loop == true then + i = 1 + else + Trigger.AfterDelay(5, function() + Utils.Do(team, function(a) + if not a.IsDead then + a.Stance = "AttackAnything" + IdleHunt(a) + end + end) + end) + return + end + else + i = i + 1 + end + Trigger.AfterDelay(20, function() + MoveAsGroup(team, path, i, loop) + end) + end + end) + end + end) +end + +SendStealthTanks = function() + Trigger.OnAllKilled(StealthTeam, function() + Trigger.AfterDelay(DateTime.Seconds(Utils.RandomInteger(90,270) + ProductionCooldownSeconds), function() + table.insert(UniqueTeamsQueue, { team = StealthTeams, job = "sneakAttack" }) + end) + end) + local SneakPath = Utils.Random(SneakPaths) + Utils.Do(StealthTeam, function(u) + Trigger.OnDamaged(u, function() + u.Stop() + u.Hunt() + end) + u.Stance = "AttackAnything" + for i = 1, #SneakPath do + if i < #SneakPath then + u.Move(SneakPath[i], 2) + else + u.AttackMove(SneakPath[i], 2) + end + end + IdleHunt(u) + end) +end + +StartGuard = function() + Trigger.OnAllKilled(BaseDefenseTeam, function() + table.insert(UniqueTeamsQueue, { team = DefenseTeams, job = "defend" }) + end) + Utils.Do(BaseDefenseTeam, function(guard) + guard.Stance = "Defend" + Trigger.OnKilled(guard, function() + if #Utils.Where(BaseDefenseTeam, function(g) return g.IsDead == false end) < 6 then + Trigger.AfterDelay(5, function() + Utils.Do(BaseDefenseTeam, function(a) + if not a.IsDead then + a.Stance = "AttackAnything" + Trigger.Clear(a, "OnKilled") + IdleHunt(a) + end + end) + end) + end + end) + end) +end + +StartPatrol = function() + Trigger.OnAllKilled(PatrolTeam, function() + Trigger.AfterDelay(DateTime.Seconds(Utils.RandomInteger(60, 150)), function() + table.insert(UniqueTeamsQueue, { team = PatrolTeams, job = "patrol" }) + end) + end) + Utils.Do(PatrolTeam, function(a) + a.Move(CPos.New(3,28)) + Trigger.OnKilled(a, function() + Utils.Do(PatrolTeam, function(a) + if not a.IsDead then + a.Stop() + IdleHunt(a) + end + end) + end) + end) + MoveAsGroup(PatrolTeam, PatrolPath, 1, true) +end + +-- Building logic +AddToRebuildQueue = function(b) + local index = #NodRebuildList + 1 + if b.Type == "proc" then + index = 1 + elseif Utils.Any(ProductionTypes, function(pt) return pt == b.Type end) then + for i = 1, #NodRebuildList do + if NodRebuildList[i].type ~= "proc" then + index = i + break + end + end + elseif Utils.Any(PowerTypes, function(pt) return pt == b.Type end) then + for i = 1, #NodRebuildList do + local lastIndex = #NodRebuildList - (i - 1) + if NodRebuildList[lastIndex].type ~= "silo" or lastIndex == 1 then + index = lastIndex + break + end + end + elseif b.Type ~= "silo" then + for i = 1, #NodRebuildList do + local lastIndex = #NodRebuildList - (i - 1) + if NodRebuildList[lastIndex].type ~= "silo" and Utils.Any(PowerTypes, function(pt) return pt == b.Type end) or lastIndex == 1 then + index = lastIndex + break + end + end + end + table.insert(NodRebuildList, index, { type = b.Type, pos = b.Location, power = b.Power, blocked = false }) +end + +CheckBuildablePlace = function(b) + local blockingActors = Map.ActorsInBox(WPos.New(b.pos.X * 1024, b.pos.Y * 1024, 0), WPos.New((b.pos.X + BuildingSizes[b.type].X) * 1024, (b.pos.Y + BuildingSizes[b.type].Y) * 1024, 0), function(a) return a.Owner == GDI end) + if #blockingActors > 0 then + b.blocked = true + return false + else + b.blocked = false + return true + end +end + +CheckBase = function() + + if Utils.All(NodCYards, function(cy) return cy.IsDead or cy.Owner ~= Nod end) then + return + end + + local queuedProcs = 0 + for i = 1, #NodRebuildList do + if queuedProcs >= AiProcsNumber and Nod.Resources < 4000 then + break + elseif NodRebuildList[i].type == "proc" then + queuedProcs = queuedProcs + 1 + end + + if NodRebuildList[i].blocked then + CheckBuildablePlace(NodRebuildList[i]) + elseif Nod.PowerProvided - Nod.PowerDrained + NodRebuildList[i].power > 0 or Utils.Any(PowerTypes, function(pt) return pt == NodRebuildList[i].type end) or NodRebuildList[i].type == "proc" then + BuildBuilding(NodRebuildList[i], NodCYards) + break + end + end + + if not CheckProgrammed then + CheckProgrammed = true + Trigger.AfterDelay(250, function() + CheckProgrammed = false + CheckBase() + end) + end + +end + +BuildBuilding = function(building, cyards) + if CyardIsBuilding or Nod.Resources < Actor.Cost(building.type) then + if Dontspam == true then + return + end + Dontspam = true + Trigger.AfterDelay(DateTime.Seconds(10), function() + Dontspam = false + CheckBase() + end) + return + end + + CyardIsBuilding = true + Nod.Resources = Nod.Resources - Actor.Cost(building.type) + + Trigger.AfterDelay(Actor.BuildTime(building.type), function() + CyardIsBuilding = false + + if Utils.All(cyards, function(cy) return cy.IsDead or cy.Owner ~= Nod end) then + Nod.Resources = Nod.Resources + Actor.Cost(building.type) + return + end + + if CheckBuildablePlace(building) == false then + CheckBase() + return + end + + local newBuilding = Actor.Create(building.type, true, { Owner = Nod, Location = building.pos }) + for i = 1, #NodRebuildList do + if NodRebuildList[i].pos == building.pos then + table.remove(NodRebuildList, i) + break + end + end + Trigger.OnKilled(newBuilding, function(b) + AddToRebuildQueue(b) + CheckBase() + end) + RepairBuilding(Nod, newBuilding, 0.75) + GuardBuilding(newBuilding) + + if newBuilding.Type == "hand" then + ProductionBuildings["infantry"] = newBuilding + Trigger.OnKilled(newBuilding, function() + ProductionQueue["infantry"] = { } + CheckTeamCompleted() + end) + + RestartUnitProduction() + elseif newBuilding.Type == "afld" then + ProductionBuildings["vehicle"] = newBuilding + Trigger.OnKilled(newBuilding, function() + ProductionQueue["vehicle"] = { } + CheckTeamCompleted() + WaitingAirDrop = false + end) + RestartUnitProduction() + NeedHarv = false + elseif newBuilding.Type == "hpad" then + ProductionBuildings["aircraft"] = newBuilding + Trigger.OnKilled(newBuilding, function() + ProductionQueue["aircraft"] = { } + CheckTeamCompleted() + end) + RestartUnitProduction() + end + + Trigger.AfterDelay(50, function() CheckBase() end) + end) + +end + +-- Units production logic +UniqueTeamsQueue = { } +ProductionCooldown = false +CheckProduction = function() + if Utils.All(ProductionBuildings, function(b) return b.IsDead end) then + return + elseif #Nod.GetActorsByType("proc") < 1 and Nod.Resources < 6000 then + RestartUnitProduction() + return + elseif not ProductionBuildings["vehicle"].IsDead and not ProductionBuildings["vehicle"].IsProducing("harv") and CheckForHarvester() then + NeedHarv = true + WaitingAirDrop = true + ProductionBuildings["vehicle"].Build({ "harv" }, function() + Trigger.AfterDelay(2, function() + WaitingAirDrop = false + end) + CheckProduction() + if Utils.RandomInteger(0,2) == 1 then + Trigger.AfterDelay(DateTime.Seconds(5), function() + if not ProductionBuildings["vehicle"].IsDead then + ProductionBuildings["vehicle"].Build({ "harv" }) + end + end) + end + end) + RestartUnitProduction() + return + end + NeedHarv = false + + if Producing and #ProductionQueue["infantry"] + #ProductionQueue["vehicle"] + #ProductionQueue["aircraft"] < 1 then + Producing = false + end + if ProductionCooldown and #UniqueTeamsQueue < 1 or Nod.Resources < 4000 then + RestartUnitProduction() + return false + else + ProduceUnits() + return true + end +end + +ReduceProdCD = function() + Trigger.AfterDelay(DateTime.Minutes(2), function() + ProductionCooldownSeconds = ProductionCooldownSeconds - 1 + if ProductionCooldownSeconds > 10 then + ReduceProdCD() + end + end) +end + +ProduceUnits = function() + if NeedHarv then + RestartUnitProduction() + return + end + if not Producing then + NewTeam = { } + CreateUnitsGroup() + if #ProductionQueue["infantry"] + #ProductionQueue["vehicle"] + #ProductionQueue["aircraft"] > 0 then + Producing = true + else + RestartUnitProduction() + return + end + end + + if #ProductionQueue["infantry"] > 0 and not ProductionBuildings["infantry"].IsDead and not ProductionBuildings["infantry"].IsProducing("e1") then + ProductionBuildings["infantry"].Build({ ProductionQueue["infantry"][1] }, function(u) + table.insert(NewTeam, u[1]) + table.remove(ProductionQueue["infantry"], 1) + CheckTeamCompleted() + end) + end + if #ProductionQueue["vehicle"] > 0 and not ProductionBuildings["vehicle"].IsDead and not ProductionBuildings["vehicle"].IsProducing("harv") and not WaitingAirDrop then + ProductionBuildings["vehicle"].Build({ ProductionQueue["vehicle"][1] }, function(u) + if u[1].Type == "harv" then + return + end + table.insert(NewTeam, u[1]) + table.remove(ProductionQueue["vehicle"], 1) + WaitingAirDrop = false + CheckTeamCompleted() + end) + WaitingAirDrop = true + end + if #ProductionQueue["aircraft"] > 0 and not ProductionBuildings["aircraft"].IsDead and not ProductionBuildings["aircraft"].IsProducing("tran") then + ProductionBuildings["aircraft"].Build({ ProductionQueue["aircraft"][1] }, function(u) + table.insert(NewTeam, u[1]) + table.remove(ProductionQueue["aircraft"], 1) + CheckTeamCompleted() + end) + end + + RestartUnitProduction() + +end + +CheckTeamCompleted = function() + if #ProductionQueue["infantry"] + #ProductionQueue["vehicle"] + #ProductionQueue["aircraft"] < 1 and #NewTeam > 0 then + NewTeam = Utils.Where(NewTeam, function(u) return not u.IsDead end) + if TeamJob == "attack" then + SendAttackGroup(NewTeam) + ProductionCooldown = true + Trigger.AfterDelay(DateTime.Seconds(ProductionCooldownSeconds), function() ProductionCooldown = false end) + elseif TeamJob == "patrol" then + TeamJob = "attack" + PatrolTeam = NewTeam + StartPatrol() + elseif TeamJob == "defend" then + TeamJob = "attack" + BaseDefenseTeam = NewTeam + StartGuard() + elseif TeamJob == "sneakAttack" then + TeamJob = "attack" + StealthTeam = NewTeam + SendStealthTanks() + end + Producing = false + NewTeam = { } + else + ProduceUnits() + end +end + +SendAttackGroup = function(team) + MoveAsGroup(team, Utils.Random(AttackPaths), 1, false) +end + +CreateUnitsGroup = function() + local team = AttackTeams + if #UniqueTeamsQueue > 0 then + team = UniqueTeamsQueue[1].team + TeamJob = UniqueTeamsQueue[1].job + table.remove(UniqueTeamsQueue, 1) + end + local pb = "infantry" + Utils.Do(Utils.Random(team), function(u) + if u == "vehicle" or u == "aircraft" then + pb = u + elseif ProductionBuildings[pb] and not ProductionBuildings[pb].IsDead and ProductionBuildings[pb].Owner == Nod then + table.insert(ProductionQueue[pb], u) + end + end) +end + +RestartUnitProduction = function() + if not Restarting then + Restarting = true + else + return + end + Trigger.AfterDelay(DateTime.Seconds(5), function() + Restarting = false + CheckProduction() + end) +end + +CheckForHarvester = function() + local harv = Nod.GetActorsByType("harv") + return #harv < MinHarvs +end diff --git a/mods/cnc/maps/twist-of-fate/twist-of-fate.lua b/mods/cnc/maps/twist-of-fate/twist-of-fate.lua new file mode 100644 index 0000000000..11f134a4a3 --- /dev/null +++ b/mods/cnc/maps/twist-of-fate/twist-of-fate.lua @@ -0,0 +1,329 @@ +--[[ + 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. +]] + +CapturableStructures = { "weap", "pyle", "hq", "nuke", "nuk2", "silo", "proc" , "fact" } + +ClearPathCameras = { Cam1, Cam2, Cam3, Cam4, Cam5 } +NodSams = { NodSam1, NodSam2, NodSam3, NodSam4, NodSam5, NodSam6, NodSam7 } +NodGuns = { Actor68, Actor69, NodGun4, Actor66 } +OldGDIBase = { OldGDIProc, OldGDIWeap, OldGDIPyle, OldGDIGtwr1, OldGDIGtwr2, OldGDIGtwr3 } + +HeliTransEngineers = { "e6", "e6", "e6", "e6", "e6" } + +CenterNodBaseEntrance = { CPos.New(35,22), CPos.New(33,23), CPos.New(34,23), CPos.New(35,23), CPos.New(36,23), CPos.New(33,24), CPos.New(34,24), CPos.New(35,24), CPos.New(33,25), CPos.New(34,25), CPos.New(32,27), CPos.New(33,27), CPos.New(32,28), CPos.New(33,28), CPos.New(31,29), CPos.New(32,29), CPos.New(33,29), CPos.New(31,30), CPos.New(32,30), CPos.New(33,30) } + +GoDemolitionMan = function(rmbo) + if rmbo.IsDead then + return + end + local structures = Utils.Where(Map.ActorsInCircle(rmbo.CenterPosition , WDist.FromCells(6)), function(u) return u.HasProperty("StartBuildingRepairs") and u.Owner == GDI end) + if #structures > 0 then + rmbo.Stop() + rmbo.Demolish(Utils.Random(structures)) + rmbo.Hunt() + Trigger.AfterDelay(DateTime.Seconds(10), function() GoDemolitionMan(rmbo) end) + end +end + +EngineersSent = false +SendEngTeam = function() + if not EngineersSent then + EngineersSent = true + local cargo = Reinforcements.ReinforceWithTransport(Nod, "tran", HeliTransEngineers, { CPos.New(0,32), waypoint11.Location }, { CPos.New(0,32) })[2] + Utils.Do(cargo, function(engs) + engs.Move(CPos.New(39,52)) + Trigger.OnIdle(engs, CaptureStructures) + end) + end +end + +CaptureStructures = function(actor) + local structures = GDI.GetActorsByTypes(CapturableStructures) + local distance = 500 + local captst = nil + Utils.Do(structures, function(st) + if not actor.IsDead and not st.IsDead and distance > (math.abs((actor.Location - st.Location).X) + math.abs((actor.Location - st.Location).Y)) then + distance = math.abs((actor.Location - st.Location).X) + math.abs((actor.Location - st.Location).Y) + captst = st + end + end) + if captst then + actor.Capture(captst) + end +end + +LaunchNuke = function(loop) + if NukeDelay == 0 then + return + end + local targets = GDI.GetActorsByTypes({ "nuk2", "atwr", "weap", "proc" }) + if #targets < 1 then + targets = GDI.GetGroundAttackers() + end + if not NodTmpl.IsDead then + Media.PlaySpeechNotification(GDI, "NuclearWarheadApproaching") + NodTmpl.ActivateNukePower(Utils.Random(targets).Location) + end + if loop then + Trigger.AfterDelay(DateTime.Seconds(NukeDelay), function() LaunchNuke(true) end) + end +end + +SendNodAirstrike = function(loop) + if AstkDelay == 0 then + return + end + local target = GetAirstrikeTarget(GDI) + if target then + NodAirSupport.TargetAirstrike(target, Angle.SouthEast) + if loop then + Trigger.AfterDelay(DateTime.Seconds(AstkDelay), function() SendNodAirstrike(true) end) + end + else + Trigger.AfterDelay(DateTime.Seconds(20), function() SendNodAirstrike(loop) end) + end +end + +SendEasyReinforcements = function(i) + local team = { } + if i < 4 then + team = ReinforceTeams[i] + else + team = ReinforceTeams[Utils.RandomInteger(1, 4)] + end + Media.PlaySpeechNotification(GDI, "Reinforce") + Reinforcements.Reinforce(GDI, team, { CPos.New(56,2), waypoint0.Location }, 25, function(a) + a.Move(waypoint1.Location, 2) + a.Move(waypoint2.Location, 2) + a.Move(CPos.New(49,44), 2) + end) + Trigger.AfterDelay(DateTime.Seconds(ReinforceDelay), function() + SendEasyReinforcements(i + 1) + end) +end + +CheckObjectives = function() + if Nod.HasNoRequiredUnits() then GDI.MarkCompletedObjective(EliminateAllNod) end + if GDI.HasNoRequiredUnits() then GDI.MarkFailedObjective(EliminateAllNod) end + Trigger.AfterDelay(50, CheckObjectives) +end + +CompleteCaptureCommCenterObjective = function() + GDI.MarkCompletedObjective(CaptureCommCenter) + if not NodCYard.IsDead and NodCYard.Owner == Nod then + Trigger.Clear(NodCYard, "OnDamaged") + Trigger.AfterDelay(1, function() + RepairBuilding(Nod, NodCYard, 0.75) + NodCYard.StartBuildingRepairs() + end) + end + Media.DisplayMessage(UserInterface.Translate("communications-center-captured-sams-located")) + local activeSams = Nod.GetActorsByType("sam") + local miniCams = { } + if #activeSams > 0 then + Utils.Do(activeSams, function(s) + table.insert(miniCams, Actor.Create("camera.mini", true, { Owner = GDI, Location = s.Location })) + table.insert(miniCams, Actor.Create("camera.mini", true, { Owner = GDI, Location = CPos.New(s.Location.X + 1, s.Location.Y) })) + end) + Trigger.AfterDelay(1, function() + Utils.Do(miniCams, function(c) + c.Destroy() + end) + end) + end +end + +WorldLoaded = function() + + Camera.Position = DefaultCameraPosition.CenterPosition + GDI = Player.GetPlayer("GDI") + Nod = Player.GetPlayer("Nod") + + if Difficulty == "hard" then + ProduceBuildingsDelay = 50 + ProduceUnitsDelay = 10 + ProductionCooldownSeconds = 30 + MinHarvs = 3 + AstkDelay = 180 + NukeDelay = 900 + ProcUpgrade = "AIHProcUpgrade" + Nod.Resources = 3000 + GDI.Cash = 3000 + MCVReinf = { "mcv" } + end + + if Difficulty == "normal" then + ProduceBuildingsDelay = 70 + ProduceUnitsDelay = 15 + ProductionCooldownSeconds = 55 + MinHarvs = 3 + AstkDelay = 220 + NukeDelay = 1200 + Nod.Resources = 3000 + GDI.Cash = 4000 + ProcUpgrade = "AINProcUpgrade" + MCVReinf = { "mtnk", "mcv" } + end + + if Difficulty == "easy" then + ProduceBuildingsDelay = 100 + ProduceUnitsDelay = 35 + ProductionCooldownSeconds = 85 + MinHarvs = 2 + AstkDelay = 250 + NukeDelay = 1500 + Nod.Resources = 2000 + GDI.Cash = 5000 + MCVReinf = { "mtnk", "mtnk", "mcv", "mtnk" } + RT1 = { "jeep", "jeep", "apc" } + RT2 = { "mtnk", "msam" } + RT3 = { "htnk" } + ReinforceTeams = { RT1, RT2, RT3 } + ReinforceDelay = 240 + Actor137.Teleport(CPos.New(57,6)) + Actor203.Destroy() + end + + InitObjectives(GDI) + ClearPath = AddPrimaryObjective(GDI, "clear-path") + Trigger.AfterDelay(DateTime.Seconds(5), function() + EliminateAllNod = AddPrimaryObjective(GDI, "eliminate-nod") + end) + + InitAI() + CheckObjectives() + + Flare = Actor.Create("flare", true, { Owner = GDI, Location = DefaultFlareLocation.Location }) + + Trigger.AfterDelay(1,function() + AmbushTeam = Map.ActorsInBox(Map.CenterOfCell(CPos.New(46,5)), Map.CenterOfCell(CPos.New(60,53)), function(a) return a.Owner == Nod and a.Type ~= "stnk" end) + Trigger.OnAllKilled(AmbushTeam, function() + GDI.MarkCompletedObjective(ClearPath) + Trigger.AfterDelay(DateTime.Seconds(2), function() + Trigger.AfterDelay(DateTime.Seconds(30), function() + Flare.Destroy() + end) + Media.PlaySpeechNotification(GDI, "Reinforce") + Reinforcements.Reinforce(GDI, MCVReinf, { CPos.New(56,2), waypoint0.Location }, 25, function(a) + a.Move(waypoint1.Location, 2) + a.Move(waypoint2.Location, 2) + a.Move(CPos.New(49,44), 2) + if a.HasProperty("Deploy") then + a.Move(CPos.New(48,51)) + a.Deploy() + end + end) + Utils.Do(ClearPathCameras, function(c) + c.Destroy() + end) + if Difficulty == "easy" then + Trigger.AfterDelay(DateTime.Seconds(ReinforceDelay), function() + SendEasyReinforcements(1) + end) + end + end) + Trigger.AfterDelay(DateTime.Seconds(8), function() + RecoverOldBase = AddSecondaryObjective(GDI, "recover-old-base") + end) + end) + end) + + NodAirSupport = Actor.Create("Astk.proxy", true, { Owner = Nod }) + Trigger.AfterDelay(DateTime.Seconds(AstkDelay), function() + if AstkDelay > 0 then + SendNodAirstrike(true) + Trigger.AfterDelay(DateTime.Seconds(15), function() + Media.DisplayMessage(UserInterface.Translate("air-strikes-intel-report")) + Trigger.AfterDelay(DateTime.Seconds(8), function() + CaptureCommCenter = AddSecondaryObjective(GDI, "capture-nod-communications-center") + if NodAstkHq.IsDead then + GDI.MarkFailedObjective(CaptureCommCenter) + return + end + if NodAstkHq.Owner == GDI then + Trigger.AfterDelay(DateTime.Seconds(4), CompleteCaptureCommCenterObjective) + end + end) + end) + end + end) + + Trigger.OnKilled(NodOutPostCYard, function() + SendNodAirstrike(false) + if not RecoverOldBase then + RecoverOldBase = AddSecondaryObjective(GDI, "recover-old-base") + end + GDI.MarkFailedObjective(RecoverOldBase) + end) + + Trigger.OnCapture(NodOutPostCYard, function() + Trigger.ClearAll(NodOutPostCYard) + Utils.Do(OldGDIBase, function(building) + if not building.IsDead then + building.Owner = GDI + end + end) + GDI.MarkCompletedObjective(RecoverOldBase) + table.insert(SneakPaths, SP2) + table.insert(SneakPaths, SP1) + table.insert(SneakPaths, SP2) + end) + + Utils.Do(NodGuns, function(g) + Trigger.OnKilled(g, function() + SendEngTeam() + Utils.Do(NodGuns, function(gun) if not gun.IsDead then Trigger.Clear(gun, "OnKilled") end end) + end) + end) + + RamboSent = false + Trigger.OnEnteredFootprint(CenterNodBaseEntrance, function(a, id) + if a.Owner == GDI and not RamboSent then + RamboSent = true + local cargo = Reinforcements.ReinforceWithTransport(Nod, "tran", { "rmbo" }, { CPos.New(0,32), waypoint12.Location }, { CPos.New(0,32) })[2] + Trigger.OnIdle(cargo[1], function() + Trigger.Clear(cargo[1], "OnIdle") + Media.PlaySpeechNotification(GDI, "BaseAttack") + Trigger.AfterDelay(5, function() GoDemolitionMan(cargo[1]) end) + end) + Trigger.RemoveFootprintTrigger(id) + end + end) + + Trigger.OnDamaged(NodCYard, function(_, atk, _) + if atk.Owner == GDI and not NukeLaunched then + NukeLaunched = true + LaunchNuke(false) + Trigger.Clear(NodCYard, "OnDamaged") + Trigger.AfterDelay(1, function() + RepairBuilding(Nod, NodCYard, 0.75) + NodCYard.StartBuildingRepairs() + end) + end + end) + + Trigger.OnKilledOrCaptured(NodTmpl, function() + NukeDelay = 0 + end) + + Trigger.OnCapture(NodAstkHq, function() + AstkDelay = 0 + Trigger.ClearAll(NodAstkHq) + if CaptureCommCenter then + CompleteCaptureCommCenterObjective() + end + end) + + Trigger.OnKilled(NodAstkHq, function() + if CaptureCommCenter then + GDI.MarkFailedObjective(CaptureCommCenter) + end + AstkDelay = 0 + end) +end diff --git a/mods/cnc/maps/twist-of-fate/weapons.yaml b/mods/cnc/maps/twist-of-fate/weapons.yaml new file mode 100644 index 0000000000..83050cfb29 --- /dev/null +++ b/mods/cnc/maps/twist-of-fate/weapons.yaml @@ -0,0 +1,3 @@ +IonCannon: + Warhead@1Dam_impact: SpreadDamage + Falloff: 1000, 60, 40, 20 diff --git a/mods/cnc/missions.yaml b/mods/cnc/missions.yaml index 260e373b63..b8c53710e2 100644 --- a/mods/cnc/missions.yaml +++ b/mods/cnc/missions.yaml @@ -36,6 +36,7 @@ Nod Campaign: nod10b Covert Operations: + twist-of-fate eviction-notice Funpark Campaign: