Changeset View
Changeset View
Standalone View
Standalone View
binaries/data/mods/public/simulation/helpers/Commands.js
Show All 10 Lines | function ProcessCommand(player, cmd) | |||||||||
let data = { | let data = { | |||||||||
"cmpPlayer": cmpPlayer, | "cmpPlayer": cmpPlayer, | |||||||||
"controlAllUnits": cmpPlayer.CanControlAllUnits() | "controlAllUnits": cmpPlayer.CanControlAllUnits() | |||||||||
}; | }; | |||||||||
if (cmd.entities) | if (cmd.entities) | |||||||||
data.entities = FilterEntityList(cmd.entities, player, data.controlAllUnits); | data.entities = FilterEntityList(cmd.entities, player, data.controlAllUnits); | |||||||||
if (cmd.formation) | ||||||||||
data.formation = cmd.formation; | ||||||||||
// Allow focusing the camera on recent commands | // Allow focusing the camera on recent commands | |||||||||
let commandData = { | let commandData = { | |||||||||
"type": "playercommand", | "type": "playercommand", | |||||||||
"players": [player], | "players": [player], | |||||||||
"cmd": cmd | "cmd": cmd | |||||||||
}; | }; | |||||||||
// Save the position, since the GUI event is received after the unit died | // Save the position, since the GUI event is received after the unit died | |||||||||
▲ Show 20 Lines • Show All 105 Lines • ▼ Show 20 Lines | "reveal-map": function(player, cmd, data) | |||||||||
// Reveal the map for all players, not just the current player, | // Reveal the map for all players, not just the current player, | |||||||||
// primarily to make it obvious to everyone that the player is cheating | // primarily to make it obvious to everyone that the player is cheating | |||||||||
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); | |||||||||
cmpRangeManager.SetLosRevealAll(-1, cmd.enable); | cmpRangeManager.SetLosRevealAll(-1, cmd.enable); | |||||||||
}, | }, | |||||||||
"walk": function(player, cmd, data) | "walk": function(player, cmd, data) | |||||||||
{ | { | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued); | cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"walk-custom": function(player, cmd, data) | "walk-custom": function(player, cmd, data) | |||||||||
{ | { | |||||||||
for (let ent in data.entities) | for (let ent in data.entities) | |||||||||
GetFormationUnitAIs([data.entities[ent]], player).forEach(cmpUnitAI => { | GetFormationUnitAIs([data.entities[ent]], player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Walk(cmd.targetPositions[ent].x, cmd.targetPositions[ent].y, cmd.queued); | cmpUnitAI.Walk(cmd.targetPositions[ent].x, cmd.targetPositions[ent].y, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"walk-to-range": function(player, cmd, data) | "walk-to-range": function(player, cmd, data) | |||||||||
{ | { | |||||||||
// Only used by the AI | // Only used by the AI | |||||||||
for (let ent of data.entities) | for (let ent of data.entities) | |||||||||
{ | { | |||||||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | |||||||||
if (cmpUnitAI) | if (cmpUnitAI) | |||||||||
cmpUnitAI.WalkToPointRange(cmd.x, cmd.z, cmd.min, cmd.max, cmd.queued); | cmpUnitAI.WalkToPointRange(cmd.x, cmd.z, cmd.min, cmd.max, cmd.queued); | |||||||||
} | } | |||||||||
}, | }, | |||||||||
"attack-walk": function(player, cmd, data) | "attack-walk": function(player, cmd, data) | |||||||||
{ | { | |||||||||
let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, allowCapture, cmd.queued); | cmpUnitAI.WalkAndFight(cmd.x, cmd.z, cmd.targetClasses, allowCapture, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"attack-walk-custom": function(player, cmd, data) | "attack-walk-custom": function(player, cmd, data) | |||||||||
{ | { | |||||||||
let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | |||||||||
for (let ent in data.entities) | for (let ent in data.entities) | |||||||||
GetFormationUnitAIs([data.entities[ent]], player).forEach(cmpUnitAI => { | GetFormationUnitAIs([data.entities[ent]], player).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.WalkAndFight(cmd.targetPositions[ent].x, cmd.targetPositions[ent].y, cmd.targetClasses, allowCapture, cmd.queued); | cmpUnitAI.WalkAndFight(cmd.targetPositions[ent].x, cmd.targetPositions[ent].y, cmd.targetClasses, allowCapture, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"attack": function(player, cmd, data) | "attack": function(player, cmd, data) | |||||||||
{ | { | |||||||||
let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | |||||||||
if (g_DebugCommands && !allowCapture && | if (g_DebugCommands && !allowCapture && | |||||||||
!(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) | !(IsOwnedByEnemyOfPlayer(player, cmd.target) || IsOwnedByNeutralOfPlayer(player, cmd.target))) | |||||||||
warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); | warn("Invalid command: attack target is not owned by enemy of player "+player+": "+uneval(cmd)); | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Attack(cmd.target, allowCapture, cmd.queued); | cmpUnitAI.Attack(cmd.target, allowCapture, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"patrol": function(player, cmd, data) | "patrol": function(player, cmd, data) | |||||||||
{ | { | |||||||||
let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | let allowCapture = cmd.allowCapture || cmd.allowCapture == null; | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => | |||||||||
cmpUnitAI.Patrol(cmd.x, cmd.z, cmd.targetClasses, allowCapture, cmd.queued) | cmpUnitAI.Patrol(cmd.x, cmd.z, cmd.targetClasses, allowCapture, cmd.queued) | |||||||||
); | ); | |||||||||
}, | }, | |||||||||
"heal": function(player, cmd, data) | "heal": function(player, cmd, data) | |||||||||
{ | { | |||||||||
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target))) | if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByAllyOfPlayer(player, cmd.target))) | |||||||||
warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd)); | warn("Invalid command: heal target is not owned by player "+player+" or their ally: "+uneval(cmd)); | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Heal(cmd.target, cmd.queued); | cmpUnitAI.Heal(cmd.target, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"repair": function(player, cmd, data) | "repair": function(player, cmd, data) | |||||||||
{ | { | |||||||||
// This covers both repairing damaged buildings, and constructing unfinished foundations | // This covers both repairing damaged buildings, and constructing unfinished foundations | |||||||||
if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target)) | if (g_DebugCommands && !IsOwnedByAllyOfPlayer(player, cmd.target)) | |||||||||
warn("Invalid command: repair target is not owned by ally of player "+player+": "+uneval(cmd)); | warn("Invalid command: repair target is not owned by ally of player "+player+": "+uneval(cmd)); | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued); | cmpUnitAI.Repair(cmd.target, cmd.autocontinue, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"gather": function(player, cmd, data) | "gather": function(player, cmd, data) | |||||||||
{ | { | |||||||||
if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByGaia(cmd.target))) | if (g_DebugCommands && !(IsOwnedByPlayer(player, cmd.target) || IsOwnedByGaia(cmd.target))) | |||||||||
warn("Invalid command: resource is not owned by gaia or player "+player+": "+uneval(cmd)); | warn("Invalid command: resource is not owned by gaia or player "+player+": "+uneval(cmd)); | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Gather(cmd.target, cmd.queued); | cmpUnitAI.Gather(cmd.target, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"gather-near-position": function(player, cmd, data) | "gather-near-position": function(player, cmd, data) | |||||||||
{ | { | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.GatherNearPosition(cmd.x, cmd.z, cmd.resourceType, cmd.resourceTemplate, cmd.queued); | cmpUnitAI.GatherNearPosition(cmd.x, cmd.z, cmd.resourceType, cmd.resourceTemplate, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"returnresource": function(player, cmd, data) | "returnresource": function(player, cmd, data) | |||||||||
{ | { | |||||||||
if (g_DebugCommands && !IsOwnedByPlayer(player, cmd.target)) | if (g_DebugCommands && !IsOwnedByPlayer(player, cmd.target)) | |||||||||
warn("Invalid command: dropsite is not owned by player "+player+": "+uneval(cmd)); | warn("Invalid command: dropsite is not owned by player "+player+": "+uneval(cmd)); | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.ReturnResource(cmd.target, cmd.queued); | cmpUnitAI.ReturnResource(cmd.target, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"back-to-work": function(player, cmd, data) | "back-to-work": function(player, cmd, data) | |||||||||
{ | { | |||||||||
for (let ent of data.entities) | for (let ent of data.entities) | |||||||||
{ | { | |||||||||
▲ Show 20 Lines • Show All 205 Lines • ▼ Show 20 Lines | "garrison": function(player, cmd, data) | |||||||||
// Verify that the building can be controlled by the player or is mutualAlly | // Verify that the building can be controlled by the player or is mutualAlly | |||||||||
if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits)) | if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits)) | |||||||||
{ | { | |||||||||
if (g_DebugCommands) | if (g_DebugCommands) | |||||||||
warn("Invalid command: garrison target cannot be controlled by player "+player+" (or ally): "+uneval(cmd)); | warn("Invalid command: garrison target cannot be controlled by player "+player+" (or ally): "+uneval(cmd)); | |||||||||
return; | return; | |||||||||
} | } | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Garrison(cmd.target, cmd.queued); | cmpUnitAI.Garrison(cmd.target, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"guard": function(player, cmd, data) | "guard": function(player, cmd, data) | |||||||||
{ | { | |||||||||
// Verify that the target can be controlled by the player or is mutualAlly | // Verify that the target can be controlled by the player or is mutualAlly | |||||||||
if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits)) | if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits)) | |||||||||
{ | { | |||||||||
if (g_DebugCommands) | if (g_DebugCommands) | |||||||||
warn("Invalid command: guard/escort target cannot be controlled by player "+player+": "+uneval(cmd)); | warn("Invalid command: guard/escort target cannot be controlled by player "+player+": "+uneval(cmd)); | |||||||||
return; | return; | |||||||||
} | } | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Guard(cmd.target, cmd.queued); | cmpUnitAI.Guard(cmd.target, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"stop": function(player, cmd, data) | "stop": function(player, cmd, data) | |||||||||
{ | { | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.Stop(cmd.queued); | cmpUnitAI.Stop(cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"unload": function(player, cmd, data) | "unload": function(player, cmd, data) | |||||||||
{ | { | |||||||||
// Verify that the building can be controlled by the player or is mutualAlly | // Verify that the building can be controlled by the player or is mutualAlly | |||||||||
if (!CanControlUnitOrIsAlly(cmd.garrisonHolder, player, data.controlAllUnits)) | if (!CanControlUnitOrIsAlly(cmd.garrisonHolder, player, data.controlAllUnits)) | |||||||||
▲ Show 20 Lines • Show All 76 Lines • ▼ Show 20 Lines | for (let ent of data.entities) | |||||||||
var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser); | var cmpAlertRaiser = Engine.QueryInterface(ent, IID_AlertRaiser); | |||||||||
if (cmpAlertRaiser) | if (cmpAlertRaiser) | |||||||||
cmpAlertRaiser.EndOfAlert(); | cmpAlertRaiser.EndOfAlert(); | |||||||||
} | } | |||||||||
}, | }, | |||||||||
"formation": function(player, cmd, data) | "formation": function(player, cmd, data) | |||||||||
{ | { | |||||||||
GetFormationUnitAIs(data.entities, player, cmd.name).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation, true).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.MoveIntoFormation(cmd); | cmpUnitAI.MoveIntoFormation(cmd); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"promote": function(player, cmd, data) | "promote": function(player, cmd, data) | |||||||||
{ | { | |||||||||
if (!data.cmpPlayer.GetCheatsEnabled()) | if (!data.cmpPlayer.GetCheatsEnabled()) | |||||||||
return; | return; | |||||||||
Show All 36 Lines | for (let ent of data.entities) | |||||||||
cmpGate.LockGate(); | cmpGate.LockGate(); | |||||||||
else | else | |||||||||
cmpGate.UnlockGate(); | cmpGate.UnlockGate(); | |||||||||
} | } | |||||||||
}, | }, | |||||||||
"setup-trade-route": function(player, cmd, data) | "setup-trade-route": function(player, cmd, data) | |||||||||
{ | { | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.SetupTradeRoute(cmd.target, cmd.source, cmd.route, cmd.queued); | cmpUnitAI.SetupTradeRoute(cmd.target, cmd.source, cmd.route, cmd.queued); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"cancel-setup-trade-route": function(player, cmd, data) | "cancel-setup-trade-route": function(player, cmd, data) | |||||||||
{ | { | |||||||||
GetFormationUnitAIs(data.entities, player).forEach(cmpUnitAI => { | GetFormationUnitAIs(data.entities, player, data.formation || undefined).forEach(cmpUnitAI => { | |||||||||
cmpUnitAI.CancelSetupTradeRoute(cmd.target); | cmpUnitAI.CancelSetupTradeRoute(cmd.target); | |||||||||
}); | }); | |||||||||
}, | }, | |||||||||
"set-trading-goods": function(player, cmd, data) | "set-trading-goods": function(player, cmd, data) | |||||||||
{ | { | |||||||||
data.cmpPlayer.SetTradingGoods(cmd.tradingGoods); | data.cmpPlayer.SetTradingGoods(cmd.tradingGoods); | |||||||||
}, | }, | |||||||||
▲ Show 20 Lines • Show All 223 Lines • ▼ Show 20 Lines | ||||||||||
} | } | |||||||||
/** | /** | |||||||||
* Get some information about the formations used by entities. | * Get some information about the formations used by entities. | |||||||||
* The entities must have a UnitAI component. | * The entities must have a UnitAI component. | |||||||||
*/ | */ | |||||||||
function ExtractFormations(ents) | function ExtractFormations(ents) | |||||||||
{ | { | |||||||||
var entities = []; // subset of ents that have UnitAI | let entities = []; // subset of ents that have UnitAI | |||||||||
var members = {}; // { formationentity: [ent, ent, ...], ... } | let members = {}; // { formationentity: [ent, ent, ...], ... } | |||||||||
let templates = {}; // { formationentity: template } | ||||||||||
for (let ent of ents) | for (let ent of ents) | |||||||||
{ | { | |||||||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | |||||||||
var fid = cmpUnitAI.GetFormationController(); | var fid = cmpUnitAI.GetFormationController(); | |||||||||
if (fid != INVALID_ENTITY) | if (fid != INVALID_ENTITY) | |||||||||
{ | { | |||||||||
if (!members[fid]) | if (!members[fid]) | |||||||||
{ | ||||||||||
members[fid] = []; | members[fid] = []; | |||||||||
templates[fid] = cmpUnitAI.GetFormationTemplate(); | ||||||||||
} | ||||||||||
members[fid].push(ent); | members[fid].push(ent); | |||||||||
} | } | |||||||||
entities.push(ent); | entities.push(ent); | |||||||||
} | } | |||||||||
return { "entities": entities, "members": members }; | return { "entities": entities, "members": members, "templates": templates }; | |||||||||
} | } | |||||||||
/** | /** | |||||||||
* Tries to find the best angle to put a dock at a given position | * Tries to find the best angle to put a dock at a given position | |||||||||
* Taken from GuiInterface.js | * Taken from GuiInterface.js | |||||||||
*/ | */ | |||||||||
function GetDockAngle(template, x, z) | function GetDockAngle(template, x, z) | |||||||||
{ | { | |||||||||
▲ Show 20 Lines • Show All 240 Lines • ▼ Show 20 Lines | function TryConstructBuilding(player, cmpPlayer, controlAllUnits, cmd) | |||||||||
if (cmd.autorepair) | if (cmd.autorepair) | |||||||||
{ | { | |||||||||
ProcessCommand(player, { | ProcessCommand(player, { | |||||||||
"type": "repair", | "type": "repair", | |||||||||
"entities": entities, | "entities": entities, | |||||||||
"target": ent, | "target": ent, | |||||||||
"autocontinue": cmd.autocontinue, | "autocontinue": cmd.autocontinue, | |||||||||
"queued": cmd.queued | "queued": cmd.queued | |||||||||
}); | }); | |||||||||
Stan: Shouldn't it send NULL_FORMATION? | ||||||||||
Done Inline ActionsNo, cmd.formation will possibly be undefined, if NULL_FORMATION is the default formation, to enable svn-like behaviour. wraitii: No, cmd.formation will possibly be `undefined`, if NULL_FORMATION is the default formation, to… | ||||||||||
} | } | |||||||||
return ent; | return ent; | |||||||||
} | } | |||||||||
function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) | function TryConstructWall(player, cmpPlayer, controlAllUnits, cmd) | |||||||||
{ | { | |||||||||
// 'cmd' message structure: | // 'cmd' message structure: | |||||||||
▲ Show 20 Lines • Show All 247 Lines • ▼ Show 20 Lines | if (cmpFormation) | |||||||||
cmpFormation.RemoveMembers(formation.members[fid]); | cmpFormation.RemoveMembers(formation.members[fid]); | |||||||||
} | } | |||||||||
} | } | |||||||||
/** | /** | |||||||||
* Returns a list of UnitAI components, each belonging either to a | * Returns a list of UnitAI components, each belonging either to a | |||||||||
* selected unit or to a formation entity for groups of the selected units. | * selected unit or to a formation entity for groups of the selected units. | |||||||||
*/ | */ | |||||||||
function GetFormationUnitAIs(ents, player, formationTemplate) | function GetFormationUnitAIs(ents, player, formationTemplate, forceTemplate) | |||||||||
{ | { | |||||||||
// If an individual was selected, remove it from any formation | // If an individual was selected, remove it from any formation | |||||||||
// and command it individually | // and command it individually | |||||||||
if (ents.length == 1) | if (ents.length == 1) | |||||||||
{ | { | |||||||||
// Skip unit if it has no UnitAI | // Skip unit if it has no UnitAI | |||||||||
var cmpUnitAI = Engine.QueryInterface(ents[0], IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(ents[0], IID_UnitAI); | |||||||||
if (!cmpUnitAI) | if (!cmpUnitAI) | |||||||||
return []; | return []; | |||||||||
RemoveFromFormation(ents); | RemoveFromFormation(ents); | |||||||||
return [ cmpUnitAI ]; | return [ cmpUnitAI ]; | |||||||||
} | } | |||||||||
// Separate out the units that don't support the chosen formation | // Separate out the units that don't support the chosen formation | |||||||||
var formedEnts = []; | var formedEnts = []; | |||||||||
var nonformedUnitAIs = []; | var nonformedUnitAIs = []; | |||||||||
for (let ent of ents) | for (let ent of ents) | |||||||||
{ | { | |||||||||
// Skip units with no UnitAI or no position | // Skip units with no UnitAI or no position | |||||||||
Not Done Inline Actions
Freagarach: | ||||||||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | |||||||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position); | var cmpPosition = Engine.QueryInterface(ent, IID_Position); | |||||||||
Not Done Inline Actions
Obvious from the code. Freagarach: Obvious from the code. | ||||||||||
if (!cmpUnitAI || !cmpPosition || !cmpPosition.IsInWorld()) | if (!cmpUnitAI || !cmpPosition || !cmpPosition.IsInWorld()) | |||||||||
continue; | continue; | |||||||||
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); | var cmpIdentity = Engine.QueryInterface(ent, IID_Identity); | |||||||||
// TODO: We only check if the formation is usable by some units | // TODO: We only check if the formation is usable by some units | |||||||||
Not Done Inline Actions
Freagarach: | ||||||||||
// if we move them to it. We should check if we can use formations | // if we move them to it. We should check if we can use formations | |||||||||
// for the other cases. | // for the other cases. | |||||||||
var nullFormation = (formationTemplate || cmpUnitAI.GetFormationTemplate()) == "special/formations/null"; | var nullFormation = (formationTemplate || cmpUnitAI.GetFormationTemplate()) == "special/formations/null"; | |||||||||
if (!nullFormation && cmpIdentity && cmpIdentity.CanUseFormation(formationTemplate || "special/formations/null")) | if (!nullFormation && cmpIdentity && cmpIdentity.CanUseFormation(formationTemplate || "special/formations/null")) | |||||||||
formedEnts.push(ent); | formedEnts.push(ent); | |||||||||
else | else | |||||||||
{ | { | |||||||||
if (nullFormation) | if (nullFormation) | |||||||||
Not Done Inline Actions
Freagarach: | ||||||||||
RemoveFromFormation([ent]); | RemoveFromFormation([ent]); | |||||||||
Not Done Inline ActionsBetter to describe the necessity or just abandon the comment. Freagarach: Better to describe the necessity or just abandon the comment. | ||||||||||
nonformedUnitAIs.push(cmpUnitAI); | nonformedUnitAIs.push(cmpUnitAI); | |||||||||
} | } | |||||||||
} | } | |||||||||
if (formedEnts.length == 0) | if (formedEnts.length == 0) | |||||||||
{ | { | |||||||||
// No units support the formation - return all the others | // No units support the formation - return all the others | |||||||||
return nonformedUnitAIs; | return nonformedUnitAIs; | |||||||||
} | } | |||||||||
// Find what formations the formationable selected entities are currently in | // Find what formations the formationable selected entities are currently in | |||||||||
var formation = ExtractFormations(formedEnts); | var formation = ExtractFormations(formedEnts); | |||||||||
var formationUnitAIs = []; | var formationUnitAIs = []; | |||||||||
let formationIds = Object.keys(formation.members); | let formationIds = Object.keys(formation.members); | |||||||||
if (formationIds.length == 1) | if (formationIds.length == 1) | |||||||||
{ | { | |||||||||
// Selected units either belong to this formation or have no formation | // Selected units either belong to this formation or have no formation | |||||||||
// Check that all its members are selected | // Check that all its members are selected | |||||||||
var fid = formationIds[0]; | var fid = formationIds[0]; | |||||||||
var cmpFormation = Engine.QueryInterface(+fid, IID_Formation); | var cmpFormation = Engine.QueryInterface(+fid, IID_Formation); | |||||||||
if (cmpFormation && cmpFormation.GetMemberCount() == formation.members[fid].length | if (cmpFormation && cmpFormation.GetMemberCount() == formation.members[fid].length && | |||||||||
&& cmpFormation.GetMemberCount() == formation.entities.length) | cmpFormation.GetMemberCount() == formation.entities.length) | |||||||||
{ | { | |||||||||
cmpFormation.DeleteTwinFormations(); | ||||||||||
// The whole formation was selected, so reuse its controller for this command | // The whole formation was selected, so reuse its controller for this command | |||||||||
if (!forceTemplate) | ||||||||||
formationTemplate = formation.templates[fid]; | ||||||||||
if (formationTemplate == formation.templates[fid]) | ||||||||||
{ | ||||||||||
cmpFormation.DeleteTwinFormations(); | ||||||||||
formationUnitAIs = [Engine.QueryInterface(+fid, IID_UnitAI)]; | formationUnitAIs = [Engine.QueryInterface(+fid, IID_UnitAI)]; | |||||||||
if (formationTemplate && CanMoveEntsIntoFormation(formation.entities, formationTemplate)) | ||||||||||
cmpFormation.LoadFormation(formationTemplate); | ||||||||||
} | } | |||||||||
} | } | |||||||||
} | ||||||||||
else if (!forceTemplate && formationIds.length) | ||||||||||
{ | ||||||||||
// Check if all entities share a common formation, if so reuse this template. | ||||||||||
let template = formation.templates[formationIds[0]]; | ||||||||||
for (let i = 1; i < formationIds.length; ++i) | ||||||||||
if (formation.templates[formationIds[i]] != template) | ||||||||||
{ | ||||||||||
template = null; | ||||||||||
break; | ||||||||||
} | ||||||||||
if (template) | ||||||||||
formationTemplate = template; | ||||||||||
} | ||||||||||
if (!formationUnitAIs.length) | if (!formationUnitAIs.length) | |||||||||
{ | { | |||||||||
// We need to give the selected units a new formation controller | // We need to give the selected units a new formation controller | |||||||||
// TODO replace the fixed 60 with something sensible, based on vision range f.e. | // TODO replace the fixed 60 with something sensible, based on vision range f.e. | |||||||||
var formationSeparation = 60; | var formationSeparation = 60; | |||||||||
var clusters = ClusterEntities(formation.entities, formationSeparation); | var clusters = ClusterEntities(formation.entities, formationSeparation); | |||||||||
var formationEnts = []; | var formationEnts = []; | |||||||||
for (let cluster of clusters) | for (let cluster of clusters) | |||||||||
{ | { | |||||||||
if (!formationTemplate || !CanMoveEntsIntoFormation(cluster, formationTemplate)) | ||||||||||
{ | ||||||||||
// Use the last formation template if everyone was using it | ||||||||||
var lastFormationTemplate = undefined; | ||||||||||
for (let ent of cluster) | ||||||||||
{ | ||||||||||
var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); | ||||||||||
if (cmpUnitAI) | ||||||||||
{ | ||||||||||
var template = cmpUnitAI.GetFormationTemplate(); | ||||||||||
if (lastFormationTemplate === undefined) | ||||||||||
{ | ||||||||||
lastFormationTemplate = template; | ||||||||||
} | ||||||||||
else if (lastFormationTemplate != template) | ||||||||||
{ | ||||||||||
lastFormationTemplate = undefined; | ||||||||||
break; | ||||||||||
} | ||||||||||
} | ||||||||||
} | ||||||||||
if (lastFormationTemplate && CanMoveEntsIntoFormation(cluster, lastFormationTemplate)) | ||||||||||
formationTemplate = lastFormationTemplate; | ||||||||||
else | ||||||||||
formationTemplate = "special/formations/null"; | ||||||||||
} | ||||||||||
RemoveFromFormation(cluster); | RemoveFromFormation(cluster); | |||||||||
if (formationTemplate == "special/formations/null") | if (!formationTemplate || !CanMoveEntsIntoFormation(cluster, formationTemplate)) | |||||||||
{ | { | |||||||||
for (let ent of cluster) | for (let ent of cluster) | |||||||||
nonformedUnitAIs.push(Engine.QueryInterface(ent, IID_UnitAI)); | nonformedUnitAIs.push(Engine.QueryInterface(ent, IID_UnitAI)); | |||||||||
continue; | continue; | |||||||||
} | } | |||||||||
// Create the new controller | // Create the new controller | |||||||||
▲ Show 20 Lines • Show All 189 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator
Shouldn't it send NULL_FORMATION?