Ticket #2706: UpgradeComponent.3.patch
File UpgradeComponent.3.patch, 44.8 KB (added by , 10 years ago) |
---|
-
binaries/data/mods/public/art/textures/ui/session/icons/upgrade.png
Cannot display: file marked as a binary type. svn:mime-type = application/octet-stream
-
binaries/data/mods/public/gui/session/input.js
Property changes on: binaries/data/mods/public/art/textures/ui/session/icons/upgrade.png ___________________________________________________________________ Added: svn:mime-type ## -0,0 +1 ## +application/octet-stream \ No newline at end of property
1614 1614 }); 1615 1615 } 1616 1616 1617 // Transform a wall to a gate1618 function transformWallToGate(template)1617 // Upgrade an entity to another 1618 function upgradeEntity(Template) 1619 1619 { 1620 1620 var selection = g_Selection.toList(); 1621 1621 Engine.PostNetworkCommand({ 1622 "type": "wall-to-gate", 1623 "entities": selection.filter( function(e) { return getWallGateTemplate(e) == template } ), 1624 "template": template, 1622 "type": "upgrade", 1623 "entities": selection, 1624 "template": Template, 1625 "queued": false 1625 1626 }); 1626 1627 } 1627 1628 1628 // Gets the gate form (if any) of a given long wall piece1629 function getWallGateTemplate(entity)1629 // Cancel upgrading entities 1630 function cancelUpgradeEntity() 1630 1631 { 1631 // TODO: find the gate template name in a better way 1632 var entState = GetEntityState(entity); 1633 var index; 1634 1635 if (entState && !entState.foundation && hasClass(entState, "LongWall") && (index = entState.template.indexOf("long")) >= 0) 1636 return entState.template.substr(0, index) + "gate"; 1637 return undefined; 1632 var selection = g_Selection.toList(); 1633 Engine.PostNetworkCommand({ 1634 "type": "cancel-upgrade", 1635 "entities": selection, 1636 "queued": false 1637 }); 1638 1638 } 1639 1639 1640 1640 // Set the camera to follow the given unit -
binaries/data/mods/public/gui/session/selection_panels.js
372 372 for (var i in selection) 373 373 { 374 374 var state = GetEntityState(selection[i]); 375 if ( hasClass(state, "LongWall") && !state.gate && !longWallTypes[state.template])375 if (state.gate && !gates.length) 376 376 { 377 var gateTemplate = getWallGateTemplate(state.id);378 if (gateTemplate)379 {380 var tooltipString = GetTemplateDataWithoutLocalization(state.template).gateConversionTooltip;381 if (!tooltipString)382 {383 warn(state.template + " is supposed to be convertable to a gate, but it's missing the GateConversionTooltip in the Identity template");384 tooltipString = "";385 }386 walls.push({387 "tooltip": translate(tooltipString),388 "template": gateTemplate,389 "callback": function (item) { transformWallToGate(item.template); }390 });391 }392 393 // We only need one entity per type.394 longWallTypes[state.template] = true;395 }396 else if (state.gate && !gates.length)397 {398 377 gates.push({ 399 378 "gate": state.gate, 400 379 "tooltip": translate("Lock Gate"), … … 424 403 }, 425 404 "setTooltip": function(data) 426 405 { 427 var tooltip = data.item.tooltip; 428 if (data.item.template) 429 { 430 data.template = GetTemplateData(data.item.template); 431 data.wallCount = data.selection.reduce(function (count, ent) { 432 var state = GetEntityState(ent); 433 if (hasClass(state, "LongWall") && !state.gate) 434 count++; 435 return count; 436 }, 0); 437 438 tooltip += "\n" + getEntityCostTooltip(data.template, data.wallCount); 439 440 441 data.neededResources = Engine.GuiInterfaceCall("GetNeededResources", multiplyEntityCosts(data.template, data.wallCount)); 442 if (data.neededResources) 443 tooltip += getNeededResourcesTooltip(data.neededResources); 444 } 445 data.button.tooltip = tooltip; 406 data.button.tooltip = data.item.tooltip; 446 407 }, 447 408 "setGraphics": function(data) 448 409 { 449 data.affordableMask.hidden == data.neededResources ? true : false;450 410 var gateIcon; 451 411 if (data.item.gate) 452 412 { 453 // If already a gate, show locking actions413 // Show locking actions 454 414 gateIcon = "icons/lock_" + GATE_ACTIONS[data.item.locked ? 0 : 1] + "ed.png"; 455 415 if (data.item.gate.locked === undefined) 456 416 data.guiSelection.hidden = false … … 457 417 else 458 418 data.guiSelection.hidden = data.item.gate.locked != data.item.locked; 459 419 } 460 else461 {462 // otherwise show gate upgrade icon463 var template = GetTemplateData(data.item.template);464 if (!template)465 return;466 gateIcon = data.template.icon ? "portraits/" + data.template.icon : "icons/gate_closed.png";467 data.guiSelection.hidden = true;468 }469 470 420 data.icon.sprite = "stretched:session/" + gateIcon; 471 421 }, 472 422 "setPosition": function(data) … … 541 491 }, 542 492 }; 543 493 494 // UPGRADE 495 g_SelectionPanels.Upgrade = { 496 "getMaxNumberOfItems": function() 497 { 498 return 24 - getNumberOfRightPanelButtons(); 499 }, 500 "getItems": function(unitEntState, selection) 501 { 502 // Interface becomes complicated with multiple units and this is meant per-entity, so prevent it if the selection has multiple units. 503 if (selection.length > 1) 504 return false; 505 var items = []; 506 507 if (!unitEntState.upgrade) 508 return false; 509 510 for each (var upgrade in unitEntState.upgrade.upgrades) 511 { 512 var item = {}; 513 item.entType = upgrade.entity; 514 item.template = GetTemplateData(upgrade.entity); 515 if (!item.template) // abort if no template 516 return false; 517 518 item.icon = upgrade.icon; 519 if (!item.icon) 520 item.icon = "portraits/" + item.template.icon; 521 522 if (upgrade.requiredTechnology !== null) 523 { 524 item.requiredTechnology = upgrade.requiredTechnology; 525 if (!item.requiredTechnology && item.template.requiredTechnology) 526 item.requiredTechnology = item.template.requiredTechnology 527 } 528 item.cost = upgrade.cost; 529 item.time = upgrade.time; 530 531 if (unitEntState.upgrade.progress === undefined) 532 { 533 item.upgrading = UPGRADING_NOT_STARTED; 534 item.tooltip = translate("Upgrade into a " + item.template.name.generic + (upgrade.tooltip? ".\n" + upgrade.tooltip : ".")); 535 item.callback = function(data) { upgradeEntity(data.entType); }; 536 } 537 else if (unitEntState.upgrade.template !== upgrade.entity) 538 { 539 item.upgrading = UPGRADING_CHOSEN_OTHER; 540 item.tooltip = translate("Cannot upgrade when the entity is already upgrading."); 541 item.callback = function(data) { }; 542 } 543 else 544 { 545 item.upgrading = unitEntState.upgrade.progress; 546 item.tooltip = translate("Cancel Upgrading"); 547 item.callback = function(data) { cancelUpgradeEntity(); }; 548 } 549 items.push(item); 550 } 551 return items; 552 }, 553 "addData" : function(data) 554 { 555 data.item.technologyEnabled = true; 556 if (data.item.requiredTechnology) 557 data.item.technologyEnabled = Engine.GuiInterfaceCall("IsTechnologyResearched", data.item.requiredTechnology); 558 if (data.item.cost) 559 { 560 var totalCost = {}; 561 for (var i in data.item.cost) 562 totalCost[i] = data.item.cost[i]; 563 data.item.neededResources = Engine.GuiInterfaceCall("GetNeededResources", totalCost); 564 } 565 data.item.limits = getEntityLimitAndCount(data.playerState, data.item.entType); 566 return true; 567 }, 568 "setAction": function(data) 569 { 570 data.button.onPress = function() { data.item.callback(data.item); }; 571 }, 572 "setTooltip": function(data) 573 { 574 var tooltip = data.item.tooltip; 575 576 if (data.item.upgrading !== UPGRADING_NOT_STARTED) 577 { 578 data.button.tooltip = tooltip; 579 return; 580 } 581 582 if (data.item.cost) 583 tooltip += "\n" + getEntityCostTooltip(data.item); 584 585 tooltip += formatLimitString(data.item.limits.entLimit, data.item.limits.entCount, data.item.limits.entLimitChangers); 586 if (!data.item.technologyEnabled) 587 { 588 var techName = getEntityNames(GetTechnologyData(data.item.requiredTechnology)); 589 tooltip += "\n" + sprintf(translate("Requires %(technology)s"), { technology: techName }); 590 } 591 if (data.item.neededResources) 592 tooltip += getNeededResourcesTooltip(data.item.neededResources); 593 594 data.button.tooltip = tooltip; 595 }, 596 "setGraphics": function(data) 597 { 598 var grayscale = ""; 599 if (data.item.upgrading == UPGRADING_CHOSEN_OTHER || !data.item.technologyEnabled || (data.item.limits.canBeAddedCount == 0 && data.item.upgrading == UPGRADING_NOT_STARTED)) 600 { 601 data.button.enabled = false; 602 grayscale = "grayscale:"; 603 data.affordableMask.hidden = false; 604 data.affordableMask.sprite = "colour: 0 0 0 127"; 605 } 606 else if (data.item.upgrading == UPGRADING_NOT_STARTED && data.item.neededResources) 607 { 608 data.button.enabled = false; 609 data.affordableMask.hidden = false; 610 data.affordableMask.sprite = resourcesToAlphaMask(data.item.neededResources); 611 } 612 613 data.icon.sprite = "stretched:" + grayscale + "session/" + data.item.icon; 614 615 var guiObject = Engine.GetGUIObjectByName("unitUpgradeProgressSlider["+data.i+"]"); 616 var size = guiObject.size; 617 if (data.item.upgrading < 0) 618 size.top = size.right; 619 else 620 size.top = size.left + Math.round(Math.max(0,data.item.upgrading) * (size.right - size.left)); 621 guiObject.size = size; 622 }, 623 "setPosition": function(data) 624 { 625 var index = data.i + getNumberOfRightPanelButtons(); 626 setPanelObjectPosition(data.button, index, data.rowLength); 627 }, 628 }; 629 544 630 // QUEUE 545 631 g_SelectionPanels.Queue = { 546 632 "getMaxNumberOfItems": function() … … 1015 1101 // RIGHT PANE 1016 1102 "Gate", // must always be shown on gates 1017 1103 "Pack", // must always be shown on packable entities 1104 "Upgrade", // must always be shown on upgradable entities 1018 1105 "Training", 1019 1106 "Construction", 1020 1107 "Research", // normal together with training -
binaries/data/mods/public/gui/session/selection_panels_helpers.js
8 8 // Gate constants 9 9 const GATE_ACTIONS = ["lock", "unlock"]; 10 10 11 // upgrade constants 12 const UPGRADING_NOT_STARTED = -2; 13 const UPGRADING_CHOSEN_OTHER = -1; 14 11 15 // ============================================== 12 16 // BARTER HELPERS 13 17 // Resources to sell on barter panel -
binaries/data/mods/public/gui/session/unit_commands.js
1 1 // The number of currently visible buttons (used to optimise showing/hiding) 2 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Construction": 0, "Command": 0, "Stance": 0, "Gate": 0, "Pack": 0 };2 var g_unitPanelButtons = {"Selection": 0, "Queue": 0, "Formation": 0, "Garrison": 0, "Training": 0, "Research": 0, "Barter": 0, "Construction": 0, "Command": 0, "Stance": 0, "Gate": 0, "Pack": 0, "Upgrade": 0}; 3 3 4 4 /** 5 5 * Set the position of a panel object according to the index, … … 289 289 sum += g_unitPanelButtons["Pack"]; 290 290 if (g_SelectionPanels["Gate"].used) 291 291 sum += g_unitPanelButtons["Gate"]; 292 if (g_SelectionPanels["Upgrade"].used) 293 sum += g_unitPanelButtons["Upgrade"]; 292 294 return sum; 293 295 } -
binaries/data/mods/public/gui/session/selection_panels_right/gate_panel.xml
7 7 <object name="unitGateButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom"> 8 8 <object name="unitGateIcon[n]" type="image" ghost="true" size="3 3 43 43"/> 9 9 <object name="unitGateSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/> 10 <object name="unitGateUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/>11 10 </object> 12 11 </repeat> 13 12 </object> -
binaries/data/mods/public/gui/session/selection_panels_right/upgrade_panel.xml
1 <?xml version="1.0" encoding="utf-8"?> 2 <object name="unitUpgradePanel" 3 size="10 12 100% 100%" 4 > 5 <object size="0 0 100% 100%"> 6 <repeat count="8"> 7 <object name="unitUpgradeButton[n]" hidden="true" style="iconButton" type="button" size="0 0 46 46" tooltip_style="sessionToolTipBottom"> 8 <object name="unitUpgradeIcon[n]" type="image" ghost="true" size="3 3 43 43"/> 9 <object name="unitUpgradeUpgradeIcon[n]" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/upgrade.png"/> 10 <object name="unitUpgradeProgressSlider[n]" type="image" sprite="queueProgressSlider" ghost="true" size="3 3 43 43" z="20"/> 11 <object name="unitUpgradeSelection[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="stretched:session/icons/corners.png"/> 12 <object name="unitUpgradeUnaffordable[n]" hidden="true" type="image" ghost="true" size="3 3 43 43" sprite="colour: 255 0 0 127"/> 13 </object> 14 </repeat> 15 </object> 16 </object> -
binaries/data/mods/public/simulation/components/GuiInterface.js
Property changes on: binaries/data/mods/public/gui/session/selection_panels_right/upgrade_panel.xml ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
180 180 "gate": null, 181 181 "guard": null, 182 182 "pack": null, 183 "upgrade" : null, 183 184 "player": -1, 184 185 "position": null, 185 186 "production": null, … … 232 233 }; 233 234 } 234 235 236 var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade); 237 if (cmpUpgrade) 238 { 239 ret.upgrade = { 240 "upgrades" : cmpUpgrade.GetUpgrades(), 241 "progress": cmpUpgrade.GetProgress(), 242 "template": cmpUpgrade.GetUpgradingTo() 243 }; 244 } 245 235 246 var cmpProductionQueue = Engine.QueryInterface(ent, IID_ProductionQueue); 236 247 if (cmpProductionQueue) 237 248 { … … 653 664 }; 654 665 ret.icon = template.Identity.Icon; 655 666 ret.tooltip = template.Identity.Tooltip; 656 ret.gateConversionTooltip = template.Identity.GateConversionTooltip;657 667 ret.requiredTechnology = template.Identity.RequiredTechnology; 658 668 ret.visibleIdentityClasses = GetVisibleIdentityClasses(template.Identity); 659 669 } -
binaries/data/mods/public/simulation/components/Identity.js
30 30 "</element>" + 31 31 "</optional>" + 32 32 "<optional>" + 33 "<element name='GateConversionTooltip'>" +34 "<text/>" +35 "</element>" +36 "</optional>" +37 "<optional>" +38 33 "<element name='Rollover'>" + 39 34 "<text/>" + 40 35 "</element>" + -
binaries/data/mods/public/simulation/components/Pack.js
116 116 117 117 Pack.prototype.PackProgress = function(data, lateness) 118 118 { 119 if (this.elapsedTime >=this.GetPackTime())119 if (this.elapsedTime < this.GetPackTime()) 120 120 { 121 this.CancelTimer(); 121 this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness); 122 return; 123 } 122 124 123 this.packed = !this.packed; 124 Engine.PostMessage(this.entity, MT_PackFinished, { packed: this.packed }); 125 this.CancelTimer(); 125 126 126 // Done un/packing, copy our parameters to the final entity 127 var newEntity = Engine.AddEntity(this.template.Entity); 128 if (newEntity == INVALID_ENTITY) 129 { 130 // Error (e.g. invalid template names) 131 error("PackProgress: Error creating entity for '" + this.template.Entity + "'"); 132 return; 133 } 127 this.packed = !this.packed; 128 Engine.PostMessage(this.entity, MT_PackFinished, { packed: this.packed }); 134 129 135 var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); 136 var cmpNewPosition = Engine.QueryInterface(newEntity, IID_Position); 137 if (cmpPosition.IsInWorld()) 138 { 139 var pos = cmpPosition.GetPosition2D(); 140 cmpNewPosition.JumpTo(pos.x, pos.y); 141 } 142 var rot = cmpPosition.GetRotation(); 143 cmpNewPosition.SetYRotation(rot.y); 144 cmpNewPosition.SetXZRotation(rot.x, rot.z); 145 cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset()); 130 var newEntity = ChangeEntityTemplate(this.entity, this.template.Entity); 146 131 147 var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); 148 var cmpNewOwnership = Engine.QueryInterface(newEntity, IID_Ownership); 149 cmpNewOwnership.SetOwner(cmpOwnership.GetOwner()); 150 151 // Maintain current health level 152 var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); 153 var cmpNewHealth = Engine.QueryInterface(newEntity, IID_Health); 154 var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints())); 155 cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel)); 156 157 var cmpUnitAI = Engine.QueryInterface(this.entity, IID_UnitAI); 158 var cmpNewUnitAI = Engine.QueryInterface(newEntity, IID_UnitAI); 159 if (cmpUnitAI && cmpNewUnitAI) 160 { 161 var pos = cmpUnitAI.GetHeldPosition(); 162 if (pos) 163 cmpNewUnitAI.SetHeldPosition(pos.x, pos.z); 164 if (cmpUnitAI.GetStanceName()) 165 cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName()); 166 cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders()); 167 cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf()); 168 } 169 170 // Maintain the list of guards 171 var cmpGuard = Engine.QueryInterface(this.entity, IID_Guard); 172 var cmpNewGuard = Engine.QueryInterface(newEntity, IID_Guard); 173 if (cmpGuard && cmpNewGuard) 174 cmpNewGuard.SetEntities(cmpGuard.GetEntities()); 175 176 Engine.BroadcastMessage(MT_EntityRenamed, { entity: this.entity, newentity: newEntity }); 177 178 // Play notification sound 132 if (newEntity) 133 { 179 134 var sound = this.packed ? "packed" : "unpacked"; 180 135 PlaySound(sound, newEntity); 181 182 // Destroy current entity183 Engine.DestroyEntity(this.entity);184 136 } 185 else186 {187 this.SetElapsedTime(this.GetElapsedTime() + PACKING_INTERVAL + lateness);188 }189 137 }; 190 138 191 139 Engine.RegisterComponentType(IID_Pack, "Pack", Pack); -
binaries/data/mods/public/simulation/components/Upgrade.js
1 function Upgrade() {} 2 3 const UPGRADING_PROGRESS_INTERVAL = 250; 4 5 Upgrade.prototype.Schema = 6 "<oneOrMore>" + 7 "<element>" + 8 "<anyName />" + 9 "<interleave>" + 10 "<element name='Entity' a:help='Entity to upgrade to'>" + 11 "<text/>" + 12 "</element>" + 13 "<optional>" + 14 "<element name='Icon' a:help='Icon to show in the GUI'>" + 15 "<text/>" + 16 "</element>" + 17 "</optional>" + 18 "<optional>" + 19 "<element name='Tooltip' a:help='This will be added to the tooltip to help the player choose why to upgrade.'>" + 20 "<text/>" + 21 "</element>" + 22 "</optional>" + 23 "<optional>" + 24 "<element name='Time' a:help='Time required to upgrade this entity, in milliseconds'>" + 25 "<data type='nonNegativeInteger'/>" + 26 "</element>" + 27 "</optional>" + 28 "<optional>" + 29 "<element name='Cost' a:help='Resource cost to upgrade this unit'>" + 30 "<oneOrMore>" + 31 "<choice>" + 32 "<element name='food'><data type='nonNegativeInteger'/></element>" + 33 "<element name='wood'><data type='nonNegativeInteger'/></element>" + 34 "<element name='stone'><data type='nonNegativeInteger'/></element>" + 35 "<element name='metal'><data type='nonNegativeInteger'/></element>" + 36 "</choice>" + 37 "</oneOrMore>" + 38 "</element>" + 39 "</optional>" + 40 "<optional>" + 41 "<element name='RequiredTechnology' a:help='Define what technology is required for this upgrade'>" + 42 "<choice>" + 43 "<text/>" + 44 "<empty/>" + 45 "</choice>" + 46 "</element>" + 47 "</optional>" + 48 "<optional>" + 49 "<element name='CheckPlacementRestrictions' a:help='Upgrading will check for placement restrictions'><empty/></element>" + 50 "</optional>" + 51 "</interleave>" + 52 "</element>" + 53 "</oneOrMore>"; 54 55 Upgrade.prototype.Init = function() 56 { 57 this.upgrading = false; 58 this.elapsedTime = 0; 59 this.timer = undefined; 60 61 this.upgradeTemplates = {}; 62 63 for (var choice in this.template) 64 { 65 var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); 66 var name = this.template[choice].Entity; 67 if (cmpIdentity) 68 name = name.replace(/\{civ\}/g, cmpIdentity.GetCiv()); 69 this.upgradeTemplates[name] = choice; 70 } 71 }; 72 73 // On owner change, abort the upgrade 74 // This will also deal with the "OnDestroy" case. 75 Upgrade.prototype.OnOwnershipChanged = function(msg) 76 { 77 this.CancelUpgrade(); 78 79 if (msg.to !== -1) 80 this.owner = msg.to; 81 }; 82 83 Upgrade.prototype.ChangeUpgradedEntityCount = function(amount) 84 { 85 if (!this.IsUpgrading()) 86 return; 87 var cmpTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 88 var template = cmpTempMan.GetTemplate(this.upgrading); 89 var category = null; 90 if (template.TrainingRestrictions) 91 category = template.TrainingRestrictions.Category; 92 else if (template.BuildRestrictions) 93 category = template.BuildRestrictions.Category; 94 95 var cmpEntityLimits = QueryPlayerIDInterface(this.owner, IID_EntityLimits); 96 cmpEntityLimits.ChangeCount(category,amount); 97 }; 98 99 Upgrade.prototype.CanUpgradeTo = function(template) 100 { 101 return this.upgradeTemplates[template] !== undefined; 102 }; 103 104 Upgrade.prototype.GetUpgrades = function() 105 { 106 var ret = []; 107 108 var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); 109 110 for each (var choice in this.template) 111 { 112 var entType = choice.Entity; 113 if (cmpIdentity) 114 entType = entType.replace(/\{civ\}/g, cmpIdentity.GetCiv()); 115 116 var hasCosts = false; 117 var cost = {}; 118 if (choice.Cost) 119 { 120 hasCosts = true; 121 for (var type in choice.Cost) 122 cost[type] = ApplyValueModificationsToTemplate("Upgrade/Cost/"+type, +choice.Cost[type], this.owner, entType); 123 } 124 if (choice.Time) 125 { 126 hasCosts = true; 127 cost["time"] = ApplyValueModificationsToTemplate("Upgrade/Time", +choice.Time/1000.0, this.owner, entType); 128 } 129 ret.push( 130 { 131 "entity": entType, 132 "icon": choice.Icon || undefined, 133 "cost": hasCosts ? cost : undefined, 134 "tooltip": choice.Tooltip || undefined, 135 "requiredTechnology": "RequiredTechnology" in choice ? choice.RequiredTechnology : null, 136 }); 137 } 138 139 return ret; 140 }; 141 142 Upgrade.prototype.CancelTimer = function() 143 { 144 if (this.timer) 145 { 146 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 147 cmpTimer.CancelTimer(this.timer); 148 this.timer = undefined; 149 } 150 }; 151 152 Upgrade.prototype.IsUpgrading = function() 153 { 154 return this.upgrading !== false; 155 }; 156 157 Upgrade.prototype.GetUpgradingTo = function() 158 { 159 return this.upgrading; 160 }; 161 162 Upgrade.prototype.CheckPlacementRestrictions = function(template) 163 { 164 if (!this.upgradeTemplates[template]) 165 return undefined; 166 167 return ("CheckPlacementRestrictions" in this.template[this.upgradeTemplates[template]]); 168 }; 169 170 Upgrade.prototype.GetRequiredTechnology = function(templateArg) 171 { 172 if (!this.upgradeTemplates[templateArg]) 173 return undefined; 174 175 var choice = this.upgradeTemplates[templateArg]; 176 177 if ("RequiredTechnology" in this.template[choice] && this.template[choice].RequiredTechnology === undefined) 178 { 179 var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 180 var template = cmpTemplateManager.GetTemplate(this.template[choice].Entity); 181 if (template.Identity.RequiredTechnology) 182 return template.Identity.RequiredTechnology; 183 } 184 else if ("RequiredTechnology" in this.template[choice]) 185 return this.template[choice].RequiredTechnology; 186 187 return null; 188 }; 189 190 Upgrade.prototype.GetResourceCosts = function(template) 191 { 192 if (!this.upgradeTemplates[template]) 193 return undefined; 194 195 var choice = this.upgradeTemplates[template]; 196 if (!this.template[choice].Cost) 197 return {}; 198 199 var costs = {}; 200 for (var r in this.template[choice].Cost) 201 { 202 costs[r] = +this.template[choice].Cost[r]; 203 costs[r] = ApplyValueModificationsToEntity("Upgrade/Cost/"+r, costs[r], this.entity); 204 } 205 return costs; 206 }; 207 208 Upgrade.prototype.Upgrade = function(template) 209 { 210 if (this.IsUpgrading()) 211 return false; 212 213 if (!this.upgradeTemplates[template]) 214 return false; 215 216 var cmpPlayer = Engine.QueryOwnerInterface(this.entity, IID_Player); 217 218 if (!cmpPlayer.TrySubtractResources(this.GetResourceCosts(template))) 219 return false; 220 221 this.upgrading = template; 222 223 // prevent cheating 224 this.ChangeUpgradedEntityCount(1); 225 226 if (this.GetUpgradeTime(template) !== 0) 227 { 228 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 229 this.timer = cmpTimer.SetInterval(this.entity, IID_Upgrade, "UpgradeProgress", 0, UPGRADING_PROGRESS_INTERVAL, {"upgrading": template}); 230 } 231 else 232 this.UpgradeProgress(); 233 234 return true; 235 }; 236 237 Upgrade.prototype.CancelUpgrade = function() 238 { 239 if (this.IsUpgrading() === false) 240 return; 241 242 var cmpPlayer = Engine.QueryOwnerInterface(this.entity, IID_Player); 243 if (cmpPlayer) 244 { 245 var costs = this.GetResourceCosts(this.upgrading); 246 cmpPlayer.AddResources(costs); 247 } 248 249 this.ChangeUpgradedEntityCount(-1); 250 251 this.upgrading = false; 252 this.CancelTimer(); 253 this.SetElapsedTime(0); 254 }; 255 256 Upgrade.prototype.GetUpgradeTime = function(templateArg) 257 { 258 var template = this.upgrading || templateArg; 259 var choice = this.upgradeTemplates[template]; 260 if (!choice) 261 return undefined; 262 return this.template[choice].Time ? ApplyValueModificationsToEntity("Upgrade/Time", +this.template[choice].Time, this.entity) : 0; 263 }; 264 265 Upgrade.prototype.GetElapsedTime = function() 266 { 267 return this.elapsedTime; 268 }; 269 270 Upgrade.prototype.GetProgress = function() 271 { 272 if (!this.IsUpgrading()) 273 return undefined; 274 return this.GetUpgradeTime() == 0 ? 1 : this.elapsedTime / this.GetUpgradeTime(); 275 }; 276 277 Upgrade.prototype.SetElapsedTime = function(time) 278 { 279 this.elapsedTime = time; 280 }; 281 282 Upgrade.prototype.UpgradeProgress = function(data, lateness) 283 { 284 if (this.elapsedTime < this.GetUpgradeTime()) 285 { 286 this.SetElapsedTime(this.GetElapsedTime() + UPGRADING_PROGRESS_INTERVAL + lateness); 287 return; 288 } 289 290 this.CancelTimer(); 291 292 var newEntity = ChangeEntityTemplate(this.entity, this.upgrading); 293 294 if (newEntity) 295 PlaySound("upgraded", newEntity); 296 }; 297 298 Engine.RegisterComponentType(IID_Upgrade, "Upgrade", Upgrade); -
binaries/data/mods/public/simulation/components/interfaces/Upgrade.js
Property changes on: binaries/data/mods/public/simulation/components/Upgrade.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
1 Engine.RegisterInterface("Upgrade"); -
binaries/data/mods/public/simulation/helpers/Commands.js
Property changes on: binaries/data/mods/public/simulation/components/interfaces/Upgrade.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
551 551 } 552 552 }, 553 553 554 "wall-to-gate": function(player, cmd, data)555 {556 for each (var ent in data.entities)557 {558 TryTransformWallToGate(ent, data.cmpPlayer, cmd.template);559 }560 },561 562 554 "lock-gate": function(player, cmd, data) 563 555 { 564 556 for each (var ent in data.entities) … … 642 634 } 643 635 } 644 636 }, 637 638 "upgrade": function(player, cmd, data) 639 { 640 for each (var ent in data.entities) 641 { 642 var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade); 643 644 if (!cmpUpgrade || !cmpUpgrade.CanUpgradeTo(cmd.template)) 645 continue; 646 647 if (cmpUpgrade.CheckPlacementRestrictions(cmd.template) && ObstructionsBlockingTemplateChange(ent, cmd.template)) 648 { 649 var notification = {"players": [data.cmpPlayer.GetPlayerID()], "message": "Cannot upgrade as distance requirements are not verified or terrain is obstructed." }; 650 var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 651 cmpGUIInterface.PushNotification(notification); 652 continue; 653 } 654 655 if (!CanGarrisonedChangeTemplate(ent, cmd.template)) 656 { 657 var notification = {"players": [data.cmpPlayer.GetPlayerID()], "message": "Cannot upgrade a garrisoned entity." }; 658 var cmpGUIInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface); 659 cmpGUIInterface.PushNotification(notification); 660 continue; 661 } 662 663 // Check entity limits 664 var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 665 var template = cmpTemplateManager.GetTemplate(cmd.template); 666 var cmpEntityLimits = QueryPlayerIDInterface(player, IID_EntityLimits); 667 if ((template.TrainingRestrictions && !cmpEntityLimits.AllowedToTrain(template.TrainingRestrictions.Category, 1)) 668 || (template.BuildRestrictions && !cmpEntityLimits.AllowedToBuild(template.BuildRestrictions.Category))) 669 { 670 if (g_DebugCommands) 671 warn("Invalid command: build limits check failed for player " + player + ": " + uneval(cmd)); 672 continue; 673 } 674 675 var cmpTechnologyManager = QueryOwnerInterface(ent, IID_TechnologyManager); 676 if (cmpUpgrade.GetRequiredTechnology() && !cmpTechnologyManager.IsTechnologyResearched(cmpUpgrade.GetRequiredTechnology())) 677 { 678 if (g_DebugCommands) 679 warn("Invalid command: upgrading requires unresearched technology: " + uneval(cmd)); 680 continue; 681 } 682 683 cmpUpgrade.Upgrade(cmd.template, data.cmpPlayer); 684 } 685 }, 686 687 "cancel-upgrade": function(player, cmd, data) 688 { 689 for each (var ent in data.entities) 690 { 691 var cmpUpgrade = Engine.QueryInterface(ent, IID_Upgrade); 692 if (cmpUpgrade) 693 cmpUpgrade.CancelUpgrade(data.cmpPlayer); 694 } 695 }, 696 645 697 "dialog-answer": function(player, cmd, data) 646 698 { 647 699 // Currently nothing. Triggers can read it anyway, and send this … … 1512 1564 return entities.filter(function(ent) { return CanControlUnitOrIsAlly(ent, player, controlAll);} ); 1513 1565 } 1514 1566 1515 /**1516 * Try to transform a wall to a gate1517 */1518 function TryTransformWallToGate(ent, cmpPlayer, template)1519 {1520 var cmpIdentity = Engine.QueryInterface(ent, IID_Identity);1521 if (!cmpIdentity)1522 return;1523 1524 // Check if this is a valid long wall segment1525 if (!cmpIdentity.HasClass("LongWall"))1526 {1527 if (g_DebugCommands)1528 warn("Invalid command: invalid wall conversion to gate for player "+player+": "+uneval(cmd));1529 return;1530 }1531 1532 var civ = cmpIdentity.GetCiv();1533 var gate = Engine.AddEntity(template);1534 1535 var cmpCost = Engine.QueryInterface(gate, IID_Cost);1536 if (!cmpPlayer.TrySubtractResources(cmpCost.GetResourceCosts()))1537 {1538 if (g_DebugCommands)1539 warn("Invalid command: convert gate cost check failed for player "+player+": "+uneval(cmd));1540 1541 Engine.DestroyEntity(gate);1542 return;1543 }1544 1545 ReplaceBuildingWith(ent, gate);1546 }1547 1548 /**1549 * Unconditionally replace a building with another one1550 */1551 function ReplaceBuildingWith(ent, building)1552 {1553 // Move the building to the right place1554 var cmpPosition = Engine.QueryInterface(ent, IID_Position);1555 var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position);1556 var pos = cmpPosition.GetPosition2D();1557 cmpBuildingPosition.JumpTo(pos.x, pos.y);1558 var rot = cmpPosition.GetRotation();1559 cmpBuildingPosition.SetYRotation(rot.y);1560 cmpBuildingPosition.SetXZRotation(rot.x, rot.z);1561 1562 // Copy ownership1563 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);1564 var cmpBuildingOwnership = Engine.QueryInterface(building, IID_Ownership);1565 cmpBuildingOwnership.SetOwner(cmpOwnership.GetOwner());1566 1567 // Copy control groups1568 var cmpObstruction = Engine.QueryInterface(ent, IID_Obstruction);1569 var cmpBuildingObstruction = Engine.QueryInterface(building, IID_Obstruction);1570 cmpBuildingObstruction.SetControlGroup(cmpObstruction.GetControlGroup());1571 cmpBuildingObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2());1572 1573 // Copy health level from the old entity to the new1574 var cmpHealth = Engine.QueryInterface(ent, IID_Health);1575 var cmpBuildingHealth = Engine.QueryInterface(building, IID_Health);1576 var healthFraction = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints()));1577 var buildingHitpoints = Math.round(cmpBuildingHealth.GetMaxHitpoints() * healthFraction);1578 cmpBuildingHealth.SetHitpoints(buildingHitpoints);1579 1580 PlaySound("constructed", building);1581 1582 Engine.PostMessage(ent, MT_ConstructionFinished,1583 { "entity": ent, "newentity": building });1584 Engine.BroadcastMessage(MT_EntityRenamed, { entity: ent, newentity: building });1585 1586 Engine.DestroyEntity(ent);1587 }1588 1589 1567 Engine.RegisterGlobal("GetFormationRequirements", GetFormationRequirements); 1590 1568 Engine.RegisterGlobal("CanMoveEntsIntoFormation", CanMoveEntsIntoFormation); 1591 1569 Engine.RegisterGlobal("GetDockAngle", GetDockAngle); -
binaries/data/mods/public/simulation/helpers/Transform.js
1 // Helper functions to change an entity's template and check if the transformation is possible 2 3 // returns the ID of the new entity or INVALID_ENTITY. 4 var ChangeEntityTemplate = function(oldEnt, newTemplate) 5 { 6 // Done un/packing, copy our parameters to the final entity 7 var newEnt = Engine.AddEntity(newTemplate); 8 if (newEnt == INVALID_ENTITY) 9 { 10 // Error (e.g. invalid template names) 11 error("Transform.js: Error replacing entity " + oldEnt + " for a '" + newTemplate + "'"); 12 return INVALID_ENTITY; 13 } 14 15 var cmpPosition = Engine.QueryInterface(oldEnt, IID_Position); 16 var cmpNewPosition = Engine.QueryInterface(newEnt, IID_Position); 17 if (cmpPosition && cmpNewPosition) 18 { 19 if (cmpPosition.IsInWorld()) 20 { 21 var pos = cmpPosition.GetPosition2D(); 22 cmpNewPosition.JumpTo(pos.x, pos.y); 23 } 24 var rot = cmpPosition.GetRotation(); 25 cmpNewPosition.SetYRotation(rot.y); 26 cmpNewPosition.SetXZRotation(rot.x, rot.z); 27 cmpNewPosition.SetHeightOffset(cmpPosition.GetHeightOffset()); 28 } 29 30 var cmpOwnership = Engine.QueryInterface(oldEnt, IID_Ownership); 31 var cmpNewOwnership = Engine.QueryInterface(newEnt, IID_Ownership); 32 if (cmpOwnership && cmpNewOwnership) 33 cmpNewOwnership.SetOwner(cmpOwnership.GetOwner()); 34 35 // Copy control groups 36 var cmpObstruction = Engine.QueryInterface(oldEnt, IID_Obstruction); 37 var cmpNewObstruction = Engine.QueryInterface(newEnt, IID_Obstruction); 38 if (cmpObstruction && cmpNewObstruction) 39 { 40 cmpNewObstruction.SetControlGroup(cmpObstruction.GetControlGroup()); 41 cmpNewObstruction.SetControlGroup2(cmpObstruction.GetControlGroup2()); 42 } 43 44 // Maintain current health level 45 var cmpHealth = Engine.QueryInterface(oldEnt, IID_Health); 46 var cmpNewHealth = Engine.QueryInterface(newEnt, IID_Health); 47 if (cmpHealth && cmpNewHealth) 48 { 49 var healthLevel = Math.max(0, Math.min(1, cmpHealth.GetHitpoints() / cmpHealth.GetMaxHitpoints())); 50 cmpNewHealth.SetHitpoints(Math.round(cmpNewHealth.GetMaxHitpoints() * healthLevel)); 51 } 52 53 var cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI); 54 var cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI); 55 if (cmpUnitAI && cmpNewUnitAI) 56 { 57 var pos = cmpUnitAI.GetHeldPosition(); 58 if (pos) 59 cmpNewUnitAI.SetHeldPosition(pos.x, pos.z); 60 if (cmpUnitAI.GetStanceName()) 61 cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName()); 62 cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders()); 63 cmpNewUnitAI.SetGuardOf(cmpUnitAI.IsGuardOf()); 64 } 65 66 // Maintain the list of guards 67 var cmpGuard = Engine.QueryInterface(oldEnt, IID_Guard); 68 var cmpNewGuard = Engine.QueryInterface(newEnt, IID_Guard); 69 if (cmpGuard && cmpNewGuard) 70 cmpNewGuard.SetEntities(cmpGuard.GetEntities()); 71 72 TransferGarrisonedUnits(oldEnt, newEnt); 73 74 Engine.BroadcastMessage(MT_EntityRenamed, { entity: oldEnt, newEnt: newEnt }); 75 76 // Destroy current entity 77 Engine.DestroyEntity(oldEnt); 78 79 return newEnt; 80 }; 81 82 var CanGarrisonedChangeTemplate = function(ent, Template) 83 { 84 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 85 var unitAI = Engine.QueryInterface(ent, IID_UnitAI); 86 if (cmpPosition && !cmpPosition.IsInWorld() && unitAI && unitAI.IsGarrisoned()) 87 { 88 // We're a garrisoned unit, assume impossibility as I've been unable to find a way to get the holder ID. 89 // TODO: change this if that ever becomes possibles 90 return false; 91 } 92 return true; 93 } 94 95 var ObstructionsBlockingTemplateChange = function(ent, Template) 96 { 97 var previewEntity = Engine.AddEntity("preview|"+Template); 98 99 if (previewEntity == INVALID_ENTITY) 100 return true; 101 102 var cmpBuildRestrictions = Engine.QueryInterface(previewEntity, IID_BuildRestrictions); 103 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 104 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 105 106 // Return false if no ownership as BuildRestrictions.CheckPlacement needs an owner and I have no idea if false or true is better 107 // Plus there are no real entities without owners currently. 108 if (!cmpBuildRestrictions || !cmpPosition || !cmpOwnership) 109 return DeleteEntityAndReturnFalse(previewEntity, false); 110 111 var pos = cmpPosition.GetPosition2D(); 112 var angle = cmpPosition.GetRotation(); 113 // move us away to prevent our own obstruction from blocking the upgrade. 114 cmpPosition.MoveOutOfWorld(); 115 116 var cmpNewPosition = Engine.QueryInterface(previewEntity, IID_Position); 117 cmpNewPosition.JumpTo(pos.x, pos.y); 118 cmpNewPosition.SetYRotation(angle.y); 119 120 var cmpNewOwnership = Engine.QueryInterface(previewEntity, IID_Ownership); 121 cmpNewOwnership.SetOwner(cmpOwnership.GetOwner()); 122 123 var checkPlacement = cmpBuildRestrictions.CheckPlacement(); 124 if (checkPlacement && !checkPlacement.success) 125 return DeleteEntityAndReturnFalse(previewEntity, true); 126 127 var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); 128 var template = cmpTemplateManager.GetTemplate(cmpTemplateManager.GetCurrentTemplateName()); 129 var newTemplate = cmpTemplateManager.GetTemplate(Template); 130 131 // Check if units are blocking our template change 132 if (canTransform && template.Obstruction && newTemplate.Obstruction) 133 { 134 // This only needs to be done if the new template is strictly bigger than the old one 135 // "Obstructions" are annoying to test so just check. 136 if (newTemplate.Obstruction.Obstructions 137 || (template.Obstruction.Static && (template.Obstruction.Static.width > newTemplate.Obstruction.Static.width 138 || template.Obstruction.Static.depth > newTemplate.Obstruction.Static.depth)) 139 || (template.Obstruction.Unit && template.Obstruction.Unit.radius > newTemplate.Obstruction.Unit.radius)) 140 { 141 var cmpNewObstruction = Engine.QueryInterface(previewEntity, IID_Obstruction); 142 if (cmpNewObstruction && cmpNewObstruction.GetBlockMovementFlag()) 143 { 144 // Check for units 145 var collisions = cmpNewObstruction.GetEntityCollisions(false, true); 146 if (collisions.length !== 0) 147 return DeleteEntityAndReturnFalse(previewEntity, true); 148 } 149 } 150 } 151 152 // Move preview entity out of world so it won't interfere with additional calls 153 // to this function for ent on the same sim update. 154 // Specifically, CheckPlacement() would incorrectly spot and fail on earlier preview entities 155 // if those are left in the world. 156 cmpNewPosition.MoveOutOfWorld(); 157 cmpPosition.JumpTo(pos.x, pos.y); 158 cmpPosition.SetYRotation(angle.y); 159 160 return DeleteEntityAndReturnFalse(previewEntity, false); 161 }; 162 163 var DeleteEntityAndReturn = function(ent, ret) 164 { 165 Engine.DestroyEntity(previewEntity); 166 return ret; 167 }; 168 169 var TransferGarrisonedUnits = function(oldEnt, newEnt) 170 { 171 // Transfer garrisoned units if possible, or unload them 172 var cmpGarrison = Engine.QueryInterface(oldEnt, IID_GarrisonHolder); 173 var cmpNewGarrison = Engine.QueryInterface(newEnt, IID_GarrisonHolder); 174 if (!cmpNewGarrison || !cmpGarrison || cmpGarrison.GetEntities().length === 0 ) 175 return; // nothing to do as the code will by default unload all. 176 177 var garrisonedEntities = cmpGarrison.GetEntities().slice(); 178 for (var j = 0; j < garrisonedEntities.length; ++j) 179 { 180 var cmpUnitAI = Engine.QueryInterface(garrisonedEntities[j], IID_UnitAI); 181 cmpGarrison.Eject(garrisonedEntities[j]); 182 cmpUnitAI.Autogarrison(newEnt); 183 cmpNewGarrison.Garrison(garrisonedEntities[j]); 184 } 185 }; 186 187 Engine.RegisterGlobal("ChangeEntityTemplate", ChangeEntityTemplate); 188 Engine.RegisterGlobal("CanGarrisonedChangeTemplate", CanGarrisonedChangeTemplate); 189 Engine.RegisterGlobal("ObstructionsBlockingTemplateChange", ObstructionsBlockingTemplateChange); -
binaries/data/mods/public/simulation/templates/template_structure_defense_wall_long.xml
Property changes on: binaries/data/mods/public/simulation/helpers/Transform.js ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
51 51 <Identity> 52 52 <Classes datatype="tokens">LongWall</Classes> 53 53 <Tooltip>Long wall segments can be converted to gates.</Tooltip> 54 <GateConversionTooltip>Convert Stone Wall into City Gate</GateConversionTooltip>55 54 </Identity> 55 <Upgrade> 56 <Gate> 57 <Entity>structures/{civ}_wall_gate</Entity> 58 <Tooltip>This will allow you to let units circulate through your fortifications.</Tooltip> 59 <Cost> 60 <stone>60</stone> 61 </Cost> 62 <Time>10000</Time> 63 </Gate> 64 </Upgrade> 56 65 </Entity> -
binaries/data/mods/public/simulation/templates/other/palisades_rocks_long.xml
21 21 <SelectionGroupName>other/wallset_palisade</SelectionGroupName> 22 22 <SpecificName>Palisade</SpecificName> 23 23 <GenericName>Wooden Wall</GenericName> 24 <GateConversionTooltip>Convert Wooden Wall into Wooden Gate</GateConversionTooltip>25 24 <Classes datatype="tokens">-StoneWall Palisade</Classes> 26 25 <History>A cheap, quick defensive structure constructed with sharpened tree trunks</History> 27 26 <Icon>gaia/special_palisade.png</Icon> … … 41 40 <WallPiece> 42 41 <Length>11.0</Length> 43 42 </WallPiece> 43 <Upgrade> 44 <Gate> 45 <Entity>other/palisades_rocks_gate</Entity> 46 <Cost> 47 <stone>0</stone> 48 <wood>20</wood> 49 </Cost> 50 <Time>5000</Time> 51 </Gate> 52 </Upgrade> 44 53 </Entity> -
binaries/data/mods/public/simulation/templates/structures/rome_siege_wall_long.xml
55 55 </Classes> 56 56 <Icon>structures/palisade.png</Icon> 57 57 <Tooltip>A wooden and turf palisade buildable in enemy and neutral territories.</Tooltip> 58 <GateConversionTooltip>Convert Siege Wall into Siege Wall Gate</GateConversionTooltip>59 58 <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> 60 59 </Identity> 61 60 <Obstruction>