mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-19 23:03:56 -07:00
parent
751c46c60b
commit
ad27deeb9d
49 changed files with 607 additions and 109 deletions
|
|
@ -90,18 +90,28 @@ function GetTemplateDataHelper(template, player)
|
|||
|
||||
if (template.Attack)
|
||||
{
|
||||
ret.attack = {};
|
||||
for (var type in template.Attack)
|
||||
let getAttackStat = function(type, stat)
|
||||
{
|
||||
ret.attack[type] = {
|
||||
"hack": func("Attack/"+type+"/Hack", +(template.Attack[type].Hack || 0), player, template),
|
||||
"pierce": func("Attack/"+type+"/Pierce", +(template.Attack[type].Pierce || 0), player, template),
|
||||
"crush": func("Attack/"+type+"/Crush", +(template.Attack[type].Crush || 0), player, template),
|
||||
"minRange": func("Attack/"+type+"/MinRange", +(template.Attack[type].MinRange || 0), player, template),
|
||||
"maxRange": func("Attack/"+type+"/MaxRange", +template.Attack[type].MaxRange, player, template),
|
||||
"elevationBonus": func("Attack/"+type+"/ElevationBonus", +(template.Attack[type].ElevationBonus || 0), player, template),
|
||||
"repeatTime": +(template.Attack[type].RepeatTime || 0),
|
||||
};
|
||||
return func("Attack/"+type+"/"+stat, +(template.Attack[type][stat] || 0), player, template);
|
||||
};
|
||||
|
||||
ret.attack = {};
|
||||
for (let type in template.Attack)
|
||||
{
|
||||
if (type == "Capture")
|
||||
ret.attack.Capture = {
|
||||
"value": getAttackStat(type,"Value"),
|
||||
};
|
||||
else
|
||||
ret.attack[type] = {
|
||||
"hack": getAttackStat(type, "Hack"),
|
||||
"pierce": getAttackStat(type, "Pierce"),
|
||||
"crush": getAttackStat(type, "Crush"),
|
||||
"minRange": getAttackStat(type, "MinRange"),
|
||||
"maxRange": getAttackStat(type, "MaxRange"),
|
||||
"elevationBonus": getAttackStat(type, "ElevationBonus"),
|
||||
};
|
||||
ret.attack[type].repeatTime = +(template.Attack[type].RepeatTime || 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,12 +166,16 @@ function initGameSpeeds()
|
|||
// ====================================================================
|
||||
|
||||
// Convert integer color values to string (for use in GUI objects)
|
||||
function rgbToGuiColor(color)
|
||||
function rgbToGuiColor(color, alpha)
|
||||
{
|
||||
var ret;
|
||||
if (color && ("r" in color) && ("g" in color) && ("b" in color))
|
||||
return color.r + " " + color.g + " " + color.b;
|
||||
|
||||
return "0 0 0";
|
||||
ret = color.r + " " + color.g + " " + color.b;
|
||||
else
|
||||
ret = "0 0 0";
|
||||
if (alpha)
|
||||
ret += " " + alpha;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
|
|
|
|||
|
|
@ -140,6 +140,7 @@ function getAttackTypeLabel(type)
|
|||
if (type === "Charge") return translate("Charge Attack:");
|
||||
if (type === "Melee") return translate("Melee Attack:");
|
||||
if (type === "Ranged") return translate("Ranged Attack:");
|
||||
if (type === "Capture") return translate("Capture Attack:");
|
||||
|
||||
warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { attackType: type }));
|
||||
return translate("Attack:");
|
||||
|
|
@ -169,6 +170,15 @@ function getAttackTooltip(template)
|
|||
});
|
||||
|
||||
var attackLabel = txtFormats.header[0] + getAttackTypeLabel(type) + txtFormats.header[1];
|
||||
if (type == "Capture")
|
||||
{
|
||||
attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
|
||||
attackLabel: attackLabel,
|
||||
details: template.attack[type].value,
|
||||
rate: rate
|
||||
}));
|
||||
continue;
|
||||
}
|
||||
if (type != "Ranged")
|
||||
{
|
||||
attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), {
|
||||
|
|
@ -206,7 +216,7 @@ function getAttackTooltip(template)
|
|||
}));
|
||||
}
|
||||
|
||||
return attacks.join(translate(", "));
|
||||
return attacks.join("\n");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ function displaySingle(entState, template)
|
|||
}
|
||||
|
||||
// Hitpoints
|
||||
Engine.GetGUIObjectByName("healthSection").hidden = !entState.hitpoints;
|
||||
if (entState.hitpoints)
|
||||
{
|
||||
var unitHealthBar = Engine.GetGUIObjectByName("healthBar");
|
||||
|
|
@ -79,21 +80,44 @@ function displaySingle(entState, template)
|
|||
hitpoints: Math.ceil(entState.hitpoints),
|
||||
maxHitpoints: entState.maxHitpoints
|
||||
});
|
||||
Engine.GetGUIObjectByName("healthSection").hidden = false;
|
||||
}
|
||||
else
|
||||
|
||||
// CapturePoints
|
||||
Engine.GetGUIObjectByName("captureSection").hidden = !entState.capturePoints;
|
||||
if (entState.capturePoints)
|
||||
{
|
||||
Engine.GetGUIObjectByName("healthSection").hidden = true;
|
||||
let setCaptureBarPart = function(playerID, startSize)
|
||||
{
|
||||
var unitCaptureBar = Engine.GetGUIObjectByName("captureBar["+playerID+"]");
|
||||
var sizeObj = unitCaptureBar.size;
|
||||
sizeObj.rleft = startSize;
|
||||
|
||||
var size = 100*Math.max(0, Math.min(1, entState.capturePoints[playerID] / entState.maxCapturePoints));
|
||||
sizeObj.rright = startSize + size;
|
||||
unitCaptureBar.size = sizeObj;
|
||||
unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128);
|
||||
unitCaptureBar.hidden=false;
|
||||
return startSize + size;
|
||||
}
|
||||
|
||||
// first handle the owner's points, to keep those points on the left for clarity
|
||||
let size = setCaptureBarPart(entState.player, 0);
|
||||
|
||||
for (let i in entState.capturePoints)
|
||||
if (i != entState.player)
|
||||
size = setCaptureBarPart(i, size);
|
||||
|
||||
|
||||
Engine.GetGUIObjectByName("captureStats").caption = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), {
|
||||
capturePoints: Math.ceil(entState.capturePoints[entState.player]),
|
||||
maxCapturePoints: entState.maxCapturePoints
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Stamina
|
||||
var player = Engine.GetPlayerID();
|
||||
if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))
|
||||
Engine.GetGUIObjectByName("staminaSection").hidden = false;
|
||||
else
|
||||
Engine.GetGUIObjectByName("staminaSection").hidden = true;
|
||||
|
||||
// Experience
|
||||
Engine.GetGUIObjectByName("experience").hidden = !entState.promotion;
|
||||
if (entState.promotion)
|
||||
{
|
||||
var experienceBar = Engine.GetGUIObjectByName("experienceBar");
|
||||
|
|
@ -112,14 +136,10 @@ function displaySingle(entState, template)
|
|||
experience: "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]",
|
||||
current: Math.floor(entState.promotion.curr)
|
||||
});
|
||||
Engine.GetGUIObjectByName("experience").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.GetGUIObjectByName("experience").hidden = true;
|
||||
}
|
||||
|
||||
// Resource stats
|
||||
Engine.GetGUIObjectByName("resourceSection").hidden = !entState.resourceSupply;
|
||||
if (entState.resourceSupply)
|
||||
{
|
||||
var resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol
|
||||
|
|
@ -136,15 +156,10 @@ function displaySingle(entState, template)
|
|||
Engine.GetGUIObjectByName("resourceStats").caption = resources;
|
||||
|
||||
if (entState.hitpoints)
|
||||
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("staminaSection").size;
|
||||
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("captureSection").size;
|
||||
else
|
||||
Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("healthSection").size;
|
||||
|
||||
Engine.GetGUIObjectByName("resourceSection").hidden = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Engine.GetGUIObjectByName("resourceSection").hidden = true;
|
||||
}
|
||||
|
||||
// Resource carrying
|
||||
|
|
@ -290,20 +305,29 @@ function displayMultiple(selection, template)
|
|||
{
|
||||
var averageHealth = 0;
|
||||
var maxHealth = 0;
|
||||
var maxCapturePoints = 0;
|
||||
var capturePoints = (new Array(9)).fill(0);
|
||||
var playerID = 0;
|
||||
|
||||
for (var i = 0; i < selection.length; i++)
|
||||
{
|
||||
var entState = GetEntityState(selection[i])
|
||||
if (entState)
|
||||
if (!entState)
|
||||
continue;
|
||||
playerID = entState.player; // trust that all selected entities have the same owner
|
||||
if (entState.hitpoints)
|
||||
{
|
||||
if (entState.hitpoints)
|
||||
{
|
||||
averageHealth += entState.hitpoints;
|
||||
maxHealth += entState.maxHitpoints;
|
||||
}
|
||||
averageHealth += entState.hitpoints;
|
||||
maxHealth += entState.maxHitpoints;
|
||||
}
|
||||
if (entState.capturePoints)
|
||||
{
|
||||
maxCapturePoints += entState.maxCapturePoints;
|
||||
capturePoints = entState.capturePoints.map(function(v, i) { return v + capturePoints[i]; });
|
||||
}
|
||||
}
|
||||
|
||||
Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0;
|
||||
if (averageHealth > 0)
|
||||
{
|
||||
var unitHealthBar = Engine.GetGUIObjectByName("healthBarMultiple");
|
||||
|
|
@ -313,13 +337,37 @@ function displayMultiple(selection, template)
|
|||
|
||||
var hitpointsLabel = "[font=\"sans-bold-13\"]" + translate("Hitpoints:") + "[/font]"
|
||||
var hitpoints = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: hitpointsLabel, current: averageHealth, max: maxHealth });
|
||||
var healthMultiple = Engine.GetGUIObjectByName("healthMultiple");
|
||||
healthMultiple.tooltip = hitpoints;
|
||||
healthMultiple.hidden = false;
|
||||
Engine.GetGUIObjectByName("healthMultiple").tooltip = hitpoints;
|
||||
}
|
||||
else
|
||||
|
||||
Engine.GetGUIObjectByName("captureMultiple").hidden = maxCapturePoints <= 0;
|
||||
if (maxCapturePoints > 0)
|
||||
{
|
||||
Engine.GetGUIObjectByName("healthMultiple").hidden = true;
|
||||
let setCaptureBarPart = function(playerID, startSize)
|
||||
{
|
||||
var unitCaptureBar = Engine.GetGUIObjectByName("captureBarMultiple["+playerID+"]");
|
||||
var sizeObj = unitCaptureBar.size;
|
||||
sizeObj.rtop = startSize;
|
||||
|
||||
var size = 100*Math.max(0, Math.min(1, capturePoints[playerID] / maxCapturePoints));
|
||||
sizeObj.rbottom = startSize + size;
|
||||
unitCaptureBar.size = sizeObj;
|
||||
unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128);
|
||||
unitCaptureBar.hidden=false;
|
||||
return startSize + size;
|
||||
}
|
||||
|
||||
let size = 0;
|
||||
for (let i in entState.capturePoints)
|
||||
if (i != playerID)
|
||||
size = setCaptureBarPart(i, size);
|
||||
|
||||
// last handle the owner's points, to keep those points on the bottom for clarity
|
||||
setCaptureBarPart(playerID, size);
|
||||
|
||||
var capturePointsLabel = "[font=\"sans-bold-13\"]" + translate("Capture points:") + "[/font]"
|
||||
var capturePointsTooltip = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: capturePointsLabel, current: Math.ceil(capturePoints[playerID]), max: Math.ceil(maxCapturePoints) });
|
||||
Engine.GetGUIObjectByName("captureMultiple").tooltip = capturePointsTooltip;
|
||||
}
|
||||
|
||||
// TODO: Stamina
|
||||
|
|
|
|||
|
|
@ -33,12 +33,13 @@
|
|||
<object type="image" sprite="statsBarShaderVertical" ghost="true"/>
|
||||
</object>
|
||||
|
||||
<!-- Stamina bar -->
|
||||
<object size="15 0 22 100%" type="image" name="staminaMultiple" tooltip_style="sessionToolTipBold">
|
||||
<translatableAttribute id="tooltip">Stamina</translatableAttribute>
|
||||
<!-- Capture bar -->
|
||||
<object size="15 0 22 100%" type="image" name="captureMultiple" tooltip_style="sessionToolTipBold">
|
||||
<translatableAttribute id="tooltip">Capture points</translatableAttribute>
|
||||
<object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
|
||||
<object type="image" sprite="staminaBackground" ghost="true"/>
|
||||
<object type="image" sprite="staminaForeground" ghost="true" name="staminaBarMultiple"/>
|
||||
<repeat count="9">
|
||||
<object type="image" sprite="playerColorBackground" ghost="true" name="captureBarMultiple[n]" hidden="true"/>
|
||||
</repeat>
|
||||
<object type="image" sprite="statsBarShaderVertical" ghost="true"/>
|
||||
</object>
|
||||
</object>
|
||||
|
|
|
|||
|
|
@ -20,16 +20,17 @@
|
|||
</object>
|
||||
</object>
|
||||
|
||||
<!-- Stamina bar -->
|
||||
<object size="88 28 100% 52" name="staminaSection">
|
||||
<object size="0 0 100% 16" name="staminaLabel" type="text" style="StatsTextLeft" ghost="true">
|
||||
<translatableAttribute id="tooltip">Stamina:</translatableAttribute>
|
||||
<!-- Capture bar -->
|
||||
<object size="88 28 100% 52" name="captureSection">
|
||||
<object size="0 0 100% 16" name="captureLabel" type="text" style="StatsTextLeft" ghost="true">
|
||||
<translatableAttribute id="tooltip">Capture points:</translatableAttribute>
|
||||
</object>
|
||||
<object size="0 0 100% 16" name="staminaStats" type="text" style="StatsTextRight" ghost="true"/>
|
||||
<object size="1 16 100% 23" name="stamina" type="image">
|
||||
<object size="0 0 100% 16" name="captureStats" type="text" style="StatsTextRight" ghost="true"/>
|
||||
<object size="1 16 100% 23" name="capture" type="image">
|
||||
<object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/>
|
||||
<object type="image" sprite="staminaBackground" ghost="true"/>
|
||||
<object type="image" sprite="staminaForeground" ghost="true" name="staminaBar"/>
|
||||
<repeat count="9">
|
||||
<object type="image" sprite="playerColorBackground" ghost="true" name="captureBar[n]" hidden="true"/>
|
||||
</repeat>
|
||||
<object type="image" sprite="statsBarShaderHorizontal" ghost="true"/>
|
||||
</object>
|
||||
</object>
|
||||
|
|
|
|||
|
|
@ -99,9 +99,7 @@ var unitActions =
|
|||
{
|
||||
if (!entState.attack || !targetState.hitpoints)
|
||||
return false;
|
||||
if (playerCheck(entState, targetState, ["Neutral", "Enemy"]))
|
||||
return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})};
|
||||
return false;
|
||||
return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})};
|
||||
},
|
||||
"hotkeyActionCheck": function(target)
|
||||
{
|
||||
|
|
@ -672,6 +670,13 @@ var g_EntityCommands =
|
|||
"icon": "kill_small.png"
|
||||
};
|
||||
|
||||
if (entState.capturePoints && entState.capturePoints[entState.player] < entState.maxCapturePoints / 2)
|
||||
return {
|
||||
"tooltip": translate("You cannot destroy this entity as you own less than half the capture points"),
|
||||
"icon": "kill_small.png"
|
||||
};
|
||||
|
||||
|
||||
return {
|
||||
"tooltip": translate("Delete"),
|
||||
"icon": "kill_small.png"
|
||||
|
|
|
|||
|
|
@ -111,6 +111,13 @@ AIProxy.prototype.OnHealthChanged = function(msg)
|
|||
this.changes.hitpoints = msg.to;
|
||||
};
|
||||
|
||||
AIProxy.prototype.OnCapturePointsChanged = function(msg)
|
||||
{
|
||||
if (!this.NotifyChange())
|
||||
return;
|
||||
this.changes.capturePoints = msg.capturePoints;
|
||||
};
|
||||
|
||||
AIProxy.prototype.OnUnitIdleChanged = function(msg)
|
||||
{
|
||||
if (!this.NotifyChange())
|
||||
|
|
|
|||
|
|
@ -158,6 +158,20 @@ Attack.prototype.Schema =
|
|||
"</interleave>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Capture'>" +
|
||||
"<interleave>" +
|
||||
"<element name='Value' a:help='Capture points value'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='MaxRange' a:help='Maximum attack range (in meters)'><ref name='nonNegativeDecimal'/></element>" +
|
||||
"<element name='RepeatTime' a:help='Time between attacks (in milliseconds). The attack animation will be stretched to match this time'>" + // TODO: it shouldn't be stretched
|
||||
"<data type='positiveInteger'/>" +
|
||||
"</element>" +
|
||||
Attack.prototype.bonusesSchema +
|
||||
Attack.prototype.preferredClassesSchema +
|
||||
Attack.prototype.restrictedClassesSchema +
|
||||
"</interleave>" +
|
||||
"</element>" +
|
||||
"</optional>" +
|
||||
"<optional>" +
|
||||
"<element name='Charge'>" +
|
||||
"<interleave>" +
|
||||
|
|
@ -198,6 +212,7 @@ Attack.prototype.GetAttackTypes = function()
|
|||
if (this.template.Charge) ret.push("Charge");
|
||||
if (this.template.Melee) ret.push("Melee");
|
||||
if (this.template.Ranged) ret.push("Ranged");
|
||||
if (this.template.Capture) ret.push("Capture");
|
||||
return ret;
|
||||
};
|
||||
|
||||
|
|
@ -223,6 +238,10 @@ Attack.prototype.GetRestrictedClasses = function(type)
|
|||
|
||||
Attack.prototype.CanAttack = function(target)
|
||||
{
|
||||
var cmpArmour = Engine.QueryInterface(target, IID_DamageReceiver);
|
||||
if (!cmpArmour)
|
||||
return false;
|
||||
|
||||
var cmpFormation = Engine.QueryInterface(target, IID_Formation);
|
||||
if (cmpFormation)
|
||||
return true;
|
||||
|
|
@ -309,23 +328,40 @@ Attack.prototype.GetBestAttackAgainst = function(target)
|
|||
if (cmpFormation)
|
||||
return this.GetBestAttack();
|
||||
|
||||
const cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
||||
var cmpIdentity = Engine.QueryInterface(target, IID_Identity);
|
||||
if (!cmpIdentity)
|
||||
return undefined;
|
||||
|
||||
const targetClasses = cmpIdentity.GetClassesList();
|
||||
const isTargetClass = function (value, i, a) { return targetClasses.indexOf(value) != -1; };
|
||||
const types = this.GetAttackTypes();
|
||||
const attack = this;
|
||||
const isAllowed = function (value, i, a) { return !attack.GetRestrictedClasses(value).some(isTargetClass); }
|
||||
const isPreferred = function (value, i, a) { return attack.GetPreferredClasses(value).some(isTargetClass); }
|
||||
const byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }
|
||||
|
||||
var targetClasses = cmpIdentity.GetClassesList();
|
||||
var isTargetClass = function (className) { return targetClasses.indexOf(className) != -1; };
|
||||
|
||||
// Always slaughter domestic animals instead of using a normal attack
|
||||
if (isTargetClass("Domestic") && this.template.Slaughter)
|
||||
return "Slaughter";
|
||||
|
||||
return types.filter(isAllowed).sort(byPreference).pop();
|
||||
var attack = this;
|
||||
var isAllowed = function (type) { return !attack.GetRestrictedClasses(type).some(isTargetClass); }
|
||||
|
||||
var types = this.GetAttackTypes().filter(isAllowed);
|
||||
|
||||
// check if the target is capturable
|
||||
var captureIndex = types.indexOf("Capture")
|
||||
if (captureIndex != -1)
|
||||
{
|
||||
var cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
|
||||
var cmpPlayer = QueryOwnerInterface(this.entity);
|
||||
if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID()))
|
||||
return "Capture";
|
||||
// not captureable, so remove this attack
|
||||
types.splice(captureIndex, 1);
|
||||
}
|
||||
|
||||
var isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); }
|
||||
var byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }
|
||||
|
||||
|
||||
return types.sort(byPreference).pop();
|
||||
};
|
||||
|
||||
Attack.prototype.CompareEntitiesByPreference = function(a, b)
|
||||
|
|
@ -367,7 +403,10 @@ Attack.prototype.GetAttackStrengths = function(type)
|
|||
{
|
||||
return ApplyValueModificationsToEntity("Attack/" + type + splash + "/" + damageType, +(template[damageType] || 0), self.entity);
|
||||
};
|
||||
|
||||
|
||||
if (type == "Capture")
|
||||
return {value: applyMods("Value")};
|
||||
|
||||
return {
|
||||
hack: applyMods("Hack"),
|
||||
pierce: applyMods("Pierce"),
|
||||
|
|
@ -517,6 +556,25 @@ Attack.prototype.PerformAttack = function(type, target)
|
|||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget*1000, {"type": type, "target": target, "position": realTargetPosition, "direction": missileDirection, "projectileId": id, "playerId":playerId});
|
||||
}
|
||||
else if (type == "Capture")
|
||||
{
|
||||
var multiplier = this.GetAttackBonus(type, target);
|
||||
var cmpHealth = Engine.QueryInterface(target, IID_Health);
|
||||
if (!cmpHealth || cmpHealth.GetHitpoints() == 0)
|
||||
return;
|
||||
multiplier *= cmpHealth.GetMaxHitpoints() / cmpHealth.GetHitpoints();
|
||||
|
||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
|
||||
return;
|
||||
var owner = cmpOwnership.GetOwner();
|
||||
var cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
|
||||
if (!cmpCapturable || !cmpCapturable.CanCapture(owner))
|
||||
return;
|
||||
|
||||
var strength = this.GetAttackStrengths("Capture").value;
|
||||
cmpCapturable.Reduce(strength * multiplier, owner);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Melee attack - hurt the target immediately
|
||||
|
|
@ -585,7 +643,7 @@ Attack.prototype.MissileHit = function(data, lateness)
|
|||
// If friendlyFire isn't enabled, get all player enemies to pass to "Damage.CauseSplashDamage".
|
||||
if (friendlyFire == "false")
|
||||
{
|
||||
var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)
|
||||
var cmpPlayer = QueryPlayerIDInterface(data.playerId);
|
||||
playersToDamage = cmpPlayer.GetEnemies();
|
||||
}
|
||||
// Damage the units.
|
||||
|
|
@ -607,7 +665,7 @@ Attack.prototype.MissileHit = function(data, lateness)
|
|||
else
|
||||
{
|
||||
// If we didn't hit the main target look for nearby units
|
||||
var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)
|
||||
var cmpPlayer = QueryPlayerIDInterface(data.playerId);
|
||||
var ents = Damage.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies());
|
||||
|
||||
for (var i = 0; i < ents.length; i++)
|
||||
|
|
|
|||
212
binaries/data/mods/public/simulation/components/Capturable.js
Normal file
212
binaries/data/mods/public/simulation/components/Capturable.js
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
function Capturable() {}
|
||||
|
||||
Capturable.prototype.Schema =
|
||||
"<element name='CapturePoints' a:help='Maximum capture points'>" +
|
||||
"<ref name='positiveDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='RegenRate' a:help='Number of capture are regenerated per second in favour of the owner'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>" +
|
||||
"<element name='GarrisonRegenRate' a:help='Number of capture are regenerated per second and per garrisoned unit in favour of the owner'>" +
|
||||
"<ref name='nonNegativeDecimal'/>" +
|
||||
"</element>";
|
||||
|
||||
Capturable.prototype.Init = function()
|
||||
{
|
||||
// Cache this value
|
||||
this.maxCp = +this.template.CapturePoints;
|
||||
this.cp = [];
|
||||
this.startRegenTimer();
|
||||
};
|
||||
|
||||
//// Interface functions ////
|
||||
|
||||
/**
|
||||
* Returns the current capture points array
|
||||
*/
|
||||
Capturable.prototype.GetCapturePoints = function()
|
||||
{
|
||||
return this.cp;
|
||||
};
|
||||
|
||||
Capturable.prototype.GetMaxCapturePoints = function()
|
||||
{
|
||||
return this.maxCp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the new capture points, used for cloning entities
|
||||
* The caller should assure that the sum of capture points
|
||||
* matches the max.
|
||||
*/
|
||||
Capturable.prototype.SetCapturePoints = function(capturePointsArray)
|
||||
{
|
||||
this.cp = capturePointsArray;
|
||||
};
|
||||
|
||||
/**
|
||||
* Reduces the amount of capture points of an entity,
|
||||
* in favour of the player of the source
|
||||
* Returns the number of capture points actually taken
|
||||
*/
|
||||
Capturable.prototype.Reduce = function(amount, playerID)
|
||||
{
|
||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
|
||||
return 0;
|
||||
|
||||
var cmpPlayerSource = QueryPlayerIDInterface(playerID);
|
||||
if (!cmpPlayerSource)
|
||||
return 0;
|
||||
|
||||
// Before changing the value, activate Fogging if necessary to hide changes
|
||||
var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
|
||||
if (cmpFogging)
|
||||
cmpFogging.Activate();
|
||||
|
||||
var enemiesFilter = function(v, i) { return v > 0 && !cmpPlayerSource.IsAlly(i); };
|
||||
var numberOfEnemies = this.cp.filter(enemiesFilter).length;
|
||||
|
||||
if (numberOfEnemies == 0)
|
||||
return 0;
|
||||
|
||||
// distribute the capture points over all enemies
|
||||
var distributedAmount = amount / numberOfEnemies;
|
||||
for (let i in this.cp)
|
||||
{
|
||||
if (cmpPlayerSource.IsAlly(i))
|
||||
continue;
|
||||
if (this.cp[i] > distributedAmount)
|
||||
this.cp[i] -= distributedAmount;
|
||||
else
|
||||
this.cp[i] = 0;
|
||||
}
|
||||
|
||||
// give all cp taken to the player
|
||||
var takenCp = this.maxCp - this.cp.reduce(function(a, b) { return a + b; });
|
||||
this.cp[playerID] += takenCp;
|
||||
|
||||
this.startRegenTimer();
|
||||
|
||||
Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp })
|
||||
|
||||
if (this.cp[cmpOwnership.GetOwner()] > 0)
|
||||
return takenCp;
|
||||
|
||||
// if all cp has been taken from the owner, convert it to the best player
|
||||
var bestPlayer = 0;
|
||||
for (let i in this.cp)
|
||||
if (this.cp[i] >= this.cp[bestPlayer])
|
||||
bestPlayer = +i;
|
||||
|
||||
cmpOwnership.SetOwner(bestPlayer);
|
||||
|
||||
return takenCp;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the source can (re)capture points from this building
|
||||
*/
|
||||
Capturable.prototype.CanCapture = function(playerID)
|
||||
{
|
||||
var cmpPlayerSource = QueryPlayerIDInterface(playerID);
|
||||
|
||||
if (!cmpPlayerSource)
|
||||
warn(source + " has no player component defined on its owner ");
|
||||
var cp = this.GetCapturePoints()
|
||||
var sourceEnemyCp = 0;
|
||||
for (let i in this.GetCapturePoints())
|
||||
if (!cmpPlayerSource.IsAlly(i))
|
||||
sourceEnemyCp += cp[i];
|
||||
return sourceEnemyCp > 0;
|
||||
};
|
||||
|
||||
//// Private functions ////
|
||||
|
||||
Capturable.prototype.GetRegenRate = function()
|
||||
{
|
||||
var regenRate = +this.template.RegenRate;
|
||||
regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity);
|
||||
|
||||
var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
|
||||
if (!cmpGarrisonHolder)
|
||||
return regenRate;
|
||||
|
||||
var garrisonRegenRate = +this.template.GarrisonRegenRate;
|
||||
garrisonRegenRate = ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", garrisonRegenRate, this.entity);
|
||||
var garrisonedUnits = cmpGarrisonHolder.GetEntities().length;
|
||||
return regenRate + garrisonedUnits * garrisonRegenRate;
|
||||
};
|
||||
|
||||
Capturable.prototype.RegenCapturePoints = function()
|
||||
{
|
||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
if (!cmpOwnership || cmpOwnership.GetOwner() == -1)
|
||||
return;
|
||||
|
||||
var takenCp = this.Reduce(this.GetRegenRate(), cmpOwnership.GetOwner())
|
||||
if (takenCp > 0)
|
||||
return;
|
||||
|
||||
// no capture points taken, stop the timer
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.CancelTimer(this.regenTimer);
|
||||
this.regenTimer = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start the regeneration timer when no timer exists
|
||||
* When nothing can be regenerated (f.e. because the
|
||||
* rate is 0, or because it is fully regenerated),
|
||||
* the timer stops automatically after one execution.
|
||||
*/
|
||||
Capturable.prototype.startRegenTimer = function()
|
||||
{
|
||||
if (this.regenTimer)
|
||||
return;
|
||||
var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
this.regenTimer = cmpTimer.SetInterval(this.entity, IID_Capturable, "RegenCapturePoints", 1000, 1000, null);
|
||||
};
|
||||
|
||||
//// Message Listeners ////
|
||||
|
||||
Capturable.prototype.OnValueModification = function(msg)
|
||||
{
|
||||
if (msg.component != "Capturable")
|
||||
return;
|
||||
|
||||
var oldMaxCp = this.GetMaxCapturePoints();
|
||||
this.maxCp = ApplyValueModificationsToEntity("Capturable/Max", +this.template.Max, this.entity);
|
||||
if (oldMaxCp == this.maxCp)
|
||||
return;
|
||||
|
||||
var scale = this.maxCp / oldMaxCp;
|
||||
for (let i in this.cp)
|
||||
this.cp[i] *= scale;
|
||||
Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp });
|
||||
this.startRegenTimer();
|
||||
};
|
||||
|
||||
Capturable.prototype.OnGarrisonedUnitsChanged = function(msg)
|
||||
{
|
||||
this.startRegenTimer();
|
||||
};
|
||||
|
||||
Capturable.prototype.OnOwnershipChanged = function(msg)
|
||||
{
|
||||
this.startRegenTimer();
|
||||
|
||||
if (msg.from != -1)
|
||||
return;
|
||||
|
||||
// initialise the capture points when created
|
||||
this.cp = [];
|
||||
var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
|
||||
for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i)
|
||||
if (i == msg.to)
|
||||
this.cp[i] = this.maxCp;
|
||||
else
|
||||
this.cp[i] = 0;
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable);
|
||||
|
|
@ -125,6 +125,13 @@ Fogging.prototype.LoadMirage = function(player)
|
|||
cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints())
|
||||
);
|
||||
|
||||
var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
|
||||
if (cmpCapturable)
|
||||
cmpMirage.CopyCapturable(
|
||||
cmpCapturable.GetCapturePoints(),
|
||||
cmpCapturable.GetMaxCapturePoints()
|
||||
);
|
||||
|
||||
var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply);
|
||||
if (cmpResourceSupply)
|
||||
cmpMirage.CopyResourceSupply(
|
||||
|
|
|
|||
|
|
@ -267,6 +267,18 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
|
|||
ret.needsRepair = cmpMirage.NeedsRepair();
|
||||
}
|
||||
|
||||
var cmpCapturable = Engine.QueryInterface(ent, IID_Capturable);
|
||||
if (cmpCapturable)
|
||||
{
|
||||
ret.capturePoints = cmpCapturable.GetCapturePoints();
|
||||
ret.maxCapturePoints = cmpCapturable.GetMaxCapturePoints();
|
||||
}
|
||||
if (cmpMirage && cmpMirage.Capturable())
|
||||
{
|
||||
ret.capturePoints = cmpMirage.GetCapturePoints();
|
||||
ret.maxCapturePoints = cmpMirage.GetMaxCapturePoints();
|
||||
}
|
||||
|
||||
var cmpBuilder = Engine.QueryInterface(ent, IID_Builder);
|
||||
if (cmpBuilder)
|
||||
ret.builder = true;
|
||||
|
|
@ -1701,8 +1713,22 @@ GuiInterface.prototype.CanAttack = function(player, data)
|
|||
var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack);
|
||||
if (!cmpAttack)
|
||||
return false;
|
||||
var cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player);
|
||||
var cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player);
|
||||
if (!cmpEntityPlayer || !cmpTargetPlayer)
|
||||
return false;
|
||||
|
||||
return cmpAttack.CanAttack(data.target);
|
||||
|
||||
// if the owner is an enemy, it's up to the attack component to decide
|
||||
if (!cmpEntityPlayer.IsAlly(cmpTargetPlayer.GetPlayerID()))
|
||||
return cmpAttack.CanAttack(data.target);
|
||||
|
||||
// if the owner is an ally, we could still want to capture some capture points back
|
||||
var cmpCapturable = Engine.QueryInterface(data.target, IID_Capturable);
|
||||
if (cmpCapturable && cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID()) && cmpAttack.GetAttackTypes().indexOf("Capture") != -1)
|
||||
return cmpAttack.CanAttack(data.target);
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -21,6 +21,10 @@ Mirage.prototype.Init = function()
|
|||
this.hitpoints = null;
|
||||
this.needsRepair = null;
|
||||
|
||||
this.capturable = false;
|
||||
this.capturePoints = [];
|
||||
this.maxCapturePoints = 0;
|
||||
|
||||
this.resourceSupply = false;
|
||||
this.maxAmount = null;
|
||||
this.amount = null;
|
||||
|
|
@ -94,6 +98,30 @@ Mirage.prototype.NeedsRepair = function()
|
|||
return this.needsRepair;
|
||||
};
|
||||
|
||||
// Capture data
|
||||
|
||||
Mirage.prototype.CopyCapturable = function(capturePoints, maxCapturePoints)
|
||||
{
|
||||
this.capturable = true;
|
||||
this.capturePoints = capturePoints;
|
||||
this.maxCapturePoints = maxCapturePoints;
|
||||
};
|
||||
|
||||
Mirage.prototype.Capturable = function()
|
||||
{
|
||||
return this.capturable;
|
||||
};
|
||||
|
||||
Mirage.prototype.GetMaxCapturePoints = function()
|
||||
{
|
||||
return this.maxCapturePoints;
|
||||
};
|
||||
|
||||
Mirage.prototype.GetCapturePoints = function()
|
||||
{
|
||||
return this.capturePoints;
|
||||
};
|
||||
|
||||
// ResourceSupply data
|
||||
|
||||
Mirage.prototype.CopyResourceSupply = function(maxAmount, amount, type, isInfinite)
|
||||
|
|
|
|||
|
|
@ -148,6 +148,16 @@ Pack.prototype.PackProgress = function(data, lateness)
|
|||
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);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
function TerritoryDecay() {}
|
||||
|
||||
TerritoryDecay.prototype.Schema =
|
||||
"<element name='HealthDecayRate' a:help='Decay rate in hitpoints per second'>" +
|
||||
"<element name='DecayRate' a:help='Decay rate in hitpoints per second'>" +
|
||||
"<data type='positiveInteger'/>" +
|
||||
"</element>";
|
||||
|
||||
|
|
@ -33,8 +33,6 @@ TerritoryDecay.prototype.IsConnected = function()
|
|||
var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y);
|
||||
if (tileOwner != cmpOwnership.GetOwner())
|
||||
return false;
|
||||
// TODO: this should probably use the same territory restriction
|
||||
// logic as BuildRestrictions, to handle allies etc
|
||||
|
||||
return cmpTerritoryManager.IsConnected(pos.x, pos.y);
|
||||
};
|
||||
|
|
@ -64,7 +62,7 @@ TerritoryDecay.prototype.UpdateDecayState = function()
|
|||
if (connected)
|
||||
var decaying = false;
|
||||
else
|
||||
var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/HealthDecayRate", +this.template.HealthDecayRate, this.entity)) > 0);
|
||||
var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/DecayRate", +this.template.DecayRate, this.entity)) > 0);
|
||||
if (decaying === this.decaying)
|
||||
return;
|
||||
this.decaying = decaying;
|
||||
|
|
@ -88,13 +86,17 @@ TerritoryDecay.prototype.OnOwnershipChanged = function(msg)
|
|||
|
||||
TerritoryDecay.prototype.Decay = function()
|
||||
{
|
||||
var cmpHealth = Engine.QueryInterface(this.entity, IID_Health);
|
||||
if (!cmpHealth)
|
||||
var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable);
|
||||
if (!cmpCapturable)
|
||||
return; // error
|
||||
|
||||
var decayRate = ApplyValueModificationsToEntity("TerritoryDecay/HealthDecayRate", +this.template.HealthDecayRate, this.entity);
|
||||
var decayRate = ApplyValueModificationsToEntity(
|
||||
"TerritoryDecay/DecayRate",
|
||||
+this.template.DecayRate,
|
||||
this.entity);
|
||||
|
||||
cmpHealth.Reduce(Math.round(decayRate));
|
||||
// Reduce capture points in favour of Gaia
|
||||
cmpCapturable.Reduce(decayRate, 0);
|
||||
};
|
||||
|
||||
Engine.RegisterComponentType(IID_TerritoryDecay, "TerritoryDecay", TerritoryDecay);
|
||||
|
|
|
|||
|
|
@ -5559,20 +5559,25 @@ UnitAI.prototype.CanAttack = function(target, forceResponse)
|
|||
return false;
|
||||
|
||||
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
|
||||
if (!cmpOwnership)
|
||||
if (!cmpOwnership || cmpOwnership.GetOwner() < 0)
|
||||
return false;
|
||||
var owner = cmpOwnership.GetOwner();
|
||||
|
||||
// Verify that the target is an attackable resource supply like a domestic animal
|
||||
// or that it isn't owned by an ally of this entity's player or is responding to
|
||||
// an attack.
|
||||
var owner = cmpOwnership.GetOwner();
|
||||
if (!this.MustKillGatherTarget(target)
|
||||
&& !(IsOwnedByEnemyOfPlayer(owner, target)
|
||||
|| IsOwnedByNeutralOfPlayer(owner, target)
|
||||
|| (forceResponse && !IsOwnedByPlayer(owner, target))))
|
||||
return false;
|
||||
if (this.MustKillGatherTarget(target))
|
||||
return true;
|
||||
|
||||
return true;
|
||||
var cmpCapturable = Engine.QueryInterface(target, IID_Capturable);
|
||||
if (cmpCapturable && cmpCapturable.CanCapture(owner) && cmpAttack.GetAttackTypes().indexOf("Capture") != -1)
|
||||
return true;
|
||||
|
||||
if (IsOwnedByEnemyOfPlayer(owner, target) || IsOwnedByNeutralOfPlayer(owner, target))
|
||||
return true;
|
||||
if (forceResponse && !IsOwnedByPlayer(owner, target))
|
||||
return true;
|
||||
return false;
|
||||
};
|
||||
|
||||
UnitAI.prototype.CanGarrison = function(target)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
Engine.RegisterInterface("Capturable");
|
||||
|
||||
// Message in the form of {"capturePoints": [gaia, p1, p2, ...]}
|
||||
Engine.RegisterMessageType("CapturePointsChanged");
|
||||
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
"icon": "blocks_three.png",
|
||||
"researchTime": 40,
|
||||
"tooltip": "Territory decay -50% for Outposts.",
|
||||
"modifications": [{"value": "TerritoryDecay/HealthDecayRate", "multiply": 0.5}],
|
||||
"modifications": [{"value": "TerritoryDecay/DecayRate", "multiply": 0.5}],
|
||||
"affects": ["Outpost"],
|
||||
"soundComplete": "interface/alarm/alarm_upgradearmory.xml"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
"icon": "handcart_empty.png",
|
||||
"researchTime": 40,
|
||||
"tooltip": "Entrenched Camps and Siege Walls decay 50% slower.",
|
||||
"modifications": [{"value": "TerritoryDecay/HealthDecayRate", "multiply": 0.5}],
|
||||
"modifications": [{"value": "TerritoryDecay/DecayRate", "multiply": 0.5}],
|
||||
"affects": ["ArmyCamp", "SiegeWall"],
|
||||
"soundComplete": "interface/alarm/alarm_upgradearmory.xml"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -349,8 +349,18 @@ var commands = {
|
|||
|
||||
"delete-entities": function(player, cmd, data)
|
||||
{
|
||||
for each (var ent in data.entities)
|
||||
for (let ent of data.entities)
|
||||
{
|
||||
// don't allow to delete entities who are half-captured
|
||||
var cmpCapturable = Engine.QueryInterface(ent, IID_Capturable);
|
||||
if (cmpCapturable)
|
||||
{
|
||||
var capturePoints = cmpCapturable.GetCapturePoints();
|
||||
var maxCapturePoints = cmpCapturable.GetMaxCapturePoints();
|
||||
if (capturePoints[player] < maxCapturePoints / 2)
|
||||
return;
|
||||
}
|
||||
// either kill or delete the entity
|
||||
var cmpHealth = Engine.QueryInterface(ent, IID_Health);
|
||||
if (cmpHealth)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@
|
|||
</SoundGroups>
|
||||
</Sound>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
</SoundGroups>
|
||||
</Sound>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
</SoundGroups>
|
||||
</Sound>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence>
|
||||
<Radius>80</Radius>
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@
|
|||
</SoundGroups>
|
||||
</Sound>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>10</HealthDecayRate>
|
||||
<DecayRate>10</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<ProductionQueue>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
</Obstructions>
|
||||
</Obstruction>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@
|
|||
<Static width="37.0" depth="5.0"/>
|
||||
</Obstruction>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
<Static width="25.0" depth="5.0"/>
|
||||
</Obstruction>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
<Static width="13.0" depth="5.0"/>
|
||||
</Obstruction>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<Static width="7.0" depth="7.0"/>
|
||||
</Obstruction>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
</SoundGroups>
|
||||
</Sound>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence disable=""/>
|
||||
<VisualActor>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@
|
|||
</SoundGroups>
|
||||
</Sound>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>1</HealthDecayRate>
|
||||
<DecayRate>1</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<TerritoryInfluence>
|
||||
<Radius>80</Radius>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,11 @@
|
|||
<PlacementType>land</PlacementType>
|
||||
<Territory>own</Territory>
|
||||
</BuildRestrictions>
|
||||
<Capturable>
|
||||
<CapturePoints>1000</CapturePoints>
|
||||
<RegenRate>0</RegenRate>
|
||||
<GarrisonRegenRate>3</GarrisonRegenRate>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<Population>0</Population>
|
||||
<PopulationBonus>0</PopulationBonus>
|
||||
|
|
@ -100,7 +105,7 @@
|
|||
<HeightOffset>12.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>5</HealthDecayRate>
|
||||
<DecayRate>5</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<Visibility>
|
||||
<RetainInFog>true</RetainInFog>
|
||||
|
|
|
|||
|
|
@ -35,6 +35,9 @@
|
|||
<MinDistance>200</MinDistance>
|
||||
</Distance>
|
||||
</BuildRestrictions>
|
||||
<Capturable>
|
||||
<CapturePoints>3000</CapturePoints>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<PopulationBonus>20</PopulationBonus>
|
||||
<BuildTime>500</BuildTime>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
<BuildRestrictions>
|
||||
<Category>House</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable>
|
||||
<CapturePoints>300</CapturePoints>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<PopulationBonus>5</PopulationBonus>
|
||||
<BuildTime>30</BuildTime>
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@
|
|||
<HeightOffset>18.0</HeightOffset>
|
||||
</StatusBars>
|
||||
<TerritoryDecay>
|
||||
<HealthDecayRate>2</HealthDecayRate>
|
||||
<DecayRate>2</DecayRate>
|
||||
</TerritoryDecay>
|
||||
<Vision>
|
||||
<Range>80</Range>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
<PlacementType>land-shore</PlacementType>
|
||||
<Category>Wall</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable disable=""/>
|
||||
<Cost>
|
||||
<BuildTime>10</BuildTime>
|
||||
<Resources>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
<BuildRestrictions>
|
||||
<Category>Wall</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable disable=""/>
|
||||
<Cost>
|
||||
<BuildTime>0</BuildTime>
|
||||
<Resources>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
<PlacementType>land-shore</PlacementType>
|
||||
<Category>Wall</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable disable=""/>
|
||||
<Cost>
|
||||
<BuildTime>120</BuildTime>
|
||||
<Resources>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
<BuildRestrictions>
|
||||
<Category>Farmstead</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable>
|
||||
<CapturePoints>300</CapturePoints>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<BuildTime>45</BuildTime>
|
||||
<Resources>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@
|
|||
<BuildRestrictions>
|
||||
<Category>Storehouse</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable>
|
||||
<CapturePoints>300</CapturePoints>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<BuildTime>40</BuildTime>
|
||||
<Resources>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@
|
|||
<MinDistance>80</MinDistance>
|
||||
</Distance>
|
||||
</BuildRestrictions>
|
||||
<Capturable>
|
||||
<CapturePoints>4000</CapturePoints>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<PopulationBonus>10</PopulationBonus>
|
||||
<BuildTime>300</BuildTime>
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
<BuildRestrictions>
|
||||
<Category>Field</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable disable=""/>
|
||||
<Cost>
|
||||
<BuildTime>50</BuildTime>
|
||||
<Resources>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@
|
|||
<BuildRestrictions>
|
||||
<Category>Wonder</Category>
|
||||
</BuildRestrictions>
|
||||
<Capturable>
|
||||
<CapturePoints>5000</CapturePoints>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<BuildTime>1000</BuildTime>
|
||||
<Resources>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@
|
|||
<Crush>15</Crush>
|
||||
</Armour>
|
||||
<Attack>
|
||||
<Capture>
|
||||
<Value>3</Value>
|
||||
<MaxRange>4</MaxRange>
|
||||
<RepeatTime>1000</RepeatTime>
|
||||
</Capture>
|
||||
<Slaughter>
|
||||
<Hack>100.0</Hack>
|
||||
<Pierce>0.0</Pierce>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity parent="template_unit">
|
||||
<Attack>
|
||||
<Capture>
|
||||
<Value>3</Value>
|
||||
<MaxRange>4</MaxRange>
|
||||
<RepeatTime>1000</RepeatTime>
|
||||
</Capture>
|
||||
</Attack>
|
||||
<Identity>
|
||||
<GenericName>Champion Unit</GenericName>
|
||||
<Classes datatype="tokens">Organic Human</Classes>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Entity parent="template_unit_champion_elephant">
|
||||
<Attack>
|
||||
<Attack replace="">
|
||||
<Melee>
|
||||
<Hack>20</Hack>
|
||||
<Pierce>0</Pierce>
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<Pierce>10</Pierce>
|
||||
<Crush>12</Crush>
|
||||
</Armour>
|
||||
<Attack>
|
||||
<Attack replace="">
|
||||
<Melee>
|
||||
<Hack>17.5</Hack>
|
||||
<Pierce>0</Pierce>
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@
|
|||
<Crush>15</Crush>
|
||||
</Armour>
|
||||
<Attack>
|
||||
<Capture>
|
||||
<Value>3</Value>
|
||||
<MaxRange>4</MaxRange>
|
||||
<RepeatTime>1000</RepeatTime>
|
||||
</Capture>
|
||||
<Slaughter>
|
||||
<Hack>50.0</Hack>
|
||||
<Pierce>0.0</Pierce>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,11 @@
|
|||
<Pierce>5</Pierce>
|
||||
<Crush>5</Crush>
|
||||
</Armour>
|
||||
<Capturable>
|
||||
<CapturePoints>100</CapturePoints>
|
||||
<RegenRate>0</RegenRate>
|
||||
<GarrisonRegenRate>1.5</GarrisonRegenRate>
|
||||
</Capturable>
|
||||
<Cost>
|
||||
<Population>5</Population>
|
||||
</Cost>
|
||||
|
|
|
|||
Loading…
Reference in a new issue