Ticket #610: Garrisoning.patch
File Garrisoning.patch, 19.2 KB (added by , 14 years ago) |
---|
-
binaries/data/mods/public/gui/session_new/utility_functions.js
160 160 { 161 161 case "delete": 162 162 return 1; 163 case "unload-all": 164 return 2; 163 165 default: 164 166 return -1; 165 167 } … … 188 190 function getEntityCommandsList(entState) 189 191 { 190 192 var commands = []; 193 commands.push("Stop"); 194 commands.push("Stop that!"); 195 if (entState.garrisonHolder) 196 commands.push("unload-all"); 191 197 commands.push("delete"); 192 198 return commands; 193 199 } -
binaries/data/mods/public/gui/session_new/input.js
7 7 const SDLK_LSHIFT = 304; 8 8 const SDLK_RCTRL = 305; 9 9 const SDLK_LCTRL = 306; 10 10 const SDLK_RALT = 307; 11 const SDLK_LALT = 308; 11 12 // TODO: these constants should be defined somewhere else instead, in 12 13 // case any other code wants to use them too 13 14 … … 34 35 specialKeyStates[SDLK_LSHIFT] = 0; 35 36 specialKeyStates[SDLK_RCTRL] = 0; 36 37 specialKeyStates[SDLK_LCTRL] = 0; 38 specialKeyStates[SDLK_RALT] = 0; 39 specialKeyStates[SDLK_LALT] = 0; 40 37 41 // (TODO: maybe we should fix the hotkey system to be usable in this situation, 38 42 // rather than hardcoding Shift into this code?) 39 43 … … 72 76 function determineAction(x, y, fromMinimap) 73 77 { 74 78 var selection = g_Selection.toList(); 75 79 var ctrlPressed = specialKeyStates[SDLK_LCTRL] || specialKeyStates[SDLK_RCTRL]; 76 80 // No action if there's no selection 77 81 if (!selection.length) 78 82 return undefined; … … 122 126 var enemyOwned = ((targetState.player != entState.player)? true : false); 123 127 var gaiaOwned = ((targetState.player == 0)? true : false); 124 128 125 // If the target is a resource and we have the right kind of resource gatherers selected, then gather 126 // If the target is a foundation and we have builders selected, then build (or repair) 127 // If the target is an enemy, then attack 128 if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 129 130 if (targetState.garrisonHolder && playerOwned && ctrlPressed) 129 131 { 132 return {"type": "garrison", "cursor": "action-garrison", "target": targets[0]}; 133 } 134 else if (targetState.resourceSupply && (playerOwned || gaiaOwned)) 135 { 136 // If the target is a resource and we have the right kind of resource gatherers selected, then gather 137 // If the target is a foundation and we have builders selected, then build (or repair) 138 // If the target is an enemy, then attack 130 139 var resource = findGatherType(entState.resourceGatherRates, targetState.resourceSupply); 131 140 if (resource) 132 141 return {"type": "gather", "cursor": "action-gather-"+resource, "target": targets[0]}; … … 522 531 Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] }); 523 532 return true; 524 533 534 case "garrison": 535 Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued}); 536 //Need to play some sound here?? 537 return true; 538 525 539 case "set-rallypoint": 526 540 var target = Engine.GetTerrainAtPoint(ev.x, ev.y); 527 541 Engine.PostNetworkCommand({"type": "set-rallypoint", "entities": selection, "x": target.x, "z": target.z}); … … 801 815 messageBox(320, 180, message, "Confirmation", 0, btCaptions, btCode); 802 816 } 803 817 break; 818 case "unload-all": 819 unloadAll(entity); 820 break; 804 821 default: 805 822 break; 806 823 } … … 835 852 Engine.CameraFollow(0); 836 853 } 837 854 855 function unload(garrisonHolder, entity) 856 { 857 Engine.PostNetworkCommand({"type": "unload", "entity": entity, "garrisonHolder": garrisonHolder}); 858 } 859 860 function unloadAll(garrisonHolder) 861 { 862 Engine.PostNetworkCommand({"type": "unload-all", "garrisonHolder": garrisonHolder}); 863 } 864 No newline at end of file -
binaries/data/mods/public/gui/session_new/unit_commands.js
114 114 usedPanels[guiName] = 1; 115 115 var numberOfItems = items.length; 116 116 var selection = g_Selection.toList(); 117 117 var garrisonGroups = new EntityGroups(); 118 118 if (guiName == "Selection") 119 119 { 120 120 if (numberOfItems > 16) 121 121 numberOfItems = 16; 122 122 } 123 if (guiName == "Formation" || guiName == "Garrison")123 if (guiName == "Formation") 124 124 { 125 125 if (numberOfItems > 16) 126 126 numberOfItems = 16; … … 130 130 if (numberOfItems > 16) 131 131 numberOfItems = 16; 132 132 } 133 else if (guiName == "Garrison") 134 { 135 if (numberOfItems > 16) 136 numberOfItems = 16; 137 //Group garrisoned units based on class 138 garrisonGroups.add(unitEntState.garrisonHolder.entities); 139 } 133 140 else if (guiName == "Command") 134 141 { 135 142 if (numberOfItems > 4) … … 177 184 break; 178 185 179 186 case GARRISON: 180 181 182 183 184 185 /* 186 187 !!!!! 188 GARRISON GOES HERE (need to customize this) 189 !!!!! 190 191 192 var tooltip = getEntityName(template); 193 var count = g_Selection.groups.getCount(item); 187 var name = getEntityName(template); 188 var tooltip = "Unload " + getEntityName(template); 189 var count = garrisonGroups.getCount(name); 194 190 getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 1 ? count : ""); 195 196 197 */198 199 200 201 202 203 191 break; 204 192 205 193 case FORMATION: … … 337 325 if (commands.length) 338 326 setupUnitPanel("Command", usedPanels, entState, commands, 339 327 function (item) { performCommand(entState.id, item); } ); 340 341 342 343 344 345 346 /* 347 !!!!! 348 GARRISON GOES HERE (need to customize this) 349 !!!!! 350 */ 351 352 353 /* 354 if (selection.length > 1) 355 setupUnitPanel("Garrison", usedPanels, entState, g_Selection.groups.getTemplateNames(), 356 function (entType) { changePrimarySelectionGroup(entType); } ); 357 */ 358 359 360 361 362 363 328 if (entState.garrisonHolder) 329 { 330 var groups = new EntityGroups(); 331 groups.add(entState.garrisonHolder.entities); 332 setupUnitPanel("Garrison", usedPanels, entState, groups.getTemplateNames(), 333 function (item) 334 { 335 var template = GetTemplateData(item); 336 var unitName = template.name.specific || template.name.generic || "???"; 337 unload(entState.id, groups.getEntsByName(unitName)[0]); 338 } ); 339 } 364 340 var formations = getEntityFormationsList(entState); 365 341 if (formations.length) 366 342 setupUnitPanel("Formation", usedPanels, entState, formations, -
binaries/data/mods/public/simulation/helpers/Commands.js
160 160 Engine.PostMessage(playerEnt, MT_PlayerDefeated, null); 161 161 break; 162 162 163 case "garrison": 164 var targetCmpOwnership = Engine.QueryInterface(cmd.target, IID_Ownership); 165 if (!targetCmpOwnership || targetCmpOwnership.GetOwner() != player) 166 break; 167 for each (var ent in cmd.entities) 168 { 169 var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); 170 if (!cmpOwnership || cmpOwnership.GetOwner() != player) 171 break; 172 var cmpUnitAI = Engine.QueryInterface(ent, IID_UnitAI); 173 if (cmpUnitAI) 174 cmpUnitAI.Garrison(cmd.target); 175 } 176 break; 177 178 case "unload": 179 var cmpOwnership = Engine.QueryInterface(cmd.garrisonHolder, IID_Ownership); 180 if (!cmpOwnership || cmpOwnership.GetOwner() != player) 181 break; 182 var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder); 183 if (cmpGarrisonHolder) 184 cmpGarrisonHolder.Unload(cmd.entity); 185 break; 186 187 case "unload-all": 188 var cmpOwnership = Engine.QueryInterface(cmd.garrisonHolder, IID_Ownership); 189 if (!cmpOwnership || cmpOwnership.GetOwner() != player) 190 break; 191 192 var cmpGarrisonHolder = Engine.QueryInterface(cmd.garrisonHolder, IID_GarrisonHolder); 193 cmpGarrisonHolder.UnloadAll(); 194 break; 195 163 196 default: 164 197 error("Ignoring unrecognised command type '" + cmd.type + "'"); 165 198 } -
binaries/data/mods/public/simulation/components/GuiInterface.js
69 69 } 70 70 71 71 var cmpPosition = Engine.QueryInterface(ent, IID_Position); 72 if (cmpPosition )72 if (cmpPosition && cmpPosition.IsInWorld()) 73 73 { 74 74 ret.position = cmpPosition.GetPosition(); 75 75 } … … 146 146 ret.rallyPoint = { }; 147 147 } 148 148 149 var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder); 150 if (cmpGarrisonHolder) 151 { 152 ret.garrisonHolder = { 153 "entities": cmpGarrisonHolder.GetEntities() 154 }; 155 } 156 149 157 var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); 150 158 ret.visibility = cmpRangeManager.GetLosVisibility(ent, player); 151 159 -
binaries/data/mods/public/simulation/components/GarrisonHolder.js
3 3 GarrisonHolder.prototype.Schema = 4 4 "<element name='Max'>" + 5 5 "<data type='positiveInteger'/>" + 6 "</element>" + 7 "<element name='EjectHealth'>" + 8 "<ref name='positiveDecimal'/>" + 9 "</element>" + 10 "<element name='List'>" + 11 "<attribute name='datatype'>" + 12 "<value>tokens</value>" + 13 "</attribute>" + 14 "<text/>" + 15 "</element>" + 16 "<element name='BuffHeal'>" + 17 "<data type='positiveInteger'/>" + 6 18 "</element>"; 7 19 8 /* 9 * TODO: this all needs to be designed and implemented20 /** 21 * Initialize GarrisonHolder Component 10 22 */ 23 GarrisonHolder.prototype.Init = function() 24 { 25 //Garrisoned Units 26 this.entities = []; 27 this.spaceOccupied = 0; 28 this.timer = undefined; 29 this.healRate = this.template.BuffHeal; 11 30 31 }; 32 33 /** 34 * Return the list of entities garrisoned inside 35 */ 36 GarrisonHolder.prototype.GetEntities = function() 37 { 38 return this.entities; 39 } 40 41 /** 42 * Returns an array of unit classes which can be garrisoned inside this 43 * particualar entity. Obtained from the entity's template 44 */ 45 GarrisonHolder.prototype.GetAllowedClassesList = function() 46 { 47 var string = this.template.List._string; 48 return string.split(/\s+/); 49 }; 50 51 /** 52 * Get Maximum pop which can be garrisoned 53 */ 54 GarrisonHolder.prototype.GetCapacity = function() 55 { 56 return this.template.Max; 57 }; 58 59 /** 60 * Garrison a unit inside. 61 * Returns true if successful, false if not 62 * The timer for AutoHeal is started here 63 */ 64 GarrisonHolder.prototype.Garrison = function(entity) 65 { 66 var entityPopCost = (Engine.QueryInterface(entity, IID_Cost)).GetPopCost(); 67 var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList(); 68 var allowedClasses = this.GetAllowedClassesList(); 69 var classNotAllowed = true; 70 71 if (!this.HasEnoughHealth()) 72 return false; 73 //Check if the unit is allowed to be garrisoned inside the building 74 for each (var allowedClass in allowedClasses) 75 { 76 if (entityClasses.indexOf(allowedClass) != -1) 77 { 78 classNotAllowed = false; 79 break; 80 } 81 } 82 if (classNotAllowed) 83 return false; 84 if (this.GetCapacity() < (this.spaceOccupied + entityPopCost)) 85 return false; 86 var cmpPosition = Engine.QueryInterface(entity, IID_Position); 87 if (!this.timer) 88 { 89 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 90 this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {}); 91 } 92 if (cmpPosition) 93 { 94 //Actual garrisoning happens here 95 this.entities.push(entity); 96 this.spaceOccupied += entityPopCost; 97 cmpPosition.MoveOutOfWorld(); 98 return true; 99 } 100 else 101 { 102 return false; 103 } 104 }; 105 106 /** 107 * Unload units from the garrisoning entity 108 */ 109 GarrisonHolder.prototype.Unload = function(entity) 110 { 111 var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); 112 var entityIndex = this.entities.indexOf(entity); 113 this.spaceOccupied -= (Engine.QueryInterface(entity, IID_Cost)).GetPopCost(); 114 this.entities.splice(entityIndex, 1); 115 var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); 116 var pos = cmpFootprint.PickSpawnPoint(entity); 117 if (pos.y < 0) 118 { 119 // Whoops, something went wrong (maybe there wasn't any space to place the unit). 120 // What should we do here? 121 // For now, just move the unit into the middle of the building where it'll probably get stuck 122 pos = cmpPosition.GetPosition(); 123 warn("Can't find free space to spawn trained unit"); 124 } 125 126 var cmpNewPosition = Engine.QueryInterface(entity, IID_Position); 127 cmpNewPosition.JumpTo(pos.x, pos.z); 128 // TODO: what direction should they face in? 129 130 // If a rally point is set, walk towards it 131 var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); 132 if (cmpUnitAI && cmpRallyPoint) 133 { 134 var rallyPos = cmpRallyPoint.GetPosition(); 135 if (rallyPos) 136 { 137 cmpUnitAI.Walk(rallyPos.x, rallyPos.z, false); 138 } 139 else 140 { 141 //Reset state. This needs to be done since they were walking before being moved 142 //out of the world 143 } 144 } 145 }; 146 147 /** 148 * Used to check if the garrisoning entity's health has fallen below 149 * a certain limit after which all garrisoned units are unloaded 150 */ 151 GarrisonHolder.prototype.OnHealthChanged = function(msg) 152 { 153 if (!this.HasEnoughHealth()) 154 { 155 this.UnloadAll(); 156 } 157 158 }; 159 160 /** 161 * Check if this entity has enough health to garrison units inside it 162 */ 163 GarrisonHolder.prototype.HasEnoughHealth = function() 164 { 165 var cmpHealth = Engine.QueryInterface(this.entity, IID_Health) 166 var hitpoints = cmpHealth.GetHitpoints(); 167 var maxHitpoints = cmpHealth.GetMaxHitpoints(); 168 var ejectHitpoints = parseInt(parseFloat(this.template.EjectHealth) * maxHitpoints); 169 return hitpoints > ejectHitpoints; 170 171 }; 172 173 /** 174 * Unload all units from the entity 175 */ 176 GarrisonHolder.prototype.UnloadAll = function() 177 { 178 //The entities list is saved to a temporary variable 179 //because during each loop an element is removed 180 //from the list 181 var entities = this.entities.splice(0); 182 for each (var entity in entities) 183 { 184 this.Unload(entity); 185 } 186 }; 187 188 /** 189 * Called every second. Heals garrisoned units 190 */ 191 GarrisonHolder.prototype.HealTimeout = function(data) 192 { 193 if (this.entities.length == 0) 194 { 195 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 196 cmpTimer.CancelTimer(this.timer); 197 this.timer = undefined; 198 } 199 else 200 { 201 for each (var entity in this.entities) 202 { 203 var cmpHealth = Engine.QueryInterface(entity, IID_Health); 204 if (cmpHealth) 205 { 206 if (cmpHealth.GetHitpoints() < cmpHealth.GetMaxHitpoints()) 207 cmpHealth.Increase(this.healRate); 208 } 209 } 210 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 211 this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {}); 212 } 213 }; 214 215 GarrisonHolder.prototype.OnOwnershipChanged = function(msg) 216 { 217 218 }; 219 220 /** 221 * Cancel timer when destroyed 222 */ 223 GarrisonHolder.prototype.OnDestroy = function() 224 { 225 if (this.timer) 226 { 227 var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); 228 cmpTimer.CancelTimer(this.timer); 229 } 230 }; 231 12 232 Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder); -
binaries/data/mods/public/simulation/components/Identity.js
60 60 "<list>" + 61 61 "<zeroOrMore>" + 62 62 "<choice>" + 63 "<value>Unit</value>" + 63 64 "<value>Organic</value>" + 64 65 "<value>Foot</value>" + 65 66 "<value>Mounted</value>" + -
binaries/data/mods/public/simulation/components/UnitAI.js
162 162 this.SetNextState("INDIVIDUAL.REPAIR.REPAIRING"); 163 163 } 164 164 }, 165 166 "Order.Garrison": function(msg) { 167 if (this.MoveToTarget(this.order.data.target)) 168 { 169 this.SetNextState("INDIVIDUAL.GARRISON.APPROACHING"); 170 } 171 else 172 { 173 this.SetNextState("INDIVIDUAL.GARRISON.GARRISONED"); 174 } 175 }, 165 176 166 167 177 // States for the special entity representing a group of units moving in formation: 168 178 "FORMATIONCONTROLLER": { 169 179 … … 508 518 } 509 519 }, 510 520 }, 521 522 "GARRISON": { 523 "APPROACHING": { 524 "enter": function() { 525 this.SelectAnimation("walk", false, this.GetWalkSpeed()); 526 this.PlaySound("walk"); 527 }, 528 529 "MoveCompleted": function() { 530 this.SetNextState("GARRISONED"); 531 }, 532 533 "leave": function() { 534 this.StopTimer(); 535 } 536 }, 537 538 "GARRISONED": { 539 "enter": function() { 540 var cmpGarrisonHolder = Engine.QueryInterface(this.order.data.target, IID_GarrisonHolder); 541 if (cmpGarrisonHolder) 542 { 543 cmpGarrisonHolder.Garrison(this.entity); 544 545 } 546 if (this.FinishOrder()) 547 return; 548 }, 549 550 "leave": function() { 551 552 } 553 }, 554 }, 555 511 556 }, 512 557 }; 513 558 … … 1002 1047 this.AddOrder("Attack", { "target": target }, queued); 1003 1048 }; 1004 1049 1050 UnitAI.prototype.Garrison = function(target, queued) 1051 { 1052 if (!this.CanGarrison(target)) 1053 { 1054 this.WalkToTarget(target, queued); 1055 return; 1056 } 1057 this.AddOrder("Garrison", { "target": target }, queued); 1058 }; 1059 1005 1060 UnitAI.prototype.Gather = function(target, queued) 1006 1061 { 1007 1062 if (!this.CanGather(target)) … … 1062 1117 return true; 1063 1118 }; 1064 1119 1120 UnitAI.prototype.CanGarrison = function(target) 1121 { 1122 var cmpGarrisonHolder = Engine.QueryInterface(target, IID_GarrisonHolder); 1123 if (!cmpGarrisonHolder) 1124 return false; 1125 1126 return true; 1127 }; 1128 1065 1129 UnitAI.prototype.CanGather = function(target) 1066 1130 { 1067 1131 // Formation controllers should always respond to commands -
binaries/data/mods/public/simulation/templates/template_structure.xml
54 54 <RetainInFog>true</RetainInFog> 55 55 <AlwaysVisible>false</AlwaysVisible> 56 56 </Vision> 57 <GarrisonHolder> 58 <Max>15</Max> 59 <EjectHealth>0.1</EjectHealth> 60 <List datatype="tokens">Unit</List> 61 <BuffHeal>1</BuffHeal> 62 </GarrisonHolder> 57 63 <RallyPoint/> 58 64 <Sound> 59 65 <SoundGroups> -
binaries/data/mods/public/simulation/templates/template_unit.xml
3 3 <Identity> 4 4 <GenericName>Unit</GenericName> 5 5 <Classes datatype="tokens"> 6 ConquestCritical 6 ConquestCritical Unit 7 7 </Classes> 8 8 </Identity> 9 9 <Minimap>