diff --git a/mods/ra/languages/campaign/en.ftl b/mods/ra/languages/campaign/en.ftl index d004f11742..1dee249463 100644 --- a/mods/ra/languages/campaign/en.ftl +++ b/mods/ra/languages/campaign/en.ftl @@ -14,7 +14,7 @@ actor-crate-name = Crate ## campaign-tooltips.yaml actor-technology-center-name = Technology Center -## allies-05a, allies-05b, allies-05c +## allies-05a, allies-05b, allies-05c, negotiations actor-prison-name = Prison ## fall-of-greece-1-personal-war, situation-critical diff --git a/mods/ra/languages/lua/en.ftl b/mods/ra/languages/lua/en.ftl index 0f5a4cc6ac..0597b6c805 100644 --- a/mods/ra/languages/lua/en.ftl +++ b/mods/ra/languages/lua/en.ftl @@ -17,7 +17,7 @@ find-einstein = Find Einstein. einstein-survive = Einstein must survive. protect-civilians = Protect all civilians. -## allies-01, allies-03, allies-05abc, evacuation +## allies-01, allies-03, allies-05abc, evacuation, negotiations tanya-rules-of-engagement = According to the rules of engagement I need your explicit orders to fire, Commander! # allies-01, allies-05abc, evacuation @@ -214,6 +214,20 @@ all-engineers-killed = All engineers were killed. kill-stavros = Kill Stavros. sabotage-facility = Sabotage the facility with our engineers. +## negotiations +all-hostages-killed = All hostages were killed. +church-destroyed = The church was destroyed. +free-hostages = Locate & free the hostages. +get-hostages-to-church = Get hostages to church. +signal-for-reinforcements = Signal for reinforcements at your drop-off point. +guide-thank-you = Thank you! I'll help you get into town! +guide-follow-me = Follow me! +guide-patrol-coming = Uh oh, a patrol is coming this way. +guide-come-this-way = Come this way! Hurry! +guide-safe-to-move = It's safe to move now. Let's go. +hostage-dies-in = Hostage dies in { $time } +keep-all-hostages-alive = Keep all hostages alive. + ## production-disruption, shock-therapy capture-enemy-radar-dome = Capture the enemy radar dome. @@ -274,7 +288,7 @@ destroy-soviet-convoy = Destroy all Soviet convoy trucks. destroy-bridges-slow-convoy = Destroy the nearby bridges to slow the convoys down. -## siberian-conflict-3-wasteland +## siberian-conflict-3-wasteland, negotiations destroy-soviet-units-infrastructure = Destroy all Soviet units and structures. ## situation-critical diff --git a/mods/ra/maps/negotiations/map.bin b/mods/ra/maps/negotiations/map.bin new file mode 100644 index 0000000000..726290f776 Binary files /dev/null and b/mods/ra/maps/negotiations/map.bin differ diff --git a/mods/ra/maps/negotiations/map.png b/mods/ra/maps/negotiations/map.png new file mode 100644 index 0000000000..25ffc8284f Binary files /dev/null and b/mods/ra/maps/negotiations/map.png differ diff --git a/mods/ra/maps/negotiations/map.yaml b/mods/ra/maps/negotiations/map.yaml new file mode 100644 index 0000000000..533949c69d --- /dev/null +++ b/mods/ra/maps/negotiations/map.yaml @@ -0,0 +1,1346 @@ +MapFormat: 12 + +RequiresMod: ra + +Title: Negotiations + +Author: Westwood Studios + +Tileset: TEMPERAT + +MapSize: 128,128 + +Bounds: 30,40,70,50 + +Visibility: MissionSelector + +Categories: Mission + +LockPreview: True + +Players: + PlayerReference@Neutral: + Name: Neutral + OwnsWorld: True + NonCombatant: True + Faction: england + PlayerReference@BadGuy: + Name: BadGuy + Bot: campaign + LockFaction: True + Faction: soviet + Color: FF8800 + Allies: USSR + Enemies: England, GoodGuy, Greece + PlayerReference@USSR: + Name: USSR + Bot: campaign + Faction: soviet + Color: FE1100 + Allies: BadGuy + Enemies: England, GoodGuy, Greece + PlayerReference@GoodGuy: + Name: GoodGuy + Bot: campaign + Faction: allies + Color: ABB7E4 + Allies: England, Germany, Greece + Enemies: BadGuy, USSR + PlayerReference@Greece: + Name: Greece + AllowBots: False + Playable: True + Required: True + LockFaction: True + Faction: allies + LockColor: True + Color: ABB7E4 + LockSpawn: True + LockTeam: True + Allies: England, Germany, GoodGuy + Enemies: BadGuy, USSR + PlayerReference@England: + Name: England + Bot: campaign + Faction: allies + Color: A1EF8C + Allies: Germany, GoodGuy, Greece + PlayerReference@Germany: + Name: Germany + Bot: campaign + Faction: allies + Color: B97A57 + Allies: England, GoodGuy, Greece + +Actors: + Actor0: v18 + Location: 77,54 + Owner: Neutral + Actor1: v17 + Location: 78,54 + Owner: Neutral + Actor2: v18 + Location: 77,55 + Owner: Neutral + Actor3: v17 + Location: 78,55 + Owner: Neutral + Actor4: v16 + Location: 78,56 + Owner: Neutral + Actor5: v17 + Location: 78,57 + Owner: Neutral + Actor6: v17 + Location: 79,62 + Owner: Neutral + Actor7: v17 + Location: 80,62 + Owner: Neutral + Actor8: v18 + Location: 81,62 + Owner: Neutral + Actor9: v17 + Location: 79,63 + Owner: Neutral + Actor10: v18 + Location: 80,63 + Owner: Neutral + Actor11: v16 + Location: 81,63 + Owner: Neutral + Actor12: healcrate + Location: 98,67 + Owner: Neutral + Actor13: tc01 + Location: 94,81 + Owner: Neutral + Actor14: tc02 + Location: 89,80 + Owner: Neutral + Actor15: tc04 + Location: 87,84 + Owner: Neutral + Actor16: tc03 + Location: 94,85 + Owner: Neutral + Actor17: tc02 + Location: 92,85 + Owner: Neutral + Actor18: t16 + Location: 96,82 + Owner: Neutral + Actor19: t15 + Location: 91,81 + Owner: Neutral + Actor20: t11 + Location: 83,82 + Owner: Neutral + Actor21: t08 + Location: 90,79 + Owner: Neutral + Actor22: t07 + Location: 88,78 + Owner: Neutral + Actor23: tc04 + Location: 83,69 + Owner: Neutral + Actor24: tc05 + Location: 97,69 + Owner: Neutral + Actor25: tc04 + Location: 96,68 + Owner: Neutral + Actor26: tc03 + Location: 94,68 + Owner: Neutral + Actor27: tc02 + Location: 85,68 + Owner: Neutral + Actor28: tc01 + Location: 87,67 + Owner: Neutral + Actor29: tc01 + Location: 89,69 + Owner: Neutral + Actor30: tc02 + Location: 91,69 + Owner: Neutral + Actor31: t08 + Location: 93,69 + Owner: Neutral + Actor32: t07 + Location: 87,71 + Owner: Neutral + Actor33: t06 + Location: 95,70 + Owner: Neutral + Actor35: tc05 + Location: 91,75 + Owner: Neutral + Actor36: tc04 + Location: 95,71 + Owner: Neutral + Actor37: tc04 + Location: 90,65 + Owner: Neutral + Actor38: tc01 + Location: 91,66 + Owner: Neutral + Actor39: t16 + Location: 94,65 + Owner: Neutral + Actor40: t14 + Location: 96,66 + Owner: Neutral + Actor41: t12 + Location: 87,64 + Owner: Neutral + Actor42: t11 + Location: 90,62 + Owner: Neutral + Actor43: t10 + Location: 95,63 + Owner: Neutral + Actor44: tc02 + Location: 98,64 + Owner: Neutral + Actor45: tc04 + Location: 98,62 + Owner: Neutral + Actor46: tc01 + Location: 97,61 + Owner: Neutral + Actor47: tc02 + Location: 95,61 + Owner: Neutral + Actor48: t15 + Location: 94,60 + Owner: Neutral + Actor49: tc02 + Location: 98,56 + Owner: Neutral + Actor50: tc01 + Location: 96,56 + Owner: Neutral + Actor51: tc05 + Location: 93,57 + Owner: Neutral + Actor52: t16 + Location: 92,59 + Owner: Neutral + Actor53: t15 + Location: 98,57 + Owner: Neutral + Actor54: tc03 + Location: 89,59 + Owner: Neutral + Actor55: tc02 + Location: 87,60 + Owner: Neutral + Actor56: t16 + Location: 85,62 + Owner: Neutral + Actor57: tc05 + Location: 86,55 + Owner: Neutral + Actor58: tc02 + Location: 93,51 + Owner: Neutral + Actor59: tc01 + Location: 92,53 + Owner: Neutral + Actor60: t14 + Location: 91,54 + Owner: Neutral + Actor61: t08 + Location: 92,45 + Owner: Neutral + Actor62: t12 + Location: 97,42 + Owner: Neutral + Actor63: tc02 + Location: 86,44 + Owner: Neutral + Actor64: tc03 + Location: 97,45 + Owner: Neutral + Actor65: t07 + Location: 96,50 + Owner: Neutral + Actor66: t06 + Location: 82,49 + Owner: Neutral + Actor68: tc01 + Location: 81,44 + Owner: Neutral + Actor69: tc04 + Location: 68,45 + Owner: Neutral + Actor70: tc01 + Location: 77,50 + Owner: Neutral + Actor71: tc02 + Location: 69,48 + Owner: Neutral + Actor72: t16 + Location: 72,49 + Owner: Neutral + Actor73: tc01 + Location: 70,42 + Owner: Neutral + Actor74: t16 + Location: 81,42 + Owner: Neutral + Actor75: t02 + Location: 86,41 + Owner: Neutral + Actor76: tc03 + Location: 33,85 + Owner: Neutral + Actor77: tc04 + Location: 42,78 + Owner: Neutral + Actor78: tc02 + Location: 35,80 + Owner: Neutral + Actor79: t14 + Location: 40,86 + Owner: Neutral + Actor80: t13 + Location: 44,83 + Owner: Neutral + Actor82: mine + Location: 34,63 + Owner: Neutral + Actor83: t01 + Location: 37,67 + Owner: Neutral + Actor84: t03 + Location: 36,61 + Owner: Neutral + Actor85: t05 + Location: 33,66 + Owner: Neutral + Actor86: t06 + Location: 39,61 + Owner: Neutral + Actor87: t14 + Location: 30,62 + Owner: Neutral + Actor88: tc02 + Location: 46,45 + Owner: Neutral + Actor89: t17 + Location: 48,47 + Owner: Neutral + Actor90: tc04 + Location: 45,42 + Owner: Neutral + Actor91: tc02 + Location: 58,72 + Owner: Neutral + Actor92: tc02 + Location: 81,67 + Owner: Neutral + Actor93: tc01 + Location: 81,52 + Owner: Neutral + Actor94: t07 + Location: 81,51 + Owner: Neutral + Actor95: tc04 + Location: 74,79 + Owner: Neutral + Actor96: tc03 + Location: 75,81 + Owner: Neutral + Actor97: tc01 + Location: 80,83 + Owner: Neutral + Actor98: tc04 + Location: 67,38 + Owner: Neutral + Actor99: tc04 + Location: 59,88 + Owner: Neutral + Actor100: tc03 + Location: 50,88 + Owner: Neutral + Actor101: tc01 + Location: 54,74 + Owner: Neutral + Actor102: tc05 + Location: 61,82 + Owner: Neutral + Actor103: tc02 + Location: 53,82 + Owner: Neutral + Actor104: tc03 + Location: 49,66 + Owner: Neutral + Actor105: tc05 + Location: 50,70 + Owner: Neutral + Actor106: t13 + Location: 66,74 + Owner: Neutral + Actor107: t13 + Location: 73,70 + Owner: Neutral + Actor108: tc04 + Location: 95,39 + Owner: Neutral + Actor109: tc01 + Location: 98,39 + Owner: Neutral + Actor110: tc05 + Location: 51,44 + Owner: Neutral + Actor111: tc04 + Location: 51,46 + Owner: Neutral + Actor112: tc01 + Location: 63,44 + Owner: Neutral + Actor113: t16 + Location: 65,46 + Owner: Neutral + Actor114: t07 + Location: 59,63 + Owner: Neutral + Actor115: t08 + Location: 58,58 + Owner: Neutral + Actor116: t05 + Location: 68,54 + Owner: Neutral + Actor117: t03 + Location: 61,51 + Owner: Neutral + Actor118: t01 + Location: 66,65 + Owner: Neutral + Actor119: tc03 + Location: 55,69 + Owner: Neutral + Actor120: tc04 + Location: 54,60 + Owner: Neutral + Actor121: tc03 + Location: 75,68 + Owner: Neutral + Actor122: tc01 + Location: 76,66 + Owner: Neutral + Actor123: t16 + Location: 83,57 + Owner: Neutral + Actor124: t14 + Location: 63,61 + Owner: Neutral + Actor125: tc03 + Location: 73,58 + Owner: Neutral + Actor126: t17 + Location: 48,56 + Owner: Neutral + Actor127: t01 + Location: 98,71 + Owner: Neutral + Actor128: tc04 + Location: 82,74 + Owner: Neutral + Actor129: tc02 + Location: 59,75 + Owner: Neutral + Actor130: t16 + Location: 58,75 + Owner: Neutral + Actor131: t15 + Location: 58,78 + Owner: Neutral + Actor132: t01 + Location: 71,60 + Owner: Neutral + Actor133: tc05 + Location: 76,58 + Owner: Neutral + Actor134: tc02 + Location: 74,62 + Owner: Neutral + Actor135: t01 + Location: 75,52 + Owner: Neutral + Actor136: t01 + Location: 63,70 + Owner: Neutral + Actor137: tc05 + Location: 72,87 + Owner: Neutral + Actor138: tc02 + Location: 77,88 + Owner: Neutral + Actor139: t08 + Location: 79,86 + Owner: Neutral + Actor140: tc05 + Location: 76,75 + Owner: Neutral + Actor141: t16 + Location: 79,73 + Owner: Neutral + Actor142: t17 + Location: 79,74 + Owner: Neutral + Actor143: t16 + Location: 78,77 + Owner: Neutral + Actor144: t16 + Location: 66,78 + Owner: Neutral + Church: v01 + Location: 91,83 + Owner: Germany + Health: 49 + Prison: miss + Location: 58,48 + Owner: Germany + Health: 51 + Actor147: v08 + Location: 41,44 + Owner: Germany + Actor148: brl3 + Location: 46,40 + Owner: USSR + Actor149: barl + Location: 45,41 + Owner: USSR + NorthBeachPump1: v19 + Location: 46,41 + Owner: Neutral + Actor151: brl3 + Location: 44,41 + Owner: USSR + Actor152: barl + Location: 44,42 + Owner: USSR + Actor153: barl + Location: 46,42 + Owner: USSR + Actor154: barl + Location: 43,42 + Owner: USSR + NorthBeachPump2: v19 + Location: 44,43 + Owner: Neutral + Actor156: brl3 + Location: 43,43 + Owner: USSR + SouthBeachPump2: v19 + Location: 58,83 + Owner: Neutral + GuardHouse: v02 + Location: 60,54 + Owner: Germany + Actor159: v04 + Location: 56,57 + Owner: Germany + Actor160: v05 + Location: 69,55 + Owner: Germany + Actor161: v06 + Location: 74,56 + Owner: Germany + Actor162: v07 + Location: 75,60 + Owner: Germany + Actor163: v08 + Location: 61,61 + Owner: Germany + Actor164: v11 + Location: 77,63 + Owner: Germany + Actor165: v07 + Location: 52,56 + Owner: Germany + Actor166: v02 + Location: 80,64 + Owner: Germany + DemoHouse: v05 + Location: 59,69 + Owner: Germany + PrisonBarrel3: brl3 + Location: 60,47 + Owner: USSR + PrisonBarrel2: barl + Location: 59,47 + Owner: USSR + Health: 9 + PrisonPump: v19 + Location: 58,47 + Owner: Neutral + PrisonBarrel4: brl3 + Location: 61,48 + Owner: USSR + PrisonBarrel1: barl + Location: 57,46 + Owner: USSR + Actor174: barl + Location: 43,44 + Owner: USSR + DemoBarrel4: brl3 + Location: 65,67 + Owner: USSR + DemoBarrel7: barl + Location: 65,68 + Owner: USSR + DemoPump3: v19 + Location: 64,68 + Owner: Neutral + DemoBarrel6: brl3 + Location: 63,68 + Owner: USSR + DemoBarrel8: barl + Location: 62,69 + Owner: USSR + DemoPump2: v19 + Location: 62,68 + Owner: Neutral + DemoBarrel5: brl3 + Location: 61,68 + Owner: USSR + DemoBarrel2: brl3 + Location: 66,65 + Owner: USSR + DemoPump1: v19 + Location: 65,66 + Owner: Neutral + DemoBarrel3: barl + Location: 64,66 + Owner: USSR + DemoBarrel1: barl + Location: 65,65 + Owner: USSR + Actor186: brl3 + Location: 58,82 + Owner: USSR + Actor187: barl + Location: 59,82 + Owner: USSR + Actor188: barl + Location: 59,81 + Owner: USSR + SouthBeachPump1: v19 + Location: 58,81 + Owner: Neutral + Actor190: brl3 + Location: 59,80 + Owner: USSR + Actor191: barl + Location: 57,79 + Owner: USSR + Actor192: barl + Location: 58,80 + Owner: USSR + Actor193: barl + Location: 57,81 + Owner: USSR + Actor194: brl3 + Location: 57,83 + Owner: USSR + ForwardTech: stek + Location: 53,63 + Owner: BadGuy + ForwardCommand: fcom + Location: 53,66 + Owner: USSR + Actor196: ftur + Location: 56,65 + Owner: USSR + LakePump1: v19 + Location: 64,57 + Owner: Neutral + LakePump2: v19 + Location: 64,59 + Owner: Neutral + Actor199: brl3 + Location: 65,56 + Owner: USSR + Actor200: barl + Location: 64,56 + Owner: USSR + Actor201: brl3 + Location: 63,57 + Owner: USSR + Actor202: barl + Location: 64,58 + Owner: USSR + Actor203: brl3 + Location: 63,59 + Owner: USSR + Actor204: barl + Location: 64,60 + Owner: USSR + Actor205: v19 + Location: 87,50 + Owner: Neutral + Actor206: brl3 + Location: 86,51 + Owner: USSR + Actor207: barl + Location: 86,52 + Owner: USSR + Actor208: brl3 + Location: 85,53 + Owner: USSR + Actor209: barl + Location: 85,54 + Owner: USSR + Actor210: brl3 + Location: 84,54 + Owner: USSR + Actor211: brl3 + Location: 83,55 + Owner: USSR + Actor212: v19 + Location: 82,54 + Owner: Neutral + Actor213: v19 + Location: 83,53 + Owner: Neutral + Actor214: barl + Location: 82,55 + Owner: USSR + Actor215: barl + Location: 81,56 + Owner: USSR + Actor216: barl + Location: 81,57 + Owner: USSR + Actor217: brl3 + Location: 80,58 + Owner: USSR + Actor218: barl + Location: 80,59 + Owner: USSR + Actor219: brl3 + Location: 74,45 + Owner: USSR + Actor220: barl + Location: 73,45 + Owner: USSR + Actor221: brl3 + Location: 72,46 + Owner: USSR + Actor222: barl + Location: 71,46 + Owner: USSR + Actor223: brl3 + Location: 71,47 + Owner: USSR + Actor224: barl + Location: 72,48 + Owner: USSR + RoadTurretWest: ftur + Location: 42,65 + Owner: USSR + RoadTurretEast: ftur + Location: 45,65 + Owner: USSR + ForwardPower: apwr + Location: 56,66 + Owner: USSR + SoutheastTurret: ftur + Location: 76,84 + Owner: USSR + WestSam: sam + Location: 66,80 + Owner: USSR + Facing: 640 + EastSam: sam + Location: 75,77 + Owner: USSR + Facing: 384 + BlockerTank: 3tnk + Location: 91,41 + Faction: england + Facing: 640 + Owner: USSR + Actor232: 3tnk + Location: 43,60 + Facing: 512 + Owner: BadGuy + Actor233: 3tnk + Location: 42,60 + Facing: 512 + Owner: BadGuy + SovietDemoTruck: dtrk + Location: 63,65 + Faction: england + Health: 56 + Facing: 128 + Owner: BadGuy + Actor235: 3tnk + Location: 43,54 + Facing: 640 + Owner: BadGuy + TechTank: 3tnk + Location: 57,64 + Owner: BadGuy + Facing: 896 + FarmGuard3: 3tnk + Location: 76,62 + Facing: 128 + Owner: USSR + ForwardCliffGuard1: e3 + Location: 49,69 + SubCell: 1 + Owner: BadGuy + Stance: Defend + Actor239: e2 + Location: 41,59 + SubCell: 4 + Facing: 640 + Owner: BadGuy + Actor240: e1 + Location: 43,58 + SubCell: 0 + Facing: 768 + Owner: BadGuy + Actor241: e1 + Location: 41,58 + SubCell: 3 + Facing: 768 + Owner: BadGuy + Actor242: e2 + Location: 43,59 + SubCell: 4 + Facing: 768 + Owner: BadGuy + Actor243: e2 + Location: 42,59 + SubCell: 2 + Facing: 128 + Owner: BadGuy + Actor244: e1 + Location: 40,60 + SubCell: 1 + Facing: 256 + Owner: BadGuy + Hostage1: c2 + Location: 53,50 + Owner: Neutral + Facing: 512 + SubCell: 1 + Hostage2: c3 + Location: 56,50 + Owner: Neutral + SubCell: 1 + Facing: 768 + Hostage3: c4 + Location: 55,51 + Owner: Neutral + Facing: 640 + SubCell: 0 + Hostage4: c5 + Location: 55,51 + Owner: Neutral + SubCell: 1 + Facing: 128 + Hostage5: c6 + Location: 54,50 + Owner: Neutral + SubCell: 4 + Facing: 512 + General: gnrl + Location: 59,50 + Owner: USSR + SubCell: 0 + Facing: 512 + GuideRifleEast: e1 + Location: 86,77 + SubCell: 2 + Faction: england + Facing: 256 + Owner: USSR + GuideRifleWest: e1 + Location: 84,77 + SubCell: 3 + Faction: england + Facing: 896 + Owner: USSR + ForestPatroller3: e1 + Location: 93,47 + Owner: USSR + SubCell: 2 + ForestPatroller2: e1 + Location: 92,47 + Owner: USSR + SubCell: 4 + Facing: 256 + ForestPatroller5: e2 + Location: 92,48 + Owner: USSR + SubCell: 4 + ForestPatroller6: e2 + Location: 93,48 + Owner: USSR + SubCell: 0 + Facing: 256 + ForestPatroller4: dog + Location: 91,48 + Owner: USSR + SubCell: 4 + Facing: 384 + ForestPatroller7: dog + Location: 94,48 + Owner: USSR + SubCell: 1 + Facing: 896 + Executioner: e1 + Location: 57,49 + Owner: BadGuy + SubCell: 0 + Facing: 256 + Stance: Defend + BlockerGuard1: e4 + Location: 91,40 + SubCell: 3 + Faction: england + Facing: 384 + Owner: USSR + BlockerGuard2: e4 + Location: 90,41 + SubCell: 0 + Faction: england + Facing: 896 + Owner: USSR + DemoFlamer2: e4 + Location: 62,67 + SubCell: 3 + Owner: BadGuy + DemoFlamer1: e4 + Location: 63,66 + SubCell: 4 + Owner: BadGuy + Facing: 512 + StartSoldier: e1 + Location: 40,81 + SubCell: 4 + Faction: england + Owner: USSR + SamFenceGuard: e1 + Location: 71,67 + SubCell: 0 + Owner: USSR + Facing: 640 + SouthBeachGuard2: e1 + Location: 56,83 + Owner: USSR + SubCell: 4 + Facing: 384 + SouthBeachGuard1: e1 + Location: 60,82 + Owner: USSR + SubCell: 3 + Facing: 768 + SouthBeachGuard3: dog.areaguard + Location: 58,84 + Owner: USSR + SubCell: 3 + Facing: 384 + SouthBeachGuard4: dog.areaguard + Location: 59,84 + Owner: USSR + SubCell: 0 + Facing: 640 + ForwardCliffGuard2: e3 + Location: 53,72 + SubCell: 4 + Owner: BadGuy + Facing: 384 + FarmGuard4: e1 + Location: 77,62 + SubCell: 2 + Owner: USSR + Actor272: e4 + Location: 46,52 + SubCell: 4 + Facing: 640 + Owner: BadGuy + Actor273: e4 + Location: 45,56 + SubCell: 4 + Facing: 640 + Owner: BadGuy + FarmGuard2: e4 + Location: 73,61 + SubCell: 0 + Owner: USSR + FarmGuard1: e4 + Location: 74,60 + SubCell: 3 + Owner: USSR + ForwardRifle1: e1 + Location: 52,65 + SubCell: 0 + Owner: BadGuy + Facing: 256 + ForwardRifle2: e1 + Location: 53,68 + SubCell: 2 + Owner: BadGuy + Facing: 384 + ForwardGrenadier: e2 + Location: 57,68 + SubCell: 1 + Owner: BadGuy + Facing: 640 + RoadTurretRifle1: e1 + Location: 43,67 + Owner: USSR + SubCell: 0 + Facing: 512 + RoadTurretRifle2: e1 + Location: 44,67 + Owner: USSR + SubCell: 0 + Facing: 640 + ForestPatroller8: shok + Location: 94,49 + Owner: USSR + SubCell: 1 + Facing: 640 + ForestPatroller1: shok + Location: 91,47 + Owner: USSR + SubCell: 4 + Facing: 256 + SoutheastTurretRifle3: e1 + Location: 76,86 + Owner: USSR + SubCell: 1 + Facing: 128 + SoutheastTurretRifle1: e1 + Location: 75,84 + Owner: USSR + SubCell: 1 + Facing: 384 + SoutheastTurretRifle2: e1 + Location: 75,85 + Owner: USSR + SubCell: 0 + Facing: 640 + WestRoadEntry: waypoint + Location: 30,72 + Owner: Neutral + WestRoadRally: waypoint + Location: 41,74 + Owner: Neutral + RoadTurretReveal: waypoint + Location: 44,63 + Owner: Neutral + SouthBeachReveal: waypoint + Location: 59,83 + Owner: Neutral + GuideHouseReveal: waypoint + Location: 85,80 + Owner: Neutral + SouthRiver: waypoint + Location: 90,72 + Owner: Neutral + ForestSouthwest: waypoint + Location: 88,64 + Owner: Neutral + ForestCenter: waypoint + Location: 91,61 + Owner: Neutral + ForestHideout: waypoint + Location: 98,60 + Owner: Neutral + ForestNorthwest: waypoint + Location: 89,58 + Owner: Neutral + ForestPatrolStart: waypoint + Location: 93,46 + Owner: Neutral + VillageBridgeNortheast: waypoint + Location: 77,48 + Owner: Neutral + VillageNortheast: waypoint + Location: 71,55 + Owner: Neutral + PrisonReveal: waypoint + Location: 60,51 + Owner: Neutral + BadGuyRally: waypoint + Location: 43,53 + Owner: Neutral + GeneralRally: waypoint + Location: 42,44 + Owner: Neutral + NorthFlare: waypoint + Location: 41,43 + Owner: Neutral + SamFenceReveal: waypoint + Location: 70,70 + Owner: Neutral + VillagePatrolEntry: waypoint + Location: 75,60 + Owner: Neutral + PrisonIntersection: waypoint + Location: 66,51 + Owner: Neutral + GuardHouseSpawn: waypoint + Location: 60,55 + Owner: Neutral + VillageNorthwest: waypoint + Location: 63,54 + Owner: Neutral + BuilderRally: waypoint + Location: 37,53 + Owner: Neutral + NorthWaterEntry: waypoint + Location: 34,40 + Owner: Neutral + VillageSouthwest: waypoint + Location: 61,64 + Owner: Neutral + VillageSoutheast: waypoint + Location: 72,64 + Owner: Neutral + NorthChinookUnload: waypoint + Location: 73,46 + Owner: Neutral + VillageBridgeCenter: waypoint + Location: 74,52 + Owner: Neutral + LongbowEntry: waypoint + Location: 76,91 + Owner: Neutral + SouthChinookProximity: waypoint + Location: 71,84 + Owner: Neutral + SovietDemoReveal: waypoint + Location: 64,64 + Owner: Neutral + ChurchRally: waypoint + Owner: Neutral + Location: 90,85 + Actor325: fenc + Owner: USSR + Location: 57,48 + Actor326: fenc + Owner: USSR + Location: 56,48 + Actor327: fenc + Owner: USSR + Location: 55,48 + Actor328: fenc + Owner: USSR + Location: 54,48 + Actor329: fenc + Owner: USSR + Location: 53,48 + Actor330: fenc + Owner: USSR + Location: 52,48 + Actor331: fenc + Owner: USSR + Location: 52,49 + Actor332: fenc + Owner: USSR + Location: 52,50 + Actor333: fenc + Owner: USSR + Location: 52,51 + Actor334: fenc + Owner: USSR + Location: 52,52 + Actor335: fenc + Owner: USSR + Location: 53,52 + Actor336: fenc + Owner: USSR + Location: 54,52 + Actor337: fenc + Owner: USSR + Location: 55,52 + Actor338: fenc + Owner: USSR + Location: 56,52 + Actor339: fenc + Owner: USSR + Location: 57,52 + Actor340: fenc + Owner: USSR + Location: 57,51 + Actor341: fenc + Owner: USSR + Location: 57,50 + Actor342: fenc + Owner: USSR + Location: 58,51 + Actor343: fenc + Owner: USSR + Location: 73,65 + Actor344: fenc + Owner: USSR + Location: 72,65 + Actor345: fenc + Owner: USSR + Location: 71,65 + Actor346: fenc + Owner: USSR + Location: 70,65 + Actor347: fenc + Owner: USSR + Location: 69,65 + Actor348: fenc + Owner: USSR + Location: 68,65 + Actor349: fenc + Owner: USSR + Location: 41,66 + Actor350: fenc + Owner: USSR + Location: 42,66 + Actor351: fenc + Owner: USSR + Location: 43,65 + Actor352: fenc + Owner: USSR + Location: 44,65 + Actor353: fenc + Owner: USSR + Location: 45,66 + Actor354: fenc + Owner: USSR + Location: 46,66 + Actor355: fenc + Owner: USSR + Location: 77,82 + Actor356: fenc + Owner: USSR + Location: 77,83 + Actor357: fenc + Owner: USSR + Location: 77,84 + Actor358: fenc + Owner: USSR + Location: 77,86 + Actor359: fenc + Owner: USSR + Location: 77,87 + Actor360: fenc + Owner: USSR + Location: 76,87 + Actor361: fenc + Owner: USSR + Location: 76,88 + Actor362: fenc + Owner: USSR + Location: 76,89 + HostageCenter: waypoint + Owner: Neutral + Location: 54,50 + Actor67: tc05 + Location: 75,44 + Owner: Neutral + TanyaDrop: waypoint + Location: 40,82 + Owner: Neutral + ForestNorth: waypoint + Location: 91,55 + Owner: Neutral + GuideBarrelGoal: waypoint + Location: 58,46 + Owner: Neutral + GuardHouseExit: waypoint + Owner: Neutral + Location: 59,56 + LongbowExit: waypoint + Location: 30,86 + Owner: Neutral + BlockerReturnProximity: waypoint + Owner: Neutral + Location: 92,51 + GuideHouse: v08 + Location: 85,76 + Owner: Germany + Health: 53 + SovietSouthBeachCamera: camera + Location: 56,85 + Owner: USSR + LongbowGoal1: waypoint + Owner: Neutral + Location: 76,86 + LongbowGoal2: waypoint + Owner: Neutral + Location: 78,86 + ForestSafetyCheck: waypoint + Owner: Neutral + Location: 89,66 + Actor81: t12 + Location: 37,77 + Owner: Neutral + ChronoEntryWest2: waypoint + Owner: Neutral + Location: 46,64 + ChronoEntryWest1: waypoint + Owner: Neutral + Location: 45,64 + ChronoEntryEast2: waypoint + Owner: Neutral + Location: 72,70 + ChronoEntryEast1: waypoint + Owner: Neutral + Location: 68,70 + LongbowGoal3: waypoint + Owner: Neutral + Location: 79,84 + SamRocketEntry: waypoint + Owner: Neutral + Location: 66,78 + BuilderBeach: waypoint + Owner: Neutral + Location: 33,49 + BeachExplosionReveal: waypoint + Owner: Neutral + Location: 58,82 + SovietStartCamera: camera + Owner: USSR + Location: 41,78 + SouthChinookExit: waypoint + Owner: Neutral + Location: 99,86 + GuideHouseTree: t05 + Location: 86,74 + Owner: Neutral + LakeExplosionReveal: waypoint + Owner: Neutral + Location: 64,58 + NorthChinookEntry: waypoint + Owner: Neutral + Location: 74,33 + SouthChinookEntry: waypoint + Owner: Neutral + Location: 59,86 + NorthChinookRally: waypoint + Location: 73,47 + Owner: Neutral + GuideBackdoorGoal: waypoint + Owner: Neutral + Location: 58,41 + NorthEdgeGuardEntry: waypoint + Owner: Neutral + Location: 67,42 + +Rules: ra|rules/campaign-rules.yaml, ra|rules/campaign-palettes.yaml, ra|rules/campaign-tooltips.yaml, rules.yaml + +Translations: ra|languages/lua/en.ftl, ra|languages/campaign/en.ftl + +Weapons: weapons.yaml + +Notifications: notifications.yaml diff --git a/mods/ra/maps/negotiations/negotiations-ai.lua b/mods/ra/maps/negotiations/negotiations-ai.lua new file mode 100644 index 0000000000..14d08b59c8 --- /dev/null +++ b/mods/ra/maps/negotiations/negotiations-ai.lua @@ -0,0 +1,347 @@ +--[[ + 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. +]] +BuildIntervals = +{ + easy = DateTime.Seconds(25), + normal = DateTime.Seconds(15), + hard = DateTime.Seconds(10) +} +StructureRebuildGrace = BuildIntervals[Difficulty] + +InfantryTeams = +{ + { + types = { "e1", "e1" } + }, + { + types = { "e2", "e2" } + }, + { + types = { "e4", "e4" }, + requirements = { "ftur" } + }, + { + types = { "e1", "e1", "e2", "e2" }, + onBuilt = AttackFromChurch + }, + { + types = { "shok", "shok" }, + requirements = { "ai.hard", "techcenter", "tsla" } + } +} +VehicleTeams = +{ + { + types = { "3tnk" }, + requirements = { "fix" } + }, + { + types = { "ttnk", "3tnk" }, + requirements = { "techcenter", "fix", "tsla" } + } +} +HarvesterTeam = +{ + types = { "harv" }, + buildTime = Actor.BuildTime("harv"), + onBuilt = function(actors) + Utils.Do(actors, function(actor) + actor.FindResources() + end) + end +} + +PrepareBadGuy = function() + BadGuy.Cash = 100000 + if Difficulty == "hard" then + SpawnMiscActor("ai.hard", BadGuy) + end + PrepareTeamBuildTimes(InfantryTeams) + PrepareTeamBuildTimes(VehicleTeams) + + SetBotStructures( + { type = "powr", exists = false, shape = "2x3", location = CPos.New(40, 51) }, + { type = "proc", exists = false, shape = "3x4", location = CPos.New(33, 52) }, + { type = "barr", exists = false, shape = "2x3", location = CPos.New(40, 55), onBuilt = BuildInfantry, onKilled = CheckNewBase, rebuildSkipped = true }, + { type = "powr", exists = false, shape = "2x3", location = CPos.New(30, 50) }, + { type = "ftur", exists = false, shape = "1x1", location = CPos.New(39, 58), onKilled = CheckNewBase }, + { type = "ftur", exists = false, shape = "1x1", location = CPos.New(43, 57), onKilled = CheckNewBase }, + { type = "powr", exists = false, shape = "2x3", location = CPos.New(30, 53) }, + { type = "weap", exists = false, shape = "3x3", location = CPos.New(33, 56), onBuilt = BuildVehicles, onKilled = CheckNewBase, rebuildSkipped = true }, + -- Tesla coil is originally built before factory. + -- For convention, coil is swapped (and service depot added). + { type = "tsla", exists = false, shape = "1x1", location = CPos.New(37, 57), onKilled = CheckNewBase }, + { type = "fix", exists = false, shape = "3x3", location = CPos.New(30, 56) } + ) + + Trigger.OnEnteredFootprint( { BuilderRally.Location }, function(actor, id) + if actor.Type ~= "fact" then + return + end + + Trigger.RemoveFootprintTrigger(id) + ActivateBot() + Trigger.OnKilled(actor, CheckNewBase) + end) +end + +CheckNewBase = function() + if IsNewBaseOkay() then + return + end + PrepareTechSale() + + if Difficulty == "hard" then + StartFireSale(BadGuy) + end +end + +IsNewBaseOkay = function() + local types = { "fact", "barr", "ftur", "weap", "tsla" } + + return Utils.Any(types, function(type) + return BadGuy.HasPrerequisites({ type }) + end) +end + +PrepareTechSale = function() + if ForwardTech.IsDead or ForwardTech.Owner == USSR then + return + end + + ForwardTech.Owner = USSR + if ForwardCommand.IsDead then + StartFireSale(USSR) + end +end + +ActivateBot = function() + CheckStructures(BadGuy, DateTime.Seconds(5)) + BadGuy.GrantCondition("ai-enabled") +end + +PrepareTeamBuildTimes = function(teamCollection) + Utils.Do(teamCollection, function(team) + local time = 0 + + Utils.Do(team.types, function(type) + time = time + Actor.BuildTime(type) + end) + + team.buildTime = time + end) +end + +SelectTeam = function(teamCollection) + local teams = Utils.Shuffle(teamCollection) + + for i = 1, #teams do + local team = teams[i] + if AreTeamRequirementsMet(team.requirements) then + return team + end + end + + return { } +end + +AreTeamRequirementsMet = function(requirements) + if not requirements then + return true + end + + return BadGuy.HasPrerequisites(requirements) +end + +BuildTeam = function(player, team, nextBuild) + if not team.types then + -- No teams had their prerequisites. + Trigger.AfterDelay(BuildIntervals[Difficulty], nextBuild) + return + end + + local onBuilt = team.onBuilt or GroupIdleHunt + player.Build(team.types, onBuilt) + Trigger.AfterDelay(team.buildTime, nextBuild) +end + +BuildInfantry = function() + if #BadGuy.GetActorsByType("barr") < 1 then + return + end + + local team = SelectTeam(InfantryTeams) + + BuildTeam(BadGuy, team, function() + Trigger.AfterDelay(BuildIntervals[Difficulty], BuildInfantry) + end) +end + +BuildVehicles = function() + if #BadGuy.GetActorsByType("weap") < 1 then + return + end + + local team = nil + if IsHarvesterNeeded(BadGuy) then + team = HarvesterTeam + end + team = team or SelectTeam(VehicleTeams) + + BuildTeam(BadGuy, team, function() + Trigger.AfterDelay(BuildIntervals[Difficulty], BuildVehicles) + end) +end + +IsHarvesterNeeded = function(player) + return player.HasPrerequisites({ "proc" }) and #player.GetActorsByType("harv") < 1 +end + +AttackFromChurch = function(actors) + local path = + { + BadGuyRally.Location, + ForestPatrolStart.Location, + GuideHouseReveal.Location, + ChurchRally.Location, + GuideHouseReveal.Location + } + GroupTightPatrol(actors, path, false, 0, GroupIdleHunt) +end + +--[[ + These kind of work like the original [BASE] and [STRUCTURES] .INI sections. + Check a list every so often and (re)build structures that are missing, + in order, and if circumstances allow for it. +]] +SetBotStructures = function(...) + local structures = { ... } + + Utils.Do(structures, function(structure) + structure.cost = Actor.Cost(structure.type) + structure.buildTime = Actor.BuildTime(structure.type) + structure.position = WPos.New(structure.location.X * 1024, structure.location.Y * 1024, 0) + end) + + BadGuyStructures = structures +end + +CheckStructures = function(player, interval, rushed) + if #player.GetActorsByType("fact") < 1 then + return + end + + local buildingScheduled = false + + for _, structure in pairs(BadGuyStructures) do + if not structure.exists then + buildingScheduled = true + ScheduleStructure(player, structure, rushed or structure.buildTime, interval) + break + end + end + + if buildingScheduled then + return + end + + Trigger.AfterDelay(interval, function() + CheckStructures(player, interval) + end) +end + +ScheduleStructure = function(player, structure, buildTime, interval) + Trigger.AfterDelay(buildTime, function() + if #player.GetActorsByType("fact") < 1 then + return + end + + local success, blocked = BuildStructure(player, structure) + if not success and blocked then + CheckStructures(player, interval, DateTime.Seconds(2)) + return + end + + Trigger.AfterDelay(interval, function() + CheckStructures(player, interval) + end) + end) +end + +AddRebuildTrigger = function(actor, structure) + if structure.rebuildSkipped then + return + end + + Trigger.OnRemovedFromWorld(actor, function() + -- Period before the bot can consider replacement. + Trigger.AfterDelay(StructureRebuildGrace, function() + structure.exists = false + end) + end) +end + +BuildStructure = function(player, structure) + if structure.cost > player.Cash then + return false + end + + local blocked = IsStructureAreaBlocked(player, structure.position, structure.shape) + if blocked then + return false, "blocked" + end + + local actor = Actor.Create(structure.type, true, { Owner = player, Location = structure.location }) + structure.exists = true + player.Cash = player.Cash - structure.cost + AddRebuildTrigger(actor, structure) + + if structure.onKilled then + Trigger.OnKilled(actor, structure.onKilled) + end + + if structure.onBuilt then + -- Build() will not work properly on producers if called immediately. + Trigger.AfterDelay(1, function() + structure.onBuilt(actor) + end) + end + + return actor +end + +StructureFootprints = +{ + ["1x1"] = WVec.New(1 * 1024, 1 * 1024, 0), + ["2x3"] = WVec.New(2 * 1024, 3 * 1024, 0), + ["3x3"] = WVec.New(3 * 1024, 3 * 1024, 0), + ["3x4"] = WVec.New(3 * 1024, 4 * 1024, 0), +} + +IsStructureAreaBlocked = function(player, position, shape) + local foot = StructureFootprints[shape] + local blockers = Map.ActorsInBox(position, position + foot, function(actor) + return actor.CenterPosition.Z == 0 and actor.HasProperty("Health") + end) + + if #blockers == 0 then + return false + end + + ScatterBlockers(player, blockers) + return true +end + +ScatterBlockers = function(player, actors) + Utils.Do(actors, function(actor) + if actor.IsIdle and actor.Owner == player and actor.HasProperty("Scatter") then + actor.Scatter() + end + end) +end diff --git a/mods/ra/maps/negotiations/negotiations-reinforcements.lua b/mods/ra/maps/negotiations/negotiations-reinforcements.lua new file mode 100644 index 0000000000..3d422c00c7 --- /dev/null +++ b/mods/ra/maps/negotiations/negotiations-reinforcements.lua @@ -0,0 +1,397 @@ +--[[ + 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. +]] +ReinforceAllies = function() + Media.PlaySpeechNotification(Greece, "ReinforcementsArrived") + local proxy = SpawnMiscActor("paradrop.allies", GoodGuy, TanyaDrop.Location, DateTime.Seconds(1)) + local planes = proxy.TargetParatroopers(TanyaDrop.CenterPosition, Angle.SouthEast) + + Utils.Do(planes, function(plane) + Trigger.OnPassengerExited(plane, function(_, passenger) + passenger.Owner = Greece + end) + + Trigger.OnAllKilled(plane.Passengers, CheckAlliedDestruction) + end) + + local demoTypes = { "2tnk", "2tnk", "2tnk", "dtrk" } + local artilleryTypes = {"2tnk", "2tnk", "arty", "arty", "mech", "mech", "mech" } + local groundPath = { WestRoadEntry.Location, WestRoadRally.Location } + + local demoTeam = Reinforcements.Reinforce(Greece, demoTypes, groundPath) + Trigger.OnAllKilled(demoTeam, CheckAlliedDestruction) + + Trigger.AfterDelay(DateTime.Seconds(30), function() + if AlliesDefeated then + return + end + + Media.PlaySpeechNotification(Greece, "ReinforcementsArrived") + local artilleryTeam = Reinforcements.Reinforce(Greece, artilleryTypes, groundPath) + Trigger.OnAllKilled(artilleryTeam, CheckAlliedDestruction) + end) +end + +ReinforceChronoTanks = function(locations) + if ChronoTanksReinforced then + return + end + + ChronoTanksReinforced = true + Media.PlaySoundNotification(Greece, "Chronoshift") + + Utils.Do(locations, function(location) + local proxy = SpawnMiscActor("powerproxy.chronoshift", GoodGuy, location, DateTime.Seconds(1)) + local tank = Actor.Create("ctnk", true, { Owner = Greece, Facing = Angle.NorthWest }) + local payload = { [tank] = location } + proxy.Chronoshift(payload) + end) +end + +ReinforceTanya = function() + local path = { WestRoadEntry.Location, TanyaDrop.Location } + local plane = Reinforcements.Reinforce(Greece, { "badr.tanya" }, path)[1] + local passengers = { TanyaType } + + Trigger.OnAddedToWorld(plane, function() + Utils.Do(passengers, function(type) + local passenger = Actor.Create(type, false, { Owner = Greece }) + plane.LoadPassenger(passenger) + Trigger.OnKilled(passenger, OnTanyaKilled) + Tanya = passenger + AnnounceTanyaRules(passenger) + end) + + plane.Paradrop(path[2]) + end) +end + +AnnounceTanyaRules = function(tanya) + if Difficulty == "easy" then + return + end + + Trigger.OnAddedToWorld(tanya, function() + Trigger.AfterDelay(DateTime.Seconds(1), function() + Media.DisplayMessage(UserInterface.Translate("tanya-rules-of-engagement"), tanya.TooltipName) + Media.PlaySoundNotification(Greece, "AlertBleep") + end) + end) +end + +ReinforceLongbows = function() + if SoutheastTurret.IsDead then + return + end + + local goals = { LongbowGoal1, LongbowGoal2, LongbowGoal3 } + local spawnOffset = 0 + + Utils.Do(goals, function(goal) + local entry = { LongbowEntry.Location + CVec.New(spawnOffset, 15) } + + Reinforcements.Reinforce(GoodGuy, { "heli" }, entry, 0, function(helicopter) + helicopter.Wait(DateTime.Seconds(1)) + helicopter.AttackMove(goal.Location) + + -- Original behavior was to fire at the ground until out of ammo. + Trigger.OnKilled(SoutheastTurret, function() + helicopter.Stop() + helicopter.Wait(10) + helicopter.Move(LongbowExit.Location, 1) + helicopter.Destroy() + end) + end) + + spawnOffset = spawnOffset + 5 + end) +end + +ReinforceBadGuy = function() + -- If ForwardCommand dies early, the General will be busy hunting. + if General.IsDead or ForwardCommand.IsDead then + return + end + + BadGuyReinforced = true + Media.PlaySpeechNotification(Greece, "SignalFlareWest") + local flare = SpawnFlare(NorthFlare.Location) + + Trigger.OnKilled(General, function() + RemoveActor(flare, DateTime.Seconds(30)) + end) + + local waterCamera = SpawnPlayerCamera(NorthWaterEntry.Location, -1) + local boatPath = { NorthWaterEntry.Location, BuilderBeach.Location } + local cargo = Reinforcements.ReinforceWithTransport(BadGuy, "lst", { "mcv" }, boatPath, { boatPath[1] })[2] + + Utils.Do(cargo, function(builder) + Trigger.OnKilled(builder, CheckNewBase) + + Trigger.OnAddedToWorld(builder, function() + local builderCamera = SpawnPlayerCamera(builder.Location, -1, "camera.small") + builder.Move(BuilderRally.Location) + + builder.CallFunc(function() + RemoveActor(waterCamera) + RemoveActor(builderCamera) + end) + + builder.Deploy() + end) + end) +end + +ReinforceNorthChinook = function() + local delay = DateTime.Seconds(4) + local camera = SpawnPlayerCamera(NorthChinookUnload.Location, -1, "camera.small", delay + DateTime.Seconds(2)) + + Trigger.AfterDelay(delay, function() + local transport = Actor.Create("tran.north", true, { Owner = USSR, Location = NorthChinookEntry.Location, Facing = Angle.South }) + local passengers = transport.Passengers + + transport.UnloadPassengers(NorthChinookUnload.Location) + transport.Move(SouthChinookExit.Location) + transport.Destroy() + + Trigger.OnPassengerExited(transport, function() + if transport.PassengerCount > 0 then + return + end + + -- Original unload is slow. Mimic the timing with a short pause. + Trigger.AfterDelay(DateTime.Seconds(3), function() + OrderNorthChinookSoldiers(passengers, camera) + end) + end) + + Trigger.OnAnyKilled(passengers, function() + RemoveActor(camera, DateTime.Seconds(1)) + end) + end) +end + +OrderNorthChinookSoldiers = function(soldiers, camera) + local patrolPath = + { + VillageNortheast.Location, + VillageNorthwest.Location, + VillageSouthwest.Location, + VillageSoutheast.Location + } + local liveSoldiers = Utils.Where(soldiers, IsAlive) + + Utils.Do(liveSoldiers, function(soldier) + soldier.AttackMove(NorthChinookRally.Location) + + soldier.CallFunc(function() + Trigger.AfterDelay(1, function() + if not AreAllIdleOrDead(liveSoldiers) then + return + end + RemoveActor(camera, DateTime.Seconds(1)) + GroupAttackMove(liveSoldiers, VillageBridgeCenter.Location, 2) + GroupTightPatrol(liveSoldiers, patrolPath, true) + GroupHuntOnDamaged(liveSoldiers, Greece) + end) + end) + end) +end + +ReinforceVillagePatrol = function() + if VillagePatrolSent then + return + end + + VillagePatrolSent = true + + local path = + { + VillageSoutheast.Location, + VillageSouthwest.Location, + VillageNorthwest.Location, + VillageNortheast.Location + } + local types = { "e1", "e1", "e2", "dog" } + local origin = { VillagePatrolEntry.Location } + local group = Reinforcements.Reinforce(USSR, types, origin, 0) + GroupTightPatrol(group, path, true) +end + +ReinforceGuardHouse = function(intruderType) + if GuardHouse.IsDead then + return + end + + local goal = GuideBarrelGoal.Location + if intruderType == TanyaType then + goal = PrisonReveal.Location + end + + for i = 1, 4 do + Trigger.AfterDelay(i * 5, function() + local guard = Actor.Create("e1", true, { Owner = USSR, Location = GuardHouseSpawn.Location, SubCell = 4, Facing = Angle.SouthWest }) + guard.Move(GuardHouseExit.Location) + guard.AttackMove(goal) + IdleHunt(guard) + end) + end +end + +ReinforceHardTeams = function() + if Difficulty ~= "hard" then + return + end + + ReinforceBaseDefenders() + ReinforceNorthBeachGuards() + ReinforceV2Team() + ReinforceNorthEdgeGuards() + PrepareSouthChinook() + ReinforceHardDogs() +end + +ReinforceBaseDefenders = function() + local origin = BadGuyRally.Location + CVec.New(2, 2) + local types = { "3tnk", "3tnk" } + local defenders = Reinforcements.Reinforce(USSR, types, { origin }, 0, function(actor) + actor.Wait(1) + actor.Scatter() + end) + GroupHuntOnDamaged(defenders, Greece) + + local structures = { RoadTurretWest, RoadTurretEast, ForwardPower, ForwardCommand, ForwardTech } + Trigger.OnAnyKilled(structures, function() + GroupIdleHunt(defenders) + end) +end + +ReinforceNorthBeachGuards = function() + local types = { "e1", "e1", "e2", "e1" } + local patrolPath = { VillageNortheast.Location, VillageSouthwest.Location } + local soldiers = Reinforcements.Reinforce(USSR, types, { NorthFlare.Location }, 0, function(actor) + actor.Scatter() + end) + + Trigger.OnEnteredFootprint({ GeneralRally.Location }, function(actor, id) + if actor.Type ~= "gnrl" then + return + end + + Trigger.RemoveFootprintTrigger(id) + GroupHuntOnDamaged(soldiers, Greece) + + Utils.Do(soldiers, function(soldier) + if soldier.IsDead then + return + end + + soldier.AttackMove(BadGuyRally.Location) + soldier.AttackMove(GuideBarrelGoal.Location) + soldier.AttackMove(VillageNorthwest.Location) + soldier.Patrol(patrolPath, true) + end) + end) +end + +ReinforceV2Team = function() + local rocket = Actor.Create("v2rl", true, { Owner = USSR, Facing = Angle.West, Location = SamRocketEntry.Location }) + Reinforcements.Reinforce(USSR, { "e1" }, { SamFenceReveal.Location }, 0, function(actor) + actor.Guard(rocket) + end) +end + +PrepareSouthChinook = function() + local startPosition = SouthChinookEntry.CenterPosition + WVec.New(0, 0, Actor.CruiseAltitude("tran")) + + Trigger.OnEnteredProximityTrigger(SouthChinookProximity.CenterPosition, WDist.FromCells(8), function(actor, id) + if actor.Type ~= TanyaType or not EscortFinished then + return + end + + Trigger.RemoveProximityTrigger(id) + + local transport = Actor.Create("tran.south", true, { Owner = USSR, CenterPosition = startPosition, Facing = Angle.East }) + transport.UnloadPassengers(SouthChinookEntry.Location) + transport.Move(SouthChinookExit.Location) + transport.Destroy() + + Utils.Do(transport.Passengers, function(passenger) + Trigger.OnAddedToWorld(passenger, function() + passenger.Wait(DateTime.Seconds(1)) + IdleHunt(passenger) + end) + end) + end) +end + +ReinforceNorthEdgeGuards = function() + local types = { "e1", "e1", "e2" } + local guards = Reinforcements.Reinforce(USSR, types, { NorthEdgeGuardEntry.Location }, 0, function(actor) + actor.Scatter() + end) + GroupHuntOnDamaged(guards, Greece) +end + +ReinforceStartSoldiers = function() + local teams = + { + { + entry = { WestRoadEntry.Location }, + types = { "e2", "e2", "e1" } + }, + { + entry = { RoadTurretRifle2.Location }, + types = { "e1", "e2", "e4" } + } + } + + Utils.Do(teams, function(team) + local soldiers = Reinforcements.Reinforce(USSR, team.types, team.entry, 15, function(actor) + actor.AttackMove(TanyaDrop.Location) + actor.Hunt() + end) + + Trigger.OnAnyKilled(soldiers, function() + IdleHunt(StartSoldier) + end) + end) +end + +ReinforceHardDogs = function() + local startDog = Actor.Create("dog", true, { Owner = USSR, Location = WestRoadEntry.Location, Facing = Angle.South }) + startDog.Move(WestRoadRally.Location + CVec.New(-1, 0)) + PrepareStartDogAttack(startDog) + + local turretDog = Actor.Create("dog.areaguard", true, { Owner = USSR, Location = LongbowGoal2.Location, Facing = Angle.SouthWest }) + turretDog.AddTag("TurretDog") + + Trigger.OnKilled(turretDog, function() + GroupIdleHunt(SoutheastTurretGuards) + end) +end + +PrepareStartDogAttack = function(dog) + -- Tanya will take about 100 ticks/4 seconds to touch the ground. + local delay = DateTime.Seconds(5) + 5 + + Trigger.OnEnteredFootprint({ TanyaDrop.Location }, function(actor, id) + if actor.Type ~= TanyaType then + return + end + + Trigger.RemoveFootprintTrigger(id) + Trigger.AfterDelay(delay, function() + if dog.IsDead or actor.IsDead then + return + end + + dog.Attack(actor) + end) + end) +end diff --git a/mods/ra/maps/negotiations/negotiations.lua b/mods/ra/maps/negotiations/negotiations.lua new file mode 100644 index 0000000000..5ddd6993d6 --- /dev/null +++ b/mods/ra/maps/negotiations/negotiations.lua @@ -0,0 +1,1265 @@ +--[[ + Copyright (c) The OpenRA Developers and Contributors + This file is part of OpenRA, which is free software. It is made + available to you under the terms of the GNU General Public License + as published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. For more + information, see COPYING. +]] +BadGuy = Player.GetPlayer("BadGuy") +England = Player.GetPlayer("England") +Greece = Player.GetPlayer("Greece") +GoodGuy = Player.GetPlayer("GoodGuy") +Neutral = Player.GetPlayer("Neutral") +USSR = Player.GetPlayer("USSR") + +ForestTeam = { ForestPatroller1, ForestPatroller2, ForestPatroller3, ForestPatroller4, ForestPatroller5, ForestPatroller6, ForestPatroller7, ForestPatroller8 } +BlockerTeam = { BlockerGuard1, BlockerGuard2, BlockerTank } +SoutheastTurretGuards = { SoutheastTurretRifle1, SoutheastTurretRifle2, SoutheastTurretRifle3 } +Hostages = { Hostage1, Hostage2, Hostage3, Hostage4, Hostage5 } + +TimerColor = HSLColor.White +TimeRemaining = 0 + +TanyaType = "e7.noautotarget" +if Difficulty == "easy" then + TanyaType = "e7" +end + +WorldLoaded = function() + Camera.Position = TanyaDrop.CenterPosition + SpawnPlayerCamera(TanyaDrop.Location, DateTime.Seconds(20)) + + StartSoldier.Move(WestRoadRally.Location) + if Difficulty == "hard" then + StartSoldier.CallFunc(ReinforceStartSoldiers) + end + + VillageBridge = GetBridge() + ReinforceHardTeams() + PrepareBadGuy() + ReinforceTanya() + CreateInitialTriggers() + CreateInitialObjectives() + LinkCooperativeObjectives() + CheckSovietDestruction() +end + +CreateInitialObjectives = function() + InitObjectives(Greece) + if Difficulty == "hard" then + SaveAllHostages = AddPrimaryObjective(Greece, "keep-all-hostages-alive") + end + FreeTheHostages = AddPrimaryObjective(Greece, "free-hostages") + SaveAllHostages = SaveAllHostages or AddSecondaryObjective(Greece, "keep-all-hostages-alive") + SurviveUntilEnd = AddPrimaryObjective(England, "") + StopAllies = AddPrimaryObjective(USSR, "") +end + +--[[ + Link Greece's win and victory notification to England's own victory. + + To reduce message spam, the creation of some primary objectives is delayed. + That is done by making this a co-op mission between Greece and England + (owner of the hostages). Because both must achieve their objectives, Greece + can finish all primaries without immediately triggering victory. +]] +LinkCooperativeObjectives = function() + local greeceActor = Greece.GetActorsByType("player")[1] + Trigger.Clear(greeceActor, "OnPlayerWon") + + Trigger.OnPlayerWon(England, function() + USSR.MarkFailedObjective(StopAllies) + + Trigger.AfterDelay(DateTime.Seconds(1), function() + Media.PlaySpeechNotification(Greece, "MissionAccomplished") + end) + end) + + Trigger.AfterDelay(1, function() + -- Triggers any time Greece finishes all CURRENT primary objectives. + Trigger.OnPlayerWon(Greece, function() + local sovietsDestroyed = DestroySoviets ~= nil and Greece.IsObjectiveCompleted(DestroySoviets) + if sovietsDestroyed and EscortFinished then + England.MarkCompletedObjective(SurviveUntilEnd) + end + end) + end) +end + +CheckSovietDestruction = function() + local destroyed = USSR.HasNoRequiredUnits() and BadGuy.HasNoRequiredUnits() + if not destroyed then + Trigger.AfterDelay(DateTime.Seconds(1), CheckSovietDestruction) + return + end + + -- Cover unlikely case that all Soviets are destroyed with hostages in tow. + DestroySoviets = DestroySoviets or AddPrimaryObjective(Greece, "destroy-soviet-units-infrastructure") + Greece.MarkCompletedObjective(DestroySoviets) + + if EscortFinished and not Greece.IsObjectiveFailed(SaveAllHostages) then + Greece.MarkCompletedObjective(SaveAllHostages) + end +end + +CheckAlliedDestruction = function() + if #Greece.GetGroundAttackers() == 0 then + MarkAlliedDefeat() + end +end + +MarkAlliedDefeat = function(objective, delay) + if AlliesDefeated then + return + end + + StopCountdown() + AlliesDefeated = true + delay = delay or DateTime.Seconds(1) + + Trigger.AfterDelay(delay, function() + if not Greece.IsObjectiveCompleted(FreeTheHostages) then + Greece.MarkFailedObjective(SaveAllHostages) + end + + if objective then + Greece.MarkFailedObjective(objective) + return + end + + local mainObjectives = { FreeTheHostages, EscortTheHostages, DestroySoviets } + Utils.Do(mainObjectives, function(o) + if not Greece.IsObjectiveCompleted(o) then + Greece.MarkFailedObjective(o) + end + end) + end) +end + +CheckEscortObjective = function() + if EscortFinished then + return + end + + local maximum = Utils.Where(Hostages, IsAlive) + if Church.PassengerCount < #maximum then + return + end + OnHostagesEscorted() +end + +CreateInitialTriggers = function() + PrepareChurch() + PrepareHostages() + PrepareReveals() + PrepareSovietGuards() + PrepareForestEncounter() + PrepareChronoTanks() + PrepareBlockers() + + Trigger.OnKilled(ForwardCommand, OnForwardCommandKilled) + + local sams = { WestSam, EastSam } + Trigger.OnAllKilled(sams, ReinforceLongbows) + + local guideHouseAttackers = { GuideRifleWest, GuideRifleEast } + Trigger.OnAllKilled(guideHouseAttackers, OnGuideRiflesKilled) + + Trigger.OnEnteredProximityTrigger(TanyaDrop.CenterPosition, WDist.FromCells(4), function(actor, id) + if not EscortFinished or actor.Type ~= TanyaType then + return + end + + Trigger.RemoveProximityTrigger(id) + SignalAllies() + end) + + local prisonApproached = false + Trigger.OnEnteredProximityTrigger(Prison.CenterPosition, WDist.FromCells(8), function(actor, id) + if prisonApproached or not IsGuideOrAllies(actor) then + return + end + + prisonApproached = true + Trigger.RemoveProximityTrigger(id) + ReinforceGuardHouse(actor.Type) + end) + + local prisonBarrels = { PrisonBarrel1, PrisonBarrel2, PrisonBarrel3, PrisonBarrel4 } + Trigger.OnAllKilled(prisonBarrels, function() + local targets = { Prison, Executioner } + Utils.Do(targets, function(target) + if not target.IsDead then + target.Kill("FireDeath") + end + end) + end) +end + +PrepareHostages = function() + ScheduleExecutions() + Trigger.OnKilled(Executioner, FreeHostages) + Trigger.OnAllKilled(Hostages, OnAllHostagesKilled) + Utils.Do(Hostages, function(hostage) + Trigger.OnKilled(hostage, OnHostageKilled) + end) + + -- Keep hostages wandering, but only inside the pen. + Trigger.OnEnteredProximityTrigger(Executioner.CenterPosition, WDist.FromCells(2), function(actor, id) + if Executioner.IsDead then + Trigger.RemoveProximityTrigger(id) + return + end + if actor.Owner ~= Neutral then + return + end + + actor.Stop() + actor.Move(HostageCenter.Location) + end) +end + +Tick = function() + if TimeRemaining < 1 then + return + end + TimeRemaining = TimeRemaining - 1 + + if TimeRemaining % DateTime.Seconds(1) ~= 0 then + return + end + + local text = UserInterface.Translate("hostage-dies-in", { ["time"] = Utils.FormatTime(TimeRemaining) }) + UserInterface.SetMissionText(text, TimerColor) +end + +PrepareCountdown = function(interval) + local text = UserInterface.Translate("hostage-dies-in", { ["time"] = Utils.FormatTime(interval) }) + UserInterface.SetMissionText(text, TimerColor) + DateTime.TimeLimit = 1 + + Trigger.OnTimerExpired(function() + local survivors = Utils.Where(Hostages, IsAlive) + if #survivors > 1 then + ResetCountdown(interval) + return + end + UserInterface.SetMissionText("") + end) +end + +ResetCountdown = function(interval) + DateTime.TimeLimit = interval + TimeRemaining = interval + TimerColor = HSLColor.White + + Trigger.AfterDelay(interval - DateTime.Minutes(2), function() + TimerColor = USSR.Color + end) +end + +StopCountdown = function() + DateTime.TimeLimit = 0 + TimeRemaining = 0 + UserInterface.SetMissionText("") +end + +ScheduleExecutions = function() + local intervals = + { + easy = 5, + normal = 4, + hard = 3, + } + -- -1 tick avoids the redundant x-minute warning speech upon timer refresh. + local interval = DateTime.Minutes(intervals[Difficulty]) - 1 + local delay = 0 + + PrepareCountdown(interval) + + Utils.Do(Hostages, function(hostage) + delay = delay + interval + + Trigger.AfterDelay(delay, function() + ExecuteHostage(hostage) + end) + end) +end + +ExecuteHostage = function(hostage) + if hostage.IsDead then + local survivors = Utils.Where(Hostages, IsAlive) + if #survivors > 0 then + hostage = Utils.Random(survivors) + end + end + + if Executioner.IsDead or hostage.IsDead then + return + end + + Media.PlaySoundNotification(Greece, "HostageShot") + hostage.Kill("DefaultDeath") +end + +FreeHostages = function() + StopCountdown() + ReinforceNorthChinook() + Greece.MarkCompletedObjective(FreeTheHostages) + + Trigger.AfterDelay(DateTime.Seconds(3), function() + EscortTheHostages = AddPrimaryObjective(Greece, "get-hostages-to-church") + end) + + local liveHostages = Utils.Where(Hostages, IsAlive) + Utils.Do(liveHostages, function(hostage) + hostage.Owner = England + FollowTanya(hostage) + end) + + local guide = GetGuide() + if guide then + guide.Stop() + FollowTanya(guide) + end +end + +FollowTanya = function(actor) + -- Hostages' panic can make them forget orders; OnIdle will refresh them. + -- For the same reason, the Guide elsewhere uses OnIdle for his actions. + Trigger.OnIdle(actor, function() + if Tanya.IsDead then + return + end + + actor.Guard(Tanya) + end) +end + +PrepareForestEncounter = function() + ForestTeamReachedHouse = false + local patrolPassed = false + + local forestFoot = Trigger.OnEnteredFootprint({ ForestSafetyCheck.Location }, function(actor, id) + if patrolPassed or actor.Owner ~= USSR then + return + end + + patrolPassed = true + Trigger.RemoveFootprintTrigger(id) + GuideToVillage() + end) + + Trigger.OnAllKilled(ForestTeam, function() + if patrolPassed then + return + end + + patrolPassed = true + Trigger.RemoveFootprintTrigger(forestFoot) + GuideToVillage(nil, true) + end) + + Trigger.OnEnteredFootprint({ ForestCenter.Location }, function(actor, id) + if not IsGuide(actor) then + return + end + + Trigger.RemoveFootprintTrigger(id) + + Trigger.AfterDelay(0, function() + GuideToHideout(actor) + end) + end) + + Trigger.OnExitedFootprint({ ForestHideout.Location }, function(actor, id) + if Executioner.IsDead or actor.IsDead or not IsGuide(actor) then + return + end + + Trigger.RemoveFootprintTrigger(id) + Media.DisplayMessage(UserInterface.Translate("guide-follow-me"), actor.TooltipName) + end) + + Trigger.OnEnteredFootprint({ ForestSouthwest.Location }, function(actor, id) + if actor.Type ~= "dog" then + return + end + + Trigger.RemoveFootprintTrigger(id) + + Trigger.AfterDelay(DateTime.Seconds(1), function() + if not actor.IsDead then + Media.PlaySoundNotification(Greece, "DogWhine") + end + end) + end) + + Trigger.OnEnteredFootprint({ GuideHouseReveal.Location }, function(actor, id) + if actor.Type ~= "dog" then + return + end + + ForestTeamReachedHouse = true + Trigger.RemoveFootprintTrigger(id) + end) +end + +PrepareChronoTanks = function() + local sides = + { + westRoad = + { + cells = { CPos.New(41, 65), CPos.New(42, 65), CPos.New(43, 65), CPos.New(44, 65), CPos.New(45, 65), CPos.New(46, 65) }, + spawns = { ChronoEntryWest1.Location, ChronoEntryWest2.Location } + }, + eastRoad = + { + cells = { CPos.New(66, 70), CPos.New(66, 71), CPos.New(67, 71), CPos.New(68, 71), CPos.New(69, 71), CPos.New(70, 71), CPos.New(71, 71), CPos.New(72, 71), CPos.New(72, 71), CPos.New(73, 71) }, + spawns = { ChronoEntryEast1.Location, ChronoEntryEast2.Location } + } + } + + Utils.Do(sides, function(side) + Trigger.OnEnteredFootprint(side.cells, function(actor, id) + local signaled = SignalForReinforcements and Greece.IsObjectiveCompleted(SignalForReinforcements) + if not signaled or actor.Owner ~= Greece then + return + end + + Trigger.RemoveFootprintTrigger(id) + ReinforceChronoTanks(side.spawns) + end) + end) +end + +PrepareSovietGuards = function() + Utils.Do(BadGuy.GetGroundAttackers(), function(soviet) + Trigger.OnDamaged(soviet, function(_, attacker) + if attacker.Type == "dtrk" or attacker.Owner ~= Greece then + return + end + + Trigger.Clear(soviet, "OnDamaged") + IdleHunt(soviet) + end) + end) + + local beachAlerted = false + local beachGroup = { SouthBeachGuard1, SouthBeachGuard2, SouthBeachGuard3, SouthBeachGuard4 } + Trigger.OnAllKilled(beachGroup, SovietSouthBeachCamera.Destroy) + + Trigger.OnAnyKilled(beachGroup, function() + if beachAlerted or Tanya.IsDead then + return + end + + beachAlerted = true + GroupIdleHunt(beachGroup) + + Utils.Do(beachGroup, function(actor) + if actor.IsDead then + return + end + -- Get attack dogs sprinting if they aren't already. + actor.Attack(Tanya) + end) + end) + + local others = + { + { RoadTurretRifle1, RoadTurretRifle2, RoadTurretWest, RoadTurretEast }, + { GuideRifleWest, GuideRifleEast }, + { FarmGuard1, FarmGuard2, FarmGuard3, FarmGuard4 }, + SoutheastTurretGuards, + ForestTeam, + BlockerTeam + } + + Utils.Do(others, function(group) + GroupHuntOnDamaged(group, Greece) + end) +end + +PrepareMainReveals = function() + local mainReveals = + { + roadTurrets = + { + cells = { CPos.New(36, 71), CPos.New(36, 72), CPos.New(36, 73), CPos.New(37, 73), CPos.New(38, 73), CPos.New(39, 73), CPos.New(40, 73), CPos.New(41, 73), CPos.New(42, 73), CPos.New(43, 73), CPos.New(44, 73), CPos.New(45, 73), CPos.New(46, 73), CPos.New(47, 73) }, + location = RoadTurretReveal.Location + }, + southBeachBarrels = + { + cells = { CPos.New(51, 75), CPos.New(51, 76), CPos.New(51, 77), CPos.New(51, 78), CPos.New(51, 79) }, + location = SouthBeachReveal.Location, + onTriggered = SovietStartCamera.Destroy + }, + southeastTurret = + { + cells = { CPos.New(68, 82), CPos.New(68, 83), CPos.New(68, 84), CPos.New(68, 85), CPos.New(68, 86), CPos.New(68, 87), CPos.New(68, 88), CPos.New(68, 89) }, + location = SoutheastTurret.Location, + onTriggered = function() + local dogs = Map.ActorsWithTag("TurretDog") + GroupAttackMove(dogs, SoutheastTurret.Location + CVec.New(0, 1)) + end + }, + samFence = + { + cells = { CPos.New(69, 74), CPos.New(70, 74), CPos.New(71, 74), CPos.New(72, 74), CPos.New(73, 74), CPos.New(74, 74) }, + location = SamFenceReveal.Location, + onTriggered = ReinforceVillagePatrol + }, + riverHouse = + { + cells = { CPos.New(80, 79), CPos.New(80, 80), CPos.New(80, 81), CPos.New(80, 82), CPos.New(80, 83) }, + location = GuideHouse.Location, + type = "camera.paradrop", + onTriggered = function() + ReinforceVillagePatrol() + OrderGuideRifles() + end + }, + forest = + { + cells = { CPos.New(85, 62), CPos.New(86, 62), CPos.New(87, 62), CPos.New(88, 62), CPos.New(89, 62), CPos.New(90, 62), CPos.New(91, 62), CPos.New(92, 62), CPos.New(93, 62), CPos.New(94, 62) }, + location = ForestCenter.Location, + duration = DateTime.Seconds(20), + onTriggered = function() + OrderForestPatrol(DateTime.Seconds(3)) + SpawnPlayerCamera(ForestPatrolStart.Location, DateTime.Seconds(6), "camera") + end + }, + forestTeamBackAtForest = + { + location = ForestCenter.Location, + type = "camera.tiny", + condition = function(actor) + return ForestTeamReachedHouse and actor.Type == "dog" + end + }, + forestTeamBackAtStart = + { + location = ForestPatrolStart.Location, + type = "camera.small", + condition = function(actor) + return ForestTeamReachedHouse and actor.Type == "dog" + end + }, + forestTeamAtBridge = + { + location = VillageBridgeNortheast.Location, + type = "camera.small", + condition = function(actor) + return ForestTeamReachedHouse and actor.Type == "dog" + end + } + } + + Utils.Do(mainReveals, function(reveal) + local triggered = false + local isCorrect = reveal.condition or function(actor) + return IsGuideOrAllies(actor) + end + + Trigger.OnEnteredFootprint(reveal.cells or { reveal.location }, function(actor, id) + if triggered or not isCorrect(actor) then + return + end + + triggered = true + Trigger.RemoveFootprintTrigger(id) + + if reveal.location then + SpawnPlayerCamera(reveal.location, reveal.duration, reveal.type) + end + + if reveal.onTriggered then + reveal.onTriggered() + end + end) + end) +end + +PrepareBlockers= function() + local houseReached = false + + Trigger.OnEnteredProximityTrigger(BlockerReturnProximity.CenterPosition, WDist.FromCells(2), function(actor, id) + if not Executioner.IsDead or actor.Owner ~= Greece then + return + end + + Trigger.RemoveProximityTrigger(id) + BlockersToBridgePatrol() + end) + + Trigger.OnEnteredProximityTrigger(GuideHouseReveal.CenterPosition, WDist.FromCells(2), function(actor, id) + if BlockerTank.IsDead then + Trigger.RemoveProximityTrigger(id) + return + end + + if not Executioner.IsDead or actor ~= BlockerTank then + return + end + + houseReached = true + Trigger.RemoveProximityTrigger(id) + end) + + -- For BlockerTeam returning north from the church/Guide house area. + Trigger.OnEnteredProximityTrigger(SouthRiver.CenterPosition, WDist.FromCells(3), function(actor, id) + if BlockerTank.IsDead then + Trigger.RemoveProximityTrigger(id) + return + end + + if not houseReached or not Executioner.IsDead or actor ~= BlockerTank then + return + end + + Trigger.RemoveProximityTrigger(id) + SpawnPlayerCamera(SouthRiver.Location, DateTime.Seconds(6), "camera.tiny") + SpawnPlayerCamera(ForestCenter.Location, DateTime.Seconds(20)) + end) +end + +PrepareReveals = function() + PrepareMainReveals() + + Trigger.OnEnteredProximityTrigger(Prison.CenterPosition, WDist.FromCells(10), function(actor, id) + if not IsGuideOrAllies(actor) then + return + end + + Trigger.RemoveProximityTrigger(id) + SpawnPlayerCamera(PrisonReveal.Location, DateTime.Seconds(20)) + BlockersToGuideHouse() + + local delay = DateTime.Seconds(1) + if actor.Type == TanyaType then + delay = 5 + end + Trigger.AfterDelay(delay, OrderGeneralRetreat) + end) + + Trigger.OnKilled(SovietDemoTruck, function() + if not SovietDemoTruck.IsIdle then + return + end + SpawnPlayerCamera(SovietDemoTruck.Location, DateTime.Seconds(1), "camera.paradrop") + end) + + local otherExplosions = + { + { + actors = { SouthBeachPump1, SouthBeachPump2 }, + origin = BeachExplosionReveal.Location + }, + { + actors = { PrisonPump, PrisonBarrel1, PrisonBarrel2, PrisonBarrel3, PrisonBarrel4 }, + origin = PrisonPump.Location + }, + { + actors = { DemoPump1, DemoPump2, DemoPump3, DemoHouse }, + origin = DemoBarrel3.Location + }, + { + actors = { NorthBeachPump2 }, + origin = NorthBeachPump2.Location + }, + { + actors = { LakePump1, LakePump2 }, + origin = LakeExplosionReveal.Location + }, + } + + Utils.Do(otherExplosions, function(group) + PrepareExplosionReveal(group.actors, group.origin, DateTime.Seconds(1)) + end) +end + +PrepareExplosionReveal = function(actors, origin, duration) + local revealed = false + + Utils.Do(actors, function(actor) + Trigger.OnDamaged(actor, function(_, attacker) + if revealed or (attacker.Type ~= "barl" and attacker.Type ~= "brl3") then + return + end + + revealed = true + SpawnPlayerCamera(origin, duration, "camera.small") + end) + end) +end + +PrepareChurch = function() + Trigger.OnKilled(Church, OnChurchKilled) + Trigger.OnPassengerEntered(Church, CheckEscortObjective) + + Trigger.OnEnteredProximityTrigger(Church.CenterPosition, WDist.FromCells(4), function(actor) + local civilian = IsHostage(actor) or IsGuide(actor) + if civilian then + EnterChurch(actor) + end + end) +end + +EnterChurch = function(actor) + if Church.IsDead then + return + end + + actor.Stop() + actor.Move(ChurchRally.Location) + if IsGuide(actor) then + actor.Infiltrate(Church) + return + end + actor.EnterTransport(Church) +end + +StartFireSale = function(player) + -- Short delay in case the Soviets' Tech Center must change owner first. + Trigger.AfterDelay(1, function() + local buildings = Utils.Where(player.GetActors(), function(actor) + return actor.HasProperty("StartBuildingRepairs") + end) + + if #buildings == 0 then + return + end + + Utils.Do(buildings, function(building) + building.Sell() + end) + + Trigger.OnAllRemovedFromWorld(buildings, function() + GroupIdleHunt(player.GetGroundAttackers()) + end) + end) +end + +SignalAllies = function() + Greece.MarkCompletedObjective(SignalForReinforcements) + SpawnFlare(TanyaDrop.Location, DateTime.Minutes(4)) + Trigger.AfterDelay(DateTime.Seconds(1), ReinforceAllies) + + if General.IsDead then + OrderBadGuyHunters() + return + end + Trigger.OnKilled(General, OrderBadGuyHunters) +end + +GroupAttackMove = function(actors, location, closeEnough) + Utils.Do(actors, function(actor) + if actor.IsDead then + return + end + actor.AttackMove(location, closeEnough or 0) + end) +end + +GroupIdleHunt = function(actors) + Utils.Do(actors, IdleHunt) +end + +-- Patrol along path. Pause at each location until others arrive. +-- At final location, call onFinished(actors) or loop and restart. +GroupTightPatrol = function(actors, path, looped, pauseTime, onFinished) + pauseTime = pauseTime or 0 + local paused = false + local goal = 1 + local arrived = { } + local patrollers = Utils.Where(actors, IsAlive) + + for id = 1, #patrollers do + Trigger.OnIdle(patrollers[id], function(actor) + if paused then + return + end + + -- Move each unit to the goal until their arrival or death. + if not arrived[id] then + actor.AttackMove(path[goal], 2) + actor.CallFunc(function() + arrived[id] = true + end) + return + end + + if not AreAllIdleOrDead(patrollers) then + return + end + + -- All live actors have arrived and are idle. + paused = true + Trigger.AfterDelay(pauseTime, function() + paused = false + end) + + arrived = { } + local nextLocation, patrolFinished = NextPatrolLocation(goal, #path, looped) + + if patrolFinished then + ClearTightPatrol(patrollers, onFinished) + return + end + + goal = nextLocation + end) + end +end + +-- Fetch the next goal in a patrol path, or signal the end of a one-way patrol. +NextPatrolLocation = function(current, final, looped) + if current < final then + return current + 1, false + end + + if looped then + return 1, false + end + + return current, true +end + +-- Clear a group's TightPatrol and give orders queued for that patrol's finish. +ClearTightPatrol = function(actors, onFinished) + Utils.Do(actors, function(actor) + if actor.IsDead then + return + end + + Trigger.Clear(actor, "OnIdle") + end) + + if not onFinished then + return + end + + Trigger.AfterDelay(1, function() + onFinished(actors) + end) +end + +GroupHuntOnDamaged = function(actors, targetPlayer) + local alerted = false + Utils.Do(actors, function(actor) + if actor.IsDead then + return + end + + Trigger.OnDamaged(actor, function(_, attacker) + if alerted or attacker.Type == "dtrk" or attacker.Owner ~= targetPlayer then + return + end + alerted = true + + Utils.Do(actors, function(hunter) + if hunter.IsDead or not hunter.HasProperty("Hunt") then + return + end + -- If the hunter is patrolling, their patrol will be aborted. + hunter.Stop() + Trigger.Clear(hunter, "OnIdle") + + Trigger.AfterDelay(1, function() + IdleHunt(hunter) + end) + end) + end) + end) +end + +OrderForestPatrol = function(delay) + local path = + { + ForestNorthwest.Location, + ForestSouthwest.Location, + SouthRiver.Location, + GuideHouseReveal.Location, + ForestCenter.Location, + ForestPatrolStart.Location, + VillageBridgeNortheast.Location, + VillageNortheast.Location, + PrisonReveal.Location, + BadGuyRally.Location + } + + Trigger.AfterDelay(delay, function() + GroupTightPatrol(ForestTeam, path, false) + end) +end + +BlockersToGuideHouse = function() + local path = + { + ForestNorthwest.Location, + SouthRiver.Location, + GuideHouseReveal.Location + } + GroupTightPatrol(BlockerTeam, path, false) +end + +BlockersToBridgePatrol = function() + local path = + { + SouthRiver.Location, + ForestSouthwest.Location, + ForestNorthwest.Location, + ForestPatrolStart.Location, + VillageBridgeNortheast.Location, + ForestPatrolStart.Location, + ForestNorthwest.Location, + ForestSouthwest.Location + } + GroupTightPatrol(BlockerTeam, path, true) +end + +SpawnGuide = function() + Trigger.AfterDelay(15, function() + if GuideHouse.IsDead then + return + end + + local guide = Actor.Create("c1", true, { Owner = GoodGuy, Location = GuideHouse.Location, SubCell = 4, Facing = Angle.South } ) + GuideToForest(guide) + end) +end + +GuideToForest = function(guide) + guide.Move(GuideHouse.Location + CVec.New(0, 1)) + + guide.CallFunc(function() + Media.DisplayMessage(UserInterface.Translate("guide-thank-you"), guide.TooltipName) + end) + + guide.Move(SouthRiver.Location) + guide.Move(ForestSouthwest.Location) + guide.Move(ForestCenter.Location) +end + +GuideToHideout = function(guide) + if IsGroupDead(ForestTeam) then + -- If this team dies, the Guide will skip ahead to GuideToVillage. + return + end + + guide.CallFunc(function() + Media.DisplayMessage(UserInterface.Translate("guide-patrol-coming"), guide.TooltipName) + end) + + guide.Wait(DateTime.Seconds(2)) + + guide.CallFunc(function() + if IsGroupDead(ForestTeam) then + return + end + + Media.DisplayMessage(UserInterface.Translate("guide-come-this-way"), guide.TooltipName) + guide.Move(ForestHideout.Location) + end) +end + +GuideToVillage = function(guide, patrolKilled) + guide = guide or GetGuide() + if not guide then + return + end + + guide.Stop() + Media.PlaySoundNotification(Greece, "GuideOkay") + Media.DisplayMessage(UserInterface.Translate("guide-safe-to-move"), guide.TooltipName) + if not patrolKilled then + guide.Wait(DateTime.Seconds(1)) + end + + if VillageBridge.IsDead then + GuideToBackdoor(guide) + return + else + Trigger.OnKilled(VillageBridge, function() + GuideToBackdoor(guide) + end) + end + + guide.Move(ForestNorthwest.Location) + guide.Move(VillageBridgeNortheast.Location) + + Trigger.OnIdle(guide, function() + guide.AttackMove(PrisonIntersection.Location) + guide.CallFunc(function() + GuideToHostages(guide) + end) + end) +end + +GuideToBackdoor = function(guide) + if guide.IsDead or Greece.IsObjectiveCompleted(FreeTheHostages) then + return + end + + Trigger.Clear(guide, "OnIdle") + Trigger.AfterDelay(1, function() + if guide.IsDead then + return + end + + guide.Stop() + Trigger.OnIdle(guide, function() + guide.AttackMove(GuideBackdoorGoal.Location) + guide.CallFunc(function() + GuideToHostages(guide) + end) + end) + end) +end + +GuideToHostages = function(guide) + if Executioner.IsDead or PrisonBarrel2.IsDead or PrisonBarrel3.IsDead then + return + end + + -- To compensate for the guards' better speed/range with minimal changes, + -- the Guide is made untargetable until actors are in position (or dead). + local ghost = guide.GrantCondition("untargetable") + Trigger.Clear(guide, "OnIdle") + + Trigger.OnEnteredProximityTrigger(PrisonBarrel3.CenterPosition, WDist.FromCells(2), function(actor, id) + if Executioner.IsDead or PrisonBarrel2.IsDead or guide.IsDead then + Trigger.RemoveProximityTrigger(id) + return + end + + if actor.Type ~= "e1" or guide.Location ~= GuideBarrelGoal.Location then + return + end + + Trigger.RemoveProximityTrigger(id) + guide.Stop() + guide.Attack(PrisonBarrel2) + end) + + guide.Move(GuideBarrelGoal.Location) + local revokeTargets = Utils.Where({ Executioner, PrisonBarrel1, PrisonBarrel2, PrisonBarrel3, PrisonBarrel4 }, IsAlive) + + Trigger.OnAnyKilled(revokeTargets, function() + if guide.IsDead then + return + end + + guide.RevokeCondition(ghost) + end) +end + +OrderGeneralRetreat = function() + if General.IsDead or not General.IsIdle then + return + end + + local reinforcementTimer = DateTime.Seconds(30) + + General.Move(BadGuyRally.Location) + General.Move(GeneralRally.Location + CVec.New(0, 3)) + + General.CallFunc(function() + local camera = SpawnPlayerCamera(GeneralRally.Location, reinforcementTimer) + + Trigger.OnKilled(General, function() + RemoveActor(camera) + end) + end) + + General.Move(GeneralRally.Location) + General.CallFunc(function() + Trigger.AfterDelay(reinforcementTimer, ReinforceBadGuy) + end) +end + +OrderGuideRifles = function() + local rifles = { GuideRifleWest, GuideRifleEast } + local offset = 0 + + Utils.Do(rifles, function(rifle) + Trigger.AfterDelay(offset, function() + if rifle.IsDead or GuideHouse.IsDead then + return + end + + rifle.Attack(GuideHouse, true, true) + end) + + offset = offset + 8 + end) +end + +OrderBadGuyHunters = function() + local blockers = Utils.Where(BlockerTeam, IsAlive) + ClearTightPatrol(blockers, function(actors) + GroupAttackMove(actors, WestRoadRally.Location, 2) + GroupIdleHunt(actors) + end) + + local others = { SovietDemoTruck, DemoFlamer1, DemoFlamer2, ForwardCliffGuard2 } + Utils.Do(others, function(other) + if other.IsDead then + return + end + + other.AttackMove(WestRoadRally.Location) + IdleHunt(other) + end) +end + +AreAllIdleOrDead = function(actors) + return Utils.All(actors, function(actor) + return actor.IsIdle or actor.IsDead + end) +end + +GetBridge = function() + local bridges = Neutral.GetActorsByType("bridge1") + return bridges[1] +end + +GetGuide = function() + local guides = GoodGuy.GetActorsByType("c1") + return guides[1] +end + +IsAlive = function(actor) + return not actor.IsDead +end + +IsGroupDead = function(actors) + return Utils.All(actors, function(actor) + return actor.IsDead + end) +end + +IsGuide = function(actor) + return actor.Type == "c1" +end + +IsGuideOrAllies = function(actor) + return actor.Owner == Greece or IsGuide(actor) +end + +IsHostage = function(actor) + return actor.Owner == England +end + +SpawnPlayerCamera = function(location, duration, type, delay) + duration = duration or DateTime.Seconds(6) + type = type or "camera" + return SpawnMiscActor(type, GoodGuy, location, duration, delay) +end + +SpawnFlare = function(location, duration, delay) + return SpawnMiscActor("flare", England, location, duration, delay) +end + +-- Spawn a supporting actor with optional delay and self-removal. +SpawnMiscActor = function(type, owner, location, duration, delay) + local actor = Actor.Create(type, false, { Owner = owner, Location = location or CPos.New(0, 0) }) + + if delay then + Trigger.AfterDelay(delay, function() + actor.IsInWorld = true + end) + else + actor.IsInWorld = true + end + + if duration and duration > 0 then + RemoveActor(actor, duration) + end + + return actor +end + +RemoveActor = function(actor, delay) + Trigger.AfterDelay(delay or 0, function() + if not actor or not actor.IsInWorld then + return + end + actor.Destroy() + end) +end + +OnHostageKilled = function() + if Greece.GetObjectiveType(SaveAllHostages) == "Primary" then + MarkAlliedDefeat(SaveAllHostages) + return + end + + Greece.MarkFailedObjective(SaveAllHostages) + CheckEscortObjective() +end + +OnAllHostagesKilled = function() + Media.PlaySoundNotification(Greece, "AlertBleep") + Media.DisplayMessage(UserInterface.Translate("all-hostages-killed")) + MarkAlliedDefeat(EscortTheHostages or FreeTheHostages) +end + +OnHostagesEscorted = function() + EscortFinished = true + Media.PlaySpeechNotification(Greece, "ObjectiveMet") + Greece.MarkCompletedObjective(EscortTheHostages) + + local delay = DateTime.Seconds(3) + if DestroySoviets then + delay = 5 + if not Greece.IsObjectiveFailed(SaveAllHostages) then + Greece.MarkCompletedObjective(SaveAllHostages) + end + end + + Trigger.AfterDelay(delay, function() + DestroySoviets = DestroySoviets or AddPrimaryObjective(Greece, "destroy-soviet-units-infrastructure") + SignalForReinforcements = AddSecondaryObjective(Greece, "signal-for-reinforcements") + end) +end + +OnChurchKilled = function() + Media.PlaySoundNotification(Greece, "AlertBleep") + Media.DisplayMessage(UserInterface.Translate("church-destroyed")) + MarkAlliedDefeat(EscortTheHostages, DateTime.Seconds(2)) +end + +OnForwardCommandKilled = function() + if not BadGuyReinforced then + PrepareTechSale() + end + + StartFireSale(USSR) + + local forwardBaseGuards = { ForwardRifle1, ForwardRifle2, ForwardGrenadier, ForwardCliffGuard1, ForwardCliffGuard2, TechTank } + GroupIdleHunt(forwardBaseGuards) +end + +OnGuideRiflesKilled = function() + if not GuideHouse.IsDead then + SpawnGuide() + end +end + +OnTanyaKilled = function(_, killer) + CheckAlliedDestruction() + + if killer.Type == "v2rl" and IsAlive(killer) then + SpawnPlayerCamera(killer.Location, DateTime.Minutes(1), "camera.tiny") + end + + if not EscortFinished then + local liveHostages = Utils.Where(Hostages, IsAlive) + Utils.Do(liveHostages, function(hostage) + hostage.Panic() + end) + end +end diff --git a/mods/ra/maps/negotiations/notifications.yaml b/mods/ra/maps/negotiations/notifications.yaml new file mode 100644 index 0000000000..cc87730c8c --- /dev/null +++ b/mods/ra/maps/negotiations/notifications.yaml @@ -0,0 +1,6 @@ +Sounds: + Notifications: + HostageShot: gun27 + DogWhine: dogw5 + GuideOkay: guyokay1 + Chronoshift: chrono2 diff --git a/mods/ra/maps/negotiations/rules.yaml b/mods/ra/maps/negotiations/rules.yaml new file mode 100644 index 0000000000..44878b4251 --- /dev/null +++ b/mods/ra/maps/negotiations/rules.yaml @@ -0,0 +1,196 @@ +World: + LuaScript: + Scripts: campaign.lua, utils.lua, negotiations.lua, negotiations-ai.lua, negotiations-reinforcements.lua + MissionData: + Briefing: A Soviet force has holed up in a small town, threatening to kill a hostage every few minutes until their demands are met. We do not negotiate with terrorists -- explain this to them.\n\nLocate the hostages, free as many of them as you can, and get them safely to a nearby abandoned church. Once done, return to your drop-off point, signal for reinforcements, and finish off the Soviet forces. + WinVideo: allymorf.vqa + LossVideo: battle.vqa + TimeLimitManager: + SkipTimerExpiredNotification: true + StartGameNotification: + Notification: TimerStarted + 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: + ExternalCondition@BadGuy: + Condition: ai-enabled + HarvesterBotModule: + RequiresCondition: ai-enabled + BuildingRepairBotModule: + RequiresCondition: ai-enabled + MissionObjectives: + Cooperative: true + +ai.hard: + ProvidesPrerequisite: + Prerequisite: ai.hard + Interactable: + AlwaysVisible: + +paradrop.allies: + Inherits: powerproxy.paratroopers + ParatroopersPower: + DropItems: e1,e1,e3,e3,e3,e3,e1,e1,e1,medi + SquadSize: 2 + DisplayBeacon: false + +powerproxy.chronoshift: + AlwaysVisible: + ChronoshiftPower: + Dimensions: 1, 1 + Footprint: x + +CAMERA.tiny: + Inherits: CAMERA + RevealsShroud: + Range: 2c0 + +CAMERA.small: + Inherits: CAMERA + RevealsShroud: + Range: 4c0 + +CTNK: + Chronoshiftable: + ChronoshiftSound: + +V2RL: + AutoTarget: + # A mobile V2 can be annoying to chase, or too much with the dog attack. + InitialStanceAI: Defend + +# This type will mimic the wide guard range sometimes given to RA '96 dogs by +# Area Guard orders. An extra dog on Hard difficulty will also use this. +DOG.areaguard: + Inherits: DOG + RenderSprites: + Image: dog + AutoTarget: + ScanRadius: 9 + RevealsShroud: + Range: 9c0 + ScriptTags: + +# Keep SAMs online if the nearby truck blows; original SAMs require no power. +# This does not affect the MCV-built base. +APWR: + PowerMultiplier: + Modifier: 200 + +MISS: + Tooltip: + Name: actor-prison-name + +STEK: + ProvidesPrerequisite@TeslaTanks: + Prerequisite: vehicles.russia + ProvidesPrerequisite@ShockTroopers: + Prerequisite: infantry.russia + Power: + Amount: 0 + SpawnActorsOnSell: + # Swap out the normal Technician that resembles one of the civilians. + # Swap Engineers as well because they're useless for hunting. + ActorTypes: e1,tecn2,tecn2,c8,c9 + +V08: + # Keep close to original death time of ~10s (at normal 5/7). + DamageMultiplier: + Modifier: 250 + +C1: + ExternalCondition@untargetable: + Condition: untargetable + Targetable: + RequiresCondition: !untargetable + Infiltrates: + Types: SpyInfiltrate + ValidRelationships: Ally, Neutral + -Wanders: + +V01: + InfiltrateForPowerOutage: + Types: SpyInfiltrate + Targetable@Guide: + TargetTypes: SpyInfiltrate + Cargo: + MaxWeight: 5 + Types: c2,c3,c4,c5,c6 + LoadedCondition: loaded + RevealsShroud: + RequiresCondition: !loaded + Range: 4c0 + Type: CenterPosition + ValidRelationships: Ally, Neutral + +^Hostage: + # Not hostile to Soviets but will "guard" Tanya as she returns to the church. + Inherits@Armed: ^ArmedCivilian + Guard: + TargetLineColor: 00000000 + RevealsShroud@Imprisoned: + Range: 1c512 + ValidRelationships: Neutral + +C2: + Inherits@Hostage: ^Hostage + +C3: + Inherits@Hostage: ^Hostage + +C4: + Inherits@Hostage: ^Hostage + +C5: + Inherits@Hostage: ^Hostage + +C6: + Inherits@Hostage: ^Hostage + +C8: + Inherits@Armed: ^ArmedCivilian + +C9: + Inherits@Armed: ^ArmedCivilian + +GNRL: + SpeedMultiplier: + Modifier: 150 + AutoTarget: + InitialStanceAI: Defend + +BADR.tanya: + Inherits: BADR + RenderSprites: + Image: badr + ParaDrop: + # Avoid dropping Tanya short of her target waypoint (and any triggers). + DropRange: 0c512 + +HELI: + # Make it less obvious that the Guide House rifles do nothing until revealed. + RevealsShroudMultiplier: + Modifier: 70 + +TRAN.north: + Inherits: TRAN + RenderSprites: + Image: tran + Cargo: + InitialUnits: e2,e2,e3,shok,shok + +TRAN.south: + Inherits: TRAN + RenderSprites: + Image: tran + Cargo: + InitialUnits: e1,e1,e4,e2,e2 + AfterUnloadDelay: 0 diff --git a/mods/ra/maps/negotiations/weapons.yaml b/mods/ra/maps/negotiations/weapons.yaml new file mode 100644 index 0000000000..b3a686ca32 --- /dev/null +++ b/mods/ra/maps/negotiations/weapons.yaml @@ -0,0 +1,12 @@ +# Like original, avoid hitting targets besides the southeast flame turret. +HellfireAG: + ValidTargets: Structure + +# The default explosion can kill the prison barrels, freeing all hostages. +# The original spread seems closer to half the default. +# Clipped the last two damage warheads to avoid this. +MiniNuke: + Warhead@7Dam_areanuke2: SpreadDamage + Spread: 2c512 + Warhead@10Dam_areanuke3: SpreadDamage + Spread: 2c512 diff --git a/mods/ra/missions.yaml b/mods/ra/missions.yaml index 9ac6020100..22d0ae332f 100644 --- a/mods/ra/missions.yaml +++ b/mods/ra/missions.yaml @@ -50,6 +50,7 @@ Aftermath Allied Missions: in-the-nick-of-time production-disruption monster-tank-madness + negotiations Aftermath Soviet Missions: shock-therapy situation-critical