Move the controllability of an entity to cmpIdentity.
Where controllability is defined as the ability to receive _any_ order from the player. Fixes the concern not raised at a1dc9cadd8#42637. Differential Revision: D1960 Reviewed By: @wraitii Comments by: @bb, @Stan. This was SVN commit r24148.
This commit is contained in:
parent
41570a2dc1
commit
b97d251322
@ -1105,6 +1105,33 @@ var g_UnitActions =
|
||||
"specificness": 11,
|
||||
},
|
||||
|
||||
// This is a "fake" action to show a failure cursor
|
||||
// when only uncontrollable entities are selected.
|
||||
"uncontrollable":
|
||||
{
|
||||
"execute": function(target, action, selection, queued)
|
||||
{
|
||||
return true;
|
||||
},
|
||||
"actionCheck": function(target, selection)
|
||||
{
|
||||
// Only show this action if all entities are marked uncontrollable.
|
||||
let playerState = g_SimState.players[g_ViewedPlayer];
|
||||
if (playerState && playerState.controlsAll || selection.some(ent => {
|
||||
let entState = GetEntityState(ent);
|
||||
return entState && entState.identity && entState.identity.controllable;
|
||||
}))
|
||||
return false;
|
||||
|
||||
return {
|
||||
"type": "none",
|
||||
"cursor": "cursor-no",
|
||||
"tooltip": translatePlural("This entity cannot be controlled.", "These entities cannot be controlled.", selection.length)
|
||||
};
|
||||
},
|
||||
"specificness": 100,
|
||||
},
|
||||
|
||||
"none":
|
||||
{
|
||||
"execute": function(target, action, selection, queued)
|
||||
@ -1600,14 +1627,19 @@ function findGatherType(gatherer, supply)
|
||||
|
||||
function getActionInfo(action, target, selection)
|
||||
{
|
||||
let simState = GetSimState();
|
||||
|
||||
// If the selection doesn't exist, no action
|
||||
if (!GetEntityState(selection[0]))
|
||||
if (!selection || !selection.length || !GetEntityState(selection[0]))
|
||||
return { "possible": false };
|
||||
|
||||
if (!target) // TODO move these non-target actions to an object like unit_actions.js
|
||||
{
|
||||
// Ensure one entity at least is controllable.
|
||||
let playerState = g_SimState.players[g_ViewedPlayer];
|
||||
if (playerState && !playerState.controlsAll && !selection.some(ent => {
|
||||
let entState = GetEntityState(ent);
|
||||
return entState && entState.identity && entState.identity.controllable;
|
||||
}))
|
||||
return { "possible": false };
|
||||
|
||||
if (action == "set-rallypoint")
|
||||
{
|
||||
let cursor = "";
|
||||
@ -1644,6 +1676,9 @@ function getActionInfo(action, target, selection)
|
||||
if (!targetState)
|
||||
return { "possible": false };
|
||||
|
||||
let simState = GetSimState();
|
||||
let playerState = g_SimState.players[g_ViewedPlayer];
|
||||
|
||||
// Check if any entities in the selection can do some of the available actions with target
|
||||
for (let entityID of selection)
|
||||
{
|
||||
@ -1651,6 +1686,9 @@ function getActionInfo(action, target, selection)
|
||||
if (!entState)
|
||||
continue;
|
||||
|
||||
if (playerState && !playerState.controlsAll && !entState.identity.controllable)
|
||||
continue;
|
||||
|
||||
if (g_UnitActions[action] && g_UnitActions[action].getActionInfo)
|
||||
{
|
||||
let r = g_UnitActions[action].getActionInfo(entState, targetState, simState);
|
||||
|
@ -135,7 +135,13 @@ function updateUnitCommands(entStates, supplementalDetailsPanel, commandsPanel)
|
||||
let playerStates = GetSimState().players;
|
||||
let playerState = playerStates[Engine.GetPlayerID()];
|
||||
|
||||
if (g_IsObserver || entStates.every(entState => controlsPlayer(entState.player)))
|
||||
// Always show selection.
|
||||
setupUnitPanel("Selection", entStates, playerStates[entStates[0].player]);
|
||||
|
||||
if (g_IsObserver || entStates.every(entState =>
|
||||
controlsPlayer(entState.player) &&
|
||||
(!entState.identity || entState.identity.controllable)) ||
|
||||
playerState.controlsAll)
|
||||
{
|
||||
for (let guiName of g_PanelsOrder)
|
||||
{
|
||||
|
@ -261,7 +261,8 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
||||
"selectionGroupName": cmpIdentity.GetSelectionGroupName(),
|
||||
"canDelete": !cmpIdentity.IsUndeletable(),
|
||||
"hasSomeFormation": cmpIdentity.HasSomeFormation(),
|
||||
"formations": cmpIdentity.GetFormationsList()
|
||||
"formations": cmpIdentity.GetFormationsList(),
|
||||
"controllable": cmpIdentity.IsControllable()
|
||||
};
|
||||
|
||||
let cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
|
@ -90,6 +90,11 @@ Identity.prototype.Schema =
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Controllable' a:help='Whether players can control this entity. Defaults to true.'>" +
|
||||
"<data type='boolean'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<element name='Undeletable' a:help='Prevent players from deleting this entity.'>" +
|
||||
"<data type='boolean'/>" +
|
||||
"</element>";
|
||||
@ -102,6 +107,8 @@ Identity.prototype.Init = function()
|
||||
this.phenotype = pickRandom(this.GetPossiblePhenotypes());
|
||||
else
|
||||
this.phenotype = "default";
|
||||
|
||||
this.controllable = this.template.Controllable ? this.template.Controllable == "true" : true;
|
||||
};
|
||||
|
||||
Identity.prototype.HasSomeFormation = function()
|
||||
@ -184,4 +191,14 @@ Identity.prototype.IsUndeletable = function()
|
||||
return this.template.Undeletable == "true";
|
||||
};
|
||||
|
||||
Identity.prototype.IsControllable = function()
|
||||
{
|
||||
return this.controllable;
|
||||
};
|
||||
|
||||
Identity.prototype.SetControllable = function(controllability)
|
||||
{
|
||||
this.controllable = controllability;
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Identity, "Identity", Identity);
|
||||
|
@ -214,8 +214,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
// Called when being told to walk as part of a formation
|
||||
"Order.FormationWalk": function(msg) {
|
||||
// Let players move captured domestic animals around
|
||||
if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
|
||||
if (!this.AbleToMove())
|
||||
{
|
||||
this.FinishOrder();
|
||||
return;
|
||||
@ -247,13 +246,6 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
// (these will switch the unit out of formation mode)
|
||||
|
||||
"Order.Stop": function(msg) {
|
||||
// We have no control over non-domestic animals.
|
||||
if (this.IsAnimal() && !this.IsDomestic())
|
||||
{
|
||||
this.FinishOrder();
|
||||
return;
|
||||
}
|
||||
|
||||
this.StopMoving();
|
||||
this.FinishOrder();
|
||||
|
||||
@ -267,8 +259,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"Order.Walk": function(msg) {
|
||||
// Let players move captured domestic animals around
|
||||
if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
|
||||
if (!this.AbleToMove())
|
||||
{
|
||||
this.FinishOrder();
|
||||
return;
|
||||
@ -289,8 +280,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
},
|
||||
|
||||
"Order.WalkAndFight": function(msg) {
|
||||
// Let players move captured domestic animals around
|
||||
if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
|
||||
if (!this.AbleToMove())
|
||||
{
|
||||
this.FinishOrder();
|
||||
return;
|
||||
@ -312,8 +302,7 @@ UnitAI.prototype.UnitFsmSpec = {
|
||||
|
||||
|
||||
"Order.WalkToTarget": function(msg) {
|
||||
// Let players move captured domestic animals around
|
||||
if (this.IsAnimal() && !this.IsDomestic() || !this.AbleToMove())
|
||||
if (!this.AbleToMove())
|
||||
{
|
||||
this.FinishOrder();
|
||||
return;
|
||||
@ -3341,12 +3330,6 @@ UnitAI.prototype.IsDangerousAnimal = function()
|
||||
this.template.NaturalBehaviour == "aggressive"));
|
||||
};
|
||||
|
||||
UnitAI.prototype.IsDomestic = function()
|
||||
{
|
||||
var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
|
||||
return cmpIdentity && cmpIdentity.HasClass("Domestic");
|
||||
};
|
||||
|
||||
UnitAI.prototype.IsHealer = function()
|
||||
{
|
||||
return Engine.QueryInterface(this.entity, IID_Heal);
|
||||
|
@ -548,6 +548,7 @@ AddMock(10, IID_Identity, {
|
||||
"GetSelectionGroupName": function() { return "Selection Group Name"; },
|
||||
"HasClass": function() { return true; },
|
||||
"IsUndeletable": function() { return false; },
|
||||
"IsControllable": function() { return true; },
|
||||
"HasSomeFormation": function() { return false; },
|
||||
"GetFormationsList": function() { return []; },
|
||||
});
|
||||
@ -581,6 +582,7 @@ TS_ASSERT_UNEVAL_EQUALS(cmp.GetEntityState(-1, 10), {
|
||||
"canDelete": true,
|
||||
"hasSomeFormation": false,
|
||||
"formations": [],
|
||||
"controllable": true,
|
||||
},
|
||||
"position": { "x": 1, "y": 2, "z": 3 },
|
||||
"hitpoints": 50,
|
||||
|
@ -340,13 +340,6 @@ var g_Commands = {
|
||||
|
||||
"research": function(player, cmd, data)
|
||||
{
|
||||
if (!CanControlUnit(cmd.entity, player, data.controlAllUnits))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: research building cannot be controlled by player "+player+": "+uneval(cmd));
|
||||
return;
|
||||
}
|
||||
|
||||
var cmpTechnologyManager = QueryOwnerInterface(cmd.entity, IID_TechnologyManager);
|
||||
if (cmpTechnologyManager && !cmpTechnologyManager.CanResearch(cmd.template))
|
||||
{
|
||||
@ -362,13 +355,6 @@ var g_Commands = {
|
||||
|
||||
"stop-production": function(player, cmd, data)
|
||||
{
|
||||
if (!CanControlUnit(cmd.entity, player, data.controlAllUnits))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: production building cannot be controlled by player "+player+": "+uneval(cmd));
|
||||
return;
|
||||
}
|
||||
|
||||
var queue = Engine.QueryInterface(cmd.entity, IID_ProductionQueue);
|
||||
if (queue)
|
||||
queue.RemoveBatch(cmd.id);
|
||||
@ -458,8 +444,7 @@ var g_Commands = {
|
||||
|
||||
"garrison": function(player, cmd, data)
|
||||
{
|
||||
// Verify that the building can be controlled by the player or is mutualAlly
|
||||
if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits))
|
||||
if (!CanPlayerOrAllyControlUnit(cmd.target, player, data.controlAllUnits))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: garrison target cannot be controlled by player "+player+" (or ally): "+uneval(cmd));
|
||||
@ -473,11 +458,10 @@ var g_Commands = {
|
||||
|
||||
"guard": function(player, cmd, data)
|
||||
{
|
||||
// Verify that the target can be controlled by the player or is mutualAlly
|
||||
if (!CanControlUnitOrIsAlly(cmd.target, player, data.controlAllUnits))
|
||||
if (!IsOwnedByPlayerOrMutualAlly(cmd.target, player, data.controlAllUnits))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: guard/escort target cannot be controlled by player "+player+": "+uneval(cmd));
|
||||
warn("Invalid command: Guard/escort target is not owned by player " + player + " or ally thereof: " + uneval(cmd));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -495,8 +479,7 @@ var g_Commands = {
|
||||
|
||||
"unload": function(player, cmd, data)
|
||||
{
|
||||
// Verify that the building can be controlled by the player or is mutualAlly
|
||||
if (!CanControlUnitOrIsAlly(cmd.garrisonHolder, player, data.controlAllUnits))
|
||||
if (!CanPlayerOrAllyControlUnit(cmd.garrisonHolder, player, data.controlAllUnits))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: unload target cannot be controlled by player "+player+" (or ally): "+uneval(cmd));
|
||||
@ -875,6 +858,27 @@ function notifyBackToWorkFailure(player)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a GUI notification about entities that can't be controlled.
|
||||
* @param {number} player - The player-ID of the player that needs to receive this message.
|
||||
*/
|
||||
function notifyOrderFailure(entity, player)
|
||||
{
|
||||
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
|
||||
if (!cmpIdentity)
|
||||
return;
|
||||
|
||||
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
||||
cmpGUIInterface.PushNotification({
|
||||
"type": "text",
|
||||
"players": [player],
|
||||
"message": sprintf(markForTranslation("%(unit)s can't be controlled."), {
|
||||
"unit": cmpIdentity.GetGenericName()
|
||||
}),
|
||||
"translateMessage": true
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get some information about the formations used by entities.
|
||||
* The entities must have a UnitAI component.
|
||||
@ -1661,27 +1665,55 @@ function CanMoveEntsIntoFormation(ents, formationTemplate)
|
||||
|
||||
/**
|
||||
* Check if player can control this entity
|
||||
* returns: true if the entity is valid and owned by the player
|
||||
* returns: true if the entity is owned by the player and controllable
|
||||
* or control all units is activated, else false
|
||||
*/
|
||||
function CanControlUnit(entity, player, controlAll)
|
||||
{
|
||||
return IsOwnedByPlayer(player, entity) || controlAll;
|
||||
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
|
||||
let canBeControlled = IsOwnedByPlayer(player, entity) &&
|
||||
(!cmpIdentity || cmpIdentity.IsControllable()) ||
|
||||
controlAll;
|
||||
|
||||
if (!canBeControlled)
|
||||
notifyOrderFailure(entity, player);
|
||||
|
||||
return canBeControlled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} entity - The entityID to verify.
|
||||
* @param {number} player - The playerID to check against.
|
||||
* @return {boolean}.
|
||||
*/
|
||||
function IsOwnedByPlayerOrMutualAlly(entity, player)
|
||||
{
|
||||
return IsOwnedByPlayer(player, entity) || IsOwnedByMutualAllyOfPlayer(player, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if player can control this entity
|
||||
* returns: true if the entity is valid and owned by the player
|
||||
* or the entity is owned by an mutualAlly
|
||||
* or control all units is activated, else false
|
||||
* @return {boolean} - True if the entity is valid and controlled by the player
|
||||
* or the entity is owned by an mutualAlly and can be controlled
|
||||
* or control all units is activated, else false.
|
||||
*/
|
||||
function CanControlUnitOrIsAlly(entity, player, controlAll)
|
||||
function CanPlayerOrAllyControlUnit(entity, player, controlAll)
|
||||
{
|
||||
return IsOwnedByPlayer(player, entity) || IsOwnedByMutualAllyOfPlayer(player, entity) || controlAll;
|
||||
return CanControlUnit(player, entity, controlAll) ||
|
||||
IsOwnedByMutualAllyOfPlayer(player, entity) && CanOwnerControlEntity(entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter entities which the player can control
|
||||
* @return {boolean} - Whether the owner of this entity can control the entity.
|
||||
*/
|
||||
function CanOwnerControlEntity(entity)
|
||||
{
|
||||
let cmpOwner = QueryOwnerInterface(entity);
|
||||
return cmpOwner && CanControlUnit(entity, cmpOwner.GetPlayerID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter entities which the player can control.
|
||||
*/
|
||||
function FilterEntityList(entities, player, controlAll)
|
||||
{
|
||||
@ -1693,7 +1725,7 @@ function FilterEntityList(entities, player, controlAll)
|
||||
*/
|
||||
function FilterEntityListWithAllies(entities, player, controlAll)
|
||||
{
|
||||
return entities.filter(ent => CanControlUnitOrIsAlly(ent, player, controlAll));
|
||||
return entities.filter(ent => CanPlayerOrAllyControlUnit(ent, player, controlAll));
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user