mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Allow entities to upgrade into other entities.
This new components allows giving the upgrade a cost, required technologies, and a required time. Implement gates using this generic component. Fixes #2706 This was SVN commit r18467.
This commit is contained in:
parent
1276b98965
commit
b2f4b0f494
18 changed files with 828 additions and 245 deletions
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:c0491f1ad9ca0c77cf2a4c187cc3883155d3a2b49d72fc7591c98c74e86763fb
|
||||
size 2938
|
||||
|
|
@ -277,7 +277,6 @@ function GetTemplateDataHelper(template, player, auraTemplates)
|
|||
};
|
||||
ret.icon = template.Identity.Icon;
|
||||
ret.tooltip = template.Identity.Tooltip;
|
||||
ret.gateConversionTooltip = template.Identity.GateConversionTooltip;
|
||||
ret.requiredTechnology = template.Identity.RequiredTechnology;
|
||||
ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,7 +291,8 @@ function getEntityCostComponentsTooltipString(template, trainNum, entity)
|
|||
trainNum = 1;
|
||||
|
||||
let totalCosts = multiplyEntityCosts(template, trainNum);
|
||||
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
|
||||
if (template.cost.time)
|
||||
totalCosts.time = Math.ceil(template.cost.time * (entity ? Engine.GuiInterfaceCall("GetBatchTime", { "entity": entity, "batchSize": trainNum }) : 1));
|
||||
|
||||
let costs = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -1632,27 +1632,25 @@ function cancelPackUnit(pack)
|
|||
});
|
||||
}
|
||||
|
||||
// Transform a wall to a gate
|
||||
function transformWallToGate(template)
|
||||
// Upgrade an entity to another
|
||||
function upgradeEntity(Template)
|
||||
{
|
||||
var selection = g_Selection.toList();
|
||||
Engine.PostNetworkCommand({
|
||||
"type": "wall-to-gate",
|
||||
"entities": selection.filter(e => getWallGateTemplate(e) == template),
|
||||
"template": template,
|
||||
"type": "upgrade",
|
||||
"entities": g_Selection.toList(),
|
||||
"template": Template,
|
||||
"queued": false
|
||||
});
|
||||
}
|
||||
|
||||
// Gets the gate form (if any) of a given long wall piece
|
||||
function getWallGateTemplate(entity)
|
||||
// Cancel upgrading entities
|
||||
function cancelUpgradeEntity()
|
||||
{
|
||||
// TODO: find the gate template name in a better way
|
||||
var entState = GetEntityState(entity);
|
||||
var index;
|
||||
|
||||
if (entState && !entState.foundation && hasClass(entState, "LongWall") && (index = entState.template.indexOf("long")) >= 0)
|
||||
return entState.template.substr(0, index) + "gate";
|
||||
return undefined;
|
||||
Engine.PostNetworkCommand({
|
||||
"type": "cancel-upgrade",
|
||||
"entities": g_Selection.toList(),
|
||||
"queued": false
|
||||
});
|
||||
}
|
||||
|
||||
// Set the camera to follow the given unit
|
||||
|
|
|
|||
|
|
@ -472,35 +472,11 @@ g_SelectionPanels.Gate = {
|
|||
},
|
||||
"getItems": function(unitEntState, selection)
|
||||
{
|
||||
// Allow long wall pieces to be converted to gates
|
||||
let longWallTypes = {};
|
||||
let walls = [];
|
||||
let gates = [];
|
||||
for (let ent of selection)
|
||||
{
|
||||
let state = GetEntityState(ent);
|
||||
if (hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])
|
||||
{
|
||||
let gateTemplate = getWallGateTemplate(state.id);
|
||||
if (gateTemplate)
|
||||
{
|
||||
let tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;
|
||||
if (!tooltipString)
|
||||
{
|
||||
warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");
|
||||
tooltipString = "";
|
||||
}
|
||||
walls.push({
|
||||
"tooltip": translate(tooltipString),
|
||||
"template": gateTemplate,
|
||||
"callback": function (item) { transformWallToGate(item.template); }
|
||||
});
|
||||
}
|
||||
|
||||
// We only need one entity per type.
|
||||
longWallTypes[state.template] = true;
|
||||
}
|
||||
else if (state.gate && !gates.length)
|
||||
if (state.gate && !gates.length)
|
||||
{
|
||||
gates.push({
|
||||
"gate": state.gate,
|
||||
|
|
@ -521,54 +497,25 @@ g_SelectionPanels.Gate = {
|
|||
delete gates[j].gate.locked;
|
||||
}
|
||||
|
||||
// Place wall conversion options after gate lock/unlock icons.
|
||||
return gates.concat(walls);
|
||||
return gates;
|
||||
},
|
||||
"setupButton": function(data)
|
||||
{
|
||||
data.button.onPress = function() {data.item.callback(data.item); };
|
||||
|
||||
let tooltips = [data.item.tooltip];
|
||||
if (data.item.template)
|
||||
{
|
||||
data.template = GetTemplateData(data.item.template);
|
||||
data.wallCount = data.selection.reduce(count, ent => {
|
||||
let state = GetEntityState(ent);
|
||||
if (hasClass(state, "LongWall") && !state.gate)
|
||||
++count;
|
||||
return count;
|
||||
}, 0);
|
||||
|
||||
tooltips.push(getEntityCostTooltip(data.template, data.wallCount));
|
||||
|
||||
data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
|
||||
"cost": multiplyEntityCosts(data.template, data.wallCount)
|
||||
});
|
||||
|
||||
tooltips.push(getNeededResourcesTooltip(data.neededResources));
|
||||
}
|
||||
data.button.tooltip = tooltips.filter(tip => tip).join("\n");
|
||||
data.button.tooltip = data.item.tooltip;
|
||||
|
||||
data.button.enabled = controlsPlayer(data.unitEntState.player);
|
||||
let gateIcon;
|
||||
if (data.item.gate)
|
||||
{
|
||||
// If already a gate, show locking actions
|
||||
// show locking actions
|
||||
gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png";
|
||||
if (data.item.gate.locked === undefined)
|
||||
data.guiSelection.hidden = false;
|
||||
else
|
||||
data.guiSelection.hidden = data.item.gate.locked != data.item.locked;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Otherwise show gate upgrade icon
|
||||
let template = GetTemplateData(data.item.template);
|
||||
if (!template)
|
||||
return false;
|
||||
gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";
|
||||
data.guiSelection.hidden = true;
|
||||
}
|
||||
|
||||
data.icon.sprite = (data.neededResources ? resourcesToAlphaMask(data.neededResources) + ":" : "") + "stretched:session/" + gateIcon;
|
||||
|
||||
|
|
@ -609,13 +556,33 @@ g_SelectionPanels.Pack = {
|
|||
}
|
||||
let items = [];
|
||||
if (checks.packButton)
|
||||
items.push({ "packing": false, "packed": false, "tooltip": translate("Pack"), "callback": function() { packUnit(true); } });
|
||||
items.push({
|
||||
"packing": false,
|
||||
"packed": false,
|
||||
"tooltip": translate("Pack"),
|
||||
"callback": function() { packUnit(true); }
|
||||
});
|
||||
if (checks.unpackButton)
|
||||
items.push({ "packing": false, "packed": true, "tooltip": translate("Unpack"), "callback": function() { packUnit(false); } });
|
||||
items.push({
|
||||
"packing": false,
|
||||
"packed": true,
|
||||
"tooltip": translate("Unpack"),
|
||||
"callback": function() { packUnit(false); }
|
||||
});
|
||||
if (checks.packCancelButton)
|
||||
items.push({ "packing": true, "packed": false, "tooltip": translate("Cancel Packing"), "callback": function() { cancelPackUnit(true); } });
|
||||
items.push({
|
||||
"packing": true,
|
||||
"packed": false,
|
||||
"tooltip": translate("Cancel Packing"),
|
||||
"callback": function() { cancelPackUnit(true); }
|
||||
});
|
||||
if (checks.unpackCancelButton)
|
||||
items.push({ "packing": true, "packed": true, "tooltip": translate("Cancel Unpacking"), "callback": function() { cancelPackUnit(false); } });
|
||||
items.push({
|
||||
"packing": true,
|
||||
"packed": true,
|
||||
"tooltip": translate("Cancel Unpacking"),
|
||||
"callback": function() { cancelPackUnit(false); }
|
||||
});
|
||||
return items;
|
||||
},
|
||||
"setupButton": function(data)
|
||||
|
|
@ -1052,6 +1019,128 @@ g_SelectionPanels.Training = {
|
|||
}
|
||||
};
|
||||
|
||||
g_SelectionPanels.Upgrade = {
|
||||
"getMaxNumberOfItems": function()
|
||||
{
|
||||
return 24 - getNumberOfRightPanelButtons();
|
||||
},
|
||||
"getItems": function(unitEntState, selection)
|
||||
{
|
||||
// Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units.
|
||||
// TODO: if the units are all the same, this should probably still be possible.
|
||||
if (selection.length > 1)
|
||||
return false;
|
||||
|
||||
if (!unitEntState.upgrade)
|
||||
return false;
|
||||
|
||||
var items = [];
|
||||
|
||||
for (let upgrade of unitEntState.upgrade.upgrades)
|
||||
{
|
||||
items.push({
|
||||
"entity": upgrade.entity,
|
||||
"cost": upgrade.cost,
|
||||
"time": upgrade.time,
|
||||
"icon": upgrade.icon,
|
||||
"tooltip": upgrade.tooltip,
|
||||
"requiredTechnology": upgrade.requiredTechnology,
|
||||
});
|
||||
}
|
||||
return items;
|
||||
},
|
||||
"setupButton" : function(data)
|
||||
{
|
||||
let template = GetTemplateData(data.item.entity);
|
||||
if (!template)
|
||||
return false;
|
||||
|
||||
let technologyEnabled = true;
|
||||
|
||||
if (data.item.requiredTechnology)
|
||||
technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", {
|
||||
"tech": requiredTechnology,
|
||||
"player": data.unitEntState.player
|
||||
});
|
||||
|
||||
let neededResources;
|
||||
if (data.item.cost)
|
||||
neededResources = Engine.GuiInterfaceCall("GetNeededResources", {
|
||||
"cost": data.item.cost,
|
||||
"player": data.unitEntState.player
|
||||
});
|
||||
|
||||
let limits = getEntityLimitAndCount(data.playerState, data.item.entity);
|
||||
|
||||
let progress = data.unitEntState.upgrade.progress || 0;
|
||||
let isUpgrading = data.unitEntState.upgrade.template == data.item.entity;
|
||||
|
||||
let tooltip;
|
||||
if (!progress)
|
||||
{
|
||||
if (data.item.tooltip)
|
||||
tooltip = sprintf(translate("Upgrade into a %(name)s. %(tooltip)s"), {
|
||||
"name": template.name.generic,
|
||||
"tooltip": data.item.tooltip
|
||||
});
|
||||
else
|
||||
tooltip = sprintf(translate("Upgrade into a %(name)s."), {"name": template.name.generic});
|
||||
|
||||
if (data.item.cost)
|
||||
tooltip += "\n" + getEntityCostTooltip(data.item);
|
||||
|
||||
tooltip += formatLimitString(limits.entLimit, limits.entCount, limits.entLimitChangers);
|
||||
if (!technologyEnabled)
|
||||
tooltip += "\n" + sprintf(translate("Requires %(technology)s"), {
|
||||
"technology": getEntityNames(GetTechnologyData(data.item.requiredTechnology))
|
||||
});
|
||||
if (neededResources)
|
||||
tooltip += getNeededResourcesTooltip(neededResources);
|
||||
|
||||
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.tooltip = tooltip;
|
||||
|
||||
let modifier = "";
|
||||
if (!isUpgrading)
|
||||
{
|
||||
if (progress || !technologyEnabled || limits.canBeAddedCount == 0)
|
||||
{
|
||||
data.button.enabled = false;
|
||||
modifier = "color:0 0 0 127:grayscale:";
|
||||
}
|
||||
else if (neededResources)
|
||||
{
|
||||
data.button.enabled = false;
|
||||
modifier = resourcesToAlphaMask(neededResources) + ":";
|
||||
}
|
||||
}
|
||||
|
||||
data.icon.sprite = modifier + "stretched:session/" +
|
||||
(data.item.icon || "portraits/" + template.icon);
|
||||
|
||||
let progressOverlay = Engine.GetGUIObjectByName("unitUpgradeProgressSlider[" + data.i + "]");
|
||||
if (isUpgrading)
|
||||
progressOverlay.size.top = progressOverlay.size.left + Math.round(progress * (progressOverlay.size.right - progressOverlay.size.left));
|
||||
|
||||
progressOverlay.hidden = !isUpgrading;
|
||||
|
||||
let index = data.i + getNumberOfRightPanelButtons();
|
||||
setPanelObjectPosition(data.button, index, data.rowLength);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* If two panels need the same space, so they collide,
|
||||
* the one appearing first in the order is rendered.
|
||||
|
|
@ -1069,6 +1158,7 @@ let g_PanelsOrder = [
|
|||
// RIGHT PANE
|
||||
"Gate", // Must always be shown on gates
|
||||
"Pack", // Must always be shown on packable entities
|
||||
"Upgrade", // Must always be shown on upgradable entities
|
||||
"Training",
|
||||
"Construction",
|
||||
"Research", // Normal together with training
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@ const BARTER_RESOURCES = ["food", "wood", "stone", "metal"];
|
|||
const BARTER_ACTIONS = ["Sell", "Buy"];
|
||||
const GATE_ACTIONS = ["lock", "unlock"];
|
||||
|
||||
// upgrade constants
|
||||
const UPGRADING_NOT_STARTED = -2;
|
||||
const UPGRADING_CHOSEN_OTHER = -1;
|
||||
|
||||
// ==============================================
|
||||
// BARTER HELPERS
|
||||
// Resources to sell on barter panel
|
||||
var g_BarterSell = "food";
|
||||
|
||||
function canMoveSelectionIntoFormation(formationTemplate)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<object name="unitUpgradePanel"
|
||||
size="10 12 100% 100%"
|
||||
>
|
||||
<object size="0 0 100% 100%">
|
||||
<repeat count="8">
|
||||
<object name="unitUpgradeButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom">
|
||||
<object name="unitUpgradeIcon[n]" type="image" ghost="true" size="3 3 43 43"/>
|
||||
<object name="unitUpgradeUpgradeIcon[n]" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/upgrade.png"/>
|
||||
<object name="unitUpgradeProgressSlider[n]" type="image" sprite="queueProgressSlider" ghost="true" size="3 3 43 43" z="20"/>
|
||||
<object name="unitUpgradeSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/>
|
||||
</object>
|
||||
</repeat>
|
||||
</object>
|
||||
</object>
|
||||
|
|
@ -1,5 +1,21 @@
|
|||
// The number of currently visible buttons (used to optimise showing/hiding)
|
||||
var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Alert": 0, "Barter": 0, "Construction": 0, "Command": 0, "AllyCommand": 0, "Stance": 0, "Gate": 0, "Pack": 0};
|
||||
var g_unitPanelButtons = {
|
||||
"Selection": 0,
|
||||
"Queue": 0,
|
||||
"Formation": 0,
|
||||
"Garrison": 0,
|
||||
"Training": 0,
|
||||
"Research": 0,
|
||||
"Alert": 0,
|
||||
"Barter": 0,
|
||||
"Construction": 0,
|
||||
"Command": 0,
|
||||
"AllyCommand": 0,
|
||||
"Stance": 0,
|
||||
"Gate":0,
|
||||
"Pack": 0,
|
||||
"Upgrade": 0
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the position of a panel object according to the index,
|
||||
|
|
@ -225,7 +241,7 @@ function getNumberOfRightPanelButtons()
|
|||
{
|
||||
var sum = 0;
|
||||
|
||||
for (let prop of ["Construction", "Training", "Pack", "Gate"])
|
||||
for (let prop of ["Construction", "Training", "Pack", "Gate", "Upgrade"])
|
||||
if (g_SelectionPanels[prop].used)
|
||||
sum += g_unitPanelButtons[prop];
|
||||
|
||||
|
|
|
|||
|
|
@ -237,6 +237,7 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
"market": null,
|
||||
"mirage": null,
|
||||
"pack": null,
|
||||
"upgrade" : null,
|
||||
"player": -1,
|
||||
"position": null,
|
||||
"production": null,
|
||||
|
|
@ -303,6 +304,14 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
"progress": cmpPack.GetProgress(),
|
||||
};
|
||||
|
||||
var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
|
||||
if (cmpUpgrade)
|
||||
ret.upgrade = {
|
||||
"upgrades" : cmpUpgrade.GetUpgrades(),
|
||||
"progress": cmpUpgrade.GetProgress(),
|
||||
"template": cmpUpgrade.GetUpgradingTo()
|
||||
};
|
||||
|
||||
let cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue);
|
||||
if (cmpProductionQueue)
|
||||
ret.production = {
|
||||
|
|
|
|||
|
|
@ -39,11 +39,6 @@ Identity.prototype.Schema =
|
|||
"<text/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='GateConversionTooltip'>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Rollover'>" +
|
||||
"<text/>" +
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ Pack.prototype.CancelTimer = function()
|
|||
{
|
||||
if (this.timer)
|
||||
{
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.CancelTimer(this.timer);
|
||||
this.timer = undefined;
|
||||
}
|
||||
|
|
@ -56,9 +56,9 @@ Pack.prototype.Pack = function()
|
|||
return;
|
||||
|
||||
this.packing = true;
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.timer = cmpTimer.SetInterval(this.entity, IID_Pack, "PackProgress", 0, PACKING_INTERVAL, {"packing": true});
|
||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("packing", true, 1.0, "packing");
|
||||
};
|
||||
|
|
@ -70,9 +70,9 @@ Pack.prototype.Unpack = function()
|
|||
return;
|
||||
|
||||
this.packing = true;
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.timer = cmpTimer.SetInterval(this.entity, IID_Pack, "PackProgress", 0, PACKING_INTERVAL, {"packing": false});
|
||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("unpacking", true, 1.0, "unpacking");
|
||||
};
|
||||
|
|
@ -88,7 +88,7 @@ Pack.prototype.CancelPack = function()
|
|||
this.SetElapsedTime(0);
|
||||
|
||||
// Clear animation
|
||||
var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
let cmpVisual = Engine.QueryInterface(this.entity, IID_Visual);
|
||||
if (cmpVisual)
|
||||
cmpVisual.SelectAnimation("idle", false, 1.0, "");
|
||||
};
|
||||
|
|
@ -116,87 +116,22 @@ Pack.prototype.SetElapsedTime = function(time)
|
|||
|
||||
Pack.prototype.PackProgress = function(data, lateness)
|
||||
{
|
||||
if (this.elapsedTime >= this.GetPackTime())
|
||||
if (this.elapsedTime < this.GetPackTime())
|
||||
{
|
||||
this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness);
|
||||
return;
|
||||
}
|
||||
|
||||
this.CancelTimer();
|
||||
|
||||
this.packed = !this.packed;
|
||||
this.packing = false;
|
||||
Engine.PostMessage(this.entity, MT_PackFinished, { packed: this.packed });
|
||||
|
||||
// Done un/packing, copy our parameters to the final entity
|
||||
var newEntity = Engine.AddEntity(this.template.Entity);
|
||||
if (newEntity == INVALID_ENTITY)
|
||||
{
|
||||
// Error (e.g. invalid template names)
|
||||
error("PackProgress: Error creating entity for '" + this.template.Entity + "'");
|
||||
return;
|
||||
}
|
||||
let newEntity = ChangeEntityTemplate(this.entity, this.template.Entity);
|
||||
|
||||
var cmpPosition = Engine.QueryInterface(this.entity, IID_Position);
|
||||
var cmpNewPosition = Engine.QueryInterface(newEntity, IID_Position);
|
||||
if (cmpPosition.IsInWorld())
|
||||
{
|
||||
var pos = cmpPosition.GetPosition2D();
|
||||
cmpNewPosition.JumpTo(pos.x, pos.y);
|
||||
}
|
||||
var rot = cmpPosition.GetRotation();
|
||||
cmpNewPosition.SetYRotation(rot.y);
|
||||
cmpNewPosition.SetXZRotation(rot.x, rot.z);
|
||||
cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
|
||||
if (newEntity)
|
||||
PlaySound(this.packed ? "packed" : "unpacked", newEntity);
|
||||
|
||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
var cmpNewOwnership = Engine.QueryInterface(newEntity, IID_Ownership);
|
||||
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
|
||||
|
||||
// rescale capture points
|
||||
var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
|
||||
var cmpNewCapturable = Engine.QueryInterface(newEntity, IID_Capturable);
|
||||
if (cmpCapturable && cmpNewCapturable)
|
||||
{
|
||||
let scale = cmpCapturable.GetMaxCapturePoints() / cmpNewCapturable.GetMaxCapturePoints();
|
||||
let newCp = cmpCapturable.GetCapturePoints().map(function (v) { return v / scale; });
|
||||
cmpNewCapturable.SetCapturePoints(newCp);
|
||||
}
|
||||
|
||||
// Maintain current health level
|
||||
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
|
||||
var cmpNewHealth = Engine.QueryInterface(newEntity, IID_Health);
|
||||
var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
|
||||
cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel));
|
||||
|
||||
var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI);
|
||||
var cmpNewUnitAI = Engine.QueryInterface(newEntity, IID_UnitAI);
|
||||
if (cmpUnitAI && cmpNewUnitAI)
|
||||
{
|
||||
var pos = cmpUnitAI.GetHeldPosition();
|
||||
if (pos)
|
||||
cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
|
||||
if (cmpUnitAI.GetStanceName())
|
||||
cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
|
||||
cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
|
||||
cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf());
|
||||
}
|
||||
|
||||
// Maintain the list of guards
|
||||
var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard);
|
||||
var cmpNewGuard = Engine.QueryInterface(newEntity, IID_Guard);
|
||||
if (cmpGuard && cmpNewGuard)
|
||||
cmpNewGuard.SetEntities(cmpGuard.GetEntities());
|
||||
|
||||
Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: newEntity });
|
||||
|
||||
// Play notification sound
|
||||
var sound = this.packed ? "packed" : "unpacked";
|
||||
PlaySound(sound, newEntity);
|
||||
|
||||
// Destroy current entity
|
||||
Engine.DestroyEntity(this.entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness);
|
||||
}
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Pack, "Pack", Pack);
|
||||
|
|
|
|||
306
binaries/data/mods/public/simulation/components/Upgrade.js
Normal file
306
binaries/data/mods/public/simulation/components/Upgrade.js
Normal file
|
|
@ -0,0 +1,306 @@
|
|||
function Upgrade() {}
|
||||
|
||||
const UPGRADING_PROGRESS_INTERVAL = 250;
|
||||
|
||||
Upgrade.prototype.Schema =
|
||||
"<oneOrMore>" +
|
||||
"<element>" +
|
||||
"<anyName />" +
|
||||
"<interleave>" +
|
||||
"<element name='Entity' a:help='Entity to upgrade to'>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"<optional>" +
|
||||
"<element name='Icon' a:help='Icon to show in the GUI'>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Tooltip' a:help='This will be added to the tooltip to help the player choose why to upgrade.'>" +
|
||||
"<text/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Time' a:help='Time required to upgrade this entity, in milliseconds'>" +
|
||||
"<data type='nonNegativeInteger'/>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Cost' a:help='Resource cost to upgrade this unit'>" +
|
||||
"<oneOrMore>" +
|
||||
"<choice>" +
|
||||
"<element name='food'><data type='nonNegativeInteger'/></element>" +
|
||||
"<element name='wood'><data type='nonNegativeInteger'/></element>" +
|
||||
"<element name='stone'><data type='nonNegativeInteger'/></element>" +
|
||||
"<element name='metal'><data type='nonNegativeInteger'/></element>" +
|
||||
"</choice>" +
|
||||
"</oneOrMore>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='RequiredTechnology' a:help='Define what technology is required for this upgrade'>" +
|
||||
"<choice>" +
|
||||
"<text/>" +
|
||||
"<empty/>" +
|
||||
"</choice>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='CheckPlacementRestrictions' a:help='Upgrading will check for placement restrictions (nb:GUI only)'><empty/></element>" +
|
||||
"</optional>" +
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
"</oneOrMore>";
|
||||
|
||||
Upgrade.prototype.Init = function()
|
||||
{
|
||||
this.upgrading = false;
|
||||
this.elapsedTime = 0;
|
||||
this.timer = undefined;
|
||||
|
||||
this.upgradeTemplates = {};
|
||||
|
||||
for (let choice in this.template)
|
||||
{
|
||||
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
|
||||
let name = this.template[choice].Entity;
|
||||
if (cmpIdentity)
|
||||
name = name.replace(/\{civ\}/g, cmpIdentity.GetCiv());
|
||||
if (this.upgradeTemplates.name)
|
||||
warn("Upgrade Component: entity " + this.entity + " has two upgrades to the same entity, only the last will be used.");
|
||||
this.upgradeTemplates[name] = choice;
|
||||
}
|
||||
};
|
||||
|
||||
// On owner change, abort the upgrade
|
||||
// This will also deal with the "OnDestroy" case.
|
||||
Upgrade.prototype.OnOwnershipChanged = function(msg)
|
||||
{
|
||||
this.CancelUpgrade();
|
||||
|
||||
if (msg.to !== -1)
|
||||
this.owner = msg.to;
|
||||
};
|
||||
|
||||
Upgrade.prototype.ChangeUpgradedEntityCount = function(amount)
|
||||
{
|
||||
if (!this.IsUpgrading())
|
||||
return;
|
||||
|
||||
let cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
let template = cmpTempMan.GetTemplate(this.upgrading);
|
||||
|
||||
let category;
|
||||
if (template.TrainingRestrictions)
|
||||
category = template.TrainingRestrictions.Category;
|
||||
else if (template.BuildRestrictions)
|
||||
category = template.BuildRestrictions.Category;
|
||||
|
||||
if (!category)
|
||||
return;
|
||||
|
||||
let cmpEntityLimits = QueryPlayerIDInterface(this.owner, IID_EntityLimits);
|
||||
cmpEntityLimits.ChangeCount(category, amount);
|
||||
};
|
||||
|
||||
Upgrade.prototype.CanUpgradeTo = function(template)
|
||||
{
|
||||
return this.upgradeTemplates[template] !== undefined;
|
||||
};
|
||||
|
||||
Upgrade.prototype.GetUpgrades = function()
|
||||
{
|
||||
let ret = [];
|
||||
|
||||
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
|
||||
|
||||
for (let option in this.template)
|
||||
{
|
||||
let choice = this.template[option];
|
||||
let entType = choice.Entity;
|
||||
if (cmpIdentity)
|
||||
entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
|
||||
|
||||
let hasCosts;
|
||||
let cost = {};
|
||||
if (choice.Cost)
|
||||
{
|
||||
hasCosts = true;
|
||||
for (let type in choice.Cost)
|
||||
cost[type] = ApplyValueModificationsToTemplate("Upgrade/Cost/"+type, +choice.Cost[type], this.owner, entType);
|
||||
}
|
||||
if (choice.Time)
|
||||
{
|
||||
hasCosts = true;
|
||||
cost.time = ApplyValueModificationsToTemplate("Upgrade/Time", +choice.Time/1000.0, this.owner, entType);
|
||||
}
|
||||
ret.push({
|
||||
"entity": entType,
|
||||
"icon": choice.Icon || undefined,
|
||||
"cost": hasCosts,
|
||||
"tooltip": choice.Tooltip || undefined,
|
||||
"requiredTechnology": this.GetRequiredTechnology(option),
|
||||
});
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
Upgrade.prototype.CancelTimer = function()
|
||||
{
|
||||
if (!this.timer)
|
||||
return;
|
||||
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.CancelTimer(this.timer);
|
||||
this.timer = undefined;
|
||||
};
|
||||
|
||||
Upgrade.prototype.IsUpgrading = function()
|
||||
{
|
||||
return !!this.upgrading;
|
||||
};
|
||||
|
||||
Upgrade.prototype.GetUpgradingTo = function()
|
||||
{
|
||||
return this.upgrading;
|
||||
};
|
||||
|
||||
Upgrade.prototype.WillCheckPlacementRestrictions = function(template)
|
||||
{
|
||||
if (!this.upgradeTemplates[template])
|
||||
return undefined;
|
||||
|
||||
// is undefined by default so use X in Y
|
||||
return "CheckPlacementRestrictions" in this.template[this.upgradeTemplates[template]];
|
||||
};
|
||||
|
||||
Upgrade.prototype.GetRequiredTechnology = function(templateArg)
|
||||
{
|
||||
let choice = this.upgradeTemplates[templateArg] || templateArg
|
||||
|
||||
if (this.template[choice].RequiredTechnology)
|
||||
return this.template[choice].RequiredTechnology;
|
||||
|
||||
if (!("RequiredTechnology" in this.template[choice]))
|
||||
return undefined;
|
||||
|
||||
let cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
let cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity);
|
||||
|
||||
let entType = this.template[choice].Entity;
|
||||
if (cmpIdentity)
|
||||
entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv());
|
||||
|
||||
let template = cmpTemplateManager.GetTemplate(entType);
|
||||
if (template.Identity.RequiredTechnology)
|
||||
return template.Identity.RequiredTechnology;
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
Upgrade.prototype.GetResourceCosts = function(template)
|
||||
{
|
||||
if (!this.upgradeTemplates[template])
|
||||
return undefined;
|
||||
|
||||
let choice = this.upgradeTemplates[template];
|
||||
if (!this.template[choice].Cost)
|
||||
return {};
|
||||
|
||||
let costs = {};
|
||||
for (let r in this.template[choice].Cost)
|
||||
{
|
||||
costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, +this.template[choice].Cost[r], this.entity);
|
||||
}
|
||||
return costs;
|
||||
};
|
||||
|
||||
Upgrade.prototype.Upgrade = function(template)
|
||||
{
|
||||
if (this.IsUpgrading() || !this.upgradeTemplates[template])
|
||||
return false;
|
||||
|
||||
let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
|
||||
|
||||
if (!cmpPlayer.TrySubtractResources(this.GetResourceCosts(template)))
|
||||
return false;
|
||||
|
||||
this.upgrading = template;
|
||||
|
||||
// Prevent cheating
|
||||
this.ChangeUpgradedEntityCount(1);
|
||||
|
||||
if (this.GetUpgradeTime(template) !== 0)
|
||||
{
|
||||
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, { "upgrading": template });
|
||||
}
|
||||
else
|
||||
this.UpgradeProgress();
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
Upgrade.prototype.CancelUpgrade = function()
|
||||
{
|
||||
if (!this.IsUpgrading())
|
||||
return;
|
||||
|
||||
let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
|
||||
if (cmpPlayer)
|
||||
{
|
||||
let costs = this.GetResourceCosts(this.upgrading);
|
||||
cmpPlayer.AddResources(costs);
|
||||
}
|
||||
|
||||
this.ChangeUpgradedEntityCount(-1);
|
||||
|
||||
this.upgrading = false;
|
||||
this.CancelTimer();
|
||||
this.SetElapsedTime(0);
|
||||
};
|
||||
|
||||
Upgrade.prototype.GetUpgradeTime = function(templateArg)
|
||||
{
|
||||
let template = this.upgrading || templateArg;
|
||||
let choice = this.upgradeTemplates[template];
|
||||
if (!choice)
|
||||
return undefined;
|
||||
return this.template[choice].Time ? ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity) : 0;
|
||||
};
|
||||
|
||||
Upgrade.prototype.GetElapsedTime = function()
|
||||
{
|
||||
return this.elapsedTime;
|
||||
};
|
||||
|
||||
Upgrade.prototype.GetProgress = function()
|
||||
{
|
||||
if (!this.IsUpgrading())
|
||||
return undefined;
|
||||
return this.GetUpgradeTime() == 0 ? 1 : this.elapsedTime / this.GetUpgradeTime();
|
||||
};
|
||||
|
||||
Upgrade.prototype.SetElapsedTime = function(time)
|
||||
{
|
||||
this.elapsedTime = time;
|
||||
};
|
||||
|
||||
Upgrade.prototype.UpgradeProgress = function(data, lateness)
|
||||
{
|
||||
if (this.elapsedTime < this.GetUpgradeTime())
|
||||
{
|
||||
this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness);
|
||||
return;
|
||||
}
|
||||
|
||||
this.CancelTimer();
|
||||
|
||||
let newEntity = ChangeEntityTemplate(this.entity, this.upgrading);
|
||||
|
||||
if (newEntity)
|
||||
PlaySound("upgraded", newEntity);
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade);
|
||||
|
|
@ -0,0 +1 @@
|
|||
Engine.RegisterInterface("Upgrade");
|
||||
|
|
@ -581,12 +581,6 @@ var g_Commands = {
|
|||
}
|
||||
},
|
||||
|
||||
"wall-to-gate": function(player, cmd, data)
|
||||
{
|
||||
for (let ent of data.entities)
|
||||
TryTransformWallToGate(ent, data.cmpPlayer, cmd);
|
||||
},
|
||||
|
||||
"lock-gate": function(player, cmd, data)
|
||||
{
|
||||
for (let ent of data.entities)
|
||||
|
|
@ -661,6 +655,69 @@ var g_Commands = {
|
|||
}
|
||||
},
|
||||
|
||||
"upgrade": function(player, cmd, data)
|
||||
{
|
||||
for (let ent of data.entities)
|
||||
{
|
||||
var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
|
||||
|
||||
if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(cmd.template))
|
||||
continue;
|
||||
|
||||
if (cmpUpgrade.WillCheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template))
|
||||
{
|
||||
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
||||
cmpGUIInterface.PushNotification({
|
||||
"players": [data.cmpPlayer.GetPlayerID()],
|
||||
"message": markForTranslation("Cannot upgrade as distance requirements are not verified or terrain is obstructed.")
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!CanGarrisonedChangeTemplate(ent, cmd.template))
|
||||
{
|
||||
var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
|
||||
cmpGUIInterface.PushNotification({
|
||||
"players": [data.cmpPlayer.GetPlayerID()],
|
||||
"message": markForTranslation("Cannot upgrade a garrisoned entity.")
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check entity limits
|
||||
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
var template = cmpTemplateManager.GetTemplate(cmd.template);
|
||||
var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits);
|
||||
if (template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category, 1) ||
|
||||
template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd));
|
||||
continue;
|
||||
}
|
||||
|
||||
var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager);
|
||||
if (cmpUpgrade.GetRequiredTechnology(cmd.template) && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology(cmd.template)))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd));
|
||||
continue;
|
||||
}
|
||||
|
||||
cmpUpgrade.Upgrade(cmd.template, data.cmpPlayer);
|
||||
}
|
||||
},
|
||||
|
||||
"cancel-upgrade": function(player, cmd, data)
|
||||
{
|
||||
for (let ent of data.entities)
|
||||
{
|
||||
let cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade);
|
||||
if (cmpUpgrade)
|
||||
cmpUpgrade.CancelUpgrade(data.cmpPlayer);
|
||||
}
|
||||
},
|
||||
|
||||
"attack-request": function(player, cmd, data)
|
||||
{
|
||||
// Send a chat message to human players
|
||||
|
|
@ -1558,78 +1615,6 @@ function FilterEntityListWithAllies(entities, player, controlAll)
|
|||
return entities.filter(ent => CanControlUnitOrIsAlly(ent, player, controlAll));
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to transform a wall to a gate
|
||||
*/
|
||||
function TryTransformWallToGate(ent, cmpPlayer, cmd)
|
||||
{
|
||||
var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);
|
||||
if (!cmpIdentity)
|
||||
return;
|
||||
|
||||
if (!cmpIdentity.HasClass("LongWall"))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: invalid wall conversion to gate for player: " + uneval(cmd));
|
||||
return;
|
||||
}
|
||||
|
||||
var gate = Engine.AddEntity(cmd.template);
|
||||
|
||||
var cmpCost = Engine.QueryInterface(gate, IID_Cost);
|
||||
if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))
|
||||
{
|
||||
if (g_DebugCommands)
|
||||
warn("Invalid command: convert gate cost check failed: " + uneval(cmd));
|
||||
|
||||
Engine.DestroyEntity(gate);
|
||||
return;
|
||||
}
|
||||
|
||||
ReplaceBuildingWith(ent, gate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unconditionally replace a building with another one
|
||||
*/
|
||||
function ReplaceBuildingWith(ent, building)
|
||||
{
|
||||
// Move the building to the right place
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);
|
||||
var pos = cmpPosition.GetPosition2D();
|
||||
cmpBuildingPosition.JumpTo(pos.x, pos.y);
|
||||
var rot = cmpPosition.GetRotation();
|
||||
cmpBuildingPosition.SetYRotation(rot.y);
|
||||
cmpBuildingPosition.SetXZRotation(rot.x, rot.z);
|
||||
|
||||
// Copy ownership
|
||||
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||
var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);
|
||||
cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());
|
||||
|
||||
// Copy control groups
|
||||
var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);
|
||||
var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);
|
||||
cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
|
||||
cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
|
||||
|
||||
// Copy health level from the old entity to the new
|
||||
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
|
||||
var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);
|
||||
var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
|
||||
var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);
|
||||
cmpBuildingHealth.SetHitpoints(buildingHitpoints);
|
||||
|
||||
PlaySound("constructed", building);
|
||||
|
||||
Engine.PostMessage(ent, MT_ConstructionFinished,
|
||||
{ "entity": ent, "newentity": building });
|
||||
Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });
|
||||
|
||||
Engine.DestroyEntity(ent);
|
||||
}
|
||||
|
||||
Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements);
|
||||
Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation);
|
||||
Engine.RegisterGlobal("GetDockAngle", GetDockAngle);
|
||||
|
|
|
|||
206
binaries/data/mods/public/simulation/helpers/Transform.js
Normal file
206
binaries/data/mods/public/simulation/helpers/Transform.js
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
// Helper functions to change an entity's template and check if the transformation is possible
|
||||
|
||||
// returns the ID of the new entity or INVALID_ENTITY.
|
||||
function ChangeEntityTemplate(oldEnt, newTemplate)
|
||||
{
|
||||
// Done un/packing, copy our parameters to the final entity
|
||||
var newEnt = Engine.AddEntity(newTemplate);
|
||||
if (newEnt == INVALID_ENTITY)
|
||||
{
|
||||
error("Transform.js: Error replacing entity " + oldEnt + " for a '" + newTemplate + "'");
|
||||
return INVALID_ENTITY;
|
||||
}
|
||||
|
||||
var cmpPosition = Engine.QueryInterface(oldEnt, IID_Position);
|
||||
var cmpNewPosition = Engine.QueryInterface(newEnt, IID_Position);
|
||||
if (cmpPosition && cmpNewPosition)
|
||||
{
|
||||
if (cmpPosition.IsInWorld())
|
||||
{
|
||||
var pos = cmpPosition.GetPosition2D();
|
||||
cmpNewPosition.JumpTo(pos.x, pos.y);
|
||||
}
|
||||
var rot = cmpPosition.GetRotation();
|
||||
cmpNewPosition.SetYRotation(rot.y);
|
||||
cmpNewPosition.SetXZRotation(rot.x, rot.z);
|
||||
cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset());
|
||||
}
|
||||
|
||||
var cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership);
|
||||
var cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership);
|
||||
if (cmpOwnership && cmpNewOwnership)
|
||||
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
|
||||
|
||||
// Copy control groups
|
||||
var cmpObstruction = Engine.QueryInterface(oldEnt, IID_Obstruction);
|
||||
var cmpNewObstruction = Engine.QueryInterface(newEnt, IID_Obstruction);
|
||||
if (cmpObstruction && cmpNewObstruction)
|
||||
{
|
||||
cmpNewObstruction.SetControlGroup(cmpObstruction.GetControlGroup());
|
||||
cmpNewObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());
|
||||
}
|
||||
|
||||
// Rescale capture points
|
||||
var cmpCapturable = Engine.QueryInterface(oldEnt, IID_Capturable);
|
||||
var cmpNewCapturable = Engine.QueryInterface(newEnt, IID_Capturable);
|
||||
if (cmpCapturable && cmpNewCapturable)
|
||||
{
|
||||
let scale = cmpCapturable.GetMaxCapturePoints() / cmpNewCapturable.GetMaxCapturePoints();
|
||||
let newCp = cmpCapturable.GetCapturePoints().map(v => v / scale);
|
||||
cmpNewCapturable.SetCapturePoints(newCp);
|
||||
}
|
||||
|
||||
// Maintain current health level
|
||||
var cmpHealth = Engine.QueryInterface(oldEnt, IID_Health);
|
||||
var cmpNewHealth = Engine.QueryInterface(newEnt, IID_Health);
|
||||
if (cmpHealth && cmpNewHealth)
|
||||
{
|
||||
var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));
|
||||
cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel));
|
||||
}
|
||||
|
||||
var cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
|
||||
var cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI);
|
||||
if (cmpUnitAI && cmpNewUnitAI)
|
||||
{
|
||||
var pos = cmpUnitAI.GetHeldPosition();
|
||||
if (pos)
|
||||
cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
|
||||
if (cmpUnitAI.GetStanceName())
|
||||
cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
|
||||
cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
|
||||
cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf());
|
||||
}
|
||||
|
||||
// Maintain the list of guards
|
||||
var cmpGuard = Engine.QueryInterface(oldEnt, IID_Guard);
|
||||
var cmpNewGuard = Engine.QueryInterface(newEnt, IID_Guard);
|
||||
if (cmpGuard && cmpNewGuard)
|
||||
cmpNewGuard.SetEntities(cmpGuard.GetEntities());
|
||||
|
||||
TransferGarrisonedUnits(oldEnt, newEnt);
|
||||
|
||||
Engine.BroadcastMessage(MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
|
||||
|
||||
Engine.DestroyEntity(oldEnt);
|
||||
|
||||
return newEnt;
|
||||
};
|
||||
|
||||
function CanGarrisonedChangeTemplate(ent, template)
|
||||
{
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
var unitAI = Engine.QueryInterface(ent, IID_UnitAI);
|
||||
if (cmpPosition && !cmpPosition.IsInWorld() && unitAI && unitAI.IsGarrisoned())
|
||||
{
|
||||
// We're a garrisoned unit, assume impossibility as I've been unable to find a way to get the holder ID.
|
||||
// TODO: change this if that ever becomes possibles
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function ObstructionsBlockingTemplateChange(ent, templateArg)
|
||||
{
|
||||
var previewEntity = Engine.AddEntity("preview|"+templateArg);
|
||||
|
||||
if (previewEntity == INVALID_ENTITY)
|
||||
return true;
|
||||
|
||||
var cmpBuildRestrictions = Engine.QueryInterface(previewEntity, IID_BuildRestrictions);
|
||||
var cmpPosition = Engine.QueryInterface(ent, IID_Position);
|
||||
var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
|
||||
|
||||
var cmpNewPosition = Engine.QueryInterface(previewEntity, IID_Position);
|
||||
|
||||
// Return false if no ownership as BuildRestrictions.CheckPlacement needs an owner and I have no idea if false or true is better
|
||||
// Plus there are no real entities without owners currently.
|
||||
if (!cmpBuildRestrictions || !cmpPosition || !cmpOwnership)
|
||||
return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
|
||||
|
||||
var pos = cmpPosition.GetPosition2D();
|
||||
var angle = cmpPosition.GetRotation();
|
||||
// move us away to prevent our own obstruction from blocking the upgrade.
|
||||
cmpPosition.MoveOutOfWorld();
|
||||
|
||||
cmpNewPosition.JumpTo(pos.x, pos.y);
|
||||
cmpNewPosition.SetYRotation(angle.y);
|
||||
|
||||
var cmpNewOwnership = Engine.QueryInterface(previewEntity, IID_Ownership);
|
||||
cmpNewOwnership.SetOwner(cmpOwnership.GetOwner());
|
||||
|
||||
var checkPlacement = cmpBuildRestrictions.CheckPlacement();
|
||||
|
||||
if (checkPlacement && !checkPlacement.success)
|
||||
return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
|
||||
|
||||
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
|
||||
var template = cmpTemplateManager.GetTemplate(cmpTemplateManager.GetCurrentTemplateName(ent));
|
||||
var newTemplate = cmpTemplateManager.GetTemplate(templateArg);
|
||||
|
||||
// Check if units are blocking our template change
|
||||
if (template.Obstruction && newTemplate.Obstruction)
|
||||
{
|
||||
// This only needs to be done if the new template is strictly bigger than the old one
|
||||
// "Obstructions" are annoying to test so just check.
|
||||
if (newTemplate.Obstruction.Obstructions ||
|
||||
|
||||
newTemplate.Obstruction.Static && template.Obstruction.Static &&
|
||||
(newTemplate.Obstruction.Static["@width"] > template.Obstruction.Static["@width"] ||
|
||||
newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Static["@depth"]) ||
|
||||
newTemplate.Obstruction.Static && template.Obstruction.Unit &&
|
||||
(newTemplate.Obstruction.Static["@width"] > template.Obstruction.Unit["@radius"] ||
|
||||
newTemplate.Obstruction.Static["@depth"] > template.Obstruction.Unit["@radius"]) ||
|
||||
|
||||
newTemplate.Obstruction.Unit && template.Obstruction.Unit &&
|
||||
newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Unit["@radius"] ||
|
||||
newTemplate.Obstruction.Unit && template.Obstruction.Static &&
|
||||
(newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@width"] ||
|
||||
newTemplate.Obstruction.Unit["@radius"] > template.Obstruction.Static["@depth"]))
|
||||
{
|
||||
var cmpNewObstruction = Engine.QueryInterface(previewEntity, IID_Obstruction);
|
||||
if (cmpNewObstruction && cmpNewObstruction.GetBlockMovementFlag())
|
||||
{
|
||||
// Check for units
|
||||
var collisions = cmpNewObstruction.GetEntityCollisions(false, true);
|
||||
if (collisions.length)
|
||||
return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DeleteEntityAndReturn(previewEntity, cmpPosition, pos, angle, cmpNewPosition, false);
|
||||
};
|
||||
|
||||
function DeleteEntityAndReturn(ent, cmpPosition, position, angle, cmpNewPosition, ret)
|
||||
{
|
||||
// prevent preview from interfering in the world
|
||||
cmpNewPosition.MoveOutOfWorld();
|
||||
cmpPosition.JumpTo(position.x, position.y);
|
||||
cmpPosition.SetYRotation(angle.y);
|
||||
|
||||
Engine.DestroyEntity(ent);
|
||||
return ret;
|
||||
};
|
||||
|
||||
function TransferGarrisonedUnits(oldEnt, newEnt)
|
||||
{
|
||||
// Transfer garrisoned units if possible, or unload them
|
||||
var cmpGarrison = Engine.QueryInterface(oldEnt, IID_GarrisonHolder);
|
||||
var cmpNewGarrison = Engine.QueryInterface(newEnt, IID_GarrisonHolder);
|
||||
if (!cmpNewGarrison || !cmpGarrison || !cmpGarrison.GetEntities().length)
|
||||
return; // nothing to do as the code will by default unload all.
|
||||
|
||||
var garrisonedEntities = cmpGarrison.GetEntities().slice();
|
||||
for (let j in garrisonedEntities)
|
||||
{
|
||||
var cmpUnitAI = Engine.QueryInterface(garrisonedEntities[j], IID_UnitAI);
|
||||
cmpGarrison.Eject(garrisonedEntities[j]);
|
||||
cmpUnitAI.Autogarrison(newEnt);
|
||||
cmpNewGarrison.Garrison(garrisonedEntities[j]);
|
||||
}
|
||||
};
|
||||
|
||||
Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate);
|
||||
Engine.RegisterGlobal("CanGarrisonedChangeTemplate", CanGarrisonedChangeTemplate);
|
||||
Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange);
|
||||
|
|
@ -26,7 +26,6 @@
|
|||
<SelectionGroupName>other/wallset_palisade</SelectionGroupName>
|
||||
<SpecificName>Palisade</SpecificName>
|
||||
<GenericName>Wooden Wall</GenericName>
|
||||
<GateConversionTooltip>Convert Wooden Wall into Wooden Gate</GateConversionTooltip>
|
||||
<Classes datatype="tokens">-StoneWall Palisade</Classes>
|
||||
<Icon>gaia/special_palisade.png</Icon>
|
||||
<RequiredTechnology>phase_village</RequiredTechnology>
|
||||
|
|
@ -45,4 +44,14 @@
|
|||
<WallPiece>
|
||||
<Length>11.0</Length>
|
||||
</WallPiece>
|
||||
<Upgrade>
|
||||
<Gate>
|
||||
<Entity>other/palisades_rocks_gate</Entity>
|
||||
<Cost>
|
||||
<stone>0</stone>
|
||||
<wood>20</wood>
|
||||
</Cost>
|
||||
<Time>5000</Time>
|
||||
</Gate>
|
||||
</Upgrade>
|
||||
</Entity>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@
|
|||
</Classes>
|
||||
<Icon>structures/palisade_wall.png</Icon>
|
||||
<Tooltip>A wooden and turf palisade buildable in enemy and neutral territories.</Tooltip>
|
||||
<GateConversionTooltip>Convert Siege Wall into Siege Wall Gate</GateConversionTooltip>
|
||||
<History>Quick building, but expensive wooden and earthen walls used to surround and siege an enemy town or fortified position. The most famous examples are the Roman sieges of the Iberian stronghold of Numantia and the Gallic stronghold of Alesia.</History>
|
||||
</Identity>
|
||||
<Obstruction>
|
||||
|
|
|
|||
|
|
@ -38,6 +38,15 @@
|
|||
<Identity>
|
||||
<Classes datatype="tokens">LongWall</Classes>
|
||||
<Tooltip>Long wall segments can be converted to gates.</Tooltip>
|
||||
<GateConversionTooltip>Convert Stone Wall into City Gate</GateConversionTooltip>
|
||||
</Identity>
|
||||
<Upgrade>
|
||||
<Gate>
|
||||
<Entity>structures/{civ}_wall_gate</Entity>
|
||||
<Tooltip>This will allow you to let units circulate through your fortifications.</Tooltip>
|
||||
<Cost>
|
||||
<stone>60</stone>
|
||||
</Cost>
|
||||
<Time>10000</Time>
|
||||
</Gate>
|
||||
</Upgrade>
|
||||
</Entity>
|
||||
|
|
|
|||
Loading…
Reference in a new issue