diff --git a/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_b.xml b/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_b.xml index 53a0eb5ebc..938adbf0c9 100644 --- a/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_b.xml +++ b/binaries/data/mods/public/art/actors/units/celts/infantry_spearman_b.xml @@ -6,6 +6,10 @@ + + + + @@ -110,6 +114,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + player_trans.xml diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-food.png b/binaries/data/mods/public/art/textures/cursors/action-return-food.png new file mode 100644 index 0000000000..10e86fb74f --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-food.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:077f5efeae33a5846ac687626e7c43e6b53ef1d077b85669bae940c1a24e97a9 +size 2414 diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-food.txt b/binaries/data/mods/public/art/textures/cursors/action-return-food.txt new file mode 100644 index 0000000000..2fb73a07ec --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-food.txt @@ -0,0 +1 @@ +1 1 diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-metal.png b/binaries/data/mods/public/art/textures/cursors/action-return-metal.png new file mode 100644 index 0000000000..9e2f3d200b --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-metal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e07fd67fce20af8d03a27965c0bfdd5bcbe0f98c0bd3339ac8977725b59863be +size 2404 diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-metal.txt b/binaries/data/mods/public/art/textures/cursors/action-return-metal.txt new file mode 100644 index 0000000000..2fb73a07ec --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-metal.txt @@ -0,0 +1 @@ +1 1 diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-stone.png b/binaries/data/mods/public/art/textures/cursors/action-return-stone.png new file mode 100644 index 0000000000..e7c1f46060 --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-stone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5ea165f310cd437015af1274e84e0e8a42cf761662c97eb6ca6898cb1fe7fb1b +size 2519 diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-stone.txt b/binaries/data/mods/public/art/textures/cursors/action-return-stone.txt new file mode 100644 index 0000000000..2fb73a07ec --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-stone.txt @@ -0,0 +1 @@ +1 1 diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-wood.png b/binaries/data/mods/public/art/textures/cursors/action-return-wood.png new file mode 100644 index 0000000000..facbacee76 --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-wood.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e493ad830691469e132f0b5243a1833b9c45237def72041769facb47589d121 +size 2705 diff --git a/binaries/data/mods/public/art/textures/cursors/action-return-wood.txt b/binaries/data/mods/public/art/textures/cursors/action-return-wood.txt new file mode 100644 index 0000000000..2fb73a07ec --- /dev/null +++ b/binaries/data/mods/public/art/textures/cursors/action-return-wood.txt @@ -0,0 +1 @@ +1 1 diff --git a/binaries/data/mods/public/gui/session_new/input.js b/binaries/data/mods/public/gui/session_new/input.js index 9ef0a830c1..51635dc692 100644 --- a/binaries/data/mods/public/gui/session_new/input.js +++ b/binaries/data/mods/public/gui/session_new/input.js @@ -114,8 +114,9 @@ function determineAction(x, y, fromMinimap) if (haveRallyPoints && selection.indexOf(targets[0]) != -1) return {"type": "unset-rallypoint"}; - // Check if the target entity is a resource, foundation, or enemy unit. - // Check if any entities in the selection can gather the requested resource, can build the foundation, or can attack the enemy + // Check if the target entity is a resource, dropsite, foundation, or enemy unit. + // Check if any entities in the selection can gather the requested resource, + // can return to the dropsite, can build the foundation, or can attack the enemy for each (var entityID in selection) { var entState = GetEntityState(entityID); @@ -125,6 +126,11 @@ function determineAction(x, y, fromMinimap) var playerOwned = ((targetState.player == entState.player)? true : false); var enemyOwned = ((targetState.player != entState.player && entState.diplomacy && entState.diplomacy[targetState.player - 1] < 0)? true : false); var gaiaOwned = ((targetState.player == 0)? true : false); + + // Find the resource type we're carrying, if any + var carriedType = undefined; + if (entState.resourceCarrying && entState.resourceCarrying.length) + carriedType = entState.resourceCarrying[0].type; if (targetState.garrisonHolder && playerOwned && Engine.HotkeyIsPressed("session.garrison")) { @@ -139,6 +145,11 @@ function determineAction(x, y, fromMinimap) if (resource) return {"type": "gather", "cursor": "action-gather-"+resource, "target": targets[0]}; } + else if (targetState.resourceDropsite && playerOwned && carriedType && + targetState.resourceDropsite.types.indexOf(carriedType) != -1) + { + return {"type": "returnresource", "cursor": "action-return-"+carriedType, "target": targets[0]}; + } else if (targetState.foundation && entState.buildEntities && playerOwned) { return {"type": "build", "cursor": "action-build", "target": targets[0]}; @@ -517,6 +528,11 @@ function handleInputAfterGui(ev) Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] }); return true; + case "returnresource": + Engine.PostNetworkCommand({"type": "returnresource", "entities": selection, "target": action.target, "queued": queued}); + Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] }); + return true; + case "garrison": Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued}); //Need to play some sound here?? diff --git a/binaries/data/mods/public/gui/session_new/selection_details.js b/binaries/data/mods/public/gui/session_new/selection_details.js index d4fcd70c58..11a0677495 100644 --- a/binaries/data/mods/public/gui/session_new/selection_details.js +++ b/binaries/data/mods/public/gui/session_new/selection_details.js @@ -95,7 +95,24 @@ function displaySingle(entState, template) { getGUIObjectByName("resources").hidden = true; } - + + // Resource carrying + if (entState.resourceCarrying && entState.resourceCarrying.length) + { + var carried = entState.resourceCarrying[0]; + // (we should only be carrying one resource type at once, so just display the first) + + getGUIObjectByName("resourceCarryingIcon").hidden = false; + getGUIObjectByName("resourceCarryingText").hidden = false; + getGUIObjectByName("resourceCarryingIcon").cell_id = RESOURCE_ICON_CELL_IDS[carried.type]; + getGUIObjectByName("resourceCarryingText").caption = carried.amount + "/" + carried.max; + } + else + { + getGUIObjectByName("resourceCarryingIcon").hidden = true; + getGUIObjectByName("resourceCarryingText").hidden = true; + } + // Set Captions getGUIObjectByName("specific").caption = specificName; getGUIObjectByName("player").caption = playerName; diff --git a/binaries/data/mods/public/gui/session_new/session.xml b/binaries/data/mods/public/gui/session_new/session.xml index bd58f8d434..ac21b0f63f 100644 --- a/binaries/data/mods/public/gui/session_new/session.xml +++ b/binaries/data/mods/public/gui/session_new/session.xml @@ -468,6 +468,10 @@ + + + + diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index f571dbcac2..af2e48ff5e 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -153,6 +153,15 @@ GuiInterface.prototype.GetEntityState = function(player, ent) if (cmpResourceGatherer) { ret.resourceGatherRates = cmpResourceGatherer.GetGatherRates(); + ret.resourceCarrying = cmpResourceGatherer.GetCarryingStatus(); + } + + var cmpResourceDropsite = Engine.QueryInterface(ent, IID_ResourceDropsite); + if (cmpResourceDropsite) + { + ret.resourceDropsite = { + "types": cmpResourceDropsite.GetTypes() + }; } var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint); diff --git a/binaries/data/mods/public/simulation/components/Player.js b/binaries/data/mods/public/simulation/components/Player.js index 9cbc477b91..1781c93ef1 100644 --- a/binaries/data/mods/public/simulation/components/Player.js +++ b/binaries/data/mods/public/simulation/components/Player.js @@ -122,20 +122,17 @@ Player.prototype.GetResourceCounts = function() }; /** - * Add resource of specified type to player and increase gathered resources statistics + * Add resource of specified type to player * @param type Generic type of resource (string) * @param amount Amount of resource, whick should be added (integer) - * @param specificType Specific type of resource (string, optional) */ -Player.prototype.AddResource = function(type, amount, specificType) +Player.prototype.AddResource = function(type, amount) { this.resourceCount[type] += (+amount); - var cmpStatisticsTracker = Engine.QueryInterface(this.entity, IID_StatisticsTracker); - cmpStatisticsTracker.IncreaseResourceGatheredCounter(type, amount, specificType); }; /** - * Add resources to player but not increase gathered resources statistics + * Add resources to player */ Player.prototype.AddResources = function(amounts) { diff --git a/binaries/data/mods/public/simulation/components/ResourceDropsite.js b/binaries/data/mods/public/simulation/components/ResourceDropsite.js index 741b11561d..0cb40e0dfd 100644 --- a/binaries/data/mods/public/simulation/components/ResourceDropsite.js +++ b/binaries/data/mods/public/simulation/components/ResourceDropsite.js @@ -1,9 +1,6 @@ function ResourceDropsite() {} ResourceDropsite.prototype.Schema = - "" + - "" + - "" + "" + "" + "" + @@ -17,8 +14,21 @@ ResourceDropsite.prototype.Schema = "" + ""; -/* - * TODO: this all needs to be designed and implemented + +/** + * Returns the list of resource types accepted by this dropsite. */ +ResourceDropsite.prototype.GetTypes = function() +{ + return this.template.Types.split(/\s+/); +}; + +/** + * Returns whether this dropsite accepts the given generic type of resource. + */ +ResourceDropsite.prototype.AcceptsType = function(type) +{ + return this.GetTypes().indexOf(type) != -1; +}; Engine.RegisterComponentType(IID_ResourceDropsite, "ResourceDropsite", ResourceDropsite); diff --git a/binaries/data/mods/public/simulation/components/ResourceGatherer.js b/binaries/data/mods/public/simulation/components/ResourceGatherer.js index b0cbdd583a..e599d7a9c6 100644 --- a/binaries/data/mods/public/simulation/components/ResourceGatherer.js +++ b/binaries/data/mods/public/simulation/components/ResourceGatherer.js @@ -11,6 +11,12 @@ ResourceGatherer.prototype.Schema = "3" + "2" + "" + + "" + + "10" + + "10" + + "10" + + "10" + + "" + "" + "" + "" + @@ -39,10 +45,53 @@ ResourceGatherer.prototype.Schema = "" + "" + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + + "" + ""; ResourceGatherer.prototype.Init = function() { + this.carrying = {}; // { type: integer amount currently carried } + // (Note that this component supports carrying multiple types of resources, + // each with an independent capacity, but the rest of the game currently + // ensures and assumes we'll only be carrying one type at once) +}; + +/** + * Returns data about what resources the unit is currently carrying, + * in the form [ {"type":"wood", "amount":7, "max":10} ] + */ +ResourceGatherer.prototype.GetCarryingStatus = function() +{ + var ret = []; + for (var type in this.carrying) + { + ret.push({ + "type": type, + "amount": this.carrying[type], + "max": +this.template.Capacities[type] + }); + } + return ret; +}; + +/** + * Returns the type of one particular resource this unit is + * currently carrying, or undefined if none. + */ +ResourceGatherer.prototype.GetMainCarryingType = function() +{ + // Return the first key, if any + for (var type in this.carrying) + return type; + + return undefined; }; ResourceGatherer.prototype.GetGatherRates = function() @@ -73,19 +122,33 @@ ResourceGatherer.prototype.PerformGather = function(target) var cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply); var type = cmpResourceSupply.GetType(); - var status = cmpResourceSupply.TakeResources(rate); + // Initialise the carried count if necessary + if (!this.carrying[type.generic]) + this.carrying[type.generic] = 0; - // Give the gathered resources to the player - var cmpPlayerManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); - var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); - var cmpPlayer = Engine.QueryInterface(cmpPlayerManager.GetPlayerByID(cmpOwnership.GetOwner()), IID_Player); - cmpPlayer.AddResource(type.generic, status.amount, type.specific); + // Find the maximum so we won't exceed our capacity + var maxGathered = this.template.Capacities[type.generic] - this.carrying[type.generic]; + + var status = cmpResourceSupply.TakeResources(Math.min(rate, maxGathered)); + + this.carrying[type.generic] += status.amount; + + // Update stats of how much the player collected. + // (We have to do it here rather than at the dropsite, because we + // need to know what subtype it was) + var cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker); + if (cmpStatisticsTracker) + cmpStatisticsTracker.IncreaseResourceGatheredCounter(type.generic, status.amount, type.specific); // Tell the target we're gathering from it Engine.PostMessage(target, MT_ResourceGather, { "entity": target, "gatherer": this.entity }); - return status; + return { + "amount": status.amount, + "exhausted": status.exhausted, + "filled": (this.carrying[type.generic] >= this.template.Capacities[type.generic]) + }; }; /** @@ -110,4 +173,58 @@ ResourceGatherer.prototype.GetTargetGatherRate = function(target) return (rate || 0) * this.template.BaseSpeed; } +/** + * Returns whether this unit can carry more of the given type of resource. + * (This ignores whether the unit is actually able to gather that + * resource type or not.) + */ +ResourceGatherer.prototype.CanCarryMore = function(type) +{ + var amount = (this.carrying[type] || 0); + return (amount < this.template.Capacities[type]); +}; + +/** + * Returns whether this unit is carrying any resources of a type that is + * not the requested type. (This is to support cases where the unit is + * only meant to be able to carry one type at once.) + */ +ResourceGatherer.prototype.IsCarryingAnythingExcept = function(exceptedType) +{ + for (var type in this.carrying) + if (type != exceptedType) + return true; + + return false; +}; + +/** + * Transfer our carried resources to our owner immediately. + * Only resources of the given types will be transferred. + * (This should typically be called after reaching a dropsite). + */ +ResourceGatherer.prototype.CommitResources = function(types) +{ + var cmpPlayer = QueryOwnerInterface(this.entity, IID_Player); + + for each (var type in types) + { + if (type in this.carrying) + { + cmpPlayer.AddResource(type, this.carrying[type]); + delete this.carrying[type]; + } + } +}; + +/** + * Drop all currently-carried resources. + * (Currently they just vanish after being dropped - we don't bother depositing + * them onto the ground.) + */ +ResourceGatherer.prototype.DropResources = function() +{ + this.carrying = {}; +}; + Engine.RegisterComponentType(IID_ResourceGatherer, "ResourceGatherer", ResourceGatherer); diff --git a/binaries/data/mods/public/simulation/components/UnitAI.js b/binaries/data/mods/public/simulation/components/UnitAI.js index 0d9758c187..2e33c7552b 100644 --- a/binaries/data/mods/public/simulation/components/UnitAI.js +++ b/binaries/data/mods/public/simulation/components/UnitAI.js @@ -146,6 +146,24 @@ var UnitFsmSpec = { this.SetNextState("INDIVIDUAL.GATHER.GATHERING"); } }, + + "Order.ReturnResource": function(msg) { + // Try to move to the dropsite + if (this.MoveToTarget(this.order.data.target)) + { + // We've started walking to the target + this.SetNextState("INDIVIDUAL.RETURNRESOURCE.APPROACHING"); + } + else + { + // Oops, we can't reach the dropsite. + // Maybe we should try to pick another dropsite, to find an + // accessible one? + // For now, just give up. + this.FinishOrder(); + return; + } + }, "Order.Repair": function(msg) { // Try to move within range @@ -209,6 +227,13 @@ var UnitFsmSpec = { cmpFormation.Disband(); }, + "Order.ReturnResource": function(msg) { + // TODO: see notes in Order.Attack + var cmpFormation = Engine.QueryInterface(this.entity, IID_Formation); + cmpFormation.CallMemberFunction("ReturnResource", [msg.data.target, false]); + cmpFormation.Disband(); + }, + "IDLE": { "enter": function() { this.SelectAnimation("idle"); @@ -410,8 +435,50 @@ var UnitFsmSpec = { // Check we can still reach the target if (this.CheckTargetRange(this.order.data.target, IID_ResourceGatherer)) { + // Gather the resources: + var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + + // If we've already got some resources but they're the wrong type, + // drop them first to ensure we're only ever carrying one type + if (cmpResourceGatherer.IsCarryingAnythingExcept(this.order.data.type.generic)) + cmpResourceGatherer.DropResources(); + + // Collect from the target var status = cmpResourceGatherer.PerformGather(this.order.data.target); + + // TODO: if exhausted, we should probably stop immediately + // and choose a new target + + // If we've collected as many resources as possible, + // return to the nearest dropsite + if (status.filled) + { + var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); + + // Find dropsites owned by this unit's player + var players = []; + var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + if (cmpOwnership) + players.push(cmpOwnership.GetOwner()); + + var dropsites = cmpRangeManager.ExecuteQuery(this.entity, -1, players, IID_ResourceDropsite); + + // Try to find the first (nearest) dropsite which supports this resource type + for each (var dropsite in dropsites) + { + var cmpDropsite = Engine.QueryInterface(dropsite, IID_ResourceDropsite); + if (!cmpDropsite.AcceptsType(this.order.data.type.generic)) + continue; + + // This dropsite is okay - return our resources to it + this.PushOrderFront("ReturnResource", { "target": dropsite }); + return; + } + + // Oh no, couldn't find any drop sites. + // Give up and stand here like a lemon. + } } else { @@ -462,6 +529,39 @@ var UnitFsmSpec = { }, }, + // Returning to dropsite + "RETURNRESOURCE": { + "APPROACHING": { + "enter": function () { + // Work out what we're carrying, in order to select an appropriate animation + var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + var typename = "carry_" + cmpResourceGatherer.GetMainCarryingType(); + this.SelectAnimation(typename, false, this.GetWalkSpeed()); + }, + + "MoveCompleted": function() { + var cmpResourceDropsite = Engine.QueryInterface(this.order.data.target, IID_ResourceDropsite); + if (cmpResourceDropsite) + { + // TODO: check the dropsite really is in range + // (we didn't get stopped before reaching it) + + var dropsiteTypes = cmpResourceDropsite.GetTypes(); + + var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + cmpResourceGatherer.CommitResources(dropsiteTypes); + } + else + { + // The dropsite was destroyed or something. + // TODO: We ought to look for a new one, probably. + } + + this.FinishOrder(); + }, + }, + }, + "REPAIR": { "APPROACHING": { "enter": function () { @@ -1007,6 +1107,7 @@ UnitAI.prototype.ComputeWalkingDistance = function() case "WalkToTarget": case "Attack": case "Gather": + case "ReturnResource": case "Repair": // Find the target unit's position var cmpTargetPosition = Engine.QueryInterface(order.data.target, IID_Position); @@ -1024,7 +1125,7 @@ UnitAI.prototype.ComputeWalkingDistance = function() return distance; default: - error("Unrecognised order type '"+order.type+"'"); + error("ComputeWalkingDistance: Unrecognised order type '"+order.type+"'"); return distance; } } @@ -1089,6 +1190,17 @@ UnitAI.prototype.Gather = function(target, queued) this.AddOrder("Gather", { "target": target, "type": type }, queued); }; +UnitAI.prototype.ReturnResource = function(target, queued) +{ + if (!this.CanReturnResource(target)) + { + this.WalkToTarget(target, queued); + return; + } + + this.AddOrder("ReturnResource", { "target": target }, queued); +}; + UnitAI.prototype.Repair = function(target, queued) { if (!this.CanRepair(target)) @@ -1162,6 +1274,34 @@ UnitAI.prototype.CanGather = function(target) return true; }; +UnitAI.prototype.CanReturnResource = function(target) +{ + // Formation controllers should always respond to commands + // (then the individual units can make up their own minds) + if (this.IsFormationController()) + return true; + + // Verify that we're able to respond to ReturnResource commands + var cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); + if (!cmpResourceGatherer) + return false; + + // Verify that the target is a dropsite + var cmpResourceDropsite = Engine.QueryInterface(target, IID_ResourceDropsite); + if (!cmpResourceDropsite) + return false; + + // Verify that we are carrying some resources, + // and can return our current resource to this target + var type = cmpResourceGatherer.GetMainCarryingType(); + if (!type || !cmpResourceDropsite.AcceptsType(type)) + return false; + + // TODO: should verify it's owned by the correct player, etc + + return true; +}; + UnitAI.prototype.CanRepair = function(target) { // Formation controllers should always respond to commands diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js index a548936e62..662ec43d31 100644 --- a/binaries/data/mods/public/simulation/helpers/Commands.js +++ b/binaries/data/mods/public/simulation/helpers/Commands.js @@ -41,6 +41,12 @@ function ProcessCommand(player, cmd) cmpUnitAI.Gather(cmd.target, cmd.queued); break; + case "returnresource": + var cmpUnitAI = GetFormationUnitAI(cmd.entities); + if (cmpUnitAI) + cmpUnitAI.ReturnResource(cmd.target, cmd.queued); + break; + case "train": var queue = Engine.QueryInterface(cmd.entity, IID_TrainingQueue); if (queue) diff --git a/binaries/data/mods/public/simulation/helpers/FSM.js b/binaries/data/mods/public/simulation/helpers/FSM.js index b1c0e88c66..f28757678e 100644 --- a/binaries/data/mods/public/simulation/helpers/FSM.js +++ b/binaries/data/mods/public/simulation/helpers/FSM.js @@ -1,7 +1,7 @@ // Hierarchical finite state machine implementation. // // FSMs are specified as a JS data structure; -// see e.g. AnimalAI.js for an example of the syntax. +// see e.g. UnitAI.js for an example of the syntax. // // FSMs are implicitly linked with an external object. // That object stores all FSM-related state. diff --git a/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml b/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml index f62aab5cfb..cf80e90608 100644 --- a/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml +++ b/binaries/data/mods/public/simulation/templates/other/celt_homestead.xml @@ -40,7 +40,6 @@ 80 - 60 food wood stone metal diff --git a/binaries/data/mods/public/simulation/templates/other/celt_hut.xml b/binaries/data/mods/public/simulation/templates/other/celt_hut.xml index f82d0154cf..74a3f5192d 100644 --- a/binaries/data/mods/public/simulation/templates/other/celt_hut.xml +++ b/binaries/data/mods/public/simulation/templates/other/celt_hut.xml @@ -29,7 +29,6 @@ - 12 food wood stone metal diff --git a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml index 50cbdf6728..ff789a5428 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml @@ -53,7 +53,6 @@ 1 - 60 food wood stone metal diff --git a/binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml b/binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml index cf9ef6a61a..fb766c0b11 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_economic_farmstead.xml @@ -37,7 +37,6 @@ 60 - 56 food diff --git a/binaries/data/mods/public/simulation/templates/template_structure_economic_mill.xml b/binaries/data/mods/public/simulation/templates/template_structure_economic_mill.xml index 96ec07d920..0be3d1ebb6 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_economic_mill.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_economic_mill.xml @@ -37,7 +37,6 @@ 60 - 56 wood stone metal diff --git a/binaries/data/mods/public/simulation/templates/template_unit.xml b/binaries/data/mods/public/simulation/templates/template_unit.xml index 98d7f3462d..f70f4cbd57 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit.xml @@ -48,6 +48,12 @@ 1000 1000 + + 15 + 15 + 15 + 15 + false diff --git a/source/lib/res/graphics/cursor.cpp b/source/lib/res/graphics/cursor.cpp index 5f5bbc334f..da3b65dea0 100644 --- a/source/lib/res/graphics/cursor.cpp +++ b/source/lib/res/graphics/cursor.cpp @@ -299,6 +299,9 @@ LibError cursor_draw(const PIVFS& vfs, const wchar_t* name, int x, int y) } Handle hc = cursor_load(vfs, name); + + RETURN_ERR(hc); // silently ignore failures + H_DEREF(hc, Cursor, c); switch(c->kind) diff --git a/source/ps/GameSetup/GameSetup.cpp b/source/ps/GameSetup/GameSetup.cpp index 9a5b727304..fdd4d527b9 100644 --- a/source/ps/GameSetup/GameSetup.cpp +++ b/source/ps/GameSetup/GameSetup.cpp @@ -278,9 +278,14 @@ void Render() // Draw the cursor (or set the Windows cursor, on Windows) CStrW cursorName = g_CursorName; if (cursorName.empty()) + { cursor_draw(g_VFS, NULL, g_mouse_x, g_yres-g_mouse_y); + } else - cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y); + { + if (cursor_draw(g_VFS, cursorName.c_str(), g_mouse_x, g_yres-g_mouse_y) < 0) + LOGWARNING(L"Failed to draw cursor '%ls'", cursorName.c_str()); + } // restore glMatrixMode(GL_PROJECTION); diff --git a/source/simulation2/components/CCmpRangeManager.cpp b/source/simulation2/components/CCmpRangeManager.cpp index 4d5dc71a64..0beabb87d6 100644 --- a/source/simulation2/components/CCmpRangeManager.cpp +++ b/source/simulation2/components/CCmpRangeManager.cpp @@ -631,6 +631,30 @@ private: GetSimContext().GetComponentManager().PostMessage(messages[i].first, messages[i].second); } + /** + * Returns whether the given entity matches the given query (ignoring maxRange) + */ + bool TestEntityQuery(const Query& q, entity_id_t id, const EntityData& entity) + { + // Quick filter to ignore entities with the wrong owner + if (!(CalcOwnerMask(entity.owner) & q.ownersMask)) + return false; + + // Ignore entities not present in the world + if (!entity.inWorld) + return false; + + // Ignore self + if (id == q.source) + return false; + + // Ignore if it's missing the required interface + if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(id, q.interface)) + return false; + + return true; + } + /** * Returns a list of distinct entity IDs that match the given query, sorted by ID. */ @@ -641,39 +665,46 @@ private: return; CFixedVector2D pos = cmpSourcePosition->GetPosition2D(); - // Get a quick list of entities that are potentially in range - std::vector ents = m_Subdivision.GetNear(pos, q.maxRange); - - for (size_t i = 0; i < ents.size(); ++i) + // Special case: range -1.0 means check all entities ignoring distance + if (q.maxRange == entity_pos_t::FromInt(-1)) { - std::map::const_iterator it = m_EntityData.find(ents[i]); - debug_assert(it != m_EntityData.end()); + for (std::map::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) + { + if (!TestEntityQuery(q, it->first, it->second)) + continue; - // Quick filter to ignore entities with the wrong owner - if (!(CalcOwnerMask(it->second.owner) & q.ownersMask)) - continue; + r.push_back(it->first); + } + } + else + { + // Get a quick list of entities that are potentially in range + std::vector ents = m_Subdivision.GetNear(pos, q.maxRange); - // Restrict based on precise location - if (!it->second.inWorld) - continue; - int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange); - if (distVsMax > 0) - continue; + for (size_t i = 0; i < ents.size(); ++i) + { + std::map::const_iterator it = m_EntityData.find(ents[i]); + debug_assert(it != m_EntityData.end()); - // Ignore self - if (it->first == q.source) - continue; + if (!TestEntityQuery(q, it->first, it->second)) + continue; - // Ignore if it's missing the required interface - if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(it->first, q.interface)) - continue; + // Restrict based on precise distance + int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange); + if (distVsMax > 0) + continue; - r.push_back(it->first); + r.push_back(it->first); + } } } Query ConstructQuery(entity_id_t source, entity_pos_t maxRange, std::vector owners, int requiredInterface) { + // Range must be non-negative, or else -1 + if (maxRange < entity_pos_t::Zero() && maxRange != entity_pos_t::FromInt(-1)) + LOGWARNING(L"CCmpRangeManager: Invalid range %f in query for entity %d", maxRange.ToDouble(), source); + Query q; q.enabled = false; q.source = source; diff --git a/source/simulation2/components/ICmpRangeManager.h b/source/simulation2/components/ICmpRangeManager.h index 90ab67857b..f36e2fac2e 100644 --- a/source/simulation2/components/ICmpRangeManager.h +++ b/source/simulation2/components/ICmpRangeManager.h @@ -80,7 +80,7 @@ public: /** * Execute a passive query. * @param source the entity around which the range will be computed. - * @param maxRange maximum distance in metres (inclusive). + * @param maxRange non-negative maximum distance in metres (inclusive); or -1.0 to ignore distance. * @param owners list of player IDs that matching entities may have; -1 matches entities with no owner. * @param requiredInterface if non-zero, an interface ID that matching entities must implement. * @return list of entities matching the query, ordered by increasing distance from the source entity. @@ -90,7 +90,7 @@ public: /** * Construct an active query. The query will be disabled by default. * @param source the entity around which the range will be computed. - * @param maxRange maximum distance in metres (inclusive). + * @param maxRange non-negative maximum distance in metres (inclusive); or -1.0 to ignore distance. * @param owners list of player IDs that matching entities may have; -1 matches entities with no owner. * @param requiredInterface if non-zero, an interface ID that matching entities must implement. * @return unique non-zero identifier of query.