1
0
forked from 0ad/0ad

Split treasures from ResourceSupply.

Removes some hardcoding and allows for easier modding of treasures.

Refs.: #5888
Differential revision: D3303
Comments by: @Imarok, @Nescio, @Stan, @wraitii
This was SVN commit r24989.
This commit is contained in:
Freagarach 2021-03-03 07:47:38 +00:00
parent 0de47dd1ec
commit ea96e81098
69 changed files with 837 additions and 263 deletions

View File

@ -18,13 +18,6 @@ function Resources()
if (data.code != data.code.toLowerCase()) if (data.code != data.code.toLowerCase())
warn("Resource codes should use lower case: " + data.code); warn("Resource codes should use lower case: " + data.code);
// Treasures are supported for every specified resource
if (data.code == "treasure")
{
error("Encountered resource with reserved keyword: " + data.code);
continue;
}
this.resourceData.push(data); this.resourceData.push(data);
this.resourceDataObj[data.code] = data; this.resourceDataObj[data.code] = data;
this.resourceCodes.push(data.code); this.resourceCodes.push(data.code);

View File

@ -481,6 +481,16 @@ function GetTemplateDataHelper(template, player, auraTemplates, modifiers = {})
"GainMultiplier": getEntityValue("Trader/GainMultiplier") "GainMultiplier": getEntityValue("Trader/GainMultiplier")
}; };
if (template.Treasure)
{
ret.treasure = {
"collectTime": getEntityValue("Treasure/CollectTime"),
"resources": {}
};
for (let resource in template.Treasure.Resources)
ret.treasure.resources[resource] = getEntityValue("Treasure/Resources/" + resource);
}
if (template.WallSet) if (template.WallSet)
{ {
ret.wallSet = { ret.wallSet = {

View File

@ -761,17 +761,48 @@ function getResourceSupplyTooltip(template)
return ""; return "";
let supply = template.supply; let supply = template.supply;
let type = supply.type[0] == "treasure" ? supply.type[1] : supply.type[0];
// Translation: Label in tooltip showing the resource type and quantity of a given resource supply. // Translation: Label in tooltip showing the resource type and quantity of a given resource supply.
return sprintf(translate("%(label)s %(component)s %(amount)s"), { return sprintf(translate("%(label)s %(component)s %(amount)s"), {
"label": headerFont(translate("Resource Supply:")), "label": headerFont(translate("Resource Supply:")),
"component": resourceIcon(type), "component": resourceIcon(supply.type[0]),
// Translation: Marks that a resource supply entity has an unending, infinite, supply of its resource. // Translation: Marks that a resource supply entity has an unending, infinite, supply of its resource.
"amount": Number.isFinite(+supply.amount) ? supply.amount : translate("∞") "amount": Number.isFinite(+supply.amount) ? supply.amount : translate("∞")
}); });
} }
/**
* @param {Object} template - The entity's template.
* @return {string} - The resources this entity rewards to a collecter.
*/
function getTreasureTooltip(template)
{
if (!template.treasure)
return "";
let resources = {};
for (let resource of g_ResourceData.GetResources())
{
let type = resource.code;
if (template.treasure.resources[type])
resources[type] = template.treasure.resources[type];
}
let resourceNames = Object.keys(resources);
if (!resourceNames.length)
return "";
return sprintf(translate("%(label)s %(details)s"), {
"label": headerFont(translate("Reward:")),
"details":
resourceNames.map(
type => sprintf(translate("%(resourceIcon)s %(reward)s"), {
"resourceIcon": resourceIcon(type),
"reward": resources[type]
})
).join(" ")
});
}
function getResourceTrickleTooltip(template) function getResourceTrickleTooltip(template)
{ {
if (!template.resourceTrickle) if (!template.resourceTrickle)

View File

@ -10,14 +10,6 @@ function layoutSelectionMultiple()
Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true; Engine.GetGUIObjectByName("detailsAreaSingle").hidden = true;
} }
function getResourceTypeDisplayName(resourceType)
{
return resourceNameFirstWord(
resourceType.generic == "treasure" ?
resourceType.specific :
resourceType.generic);
}
// Updates the health bar of garrisoned units // Updates the health bar of garrisoned units
function updateGarrisonHealthBar(entState, selection) function updateGarrisonHealthBar(entState, selection)
{ {
@ -228,7 +220,7 @@ function displaySingle(entState)
unitResourceBar.size = resourceSize; unitResourceBar.size = resourceSize;
Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), { Engine.GetGUIObjectByName("resourceLabel").caption = sprintf(translate("%(resource)s:"), {
"resource": getResourceTypeDisplayName(entState.resourceSupply.type) "resource": resourceNameFirstWord(entState.resourceSupply.type.generic)
}); });
Engine.GetGUIObjectByName("resourceStats").caption = resources; Engine.GetGUIObjectByName("resourceStats").caption = resources;
@ -347,6 +339,7 @@ function displaySingle(entState)
getVisibleEntityClassesFormatted, getVisibleEntityClassesFormatted,
getAurasTooltip, getAurasTooltip,
getEntityTooltip, getEntityTooltip,
getTreasureTooltip,
showTemplateViewerOnRightClickTooltip showTemplateViewerOnRightClickTooltip
].map(func => func(template))); ].map(func => func(template)));

View File

@ -803,6 +803,49 @@ var g_UnitActions =
"specificness": 40, "specificness": 40,
}, },
"collect-treasure":
{
"execute": function(target, action, selection, queued)
{
Engine.PostNetworkCommand({
"type": "collect-treasure",
"entities": selection,
"target": action.target,
"queued": queued,
"formation": g_AutoFormation.getNull()
});
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_collect_treasure",
"entity": action.firstAbleEntity
});
return true;
},
"getActionInfo": function(entState, targetState)
{
if (!entState.treasureCollecter ||
!targetState || !targetState.treasure)
return false;
return {
"possible": true,
"cursor": "action-collect-treasure"
};
},
"actionCheck": function(target, selection)
{
let actionInfo = getActionInfo("collect-treasure", target, selection);
return actionInfo.possible && {
"type": "collect-treasure",
"cursor": actionInfo.cursor,
"target": target,
"firstAbleEntity": actionInfo.entity
};
},
"specificness": 1,
},
"remove-guard": "remove-guard":
{ {
"execute": function(target, action, selection, queued) "execute": function(target, action, selection, queued)
@ -932,10 +975,7 @@ var g_UnitActions =
else if (targetState && targetState.resourceSupply) else if (targetState && targetState.resourceSupply)
{ {
let resourceType = targetState.resourceSupply.type; let resourceType = targetState.resourceSupply.type;
if (resourceType.generic == "treasure") cursor = "action-gather-" + resourceType.specific;
cursor = "action-gather-" + resourceType.generic;
else
cursor = "action-gather-" + resourceType.specific;
data.command = "gather-near-position"; data.command = "gather-near-position";
data.resourceType = resourceType; data.resourceType = resourceType;
@ -946,6 +986,12 @@ var g_UnitActions =
data.target = targetState.id; data.target = targetState.id;
} }
} }
else if (targetState && targetState.treasure)
{
cursor = "action-collect-treasure";
data.command = "collect-treasure";
data.target = targetState.id;
}
else if (entState.market && targetState && targetState.market && else if (entState.market && targetState && targetState.market &&
entState.id != targetState.id && entState.id != targetState.id &&
(!entState.market.naval || targetState.market.naval) && (!entState.market.naval || targetState.market.naval) &&

View File

@ -468,7 +468,7 @@ function placePlayersNomad(playerClass, constraints)
let count = Math.max(0, Math.ceil( let count = Math.max(0, Math.ceil(
(ccCost[resourceType] - (g_MapSettings.StartingResources || 0)) / (ccCost[resourceType] - (g_MapSettings.StartingResources || 0)) /
Engine.GetTemplate(treasureTemplate).ResourceSupply.Amount)); Engine.GetTemplate(treasureTemplate).Treasure.Resources[resourceType]));
objects.push(new SimpleObject(treasureTemplate, count, count, 3, 5)); objects.push(new SimpleObject(treasureTemplate, count, count, 3, 5));
} }

View File

@ -380,14 +380,11 @@ m.Template = m.Class({
let [type, subtype] = this.get("ResourceSupply/Type").split('.'); let [type, subtype] = this.get("ResourceSupply/Type").split('.');
return { "generic": type, "specific": subtype }; return { "generic": type, "specific": subtype };
}, },
// will return either "food", "wood", "stone", "metal" and not treasure.
"getResourceType": function() { "getResourceType": function() {
if (!this.get("ResourceSupply")) if (!this.get("ResourceSupply"))
return undefined; return undefined;
let [type, subtype] = this.get("ResourceSupply/Type").split('.'); return this.get("ResourceSupply/Type").split('.')[0];
if (type == "treasure")
return subtype;
return type;
}, },
"getDiminishingReturns": function() { return +(this.get("ResourceSupply/DiminishingReturns") || 1); }, "getDiminishingReturns": function() { return +(this.get("ResourceSupply/DiminishingReturns") || 1); },
@ -414,6 +411,17 @@ m.Template = m.Class({
return types ? types.split(/\s+/) : []; return types ? types.split(/\s+/) : [];
}, },
"isTreasure": function() { return this.get("Treasure") !== undefined; },
"treasureResources": function() {
if (!this.get("Treasure"))
return undefined;
let ret = {};
for (let r in this.get("Treasure/Resources"))
ret[r] = +this.get("Treasure/Resources/" + r);
return ret;
},
"garrisonableClasses": function() { return this.get("GarrisonHolder/List/_string"); }, "garrisonableClasses": function() { return this.get("GarrisonHolder/List/_string"); },
@ -557,6 +565,8 @@ m.Template = m.Class({
"canGuard": function() { return this.get("UnitAI/CanGuard") === "true"; }, "canGuard": function() { return this.get("UnitAI/CanGuard") === "true"; },
"canGarrison": function() { return "Garrisonable" in this._template; }, "canGarrison": function() { return "Garrisonable" in this._template; },
"isTreasureCollecter": function() { return this.get("TreasureCollecter") !== undefined; },
}); });
@ -723,9 +733,6 @@ m.Entity = m.Class({
if (!type) if (!type)
return 0; return 0;
if (type.generic == "treasure")
return 1000;
let tstring = type.generic + "." + type.specific; let tstring = type.generic + "." + type.specific;
let rate = +this.get("ResourceGatherer/BaseSpeed"); let rate = +this.get("ResourceGatherer/BaseSpeed");
rate *= +this.get("ResourceGatherer/Rates/" +tstring); rate *= +this.get("ResourceGatherer/Rates/" +tstring);
@ -795,7 +802,7 @@ m.Entity = m.Class({
let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string"); let restrictedClasses = this.get("Attack/" + type + "/RestrictedClasses/_string");
if (!restrictedClasses || !MatchesClassList(target.classes(), restrictedClasses)) if (!restrictedClasses || !MatchesClassList(target.classes(), restrictedClasses))
return true; return true;
}; }
return false; return false;
}, },
@ -852,6 +859,16 @@ m.Entity = m.Class({
return this; return this;
}, },
"collectTreasure": function(target, queued = false) {
Engine.PostCommand(PlayerID, {
"type": "collect-treasure",
"entities": [this.id()],
"target": target.id(),
"queued": queued
});
return this;
},
// moveApart from a point in the opposite direction with a distance dist // moveApart from a point in the opposite direction with a distance dist
"moveApart": function(point, dist) { "moveApart": function(point, dist) {
if (this.position() !== undefined) { if (this.position() !== undefined) {

View File

@ -117,6 +117,20 @@ m.Filters = {
"dynamicProperties": [] "dynamicProperties": []
}), }),
"isTreasure": () => ({
"func": ent => {
if (!ent.isTreasure())
return false;
// Don't go for floating treasures since we might not be able
// to reach them and that kills the pathfinder.
let template = ent.templateName();
return template != "gaia/treasure/shipwreck_debris" &&
template != "gaia/treasure/shipwreck";
},
"dynamicProperties": []
}),
"byResource": resourceType => ({ "byResource": resourceType => ({
"func": ent => { "func": ent => {
if (!ent.resourceSupplyMax()) if (!ent.resourceSupplyMax())
@ -130,14 +144,6 @@ m.Filters = {
if (!ent.isHuntable() || ent.hasClass("SeaCreature")) if (!ent.isHuntable() || ent.hasClass("SeaCreature"))
return false; return false;
// Don't go for floating treasures since we won't be able to reach them and it kills the pathfinder.
if (ent.templateName() == "gaia/treasure/shipwreck_debris" ||
ent.templateName() == "gaia/treasure/shipwreck")
return false;
if (type.generic == "treasure")
return resourceType == type.specific;
return resourceType == type.generic; return resourceType == type.generic;
}, },
"dynamicProperties": [] "dynamicProperties": []

View File

@ -397,7 +397,7 @@ m.SharedScript.prototype.createResourceMaps = function()
} }
for (let ent of this._entities.values()) for (let ent of this._entities.values())
{ {
if (!ent || !ent.position() || !ent.resourceSupplyType() || ent.resourceSupplyType().generic === "treasure") if (!ent || !ent.position() || !ent.resourceSupplyType())
continue; continue;
let resource = ent.resourceSupplyType().generic; let resource = ent.resourceSupplyType().generic;
if (!this.resourceMaps[resource]) if (!this.resourceMaps[resource])
@ -439,7 +439,7 @@ m.SharedScript.prototype.updateResourceMaps = function(events)
if (!e.entityObj) if (!e.entityObj)
continue; continue;
let ent = e.entityObj; let ent = e.entityObj;
if (!ent || !ent.position() || !ent.resourceSupplyType() || ent.resourceSupplyType().generic === "treasure") if (!ent || !ent.position() || !ent.resourceSupplyType())
continue; continue;
let resource = ent.resourceSupplyType().generic; let resource = ent.resourceSupplyType().generic;
if (!this.resourceMaps[resource]) if (!this.resourceMaps[resource])
@ -458,7 +458,7 @@ m.SharedScript.prototype.updateResourceMaps = function(events)
if (!e.entity || !this._entities.has(e.entity)) if (!e.entity || !this._entities.has(e.entity))
continue; continue;
let ent = this._entities.get(e.entity); let ent = this._entities.get(e.entity);
if (!ent || !ent.position() || !ent.resourceSupplyType() || ent.resourceSupplyType().generic === "treasure") if (!ent || !ent.position() || !ent.resourceSupplyType())
continue; continue;
let resource = ent.resourceSupplyType().generic; let resource = ent.resourceSupplyType().generic;
if (!this.resourceMaps[resource]) if (!this.resourceMaps[resource])

View File

@ -170,8 +170,6 @@ PETRA.BaseManager.prototype.assignResourceToDropsite = function(gameState, drops
return; return;
if (supply.hasClass("Field")) // fields are treated separately if (supply.hasClass("Field")) // fields are treated separately
return; return;
if (supply.resourceSupplyType().generic == "treasure") // treasures are treated separately
return;
// quick accessibility check // quick accessibility check
if (PETRA.getLandAccess(gameState, supply) != accessIndex) if (PETRA.getLandAccess(gameState, supply) != accessIndex)
return; return;

View File

@ -382,16 +382,13 @@ PETRA.gatherTreasure = function(gameState, ent, water = false)
return false; return false;
if (!ent || !ent.position()) if (!ent || !ent.position())
return false; return false;
let rates = ent.resourceGatherRates(); if (!ent.isTreasureCollecter)
if (!rates || !rates.treasure || rates.treasure <= 0)
return false; return false;
let treasureFound; let treasureFound;
let distmin = Math.min(); let distmin = Math.min();
let access = water ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent); let access = water ? PETRA.getSeaAccess(gameState, ent) : PETRA.getLandAccess(gameState, ent);
for (let treasure of gameState.ai.HQ.treasures.values()) for (let treasure of gameState.ai.HQ.treasures.values())
{ {
if (PETRA.IsSupplyFull(gameState, treasure))
continue;
// let some time for the previous gatherer to reach the treasure before trying again // let some time for the previous gatherer to reach the treasure before trying again
let lastGathered = treasure.getMetadata(PlayerID, "lastGathered"); let lastGathered = treasure.getMetadata(PlayerID, "lastGathered");
if (lastGathered && gameState.ai.elapsedTime - lastGathered < 20) if (lastGathered && gameState.ai.elapsedTime - lastGathered < 20)
@ -414,9 +411,8 @@ PETRA.gatherTreasure = function(gameState, ent, water = false)
if (!treasureFound) if (!treasureFound)
return false; return false;
treasureFound.setMetadata(PlayerID, "lastGathered", gameState.ai.elapsedTime); treasureFound.setMetadata(PlayerID, "lastGathered", gameState.ai.elapsedTime);
ent.gather(treasureFound); ent.collectTreasure(treasureFound);
gameState.ai.HQ.AddTCGatherer(treasureFound.id()); ent.setMetadata(PlayerID, "treasure", treasureFound.id());
ent.setMetadata(PlayerID, "supply", treasureFound.id());
return true; return true;
}; };

View File

@ -65,10 +65,7 @@ PETRA.HQ.prototype.init = function(gameState, queues)
this.navalMap = false; this.navalMap = false;
this.navalRegions = {}; this.navalRegions = {};
this.treasures = gameState.getEntities().filter(ent => { this.treasures = gameState.getEntities().filter(ent => ent.isTreasure());
let type = ent.resourceSupplyType();
return type && type.generic == "treasure";
});
this.treasures.registerUpdates(); this.treasures.registerUpdates();
this.currentPhase = gameState.currentPhase(); this.currentPhase = gameState.currentPhase();
this.decayingStructures = new Set(); this.decayingStructures = new Set();

View File

@ -268,17 +268,17 @@ PETRA.HQ.prototype.buildFirstBase = function(gameState)
if (ent.isIdle()) if (ent.isIdle())
PETRA.gatherTreasure(gameState, ent); PETRA.gatherTreasure(gameState, ent);
// Then count the resources from the treasures being collected // Then count the resources from the treasures being collected
let supplyId = ent.getMetadata(PlayerID, "supply"); let treasureId = ent.getMetadata(PlayerID, "treasure");
if (!supplyId) if (!treasureId)
continue; continue;
let supply = gameState.getEntityById(supplyId); let treasure = gameState.getEntityById(treasureId);
if (!supply || supply.resourceSupplyType().generic != "treasure") if (!treasure)
continue; continue;
let type = supply.resourceSupplyType().specific; let types = treasure.treasureResources();
if (!(type in totalExpected)) for (let type in types)
continue; if (type in totalExpected)
totalExpected[type] += supply.resourceSupplyMax(); totalExpected[type] += types[type];
// If we can collect enough resources from these treasures, wait for them // If we can collect enough resources from these treasures, wait for them.
if (totalExpected.canAfford(new API3.Resources(template.cost()))) if (totalExpected.canAfford(new API3.Resources(template.cost())))
return; return;
} }

View File

@ -220,7 +220,6 @@ PETRA.Worker.prototype.update = function(gameState, ent)
let supplyId = ent.unitAIOrderData()[0].target; let supplyId = ent.unitAIOrderData()[0].target;
let supply = gameState.getEntityById(supplyId); let supply = gameState.getEntityById(supplyId);
if (supply && !supply.hasClass("Field") && !supply.hasClass("Animal") && if (supply && !supply.hasClass("Field") && !supply.hasClass("Animal") &&
supply.resourceSupplyType().generic != "treasure" &&
supplyId != ent.getMetadata(PlayerID, "supply")) supplyId != ent.getMetadata(PlayerID, "supply"))
{ {
let nbGatherers = supply.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supplyId); let nbGatherers = supply.resourceSupplyNumGatherers() + gameState.ai.HQ.GetTCGatherer(supplyId);

View File

@ -552,6 +552,17 @@ GuiInterface.prototype.GetEntityState = function(player, ent)
"rates": cmpResourceTrickle.GetRates() "rates": cmpResourceTrickle.GetRates()
}; };
let cmpTreasure = Engine.QueryInterface(ent, IID_Treasure);
if (cmpTreasure)
ret.treasure = {
"collectTime": cmpTreasure.CollectionTime(),
"resources": cmpTreasure.Resources()
};
let cmpTreasureCollecter = Engine.QueryInterface(ent, IID_TreasureCollecter);
if (cmpTreasureCollecter)
ret.treasureCollecter = true;
let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion); let cmpUnitMotion = Engine.QueryInterface(ent, IID_UnitMotion);
if (cmpUnitMotion) if (cmpUnitMotion)
ret.speed = { ret.speed = {

View File

@ -25,7 +25,7 @@ ResourceGatherer.prototype.Schema =
"<ref name='positiveDecimal'/>" + "<ref name='positiveDecimal'/>" +
"</element>" + "</element>" +
"<element name='Rates' a:help='Per-resource-type gather rate multipliers. If a resource type is not specified then it cannot be gathered by this unit'>" + "<element name='Rates' a:help='Per-resource-type gather rate multipliers. If a resource type is not specified then it cannot be gathered by this unit'>" +
Resources.BuildSchema("positiveDecimal", ["treasure"], true) + Resources.BuildSchema("positiveDecimal", [], true) +
"</element>" + "</element>" +
"<element name='Capacities' a:help='Per-resource-type maximum carrying capacity'>" + "<element name='Capacities' a:help='Per-resource-type maximum carrying capacity'>" +
Resources.BuildSchema("positiveDecimal") + Resources.BuildSchema("positiveDecimal") +
@ -114,7 +114,7 @@ ResourceGatherer.prototype.RecalculateGatherRates = function()
{ {
let type = r.split("."); let type = r.split(".");
if (type[0] != "treasure" && type.length > 1 && !Resources.GetResource(type[0]).subtypes[type[1]]) if (!Resources.GetResource(type[0]).subtypes[type[1]])
{ {
error("Resource subtype not found: " + type[0] + "." + type[1]); error("Resource subtype not found: " + type[0] + "." + type[1]);
continue; continue;
@ -164,35 +164,6 @@ ResourceGatherer.prototype.GetRange = function()
// maybe this should depend on the unit or target or something? // maybe this should depend on the unit or target or something?
}; };
/**
* Try to gather treasure
* @return 'true' if treasure is successfully gathered, otherwise 'false'
*/
ResourceGatherer.prototype.TryInstantGather = function(target)
{
let cmpResourceSupply = Engine.QueryInterface(target, IID_ResourceSupply);
let type = cmpResourceSupply.GetType();
if (type.generic != "treasure")
return false;
let status = cmpResourceSupply.TakeResources(cmpResourceSupply.GetCurrentAmount());
let cmpPlayer = QueryOwnerInterface(this.entity, IID_Player);
if (cmpPlayer)
cmpPlayer.AddResource(type.specific, status.amount);
let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseTreasuresCollectedCounter();
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
if (cmpTrigger && cmpPlayer)
cmpTrigger.CallEvent("TreasureCollected", { "player": cmpPlayer.GetPlayerID(), "type": type.specific, "amount": status.amount });
return true;
};
/** /**
* Gather from the target entity. This should only be called after a successful range check, * Gather from the target entity. This should only be called after a successful range check,
* and if the target has a compatible ResourceSupply. * and if the target has a compatible ResourceSupply.

View File

@ -45,7 +45,7 @@ ResourceSupply.prototype.Schema =
"</element>" + "</element>" +
"</optional>" + "</optional>" +
"<element name='Type' a:help='Type and Subtype of resource available from this entity'>" + "<element name='Type' a:help='Type and Subtype of resource available from this entity'>" +
Resources.BuildChoicesSchema(true, true) + Resources.BuildChoicesSchema(true) +
"</element>" + "</element>" +
"<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" + "<element name='MaxGatherers' a:help='Amount of gatherers who can gather resources from this entity at the same time'>" +
"<data type='nonNegativeInteger'/>" + "<data type='nonNegativeInteger'/>" +

View File

@ -0,0 +1,111 @@
function Treasure() {}
Treasure.prototype.Schema =
"<a:help>Provides a bonus when taken. E.g. a supply of resources.</a:help>" +
"<a:example>" +
"<CollectTime>1000</CollectTime>" +
"<Resources>" +
"<Food>1000</Food>" +
"</Resources>" +
"</a:example>" +
"<element name='CollectTime' a:help='Amount of milliseconds that it takes to collect this treasure.'>" +
"<ref name='nonNegativeDecimal'/>" +
"</element>" +
"<optional>" +
"<element name='Resources' a:help='Amount of resources that are in this.'>" +
Resources.BuildSchema("positiveDecimal") +
"</element>" +
"</optional>";
Treasure.prototype.Init = function()
{
};
Treasure.prototype.ComputeReward = function()
{
for (let resource in this.template.Resources)
{
let amount = ApplyValueModificationsToEntity("Treasure/Resources/" + resource, this.template.Resources[resource], this.entity);
if (!amount)
continue;
if (!this.resources)
this.resources = {};
this.resources[resource] = amount;
}
};
/**
* @return {Object} - The resources given by this treasure.
*/
Treasure.prototype.Resources = function()
{
return this.resources || {};
};
/**
* @return {number} - The time in miliseconds it takes to collect this treasure.
*/
Treasure.prototype.CollectionTime = function()
{
return +this.template.CollectTime;
};
/**
* @param {number} entity - The entity collecting us.
* @return {boolean} - Whether the reward was granted.
*/
Treasure.prototype.Reward = function(entity)
{
if (this.isTaken)
return false;
let cmpPlayer = QueryOwnerInterface(entity);
if (!cmpPlayer)
return false;
let cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging)
cmpFogging.Activate();
if (this.resources)
cmpPlayer.AddResources(this.resources);
let cmpStatisticsTracker = QueryOwnerInterface(this.entity, IID_StatisticsTracker);
if (cmpStatisticsTracker)
cmpStatisticsTracker.IncreaseTreasuresCollectedCounter();
let cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
cmpTrigger.CallEvent("TreasureCollected", {
"player": cmpPlayer.GetPlayerID(),
"treasure": this.entity
});
this.isTaken = true;
Engine.DestroyEntity(this.entity);
return true;
};
/**
* We might live long enough for a collecting entity
* to find us again after taking us.
* @return {boolean} - Whether we are taken already.
*/
Treasure.prototype.IsAvailable = function()
{
return !this.isTaken;
};
Treasure.prototype.OnOwnershipChanged = function(msg)
{
if (msg.to != INVALID_PLAYER)
this.ComputeReward();
};
Treasure.prototype.OnValueModification = function(msg)
{
if (msg.component != "Treasure")
return;
this.ComputeReward();
};
Engine.RegisterComponentType(IID_Treasure, "Treasure", Treasure);

View File

@ -0,0 +1,119 @@
function TreasureCollecter() {}
TreasureCollecter.prototype.Schema =
"<a:help>Defines the treasure collecting abilities.</a:help>" +
"<a:example>" +
"<MaxDistance>2.0</MaxDistance>" +
"</a:example>" +
"<element name='MaxDistance' a:help='The maximum treasure taking distance in m.'>" +
"<ref name='positiveDecimal'/>" +
"</element>";
TreasureCollecter.prototype.Init = function()
{
};
/**
* @return {Object} - Min/Max range at which this entity can claim a treasure.
*/
TreasureCollecter.prototype.GetRange = function()
{
return { "min": 0, "max": +this.template.MaxDistance };
};
/**
* @param {number} target - Entity ID of the target.
* @return {boolean} - Whether we can collect from the target.
*/
TreasureCollecter.prototype.CanCollect = function(target)
{
let cmpTreasure = Engine.QueryInterface(target, IID_Treasure);
return cmpTreasure && cmpTreasure.IsAvailable();
};
/**
* @param {number} target - The target to collect.
* @param {number} callerIID - The IID to notify on specific events.
*
* @return {boolean} - Whether we started collecting.
*/
TreasureCollecter.prototype.StartCollecting = function(target, callerIID)
{
if (this.target)
this.StopCollecting();
let cmpTreasure = Engine.QueryInterface(target, IID_Treasure);
if (!cmpTreasure || !cmpTreasure.IsAvailable())
return false;
this.target = target;
this.callerIID = callerIID;
// ToDo: Implement rate modifiers.
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
this.timer = cmpTimer.SetTimeout(this.entity, IID_TreasureCollecter, "CollectTreasure", cmpTreasure.CollectionTime(), null);
return true;
};
/**
* @param {string} reason - The reason why we stopped collecting, used to notify the caller.
*/
TreasureCollecter.prototype.StopCollecting = function(reason)
{
if (this.timer)
{
let cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
cmpTimer.CancelTimer(this.timer);
delete this.timer;
}
delete this.target;
// The callerIID component may start gathering again,
// replacing the callerIID, which gets deleted after
// the callerIID has finished. Hence save the data.
let callerIID = this.callerIID;
delete this.callerIID;
if (reason && callerIID)
{
let component = Engine.QueryInterface(this.entity, callerIID);
if (component)
component.ProcessMessage(reason, null);
}
};
/**
* @params - Data and lateness are unused.
*/
TreasureCollecter.prototype.CollectTreasure = function(data, lateness)
{
let cmpTreasure = Engine.QueryInterface(this.target, IID_Treasure);
if (!cmpTreasure || !cmpTreasure.IsAvailable())
{
this.StopCollecting("TargetInvalidated");
return;
}
if (!this.IsTargetInRange(this.target))
{
this.StopCollecting("OutOfRange");
return;
}
cmpTreasure.Reward(this.entity);
this.StopCollecting("TargetInvalidated");
};
/**
* @param {number} - The entity ID of the target to check.
* @return {boolean} - Whether this entity is in range of its target.
*/
TreasureCollecter.prototype.IsTargetInRange = function(target)
{
let range = this.GetRange();
let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager);
return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, false);
};
Engine.RegisterComponentType(IID_TreasureCollecter, "TreasureCollecter", TreasureCollecter);

View File

@ -477,9 +477,8 @@ UnitAI.prototype.UnitFsmSpec = {
} }
// If the unit is full go to the nearest dropsite instead of trying to gather. // If the unit is full go to the nearest dropsite instead of trying to gather.
// Unless our target is a treasure which we cannot be full enough with (we can't carry treasures).
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
if (msg.data.type.generic !== "treasure" && cmpResourceGatherer && !cmpResourceGatherer.CanCarryMore(msg.data.type.generic)) if (cmpResourceGatherer && !cmpResourceGatherer.CanCarryMore(msg.data.type.generic))
{ {
let nearestDropsite = this.FindNearestDropsite(msg.data.type.generic); let nearestDropsite = this.FindNearestDropsite(msg.data.type.generic);
if (nearestDropsite) if (nearestDropsite)
@ -645,6 +644,15 @@ UnitAI.prototype.UnitFsmSpec = {
return this.FinishOrder(); return this.FinishOrder();
}, },
"Order.CollectTreasure": function(msg) {
let cmpTreasureCollecter = Engine.QueryInterface(this.entity, IID_TreasureCollecter);
if (!cmpTreasureCollecter || !cmpTreasureCollecter.CanCollect(msg.data.target))
return this.FinishOrder();
this.SetNextState("COLLECTTREASURE");
return ACCEPT_ORDER;
},
// States for the special entity representing a group of units moving in formation: // States for the special entity representing a group of units moving in formation:
"FORMATIONCONTROLLER": { "FORMATIONCONTROLLER": {
@ -2551,14 +2559,9 @@ UnitAI.prototype.UnitFsmSpec = {
return; return;
} }
// Gather the resources:
let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer); let cmpResourceGatherer = Engine.QueryInterface(this.entity, IID_ResourceGatherer);
// Try to gather treasure
if (cmpResourceGatherer.TryInstantGather(this.gatheringTarget))
return;
// If we've already got some resources but they're the wrong type, // 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 // drop them first to ensure we're only ever carrying one type
if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic)) if (cmpResourceGatherer.IsCarryingAnythingExcept(resourceType.generic))
@ -2566,11 +2569,8 @@ UnitAI.prototype.UnitFsmSpec = {
this.FaceTowardsTarget(this.order.data.target); this.FaceTowardsTarget(this.order.data.target);
// Collect from the target
let status = cmpResourceGatherer.PerformGather(this.gatheringTarget); let status = cmpResourceGatherer.PerformGather(this.gatheringTarget);
// If we've collected as many resources as possible,
// return to the nearest dropsite
if (status.filled) if (status.filled)
{ {
let nearestDropsite = this.FindNearestDropsite(resourceType.generic); let nearestDropsite = this.FindNearestDropsite(resourceType.generic);
@ -2642,12 +2642,9 @@ UnitAI.prototype.UnitFsmSpec = {
if (previousTarget == ent) if (previousTarget == ent)
return false; return false;
if (type.generic == "treasure" && resourceType.generic == "treasure")
return true;
return type.specific == resourceType.specific && return type.specific == resourceType.specific &&
(type.specific != "meat" || resourceTemplate == template); (type.specific != "meat" || resourceTemplate == template);
}); });
if (nearbyResource) if (nearbyResource)
{ {
@ -2874,6 +2871,76 @@ UnitAI.prototype.UnitFsmSpec = {
}, },
}, },
"COLLECTTREASURE": {
"enter": function() {
let cmpTreasureCollecter = Engine.QueryInterface(this.entity, IID_TreasureCollecter);
if (!cmpTreasureCollecter || !cmpTreasureCollecter.CanCollect(this.order.data.target))
{
this.FinishOrder();
return true;
}
if (this.CheckTargetRange(this.order.data.target, IID_TreasureCollecter))
this.SetNextState("COLLECTING");
else
this.SetNextState("APPROACHING");
return true;
},
"leave": function() {
},
"APPROACHING": {
"enter": function() {
if (!this.MoveToTargetRange(this.order.data.target, IID_TreasureCollecter))
{
this.FinishOrder();
return true;
}
return false;
},
"leave": function() {
this.StopMoving();
},
"MovementUpdate": function(msg) {
if (this.CheckTargetRange(this.order.data.target, IID_TreasureCollecter))
this.SetNextState("COLLECTING");
else if (msg.likelyFailure)
this.FinishOrder();
},
},
"COLLECTING": {
"enter": function() {
let cmpTreasureCollecter = Engine.QueryInterface(this.entity, IID_TreasureCollecter);
if (!cmpTreasureCollecter.StartCollecting(this.order.data.target, IID_UnitAI))
{
this.ProcessMessage("TargetInvalidated");
return true;
}
this.FaceTowardsTarget(this.order.data.target);
this.SelectAnimation("collecting_treasure");
return false;
},
"leave": function() {
let cmpTreasureCollecter = Engine.QueryInterface(this.entity, IID_TreasureCollecter);
if (cmpTreasureCollecter)
cmpTreasureCollecter.StopCollecting();
this.ResetAnimation();
},
"OutOfRange": function(msg) {
this.SetNextState("APPROACHING");
},
"TargetInvalidated": function(msg) {
this.FinishOrder();
},
},
},
"TRADE": { "TRADE": {
"Attacked": function(msg) { "Attacked": function(msg) {
// Ignore attack // Ignore attack
@ -4240,6 +4307,16 @@ UnitAI.prototype.OnPackFinished = function(msg)
this.UnitFsm.ProcessMessage(this, {"type": "PackFinished", "packed": msg.packed}); this.UnitFsm.ProcessMessage(this, {"type": "PackFinished", "packed": msg.packed});
}; };
/**
* A general function to process messages sent from components.
* @param {string} type - The type of message to process.
* @param {Object} msg - Optionally extra data to use.
*/
UnitAI.prototype.ProcessMessage = function(type, msg)
{
this.UnitFsm.ProcessMessage(this, { "type": type, "data": msg });
};
//// Helper functions to be called by the FSM //// //// Helper functions to be called by the FSM ////
UnitAI.prototype.GetWalkSpeed = function() UnitAI.prototype.GetWalkSpeed = function()
@ -5607,6 +5684,14 @@ UnitAI.prototype.ReturnResource = function(target, queued)
this.AddOrder("ReturnResource", { "target": target, "force": true }, queued); this.AddOrder("ReturnResource", { "target": target, "force": true }, queued);
}; };
/**
* Adds order to collect a treasure to queue, forced by the player.
*/
UnitAI.prototype.CollectTreasure = function(target, queued)
{
this.AddOrder("CollectTreasure", { "target": target, "force": true }, queued);
};
UnitAI.prototype.CancelSetupTradeRoute = function(target) UnitAI.prototype.CancelSetupTradeRoute = function(target)
{ {
let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader); let cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);

View File

@ -0,0 +1 @@
Engine.RegisterInterface("Treasure");

View File

@ -0,0 +1 @@
Engine.RegisterInterface("TreasureCollecter");

View File

@ -32,6 +32,8 @@ Engine.LoadComponentScript("interfaces/TechnologyManager.js");
Engine.LoadComponentScript("interfaces/Trader.js"); Engine.LoadComponentScript("interfaces/Trader.js");
Engine.LoadComponentScript("interfaces/TurretHolder.js"); Engine.LoadComponentScript("interfaces/TurretHolder.js");
Engine.LoadComponentScript("interfaces/Timer.js"); Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/Treasure.js");
Engine.LoadComponentScript("interfaces/TreasureCollecter.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js"); Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js"); Engine.LoadComponentScript("interfaces/StatusEffectsReceiver.js");
Engine.LoadComponentScript("interfaces/UnitAI.js"); Engine.LoadComponentScript("interfaces/UnitAI.js");

View File

@ -0,0 +1,56 @@
Resources = {
"BuildSchema": () => {
let schema = "";
for (let res of ["food", "metal"])
{
for (let subtype in ["meat", "grain"])
schema += "<value>" + res + "." + subtype + "</value>";
schema += "<value> treasure." + res + "</value>";
}
return "<choice>" + schema + "</choice>";
}
};
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/Treasure.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
Engine.LoadComponentScript("Treasure.js");
Engine.LoadComponentScript("Trigger.js");
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oVal);
ConstructComponent(SYSTEM_ENTITY, "Trigger", {});
const entity = 11;
let treasurer = 12;
let treasurerOwner = 1;
let cmpTreasure = ConstructComponent(entity, "Treasure", {
"CollectTime": "1000",
"Resources": {
"Food": "10"
}
});
cmpTreasure.OnOwnershipChanged({ "to": 0 });
TS_ASSERT(!cmpTreasure.Reward(treasurer));
AddMock(treasurer, IID_Ownership, {
"GetOwner": () => treasurerOwner
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": (id) => treasurerOwner
});
let cmpPlayer = AddMock(treasurerOwner, IID_Player, {
"AddResources": (type, amount) => {},
"GetPlayerID": () => treasurerOwner
});
let spy = new Spy(cmpPlayer, "AddResources");
TS_ASSERT(cmpTreasure.Reward(treasurer));
TS_ASSERT_EQUALS(spy._called, 1);
// Don't allow collecting twice.
TS_ASSERT(!cmpTreasure.Reward(treasurer));
TS_ASSERT_EQUALS(spy._called, 1);

