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 0000000000..49b034bcca Binary files /dev/null and b/mods/cnc/maps/twist-of-fate/map.bin differ 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 0000000000..048999e527 Binary files /dev/null and b/mods/cnc/maps/twist-of-fate/map.png differ diff --git a/mods/cnc/maps/twist-of-fate/map.yaml b/mods/cnc/maps/twist-of-fate/map.yaml new file mode 100644 index 0000000000..ca8dc79ae0 --- /dev/null +++ b/mods/cnc/maps/twist-of-fate/map.yaml @@ -0,0 +1,1147 @@ +MapFormat: 12 + +RequiresMod: cnc + +Title: GDI - Twist of Fate + +Author: Westwood Studios + +Tileset: DESERT + +MapSize: 64,64 + +Bounds: 2,2,60,60 + +Visibility: MissionSelector + +Categories: Campaign + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: Random + PlayerReference@Creeps: + Name: Creeps + NonCombatant: True + Faction: Random + PlayerReference@Nod: + Name: Nod + Bot: campaign + Faction: nod + Color: FE1100 + Enemies: GDI + PlayerReference@GDI: + Name: GDI + AllowBots: False + Playable: True + Required: True + LockFaction: True + Faction: gdi + LockColor: True + Color: F5D378 + LockSpawn: True + LockTeam: True + Enemies: Nod + +Actors: + Actor0: brik + Location: 53,61 + Owner: GDI + Actor1: brik + Location: 52,61 + Owner: GDI + Actor2: brik + Location: 53,60 + Owner: GDI + Actor3: brik + Location: 52,60 + Owner: GDI + Actor4: brik + Location: 52,59 + Owner: GDI + Actor5: brik + Location: 52,58 + Owner: GDI + Actor6: brik + Location: 52,57 + Owner: GDI + Actor7: brik + Location: 59,56 + Owner: GDI + Actor8: brik + Location: 58,56 + Owner: GDI + Actor9: brik + Location: 55,56 + Owner: GDI + Actor10: brik + Location: 54,56 + Owner: GDI + Actor11: brik + Location: 52,56 + Owner: GDI + Actor12: brik + Location: 59,55 + Owner: GDI + Actor13: brik + Location: 58,55 + Owner: GDI + Actor14: brik + Location: 55,55 + Owner: GDI + Actor15: brik + Location: 54,55 + Owner: GDI + Actor16: brik + Location: 53,55 + Owner: GDI + Actor17: brik + Location: 52,55 + Owner: GDI + Actor18: t13.transformable + Location: 47,4 + Owner: Neutral + Actor19: t13.transformable + Location: 4,20 + Owner: Neutral + Actor20: t13.transformable + Location: 9,13 + Owner: Neutral + Actor21: rock3 + Location: 42,47 + Owner: Neutral + Actor22: rock6 + Location: 32,25 + Owner: Neutral + Actor23: t04 + Location: 38,8 + Owner: Neutral + Actor24: rock2 + Location: 44,9 + Owner: Neutral + Actor25: rock1 + Location: 50,3 + Owner: Neutral + Actor26: t08 + Location: 58,11 + Owner: Neutral + Actor27: t18 + Location: 51,17 + Owner: Neutral + Actor28: t09 + Location: 43,38 + Owner: Neutral + Actor29: t18 + Location: 52,41 + Owner: Neutral + Actor30: t18 + Location: 48,48 + Owner: Neutral + Actor31: t08 + Location: 50,50 + Owner: Neutral + Actor32: t08 + Location: 38,33 + Owner: Neutral + Actor33: t13.transformable + Location: 13,26 + Owner: Neutral + Actor34: t13.transformable + Location: 6,34 + Owner: Neutral + Actor35: t13.transformable + Location: 39,41 + Owner: Neutral + Actor36: t13.transformable + Location: 25,5 + Owner: Neutral + Actor37: t18 + Location: 14,1 + Owner: Neutral + Actor38: t08 + Location: 18,2 + Owner: Neutral + Actor39: t13.transformable + Location: 24,46 + Owner: Neutral + Actor40: t13.transformable + Location: 22,56 + Owner: Neutral + Actor41: t13.transformable + Location: 29,57 + Owner: Neutral + Actor42: t13.transformable + Location: 37,56 + Owner: Neutral + Actor43: t13.transformable + Location: 34,50 + Owner: Neutral + Actor44: t13.transformable + Location: 56,40 + Owner: Neutral + Actor45: t13.transformable + Location: 58,35 + Owner: Neutral + Actor46: t13.transformable + Location: 54,21 + Owner: Neutral + Actor47: rock2 + Location: 42,2 + Owner: Neutral + Actor48: t08 + Location: 44,2 + Owner: Neutral + Actor49: t08 + Location: 39,2 + Owner: Neutral + Actor50: rock1 + Location: 2,48 + Owner: Neutral + Actor51: t08 + Location: 2,51 + Owner: Neutral + Actor52: t04 + Location: 2,50 + Owner: Neutral + Actor53: t18 + Location: 24,18 + Owner: Neutral + Actor54: t08 + Location: 22,16 + Owner: Neutral + Actor55: rock1 + Location: 4,5 + Owner: Neutral + Actor56: rock2 + Location: 24,21 + Owner: Neutral + Actor57: obli + Location: 37,27 + Owner: Nod + Actor58: gun + Location: 36,23 + Owner: Nod + Actor59: gun + Location: 31,28 + Owner: Nod + Facing: 256 + Actor60: nuk2 + Location: 41,25 + Owner: Nod + Actor61: nuk2 + Location: 39,22 + Owner: Nod + Actor62: nuk2 + Location: 35,25 + Owner: Nod + NodOutPostCYard: fact + Location: 37,29 + Owner: Nod + NodHpad2: hpad + Location: 9,45 + Owner: Nod + NodHpad1: hpad + Location: 4,57 + Owner: Nod + Actor66: gun + Location: 20,53 + Owner: Nod + Facing: 896 + NodGun4: gun + Location: 19,49 + Owner: Nod + Facing: 768 + TurretFacing: 192 + Actor68: gun + Location: 4,39 + Owner: Nod + Facing: 896 + Actor69: gun + Location: 10,39 + Owner: Nod + Facing: 128 + NodSam5: sam + Location: 4,53 + Owner: Nod + NodCYard: fact + Location: 6,57 + Owner: Nod + Actor72: gun + Location: 15,37 + Owner: Nod + Facing: 256 + NodGun6: gun + Location: 16,36 + Owner: Nod + Facing: 896 + TurretFacing: 192 + Actor74: gun + Location: 17,37 + Owner: Nod + Facing: 768 + Actor75: gun + Location: 10,6 + Owner: Nod + Facing: 640 + Actor76: nuk2 + Location: 7,2 + Owner: Nod + NodAstkHq: hq + Location: 11,3 + Owner: Nod + Actor78: gtwr + Location: 59,54 + Owner: GDI + Actor79: gtwr + Location: 54,54 + Owner: GDI + Actor80: hpad + Location: 54,58 + Owner: GDI + Actor81: hpad + Location: 57,58 + Owner: GDI + Actor82: obli + Location: 13,3 + Owner: Nod + Actor83: nuke + Location: 9,2 + Owner: Nod + NodOutPostHpad: hpad + Location: 38,26 + Owner: Nod + NodSam7: sam + Location: 38,25 + Owner: Nod + NodNuke2: nuk2 + Location: 9,56 + Owner: Nod + NodAfld: afld + Location: 13,54 + Owner: Nod + NodHand: hand + Location: 16,47 + Owner: Nod + NodSilo2: silo + Location: 14,44 + Owner: Nod + NodSilo4: silo + Location: 14,41 + Owner: Nod + NodSilo3: silo + Location: 16,42 + Owner: Nod + NodNuke6: nuk2 + Location: 7,47 + Owner: Nod + NodNuke7: nuk2 + Location: 4,45 + Owner: Nod + NodSam3: sam + Location: 5,42 + Owner: Nod + NodSam1: sam + Location: 17,44 + Owner: Nod + NodSam4: sam + Location: 15,38 + Owner: Nod + NodSam6: sam + Location: 8,60 + Owner: Nod + NodSam2: sam + Location: 17,56 + Owner: Nod + NodTmpl: tmpl + Location: 6,51 + Owner: Nod + NodNuke1: nuk2 + Location: 6,54 + Owner: Nod + NodNuke4: nuk2 + Location: 10,53 + Owner: Nod + NodObli3: obli + Location: 9,55 + Owner: Nod + Actor103: obli + Location: 16,37 + Owner: Nod + NodNuke3: nuk2 + Location: 11,58 + Owner: Nod + NodNuke5: nuk2 + Location: 9,50 + Owner: Nod + NodNuke8: nuk2 + Location: 12,46 + Owner: Nod + NodObli1: obli + Location: 18,54 + Owner: Nod + NodObli4: obli + Location: 19,45 + Owner: Nod + NodObli2: obli + Location: 7,45 + Owner: Nod + NodSilo1: silo + Location: 16,57 + Owner: Nod + NodGun1: gun + Location: 12,57 + Owner: Nod + TurretFacing: 192 + NodGun5: gun + Location: 15,43 + Owner: Nod + TurretFacing: 192 + NodGun3: gun + Location: 9,42 + Owner: Nod + TurretFacing: 192 + NodGun2: gun + Location: 11,48 + Owner: Nod + TurretFacing: 192 + Actor115: mlrs + Location: 37,25 + Owner: Nod + Facing: 128 + Actor116: mlrs + Location: 10,5 + Owner: Nod + Facing: 640 + Actor117: mlrs + Location: 21,33 + Owner: Nod + Facing: 768 + Actor118: bike + Location: 24,34 + Owner: Nod + Facing: 640 + Actor119: bike + Location: 21,35 + Owner: Nod + Facing: 512 + Actor120: bike + Location: 22,31 + Owner: Nod + Facing: 640 + Actor121: ftnk + Location: 31,27 + Owner: Nod + Facing: 128 + Actor122: ltnk + Location: 34,22 + Owner: Nod + Actor123: stnk + Location: 23,57 + Owner: Nod + Stance: Defend + Facing: 896 + Actor124: stnk + Location: 25,45 + Owner: Nod + Stance: Defend + Facing: 128 + Actor125: stnk + Location: 55,25 + Owner: Nod + Stance: Defend + Facing: 128 + Actor126: htnk + Location: 56,12 + Owner: GDI + Facing: 512 + Actor127: stnk + Location: 23,7 + Owner: Nod + Stance: Defend + Facing: 640 + Actor128: stnk + Location: 26,3 + Owner: Nod + Stance: Defend + Facing: 640 + Actor129: apc + Location: 48,35 + Owner: GDI + Health: 53 + Facing: 512 + Actor130: ltnk + Location: 52,30 + Owner: Nod + Facing: 384 + Actor131: bike + Location: 52,16 + Owner: Nod + Health: 12 + Facing: 384 + Actor132: msam + Location: 49,30 + Owner: GDI + Facing: 512 + Actor133: mtnk + Location: 50,17 + Owner: GDI + Facing: 384 + Actor134: bike + Location: 47,17 + Owner: Nod + Health: 68 + Facing: 768 + Actor135: bike + Location: 48,19 + Owner: Nod + Health: 31 + Facing: 896 + Actor136: ftnk + Location: 53,11 + Owner: Nod + Facing: 640 + Actor137: ltnk + Location: 57,6 + Owner: Nod + Facing: 384 + Actor138: jeep + Location: 55,57 + Owner: GDI + Facing: 896 + Actor139: ltnk + Location: 59,47 + Owner: Nod + Health: 70 + Facing: 384 + Actor140: ltnk + Location: 54,48 + Owner: Nod + Health: 38 + Facing: 640 + Actor141: mtnk + Location: 57,50 + Owner: GDI + Facing: 512 + Actor142: htnk + Location: 56,9 + Owner: GDI + Facing: 512 + Actor143: stnk + Location: 59,21 + Owner: Nod + Stance: Defend + Facing: 384 + Actor144: stnk + Location: 9,24 + Owner: Nod + Stance: Defend + Facing: 640 + Actor145: stnk + Location: 17,28 + Owner: Nod + Stance: Defend + Facing: 128 + Actor146: ltnk + Location: 15,7 + Owner: Nod + Facing: 640 + Actor147: ltnk + Location: 12,8 + Owner: Nod + Facing: 640 + Actor148: jeep + Location: 58,57 + Owner: GDI + Facing: 128 + Actor149: apc + Location: 53,56 + Owner: GDI + Actor150: mtnk + Location: 49,32 + Owner: GDI + Facing: 512 + Actor151: ltnk + Location: 47,29 + Owner: Nod + Facing: 640 + Actor152: ltnk + Location: 28,25 + Owner: Nod + Facing: 256 + Actor153: ltnk + Location: 31,23 + Owner: Nod + Facing: 128 + Actor154: ltnk + Location: 19,50 + Owner: Nod + Facing: 640 + Actor155: ltnk + Location: 18,52 + Owner: Nod + Facing: 768 + Actor156: ltnk + Location: 5,38 + Owner: Nod + Facing: 768 + Actor157: ltnk + Location: 10,38 + Owner: Nod + Facing: 128 + Actor158: ltnk + Location: 12,45 + Owner: Nod + Facing: 640 + Actor159: arty + Location: 16,39 + Owner: Nod + Facing: 768 + Actor160: arty + Location: 18,46 + Owner: Nod + Facing: 768 + Actor161: arty + Location: 13,41 + Owner: Nod + Facing: 128 + Actor162: arty + Location: 43,23 + Owner: Nod + Facing: 896 + Actor163: e4 + Location: 13,6 + Owner: Nod + Facing: 768 + SubCell: 0 + Actor164: e4 + Location: 12,7 + Owner: Nod + Facing: 640 + SubCell: 1 + Actor165: e1 + Location: 33,23 + Owner: Nod + Facing: 128 + SubCell: 4 + Actor166: e1 + Location: 35,24 + Owner: Nod + Facing: 256 + SubCell: 3 + Actor167: e1 + Location: 37,23 + Owner: Nod + Facing: 128 + SubCell: 2 + Actor168: e1 + Location: 38,23 + Owner: Nod + Facing: 128 + SubCell: 4 + Actor169: e1 + Location: 36,31 + Owner: Nod + SubCell: 1 + Actor170: e1 + Location: 33,30 + Owner: Nod + SubCell: 4 + Actor171: e1 + Location: 35,28 + Owner: Nod + Facing: 256 + SubCell: 3 + Actor172: e3 + Location: 53,9 + Owner: Nod + SubCell: 0 + Actor173: e3 + Location: 55,14 + Owner: Nod + SubCell: 1 + Actor174: e1 + Location: 55,11 + Owner: Nod + SubCell: 1 + Actor175: e1 + Location: 57,12 + Owner: Nod + SubCell: 4 + Actor176: e1 + Location: 47,37 + Owner: Nod + Health: 55 + Facing: 896 + SubCell: 1 + Actor177: e1 + Location: 49,36 + Owner: Nod + Health: 53 + Facing: 128 + SubCell: 0 + Actor178: e1 + Location: 47,34 + Owner: Nod + Facing: 640 + SubCell: 0 + Actor179: e1 + Location: 50,35 + Owner: Nod + Facing: 384 + SubCell: 1 + Actor180: e1 + Location: 47,35 + Owner: Nod + Facing: 768 + SubCell: 3 + Actor181: e3 + Location: 47,31 + Owner: Nod + Facing: 768 + SubCell: 1 + Actor182: e3 + Location: 48,32 + Owner: Nod + Facing: 896 + SubCell: 1 + Actor183: e1 + Location: 56,7 + Owner: Nod + Facing: 512 + SubCell: 0 + Actor184: e1 + Location: 57,7 + Owner: Nod + SubCell: 2 + Facing: 384 + Actor185: e1 + Location: 55,8 + Owner: Nod + Facing: 640 + SubCell: 2 + Actor186: e1 + Location: 57,10 + Owner: Nod + Facing: 128 + SubCell: 2 + Actor187: e1 + Location: 48,42 + Owner: Nod + Health: 61 + Facing: 640 + SubCell: 1 + Actor188: e1 + Location: 49,42 + Owner: Nod + Health: 65 + Facing: 512 + SubCell: 1 + Actor189: e1 + Location: 50,42 + Owner: Nod + Health: 21 + Facing: 384 + SubCell: 0 + Actor190: e1 + Location: 47,46 + Owner: Nod + Health: 39 + Facing: 896 + SubCell: 0 + Actor191: e1 + Location: 46,45 + Owner: Nod + Facing: 896 + SubCell: 2 + Actor192: e1 + Location: 48,43 + Owner: GDI + Facing: 512 + SubCell: 0 + Actor193: e1 + Location: 49,44 + Owner: GDI + Facing: 640 + SubCell: 2 + Actor194: e1 + Location: 48,45 + Owner: GDI + Facing: 640 + SubCell: 1 + Actor195: e1 + Location: 59,52 + Owner: Nod + Facing: 512 + SubCell: 0 + Actor196: e1 + Location: 54,52 + Owner: Nod + Facing: 640 + SubCell: 1 + Actor197: e1 + Location: 58,52 + Owner: Nod + SubCell: 4 + Actor198: e1 + Location: 58,53 + Owner: Nod + Facing: 384 + SubCell: 0 + Actor199: e1 + Location: 55,53 + Owner: Nod + Facing: 512 + SubCell: 2 + Actor200: e1 + Location: 54,52 + Owner: Nod + Facing: 512 + SubCell: 4 + Actor201: e1 + Location: 55,51 + Owner: Nod + Facing: 384 + SubCell: 0 + Actor202: e6 + Location: 57,52 + Owner: Nod + Facing: 512 + SubCell: 1 + Actor203: e3 + Location: 60,8 + Owner: Nod + SubCell: 3 + Facing: 384 + Actor204: e3 + Location: 57,11 + Owner: Nod + SubCell: 2 + Actor205: e3 + Location: 55,7 + Owner: Nod + Facing: 640 + SubCell: 1 + Actor206: e2 + Location: 56,55 + Owner: GDI + SubCell: 1 + Facing: 640 + Actor207: e2 + Location: 58,54 + Owner: GDI + SubCell: 3 + Actor208: e2 + Location: 55,54 + Owner: GDI + Facing: 896 + SubCell: 3 + Actor209: e2 + Location: 57,55 + Owner: GDI + Facing: 128 + SubCell: 0 + Actor210: e3 + Location: 34,27 + Owner: Nod + SubCell: 3 + Actor211: e3 + Location: 35,23 + Owner: Nod + Facing: 128 + SubCell: 0 + Actor212: e3 + Location: 32,24 + Owner: Nod + Facing: 256 + SubCell: 0 + Actor213: e3 + Location: 18,50 + Owner: Nod + Facing: 640 + SubCell: 1 + Actor214: e3 + Location: 15,49 + Owner: Nod + Facing: 512 + SubCell: 4 + Actor215: e3 + Location: 16,53 + Owner: Nod + SubCell: 4 + Facing: 896 + Actor216: e3 + Location: 15,39 + Owner: Nod + Facing: 256 + SubCell: 4 + Actor217: e3 + Location: 16,40 + Owner: Nod + Facing: 640 + SubCell: 0 + Actor218: e3 + Location: 6,40 + Owner: Nod + Facing: 896 + SubCell: 3 + Actor219: e3 + Location: 9,41 + Owner: Nod + Facing: 384 + SubCell: 0 + Actor220: e1 + Location: 17,50 + Owner: Nod + SubCell: 1 + Actor221: e1 + Location: 17,53 + Owner: Nod + SubCell: 4 + Actor222: e1 + Location: 19,52 + Owner: Nod + SubCell: 3 + Actor223: e1 + Location: 18,48 + Owner: Nod + SubCell: 2 + Actor224: e1 + Location: 15,48 + Owner: Nod + Facing: 256 + SubCell: 4 + Actor225: e1 + Location: 16,50 + Owner: Nod + Facing: 640 + SubCell: 0 + Actor226: e1 + Location: 7,42 + Owner: Nod + SubCell: 2 + Actor227: e1 + Location: 6,41 + Owner: Nod + Facing: 768 + SubCell: 2 + Actor228: e1 + Location: 9,40 + Owner: Nod + Facing: 128 + SubCell: 4 + DefaultChinookTarget: waypoint + Location: 32,32 + Owner: Neutral + DefaultCameraPosition: waypoint + Location: 48,54 + Owner: Neutral + DefaultFlareLocation: waypoint + Location: 45,53 + Owner: Neutral + waypoint14: waypoint + Location: 31,40 + Owner: Neutral + waypoint13: waypoint + Location: 12,12 + Owner: Neutral + waypoint12: waypoint + Location: 48,60 + Owner: Neutral + waypoint11: waypoint + Location: 36,59 + Owner: Neutral + waypoint10: waypoint + Location: 22,51 + Owner: Neutral + waypoint9: waypoint + Location: 29,20 + Owner: Neutral + waypoint8: waypoint + Location: 48,34 + Owner: Neutral + waypoint7: waypoint + Location: 24,34 + Owner: Neutral + waypoint6: waypoint + Location: 30,5 + Owner: Neutral + waypoint5: waypoint + Location: 10,35 + Owner: Neutral + waypoint4: waypoint + Location: 57,54 + Owner: Neutral + waypoint3: waypoint + Location: 50,46 + Owner: Neutral + waypoint2: waypoint + Location: 49,17 + Owner: Neutral + waypoint1: waypoint + Location: 55,17 + Owner: Neutral + waypoint0: waypoint + Location: 56,3 + Owner: Neutral + Actor245: orca + Owner: GDI + Location: 56,58 + Facing: 384 + Actor246: orca + Owner: GDI + Facing: 384 + Location: 59,58 + ApacheG: heli + Owner: Nod + Location: 9,45 + Facing: 384 + OutpostApacheG: heli + Owner: Nod + Location: 38,26 + Facing: 384 + Def1: arty + Owner: Nod + Location: 6,43 + Facing: 384 + Def2: arty + Owner: Nod + Location: 14,45 + Facing: 384 + Def3: arty + Owner: Nod + Location: 18,55 + Facing: 384 + Def5: bike + Owner: Nod + Location: 13,53 + Facing: 384 + Def4: bike + Owner: Nod + Location: 10,49 + Facing: 384 + Def7: bggy + Owner: Nod + Location: 15,47 + Facing: 384 + Def8: bggy + Owner: Nod + Location: 6,46 + Facing: 384 + Def6: bggy + Owner: Nod + Location: 15,53 + Facing: 384 + Def9: heli + Owner: Nod + Location: 12,55 + Facing: 384 + Def10: heli + Owner: Nod + Location: 9,53 + Facing: 384 + Pat1: bike + Owner: Nod + Location: 4,9 + Facing: 630 + Pat2: bike + Owner: Nod + Location: 6,9 + Facing: 630 + Pat3: bike + Owner: Nod + Location: 6,7 + Facing: 630 + NodProc1: proc + Owner: Nod + Location: 10,41 + NodProc2: proc + Owner: Nod + Location: 12,49 + NodHq: hq + Owner: Nod + Location: 13,57 + Actor267: ltnk + Owner: Nod + Facing: 384 + Location: 44,6 + Actor268: ltnk + Owner: Nod + Facing: 384 + Location: 45,7 + Actor269: stnk + Owner: Nod + Stance: Defend + Facing: 384 + Location: 45,5 + Actor270: e5 + Owner: Nod + SubCell: 3 + Facing: 384 + Location: 44,7 + Actor271: e5 + Owner: Nod + Facing: 384 + Location: 44,7 + SubCell: 1 + Actor273: e5 + Owner: Nod + Facing: 384 + Location: 44,5 + SubCell: 3 + Actor274: arty + Owner: Nod + Location: 28,59 + Facing: 884 + Cam1: camera.small + Faction: Random + Location: 56,10 + Owner: GDI + Cam2: camera.small + Faction: Random + Location: 51,17 + Owner: GDI + Cam3: camera.small + Faction: Random + Location: 49,31 + Owner: GDI + Cam4: camera.small + Faction: Random + Location: 49,37 + Owner: GDI + Cam5: camera.small + Faction: Random + Location: 49,45 + Owner: GDI + OldGDIProc: proc + Owner: Neutral + Location: 22,21 + Health: 12 + FreeActor: False + OldGDIWeap: weap + Owner: Neutral + Location: 31,18 + Health: 19 + OldGDIPyle: pyle + Owner: Neutral + Location: 30,24 + Health: 15 + OldGDIGtwr2: gtwr + Owner: Neutral + Location: 21,26 + Health: 30 + OldGDIGtwr3: gtwr + Owner: Neutral + Location: 25,26 + Health: 16 + OldGDIGtwr1: gtwr + Owner: Neutral + Location: 36,19 + Health: 10 + +Rules: cnc|rules/campaign-maprules.yaml, cnc|rules/campaign-tooltips.yaml, cnc|rules/campaign-palettes.yaml, rules.yaml + +Translations: cnc|languages/lua/en.ftl, cnc|languages/difficulties/en.ftl + +Weapons: weapons.yaml diff --git a/mods/cnc/maps/twist-of-fate/rules.yaml b/mods/cnc/maps/twist-of-fate/rules.yaml new file mode 100644 index 0000000000..7eb2a80ded --- /dev/null +++ b/mods/cnc/maps/twist-of-fate/rules.yaml @@ -0,0 +1,107 @@ +World: + MissionData: + StartVideo: airstrk.vqa + LossVideo: obel.vqa + WinVideo: paratrop.vqa + Briefing: We had set up a small recon post in Nod territory, but they captured and reinforced it.\n\nGetting that post back would be a major coup.\n\nAn MCV and armed convoy are on their way to aid you in establishing a new base.\n\nOnce established, eliminate all Nod forces in the area. + LuaScript: + Scripts: campaign.lua, utils.lua, twist-of-fate.lua, twist-of-fate-AI.lua + 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 + +Player: + PlayerResources: + DefaultCash: 0 + +CAMERA.mini: + Inherits: CAMERA + RevealsShroud: + Range: 1c512 + Type: CenterPosition + RenderSpritesEditorOnly: + Image: camera + +FLARE: + RevealsShroud: + Range: 7c0 + +PROC: + GrantConditionOnPrerequisite@AIN: + Condition: ain + Prerequisites: diffnorm + GrantConditionOnPrerequisite@AIH: + Condition: aih + Prerequisites: diffhard + ResourceValueMultiplier@AIN: + Modifier: 150 + RequiresCondition: ain + ResourceValueMultiplier@AIH: + Modifier: 200 + RequiresCondition: aih + +AIHProcUpgrade: + ProvidesPrerequisite: + Prerequisite: diffhard + StoresResources: + Capacity: 25000 + Interactable: + AlwaysVisible: + +AINProcUpgrade: + ProvidesPrerequisite: + Prerequisite: diffnorm + StoresResources: + Capacity: 15000 + Interactable: + AlwaysVisible: + +AiAnyhqPrerequisite: + ProvidesPrerequisite: + Prerequisite: anyhq + Interactable: + AlwaysVisible: + +AiTmplPrerequisite: + ProvidesPrerequisite: + Prerequisite: tmpl + Interactable: + AlwaysVisible: + +Astk.proxy: + AlwaysVisible: + AirstrikePower: + StartFullyCharged: True + Prerequisites: ~techlevel.superweapons + Icon: airstrike + ChargeInterval: 5250 + SquadSize: 3 + QuantizedFacings: 8 + Name: Air Strike + Description: Deploy an aerial napalm strike.\nBurns buildings and infantry along a line. + EndChargeSpeechNotification: AirstrikeReady + SelectTargetSpeechNotification: SelectTarget + InsufficientPowerSpeechNotification: InsufficientPower + IncomingSpeechNotification: EnemyPlanesApproaching + EndChargeTextNotification: Airstrike ready. + SelectTargetTextNotification: Select target. + InsufficientPowerTextNotification: Insufficient power. + IncomingTextNotification: Enemy planes approaching. + UnitType: a10 + DisplayBeacon: True + BeaconPoster: airstrike + BeaconPosterPalette: beaconposter + DisplayRadarPing: True + CameraActor: camera + ArrowSequence: arrow + ClockSequence: clock + CircleSequence: circles + UseDirectionalTarget: True + DirectionArrowAnimation: airstrikedirection + SupportPowerPaletteOrder: 10 diff --git a/mods/cnc/maps/twist-of-fate/twist-of-fate-AI.lua b/mods/cnc/maps/twist-of-fate/twist-of-fate-AI.lua new file mode 100644 index 0000000000..5921b2f874 --- /dev/null +++ b/mods/cnc/maps/twist-of-fate/twist-of-fate-AI.lua @@ -0,0 +1,650 @@ +--[[ + 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. +]] + +ProductionTypes = { "hand", "pyle", "afld", "weap", "hpad" } +PowerTypes = { "nuk2", "nuke" } + +NodCYards = { NodCYard, NodOutPostCYard } +NodBase = { NodNuke1, NodProc1, NodHand, NodAfld, NodProc2, NodHpad1, NodNuke2, NodHq, NodGun3, NodGun4, NodNuke3, NodObli1, NodObli2, NodNuke4, NodObli3, NodObli4, NodNuke5, NodGun1, NodGun2, NodGun5, NodNuke6, NodGun6, NodNuke7, NodNuke8, NodSilo1, NodSilo2, NodSilo3, NodSilo4 } +NodRebuildList = { } +BuildingSizes = { nuk2 = CVec.New(2,3), proc = CVec.New(3,4), hand = CVec.New(2,3), afld = CVec.New(4,3), hpad = CVec.New(2,3), hq = CVec.New(2,3), obli = CVec.New(1,1), gun = CVec.New(1,1), silo = CVec.New(2,1) } + +ProductionBuildings = { infantry = NodHand, vehicle = NodAfld, aircraft = NodHpad1 } +ProductionQueue = { infantry = { }, vehicle = { }, aircraft = { } } +NewTeam = { } +InitPatrolTeam = { Pat1, Pat2, Pat3 } +InitPatrolTeamAlive = true +PatrolTeam = { Pat1, Pat2, Pat3 } +BaseDefenseTeam = { Def1, Def2, Def3, Def4, Def5, Def6, Def7, Def8, Def9, Def10 } +StealthTeam = { } +TeamJob = "attack" + +AT1 = { "e1", "e1", "e1", "e1", "e3", "e3", "e3", "e3", "e4", "e4", "e5", "e5" } +AT2 = { "e3", "e3", "e3", "e4", "e4", "vehicle", "ftnk", "ltnk", "ltnk", "aircraft", "heli" } +AT3 = { "vehicle", "mlrs", "bggy", "bggy", "ltnk", "ltnk", "ltnk", "arty" } +AT4 = { "vehicle", "ftnk", "ftnk", "ltnk", "ltnk", "ltnk", "aircraft", "heli", "heli" } +AT5 = { "e5", "e5", "e5", "e5", "vehicle", "bike", "bike", "ltnk", "arty" } +AT6 = { "vehicle", "ftnk", "ltnk", "ltnk", "arty", "arty", "mlrs", "stnk" } +AT7 = { "e1", "e1", "e1", "e5", "e5", "e5", "vehicle", "arty", "bike", "bike", "aircraft", "heli", "heli" } +AT8 = { "e5", "e5", "e5", "vehicle", "bike", "bike", "bike", "stnk", "stnk", "stnk" } +AT9 = { "e5", "e5", "e5", "e3", "e3", "vehicle", "ltnk", "ltnk", "arty" } +AT10 = { "vehicle", "stnk", "stnk", "stnk", "stnk", "stnk" } +AttackTeams = { AT1, AT2, AT3, AT4, AT5, AT6, AT7, AT8, AT9, AT10 } + +PT1 = { "vehicle", "stnk", "stnk" } +PT2 = { "vehicle", "bike", "bike", "bike" } +PT3 = { "aircraft", "heli", "heli" } +PatrolTeams = { PT1, PT2, PT3, PT1, PT2, PT3 } + +DT1 = { "vehicle", "ftnk", "ltnk", "ltnk", "arty", "arty", "aircraft", "heli", "heli", "heli" } +DT2 = { "e5", "e5", "e5", "e3", "e3", "vehicle", "arty", "arty", "bggy", "bggy", "bike" } +DT3 = { "vehicle", "bike", "bike", "bike", "bggy", "bggy", "aircraft", "heli", "heli" } +DefenseTeams = { DT1, DT2, DT3, DT1, DT2, DT3 } + +ST1 = { "vehicle", "stnk", "stnk" } +ST2 = { "vehicle", "stnk", "stnk", "stnk" } +StealthTeams = { ST1, ST2, ST1, ST2 } + +AP1 = { waypoint10.Location, waypoint7.Location, waypoint14.Location } +AP2 = { waypoint5.Location, waypoint7.Location, waypoint14.Location } +AP3 = { waypoint10.Location, waypoint7.Location, waypoint8.Location } +AP4 = { waypoint5.Location, waypoint7.Location, waypoint8.Location } +AP5 = { waypoint10.Location, waypoint7.Location, waypoint9.Location, waypoint2.Location, waypoint8.Location } +AP6 = { waypoint5.Location, waypoint7.Location, waypoint9.Location, waypoint2.Location, waypoint8.Location } +AttackPaths = { AP1, AP2, AP3, AP4, AP5, AP6 } + +PatrolPath = { waypoint13.Location, waypoint6.Location, waypoint2.Location, waypoint9.Location } + +SP1 = { waypoint7.Location, CPos.New(14,29), CPos.New(15,15), CPos.New(49,14), CPos.New(60,38), waypoint4.Location } +SP2 = { waypoint7.Location, CPos.New(14,29), CPos.New(15,15), CPos.New(27,13), waypoint9.Location } +SneakPaths = { SP1 } + +ABP1 = { CPos.New(41,22), CPos.New(60,22), CPos.New(60,43), waypoint4.Location } +ABP2 = { CPos.New(28,27), waypoint10.Location, waypoint11.Location, waypoint12.Location } +ApacheBackdoorPaths = { ABP1, ABP2, ABP1, ABP2 } + +InitAI = function() + Utils.Do(NodBase, function(b) + GuardBuilding(b) + Trigger.OnKilled(b, function(bd) + AddToRebuildQueue(bd) + CheckBase() + end) + end) + RepairNamedActors(Nod, 0.75) + AiProcsNumber = 2 + if ProcUpgrade then + ProcUpg = Actor.Create(ProcUpgrade, true, { Owner = Nod }) + end + AiAnyhqPrerequisite = Actor.Create("AiAnyhqPrerequisite", true, { Owner = Nod }) + AiTmplPrerequisite = Actor.Create("AiTmplPrerequisite", true, { Owner = Nod }) + Trigger.OnKilled(NodHand, function() + ProductionQueue["infantry"] = { } + CheckTeamCompleted() + end) + Trigger.OnKilled(NodAfld, function() + ProductionQueue["vehicle"] = { } + CheckTeamCompleted() + end) + Trigger.OnKilled(NodHpad1, function() + ProductionQueue["aircraft"] = { } + CheckTeamCompleted() + end) + StartGuard() + Trigger.OnAnyKilled(InitPatrolTeam, function() + InitPatrolTeamAlive = false + end) + Trigger.AfterDelay(DateTime.Seconds(ProduceBuildingsDelay), function() + CheckBase() + Trigger.AfterDelay(DateTime.Seconds(NukeDelay), function() + LaunchNuke(true) + end) + Trigger.AfterDelay(DateTime.Seconds(ProduceUnitsDelay), function() + if InitPatrolTeamAlive then + StartPatrol() + else + table.insert(UniqueTeamsQueue, { team = PatrolTeams, job = "patrol" }) + end + if Difficulty == "easy" then + StealthTeams = { ST1 } + end + table.insert(UniqueTeamsQueue, { team = StealthTeams, job = "sneakAttack" }) + Trigger.AfterDelay(DateTime.Seconds(Utils.RandomInteger(90,270) + ProductionCooldownSeconds), ApacheBackdoor) + CheckProduction() + ReduceProdCD() + ApacheGuard(ApacheG, NodHpad2, CPos.New(3,32), CPos.New(25,60), true) + ApacheGuard(OutpostApacheG, NodOutPostHpad, CPos.New(24,18), CPos.New(50,38), true) + end) + end) +end + +-- Ai units logic +ApacheBackdoor = function() + if NodOutPostHpad.IsDead or NodOutPostHpad.Owner ~= Nod then + return + end + if not CheckProduction then + Trigger.AfterDelay(DateTime.Seconds(10), ApacheBackdoor) + end + local path = Utils.Random(ApacheBackdoorPaths) + NodOutPostHpad.Build({ "heli", "heli" }, function(team) + Utils.Do(team, function(h) + h.Stance = "AttackAnything" + for i = 1, #path do + if i < #path then + h.Move(path[i], 2) + else + h.AttackMove(path[i], 2) + end + end + IdleHunt(h) + end) + end) + Trigger.AfterDelay(DateTime.Seconds(Utils.RandomInteger(90,270) + ProductionCooldownSeconds), ApacheBackdoor) +end + +ApacheGuard = function(apache, hpad, topleft, botright, init) + if apache.IsDead then + return + end + if init then + Trigger.OnKilled(apache, function() + Trigger.AfterDelay(DateTime.Seconds(Utils.RandomInteger(120,220)), function() + ApacheRebuild(hpad, topleft, botright) + end) + end) + end + local targets = Map.ActorsInBox(Map.CenterOfCell(topleft), Map.CenterOfCell(botright), function(a) return a.Owner == GDI and a.Type ~= "camera.small" end) + if #targets > 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: