Files
OpenRA/mods/cnc/maps/twist-of-fate/twist-of-fate-AI.lua
let5sne.win10 9cf6ebb986
Some checks failed
Continuous Integration / Linux (.NET 8.0) (push) Has been cancelled
Continuous Integration / Windows (.NET 8.0) (push) Has been cancelled
Initial commit: OpenRA game engine
Fork from OpenRA/OpenRA with one-click launch script (start-ra.cmd)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 21:46:54 +08:00

651 lines
19 KiB
Lua

--[[
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