View File

@ -0,0 +1,47 @@
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/Treasure.js");
Engine.LoadComponentScript("interfaces/TreasureCollecter.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("Timer.js");
Engine.LoadComponentScript("TreasureCollecter.js");
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"IsInTargetRange": () => true
});
const entity = 11;
let treasure = 12;
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", {});
let cmpTreasurer = ConstructComponent(entity, "TreasureCollecter", {
"MaxDistance": "2.0"
});
TS_ASSERT(!cmpTreasurer.StartCollecting(treasure));
let cmpTreasure = AddMock(treasure, IID_Treasure, {
"Reward": (ent) => true,
"CollectionTime": () => 1000,
"IsAvailable": () => true
});
let spyTreasure = new Spy(cmpTreasure, "Reward");
TS_ASSERT(cmpTreasurer.StartCollecting(treasure));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(spyTreasure._called, 1);
// Test that starting to collect twice merely collects once.
spyTreasure._called = 0;
TS_ASSERT(cmpTreasurer.StartCollecting(treasure));
TS_ASSERT(cmpTreasurer.StartCollecting(treasure));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(spyTreasure._called, 1);
// Test callback is called.
let cmpUnitAI = AddMock(entity, IID_UnitAI, {
"ProcessMessage": (type, data) => TS_ASSERT_EQUALS(type, "TargetInvalidated")
});
let spyUnitAI = new Spy(cmpUnitAI, "ProcessMessage");
TS_ASSERT(cmpTreasurer.StartCollecting(treasure, IID_UnitAI));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(spyUnitAI._called, 1);

