From 1cdc823bca904812556ec5252de5ffd5fc01e8c8 Mon Sep 17 00:00:00 2001 From: JovialFeline Date: Tue, 20 May 2025 23:44:48 -0400 Subject: [PATCH] Add The Tiberium Strain mission (CnC) --- mods/cnc/fluent/lua.ftl | 11 +- mods/cnc/maps/the-tiberium-strain/map.bin | Bin 0 -> 20497 bytes mods/cnc/maps/the-tiberium-strain/map.png | Bin 0 -> 5943 bytes mods/cnc/maps/the-tiberium-strain/map.yaml | 1330 +++++++++++++++++ mods/cnc/maps/the-tiberium-strain/rules.yaml | 90 ++ .../the-tiberium-strain.lua | 735 +++++++++ mods/cnc/missions.yaml | 1 + 7 files changed, 2166 insertions(+), 1 deletion(-) create mode 100644 mods/cnc/maps/the-tiberium-strain/map.bin create mode 100644 mods/cnc/maps/the-tiberium-strain/map.png create mode 100644 mods/cnc/maps/the-tiberium-strain/map.yaml create mode 100644 mods/cnc/maps/the-tiberium-strain/rules.yaml create mode 100644 mods/cnc/maps/the-tiberium-strain/the-tiberium-strain.lua diff --git a/mods/cnc/fluent/lua.ftl b/mods/cnc/fluent/lua.ftl index dbfe27f288..e0530e8605 100644 --- a/mods/cnc/fluent/lua.ftl +++ b/mods/cnc/fluent/lua.ftl @@ -141,14 +141,23 @@ destroy-capture-warfactory = Destroy or capture the Weapons Factory. destroy-mammoth-tanks = Destroy the Mammoth tanks in the Research Base. keep-commando-alive = Keep your Commando alive. +## eviction-notice, the-tiberium-strain +nod-soldier = Nod Soldier + ## eviction-notice take-civilians-money-crates = Find all the civilians' money; they won't be needing it anymore. quickly-destroy-ion-cannon = Disable the GDI Ion Cannon before it fires twice. -nod-soldier = Nod Soldier civilians-running = Hey, those civilians... where are they going? destroy-ion-cannon-advice = The GDI are preparing their Ion Cannon. Don't let them get comfortable using it. village-destruction-warning = Be careful, commander. The GDI won't stay idle while we burn the entire village. +## the-tiberium-strain +destroy-gdi-bio-centers = Destroy the GDI bio centers. +keep-all-other-structures = Keep all other structures intact. +kill-every-villager-in-area = Kill every villager in the area. +careless-gdi-experiments-killed = Careless GDI experiments killed these villagers. +villagers-must-not-live = They must not live to dispute that story. + ## twist-of-fate clear-path = Repel the ambush and clear the way for our MCV. recover-old-base = Capture the Construction Yard at our recon post to regain control. diff --git a/mods/cnc/maps/the-tiberium-strain/map.bin b/mods/cnc/maps/the-tiberium-strain/map.bin new file mode 100644 index 0000000000000000000000000000000000000000..e2df7047007fdf45cdb19f136b1d6fad685ef23b GIT binary patch literal 20497 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 literal 0 HcmV?d00001 diff --git a/mods/cnc/maps/the-tiberium-strain/map.png b/mods/cnc/maps/the-tiberium-strain/map.png new file mode 100644 index 0000000000000000000000000000000000000000..fda7ce986dc145cbb39b0d9a6da6761a9b927a6d GIT binary patch literal 5943 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 literal 0 HcmV?d00001 diff --git a/mods/cnc/maps/the-tiberium-strain/map.yaml b/mods/cnc/maps/the-tiberium-strain/map.yaml new file mode 100644 index 0000000000..1f3e553020 --- /dev/null +++ b/mods/cnc/maps/the-tiberium-strain/map.yaml @@ -0,0 +1,1330 @@ +MapFormat: 12 + +RequiresMod: cnc + +Title: Nod - The Tiberium Strain + +Author: Westwood Studios + +Tileset: TEMPERAT + +MapSize: 64,64 + +Bounds: 2,2,60,60 + +Visibility: MissionSelector + +Categories: Campaign + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: gdi + PlayerReference@Nod: + Name: Nod + AllowBots: False + Playable: True + Required: True + LockFaction: True + Faction: nod + LockColor: True + Color: FE1100 + LockSpawn: True + LockTeam: True + Enemies: GDI, Villagers + PlayerReference@GDI: + Name: GDI + Bot: campaign + Faction: gdi + Color: F5D378 + Enemies: Nod + PlayerReference@Villagers: + Name: Villagers + Bot: campaign + Faction: gdi + Enemies: Nod + +Actors: + Actor0: cycl + Location: 52,60 + Owner: GDI + Actor1: cycl + Location: 51,60 + Owner: GDI + Actor2: cycl + Location: 50,60 + Owner: GDI + Actor3: wood + Location: 9,60 + Owner: Neutral + Actor4: wood + Location: 8,60 + Owner: Neutral + Actor5: wood + Location: 7,60 + Owner: Neutral + Actor6: wood + Location: 6,60 + Owner: Neutral + Actor7: wood + Location: 6,59 + Owner: Neutral + Actor8: wood + Location: 5,59 + Owner: Neutral + Actor9: cycl + Location: 51,58 + Owner: GDI + Actor10: cycl + Location: 50,58 + Owner: GDI + Actor11: wood + Location: 9,58 + Owner: Neutral + Actor12: v17 + Location: 6,58 + Owner: Neutral + Actor13: wood + Location: 5,58 + Owner: Neutral + Actor14: cycl + Location: 50,57 + Owner: GDI + Actor15: wood + Location: 9,57 + Owner: Neutral + Actor16: wood + Location: 8,57 + Owner: Neutral + Actor17: wood + Location: 7,57 + Owner: Neutral + Actor18: wood + Location: 6,57 + Owner: Neutral + Actor19: wood + Location: 5,57 + Owner: Neutral + Actor20: wood + Location: 8,55 + Owner: Neutral + Actor21: wood + Location: 7,55 + Owner: Neutral + Actor22: wood + Location: 6,55 + Owner: Neutral + Actor23: wood + Location: 5,55 + Owner: Neutral + Actor24: wood + Location: 4,55 + Owner: Neutral + Actor25: v16 + Location: 4,54 + Owner: Neutral + Actor26: v14 + Location: 5,53 + Owner: Neutral + Actor27: wood + Location: 4,49 + Owner: Neutral + Actor28: wood + Location: 3,49 + Owner: Neutral + Actor29: wood + Location: 2,49 + Owner: Neutral + Actor30: wood + Location: 7,47 + Owner: Neutral + Actor31: wood + Location: 6,47 + Owner: Neutral + Actor32: wood + Location: 5,47 + Owner: Neutral + Actor33: wood + Location: 4,47 + Owner: Neutral + Actor34: wood + Location: 3,47 + Owner: Neutral + Actor35: v14 + Location: 6,46 + Owner: Neutral + Actor36: v15 + Location: 5,46 + Owner: Neutral + Actor37: v15 + Location: 4,46 + Owner: Neutral + Actor38: wood + Location: 3,46 + Owner: Neutral + Actor39: v15 + Location: 6,45 + Owner: Neutral + Actor40: v15 + Location: 5,45 + Owner: Neutral + Actor41: v14 + Location: 4,45 + Owner: Neutral + Actor42: wood + Location: 3,45 + Owner: Neutral + Actor43: wood + Location: 7,44 + Owner: Neutral + Actor44: wood + Location: 6,44 + Owner: Neutral + Actor45: wood + Location: 5,44 + Owner: Neutral + Actor46: wood + Location: 4,44 + Owner: Neutral + Actor47: wood + Location: 3,44 + Owner: Neutral + Actor49: wood + Location: 40,29 + Owner: Neutral + Actor50: wood + Location: 39,29 + Owner: Neutral + Actor51: wood + Location: 38,29 + Owner: Neutral + Actor52: wood + Location: 42,28 + Owner: Neutral + Actor53: wood + Location: 42,27 + Owner: Neutral + Actor54: wood + Location: 42,26 + Owner: Neutral + Actor55: brik + Location: 56,20 + Owner: GDI + Actor56: brik + Location: 55,20 + Owner: GDI + Actor57: brik + Location: 54,20 + Owner: GDI + Actor58: brik + Location: 53,20 + Owner: GDI + Actor59: brik + Location: 49,20 + Owner: GDI + Actor60: brik + Location: 48,20 + Owner: GDI + Actor61: brik + Location: 47,20 + Owner: GDI + Actor62: brik + Location: 46,20 + Owner: GDI + Actor63: brik + Location: 54,19 + Owner: GDI + Actor64: brik + Location: 53,19 + Owner: GDI + Actor65: brik + Location: 49,19 + Owner: GDI + Actor66: brik + Location: 48,19 + Owner: GDI + Actor67: brik + Location: 54,18 + Owner: GDI + Actor68: brik + Location: 53,18 + Owner: GDI + Actor69: brik + Location: 49,18 + Owner: GDI + Actor70: brik + Location: 48,18 + Owner: GDI + Actor71: cycl + Location: 60,16 + Owner: GDI + Actor72: cycl + Location: 59,16 + Owner: GDI + Actor73: cycl + Location: 58,16 + Owner: GDI + Actor74: cycl + Location: 57,16 + Owner: GDI + Actor75: cycl + Location: 56,16 + Owner: GDI + Actor76: cycl + Location: 55,16 + Owner: GDI + Actor77: cycl + Location: 55,15 + Owner: GDI + Actor78: cycl + Location: 55,14 + Owner: GDI + Actor79: cycl + Location: 55,13 + Owner: GDI + Actor80: cycl + Location: 55,11 + Owner: GDI + Actor81: brik + Location: 34,11 + Owner: GDI + Actor82: brik + Location: 33,11 + Owner: GDI + Actor83: cycl + Location: 55,10 + Owner: GDI + Actor84: brik + Location: 34,10 + Owner: GDI + Actor85: cycl + Location: 60,9 + Owner: GDI + Actor86: cycl + Location: 59,9 + Owner: GDI + Actor87: cycl + Location: 58,9 + Owner: GDI + Actor88: cycl + Location: 57,9 + Owner: GDI + Actor89: cycl + Location: 56,9 + Owner: GDI + Actor90: cycl + Location: 55,9 + Owner: GDI + Actor91: t07 + Location: 48,45 + Owner: Neutral + Actor92: t06 + Location: 50,43 + Owner: Neutral + Actor93: t13.transformable + Location: 26,5 + Owner: Neutral + Actor94: t03.transformable + Location: 12,4 + Owner: Neutral + Actor95: t13.transformable + Location: 58,48 + Owner: Neutral + Actor96: tc01 + Location: 31,6 + Owner: Neutral + Actor97: tc04 + Location: 13,6 + Owner: Neutral + Actor98: tc03 + Location: 10,2 + Owner: Neutral + Actor99: t08 + Location: 12,2 + Owner: Neutral + Actor100: t07 + Location: 25,2 + Owner: Neutral + Actor101: tc03 + Location: 26,2 + Owner: Neutral + Actor102: t06 + Location: 13,60 + Owner: Neutral + Actor103: t07 + Location: 3,59 + Owner: Neutral + Actor104: t08 + Location: 1,54 + Owner: Neutral + Actor105: t01 + Location: 1,41 + Owner: Neutral + Actor106: t02 + Location: 59,58 + Owner: Neutral + Actor107: tc04 + Location: 45,55 + Owner: Neutral + Actor108: tc05 + Location: 42,52 + Owner: Neutral + Actor109: tc05 + Location: 32,49 + Owner: Neutral + Actor110: tc02 + Location: 30,58 + Owner: Neutral + Actor111: tc03 + Location: 16,55 + Owner: Neutral + Actor112: tc01 + Location: 30,47 + Owner: Neutral + Actor113: tc01 + Location: 26,48 + Owner: Neutral + Actor114: t07 + Location: 28,47 + Owner: Neutral + Actor115: t08 + Location: 29,48 + Owner: Neutral + Actor116: t08 + Location: 23,58 + Owner: Neutral + Actor117: tc02 + Location: 29,23 + Owner: Neutral + Actor118: tc02 + Location: 30,24 + Owner: Neutral + Actor119: t07 + Location: 32,24 + Owner: Neutral + Actor120: t07 + Location: 32,21 + Owner: Neutral + Actor121: t01 + Location: 32,17 + Owner: Neutral + Actor122: tc02 + Location: 3,17 + Owner: Neutral + Actor123: t05 + Location: 2,11 + Owner: Neutral + Actor124: t01 + Location: 18,57 + Owner: Neutral + Actor125: t01 + Location: 1,47 + Owner: Neutral + Actor126: t01 + Location: 15,24 + Owner: Neutral + Actor127: t10 + Location: 11,26 + Owner: Neutral + Actor128: tc04 + Location: 20,43 + Owner: Neutral + Actor129: tc05 + Location: 9,43 + Owner: Neutral + Actor130: tc03 + Location: 3,58 + Owner: Neutral + Actor131: tc02 + Location: 3,39 + Owner: Neutral + Actor132: tc04 + Location: 8,31 + Owner: Neutral + Actor133: tc05 + Location: 3,29 + Owner: Neutral + Actor134: t16 + Location: 3,30 + Owner: Neutral + Actor135: tc01 + Location: 10,18 + Owner: Neutral + Actor136: t07 + Location: 22,18 + Owner: Neutral + Actor137: t13 + Location: 25,18 + Owner: Neutral + Actor138: tc01 + Location: 21,37 + Owner: Neutral + Actor139: t02 + Location: 18,24 + Owner: Neutral + Actor140: t06 + Location: 19,25 + Owner: Neutral + Actor141: tc01 + Location: 19,27 + Owner: Neutral + Actor142: t16 + Location: 19,26 + Owner: Neutral + Actor143: tc04 + Location: 20,34 + Owner: Neutral + Actor144: tc03 + Location: 18,35 + Owner: Neutral + Actor145: tc05 + Location: 17,30 + Owner: Neutral + Actor146: t16 + Location: 18,29 + Owner: Neutral + Actor147: t16 + Location: 28,26 + Owner: Neutral + Actor148: tc04 + Location: 28,27 + Owner: Neutral + Actor149: tc02 + Location: 27,37 + Owner: Neutral + Actor150: t12 + Location: 33,28 + Owner: Neutral + Actor152: tc03 + Location: 55,31 + Owner: Neutral + Actor153: t07 + Location: 57,30 + Owner: Neutral + Actor154: t06 + Location: 57,31 + Owner: Neutral + Actor155: tc04 + Location: 46,39 + Owner: Neutral + Actor156: tc03 + Location: 49,39 + Owner: Neutral + Actor157: t07 + Location: 51,38 + Owner: Neutral + Actor158: t06 + Location: 51,39 + Owner: Neutral + Actor159: t05 + Location: 45,39 + Owner: Neutral + Actor160: t02 + Location: 45,40 + Owner: Neutral + Actor161: t01 + Location: 28,41 + Owner: Neutral + Actor162: t01 + Location: 54,20 + Owner: Neutral + Actor163: tc02 + Location: 60,43 + Owner: Neutral + Actor164: t06 + Location: 61,39 + Owner: Neutral + Actor165: t01 + Location: 61,41 + Owner: Neutral + Actor166: t08 + Location: 61,26 + Owner: Neutral + Actor167: t03.transformable + Location: 48,25 + Owner: Neutral + Actor168: t03.transformable + Location: 55,24 + Owner: Neutral + Actor169: t03.transformable + Location: 57,36 + Owner: Neutral + Actor170: t03.transformable + Location: 46,31 + Owner: Neutral + Actor171: t03.transformable + Location: 43,34 + Owner: Neutral + Actor172: tc05 + Location: 38,48 + Owner: Neutral + Actor173: tc03 + Location: 38,47 + Owner: Neutral + Actor174: tc01 + Location: 36,21 + Owner: Neutral + Actor175: t14 + Location: 38,21 + Owner: Neutral + Actor176: t15 + Location: 40,21 + Owner: Neutral + Actor180: atwr + Location: 35,11 + Owner: GDI + Actor181: v04 + Location: 33,35 + Owner: Villagers + Actor182: v05 + Location: 36,28 + Owner: Villagers + Actor183: v03 + Location: 37,25 + Owner: Villagers + Actor184: nuk2 + Location: 56,13 + Owner: GDI + Actor185: nuk2 + Location: 56,10 + Owner: GDI + Actor186: nuk2 + Location: 58,10 + Owner: GDI + Actor187: silo + Location: 40,3 + Owner: GDI + Actor188: silo + Location: 51,11 + Owner: GDI + Actor189: silo + Location: 54,4 + Owner: GDI + Actor190: proc + Location: 43,15 + Owner: GDI + FreeActor: False + Actor191: fact + Location: 52,7 + Owner: GDI + Actor192: weap + Location: 47,8 + Owner: GDI + Actor193: atwr + Location: 31,16 + Owner: GDI + Actor194: gtwr + Location: 30,14 + Owner: GDI + Actor195: gtwr + Location: 28,11 + Owner: GDI + Actor196: nuk2 + Location: 33,3 + Owner: GDI + Actor197: nuk2 + Location: 46,3 + Owner: GDI + Actor198: nuk2 + Location: 58,13 + Owner: GDI + Actor199: eye + Location: 58,17 + Owner: GDI + Actor200: proc + Location: 42,5 + Owner: GDI + FreeActor: False + Actor201: v02 + Location: 6,53 + Owner: Villagers + Actor202: v03 + Location: 12,58 + Owner: Villagers + Actor203: v04 + Location: 17,58 + Owner: Villagers + Actor204: v05 + Location: 7,58 + Owner: Villagers + Actor205: v06 + Location: 12,50 + Owner: Villagers + Actor206: v07 + Location: 5,49 + Owner: Villagers + Actor207: v01 + Location: 20,40 + Owner: Villagers + VillageHouse2: v02 + Location: 9,37 + Owner: Villagers + Actor209: v03 + Location: 5,41 + Owner: Villagers + Actor210: v04 + Location: 10,46 + Owner: Villagers + Actor211: v09 + Location: 16,41 + Owner: Villagers + Actor212: v10 + Location: 12,41 + Owner: Villagers + Actor213: v11 + Location: 18,44 + Owner: Villagers + Actor214: v08 + Location: 6,33 + Owner: Villagers + Actor215: pyle + Location: 48,14 + Owner: GDI + Actor216: pyle + Location: 38,8 + Owner: GDI + Actor217: hq + Location: 54,58 + Owner: GDI + Actor218: jeep + Location: 41,17 + Owner: GDI + Facing: 640 + Actor219: jeep + Location: 53,16 + Owner: GDI + Facing: 768 + ScriptTags: South Gate Guard, Area Guard + Actor220: jeep + Location: 24,29 + Owner: GDI + Facing: 384 + Actor222: jeep + Location: 8,38 + Owner: GDI + Facing: 128 + ScriptTags: Second House Guard, Area Guard + Actor223: jeep + Location: 15,20 + Owner: GDI + Facing: 384 + HilltopHumvee: jeep + Location: 13,26 + Owner: GDI + Facing: 128 + ScriptTags: Area Guard + CommunicationsHumvee: jeep + Location: 49,58 + Owner: GDI + Facing: 256 + ScriptTags: Area Guard + RefineryGrenadier: e2 + Location: 42,12 + Owner: GDI + Facing: 128 + ScriptTags: Inner Base Guard + BioGrenadier: e2 + Location: 51,6 + Owner: GDI + Facing: 128 + ScriptTags: Inner Base Guard, Area Guard + Actor233: e1 + Location: 41,10 + Owner: GDI + Facing: 256 + ScriptTags: Inner Base Guard + Actor234: e1 + Location: 44,12 + Owner: GDI + Facing: 640 + SubCell: 4 + ScriptTags: Inner Base Guard + Actor235: e1 + Location: 42,15 + Owner: GDI + SubCell: 1 + ScriptTags: Inner Base Guard, Area Guard + Actor236: e1 + Location: 41,13 + Owner: GDI + SubCell: 1 + ScriptTags: Inner Base Guard + Actor237: e1 + Location: 41,9 + Owner: GDI + Facing: 512 + SubCell: 5 + ScriptTags: Inner Base Guard, Area Guard + Actor238: e1 + Location: 40,9 + Owner: GDI + Facing: 768 + SubCell: 1 + ScriptTags: Inner Base Guard, Area Guard + Actor239: e1 + Location: 41,9 + Owner: GDI + Facing: 384 + SubCell: 1 + ScriptTags: Inner Base Guard, Area Guard + Actor240: e1 + Location: 40,10 + Owner: GDI + Facing: 640 + SubCell: 1 + ScriptTags: Inner Base Guard, Area Guard + Actor241: e1 + Location: 49,59 + Owner: GDI + Facing: 256 + SubCell: 4 + ScriptTags: Communications Guard, Area Guard + Actor242: e1 + Location: 49,60 + Owner: GDI + Facing: 128 + SubCell: 5 + ScriptTags: Communications Guard, Area Guard + Actor243: c6 + Location: 30,35 + SubCell: 5 + Facing: 384 + Owner: Villagers + Actor244: c5 + Location: 33,37 + SubCell: 2 + Facing: 896 + Owner: Villagers + Actor245: e1 + Location: 24,26 + Owner: GDI + Facing: 384 + Actor246: e1 + Location: 24,27 + Owner: GDI + Facing: 128 + SubCell: 1 + Actor247: e1 + Location: 23,26 + Owner: GDI + Facing: 768 + SubCell: 5 + Actor248: c8 + Location: 36,25 + SubCell: 4 + Facing: 896 + Owner: Villagers + Actor249: c7 + Location: 40,26 + SubCell: 2 + Facing: 384 + Owner: Villagers + Actor250: c1 + Location: 37,29 + SubCell: 2 + Facing: 384 + Owner: Villagers + Actor251: e1 + Location: 50,21 + Owner: GDI + Facing: 640 + SubCell: 1 + ScriptTags: South Gate Guard, Area Guard + Actor252: e1 + Location: 52,21 + Owner: GDI + Facing: 384 + SubCell: 4 + ScriptTags: South Gate Guard, Area Guard + Actor253: e1 + Location: 52,17 + Owner: GDI + Facing: 384 + SubCell: 5 + ScriptTags: South Gate Guard, Area Guard + Actor254: e1 + Location: 50,20 + Owner: GDI + Facing: 640 + SubCell: 1 + ScriptTags: South Gate Guard, Area Guard + Actor255: e1 + Location: 51,17 + Owner: GDI + Facing: 512 + SubCell: 5 + ScriptTags: South Gate Guard, Area Guard + Actor256: e1 + Location: 47,18 + Owner: GDI + Facing: 512 + SubCell: 1 + ScriptTags: South Gate Guard, Area Guard + Actor265: e1 + Location: 5,11 + Owner: GDI + SubCell: 1 + Actor266: e1 + Location: 6,11 + Owner: GDI + Facing: 512 + SubCell: 4 + Actor267: e1 + Location: 4,12 + Owner: GDI + Facing: 128 + SubCell: 1 + Actor268: e1 + Location: 5,13 + Owner: GDI + Facing: 768 + SubCell: 1 + Actor269: e1 + Location: 5,12 + Owner: GDI + Facing: 640 + SubCell: 1 + Actor270: c1 + Location: 21,40 + SubCell: 4 + Owner: Villagers + Facing: 768 + ScriptTags: West Villager + Actor271: c2 + Location: 17,42 + SubCell: 5 + Owner: Villagers + Facing: 128 + ScriptTags: West Villager + Actor272: c3 + Location: 22,42 + SubCell: 5 + Owner: Villagers + Facing: 128 + ScriptTags: West Villager + Actor273: c4 + Location: 15,41 + SubCell: 4 + Owner: Villagers + Facing: 896 + ScriptTags: West Villager + Actor274: c5 + Location: 20,39 + SubCell: 4 + Owner: Villagers + ScriptTags: West Villager + Actor275: c6 + Location: 19,44 + SubCell: 1 + Owner: Villagers + Facing: 896 + ScriptTags: West Villager + Actor276: c6 + Location: 17,43 + SubCell: 2 + Owner: Villagers + Facing: 384 + ScriptTags: West Villager + Actor277: c7 + Location: 20,40 + SubCell: 1 + Owner: Villagers + Facing: 256 + ScriptTags: West Villager + Actor278: c8 + Location: 18,41 + Owner: Villagers + ScriptTags: West Villager + Actor279: c9 + Location: 18,41 + SubCell: 5 + Owner: Villagers + Facing: 128 + ScriptTags: West Villager + Actor280: c7 + Location: 4,42 + SubCell: 5 + Owner: Villagers + Facing: 128 + ScriptTags: West Villager + Actor281: c6 + Location: 4,51 + SubCell: 1 + Owner: Villagers + Facing: 640 + ScriptTags: West Villager + Actor282: c6 + Location: 8,53 + SubCell: 5 + Owner: Villagers + Facing: 256 + ScriptTags: West Villager + Actor283: c5 + Location: 9,59 + SubCell: 4 + Owner: Villagers + ScriptTags: West Villager + Actor284: c4 + Location: 16,59 + SubCell: 1 + Facing: 128 + Owner: Villagers + ScriptTags: West Villager + Actor285: c3 + Location: 13,52 + SubCell: 4 + Owner: Villagers + Facing: 896 + ScriptTags: West Villager + Actor286: c2 + Location: 12,51 + SubCell: 5 + Owner: Villagers + Facing: 128 + ScriptTags: West Villager + Actor287: c1 + Location: 12,43 + SubCell: 1 + Owner: Villagers + Facing: 768 + ScriptTags: West Villager + Actor288: c2 + Location: 8,35 + SubCell: 5 + Owner: Villagers + ScriptTags: West Villager + Actor289: c4 + Location: 14,44 + SubCell: 2 + Owner: Villagers + Facing: 256 + ScriptTags: West Villager + Actor290: c5 + Location: 9,55 + SubCell: 1 + Owner: Villagers + Facing: 512 + ScriptTags: West Villager + Actor291: c6 + Location: 7,48 + SubCell: 2 + Owner: Villagers + Facing: 384 + ScriptTags: West Villager + Actor292: c8 + Location: 13,39 + SubCell: 2 + Owner: Villagers + Facing: 384 + ScriptTags: West Villager + Actor293: e1 + Location: 9,43 + Owner: GDI + Facing: 896 + SubCell: 5 + ScriptTags: Area Guard + Actor294: e1 + Location: 12,45 + Owner: GDI + Facing: 512 + SubCell: 2 + SecondHouseRifle: e1 + Location: 12,36 + Owner: GDI + SubCell: 1 + Facing: 384 + ScriptTags: Second House Guard, Area Guard + Actor299: e2 + Location: 8,31 + Owner: GDI + Facing: 128 + ScriptTags: Area Guard + Actor300: e2 + Location: 10,31 + Owner: GDI + Facing: 896 + SubCell: 4 + ScriptTags: Area Guard + VillageEntranceRifle1: e1 + Location: 5,27 + Owner: GDI + Facing: 896 + SubCell: 2 + ScriptTags: Village Entrance Guard, Area Guard + VillageEntranceRifle2: e1 + Location: 6,28 + Owner: GDI + Facing: 768 + SubCell: 1 + ScriptTags: Village Entrance Guard, Area Guard + VillageEntranceGrenadier: e2 + Location: 5,27 + Owner: GDI + Facing: 384 + SubCell: 4 + ScriptTags: Village Entrance Guard, Area Guard + Actor304: e1 + Location: 52,49 + Owner: GDI + Facing: 384 + SubCell: 4 + ScriptTags: Tiberium Field Guard, Area Guard + Actor305: e1 + Location: 52,50 + Owner: GDI + Facing: 256 + SubCell: 2 + ScriptTags: Tiberium Field Guard, Area Guard + Actor306: e1 + Location: 53,51 + Owner: GDI + Facing: 384 + SubCell: 1 + ScriptTags: Tiberium Field Guard, Area Guard + Actor307: e1 + Location: 54,50 + Owner: GDI + Facing: 384 + SubCell: 5 + ScriptTags: Tiberium Field Guard, Area Guard + Actor308: e1 + Location: 53,50 + Owner: GDI + Facing: 512 + SubCell: 2 + ScriptTags: Tiberium Field Guard, Area Guard + Actor309: e1 + Location: 48,17 + Owner: GDI + Facing: 768 + SubCell: 5 + ScriptTags: South Gate Guard, Area Guard + Actor310: e1 + Location: 52,18 + Owner: GDI + Facing: 384 + SubCell: 5 + ScriptTags: South Gate Guard, Area Guard + Actor311: e1 + Location: 50,19 + Owner: GDI + Facing: 512 + SubCell: 2 + ScriptTags: South Gate Guard, Area Guard + Actor312: e1 + Location: 50,18 + Owner: GDI + Facing: 640 + SubCell: 2 + ScriptTags: South Gate Guard, Area Guard + Actor313: e1 + Location: 51,19 + Owner: GDI + Facing: 256 + SubCell: 2 + ScriptTags: South Gate Guard, Area Guard + Actor314: e1 + Location: 52,20 + Owner: GDI + Facing: 512 + ScriptTags: South Gate Guard, Area Guard + Actor315: e2 + Location: 21,13 + Owner: GDI + Facing: 256 + SubCell: 4 + ScriptTags: West Gate Grenadier, Area Guard + Actor316: e2 + Location: 21,15 + Owner: GDI + Facing: 384 + SubCell: 2 + ScriptTags: West Gate Grenadier, Area Guard + Actor317: e2 + Location: 20,14 + Owner: GDI + Facing: 512 + ScriptTags: West Gate Grenadier, Area Guard + CommunicationsGrenadier: e2 + Location: 50,59 + Owner: GDI + Facing: 256 + SubCell: 1 + ScriptTags: Communications Guard, Area Guard + DefaultChinookTarget: waypoint + Location: 32,32 + Owner: Neutral + Waypoint3: waypoint + Location: 36,13 + Owner: Neutral + Waypoint2: waypoint + Location: 27,55 + Owner: Neutral + Waypoint1: waypoint + Location: 5,4 + Owner: Neutral + Waypoint0: waypoint + Location: 7,30 + Owner: Neutral + Actor151: tc02 + Location: 53,31 + Owner: Neutral + IntersectionTank: mtnk + Location: 10,57 + Owner: GDI + ScriptTags: Area Guard + Bio3: bio + Location: 57,5 + Owner: GDI + Bio2: bio + Location: 50,3 + Owner: GDI + Actor48: v13 + Location: 11,41 + Owner: Neutral + IntersectionRifle2: e1 + Location: 8,56 + Owner: GDI + SubCell: 2 + IntersectionRifle1: e1 + Location: 13,53 + Owner: GDI + SubCell: 2 + Facing: 768 + IntersectionRifle3: e1 + Location: 11,56 + Owner: GDI + SubCell: 1 + Facing: 128 + IntersectionHumvee: jeep + Location: 4,50 + Owner: GDI + Facing: 896 + ScriptTags: Area Guard + BridgeTank: mtnk + Location: 33,42 + Owner: GDI + EastEntry1: waypoint + Location: 61,28 + Owner: GDI + EastEntry2: waypoint + Location: 61,35 + Owner: GDI + EastEntry3: waypoint + Location: 61,49 + Owner: GDI + EastEntry4: waypoint + Location: 61,53 + Owner: GDI + Bio1: bio + Owner: GDI + Location: 37,4 + NorthWestEntry2: waypoint + Location: 5,2 + Faction: gdi + Owner: Nod + NorthWestEntry3: waypoint + Location: 6,2 + Faction: gdi + Owner: Nod + NorthWestEntry1: waypoint + Location: 4,2 + Faction: gdi + Owner: Nod + ChemRally1: waypoint + Location: 4,4 + Faction: gdi + Owner: Nod + ChemRally2: waypoint + Location: 6,4 + Faction: gdi + Owner: Nod + FlameRally1: waypoint + Location: 4,5 + Owner: Nod + FlameRally3: waypoint + Location: 6,5 + Owner: Nod + FlameRally2: waypoint + Location: 5,6 + Owner: Nod + SouthVillageEntry: waypoint + Location: 10,61 + Owner: Nod + WestGateReveal: waypoint + Location: 32,13 + Faction: gdi + Owner: Nod + SouthRiverRally: waypoint + Location: 19,49 + Owner: GDI + MammothPatrolPoint1: waypoint + Location: 49,27 + Owner: GDI + MammothPatrolPoint2: waypoint + Location: 47,35 + Owner: GDI + MammothPatrolPoint3: waypoint + Location: 56,44 + Owner: GDI + MammothPatrolPoint4: waypoint + Location: 57,27 + Owner: GDI + MammothEntry: waypoint + Location: 50,27 + Owner: GDI + SouthGate: waypoint + Location: 52,19 + Owner: GDI + WestVillageEntry: waypoint + Location: 2,50 + Faction: gdi + Owner: Nod + Intersection: waypoint + Location: 9,52 + Faction: gdi + Owner: Nod + ApcEntry: waypoint + Location: 54,51 + Owner: GDI + SouthGateRocketEntry: waypoint + Location: 55,19 + Owner: GDI + SouthGateGrenadierEntry: waypoint + Location: 47,19 + Owner: GDI + DefaultCameraPoint: waypoint + Owner: Neutral + Location: 2,2 + SouthBridgeRally: waypoint + Location: 33,44 + Owner: GDI + SouthRiverEntry: waypoint + Location: 39,62 + Owner: GDI + EastHumveeRally: waypoint + Owner: GDI + Location: 51,24 + SouthFordEntry: waypoint + Location: 41,59 + Owner: GDI + CentralFordEntry1: waypoint + Location: 28,31 + Owner: GDI + CentralFordEntry2: waypoint + Location: 30,32 + Owner: GDI + CentralFordEntry3: waypoint + Location: 28,33 + Owner: GDI + MammothGateReveal: waypoint + Owner: GDI + Location: 51,20 + +Rules: cnc|rules/campaign-maprules.yaml, cnc|rules/campaign-tooltips.yaml, cnc|rules/campaign-palettes.yaml, rules.yaml + +FluentMessages: cnc|fluent/lua.ftl, cnc|fluent/campaign.ftl diff --git a/mods/cnc/maps/the-tiberium-strain/rules.yaml b/mods/cnc/maps/the-tiberium-strain/rules.yaml new file mode 100644 index 0000000000..a1f6712b0b --- /dev/null +++ b/mods/cnc/maps/the-tiberium-strain/rules.yaml @@ -0,0 +1,90 @@ +World: + LuaScript: + Scripts: campaign.lua, utils.lua, the-tiberium-strain.lua + MissionData: + Briefing: A nearby GDI base is conducting further ion research. In doing so, they use a large number of chemicals, located in their bio centers. Destroy all the bio centers, and "contaminate" all units and civilians. Leave all the other structures intact, so that it looks like an accident. + StartVideo: nodsweep.vqa + LossVideo: nodflees.vqa + WinVideo: desolat.vqa + ScriptLobbyDropdown@difficulty: + ID: difficulty + Label: dropdown-difficulty.label + Description: dropdown-difficulty.description + Values: + easy: options-difficulty.easy + normal: options-difficulty.normal + hard: options-difficulty.hard + Default: normal + Locked: false + +Player: + EnemyWatcher: + PlayerResources: + DefaultCash: 0 + +^Bridge: + DamageMultiplier@INVULNERABLE: + Modifier: 0 + +^CivBuilding: + Targetable: + RequiresCondition: !force-fire-required + Targetable@FORCENEEDED: + TargetTypes: Ground, Structure + RequiresForceFire: true + RequiresCondition: force-fire-required + GrantConditionOnPrerequisite: + Condition: force-fire-required + Prerequisites: village-targetable-switch + +# Spawned on Easy difficulty. +VillageTargetableSwitch: + AlwaysVisible: + ProvidesPrerequisite: + Prerequisite: village-targetable-switch + +^CivInfantry: + AnnounceOnSeen: + MustBeDestroyed: + ScriptTags: + +E1: + ScriptTags: + +E2: + ScriptTags: + +FTNK: + GrantConditionOnTerrain@SKIPBRIDGEHUSK: + Condition: skip-bridge-husk + TerrainTypes: Bridge + SpawnActorOnDeath: + RequiresCondition: !skip-bridge-husk + +JEEP: + GrantConditionOnTerrain@SKIPBRIDGEHUSK: + Condition: skip-bridge-husk + TerrainTypes: Bridge + SpawnActorOnDeath: + RequiresCondition: !skip-bridge-husk + ScriptTags: + +MTNK: + GrantConditionOnTerrain@SKIPBRIDGEHUSK: + Condition: skip-bridge-husk + TerrainTypes: Bridge + SpawnActorOnDeath: + RequiresCondition: !skip-bridge-husk + ScriptTags: + +BIO: + # The original BIO is fragile and Nod may take heavy losses on the path here. + Health: + HP: 70000 + Armor: + Type: Light + +BIO.Husk: + RevealsShroud: + Range: 2c0 + ValidRelationships: Enemy diff --git a/mods/cnc/maps/the-tiberium-strain/the-tiberium-strain.lua b/mods/cnc/maps/the-tiberium-strain/the-tiberium-strain.lua new file mode 100644 index 0000000000..90aa79e554 --- /dev/null +++ b/mods/cnc/maps/the-tiberium-strain/the-tiberium-strain.lua @@ -0,0 +1,735 @@ +--[[ + 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. +]] + +--- This mission's human player. +local Nod = Player.GetPlayer("Nod") + +--- Owner of the defenders. +local GDI = Player.GetPlayer("GDI") + +--- Owner of the homes and villagers. +local Villagers = Player.GetPlayer("Villagers") + +---@type integer Objective to destroy GDI's bio centers. +local BioObjective + +---@type integer Objective to destroy all villagers. +local VillagerObjective + +---@type integer Final objective to keep all non-BIO structures intact. +local IntactObjective + +--- Tally of most GDI deaths. Extra units on Hard do not apply. +local GDIDeathCount = 0 + +--- Number of counted GDI deaths that will trigger a map-wide hunt. +local HuntThreshold = 65 + +--- Has Nod's starting force arrived? +local NodArrived = false + +--- Are Nod's village sweepers about to reinforce? +local SweepersRequested = false + +--- Have civilians spawned from the south village road? +local RunnersArrived = false + +--- Return one of the entry waypoints by the east ore field. +---@return actor waypoint +local function RandomEastEntry() + return Map.NamedActor("EastEntry" .. Utils.RandomInteger(1, 5)) +end + +--- Is this actor a Nod attacker? +---@param actor actor +---@return boolean +local function IsMobileNod(actor) + return not actor.IsDead and actor.HasProperty("Move") and actor.Owner == Nod +end + +--- Is this a resident of the main village? +local function IsWestVillager(actor) + return actor.HasTag("West Villager") +end + +--- Is this actor alive and tagged to guard a wide area? +---@param actor actor +---@return boolean +local function IsLiveAreaGuard(actor) + return not actor.IsDead and actor.HasTag("Area Guard") +end + +--- Is this actor either dead or idle? +---@param actor actor +---@return boolean +local function IsDeadOrIdle(actor) + return actor.IsDead or actor.IsIdle +end + +--- Is Nod wiped out with no incoming sweepers? +--- @return boolean +local function IsNodDead() + return NodArrived and Nod.HasNoRequiredUnits() and not SweepersRequested +end + +--- Is this actor free to come hunt base attackers? +---@param actor actor +---@return boolean +local function CanDefendBase(actor) + return not actor.IsDead and actor.IsIdle and not actor.HasTag("Area Guard") +end + +--- Is GDI weak enough that Nod sweepers can be reinforced? +---@return boolean +local function AreSweepersReady() + local defenders = Utils.Where(GDI.GetGroundAttackers(), function(a) + -- Disregard the slow Mammoth Tank, which can be avoided if necessary. + return a.Type ~= "htnk" + end) + + return #defenders == 0 +end + +--- Reveal the defenses by GDI's west gate. +local function RevealWestBaseEntrance() + local camera = Actor.Create("camera.small", true, { Owner = Nod, Location = WestGateReveal.Location }) + Trigger.AfterDelay(DateTime.Seconds(6), camera.Destroy) +end + +--- Mark defeat for Nod's dead task force. +local function MarkNodDead() + local objs = { BioObjective, VillagerObjective } + + Utils.Do(objs, function(obj) + if Nod.IsObjectiveCompleted(obj) then + return + end + + Nod.MarkFailedObjective(obj) + end) +end + +--- Add the villager slaying objective if it's not already present. +--- This is done more than once as a safety check, partly because the +--- visibility cheat will bypass the usual trigger for this. +---@param announced? boolean +local function AddVillagerObjective(announced) + if not announced or IsNodDead() then + VillagerObjective = VillagerObjective or AddPrimaryObjective(Nod, "kill-every-villager-in-area") + return + end + + local speaker = UserInterface.GetFluentMessage("nod-soldier") + Media.DisplayMessage(UserInterface.GetFluentMessage("careless-gdi-experiments-killed"), speaker, Nod.Color) + + Trigger.AfterDelay(DateTime.Seconds(4), function() + if IsNodDead() then + return + end + + Media.DisplayMessage(UserInterface.GetFluentMessage("villagers-must-not-live"), speaker, Nod.Color) + end) + + Trigger.AfterDelay(DateTime.Seconds(8), AddVillagerObjective) +end + +--- Check the villager objective after each has been slain. +local function CheckVillagerObjective() + -- Delay so HasNoRequiredUnits accounts for deaths during THIS tick. + Trigger.AfterDelay(1, function() + if RunnersArrived and Villagers.HasNoRequiredUnits() then + AddVillagerObjective() + Nod.MarkCompletedObjective(VillagerObjective) + end + end) +end + +--- Spawn some extra Nod units for Easy difficulty. +local function ReinforceEasyNod() + if IsNodDead() then + return + end + + local rallies = { ChemRally1, ChemRally2, FlameRally2 } + Media.PlaySpeechNotification(Nod, "Reinforce") + + Utils.Do(rallies, function(rally) + local path = { NorthWestEntry2.Location, rally.Location } + Reinforcements.Reinforce(Nod, { "e5" }, path) + end) +end + +--- Prepare to count an actor's death toward GDI's hunt threshold. +---@param actor actor +local function TallyDeath(actor) + Trigger.OnKilled(actor, function() + GDIDeathCount = GDIDeathCount + 1 + + if GDIDeathCount == HuntThreshold then + -- The expected start -> village -> bridges -> SE corner -> base + -- path should yield 56-63 GDI deaths once the south gate guards + -- are cleared. 20 are from the village (incl. helicopter cargo). + Utils.Do(GDI.GetGroundAttackers(), function(attacker) + -- Stop any patrolling or area guarding. + attacker.Stop() + attacker.RemoveTag("Area Guard") + attacker.Stance = "AttackAnything" + IdleHunt(attacker) + end) + end + + if Difficulty == "easy" and GDIDeathCount % 15 == 0 then + ReinforceEasyNod() + end + end) +end + +--- Spawn and assemble Nod's starting force. +local function ReinforceFirstNod() + Media.PlaySpeechNotification(Nod, "Reinforce") + local chemTypes = { "e5", "e5", "e5", "e5" } + local flameTypes = { "ftnk" } + + for i = 1, 3 do + local path = { Map.NamedActor("NorthWestEntry" .. i).Location, Map.NamedActor("FlameRally" .. i).Location } + Reinforcements.Reinforce(Nod, flameTypes, path) + + if i ~= 3 then + Trigger.AfterDelay(20, function() + local chemPath = { NorthWestEntry2.Location, Map.NamedActor("ChemRally" .. i).Location } + Reinforcements.Reinforce(Nod, chemTypes, chemPath, 10) + end) + end + end + + Trigger.AfterDelay(DateTime.Seconds(3), function() + NodArrived = true + end) +end + +--- Spawn another Flame Tank for Nod after the bridge tank's death. +local function ReinforceNodTank() + if IsNodDead() then + return + end + + Media.PlaySpeechNotification(Nod, "Reinforce") + Reinforcements.Reinforce(Nod, { "ftnk" }, { NorthWestEntry2.Location, Waypoint1.Location }) +end + +--- Reinforce a Flame Tank as a reward for the intersection tank's death. +--- This originally spawns in the starting area, with no runners to chase. +local function ReinforceVillageFlameTank() + if IsNodDead() then + return + end + + local runnerTypes = { "c3", "c9", "c7" } + local runners = Reinforcements.Reinforce(Villagers, runnerTypes, { SouthVillageEntry.Location }, 5, function(runner) + Trigger.OnIdle(runner, function() + if runner.Location == Waypoint3.Location then + Trigger.ClearAll(runner) + end + + runner.Move(Waypoint3.Location) + end) + end) + + RunnersArrived = true + Trigger.OnAllKilled(runners, CheckVillagerObjective) + local camera = Actor.Create("camera.small", true, { Owner = Nod, Location = SouthVillageEntry.Location }) + + Trigger.AfterDelay(DateTime.Seconds(1), function() + local path = { SouthVillageEntry.Location, SouthVillageEntry.Location + CVec.New(0, -2) } + Reinforcements.Reinforce(Nod, { "ftnk" }, path) + Media.PlaySpeechNotification(Nod, "Reinforce") + camera.Destroy() + end) +end + +--- Reinforce Nod to resolve loose ends in the main village. This will speed +--- things along if Nod is close to victory but on the opposite end of the map. +local function ScheduleVillageSweepers() + if #Utils.Where(Villagers.GetActors(), IsWestVillager) == 0 then + return + end + + if not AreSweepersReady() then + Trigger.AfterDelay(10, ScheduleVillageSweepers) + return + end + + SweepersRequested = true + + Trigger.AfterDelay(DateTime.Seconds(3), function() + Media.PlaySpeechNotification(Nod, "Reinforce") + local path = { WestVillageEntry.Location, Intersection.Location } + local types = { "ftnk", "e5", "e5", "e5", "e5", "e5" } + Reinforcements.Reinforce(Nod, types, path, 10) + end) + + Trigger.AfterDelay(DateTime.Seconds(4), function() + SweepersRequested = false + end) +end + +--- Airlift an infantry team to avenge GDI units near the intersection. +local function ReinforceVillageHelicopter() + local path = { RandomEastEntry().Location, Waypoint0.Location } + local passengerTypes = { "e2", "e2", "e2", "e3", "e3" } + local passengers = Reinforcements.ReinforceWithTransport(GDI, "tran", passengerTypes, path, { path[1] })[2] + + Utils.Do(passengers, function(passenger) + IdleHunt(passenger) + TallyDeath(passenger) + end) +end + +--- Airlift an infantry team to hunt near the south edge's river crossing. +local function ReinforceRiverHelicopter() + local path = { SouthRiverEntry.Location, Waypoint2.Location } + local exit = RandomEastEntry().Location + local passengerTypes = { "e1", "e1", "e1", "e1", "e3" } + local passengers = Reinforcements.ReinforceWithTransport(GDI, "tran", passengerTypes, path, { exit })[2] + + Utils.Do(passengers, function(passenger) + TallyDeath(passenger) + + Trigger.OnAddedToWorld(passenger, function() + -- IdleHunt on its own may cause some of these infantry to take a + -- long route around the map because the narrow canyon is occupied. + passenger.AttackMove(SouthRiverRally.Location) + passenger.AttackMove(SouthBridgeRally.Location) + IdleHunt(passenger) + end) + end) +end + +--- Reinforce some scouts to counterattack at the south gate. +local function ReinforceEdgeHumvees() + local entry = RandomEastEntry() + local path = { entry.Location, entry.Location + CVec.New(-2, 0) } + + Reinforcements.Reinforce(GDI, { "jeep", "jeep" }, path, 25, function(humvee) + humvee.AttackMove(EastHumveeRally.Location, 2) + humvee.AttackMove(SouthGate.Location, 2) + humvee.AttackMove(Waypoint3.Location, 2) + IdleHunt(humvee) + TallyDeath(humvee) + end) +end + +--- Order a group to intercept (or chase away) nearby intruders when idle. +--- This is intended to feel similar to GUARD_AREA orders in TD '95. +---@param guards actor[] List of guards. +---@param center wpos Center of the group's search area. +---@param range wdist Radius of the area to guard. +local function GuardArea(guards, center, range) + local activated = false + local refreshed = false + guards = Utils.Where(guards, IsLiveAreaGuard) + + if #guards == 0 then + return + end + + Trigger.OnEnteredProximityTrigger(center, range, function(target, id) + if activated or not IsMobileNod(target) then + return + end + + activated = true + Trigger.RemoveProximityTrigger(id) + + Utils.Do(guards, function(guard) + if not IsLiveAreaGuard(guard) or not guard.IsIdle then + return + end + + guard.AttackMove(target.Location, 2) + guard.AttackMove(guard.Location) + end) + end) + + Utils.Do(guards, function(guard) + guard.Stance = "Defend" + + Trigger.OnIdle(guard, function() + -- Does at least one guard remain after an intruder search? + -- If so, reset the guards and prepare another search area. + if refreshed or not activated or not Utils.All(guards, IsDeadOrIdle) then + return + end + + refreshed = true + local survivors = Utils.Where(guards, IsLiveAreaGuard) + + Utils.Do(survivors, function(survivor) + Trigger.Clear(survivor, "OnIdle") + end) + + Trigger.AfterDelay(10, function() + GuardArea(survivors, center, range) + end) + end) + end) +end + +--- Order a random armed villager in the west to begin hunting. +local function SendVillagerHunter() + local villagers = Utils.Where(Villagers.GetGroundAttackers(), IsWestVillager) + if #villagers == 0 then + return + end + + local guard = Utils.Random(villagers) + IdleHunt(guard) +end + +--- Send an available unit to defend a damaged base structure. +---@param location cpos Location of the structure attacker. +local function SendBaseGuard(location) + local guards = Utils.Where(GDI.GetGroundAttackers(), CanDefendBase) + if #guards == 0 then + return + end + + local guard = Utils.Random(guards) + guard.AttackMove(location, 2) + IdleHunt(guard) +end + +--- Prepare simple events for the pre-placed villagers. +local function PrepareVillagers() + Trigger.OnPlayerDiscovered(Villagers, function(_, discoverer) + if discoverer == Nod then + AddVillagerObjective(true) + end + end) + + local mobiles = Utils.Where(Villagers.GetActors(), function(actor) + return actor.HasProperty("Move") + end) + + Trigger.OnAllKilled(mobiles, CheckVillagerObjective) +end + +--- Place all the footprint triggers needed at startup. +local function PrepareFootprints() + local footprints = + { + { + action = ReinforceRiverHelicopter, + cells = + { + CPos.New(28, 43), + CPos.New(28, 44), + CPos.New(28, 45), + CPos.New(28, 46) + } + }, + { + action = ReinforceEdgeHumvees, + cells = + { + CPos.New(49, 20), + CPos.New(50, 20), + CPos.New(51, 20), + CPos.New(52, 20), + CPos.New(53, 20) + } + }, + { + action = RevealWestBaseEntrance, + cells = + { + CPos.New(22, 11), + CPos.New(22, 12), + CPos.New(22, 13), + CPos.New(22, 14), + CPos.New(22, 15), + CPos.New(22, 16) + } + } + } + + Utils.Do(footprints, function(footprint) + local activated = false + + Trigger.OnEnteredFootprint(footprint.cells, function(actor, id) + if activated or not IsMobileNod(actor) then + return + end + + activated = true + Trigger.RemoveFootprintTrigger(id) + footprint.action() + end) + end) +end + +--- Set up the flares and objective for GDI's bio centers. +local function PrepareBioCenters() + local centers = { Bio1, Bio2, Bio3 } + local offset = CVec.New(1, 0) + + Utils.Do(centers, function(center) + Trigger.OnKilled(center, function() + Actor.Create("flare", true, { Owner = GDI, Location = center.Location + offset }) + end) + end) + + Trigger.OnAllKilled(centers, function() + AddVillagerObjective() + ScheduleVillageSweepers() + Nod.MarkCompletedObjective(BioObjective) + end) +end + +--- Prepare GDI structures' survival objective, repairs, and calls for help. +local function PrepareBase() + local baseStructures = Utils.Where(GDI.GetActors(), function(actor) + return actor.HasProperty("StartBuildingRepairs") and actor.Type ~= "bio" + end) + + Utils.Do(baseStructures, function(structure) + Trigger.OnKilled(structure, function() + Nod.MarkFailedObjective(IntactObjective) + end) + + RepairBuilding(GDI, structure, 0.75) + + -- Do not guard defenses or the isolated Communications Center. + if structure.HasProperty("Attack") or structure.Type == "hq" then + return + end + + Trigger.OnDamaged(structure, function(_, attacker) + if attacker.Owner == structure.Owner or attacker.IsDead then + return + end + + SendBaseGuard(attacker.Location) + end) + end) +end + +--- Prepare certain groups to begin area guarding. Some infantry that did area +--- guard have been skipped because their ORA range makes this feel redundant. +local function PrepareAreaGuards() + local teams = + { + { + actors = Map.ActorsWithTag("West Gate Grenadier"), + range = 5 + }, + { + actors = Map.ActorsWithTag("Village Entrance Guard"), + center = VillageEntranceGrenadier.CenterPosition, + range = 5 + }, + { + actors = Map.ActorsWithTag("Second House Guard"), + center = VillageHouse2.CenterPosition, + }, + { + actors = { HilltopHumvee } + }, + { + actors = { IntersectionHumvee } + }, + { + actors = { CommunicationsHumvee }, + }, + { + actors = Map.ActorsWithTag("Communications Guard"), + center = CommunicationsGrenadier.CenterPosition, + range = 6 + }, + { + actors = { BioGrenadier }, + range = 5 + } + } + + Utils.Do(teams, function(team) + local radius = WDist.FromCells(team.range or 7) + local center = team.center or team.actors[1].CenterPosition + GuardArea(team.actors, center, radius) + end) +end + +--- Prepare houses' survival objective, targetable switch, and calls for help. +local function PrepareVillageHouses() + if Difficulty == "easy" then + -- Require force-fire to attack houses, as butterfingers insurance. + Actor.Create("villagetargetableswitch", true, { Owner = Villagers, Location = CPos.Zero }) + end + + local village = Utils.Where(Villagers.GetActors(), function(actor) + return actor.HasProperty("Health") and not actor.HasProperty("Move") + end) + + Utils.Do(village, function(house) + Trigger.OnKilled(house, function() + Nod.MarkFailedObjective(IntactObjective) + end) + + -- Only houses inside the main village should call for help. + if house.Location.X >= WestGateReveal.Location.X then + return + end + + local hunterCalled = false + + Trigger.OnDamaged(house, function() + if hunterCalled or house.IsDead then + return + end + + hunterCalled = true + SendVillagerHunter() + Trigger.Clear(house, "OnDamaged") + end) + end) +end + +--- Prepare the reveals and patrol for Hard's Mammoth Tank. +---@param tank actor +local function PrepareMammoth(tank) + local path = { MammothPatrolPoint1.Location, MammothPatrolPoint2.Location, MammothPatrolPoint3.Location, MammothPatrolPoint4.Location } + local encountered = false + + Trigger.OnEnteredProximityTrigger(MammothEntry.CenterPosition, WDist.FromCells(9), function(actor, id) + if encountered or not IsMobileNod(actor) then + return + end + + encountered = true + Trigger.RemoveProximityTrigger(id) + tank.Patrol(path, true, DateTime.Seconds(3)) + local camera = Actor.Create("camera.small", true, { Owner = Nod, Location = MammothPatrolPoint1.Location }) + Trigger.AfterDelay(DateTime.Seconds(6), camera.Destroy) + end) + + Trigger.OnEnteredProximityTrigger(MammothGateReveal.CenterPosition, WDist.FromCells(4), function(actor, id) + if actor.Type ~= "htnk" then + return + end + + Trigger.RemoveProximityTrigger(id) + local camera = Actor.Create("camera", true, { Owner = Nod, Location = MammothGateReveal.Location }) + Trigger.AfterDelay(DateTime.Seconds(6), camera.Destroy) + end) +end + +--- Spawn some extra units for the light "remix" of Hard difficulty. +local function SpawnHardUnits() + if Difficulty ~= "hard" then + return + end + + local spawns = + { + { + type = "apc", + cell = ApcEntry.Location, + facing = Angle.SouthWest + }, + { + type = "htnk", + cell = MammothEntry.Location, + facing = Angle.West, + action = PrepareMammoth + }, + { + type = "e2", + cell = SouthGateGrenadierEntry.Location, + facing = Angle.SouthEast, + stance = "Defend" + }, + { + type = "e3", + cell = SouthGateRocketEntry.Location, + facing = Angle.SouthWest, + stance = "Defend" + }, + { + type = "mtnk", + cell = Waypoint0.Location, + facing = Angle.North, + stance = "Defend" + }, + { + type = "e3", + cell = CentralFordEntry1.Location + }, + { + type = "e3", + cell = CentralFordEntry2.Location + }, + { + type = "e3", + cell = CentralFordEntry3.Location + }, + { + type = "mtnk", + cell = SouthFordEntry.Location, + stance = "Defend" + } + } + + Utils.Do(spawns, function(spawn) + local facing = spawn.facing or Angle.West + local actor = Actor.Create(spawn.type, true, { Owner = GDI, Location = spawn.cell, Facing = facing }) + actor.Stance = spawn.stance or actor.Stance + + if spawn.action then + spawn.action(actor) + end + end) +end + +WorldLoaded = function() + InitObjectives(Nod) + BioObjective = AddPrimaryObjective(Nod, "destroy-gdi-bio-centers") + IntactObjective = AddPrimaryObjective(Nod, "keep-all-other-structures") + + PrepareFootprints() + PrepareBase() + PrepareBioCenters() + PrepareVillageHouses() + + Utils.Do(GDI.GetGroundAttackers(), TallyDeath) + PrepareVillagers() + PrepareAreaGuards() + SpawnHardUnits() + ReinforceFirstNod() + + GDI.Cash = 5000 + Camera.Position = FlameRally2.CenterPosition + + Trigger.OnKilled(BridgeTank, ReinforceNodTank) + local intersectionTeam = { IntersectionHumvee, IntersectionRifle1, IntersectionRifle2, IntersectionRifle3 } + Trigger.OnAllKilled(intersectionTeam, ReinforceVillageHelicopter) + + Trigger.OnKilled(IntersectionTank, function() + Trigger.AfterDelay(DateTime.Seconds(1), ReinforceVillageFlameTank) + end) +end + +Tick = function() + if IsNodDead() then + MarkNodDead() + end + + if Nod.IsObjectiveCompleted(BioObjective) and Nod.IsObjectiveCompleted(VillagerObjective) then + Nod.MarkCompletedObjective(IntactObjective) + end +end diff --git a/mods/cnc/missions.yaml b/mods/cnc/missions.yaml index b8c53710e2..901cd8394e 100644 --- a/mods/cnc/missions.yaml +++ b/mods/cnc/missions.yaml @@ -38,6 +38,7 @@ Nod Campaign: Covert Operations: twist-of-fate eviction-notice + the-tiberium-strain Funpark Campaign: funpark01