1
0
forked from 0ad/0ad

Do not allow upgrading when entity is producing and vice versa.

Before this patch, when entity was upgrading and producing and finished
upgrading before production, production was canceled. That meant player
assumed unit/tech will be ready in certain time but it will not. Also
fixing interference between upgrade and production animations.

Differential Revision: D2652
Reviewed by: bb
Comments by: Stan, Freagarach
Fixes: #5749
Refs: #2706

This was SVN commit r24088.
This commit is contained in:
Angen 2020-10-04 10:20:20 +00:00
parent acfd466c32
commit 0bfaedb78d
8 changed files with 215 additions and 47 deletions

View File

@ -1348,7 +1348,7 @@ function getBuildingsWhichCanTrainEntity(entitiesToCheck, trainEntType)
return entitiesToCheck.filter(entity => {
let state = GetEntityState(entity);
return state && state.production && state.production.entities.length &&
state.production.entities.indexOf(trainEntType) != -1;
state.production.entities.indexOf(trainEntType) != -1 && (!state.upgrade || !state.upgrade.isUpgrading);
});
}

View File

@ -628,13 +628,18 @@ g_SelectionPanels.Research = {
unitEntStates[0].production.technologies.map(tech => ({
"tech": tech,
"techCostMultiplier": unitEntStates[0].production.techCostMultiplier,
"researchFacilityId": unitEntStates[0].id
"researchFacilityId": unitEntStates[0].id,
"isUpgrading": !!unitEntStates[0].upgrade && unitEntStates[0].upgrade.isUpgrading
}));
for (let state of unitEntStates)
let sortedEntStates = unitEntStates.sort((a, b) =>
(!b.upgrade || !b.upgrade.isUpgrading) - (!a.upgrade || !a.upgrade.isUpgrading));
for (let state of sortedEntStates)
{
if (!state.production || !state.production.technologies)
continue;
// Remove the techs we already have in ret (with the same name and techCostMultiplier)
let filteredTechs = state.production.technologies.filter(
tech => tech != null && !ret.some(
@ -653,7 +658,8 @@ g_SelectionPanels.Research = {
ret = ret.concat(filteredTechs.map(tech => ({
"tech": tech,
"techCostMultiplier": state.production.techCostMultiplier,
"researchFacilityId": state.id
"researchFacilityId": state.id,
"isUpgrading": !!state.upgrade && state.upgrade.isUpgrading
})));
}
return ret;
@ -799,6 +805,14 @@ g_SelectionPanels.Research = {
else
button.enabled = controlsPlayer(data.player);
if (data.item.isUpgrading)
{
button.enabled = false;
modifier += "color:0 0 0 127:grayscale:";
button.tooltip += "\n" + coloredText(translate("Cannot research while upgrading."), "red");
}
if (template.icon)
icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
@ -1013,6 +1027,13 @@ g_SelectionPanels.Training = {
modifier = resourcesToAlphaMask(neededResources) + ":";
}
if (data.unitEntStates.every(state => state.upgrade && state.upgrade.isUpgrading))
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
data.button.tooltip += "\n" + coloredText(translate("Cannot train while upgrading."), "red");
}
if (template.icon)
data.icon.sprite = modifier + "stretched:session/portraits/" + template.icon;
@ -1043,6 +1064,9 @@ g_SelectionPanels.Upgrade = {
if (!template)
return false;
let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
progressOverlay.hidden = true;
let technologyEnabled = true;
if (data.item.requiredTechnology)
@ -1051,17 +1075,21 @@ g_SelectionPanels.Upgrade = {
"player": data.player
});
let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
let upgradingEntStates = data.unitEntStates.filter(state => state.upgrade.template == data.item.entity);
let upgradableEntStates = data.unitEntStates.filter(state =>
!state.upgrade.progress &&
(!state.production || !state.production.queue || !state.production.queue.length));
let neededResources = data.item.cost && Engine.GuiInterfaceCall("GetNeededResources", {
"cost": multiplyEntityCosts(data.item, data.unitEntStates.length),
"cost": multiplyEntityCosts(data.item, upgradableEntStates.length),
"player": data.player
});
let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
let progress = data.unitEntStates[0].upgrade.progress || 0;
let isUpgrading = data.unitEntStates[0].upgrade.template == data.item.entity;
let tooltip;
if (!progress)
let modifier = "";
if (!upgradingEntStates.length && upgradableEntStates.length)
{
let tooltips = [];
if (data.item.tooltip)
@ -1075,7 +1103,7 @@ g_SelectionPanels.Upgrade = {
}));
tooltips.push(
getEntityCostComponentsTooltipString(data.item, undefined, data.unitEntStates.length),
getEntityCostComponentsTooltipString(data.item, undefined, upgradableEntStates.length),
formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers),
getRequiredTechnologyTooltip(technologyEnabled, data.item.requiredTechnology, GetSimState().players[data.player].civ),
getNeededResourcesTooltip(neededResources),
@ -1083,29 +1111,14 @@ g_SelectionPanels.Upgrade = {
tooltip = tooltips.filter(tip => tip).join("\n");
data.button.onPress = function() { upgradeEntity(data.item.entity); };
}
else if (isUpgrading)
{
tooltip = translate("Cancel Upgrading");
data.button.onPress = function() { cancelUpgradeEntity(); };
}
else
{
tooltip = translate("Cannot upgrade when the entity is already upgrading.");
data.button.onPress = function() {};
}
data.button.enabled = controlsPlayer(data.player);
data.button.tooltip = tooltip;
data.button.onPress = function() {
upgradeEntity(
data.item.entity,
upgradableEntStates.map(state => state.id));
};
data.button.onPressRight = function() {
showTemplateDetails(data.item.entity);
};
let modifier = "";
if (!isUpgrading)
if (progress || !technologyEnabled || limits.canBeAddedCount == 0 &&
!hasSameRestrictionCategory(data.item.entity, data.unitEntStates[0].template))
if (!technologyEnabled || limits.canBeAddedCount == 0 &&
!upgradableEntStates.some(state => hasSameRestrictionCategory(data.item.entity, state.template)))
{
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
@ -1116,20 +1129,45 @@ g_SelectionPanels.Upgrade = {
modifier = resourcesToAlphaMask(neededResources) + ":";
}
data.countDisplay.caption = upgradableEntStates.length > 1 ? upgradableEntStates.length : "";
}
else if (upgradingEntStates.length)
{
tooltip = translate("Cancel Upgrading");
data.button.onPress = function() { cancelUpgradeEntity(); };
data.countDisplay.caption = upgradingEntStates.length > 1 ? upgradingEntStates.length : "";
let progress = 0;
for (let state of upgradingEntStates)
progress = Math.max(progress, state.upgrade.progress || 1);
let progressOverlaySize = progressOverlay.size;
// TODO This is bad: we assume the progressOverlay is square
progressOverlaySize.top = progressOverlaySize.bottom + Math.round((1 - progress) * (progressOverlaySize.left - progressOverlaySize.right));
progressOverlay.size = progressOverlaySize;
progressOverlay.hidden = false;
}
else
{
tooltip = coloredText(translatePlural(
"Cannot upgrade when the entity is training, researching or already upgrading.",
"Cannot upgrade when all entities are training, researching or already upgrading.",
data.unitEntStates.length), "red");
data.button.onPress = function() {};
data.button.enabled = false;
modifier = "color:0 0 0 127:grayscale:";
}
data.button.enabled = controlsPlayer(data.player);
data.button.tooltip = tooltip;
data.button.onPressRight = function() {
showTemplateDetails(data.item.entity);
};
data.icon.sprite = modifier + "stretched:session/" +
(data.item.icon || "portraits/" + template.icon);
data.countDisplay.caption = data.unitEntStates.length > 1 ? data.unitEntStates.length : "";
let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
if (isUpgrading)
{
let size = progressOverlay.size;
size.top = size.left + Math.round(progress * (size.right - size.left));
progressOverlay.size = size;
}
progressOverlay.hidden = !isUpgrading;
setPanelObjectPosition(data.button, data.i + getNumberOfRightPanelButtons(), data.rowLength);
return true;
}

View File

@ -317,11 +317,11 @@ function cancelPackUnit(pack)
});
}
function upgradeEntity(Template)
function upgradeEntity(Template, selection)
{
Engine.PostNetworkCommand({
"type": "upgrade",
"entities": g_Selection.toList(),
"entities": selection,
"template": Template,
"queued": false
});

View File

@ -307,7 +307,8 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
ret.upgrade = {
"upgrades": cmpUpgrade.GetUpgrades(),
"progress": cmpUpgrade.GetProgress(),
"template": cmpUpgrade.GetUpgradingTo()
"template": cmpUpgrade.GetUpgradingTo(),
"isUpgrading": cmpUpgrade.IsUpgrading()
};
let cmpStatusEffects = Engine.QueryInterface(ent, IID_StatusEffectsReceiver);

View File

@ -328,6 +328,21 @@ ProductionQueue.prototype.AddBatch = function(templateName, type, count, metadat
if (!cmpPlayer)
return;
if (!this.queue.length)
{
let cmpUpgrade = Engine.QueryInterface(this.entity, IID_Upgrade);
if (cmpUpgrade && cmpUpgrade.IsUpgrading())
{
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"players": [cmpPlayer.GetPlayerID()],
"message": markForTranslation("Entity is being upgraded. Cannot start production."),
"translateMessage": true
});
return;
}
}
if (this.queue.length < this.MaxQueueSize)
{
@ -966,6 +981,11 @@ ProductionQueue.prototype.OnValueModification = function(msg)
Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface).SetSelectionDirty(cmpPlayer.GetPlayerID());
};
ProductionQueue.prototype.HasQueuedProduction = function()
{
return this.queue.length > 0;
};
ProductionQueue.prototype.OnDisabledTemplatesChanged = function(msg)
{
// If the disabled templates of the player is changed,

View File

@ -229,6 +229,20 @@ Upgrade.prototype.Upgrade = function(template)
return false;
let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (!cmpPlayer)
return false;
let cmpProductionQueue = Engine.QueryInterface(this.entity, IID_ProductionQueue);
if (cmpProductionQueue && cmpProductionQueue.HasQueuedProduction())
{
let cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
cmpGUIInterface.PushNotification({
"players": [cmpPlayer.GetPlayerID()],
"message": markForTranslation("Entity is producing. Cannot start upgrading."),
"translateMessage": true
});
return false;
}
this.expendedResources = this.GetResourceCosts(template);
if (!cmpPlayer || !cmpPlayer.TrySubtractResources(this.expendedResources))

View File

@ -9,6 +9,7 @@ Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/TrainingRestrictions.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
Engine.LoadComponentScript("interfaces/Upgrade.js");
Engine.LoadComponentScript("EntityLimits.js");
Engine.RegisterGlobal("Resources", {
@ -71,6 +72,10 @@ function testEntitiesList()
"GetCiv": () => "iber"
});
AddMock(productionQueueId, IID_Upgrade, {
"IsUpgrading": () => false
});
cmpProductionQueue.CalculateEntitiesMap();
TS_ASSERT_UNEVAL_EQUALS(
cmpProductionQueue.GetEntitiesList(),
@ -273,6 +278,10 @@ function regression_test_d1879()
"PickSpawnPoint": () => ({ "x": -1, "y": -1, "z": -1 })
});
AddMock(testEntity, IID_Upgrade, {
"IsUpgrading": () => false
});
cmpProdQueue.AddBatch("some_template", "unit", 3);
Engine.QueryInterface(testEntity, IID_ProductionQueue).ProgressTimeout();
@ -280,6 +289,87 @@ function regression_test_d1879()
TS_ASSERT_EQUALS(cmpEntLimits.GetCounts().some_limit, 6);
}
function test_batch_adding()
{
let playerEnt = 2;
let playerID = 1;
let testEntity = 3;
ConstructComponent(playerEnt, "EntityLimits", {
"Limits": {
"some_limit": 8
},
"LimitChangers": {},
"LimitRemovers": {}
});
AddMock(SYSTEM_ENTITY, IID_GuiInterface, {
"PushNotification": () => {}
});
AddMock(SYSTEM_ENTITY, IID_Trigger, {
"CallEvent": () => {}
});
AddMock(SYSTEM_ENTITY, IID_Timer, {
"SetTimeout": (ent, iid, func) => {}
});
AddMock(SYSTEM_ENTITY, IID_TemplateManager, {
"TemplateExists": () => true,
"GetTemplate": name => ({
"Cost": {
"BuildTime": 0,
"Population": 1,
"Resources": {}
},
"TrainingRestrictions": {
"Category": "some_limit"
}
})
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": id => playerEnt
});
AddMock(playerEnt, IID_Player, {
"GetCiv": () => "iber",
"GetPlayerID": () => playerID,
"GetTimeMultiplier": () => 0,
"BlockTraining": () => {},
"UnBlockTraining": () => {},
"UnReservePopulationSlots": () => {},
"TrySubtractResources": () => true,
"TryReservePopulationSlots": () => false // Always have pop space.
});
AddMock(testEntity, IID_Ownership, {
"GetOwner": () => playerID
});
let cmpProdQueue = ConstructComponent(testEntity, "ProductionQueue", {
"Entities": { "_string": "some_template" },
"BatchTimeModifier": 1
});
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
AddMock(testEntity, IID_Upgrade, {
"IsUpgrading": () => true
});
cmpProdQueue.AddBatch("some_template", "unit", 3);
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 0);
AddMock(testEntity, IID_Upgrade, {
"IsUpgrading": () => false
});
cmpProdQueue.AddBatch("some_template", "unit", 3);
TS_ASSERT_EQUALS(cmpProdQueue.GetQueue().length, 1);
}
function test_token_changes()
{
const ent = 10;
@ -350,4 +440,5 @@ function test_token_changes()
testEntitiesList();
regression_test_d1879();
test_batch_adding();
test_token_changes();

View File

@ -13,6 +13,7 @@ Resources = {
return "<interleave>" + schema + "</interleave>";
}
};
Engine.LoadComponentScript("interfaces/ProductionQueue.js");
Engine.LoadComponentScript("interfaces/ModifiersManager.js"); // Provides `IID_ModifiersManager`, used below.
Engine.LoadComponentScript("interfaces/Timer.js"); // Provides `IID_Timer`, used below.
@ -120,6 +121,9 @@ AddMock(20, IID_Ownership, {
AddMock(20, IID_Identity, {
"GetCiv": () => civCode // Called in components/Upgrade.js::init().
});
AddMock(20, IID_ProductionQueue, {
"HasQueuedProduction": () => false
});
let cmpUpgrade = ConstructComponent(20, "Upgrade", template.Upgrade);
cmpUpgrade.owner = playerID;