function ProcessCommand(player, cmd) { // print("command: " + player + " " + uneval(cmd) + "\n"); // TODO: all of this stuff needs to do checks for valid arguments // (e.g. make sure players own the units they're trying to use) switch (cmd.type) { case "debug-print": print(cmd.message); break; case "walk": var cmpUnitAI = GetFormationUnitAI(cmd.entities); if (cmpUnitAI) cmpUnitAI.Walk(cmd.x, cmd.z, cmd.queued); break; case "attack": var cmpUnitAI = GetFormationUnitAI(cmd.entities); if (cmpUnitAI) cmpUnitAI.Attack(cmd.target, cmd.queued); break; case "repair": // This covers both repairing damaged buildings, and constructing unfinished foundations var cmpUnitAI = GetFormationUnitAI(cmd.entities); if (cmpUnitAI) cmpUnitAI.Repair(cmd.target, cmd.queued); break; case "gather": var cmpUnitAI = GetFormationUnitAI(cmd.entities); if (cmpUnitAI) cmpUnitAI.Gather(cmd.target, cmd.queued); break; case "train": var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue); if (queue) queue.AddBatch(cmd.template, +cmd.count); break; case "stop-train": var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue); if (queue) queue.RemoveBatch(cmd.id); break; case "construct": /* * Construction process: * . Take resources away immediately. * . Create a foundation entity with 1hp, 0% build progress. * . Increase hp and build progress up to 100% when people work on it. * . If it's destroyed, an appropriate fraction of the resource cost is refunded. * . If it's completed, it gets replaced with the real building. */ // Find the player var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var playerEnt = cmpPlayerMan.GetPlayerByID(player); var cmpPlayer = Engine.QueryInterface(playerEnt, IID_Player); // Tentatively create the foundation (we might find later that it's a invalid build command) var ent = Engine.AddEntity("foundation|" + cmd.template); // TODO: report errors (e.g. invalid template names) // Move the foundation to the right place var cmpPosition = Engine.QueryInterface(ent, IID_Position); cmpPosition.JumpTo(cmd.x, cmd.z); cmpPosition.SetYRotation(cmd.angle); // Check whether it's obstructed by other entities var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction); if (cmpObstruction && cmpObstruction.CheckCollisions()) { // TODO: report error to player (the building site was obstructed) // Remove the foundation because the construction was aborted Engine.DestroyEntity(ent); break; } // Check whether it's in a visible region var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); var visible = (cmpRangeManager.GetLosVisibility(ent, player) == "visible"); if (!visible) { // TODO: report error to player (the building site was not visible) Engine.DestroyEntity(ent); break; } var cmpCost = Engine.QueryInterface(ent, IID_Cost); if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts())) { // TODO: report error to player (they ran out of resources) Engine.DestroyEntity(ent); break; } // Make it owned by the current player var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); cmpOwnership.SetOwner(player); // Initialise the foundation var cmpFoundation = Engine.QueryInterface(ent, IID_Foundation); cmpFoundation.InitialiseConstruction(player, cmd.template); // Tell the units to start building this new entity ProcessCommand(player, { "type": "repair", "entities": cmd.entities, "target": ent, "queued": cmd.queued }); break; case "delete-entity": // Verify the player owns the unit var cmpOwnership = Engine.QueryInterface(cmd.entity, IID_Ownership); if (!cmpOwnership || cmpOwnership.GetOwner() != player) break; var cmpHealth = Engine.QueryInterface(cmd.entity, IID_Health); if (cmpHealth) cmpHealth.Kill(); else Engine.DestroyEntity(cmd.entity); break; case "set-rallypoint": for each (var ent in cmd.entities) { var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (cmpRallyPoint) cmpRallyPoint.SetPosition(cmd.x, cmd.z); } break; case "unset-rallypoint": for each (var ent in cmd.entities) { var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); if (cmpRallyPoint) cmpRallyPoint.Unset(); } break; case "defeat-player": // Get player entity by playerId var cmpPlayerMananager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); var playerEnt = cmpPlayerManager.GetPlayerByID(cmd.playerId); // Send "OnPlayerDefeated" message to player Engine.PostMessage(playerEnt, MT_PlayerDefeated, null); break; default: error("Ignoring unrecognised command type '" + cmd.type + "'"); } } /** * Get some information about the formations used by entities. */ function ExtractFormations(ents) { var entities = []; // subset of ents that have UnitAI var members = {}; // { formationentity: [ent, ent, ...], ... } for each (var ent in ents) { var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); if (cmpUnitAI) { var fid = cmpUnitAI.GetFormationController(); if (fid != INVALID_ENTITY) { if (!members[fid]) members[fid] = []; members[fid].push(ent); } entities.push(ent); } } var ids = [ id for (id in members) ]; return { "entities": entities, "members": members, "ids": ids }; } /** * Remove the given list of entities from their current formations. */ function RemoveFromFormation(ents) { var formation = ExtractFormations(ents); for (var fid in formation.members) { var cmpFormation = Engine.QueryInterface(+fid, IID_Formation); if (cmpFormation) cmpFormation.RemoveMembers(formation.members[fid]); } } /** * Return null or a UnitAI belonging either to the selected unit * or to a formation entity for the selected group of units. */ function GetFormationUnitAI(ents) { // If an individual was selected, remove it from any formation // and command it individually if (ents.length == 1) { RemoveFromFormation(ents); return Engine.QueryInterface(ents[0], IID_UnitAI); } // Find what formations the selected entities are currently in var formation = ExtractFormations(ents); if (formation.entities.length == 0) { // No units with AI - nothing to do here return null; } var formationEnt = undefined; if (formation.ids.length == 1) { // Selected units all belong to the same formation. // Check that it doesn't have any other members var fid = formation.ids[0]; var cmpFormation = Engine.QueryInterface(+fid, IID_Formation); if (cmpFormation && cmpFormation.GetMemberCount() == formation.entities.length) { // The whole formation was selected, so reuse its controller for this command formationEnt = +fid; } } if (!formationEnt) { // We need to give the selected units a new formation controller // Remove selected units from their current formation for (var fid in formation.members) { var cmpFormation = Engine.QueryInterface(+fid, IID_Formation); if (cmpFormation) cmpFormation.RemoveMembers(formation.members[fid]); } // Create the new controller formationEnt = Engine.AddEntity("special/formation"); var cmpFormation = Engine.QueryInterface(formationEnt, IID_Formation); cmpFormation.SetMembers(formation.entities); } return Engine.QueryInterface(formationEnt, IID_UnitAI); } Engine.RegisterGlobal("ProcessCommand", ProcessCommand);