View File

@ -0,0 +1,79 @@
Resources = {
"GetCodes": () => ["food", "metal", "stone", "wood"],
"GetTradableCodes": () => ["food", "metal", "stone", "wood"],
"GetBarterableCodes": () => ["food", "metal", "stone", "wood"],
"BuildSchema": () => {
let schema = "";
for (let res of ["food", "metal"])
{
for (let subtype in ["meat", "grain"])
schema += "<value>" + res + "." + subtype + "</value>";
schema += "<value> treasure." + res + "</value>";
}
return "<choice>" + schema + "</choice>";
},
"GetResource": (type) => {
return {
"subtypes": {
"meat": "meat",
"grain": "grain"
}
};
}
};
Engine.LoadHelperScript("Player.js");
Engine.LoadComponentScript("interfaces/Player.js");
Engine.LoadComponentScript("interfaces/StatisticsTracker.js");
Engine.LoadComponentScript("interfaces/Treasure.js");
Engine.LoadComponentScript("interfaces/TreasureCollecter.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/Trigger.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
Engine.LoadComponentScript("Player.js");
Engine.LoadComponentScript("Timer.js");
Engine.LoadComponentScript("Treasure.js");
Engine.LoadComponentScript("TreasureCollecter.js");
Engine.LoadComponentScript("Trigger.js");
let cmpTimer = ConstructComponent(SYSTEM_ENTITY, "Timer", {});
Engine.RegisterGlobal("ApplyValueModificationsToEntity", (prop, oVal, ent) => oVal);
ConstructComponent(SYSTEM_ENTITY, "Trigger", {});
const treasure = 11;
const treasurer = 12;
const owner = 1;
AddMock(treasurer, IID_Ownership, {
"GetOwner": () => owner
});
AddMock(SYSTEM_ENTITY, IID_PlayerManager, {
"GetPlayerByID": (id) => owner
});
AddMock(SYSTEM_ENTITY, IID_ObstructionManager, {
"IsInTargetRange": (ent, target, min, max, invert) => true
});
let cmpPlayer = ConstructComponent(owner, "Player", {
"SpyCostMultiplier": 1,
"BarterMultiplier": {}
});
let playerSpy = new Spy(cmpPlayer, "AddResources");
let cmpTreasure = ConstructComponent(treasure, "Treasure", {
"CollectTime": "1000",
"Resources": {
"Food": "10"
}
});
cmpTreasure.OnOwnershipChanged({ "to": 0 });
let cmpTreasurer = ConstructComponent(treasurer, "TreasureCollecter", {
"MaxDistance": "2.0"
});
TS_ASSERT(cmpTreasurer.StartCollecting(treasure));
cmpTimer.OnUpdate({ "turnLength": 1 });
TS_ASSERT_EQUALS(playerSpy._called, 1);

View File

@ -16,15 +16,7 @@
{ "value": "Loot/metal", "replace": 6, "affects": "Infantry" }, { "value": "Loot/metal", "replace": 6, "affects": "Infantry" },
{ "value": "Loot/metal", "replace": 8, "affects": "Cavalry" }, { "value": "Loot/metal", "replace": 8, "affects": "Cavalry" },
{ "value": "Loot/metal", "replace": 12, "affects": "Elephant" }, { "value": "Loot/metal", "replace": 12, "affects": "Elephant" },
{ "value": "ResourceGatherer/Rates/food.fish", "replace": 0 }, { "value": "ResourceGatherer/BaseSpeed", "replace": 0 }
{ "value": "ResourceGatherer/Rates/food.fruit", "replace": 0 },
{ "value": "ResourceGatherer/Rates/food.grain", "replace": 0 },
{ "value": "ResourceGatherer/Rates/food.meat", "replace": 0 },
{ "value": "ResourceGatherer/Rates/wood.tree", "replace": 0 },
{ "value": "ResourceGatherer/Rates/wood.ruins", "replace": 0 },
{ "value": "ResourceGatherer/Rates/stone.rock", "replace": 0 },
{ "value": "ResourceGatherer/Rates/stone.ruins", "replace": 0 },
{ "value": "ResourceGatherer/Rates/metal.ore", "replace": 0 }
], ],
"affects": ["Mercenary !Champion"] "affects": ["Mercenary !Champion"]
} }

