Ticket #996: capture.3.diff
File capture.3.diff, 52.0 KB (added by , 9 years ago) |
---|
-
binaries/data/mods/public/gui/common/functions_utility.js
166 166 // ==================================================================== 167 167 168 168 // Convert integer color values to string (for use in GUI objects) 169 function rgbToGuiColor(color )169 function rgbToGuiColor(color, alpha) 170 170 { 171 var ret; 171 172 if (color && ("r" in color) && ("g" in color) && ("b" in color)) 172 return color.r + " " + color.g + " " + color.b; 173 174 return "0 0 0"; 173 ret = color.r + " " + color.g + " " + color.b; 174 else 175 ret = "0 0 0"; 176 if (alpha) 177 ret += " " + alpha; 178 return ret; 175 179 } 176 180 177 181 // ==================================================================== -
binaries/data/mods/public/gui/common/tooltips.js
140 140 if (type === "Charge") return translate("Charge Attack:"); 141 141 if (type === "Melee") return translate("Melee Attack:"); 142 142 if (type === "Ranged") return translate("Ranged Attack:"); 143 if (type === "Capture") return translate("Capture Attack:"); 143 144 144 145 warn(sprintf("Internationalization: Unexpected attack type found with code ‘%(attackType)s’. This attack type must be internationalized.", { attackType: type })); 145 146 return translate("Attack:"); … … 169 170 }); 170 171 171 172 var attackLabel = txtFormats.header[0] + getAttackTypeLabel(type) + txtFormats.header[1]; 173 if (type == "Capture") 174 { 175 attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), { 176 attackLabel: attackLabel, 177 details: template.attack[type].value, 178 rate: rate 179 })); 180 continue; 181 } 172 182 if (type != "Ranged") 173 183 { 174 184 attacks.push(sprintf(translate("%(attackLabel)s %(details)s, %(rate)s"), { -
binaries/data/mods/public/gui/session/selection_details.js
58 58 } 59 59 60 60 // Hitpoints 61 Engine.GetGUIObjectByName("healthSection").hidden = !entState.hitpoints; 61 62 if (entState.hitpoints) 62 63 { 63 64 var unitHealthBar = Engine.GetGUIObjectByName("healthBar"); … … 79 80 hitpoints: Math.ceil(entState.hitpoints), 80 81 maxHitpoints: entState.maxHitpoints 81 82 }); 82 Engine.GetGUIObjectByName("healthSection").hidden = false;83 83 } 84 else 84 85 // CapturePoints 86 Engine.GetGUIObjectByName("captureSection").hidden = !entState.capturePoints; 87 if (entState.capturePoints) 85 88 { 86 Engine.GetGUIObjectByName("healthSection").hidden = true; 89 let setCaptureBarPart = function(playerID, startSize) 90 { 91 var unitCaptureBar = Engine.GetGUIObjectByName("captureBar["+playerID+"]"); 92 var sizeObj = unitCaptureBar.size; 93 sizeObj.rleft = startSize; 94 95 var size = 100*Math.max(0, Math.min(1, entState.capturePoints[playerID] / entState.maxCapturePoints)); 96 sizeObj.rright = startSize + size; 97 unitCaptureBar.size = sizeObj; 98 unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128); 99 unitCaptureBar.hidden=false; 100 return startSize + size; 101 } 102 103 // first handle the owner's points, to keep those points on the left for clarity 104 let size = setCaptureBarPart(entState.player, 0); 105 106 for (let i in entState.capturePoints) 107 if (i != entState.player) 108 size = setCaptureBarPart(i, size); 109 110 111 Engine.GetGUIObjectByName("captureStats").caption = sprintf(translate("%(capturePoints)s / %(maxCapturePoints)s"), { 112 capturePoints: Math.ceil(entState.capturePoints[entState.player]), 113 maxCapturePoints: entState.maxCapturePoints 114 }); 87 115 } 88 116 89 117 // TODO: Stamina 90 var player = Engine.GetPlayerID();91 if (entState.stamina && (entState.player == player || g_DevSettings.controlAll))92 Engine.GetGUIObjectByName("staminaSection").hidden = false;93 else94 Engine.GetGUIObjectByName("staminaSection").hidden = true;95 118 96 119 // Experience 120 Engine.GetGUIObjectByName("experience").hidden = !entState.promotion; 97 121 if (entState.promotion) 98 122 { 99 123 var experienceBar = Engine.GetGUIObjectByName("experienceBar"); … … 112 136 experience: "[font=\"sans-bold-13\"]" + translate("Experience:") + "[/font]", 113 137 current: Math.floor(entState.promotion.curr) 114 138 }); 115 Engine.GetGUIObjectByName("experience").hidden = false;116 139 } 117 else118 {119 Engine.GetGUIObjectByName("experience").hidden = true;120 }121 140 122 141 // Resource stats 142 Engine.GetGUIObjectByName("resourceSection").hidden = !entState.resourceSupply; 123 143 if (entState.resourceSupply) 124 144 { 125 145 var resources = entState.resourceSupply.isInfinite ? translate("∞") : // Infinity symbol … … 136 156 Engine.GetGUIObjectByName("resourceStats").caption = resources; 137 157 138 158 if (entState.hitpoints) 139 Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName(" staminaSection").size;159 Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("captureSection").size; 140 160 else 141 161 Engine.GetGUIObjectByName("resourceSection").size = Engine.GetGUIObjectByName("healthSection").size; 142 162 143 Engine.GetGUIObjectByName("resourceSection").hidden = false;144 163 } 145 else146 {147 Engine.GetGUIObjectByName("resourceSection").hidden = true;148 }149 164 150 165 // Resource carrying 151 166 if (entState.resourceCarrying && entState.resourceCarrying.length) … … 290 305 { 291 306 var averageHealth = 0; 292 307 var maxHealth = 0; 308 var maxCapturePoints = 0; 309 var capturePoints = (new Array(9)).fill(0); 310 var playerID = 0; 293 311 294 312 for (var i = 0; i < selection.length; i++) 295 313 { 296 314 var entState = GetEntityState(selection[i]) 297 if (entState) 315 if (!entState) 316 continue; 317 playerID = entState.player; // trust that all selected entities have the same owner 318 if (entState.hitpoints) 298 319 { 299 if (entState.hitpoints) 300 { 301 averageHealth += entState.hitpoints; 302 maxHealth += entState.maxHitpoints; 303 } 320 averageHealth += entState.hitpoints; 321 maxHealth += entState.maxHitpoints; 304 322 } 323 if (entState.capturePoints) 324 { 325 maxCapturePoints += entState.maxCapturePoints; 326 capturePoints = entState.capturePoints.map(function(v, i) { return v + capturePoints[i]; }); 327 } 305 328 } 306 329 330 Engine.GetGUIObjectByName("healthMultiple").hidden = averageHealth <= 0; 307 331 if (averageHealth > 0) 308 332 { 309 333 var unitHealthBar = Engine.GetGUIObjectByName("healthBarMultiple"); … … 313 337 314 338 var hitpointsLabel = "[font=\"sans-bold-13\"]" + translate("Hitpoints:") + "[/font]" 315 339 var hitpoints = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: hitpointsLabel, current: averageHealth, max: maxHealth }); 316 var healthMultiple = Engine.GetGUIObjectByName("healthMultiple"); 317 healthMultiple.tooltip = hitpoints; 318 healthMultiple.hidden = false; 340 Engine.GetGUIObjectByName("healthMultiple").tooltip = hitpoints; 319 341 } 320 else 342 343 Engine.GetGUIObjectByName("captureMultiple").hidden = maxCapturePoints <= 0; 344 if (maxCapturePoints > 0) 321 345 { 322 Engine.GetGUIObjectByName("healthMultiple").hidden = true; 346 let setCaptureBarPart = function(playerID, startSize) 347 { 348 var unitCaptureBar = Engine.GetGUIObjectByName("captureBarMultiple["+playerID+"]"); 349 var sizeObj = unitCaptureBar.size; 350 sizeObj.rtop = startSize; 351 352 var size = 100*Math.max(0, Math.min(1, capturePoints[playerID] / maxCapturePoints)); 353 sizeObj.rbottom = startSize + size; 354 unitCaptureBar.size = sizeObj; 355 unitCaptureBar.sprite = "color: " + rgbToGuiColor(g_Players[playerID].color, 128); 356 unitCaptureBar.hidden=false; 357 return startSize + size; 358 } 359 360 let size = 0; 361 for (let i in entState.capturePoints) 362 if (i != playerID) 363 size = setCaptureBarPart(i, size); 364 365 // last handle the owner's points, to keep those points on the bottom for clarity 366 setCaptureBarPart(playerID, size); 367 368 var capturePointsLabel = "[font=\"sans-bold-13\"]" + translate("Capture points:") + "[/font]" 369 var capturePointsTooltip = sprintf(translate("%(label)s %(current)s / %(max)s"), { label: capturePointsLabel, current: Math.ceil(capturePoints[playerID]), max: Math.ceil(maxCapturePoints) }); 370 Engine.GetGUIObjectByName("captureMultiple").tooltip = capturePointsTooltip; 323 371 } 324 372 325 373 // TODO: Stamina -
binaries/data/mods/public/gui/session/selection_panels_middle/multiple_details_area.xml
33 33 <object type="image" sprite="statsBarShaderVertical" ghost="true"/> 34 34 </object> 35 35 36 <!-- Staminabar -->37 <object size="15 0 22 100%" type="image" name=" staminaMultiple" tooltip_style="sessionToolTipBold">38 <translatableAttribute id="tooltip"> Stamina</translatableAttribute>36 <!-- Capture bar --> 37 <object size="15 0 22 100%" type="image" name="captureMultiple" tooltip_style="sessionToolTipBold"> 38 <translatableAttribute id="tooltip">Capture points</translatableAttribute> 39 39 <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/> 40 <object type="image" sprite="staminaBackground" ghost="true"/> 41 <object type="image" sprite="staminaForeground" ghost="true" name="staminaBarMultiple"/> 40 <repeat count="9"> 41 <object type="image" sprite="playerColorBackground" ghost="true" name="captureBarMultiple[n]" hidden="true"/> 42 </repeat> 42 43 <object type="image" sprite="statsBarShaderVertical" ghost="true"/> 43 44 </object> 44 45 </object> -
binaries/data/mods/public/gui/session/selection_panels_middle/single_details_area.xml
20 20 </object> 21 21 </object> 22 22 23 <!-- Staminabar -->24 <object size="88 28 100% 52" name=" staminaSection">25 <object size="0 0 100% 16" name=" staminaLabel" type="text" style="StatsTextLeft" ghost="true">26 <translatableAttribute id="tooltip"> Stamina:</translatableAttribute>23 <!-- Capture bar --> 24 <object size="88 28 100% 52" name="captureSection"> 25 <object size="0 0 100% 16" name="captureLabel" type="text" style="StatsTextLeft" ghost="true"> 26 <translatableAttribute id="tooltip">Capture points:</translatableAttribute> 27 27 </object> 28 <object size="0 0 100% 16" name=" staminaStats" type="text" style="StatsTextRight" ghost="true"/>29 <object size="1 16 100% 23" name=" stamina" type="image">28 <object size="0 0 100% 16" name="captureStats" type="text" style="StatsTextRight" ghost="true"/> 29 <object size="1 16 100% 23" name="capture" type="image"> 30 30 <object type="image" sprite="barBorder" ghost="true" size="-1 -1 100%+1 100%+1"/> 31 <object type="image" sprite="staminaBackground" ghost="true"/> 32 <object type="image" sprite="staminaForeground" ghost="true" name="staminaBar"/> 31 <repeat count="9"> 32 <object type="image" sprite="playerColorBackground" ghost="true" name="captureBar[n]" hidden="true"/> 33 </repeat> 33 34 <object type="image" sprite="statsBarShaderHorizontal" ghost="true"/> 34 35 </object> 35 36 </object> -
binaries/data/mods/public/gui/session/unit_actions.js
99 99 { 100 100 if (!entState.attack || !targetState.hitpoints) 101 101 return false; 102 if (playerCheck(entState, targetState, ["Neutral", "Enemy"])) 103 return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})}; 104 return false; 102 return {"possible": Engine.GuiInterfaceCall("CanAttack", {"entity": entState.id, "target": targetState.id})}; 105 103 }, 106 104 "hotkeyActionCheck": function(target) 107 105 { … … 672 670 "icon": "kill_small.png" 673 671 }; 674 672 673 if (entState.capturePoints && entState.capturePoints[entState.player] < entState.maxCapturePoints / 2) 674 return { 675 "tooltip": translate("You cannot destroy this entity as you own less than half the capture points"), 676 "icon": "kill_small.png" 677 }; 678 679 675 680 return { 676 681 "tooltip": translate("Delete"), 677 682 "icon": "kill_small.png" -
binaries/data/mods/public/simulation/components/AIProxy.js
111 111 this.changes.hitpoints = msg.to; 112 112 }; 113 113 114 AIProxy.prototype.OnCapturePointsChanged = function(msg) 115 { 116 if (!this.NotifyChange()) 117 return; 118 this.changes.capturePoints = msg.capturePoints; 119 }; 120 114 121 AIProxy.prototype.OnUnitIdleChanged = function(msg) 115 122 { 116 123 if (!this.NotifyChange()) -
binaries/data/mods/public/simulation/components/Attack.js
159 159 "</element>" + 160 160 "</optional>" + 161 161 "<optional>" + 162 "<element name='Capture'>" + 163 "<interleave>" + 164 "<element name='Value' a:help='Capture points value'><ref name='nonNegativeDecimal'/></element>" + 165 "<element name='MaxRange' a:help='Maximum attack range (in meters)'><ref name='nonNegativeDecimal'/></element>" + 166 "<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 167 "<data type='positiveInteger'/>" + 168 "</element>" + 169 Attack.prototype.bonusesSchema + 170 Attack.prototype.preferredClassesSchema + 171 Attack.prototype.restrictedClassesSchema + 172 "</interleave>" + 173 "</element>" + 174 "</optional>" + 175 "<optional>" + 162 176 "<element name='Charge'>" + 163 177 "<interleave>" + 164 178 "<element name='Hack' a:help='Hack damage strength'><ref name='nonNegativeDecimal'/></element>" + … … 198 212 if (this.template.Charge) ret.push("Charge"); 199 213 if (this.template.Melee) ret.push("Melee"); 200 214 if (this.template.Ranged) ret.push("Ranged"); 215 if (this.template.Capture) ret.push("Capture"); 201 216 return ret; 202 217 }; 203 218 … … 309 324 if (cmpFormation) 310 325 return this.GetBestAttack(); 311 326 312 constcmpIdentity = Engine.QueryInterface(target, IID_Identity);327 var cmpIdentity = Engine.QueryInterface(target, IID_Identity); 313 328 if (!cmpIdentity) 314 329 return undefined; 315 330 316 const targetClasses = cmpIdentity.GetClassesList();317 const isTargetClass = function (value, i, a) { return targetClasses.indexOf(value) != -1; };318 const types = this.GetAttackTypes();319 const attack = this;320 const isAllowed = function (value, i, a) { return !attack.GetRestrictedClasses(value).some(isTargetClass); }321 const isPreferred = function (value, i, a) { return attack.GetPreferredClasses(value).some(isTargetClass); }322 const byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); }323 331 332 var targetClasses = cmpIdentity.GetClassesList(); 333 var isTargetClass = function (className) { return targetClasses.indexOf(className) != -1; }; 334 324 335 // Always slaughter domestic animals instead of using a normal attack 325 336 if (isTargetClass("Domestic") && this.template.Slaughter) 326 337 return "Slaughter"; 327 338 328 return types.filter(isAllowed).sort(byPreference).pop(); 339 var attack = this; 340 var isAllowed = function (type) { return !attack.GetRestrictedClasses(type).some(isTargetClass); } 341 342 var types = this.GetAttackTypes().filter(isAllowed); 343 344 // check if the target is capturable 345 var captureIndex = types.indexOf("Capture") 346 if (captureIndex != -1) 347 { 348 var cmpCapturable = Engine.QueryInterface(target, IID_Capturable); 349 var cmpPlayer = QueryOwnerInterface(this.entity); 350 if (cmpPlayer && cmpCapturable && cmpCapturable.CanCapture(cmpPlayer.GetPlayerID())) 351 return "Capture"; 352 // not captureable, so remove this attack 353 types.splice(captureIndex, 1); 354 } 355 356 var isPreferred = function (className) { return attack.GetPreferredClasses(className).some(isTargetClass); } 357 var byPreference = function (a, b) { return (types.indexOf(a) + (isPreferred(a) ? types.length : 0) ) - (types.indexOf(b) + (isPreferred(b) ? types.length : 0) ); } 358 359 360 return types.sort(byPreference).pop(); 329 361 }; 330 362 331 363 Attack.prototype.CompareEntitiesByPreference = function(a, b) … … 367 399 { 368 400 return ApplyValueModificationsToEntity("Attack/" + type + splash + "/" + damageType, +(template[damageType] || 0), self.entity); 369 401 }; 370 402 403 if (type == "Capture") 404 return {value: applyMods("Value")}; 405 371 406 return { 372 407 hack: applyMods("Hack"), 373 408 pierce: applyMods("Pierce"), … … 517 552 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 518 553 cmpTimer.SetTimeout(this.entity, IID_Attack, "MissileHit", timeToTarget*1000, {"type": type, "target": target, "position": realTargetPosition, "direction": missileDirection, "projectileId": id, "playerId":playerId}); 519 554 } 555 else if (type == "Capture") 556 { 557 var multiplier = this.GetAttackBonus(type, target); 558 var cmpHealth = Engine.QueryInterface(target, IID_Health); 559 if (!cmpHealth || cmpHealth.GetHitpoints() == 0) 560 return; 561 multiplier *= cmpHealth.GetMaxHitpoints() / cmpHealth.GetHitpoints(); 562 563 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 564 if (!cmpOwnership || cmpOwnership.GetOwner() == -1) 565 return; 566 var owner = cmpOwnership.GetOwner(); 567 var cmpCapturable = Engine.QueryInterface(target, IID_Capturable); 568 if (!cmpCapturable || !cmpCapturable.CanCapture(owner)) 569 return; 570 571 var strength = this.GetAttackStrengths("Capture").value; 572 cmpCapturable.Reduce(strength * multiplier, owner); 573 } 520 574 else 521 575 { 522 576 // Melee attack - hurt the target immediately … … 585 639 // If friendlyFire isn't enabled, get all player enemies to pass to "Damage.CauseSplashDamage". 586 640 if (friendlyFire == "false") 587 641 { 588 var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)642 var cmpPlayer = QueryPlayerIDInterface(data.playerId); 589 643 playersToDamage = cmpPlayer.GetEnemies(); 590 644 } 591 645 // Damage the units. … … 607 661 else 608 662 { 609 663 // If we didn't hit the main target look for nearby units 610 var cmpPlayer = Engine.QueryInterface(Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager).GetPlayerByID(data.playerId), IID_Player)664 var cmpPlayer = QueryPlayerIDInterface(data.playerId); 611 665 var ents = Damage.EntitiesNearPoint(Vector2D.from3D(data.position), targetPosition.horizDistanceTo(data.position) * 2, cmpPlayer.GetEnemies()); 612 666 613 667 for (var i = 0; i < ents.length; i++) -
binaries/data/mods/public/simulation/components/Capturable.js
1 function Capturable() {} 2 3 Capturable.prototype.Schema = 4 "<element name='CapturePoints' a:help='Maximum capture points'>" + 5 "<ref name='positiveDecimal'/>" + 6 "</element>" + 7 "<element name='RegenRate' a:help='Number of capture are regenerated per second in favour of the owner'>" + 8 "<ref name='nonNegativeDecimal'/>" + 9 "</element>" + 10 "<element name='GarrisonRegenRate' a:help='Number of capture are regenerated per second and per garrisoned unit in favour of the owner'>" + 11 "<ref name='nonNegativeDecimal'/>" + 12 "</element>"; 13 14 Capturable.prototype.Init = function() 15 { 16 // Cache this value 17 this.maxCp = +this.template.CapturePoints; 18 this.cp = []; 19 this.startRegenTimer(); 20 }; 21 22 //// Interface functions //// 23 24 /** 25 * Returns the current capture points array 26 */ 27 Capturable.prototype.GetCapturePoints = function() 28 { 29 return this.cp; 30 }; 31 32 Capturable.prototype.GetMaxCapturePoints = function() 33 { 34 return this.maxCp; 35 }; 36 37 /** 38 * Reduces the amount of capture points of an entity, 39 * in favour of the player of the source 40 * Returns the number of capture points actually taken 41 */ 42 Capturable.prototype.Reduce = function(amount, playerID) 43 { 44 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 45 if (!cmpOwnership || cmpOwnership.GetOwner() == -1) 46 return 0; 47 48 var cmpPlayerSource = QueryPlayerIDInterface(playerID); 49 if (!cmpPlayerSource) 50 return 0; 51 52 // Before changing the value, activate Fogging if necessary to hide changes 53 var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging); 54 if (cmpFogging) 55 cmpFogging.Activate(); 56 57 var enemiesFilter = function(v, i) { return v > 0 && !cmpPlayerSource.IsAlly(i); }; 58 var numberOfEnemies = this.cp.filter(enemiesFilter).length; 59 60 if (numberOfEnemies == 0) 61 return 0; 62 63 var distributedAmount = amount / numberOfEnemies; 64 for (let i in this.cp) 65 { 66 if (cmpPlayerSource.IsAlly(i)) 67 continue; 68 if (this.cp[i] > distributedAmount) 69 this.cp[i] -= distributedAmount; 70 else 71 this.cp[i] = 0; 72 } 73 74 // give all cp taken to the player 75 var takenCp = this.GetMaxCapturePoints() - this.cp.reduce(function(a, b) { return a + b; }); 76 this.cp[playerID] += takenCp; 77 78 this.startRegenTimer(); 79 80 Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp }) 81 82 if (this.cp[cmpOwnership.GetOwner()] > 0) 83 return takenCp; 84 85 // if all cp has been taken from the owner, convert it to the best player 86 var bestPlayer = 0; 87 for (let i in this.cp) 88 if (this.cp[i] >= this.cp[bestPlayer]) 89 bestPlayer = +i; 90 91 cmpOwnership.SetOwner(bestPlayer); 92 93 return takenCp; 94 }; 95 96 /** 97 * Check if the source can (re)capture points from this building 98 */ 99 Capturable.prototype.CanCapture = function(playerID) 100 { 101 var cmpPlayerSource = QueryPlayerIDInterface(playerID); 102 103 if (!cmpPlayerSource) 104 warn(source + " has no player component defined on its owner "); 105 var cp = this.GetCapturePoints() 106 var sourceEnemyCp = 0; 107 for (let i in this.GetCapturePoints()) 108 if (!cmpPlayerSource.IsAlly(i)) 109 sourceEnemyCp += cp[i]; 110 return sourceEnemyCp > 0; 111 }; 112 113 //// Private functions //// 114 115 Capturable.prototype.GetRegenRate = function() 116 { 117 var regenRate = +this.template.RegenRate; 118 regenRate = ApplyValueModificationsToEntity("Capturable/RegenRate", regenRate, this.entity); 119 120 var cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder); 121 if (!cmpGarrisonHolder) 122 return regenRate; 123 124 var garrisonRegenRate = +this.template.GarrisonRegenRate; 125 garrisonRegenRate = ApplyValueModificationsToEntity("Capturable/GarrisonRegenRate", garrisonRegenRate, this.entity); 126 var garrisonedUnits = cmpGarrisonHolder.GetEntities().length; 127 return regenRate + garrisonedUnits * garrisonRegenRate; 128 }; 129 130 Capturable.prototype.RegenCapturePoints = function() 131 { 132 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 133 if (!cmpOwnership || cmpOwnership.GetOwner() == -1) 134 return; 135 136 if (this.Reduce(this.GetRegenRate(), cmpOwnership.GetOwner())) 137 return; 138 139 // no capture points taken, stop the timer 140 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 141 cmpTimer.CancelTimer(this.regenTimer); 142 this.regenTimer = 0; 143 }; 144 145 /** 146 * Start the regeneration timer when no timer exists 147 * When nothing can be regenerated (f.e. because the 148 * rate is 0, or because it is fully regenerated), 149 * the timer stops automatically after one execution. 150 */ 151 Capturable.prototype.startRegenTimer = function() 152 { 153 if (this.regenTimer) 154 return; 155 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 156 this.regenTimer = cmpTimer.SetInterval(this.entity, IID_Capturable, "RegenCapturePoints", 1000, 1000, null); 157 }; 158 159 //// Message Listeners //// 160 161 Capturable.prototype.OnValueModification = function(msg) 162 { 163 if (msg.component != "Capturable") 164 return; 165 166 var oldMaxCp = this.GetMaxCapturePoints(); 167 this.maxCp = ApplyValueModificationsToEntity("Capturable/Max", +this.template.Max, this.entity); 168 if (oldMaxCp == this.maxCp) 169 return; 170 171 var scale = this.maxCp / oldMaxCp; 172 for (let i in this.cp) 173 this.cp[i] *= scale; 174 Engine.PostMessage(this.entity, MT_CapturePointsChanged, { "capturePoints": this.cp }); 175 this.startRegenTimer(); 176 }; 177 178 Capturable.prototype.OnGarrisonedUnitsChanged = function(msg) 179 { 180 this.startRegenTimer(); 181 }; 182 183 Capturable.prototype.OnOwnershipChanged = function(msg) 184 { 185 this.startRegenTimer(); 186 187 if (msg.from != -1) 188 return; 189 190 // initialise the capture points when created 191 this.cp = []; 192 var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); 193 for (let i = 0; i < cmpPlayerManager.GetNumPlayers(); ++i) 194 if (i == msg.to) 195 this.cp[i] = this.maxCp; 196 else 197 this.cp[i] = 0; 198 }; 199 200 Engine.RegisterComponentType(IID_Capturable, "Capturable", Capturable); -
binaries/data/mods/public/simulation/components/Fogging.js
125 125 cmpHealth.IsRepairable() && (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 126 126 ); 127 127 128 var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable); 129 if (cmpCapturable) 130 cmpMirage.CopyCapturable( 131 cmpCapturable.GetCapturePoints(), 132 cmpCapturable.GetMaxCapturePoints() 133 ); 134 128 135 var cmpResourceSupply = Engine.QueryInterface(this.entity, IID_ResourceSupply); 129 136 if (cmpResourceSupply) 130 137 cmpMirage.CopyResourceSupply( -
binaries/data/mods/public/simulation/components/GuiInterface.js
267 267 ret.needsRepair = cmpMirage.NeedsRepair(); 268 268 } 269 269 270 var cmpCapturable = Engine.QueryInterface(ent, IID_Capturable); 271 if (cmpCapturable) 272 { 273 ret.capturePoints = cmpCapturable.GetCapturePoints(); 274 ret.maxCapturePoints = cmpCapturable.GetMaxCapturePoints(); 275 } 276 if (cmpMirage && cmpMirage.Capturable()) 277 { 278 ret.capturePoints = cmpMirage.GetCapturePoints(); 279 ret.maxCapturePoints = cmpMirage.GetMaxCapturePoints(); 280 } 281 270 282 var cmpBuilder = Engine.QueryInterface(ent, IID_Builder); 271 283 if (cmpBuilder) 272 284 ret.builder = true; … … 1701 1713 var cmpAttack = Engine.QueryInterface(data.entity, IID_Attack); 1702 1714 if (!cmpAttack) 1703 1715 return false; 1716 var cmpEntityPlayer = QueryOwnerInterface(data.entity, IID_Player); 1717 var cmpTargetPlayer = QueryOwnerInterface(data.target, IID_Player); 1718 if (!cmpEntityPlayer || !cmpTargetPlayer) 1719 return false; 1704 1720 1705 return cmpAttack.CanAttack(data.target); 1721 1722 // if the owner is an enemy, it's up to the attack component to decide 1723 if (!cmpEntityPlayer.IsAlly(cmpTargetPlayer.GetPlayerID())) 1724 return cmpAttack.CanAttack(data.target); 1725 1726 // if the owner is an ally, we could still want to capture some capture points back 1727 var cmpCapturable = Engine.QueryInterface(data.target, IID_Capturable); 1728 if (cmpCapturable && cmpCapturable.CanCapture(cmpEntityPlayer.GetPlayerID())) 1729 return cmpAttack.CanAttack(data.target); 1730 1731 return false; 1706 1732 }; 1707 1733 1708 1734 /* -
binaries/data/mods/public/simulation/components/Mirage.js
21 21 this.hitpoints = null; 22 22 this.needsRepair = null; 23 23 24 this.capturable = false; 25 this.capturePoints = []; 26 this.maxCapturePoints = 0; 27 24 28 this.resourceSupply = false; 25 29 this.maxAmount = null; 26 30 this.amount = null; … … 94 98 return this.needsRepair; 95 99 }; 96 100 101 // Capture data 102 103 Mirage.prototype.CopyCapturable = function(capturePoints, maxCapturePoints) 104 { 105 this.capturable = true; 106 this.capturePoints = capturePoints; 107 this.maxCapturePoints = maxCapturePoints; 108 } 109 110 Mirage.prototype.Capturable = function() 111 { 112 return this.capturable; 113 } 114 115 Mirage.prototype.GetMaxCapturePoints = function() 116 { 117 return this.maxCapturePoints; 118 } 119 120 Mirage.prototype.GetCapturePoints = function() 121 { 122 return this.capturePoints; 123 } 124 97 125 // ResourceSupply data 98 126 99 127 Mirage.prototype.CopyResourceSupply = function(maxAmount, amount, type, isInfinite) -
binaries/data/mods/public/simulation/components/TerritoryDecay.js
1 1 function TerritoryDecay() {} 2 2 3 3 TerritoryDecay.prototype.Schema = 4 "<element name=' HealthDecayRate' a:help='Decay rate in hitpoints per second'>" +4 "<element name='DecayRate' a:help='Decay rate in hitpoints per second'>" + 5 5 "<data type='positiveInteger'/>" + 6 6 "</element>"; 7 7 … … 33 33 var tileOwner = cmpTerritoryManager.GetOwner(pos.x, pos.y); 34 34 if (tileOwner != cmpOwnership.GetOwner()) 35 35 return false; 36 // TODO: this should probably use the same territory restriction37 // logic as BuildRestrictions, to handle allies etc38 36 39 37 return cmpTerritoryManager.IsConnected(pos.x, pos.y); 40 38 }; … … 64 62 if (connected) 65 63 var decaying = false; 66 64 else 67 var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/ HealthDecayRate", +this.template.HealthDecayRate, this.entity)) > 0);65 var decaying = (Math.round(ApplyValueModificationsToEntity("TerritoryDecay/DecayRate", +this.template.DecayRate, this.entity)) > 0); 68 66 if (decaying === this.decaying) 69 67 return; 70 68 this.decaying = decaying; … … 88 86 89 87 TerritoryDecay.prototype.Decay = function() 90 88 { 91 var cmp Health = Engine.QueryInterface(this.entity, IID_Health);92 if (!cmp Health)89 var cmpCapturable = Engine.QueryInterface(this.entity, IID_Capturable); 90 if (!cmpCapturable) 93 91 return; // error 94 92 95 var decayRate = ApplyValueModificationsToEntity("TerritoryDecay/HealthDecayRate", +this.template.HealthDecayRate, this.entity); 93 var decayRate = ApplyValueModificationsToEntity( 94 "TerritoryDecay/DecayRate", 95 +this.template.DecayRate, 96 this.entity); 96 97 97 cmpHealth.Reduce(Math.round(decayRate)); 98 // Reduce capture points in favour of Gaia 99 cmpCapturable.Reduce(decayRate, 0); 98 100 }; 99 101 100 102 Engine.RegisterComponentType(IID_TerritoryDecay, "TerritoryDecay", TerritoryDecay); -
binaries/data/mods/public/simulation/components/UnitAI.js
5559 5559 return false; 5560 5560 5561 5561 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 5562 if (!cmpOwnership )5562 if (!cmpOwnership || cmpOwnership.GetOwner() < 0) 5563 5563 return false; 5564 var owner = cmpOwnership.GetOwner(); 5564 5565 5565 5566 // Verify that the target is an attackable resource supply like a domestic animal 5566 5567 // or that it isn't owned by an ally of this entity's player or is responding to 5567 5568 // an attack. 5568 var owner = cmpOwnership.GetOwner(); 5569 if (!this.MustKillGatherTarget(target) 5570 && !(IsOwnedByEnemyOfPlayer(owner, target) 5571 || IsOwnedByNeutralOfPlayer(owner, target) 5572 || (forceResponse && !IsOwnedByPlayer(owner, target)))) 5573 return false; 5569 if (this.MustKillGatherTarget(target)) 5570 return true; 5574 5571 5575 return true; 5572 var cmpCapturable = Engine.QueryInterface(target, IID_Capturable); 5573 if (cmpCapturable && cmpCapturable.CanCapture(owner)) 5574 return true; 5575 5576 if (IsOwnedByEnemyOfPlayer(owner, target) || IsOwnedByNeutralOfPlayer(owner, target)) 5577 return true; 5578 if (forceResponse && !IsOwnedByPlayer(owner, target)) 5579 return true; 5580 return false; 5576 5581 }; 5577 5582 5578 5583 UnitAI.prototype.CanGarrison = function(target) -
binaries/data/mods/public/simulation/components/interfaces/Capturable.js
1 Engine.RegisterInterface("Capturable"); 2 3 // Message in the form of {"capturePoints": [gaia, p1, p2, ...]} 4 Engine.RegisterMessageType("CapturePointsChanged"); 5 -
binaries/data/mods/public/simulation/data/technologies/decay_outpost.json
7 7 "icon": "blocks_three.png", 8 8 "researchTime": 40, 9 9 "tooltip": "Territory decay -50% for Outposts.", 10 "modifications": [{"value": "TerritoryDecay/ HealthDecayRate", "multiply": 0.5}],10 "modifications": [{"value": "TerritoryDecay/DecayRate", "multiply": 0.5}], 11 11 "affects": ["Outpost"], 12 12 "soundComplete": "interface/alarm/alarm_upgradearmory.xml" 13 13 } -
binaries/data/mods/public/simulation/data/technologies/romans/decay_logistics.json
7 7 "icon": "handcart_empty.png", 8 8 "researchTime": 40, 9 9 "tooltip": "Entrenched Camps and Siege Walls decay 50% slower.", 10 "modifications": [{"value": "TerritoryDecay/ HealthDecayRate", "multiply": 0.5}],10 "modifications": [{"value": "TerritoryDecay/DecayRate", "multiply": 0.5}], 11 11 "affects": ["ArmyCamp", "SiegeWall"], 12 12 "soundComplete": "interface/alarm/alarm_upgradearmory.xml" 13 13 } -
binaries/data/mods/public/simulation/helpers/Commands.js
349 349 350 350 "delete-entities": function(player, cmd, data) 351 351 { 352 for each (var ent indata.entities)352 for (let ent of data.entities) 353 353 { 354 // don't allow to delete entities who are half-captured 355 var cmpCapturable = Engine.QueryInterface(ent, IID_Capturable); 356 if (cmpCapturable) 357 { 358 var capturePoints = cmpCapturable.GetCapturePoints(); 359 var maxCapturePoints = cmpCapturable.GetMaxCapturePoints(); 360 if (capturePoints[player] < maxCapturePoints / 2) 361 return; 362 } 363 // either kill or delete the entity 354 364 var cmpHealth = Engine.QueryInterface(ent, IID_Health); 355 365 if (cmpHealth) 356 366 { -
binaries/data/mods/public/simulation/templates/structures/merc_camp_egyptian.xml
60 60 </SoundGroups> 61 61 </Sound> 62 62 <TerritoryDecay> 63 < HealthDecayRate>1</HealthDecayRate>63 <DecayRate>1</DecayRate> 64 64 </TerritoryDecay> 65 65 <TerritoryInfluence disable=""/> 66 66 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/ptol_mercenary_camp.xml
62 62 </SoundGroups> 63 63 </Sound> 64 64 <TerritoryDecay> 65 < HealthDecayRate>1</HealthDecayRate>65 <DecayRate>1</DecayRate> 66 66 </TerritoryDecay> 67 67 <TerritoryInfluence disable=""/> 68 68 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/ptol_military_colony.xml
76 76 </SoundGroups> 77 77 </Sound> 78 78 <TerritoryDecay> 79 < HealthDecayRate>1</HealthDecayRate>79 <DecayRate>1</DecayRate> 80 80 </TerritoryDecay> 81 81 <TerritoryInfluence> 82 82 <Radius>80</Radius> -
binaries/data/mods/public/simulation/templates/structures/rome_army_camp.xml
76 76 </SoundGroups> 77 77 </Sound> 78 78 <TerritoryDecay> 79 < HealthDecayRate>10</HealthDecayRate>79 <DecayRate>10</DecayRate> 80 80 </TerritoryDecay> 81 81 <TerritoryInfluence disable=""/> 82 82 <ProductionQueue> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_gate.xml
44 44 </Obstructions> 45 45 </Obstruction> 46 46 <TerritoryDecay> 47 < HealthDecayRate>1</HealthDecayRate>47 <DecayRate>1</DecayRate> 48 48 </TerritoryDecay> 49 49 <TerritoryInfluence disable=""/> 50 50 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml
62 62 <Static width="37.0" depth="5.0"/> 63 63 </Obstruction> 64 64 <TerritoryDecay> 65 < HealthDecayRate>1</HealthDecayRate>65 <DecayRate>1</DecayRate> 66 66 </TerritoryDecay> 67 67 <TerritoryInfluence disable=""/> 68 68 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_medium.xml
55 55 <Static width="25.0" depth="5.0"/> 56 56 </Obstruction> 57 57 <TerritoryDecay> 58 < HealthDecayRate>1</HealthDecayRate>58 <DecayRate>1</DecayRate> 59 59 </TerritoryDecay> 60 60 <TerritoryInfluence disable=""/> 61 61 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_short.xml
42 42 <Static width="13.0" depth="5.0"/> 43 43 </Obstruction> 44 44 <TerritoryDecay> 45 < HealthDecayRate>1</HealthDecayRate>45 <DecayRate>1</DecayRate> 46 46 </TerritoryDecay> 47 47 <TerritoryInfluence disable=""/> 48 48 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_tower.xml
36 36 <Static width="7.0" depth="7.0"/> 37 37 </Obstruction> 38 38 <TerritoryDecay> 39 < HealthDecayRate>1</HealthDecayRate>39 <DecayRate>1</DecayRate> 40 40 </TerritoryDecay> 41 41 <TerritoryInfluence disable=""/> 42 42 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/rome_tent.xml
50 50 </SoundGroups> 51 51 </Sound> 52 52 <TerritoryDecay> 53 < HealthDecayRate>1</HealthDecayRate>53 <DecayRate>1</DecayRate> 54 54 </TerritoryDecay> 55 55 <TerritoryInfluence disable=""/> 56 56 <VisualActor> -
binaries/data/mods/public/simulation/templates/structures/sele_military_colony.xml
75 75 </SoundGroups> 76 76 </Sound> 77 77 <TerritoryDecay> 78 < HealthDecayRate>1</HealthDecayRate>78 <DecayRate>1</DecayRate> 79 79 </TerritoryDecay> 80 80 <TerritoryInfluence> 81 81 <Radius>80</Radius> -
binaries/data/mods/public/simulation/templates/template_structure.xml
20 20 <PlacementType>land</PlacementType> 21 21 <Territory>own</Territory> 22 22 </BuildRestrictions> 23 <Capturable> 24 <CapturePoints>1000</CapturePoints> 25 <RegenRate>0</RegenRate> 26 <GarrisonRegenRate>3</GarrisonRegenRate> 27 </Capturable> 23 28 <Cost> 24 29 <Population>0</Population> 25 30 <PopulationBonus>0</PopulationBonus> … … 100 105 <HeightOffset>12.0</HeightOffset> 101 106 </StatusBars> 102 107 <TerritoryDecay> 103 < HealthDecayRate>5</HealthDecayRate>108 <DecayRate>5</DecayRate> 104 109 </TerritoryDecay> 105 110 <Visibility> 106 111 <RetainInFog>true</RetainInFog> -
binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml
35 35 <MinDistance>200</MinDistance> 36 36 </Distance> 37 37 </BuildRestrictions> 38 <Capturable> 39 <CapturePoints>3000</CapturePoints> 40 </Capturable> 38 41 <Cost> 39 42 <PopulationBonus>20</PopulationBonus> 40 43 <BuildTime>500</BuildTime> -
binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml
3 3 <BuildRestrictions> 4 4 <Category>House</Category> 5 5 </BuildRestrictions> 6 <Capturable> 7 <CapturePoints>300</CapturePoints> 8 </Capturable> 6 9 <Cost> 7 10 <PopulationBonus>5</PopulationBonus> 8 11 <BuildTime>30</BuildTime> -
binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml
90 90 <HeightOffset>18.0</HeightOffset> 91 91 </StatusBars> 92 92 <TerritoryDecay> 93 < HealthDecayRate>2</HealthDecayRate>93 <DecayRate>2</DecayRate> 94 94 </TerritoryDecay> 95 95 <Vision> 96 96 <Range>80</Range> -
binaries/data/mods/public/simulation/templates/template_structure_defense_wall.xml
4 4 <PlacementType>land-shore</PlacementType> 5 5 <Category>Wall</Category> 6 6 </BuildRestrictions> 7 <Capturable disable=""/> 7 8 <Cost> 8 9 <BuildTime>10</BuildTime> 9 10 <Resources> -
binaries/data/mods/public/simulation/templates/template_structure_defense_wall_gate.xml
6 6 <BuildRestrictions> 7 7 <Category>Wall</Category> 8 8 </BuildRestrictions> 9 <Capturable disable=""/> 9 10 <Cost> 10 11 <BuildTime>0</BuildTime> 11 12 <Resources> -
binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml
23 23 <PlacementType>land-shore</PlacementType> 24 24 <Category>Wall</Category> 25 25 </BuildRestrictions> 26 <Capturable disable=""/> 26 27 <Cost> 27 28 <BuildTime>120</BuildTime> 28 29 <Resources> -
binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml
3 3 <BuildRestrictions> 4 4 <Category>Farmstead</Category> 5 5 </BuildRestrictions> 6 <Capturable> 7 <CapturePoints>300</CapturePoints> 8 </Capturable> 6 9 <Cost> 7 10 <BuildTime>45</BuildTime> 8 11 <Resources> -
binaries/data/mods/public/simulation/templates/template_structure_economic_storehouse.xml
3 3 <BuildRestrictions> 4 4 <Category>Storehouse</Category> 5 5 </BuildRestrictions> 6 <Capturable> 7 <CapturePoints>300</CapturePoints> 8 </Capturable> 6 9 <Cost> 7 10 <BuildTime>40</BuildTime> 8 11 <Resources> -
binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml
26 26 <MinDistance>80</MinDistance> 27 27 </Distance> 28 28 </BuildRestrictions> 29 <Capturable> 30 <CapturePoints>4000</CapturePoints> 31 </Capturable> 29 32 <Cost> 30 33 <PopulationBonus>10</PopulationBonus> 31 34 <BuildTime>300</BuildTime> -
binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml
8 8 <BuildRestrictions> 9 9 <Category>Field</Category> 10 10 </BuildRestrictions> 11 <Capturable disable=""/> 11 12 <Cost> 12 13 <BuildTime>50</BuildTime> 13 14 <Resources> -
binaries/data/mods/public/simulation/templates/template_structure_wonder.xml
13 13 <BuildRestrictions> 14 14 <Category>Wonder</Category> 15 15 </BuildRestrictions> 16 <Capturable> 17 <CapturePoints>5000</CapturePoints> 18 </Capturable> 16 19 <Cost> 17 20 <BuildTime>1000</BuildTime> 18 21 <Resources> -
binaries/data/mods/public/simulation/templates/template_unit_cavalry.xml
6 6 <Crush>15</Crush> 7 7 </Armour> 8 8 <Attack> 9 <Capture> 10 <Value>3</Value> 11 <MaxRange>4</MaxRange> 12 <RepeatTime>1000</RepeatTime> 13 </Capture> 9 14 <Slaughter> 10 15 <Hack>100.0</Hack> 11 16 <Pierce>0.0</Pierce> -
binaries/data/mods/public/simulation/templates/template_unit_champion.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <Entity parent="template_unit"> 3 <Attack> 4 <Capture> 5 <Value>3</Value> 6 <MaxRange>4</MaxRange> 7 <RepeatTime>1000</RepeatTime> 8 </Capture> 9 </Attack> 3 10 <Identity> 4 11 <GenericName>Champion Unit</GenericName> 5 12 <Classes datatype="tokens">Organic Human</Classes> -
binaries/data/mods/public/simulation/templates/template_unit_champion_elephant_melee.xml
1 1 <?xml version="1.0" encoding="utf-8"?> 2 2 <Entity parent="template_unit_champion_elephant"> 3 <Attack >3 <Attack replace=""> 4 4 <Melee> 5 5 <Hack>20</Hack> 6 6 <Pierce>0</Pierce> -
binaries/data/mods/public/simulation/templates/template_unit_hero_elephant_melee.xml
5 5 <Pierce>10</Pierce> 6 6 <Crush>12</Crush> 7 7 </Armour> 8 <Attack >8 <Attack replace=""> 9 9 <Melee> 10 10 <Hack>17.5</Hack> 11 11 <Pierce>0</Pierce> -
binaries/data/mods/public/simulation/templates/template_unit_infantry.xml
6 6 <Crush>15</Crush> 7 7 </Armour> 8 8 <Attack> 9 <Capture> 10 <Value>3</Value> 11 <MaxRange>4</MaxRange> 12 <RepeatTime>1000</RepeatTime> 13 </Capture> 9 14 <Slaughter> 10 15 <Hack>50.0</Hack> 11 16 <Pierce>0.0</Pierce> -
binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege.xml
5 5 <Pierce>5</Pierce> 6 6 <Crush>5</Crush> 7 7 </Armour> 8 <Capturable> 9 <CapturePoints>100</CapturePoints> 10 <RegenRate>0</RegenRate> 11 <GarrisonRegenRate>1.5</GarrisonRegenRate> 12 </Capturable> 8 13 <Cost> 9 14 <Population>5</Population> 10 15 </Cost>