View File

@ -72,6 +72,13 @@ var g_Commands = {
Cheat(cmd); Cheat(cmd);
}, },
"collect-treasure": function(player, cmd, data)
{
GetFormationUnitAIs(data.entities, player, cmd, data.formation).forEach(cmpUnitAI => {
cmpUnitAI.CollectTreasure(cmd.target, cmd.queued);
});
},
"diplomacy": function(player, cmd, data) "diplomacy": function(player, cmd, data)
{ {
let cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager); let cmpCeasefireManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_CeasefireManager);

View File

@ -97,6 +97,14 @@ function GetRallyPointCommands(cmpRallyPoint, spawnedEnts)
"queued": true "queued": true
}); });
break; break;
case "collect-treasure":
ret.push({
"type": "collect-treasure",
"entities": spawnedEnts,
"target": data[i].target,
"queued": true,
});
break;
default: default:
ret.push({ ret.push({
"type": "walk", "type": "walk",

View File

@ -4,7 +4,7 @@
* To prevent validation errors, disabled resources are included in the schema. * To prevent validation errors, disabled resources are included in the schema.
* *
* @param datatype - The datatype of the element * @param datatype - The datatype of the element
* @param additional - Array of additional data elements. Time, xp, treasure, etc. * @param additional - Array of additional data elements. Time, xp, etc.
* @param subtypes - If true, resource subtypes will be included as well. * @param subtypes - If true, resource subtypes will be included as well.
* @return RelaxNG schema string * @return RelaxNG schema string
*/ */
@ -47,15 +47,6 @@ Resources.prototype.BuildSchema = function(datatype, additional = [], subtypes =
"</element>" + "</element>" +
"</optional>"; "</optional>";
if (additional.indexOf("treasure") !== -1)
for (let res of resCodes)
schema +=
"<optional>" +
"<element name='" + "treasure." + res + "'>" +
datatype +
"</element>" +
"</optional>";
return "<interleave>" + schema + "</interleave>"; return "<interleave>" + schema + "</interleave>";
}; };
@ -63,29 +54,19 @@ Resources.prototype.BuildSchema = function(datatype, additional = [], subtypes =
* Builds the value choices for a RelaxNG `<choice></choice>` object, based on currently valid resources. * Builds the value choices for a RelaxNG `<choice></choice>` object, based on currently valid resources.
* *
* @oaram subtypes - If set to true, the choices returned will be resource subtypes, rather than main types * @oaram subtypes - If set to true, the choices returned will be resource subtypes, rather than main types
* @param treasure - If set to true, the pseudo resource 'treasure' (or its subtypes) will be included
* @return String of RelaxNG Schema `<choice/>` values. * @return String of RelaxNG Schema `<choice/>` values.
*/ */
Resources.prototype.BuildChoicesSchema = function(subtypes = false, treasure = false) Resources.prototype.BuildChoicesSchema = function(subtypes = false)
{ {
let schema = ""; let schema = "";
if (!subtypes) if (!subtypes)
{ for (let res of this.resourceData.map(resource => resource.code))
let resCodes = this.resourceData.map(resource => resource.code);
if (treasure)
resCodes.push("treasure");
for (let res of resCodes)
schema += "<value>" + res + "</value>"; schema += "<value>" + res + "</value>";
}
else else
for (let res of this.resourceData) for (let res of this.resourceData)
{
for (let subtype in res.subtypes) for (let subtype in res.subtypes)
schema += "<value>" + res.code + "." + subtype + "</value>"; schema += "<value>" + res.code + "." + subtype + "</value>";
if (treasure)
schema += "<value>" + "treasure." + res.code + "</value>";
}
return "<choice>" + schema + "</choice>"; return "<choice>" + schema + "</choice>";
}; };

View File

@ -23,12 +23,8 @@ function LoadMapSettings(settings)
} }
if (settings.DisableTreasures) if (settings.DisableTreasures)
for (let ent of Engine.GetEntitiesWithInterface(IID_ResourceSupply)) for (let ent of Engine.GetEntitiesWithInterface(IID_Treasure))
{ Engine.DestroyEntity(ent);
let cmpResourceSupply = Engine.QueryInterface(ent, IID_ResourceSupply);
if (cmpResourceSupply.GetType().generic == "treasure")
Engine.DestroyEntity(ent);
}
let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager); let cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
if (cmpRangeManager) if (cmpRangeManager)

View File

@ -8,10 +8,6 @@
<SpecificName>Food Treasure</SpecificName> <SpecificName>Food Treasure</SpecificName>
<Icon>gaia/special_treasure_food.png</Icon> <Icon>gaia/special_treasure_food.png</Icon>
</Identity> </Identity>
<ResourceSupply>
<Max>100</Max>
<Type>treasure.food</Type>
</ResourceSupply>
<Selectable replace=""> <Selectable replace="">
<Overlay> <Overlay>
<Texture> <Texture>
@ -20,6 +16,11 @@
</Texture> </Texture>
</Overlay> </Overlay>
</Selectable> </Selectable>
<Treasure>
<Resources>
<food>100</food>
</Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/barrel_a.xml</Actor> <Actor>props/special/eyecandy/barrel_a.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -15,10 +15,11 @@
<Floating>false</Floating> <Floating>false</Floating>
<FloatDepth>0.0</FloatDepth> <FloatDepth>0.0</FloatDepth>
</Position> </Position>
<ResourceSupply> <Treasure>
<Max>200</Max> <Resources>
<Type>treasure.food</Type> <food>200</food>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/barrels_buried.xml</Actor> <Actor>props/special/eyecandy/barrels_buried.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -11,10 +11,11 @@
<Obstruction> <Obstruction>
<Static width="2.0" depth="3.0"/> <Static width="2.0" depth="3.0"/>
</Obstruction> </Obstruction>
<ResourceSupply> <Treasure>
<Max>300</Max> <Resources>
<Type>treasure.food</Type> <food>300</food>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/produce_bin_a.xml</Actor> <Actor>props/special/eyecandy/produce_bin_a.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -11,10 +11,11 @@
<Obstruction> <Obstruction>
<Static width="1.75" depth="1.75"/> <Static width="1.75" depth="1.75"/>
</Obstruction> </Obstruction>
<ResourceSupply> <Treasure>
<Max>200</Max> <Resources>
<Type>treasure.food</Type> <food>200</food>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/crate_a.xml</Actor> <Actor>props/special/eyecandy/crate_a.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -11,10 +11,11 @@
<Obstruction> <Obstruction>
<Static width="6.5" depth="6.5"/> <Static width="6.5" depth="6.5"/>
</Obstruction> </Obstruction>
<ResourceSupply> <Treasure>
<Max>300</Max> <Resources>
<Type>treasure.food</Type> <food>300</food>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/amphorae.xml</Actor> <Actor>props/special/eyecandy/amphorae.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -11,10 +11,11 @@
<Obstruction> <Obstruction>
<Static width="8.0" depth="8.0"/> <Static width="8.0" depth="8.0"/>
</Obstruction> </Obstruction>
<ResourceSupply> <Treasure>
<Max>600</Max> <Resources>
<Type>treasure.food</Type> <food>600</food>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/treasure_persian_food_big.xml</Actor> <Actor>props/special/eyecandy/treasure_persian_food_big.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -11,10 +11,11 @@
<Obstruction> <Obstruction>
<Static width="5.0" depth="5.0"/> <Static width="5.0" depth="5.0"/>
</Obstruction> </Obstruction>
<ResourceSupply> <Treasure>
<Max>400</Max> <Resources>
<Type>treasure.food</Type> <food>400</food>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/treasure_persian_food_small.xml</Actor> <Actor>props/special/eyecandy/treasure_persian_food_small.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -7,10 +7,11 @@
<Identity> <Identity>
<SpecificName>Golden Fleece</SpecificName> <SpecificName>Golden Fleece</SpecificName>
</Identity> </Identity>
<ResourceSupply> <Treasure>
<Max>1000</Max> <Resources>
<Type>treasure.metal</Type> <metal>1000</metal>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>special/golden_fleece.xml</Actor> <Actor>special/golden_fleece.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -7,15 +7,16 @@
<Identity> <Identity>
<SpecificName>Secret Box</SpecificName> <SpecificName>Secret Box</SpecificName>
</Identity> </Identity>
<ResourceSupply>
<Max>300</Max>
<Type>treasure.metal</Type>
</ResourceSupply>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<select>interface/select/resource/sel_metal.xml</select> <select>interface/select/resource/sel_metal.xml</select>
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<Treasure>
<Resources>
<metal>300</metal>
</Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/barrel_a.xml</Actor> <Actor>props/special/eyecandy/barrel_a.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -10,15 +10,16 @@
<Obstruction> <Obstruction>
<Static width="7.0" depth="7.0"/> <Static width="7.0" depth="7.0"/>
</Obstruction> </Obstruction>
<ResourceSupply>
<Max>500</Max>
<Type>treasure.metal</Type>
</ResourceSupply>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<select>interface/select/resource/sel_metal.xml</select> <select>interface/select/resource/sel_metal.xml</select>
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<Treasure>
<Resources>
<metal>500</metal>
</Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/treasure_persian_metal_big.xml</Actor> <Actor>props/special/eyecandy/treasure_persian_metal_big.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -10,15 +10,16 @@
<Obstruction> <Obstruction>
<Static width="5.0" depth="3.0"/> <Static width="5.0" depth="3.0"/>
</Obstruction> </Obstruction>
<ResourceSupply>
<Max>300</Max>
<Type>treasure.metal</Type>
</ResourceSupply>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<select>interface/select/resource/sel_metal.xml</select> <select>interface/select/resource/sel_metal.xml</select>
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<Treasure>
<Resources>
<metal>300</metal>
</Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/treasure_persian_metal_small.xml</Actor> <Actor>props/special/eyecandy/treasure_persian_metal_small.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -7,10 +7,11 @@
<Identity> <Identity>
<SpecificName>Pegasus</SpecificName> <SpecificName>Pegasus</SpecificName>
</Identity> </Identity>
<ResourceSupply> <Treasure>
<Max>1000</Max> <Resources>
<Type>treasure.metal</Type> <metal>1000</metal>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>special/pegasus.xml</Actor> <Actor>special/pegasus.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -14,10 +14,11 @@
<Floating>true</Floating> <Floating>true</Floating>
<FloatDepth>0.0</FloatDepth> <FloatDepth>0.0</FloatDepth>
</Position> </Position>
<ResourceSupply> <Treasure>
<Max>500</Max> <Resources>
<Type>treasure.wood</Type> <wood>500</wood>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/shipwreck_ram_side.xml</Actor> <Actor>props/special/eyecandy/shipwreck_ram_side.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -17,10 +17,11 @@
<Floating>true</Floating> <Floating>true</Floating>
<FloatDepth>0.0</FloatDepth> <FloatDepth>0.0</FloatDepth>
</Position> </Position>
<ResourceSupply> <Treasure>
<Max>200</Max> <Resources>
<Type>treasure.food</Type> <food>200</food>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/barrels_floating.xml</Actor> <Actor>props/special/eyecandy/barrels_floating.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -14,10 +14,11 @@
<Floating>true</Floating> <Floating>true</Floating>
<FloatDepth>0.0</FloatDepth> <FloatDepth>0.0</FloatDepth>
</Position> </Position>
<ResourceSupply> <Treasure>
<Max>550</Max> <Resources>
<Type>treasure.wood</Type> <wood>550</wood>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/shipwreck_ram_bow.xml</Actor> <Actor>props/special/eyecandy/shipwreck_ram_bow.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -14,10 +14,11 @@
<Floating>true</Floating> <Floating>true</Floating>
<FloatDepth>0.0</FloatDepth> <FloatDepth>0.0</FloatDepth>
</Position> </Position>
<ResourceSupply> <Treasure>
<Max>400</Max> <Resources>
<Type>treasure.wood</Type> <wood>400</wood>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/shipwreck_sail_boat.xml</Actor> <Actor>props/special/eyecandy/shipwreck_sail_boat.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -14,10 +14,11 @@
<Floating>true</Floating> <Floating>true</Floating>
<FloatDepth>0.0</FloatDepth> <FloatDepth>0.0</FloatDepth>
</Position> </Position>
<ResourceSupply> <Treasure>
<Max>450</Max> <Resources>
<Type>treasure.wood</Type> <wood>450</wood>
</ResourceSupply> </Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/shipwreck_sail_boat_cut.xml</Actor> <Actor>props/special/eyecandy/shipwreck_sail_boat_cut.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -10,15 +10,16 @@
<Obstruction> <Obstruction>
<Static width="2.0" depth="2.0"/> <Static width="2.0" depth="2.0"/>
</Obstruction> </Obstruction>
<ResourceSupply>
<Max>300</Max>
<Type>treasure.stone</Type>
</ResourceSupply>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<select>interface/select/resource/sel_stone.xml</select> <select>interface/select/resource/sel_stone.xml</select>
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<Treasure>
<Resources>
<stone>300</stone>
</Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/standing_stones.xml</Actor> <Actor>props/special/eyecandy/standing_stones.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -10,15 +10,16 @@
<Obstruction> <Obstruction>
<Static width="6.5" depth="6.5"/> <Static width="6.5" depth="6.5"/>
</Obstruction> </Obstruction>
<ResourceSupply>
<Max>300</Max>
<Type>treasure.stone</Type>
</ResourceSupply>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<select>interface/select/resource/sel_stone.xml</select> <select>interface/select/resource/sel_stone.xml</select>
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<Treasure>
<Resources>
<stone>300</stone>
</Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/stone_pile.xml</Actor> <Actor>props/special/eyecandy/stone_pile.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -10,15 +10,16 @@
<Obstruction> <Obstruction>
<Static width="3.5" depth="6.5"/> <Static width="3.5" depth="6.5"/>
</Obstruction> </Obstruction>
<ResourceSupply>
<Max>300</Max>
<Type>treasure.wood</Type>
</ResourceSupply>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<select>interface/select/resource/sel_tree.xml</select> <select>interface/select/resource/sel_tree.xml</select>
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<Treasure>
<Resources>
<wood>300</wood>
</Resources>
</Treasure>
<VisualActor> <VisualActor>
<Actor>props/special/eyecandy/wood_pile.xml</Actor> <Actor>props/special/eyecandy/wood_pile.xml</Actor>
</VisualActor> </VisualActor>

View File

@ -16,11 +16,6 @@
<Obstruction> <Obstruction>
<Static width="1.5" depth="1.5"/> <Static width="1.5" depth="1.5"/>
</Obstruction> </Obstruction>
<ResourceSupply>
<KillBeforeGather>false</KillBeforeGather>
<Max>300</Max>
<MaxGatherers>1</MaxGatherers>
</ResourceSupply>
<Selectable> <Selectable>
<Overlay> <Overlay>
<Outline> <Outline>
@ -36,4 +31,7 @@
<StatusBars> <StatusBars>
<HeightOffset>3.75</HeightOffset> <HeightOffset>3.75</HeightOffset>
</StatusBars> </StatusBars>
<Treasure>
<CollectTime>1000</CollectTime>
</Treasure>
</Entity> </Entity>

View File

@ -85,19 +85,6 @@
</Damage> </Damage>
</Entity> </Entity>
</Resistance> </Resistance>
<ResourceGatherer>
<MaxDistance>2.0</MaxDistance>
<BaseSpeed>1.0</BaseSpeed>
<Rates>
<treasure>1</treasure>
</Rates>
<Capacities>
<food>10</food>
<wood>10</wood>
<stone>10</stone>
<metal>10</metal>
</Capacities>
</ResourceGatherer>
<Selectable> <Selectable>
<Overlay> <Overlay>
<Texture> <Texture>
@ -120,6 +107,9 @@
<HeightOffset>5.0</HeightOffset> <HeightOffset>5.0</HeightOffset>
</StatusBars> </StatusBars>
<StatusEffectsReceiver/> <StatusEffectsReceiver/>
<TreasureCollecter>
<MaxDistance>2</MaxDistance>
</TreasureCollecter>
<UnitAI> <UnitAI>
<DefaultStance>aggressive</DefaultStance> <DefaultStance>aggressive</DefaultStance>
<FleeDistance>12.0</FleeDistance> <FleeDistance>12.0</FleeDistance>

View File

@ -28,7 +28,6 @@
<Anchor>pitch-roll</Anchor> <Anchor>pitch-roll</Anchor>
</Position> </Position>
<Resistance replace=""/> <Resistance replace=""/>
<ResourceGatherer disable=""/>
<Selectable> <Selectable>
<Overlay> <Overlay>
<Texture> <Texture>
@ -45,6 +44,7 @@
<walk>actor/singlesteps/steps_grass.xml</walk> <walk>actor/singlesteps/steps_grass.xml</walk>
</SoundGroups> </SoundGroups>
</Sound> </Sound>
<TreasureCollecter disable=""/>
<UnitAI> <UnitAI>
<DefaultStance>standground</DefaultStance> <DefaultStance>standground</DefaultStance>
<CanGuard>false</CanGuard> <CanGuard>false</CanGuard>

View File

@ -65,9 +65,6 @@
</Rates> </Rates>
<Capacities> <Capacities>
<food>20</food> <food>20</food>
<wood>20</wood>
<stone>20</stone>
<metal>20</metal>
</Capacities> </Capacities>
</ResourceGatherer> </ResourceGatherer>
<Selectable> <Selectable>

View File

@ -38,7 +38,6 @@
<xp>100</xp> <xp>100</xp>
<food>10</food> <food>10</food>
</Loot> </Loot>
<ResourceGatherer disable=""/>
<Selectable> <Selectable>
<Overlay> <Overlay>
<Texture> <Texture>
@ -65,6 +64,7 @@
<TrainingRestrictions> <TrainingRestrictions>
<Category>WarDog</Category> <Category>WarDog</Category>
</TrainingRestrictions> </TrainingRestrictions>
<TreasureCollecter disable=""/>
<UnitMotion> <UnitMotion>
<WalkSpeed op="mul">1.5</WalkSpeed> <WalkSpeed op="mul">1.5</WalkSpeed>
<RunMultiplier>2</RunMultiplier> <RunMultiplier>2</RunMultiplier>

View File

@ -21,7 +21,6 @@
<Position> <Position>
<TurnRate>4</TurnRate> <TurnRate>4</TurnRate>
</Position> </Position>
<ResourceGatherer disable=""/>
<Selectable> <Selectable>
<Overlay> <Overlay>
<Texture> <Texture>
@ -30,6 +29,7 @@
</Texture> </Texture>
</Overlay> </Overlay>
</Selectable> </Selectable>
<TreasureCollecter disable=""/>
<UnitAI> <UnitAI>
<DefaultStance>passive</DefaultStance> <DefaultStance>passive</DefaultStance>
<CanGuard>false</CanGuard> <CanGuard>false</CanGuard>

View File

@ -100,6 +100,12 @@
<stone.ruins>2</stone.ruins> <stone.ruins>2</stone.ruins>
<metal.ore>0.5</metal.ore> <metal.ore>0.5</metal.ore>
</Rates> </Rates>
<Capacities>
<food>10</food>
<wood>10</wood>
<stone>10</stone>
<metal>10</metal>
</Capacities>
</ResourceGatherer> </ResourceGatherer>
<Sound> <Sound>
<SoundGroups> <SoundGroups>

View File

@ -70,6 +70,7 @@
<BarHeight>0.5</BarHeight> <BarHeight>0.5</BarHeight>
<HeightOffset>6.0</HeightOffset> <HeightOffset>6.0</HeightOffset>
</StatusBars> </StatusBars>
<TreasureCollecter disable=""/>
<UnitMotion> <UnitMotion>
<PassabilityClass>ship</PassabilityClass> <PassabilityClass>ship</PassabilityClass>
</UnitMotion> </UnitMotion>

View File

@ -55,7 +55,6 @@
<wood>25</wood> <wood>25</wood>
<metal>15</metal> <metal>15</metal>
</Loot> </Loot>
<ResourceGatherer disable=""/>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<attack_impact_ranged>attack/impact/arrow_impact.xml</attack_impact_ranged> <attack_impact_ranged>attack/impact/arrow_impact.xml</attack_impact_ranged>

View File

@ -44,7 +44,6 @@
</Identity> </Identity>
<Loot disable=""/> <Loot disable=""/>
<Repairable disable=""/> <Repairable disable=""/>
<ResourceGatherer disable=""/>
<UnitMotion> <UnitMotion>
<PassabilityClass>ship-small</PassabilityClass> <PassabilityClass>ship-small</PassabilityClass>
<WalkSpeed op="mul">1.6</WalkSpeed> <WalkSpeed op="mul">1.6</WalkSpeed>

View File

@ -48,9 +48,9 @@
</Resistance> </Resistance>
<ResourceGatherer> <ResourceGatherer>
<MaxDistance>6.0</MaxDistance> <MaxDistance>6.0</MaxDistance>
<BaseSpeed>1.0</BaseSpeed>
<Rates> <Rates>
<food.fish>1.8</food.fish> <food.fish>1.8</food.fish>
<treasure disable=""/>
</Rates> </Rates>
<Capacities> <Capacities>
<food>40</food> <food>40</food>

View File

@ -34,13 +34,13 @@
</Damage> </Damage>
</Entity> </Entity>
</Resistance> </Resistance>
<ResourceGatherer>
<MaxDistance>12.0</MaxDistance>
</ResourceGatherer>
<Trader> <Trader>
<GainMultiplier>0.75</GainMultiplier> <GainMultiplier>0.75</GainMultiplier>
<GarrisonGainMultiplier>0.2</GarrisonGainMultiplier> <GarrisonGainMultiplier>0.2</GarrisonGainMultiplier>
</Trader> </Trader>
<TreasureCollecter>
<MaxDistance>12</MaxDistance>
</TreasureCollecter>
<UnitAI> <UnitAI>
<DefaultStance>passive</DefaultStance> <DefaultStance>passive</DefaultStance>
<CanGuard>false</CanGuard> <CanGuard>false</CanGuard>

View File

@ -59,7 +59,6 @@
<Position> <Position>
<TurnRate>2</TurnRate> <TurnRate>2</TurnRate>
</Position> </Position>
<ResourceGatherer disable=""/>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<attack_ranged>attack/siege/ballist_attack.xml</attack_ranged> <attack_ranged>attack/siege/ballist_attack.xml</attack_ranged>

View File

@ -58,7 +58,6 @@
<Position> <Position>
<TurnRate>2</TurnRate> <TurnRate>2</TurnRate>
</Position> </Position>
<ResourceGatherer disable=""/>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<attack_impact_ranged>attack/impact/arrow_impact.xml</attack_impact_ranged> <attack_impact_ranged>attack/impact/arrow_impact.xml</attack_impact_ranged>

View File

@ -85,6 +85,12 @@
<stone.ruins>2</stone.ruins> <stone.ruins>2</stone.ruins>
<metal.ore>0.35</metal.ore> <metal.ore>0.35</metal.ore>
</Rates> </Rates>
<Capacities>
<food>10</food>
<wood>10</wood>
<stone>10</stone>
<metal>10</metal>
</Capacities>
</ResourceGatherer> </ResourceGatherer>
<Sound> <Sound>
<SoundGroups> <SoundGroups>

View File

@ -53,6 +53,7 @@
<metal>5</metal> <metal>5</metal>
</Loot> </Loot>
<ResourceGatherer> <ResourceGatherer>
<MaxDistance>2.0</MaxDistance>
<BaseSpeed>1.0</BaseSpeed> <BaseSpeed>1.0</BaseSpeed>
<Rates> <Rates>
<food.fish>0.5</food.fish> <food.fish>0.5</food.fish>
@ -64,9 +65,15 @@
<stone.rock>1.0</stone.rock> <stone.rock>1.0</stone.rock>
<stone.ruins>5</stone.ruins> <stone.ruins>5</stone.ruins>
<metal.ore>1.0</metal.ore> <metal.ore>1.0</metal.ore>
<treasure disable=""/>
</Rates> </Rates>
<Capacities>
<food>10</food>
<wood>10</wood>
<stone>10</stone>
<metal>10</metal>
</Capacities>
</ResourceGatherer> </ResourceGatherer>
<TreasureCollecter disable=""/>
<Sound> <Sound>
<SoundGroups> <SoundGroups>
<build>resource/construction/con_wood.xml</build> <build>resource/construction/con_wood.xml</build>