Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_trireme.xml (revision 14550) @@ -1,70 +1,70 @@ 0.0 35.0 0.0 55.0 10.0 60.0 1000 2000 1.5 3 1 Infantry Ranged 3 30 200 150 8.0 30 0 - false + Female Infantry Support Infantry Cavalry Siege 1 10 true 1400 Medium Warship Classes: Mechanical Warship Medium Ranged Melee. Secondary Attack: Ramming. Warship Medium Ranged phase_town attack/weapon/arrowfly.xml attack/impact/arrow_metal.xml 6.0 0.5 6.0 14.5 20.0 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_tower.xml (revision 14550) @@ -1,98 +1,98 @@ 5 20 2 0.0 10.0 5.0 55.0 2.0 10 75.0 1200 2000 2.0 Structure Structure 2.0 0 0.5 Infantry 5 60 500 300 20.0 20 0.1 - true + Unit Support Infantry 0 2 500 Siege Tower Ranged SiegeTower Classes: Ranged SiegeTower. Garrison up to 20 infantry inside to increase arrow count from 0 to 10. Counters: 2x vs. Buildings. Countered by: Melee Cavalry. circle/256x256.png circle/256x256_mask.png attack/siege/ram_move.xml attack/siege/ram_attack.xml attack/siege/ram_move.xml 4.0 0.5 12.0 5.0 10.0 80 Index: ps/trunk/source/simulation2/components/ICmpFootprint.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpFootprint.cpp (revision 14549) +++ ps/trunk/source/simulation2/components/ICmpFootprint.cpp (revision 14550) @@ -1,66 +1,67 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ICmpFootprint.h" #include "simulation2/system/InterfaceScripted.h" #include "simulation2/system/SimContext.h" #include "maths/FixedVector3D.h" CScriptVal ICmpFootprint::GetShape_wrapper() { EShape shape; entity_pos_t size0, size1, height; GetShape(shape, size0, size1, height); JSContext* cx = GetSimContext().GetScriptInterface().GetContext(); JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); if (!obj) return JSVAL_VOID; if (shape == CIRCLE) { jsval ptype = ScriptInterface::ToJSVal(cx, "circle"); jsval pradius = ScriptInterface::ToJSVal(cx, size0); jsval pheight = ScriptInterface::ToJSVal(cx, height); JS_SetProperty(cx, obj, "type", &ptype); JS_SetProperty(cx, obj, "radius", &pradius); JS_SetProperty(cx, obj, "height", &pheight); } else { jsval ptype = ScriptInterface::ToJSVal(cx, "square"); jsval pwidth = ScriptInterface::ToJSVal(cx, size0); jsval pdepth = ScriptInterface::ToJSVal(cx, size1); jsval pheight = ScriptInterface::ToJSVal(cx, height); JS_SetProperty(cx, obj, "type", &ptype); JS_SetProperty(cx, obj, "width", &pwidth); JS_SetProperty(cx, obj, "depth", &pdepth); JS_SetProperty(cx, obj, "height", &pheight); } return OBJECT_TO_JSVAL(obj); } BEGIN_INTERFACE_WRAPPER(Footprint) DEFINE_INTERFACE_METHOD_1("PickSpawnPoint", CFixedVector3D, ICmpFootprint, PickSpawnPoint, entity_id_t) +DEFINE_INTERFACE_METHOD_1("PickSpawnPointBothPass", CFixedVector3D, ICmpFootprint, PickSpawnPointBothPass, entity_id_t) DEFINE_INTERFACE_METHOD_0("GetShape", CScriptVal, ICmpFootprint, GetShape_wrapper) END_INTERFACE_WRAPPER(Footprint) Index: ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/components/GarrisonHolder.js (revision 14550) @@ -1,625 +1,651 @@ function GarrisonHolder() {} GarrisonHolder.prototype.Schema = "" + "" + "" + "" + "" + "tokens" + "" + "" + "" + "" + "" + "" + - "" + - "" + + "" + + "" + + "tokens" + + "" + + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; /** * Initialize GarrisonHolder Component */ GarrisonHolder.prototype.Init = function() { // Garrisoned Units this.entities = []; this.timer = undefined; this.allowGarrisoning = {}; }; /** * Return range at which entities can garrison here */ GarrisonHolder.prototype.GetLoadingRange = function() { var max = +this.template.LoadingRange; return { "max": max, "min": 0 }; }; /** * Return true if this garrisonHolder can pickup ent */ GarrisonHolder.prototype.CanPickup = function(ent) { if (!this.template.Pickup || this.IsFull()) return false; var cmpOwner = Engine.QueryInterface(this.entity, IID_Ownership); if (!cmpOwner) return false; var player = cmpOwner.GetOwner(); return IsOwnedByPlayer(player, ent); }; /** * Return the list of entities garrisoned inside */ GarrisonHolder.prototype.GetEntities = function() { return this.entities; }; /** * Returns an array of unit classes which can be garrisoned inside this * particualar entity. Obtained from the entity's template */ GarrisonHolder.prototype.GetAllowedClassesList = function() { var classes = this.template.List._string; return classes ? classes.split(/\s+/) : []; }; /** * Get Maximum pop which can be garrisoned */ GarrisonHolder.prototype.GetCapacity = function() { return ApplyValueModificationsToEntity("GarrisonHolder/Max", +this.template.Max, this.entity); }; /** * Return true if this garrisonHolder is full */ GarrisonHolder.prototype.IsFull = function() { return this.GetGarrisonedEntitiesCount() >= this.GetCapacity(); }; /** * Get the heal rate with which garrisoned units will be healed */ GarrisonHolder.prototype.GetHealRate = function() { return ApplyValueModificationsToEntity("GarrisonHolder/BuffHeal", +this.template.BuffHeal, this.entity); }; -GarrisonHolder.prototype.EjectEntitiesOnDestroy = function() -{ - return this.template.EjectEntitiesOnDestroy == "true"; -}; - /** * Set this entity to allow or disallow garrisoning in * Every component calling this function should do it with its own ID, and as long as one * component doesn't allow this entity to garrison, it can't be garrisoned * When this entity already contains garrisoned soldiers, * these will not be able to ungarrison until the flag is set to true again. * * This more useful for modern-day features. For example you can't garrison or ungarrison * a driving vehicle or plane. */ GarrisonHolder.prototype.AllowGarrisoning = function(allow, callerID) { this.allowGarrisoning[callerID] = allow; }; /** * Check if no component of this entity blocks garrisoning * (f.e. because the vehicle is moving too fast) */ GarrisonHolder.prototype.IsGarrisoningAllowed = function() { for each (var allow in this.allowGarrisoning) { if (!allow) return false; } return true; }; /** * Return the number of recursively garrisoned units */ GarrisonHolder.prototype.GetGarrisonedEntitiesCount = function() { var count = 0; for each (var ent in this.entities) { count++; var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder); if (cmpGarrisonHolder) count += cmpGarrisonHolder.GetGarrisonedEntitiesCount(); } return count; }; /** * Get number of garrisoned units capable of shooting arrows * Not necessarily archers */ GarrisonHolder.prototype.GetGarrisonedArcherCount = function(garrisonArrowClasses) { var count = 0; for each (var entity in this.entities) { var cmpIdentity = Engine.QueryInterface(entity, IID_Identity); var classes = cmpIdentity.GetClassesList(); if (classes.some(function(c){return garrisonArrowClasses.indexOf(c) > -1;})) count++; } return count; }; /** * Checks if an entity can be allowed to garrison in the building * based on its class */ GarrisonHolder.prototype.AllowedToGarrison = function(entity) { if (!this.IsGarrisoningAllowed()) return false; var allowedClasses = this.GetAllowedClassesList(); var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList(); // Check if the unit is allowed to be garrisoned inside the building for each (var allowedClass in allowedClasses) { if (entityClasses.indexOf(allowedClass) != -1) { return true; } } return false; }; /** * Garrison a unit inside. * Returns true if successful, false if not * The timer for AutoHeal is started here */ GarrisonHolder.prototype.Garrison = function(entity) { var cmpPosition = Engine.QueryInterface(entity, IID_Position); if (!cmpPosition) return false; if (!this.PerformGarrison(entity)) return false; cmpPosition.MoveOutOfWorld(); return true; }; GarrisonHolder.prototype.PerformGarrison = function(entity) { if (!this.HasEnoughHealth()) return false; // Check if the unit is allowed to be garrisoned inside the building if(!this.AllowedToGarrison(entity)) return false; // check the capacity var extraCount = 0; var cmpGarrisonHolder = Engine.QueryInterface(entity, IID_GarrisonHolder); if (cmpGarrisonHolder) extraCount += cmpGarrisonHolder.GetGarrisonedEntitiesCount(); if (this.GetGarrisonedEntitiesCount() + extraCount >= this.GetCapacity()) return false; if (!this.timer && this.GetHealRate() > 0) { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {}); } // Actual garrisoning happens here this.entities.push(entity); this.UpdateGarrisonFlag(); var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); if (cmpProductionQueue) cmpProductionQueue.PauseProduction(); var cmpAura = Engine.QueryInterface(entity, IID_Auras); if (cmpAura && cmpAura.HasGarrisonAura()) cmpAura.ApplyGarrisonBonus(this.entity); Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {}); var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); if (cmpUnitAI && cmpUnitAI.GetAlertRaiser()) Engine.PostMessage(cmpUnitAI.GetAlertRaiser(), MT_UnitGarrisonedAfterAlert, {"holder": this.entity, "unit": entity}); return true; }; /** * Simply eject the unit from the garrisoning entity without * moving it * Returns true if successful, false if not */ GarrisonHolder.prototype.Eject = function(entity, forced) { var entityIndex = this.entities.indexOf(entity); // Error: invalid entity ID, usually it's already been ejected if (entityIndex == -1) return false; // Fail // Find spawning location var cmpFootprint = Engine.QueryInterface(this.entity, IID_Footprint); - var pos = cmpFootprint.PickSpawnPoint(entity); + var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); + var cmpIdentity = Engine.QueryInterface(this.entity, IID_Identity); + // If the garrisonHolder is a sinking ship, restrict the location to the intersection of both passabilities + // TODO: should use passability classes to be more generic + if ((!cmpHealth || cmpHealth.GetHitpoints() == 0) && cmpIdentity && cmpIdentity.HasClass("Ship")) + var pos = cmpFootprint.PickSpawnPointBothPass(entity); + else + var pos = cmpFootprint.PickSpawnPoint(entity); + if (pos.y < 0) { // Error: couldn't find space satisfying the unit's passability criteria if (forced) { // If ejection is forced, we need to continue, so use center of the building var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); pos = cmpPosition.GetPosition(); } else { // Fail return false; } } this.entities.splice(entityIndex, 1); var cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI); if (cmpUnitAI) cmpUnitAI.Ungarrison(); var cmpProductionQueue = Engine.QueryInterface(entity, IID_ProductionQueue); if (cmpProductionQueue) cmpProductionQueue.UnpauseProduction(); var cmpAura = Engine.QueryInterface(entity, IID_Auras); if (cmpAura && cmpAura.HasGarrisonAura()) cmpAura.RemoveGarrisonBonus(this.entity); var cmpNewPosition = Engine.QueryInterface(entity, IID_Position); cmpNewPosition.JumpTo(pos.x, pos.z); // TODO: what direction should they face in? Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {}); return true; }; /** * Order entities to walk to the Rally Point */ GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities) { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint); if (cmpRallyPoint) { var rallyPos = cmpRallyPoint.GetPositions()[0]; if (rallyPos) { var commands = GetRallyPointCommands(cmpRallyPoint, entities); // ignore the rally point if it is autogarrison if (commands[0].type == "garrison" && commands[0].target == this.entity) return; for each (var com in commands) { ProcessCommand(cmpOwnership.GetOwner(), com); } } } }; /** * Ejects units and orders them to move to the Rally Point. * Returns true if successful, false if not */ GarrisonHolder.prototype.PerformEject = function(entities, forced) { if (!this.IsGarrisoningAllowed() && !forced) return false; var ejectedEntities = []; var success = true; for each (var entity in entities) { if (this.Eject(entity, forced)) { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); var cmpEntOwnership = Engine.QueryInterface(entity, IID_Ownership); if (cmpOwnership && cmpEntOwnership && cmpOwnership.GetOwner() == cmpEntOwnership.GetOwner()) ejectedEntities.push(entity); } else success = false; } this.OrderWalkToRallyPoint(ejectedEntities); this.UpdateGarrisonFlag(); return success; }; /** * Unload unit from the garrisoning entity and order them * to move to the Rally Point * Returns true if successful, false if not */ GarrisonHolder.prototype.Unload = function(entity, forced) { return this.PerformEject([entity], forced); }; /** * Unload one or all units that match a template and owner from * the garrisoning entity and order them to move to the Rally Point * Returns true if successful, false if not * * extendedTemplate has the format "p"+ownerid+"&"+template */ GarrisonHolder.prototype.UnloadTemplate = function(extendedTemplate, all, forced) { var index = extendedTemplate.indexOf("&"); if (index == -1) return false; var owner = +extendedTemplate.slice(1,index); var template = extendedTemplate.slice(index+1); var entities = []; var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager); for each (var entity in this.entities) { var cmpIdentity = Engine.QueryInterface(entity, IID_Identity); // Units with multiple ranks are grouped together. var name = cmpIdentity.GetSelectionGroupName() || cmpTemplateManager.GetCurrentTemplateName(entity); if (name != template) continue; if (owner != Engine.QueryInterface(entity, IID_Ownership).GetOwner()) continue; entities.push(entity); // If 'all' is false, only ungarrison the first matched unit. if (!all) break; } return this.PerformEject(entities, forced); }; /** * Unload all units with same owner as the entity * and order them to move to the Rally Point * Returns true if all successful, false if not */ GarrisonHolder.prototype.UnloadAllOwn = function(forced) { var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership); if (!cmpOwnership) return false; var owner = cmpOwnership.GetOwner(); // Make copy of entity list var entities = []; for each (var entity in this.entities) { var cmpOwnership = Engine.QueryInterface(entity, IID_Ownership); if (cmpOwnership && cmpOwnership.GetOwner() == owner) entities.push(entity); } return this.PerformEject(entities, forced); }; /** * Unload all units from the entity * and order them to move to the Rally Point * Returns true if all successful, false if not */ GarrisonHolder.prototype.UnloadAll = function(forced) { var entities = this.entities.slice(); return this.PerformEject(entities, forced); }; /** * Used to check if the garrisoning entity's health has fallen below * a certain limit after which all garrisoned units are unloaded */ GarrisonHolder.prototype.OnHealthChanged = function(msg) { if (!this.HasEnoughHealth() && this.entities.length) { var entities = this.entities.slice(); this.EjectOrKill(entities); } }; /** * Check if this entity has enough health to garrison units inside it */ GarrisonHolder.prototype.HasEnoughHealth = function() { var cmpHealth = Engine.QueryInterface(this.entity, IID_Health) var hitpoints = cmpHealth.GetHitpoints(); var maxHitpoints = cmpHealth.GetMaxHitpoints(); var ejectHitpoints = Math.floor((+this.template.EjectHealth) * maxHitpoints); return hitpoints > ejectHitpoints; }; /** * Called every second. Heals garrisoned units */ GarrisonHolder.prototype.HealTimeout = function(data) { if (this.entities.length == 0) { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(this.timer); this.timer = undefined; } else { for each (var entity in this.entities) { var cmpHealth = Engine.QueryInterface(entity, IID_Health); if (cmpHealth) { // We do not want to heal unhealable units if (!cmpHealth.IsUnhealable()) cmpHealth.Increase(this.GetHealRate()); } } var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); this.timer = cmpTimer.SetTimeout(this.entity, IID_GarrisonHolder, "HealTimeout", 1000, {}); } }; GarrisonHolder.prototype.UpdateGarrisonFlag = function() { var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); if (!cmpVisual) return; cmpVisual.SelectAnimation("garrisoned", true, 0, ""); // TODO: ought to extend ICmpVisual to let us just select variant // keywords without changing the animation too if (this.entities.length) cmpVisual.SelectAnimation("garrisoned", false, 1.0, ""); else cmpVisual.SelectAnimation("idle", false, 1.0, ""); }; /** * Cancel timer when destroyed */ GarrisonHolder.prototype.OnDestroy = function() { if (this.timer) { var cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer); cmpTimer.CancelTimer(this.timer); } }; /** * If a garrisoned entity is captured, or about to be killed (so its owner * changes to '-1'), remove it from the building so we only ever contain valid * entities */ GarrisonHolder.prototype.OnGlobalOwnershipChanged = function(msg) { // the ownership change may be on the garrisonholder if (this.entity == msg.entity) { var entities = []; for each (var entity in this.entities) { if (msg.to == -1 || !IsOwnedByMutualAllyOfEntity(this.entity, entity)) entities.push(entity); } this.EjectOrKill(entities); return; } // or on some of its garrisoned units var entityIndex = this.entities.indexOf(msg.entity); if (entityIndex != -1) { // If the entity is dead, remove it directly instead of ejecting the corpse var cmpHealth = Engine.QueryInterface(msg.entity, IID_Health); if (cmpHealth && cmpHealth.GetHitpoints() == 0) { this.entities.splice(entityIndex, 1); Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {}); this.UpdateGarrisonFlag(); } else if(!IsOwnedByMutualAllyOfEntity(this.entity, this.entities[entityIndex])) this.EjectOrKill([this.entities[entityIndex]]); } }; /** * Update list of garrisoned entities if one gets renamed (e.g. by promotion) */ GarrisonHolder.prototype.OnGlobalEntityRenamed = function(msg) { var entityIndex = this.entities.indexOf(msg.entity); if (entityIndex != -1) { this.entities[entityIndex] = msg.newentity; Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {}); } }; /** * Eject all foreign garrisoned entities which are no more allied */ GarrisonHolder.prototype.OnDiplomacyChanged = function() { var entities = [] for each (var entity in this.entities) { if (!IsOwnedByMutualAllyOfEntity(this.entity, entity)) entities.push(entity); } this.EjectOrKill(entities); }; /** * Eject or kill a garrisoned unit which can no more be garrisoned * (garrisonholder's health too small or ownership changed) */ GarrisonHolder.prototype.EjectOrKill = function(entities) { var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); // Eject the units which can be ejected (if not in world, it generally means this holder // is inside a holder which kills its entities, so do not eject) - if (cmpPosition.IsInWorld() && this.EjectEntitiesOnDestroy()) - this.PerformEject(entities, true); + if (cmpPosition.IsInWorld()) + { + var cmpGarrisonHolder = this; + var ejectables = entities.filter(function(ent) { return cmpGarrisonHolder.IsEjectable(ent) }); + if (ejectables.length) + this.PerformEject(ejectables, false); + } // And destroy all remaining entities for each (var entity in entities) { var entityIndex = this.entities.indexOf(entity); if (entityIndex == -1) continue; var cmpHealth = Engine.QueryInterface(entity, IID_Health); if (cmpHealth) cmpHealth.Kill(); this.entities.splice(entityIndex, 1); } Engine.PostMessage(this.entity, MT_GarrisonedUnitsChanged, {}); this.UpdateGarrisonFlag(); }; +/** + * Checks if an entity is ejectable on destroy if possible + */ +GarrisonHolder.prototype.IsEjectable = function(entity) +{ + var ejectableClasses = this.template.EjectClassesOnDestroy._string; + ejectableClasses = ejectableClasses ? ejectableClasses.split(/\s+/) : []; + var entityClasses = (Engine.QueryInterface(entity, IID_Identity)).GetClassesList(); + for each (var ejectableClass in ejectableClasses) + if (entityClasses.indexOf(ejectableClass) != -1) + return true; + + return false; +}; + Engine.RegisterComponentType(IID_GarrisonHolder, "GarrisonHolder", GarrisonHolder); Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/other/plane.xml (revision 14550) @@ -1,74 +1,74 @@ 0.0 100.0 25.0 48 24 60.0 0 2000 1.5 3 1 Infantry 1 0 Support Infantry 1 5 - false + 0.0 3.0 7.0 hele P-51 Mustang This may be anachronistic. A World War 2 American fighter plane. units/global_mustang.png 100 true true 1.0 true 60.0 50.0 40.0 25.0 5.0 10.0 1.0 2.0 50.0 15.0 true 100 units/global/plane.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen_wonder.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/athen_wonder.xml (revision 14550) @@ -1,29 +1,29 @@ 12.0 30 0.1 - true + Unit Support Infantry Cavalry 3 2 athen Neṓs Parthenos The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis. Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at a quick rate. structures/fndn_8x8.xml structures/hellenes/temple_epic.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart_wonder.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/cart_wonder.xml (revision 14550) @@ -1,28 +1,28 @@ 12.0 30 0.1 - true + Unit Support Infantry Cavalry 3 2 cart Temple of Ba'al Hammon Dating from the 2nd Century BC, the Mausoleum of Atban in northern Tunisia is over twenty metres high and was built by the inhabitants of Dougga for a Numidian prince. structures/fndn_8x8.xml structures/carthaginians/wonder.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/hele_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/hele_wonder.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/hele_wonder.xml (revision 14550) @@ -1,29 +1,29 @@ 12.0 30 0.1 - true + Unit Support Infantry Cavalry 3 2 athen Neṓs Parthenos The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis. Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at a quick rate. structures/fndn_8x8.xml structures/hellenes/temple_epic.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace_wonder.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/mace_wonder.xml (revision 14550) @@ -1,29 +1,29 @@ 12.0 30 0.1 - true + Unit Support Infantry Cavalry 3 2 athen Neṓs Parthenos The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis. Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at a quick rate. structures/fndn_8x8.xml structures/hellenes/temple_epic.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol_wonder.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/ptol_wonder.xml (revision 14550) @@ -1,28 +1,28 @@ 20.0 30 0.1 - true + Unit Support Infantry Cavalry 3 2 ptol Temple of Edfu The Temple of Edfu is an ancient Egyptian temple located on the west bank of the Nile in the city of Edfu which was known in Greco-Roman times as Apollonopolis Magna, after the chief god Horus-Apollo.The temple, dedicated to the falcon god Horus, was built in the Ptolemaic period between 237 and 57 BCE. In modern times, it is one of the best preserved temples of Egypt. structures/fndn_8x8.xml structures/ptolemies/wonder.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_tent.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_tent.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_tent.xml (revision 14550) @@ -1,60 +1,60 @@ own neutral enemy 30 50 5.0 5 0.1 - true + Unit Support Infantry 0 2 200 rome Tent Tabernāculum -Village A temporary shelter for soldiers. +5 population bonus. 1.0 -units/{civ}_support_female_citizen_house -unlock_females_house interface/complete/building/complete_universal.xml attack/destruction/building_collapse_large.xml 1 props/structures/romans/rome_tent.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_wonder.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/rome_wonder.xml (revision 14550) @@ -1,28 +1,28 @@ 12.0 30 0.1 - true + Unit Support Infantry Cavalry 3 2 rome Aedes Iovis Optimi Maximi . structures/fndn_4x6.xml structures/romans/temple_mars.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart_wonder.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart_wonder.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/spart_wonder.xml (revision 14550) @@ -1,29 +1,29 @@ 12.0 30 0.1 - true + Unit Support Infantry Cavalry 3 2 athen Neṓs Parthenos The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis. Bring glory to your civilization and add large tracts of land to your empire. Garrison up to 30 units to heal them at a quick rate. structures/fndn_8x8.xml structures/hellenes/temple_epic.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_civil_centre.xml (revision 14550) @@ -1,121 +1,121 @@ 5 15 3 0.0 20.0 0.0 60.0 10.0 75.0 1200 2000 1.5 3 1 own neutral CivilCentre CivilCentre 180 20 500 0 500 500 500 8.0 20 0.1 - true + Unit Support Infantry Cavalry 1 1 3000 rubble/rubble_stone_6x6 Civic Center Build to acquire large tracts of territory. Train citizens. Garrison: 20. Defensive CivCentre CivilCentre structures/civic_centre.png phase_town 200 0 50 50 50 0.8 units/{civ}_support_female_citizen phase_town_generic phase_city_generic food wood stone metal interface/complete/building/complete_civ_center.xml attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.xml interface/alarm/alarm_alert_0.xml interface/alarm/alarm_alert_1.xml interface/alarm/alarm_alert_2.xml true 140 65536 2 140 90 structures/fndn_6x6.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_house.xml (revision 14550) @@ -1,77 +1,77 @@ House 5 30 75 5.0 3 0 0.1 - true + Unit Support 1 800 rubble/rubble_stone_3x3 House Increase the population limit. Village House -ConquestCritical structures/house.png 100 0 10 10 0 1.0 units/{civ}_support_female_citizen_house unlock_females_house interface/complete/building/complete_house.xml attack/destruction/building_collapse_large.xml 6.0 0.6 8.0 false 20 65536 20 structures/fndn_3x3.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_civic_temple.xml (revision 14550) @@ -1,84 +1,84 @@ range 40 Temple 5 200 300 12.0 20 0.1 - true + Unit Support Infantry Cavalry 3 2 2000 rubble/rubble_stone_4x6 Temple Train healers. Garrison up to 20 units to heal them at a quick rate. Research healing and religious improvements. Heals nearby units, but slower than garrisoned units. Town Temple structures/temple.png phase_town 100 0 10 50 500 interface/complete/building/complete_temple.xml attack/destruction/building_collapse_large.xml false 40 65536 0.8 units/{civ}_support_healer_b pair_heal_01 pair_heal_02 pair_heal_03 40 structures/fndn_4x6.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_defense_tower.xml (revision 14550) @@ -1,105 +1,105 @@ 0.0 20.0 0.0 57.0 8.0 15 75.0 1200 2000 1.5 Organic Siege 0.5 1 1 DefenseTower DefenseTower 50 150 100 100 15.0 5 0.1 - true + Unit Support Infantry 0 2 1000 rubble/rubble_stone_2x2 Defense Tower Shoots arrows. Garrison to provide extra defense. DefenseTower Town Tower GarrisonTower -ConquestCritical structures/defense_tower.png phase_town 100 0 10 10 0 0.7 pair_tower_01 interface/complete/building/complete_tower.xml attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.xml 6.0 0.6 21.0 false 32 65536 80 structures/fndn_2x2.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_outpost.xml (revision 14550) @@ -1,101 +1,101 @@ 5 20 1 3 5 1 0.0 20.0 0.0 55.0 13.0 75.0 1200 2000 2.0 1 own neutral House 40 80 0 15.0 1 0.1 - true + Unit Support Infantry 0 2 800 rubble/rubble_stone_2x2 Outpost Build in neutral and friendly territories to scout areas of the map. Slowly loses health while in neutral territory. Outpost Village Defensive -ConquestCritical structures/outpost.png 100 0 8 0 0 0.7 vision_outpost decay_outpost interface/complete/building/complete_tower.xml attack/destruction/building_collapse_large.xml 6.0 0.6 18.0 2 80 props/special/palisade_rocks_outpost.xml structures/fndn_2x2.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_defense_wall_tower.xml (revision 14550) @@ -1,99 +1,99 @@ 0.0 20.0 0.0 50.0 8.0 75.0 1200 2000 1.5 Organic Siege 0.5 1 1 land-shore Wall 20 100 8.0 5 0.1 - true + Unit Support Infantry 0 2 5000 rubble/rubble_stone_wall_tower Wall Turret Shoots arrows. Garrison to defend a city wall against attackers. Defensive -ConquestCritical StoneWall Tower structures/tower.png phase_town 100 0 10 15 0 0.8 pair_walls_01 interface/complete/building/complete_tower.xml attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.xml 20.0 false 20 65536 60 structures/fndn_2x2.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_barracks.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_barracks.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_barracks.xml (revision 14550) @@ -1,74 +1,74 @@ Barracks 200 300 12.0 10 0.1 - true + Unit Infantry Cavalry 0 2 2000 rubble/rubble_stone_4x4 Barracks Train citizen-soldiers. Research training improvements. Garrison: 10. Village Barracks structures/barracks.png phase_village 100 0 30 10 0 0.8 pair_levy_01 training_conscription unlock_champion_units interface/complete/building/complete_barracks.xml attack/destruction/building_collapse_large.xml false 50 65536 32 structures/fndn_4x4.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_blacksmith.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_blacksmith.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_blacksmith.xml (revision 14550) @@ -1,79 +1,79 @@ Barracks 200 200 12.0 1 0.1 - true + Unit Infantry 0 2 2000 rubble/rubble_stone_4x4 Blacksmith Research weapons and armor improvements. Town Blacksmith structures/blacksmith.png phase_town 100 0 50 0 100 0.8 pair_inf_01 pair_inf_02 pair_inf_armor_01 pair_inf_armor_02 pair_inf_armor_03 pair_inf_armor_04 pair_cav_01 armor_hero_01 interface/complete/building/complete_blacksmith.xml attack/destruction/building_collapse_large.xml false 38 65536 32 structures/fndn_4x4.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_military_fortress.xml (revision 14550) @@ -1,105 +1,105 @@ 0.0 25.0 0.0 85.0 8.0 75.0 1200 2000 1.5 Organic Siege 0.5 3 1 Fortress Fortress 60 10 300 0 650 8.0 20 0.075 - true + Unit Support Infantry Cavalry Siege 0 6 4200 rubble/rubble_stone_6x6 Fortress Train heroes, champions, and siege weapons. Research siege weapon improvements. Garrison: 20. City Defensive GarrisonFortress Fortress structures/fortress.png phase_city 100 0 0 65 0 0.8 pair_champ_02 interface/complete/building/complete_fortress.xml attack/weapon/arrowfly.xml attack/destruction/building_collapse_large.xml false 100 65536 80 structures/fndn_6x6.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_corral.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_corral.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_resource_corral.xml (revision 14550) @@ -1,66 +1,66 @@ 50 100 5.0 10 0.1 - true + Unit Animal 1 2 500 rubble/rubble_2x4 Corral Raise herd animals for food. Task domestic animals here to gain a trickle of food or other bonus (Not yet implemented). Village Corral structures/corral.png 100 0 10 0 0 0.7 gaia/fauna_sheep gather_animals_stockbreeding interface/complete/building/complete_corral.xml attack/destruction/building_collapse_large.xml 20 structures/fndn_2x4.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_special.xml (revision 14550) @@ -1,69 +1,69 @@ 5 25 5 3 10 3 Special 200 0 0 200 200 8.0 5 0.1 - true + Unit Support Infantry Cavalry 1 2 2000 rubble/rubble_stone_6x6 Special Building This is a special building unique to a particular civilization. City SpecialBuilding .png phase_city interface/complete/building/complete_broch.xml attack/destruction/building_collapse_large.xml false 38 65536 structures/fndn_5x5.xml Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_bireme.xml (revision 14550) @@ -1,69 +1,69 @@ 0.0 35.0 0.0 45.0 10.0 60.0 2000 2000 1.5 2 1 Infantry Ranged 2 20 100 100 10.0 20 0 - false + Female Infantry Support Infantry Cavalry 1 10 true 800 Light Warship Classes: Mechanical Warship Light Ranged. Warship Light Bow Ranged phase_town attack/weapon/arrowfly.xml attack/impact/arrow_metal.xml 6.0 0.5 6.0 12.5 18.0 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml (revision 14550) @@ -1,63 +1,63 @@ 2 5 2 10.0 0.0 0.0 5.0 1000 Ship Structure 6.0 1 0 - false + Female Infantry Support Infantry 1 10 true Fishing Boat Classes: Mechanical Ship FishingBoat. Fish the waters for Food. Garrison a support or infantry unit inside to boost fishing rate. FishingBoat 6.0 1.0 2 actor/ship/boat_move.xml actor/ship/boat_move.xml false 8.5 24 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_merchant.xml (revision 14550) @@ -1,60 +1,60 @@ 2 5 2 0 100 15 0 - false + Female Infantry Support Infantry Cavalry 1 10 true 400 Merchantman Classes: Mechanical Ship Trader. Trade between docks. Garrison a Trader aboard for additional profit (+20% for each garrisoned). Gather profitable aquatic treasures. Trader phase_town 12.0 75 75 50 50 6.0 0.5 6.0 10.0 1.0 false 10.5 50 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_quinquereme.xml (revision 14550) @@ -1,81 +1,81 @@ 30.0 0.0 30.0 65.0 20.0 28.0 2000 5000 6.0 Circular 8 true 40.0 0.0 40.0 0 1 Catapult 5 45 250 200 8.0 50 0 - false + Female Infantry Support Infantry Cavalry Siege 1 10 true 2000 Heavy Warship Classes: Mechanical Warship Heavy Ranged Melee. Garrison with catapults to increase ranged fire power. Secondary Attack: Ramming. Warship Heavy Ranged phase_city attack/siege/ballist_attack.xml 6.0 0.5 6.0 14.5 20 110 Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ram.xml =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ram.xml (revision 14549) +++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_mechanical_siege_ram.xml (revision 14550) @@ -1,88 +1,88 @@ 2 30 10 0.0 0.0 80.0 6.5 1500 Gates 1.5 Organic Gates Structure 0.0 0.0 100.0 6.5 0.0 Gates 1.5 Organic Gates Structure 20 5 200 100 5 0.1 - true + Unit Support Infantry 1 2 200 Battering Ram Melee Ram Classes: Siege Melee Ram. Counters: 1.5x vs. Gates. Generally good vs. Buildings. Countered by: Melee Cavalry. Cannot attack organic units (but can attack other siege and ships). attack/siege/ram_move.xml attack/siege/ram_attack_order.xml attack/siege/ram_move.xml attack/siege/ram_attack.xml 4.0 0.5 6.5 11.0 48 Index: ps/trunk/source/simulation2/components/CCmpFootprint.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpFootprint.cpp (revision 14549) +++ ps/trunk/source/simulation2/components/CCmpFootprint.cpp (revision 14550) @@ -1,255 +1,393 @@ /* Copyright (C) 2013 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "simulation2/system/Component.h" #include "ICmpFootprint.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpObstructionManager.h" #include "simulation2/components/ICmpPathfinder.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpUnitMotion.h" #include "simulation2/MessageTypes.h" #include "graphics/Terrain.h" // For TERRAIN_TILE_SIZE #include "maths/FixedVector2D.h" class CCmpFootprint : public ICmpFootprint { public: static void ClassInit(CComponentManager& UNUSED(componentManager)) { } DEFAULT_COMPONENT_ALLOCATOR(Footprint) EShape m_Shape; entity_pos_t m_Size0; // width/radius entity_pos_t m_Size1; // height/radius entity_pos_t m_Height; static std::string GetSchema() { return "Approximation of the entity's shape, for collision detection and outline rendering. " "Shapes are flat horizontal squares or circles, extended vertically to a given height." "" "" "0.0" "" "" "" "0.0" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; } virtual void Init(const CParamNode& paramNode) { if (paramNode.GetChild("Square").IsOk()) { m_Shape = SQUARE; m_Size0 = paramNode.GetChild("Square").GetChild("@width").ToFixed(); m_Size1 = paramNode.GetChild("Square").GetChild("@depth").ToFixed(); } else if (paramNode.GetChild("Circle").IsOk()) { m_Shape = CIRCLE; m_Size0 = m_Size1 = paramNode.GetChild("Circle").GetChild("@radius").ToFixed(); } else { // Error - pick some default m_Shape = CIRCLE; m_Size0 = m_Size1 = entity_pos_t::FromInt(1); } m_Height = paramNode.GetChild("Height").ToFixed(); } virtual void Deinit() { } virtual void Serialize(ISerializer& UNUSED(serialize)) { // No dynamic state to serialize } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) { Init(paramNode); } virtual void GetShape(EShape& shape, entity_pos_t& size0, entity_pos_t& size1, entity_pos_t& height) { shape = m_Shape; size0 = m_Size0; size1 = m_Size1; height = m_Height; } virtual CFixedVector3D PickSpawnPoint(entity_id_t spawned) { // Try to find a free space around the building's footprint. // (Note that we use the footprint, not the obstruction shape - this might be a bit dodgy // because the footprint might be inside the obstruction, but it hopefully gives us a nicer // shape.) const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1)); CmpPtr cmpPosition(GetEntityHandle()); if (!cmpPosition || !cmpPosition->IsInWorld()) return error; CmpPtr cmpObstructionManager(GetSystemEntity()); if (!cmpObstructionManager) return error; entity_pos_t spawnedRadius; ICmpObstructionManager::tag_t spawnedTag; CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned); if (cmpSpawnedObstruction) { spawnedRadius = cmpSpawnedObstruction->GetUnitRadius(); spawnedTag = cmpSpawnedObstruction->GetObstruction(); } // else use zero radius // Get passability class from UnitMotion CmpPtr cmpUnitMotion(GetSimContext(), spawned); if (!cmpUnitMotion) return error; ICmpPathfinder::pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass(); CmpPtr cmpPathfinder(GetSystemEntity()); if (!cmpPathfinder) return error; - + CFixedVector2D initialPos = cmpPosition->GetPosition2D(); entity_angle_t initialAngle = cmpPosition->GetRotation().Y; // Max spawning distance in tiles const i32 maxSpawningDistance = 4; if (m_Shape == CIRCLE) { // Expand outwards from foundation for (i32 dist = 0; dist <= maxSpawningDistance; ++dist) { // The spawn point should be far enough from this footprint to fit the unit, plus a little gap entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist); entity_pos_t radius = m_Size0 + clearance; // Try equally-spaced points around the circle in alternating directions, starting from the front const i32 numPoints = 31 + 2*dist; for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2] { entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints); fixed s, c; sincos_approx(angle, s, c); CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius)); SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS) return pos; // this position is okay, so return it } } } else { fixed s, c; sincos_approx(initialAngle, s, c); // Expand outwards from foundation for (i32 dist = 0; dist <= maxSpawningDistance; ++dist) { // The spawn point should be far enough from this footprint to fit the unit, plus a little gap entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist); for (i32 edge = 0; edge < 4; ++edge) { // Try equally-spaced points along the edge in alternating directions, starting from the middle const i32 numPoints = 9 + 2*dist; // Compute the direction and length of the current edge CFixedVector2D dir; fixed sx, sy; switch (edge) { case 0: dir = CFixedVector2D(c, -s); sx = m_Size0; sy = m_Size1; break; case 1: dir = CFixedVector2D(-s, -c); sx = m_Size1; sy = m_Size0; break; case 2: dir = CFixedVector2D(s, c); sx = m_Size1; sy = m_Size0; break; case 3: dir = CFixedVector2D(-c, s); sx = m_Size0; sy = m_Size1; break; } CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance); dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1)); for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2] { CFixedVector2D pos (center + dir*i); SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS) return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it } } } } return error; } + + virtual CFixedVector3D PickSpawnPointBothPass(entity_id_t spawned) + { + // Try to find a free space inside and around this footprint + // at the intersection between the footprint passability and the unit passability. + // (useful for example for destroyed ships where the spawning point should be in the intersection + // of the unit and ship passabilities). + // As the overlap between these passabilities regions may be narrow, we need a small step (1 meter) + + const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1)); + + CmpPtr cmpPosition(GetEntityHandle()); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return error; + + CmpPtr cmpObstructionManager(GetSystemEntity()); + if (!cmpObstructionManager) + return error; + + entity_pos_t spawnedRadius; + ICmpObstructionManager::tag_t spawnedTag; + + CmpPtr cmpSpawnedObstruction(GetSimContext(), spawned); + if (cmpSpawnedObstruction) + { + spawnedRadius = cmpSpawnedObstruction->GetUnitRadius(); + spawnedTag = cmpSpawnedObstruction->GetObstruction(); + } + // else use zero radius + + // Get passability class from UnitMotion + CmpPtr cmpUnitMotion(GetSimContext(), spawned); + if (!cmpUnitMotion) + return error; + + ICmpPathfinder::pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass(); + CmpPtr cmpPathfinder(GetSystemEntity()); + if (!cmpPathfinder) + return error; + + // Get the Footprint entity passability + CmpPtr cmpEntityMotion(GetEntityHandle()); + if (!cmpEntityMotion) + return error; + ICmpPathfinder::pass_class_t entityPass = cmpEntityMotion->GetPassabilityClass(); + + CFixedVector2D initialPos = cmpPosition->GetPosition2D(); + entity_angle_t initialAngle = cmpPosition->GetRotation().Y; + + // Max spawning distance + 1 (in meters) + const i32 maxSpawningDistance = 13; + + if (m_Shape == CIRCLE) + { + // Expand outwards from foundation with a fixed step of 1 meter + for (i32 dist = 0; dist <= maxSpawningDistance; ++dist) + { + // The spawn point should be far enough from this footprint to fit the unit, plus a little gap + entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(1+dist); + entity_pos_t radius = m_Size0 + clearance; + + // Try equally-spaced points around the circle in alternating directions, starting from the front + const i32 numPoints = 31 + 2*dist; + for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2] + { + entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints); + + fixed s, c; + sincos_approx(angle, s, c); + + CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius)); + + SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity + if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS && + cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, entityPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS) + return pos; // this position is okay, so return it + } + } + } + else + { + fixed s, c; + sincos_approx(initialAngle, s, c); + + // Expand outwards from foundation with a fixed step of 1 meter + for (i32 dist = 0; dist <= maxSpawningDistance; ++dist) + { + // The spawn point should be far enough from this footprint to fit the unit, plus a little gap + entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(1+dist); + + for (i32 edge = 0; edge < 4; ++edge) + { + // Compute the direction and length of the current edge + CFixedVector2D dir; + fixed sx, sy; + switch (edge) + { + case 0: + dir = CFixedVector2D(c, -s); + sx = m_Size0; + sy = m_Size1; + break; + case 1: + dir = CFixedVector2D(-s, -c); + sx = m_Size1; + sy = m_Size0; + break; + case 2: + dir = CFixedVector2D(s, c); + sx = m_Size1; + sy = m_Size0; + break; + case 3: + dir = CFixedVector2D(-c, s); + sx = m_Size0; + sy = m_Size1; + break; + } + sx = sx/2 + clearance; + sy = sy/2 + clearance; + // Try equally-spaced (1 meter) points along the edge in alternating directions, starting from the middle + i32 numPoints = 1 + 2*sx.ToInt_RoundToNearest(); + CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy); + for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2] + { + CFixedVector2D pos (center + dir*i); + + SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity + if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS && + cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, entityPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS) + return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it + } + } + } + } + + return error; + } }; REGISTER_COMPONENT_TYPE(Footprint) Index: ps/trunk/source/simulation2/components/ICmpFootprint.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpFootprint.h (revision 14549) +++ ps/trunk/source/simulation2/components/ICmpFootprint.h (revision 14550) @@ -1,70 +1,77 @@ /* Copyright (C) 2010 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_ICMPFOOTPRINT #define INCLUDED_ICMPFOOTPRINT #include "simulation2/system/Interface.h" #include "simulation2/helpers/Position.h" class CFixedVector3D; /** * Footprints - an approximation of the entity's shape, used for collision detection and for * rendering selection outlines. * A footprint is either a circle (of some radius) or square (of some width and depth (actually * it's a rectangle)), horizontally aligned, extruded to a given height. */ class ICmpFootprint : public IComponent { public: enum EShape { CIRCLE, SQUARE }; /** * Return the shape of this footprint. * Shapes are horizontal circles or squares, extended vertically upwards to make cylinders or boxes. * @param[out] shape either CIRCLE or SQUARE * @param[out] size0 if CIRCLE then radius, else width (size in X axis) * @param[out] size1 if CIRCLE then radius, else depth (size in Z axis) * @param[out] height size in Y axis */ virtual void GetShape(EShape& shape, entity_pos_t& size0, entity_pos_t& size1, entity_pos_t& height) = 0; /** * GetShape wrapper for script calls. * Returns { "type": "circle", "radius": 5.0, "height": 1.0 } * or { "type": "square", "width": 5.0, "depth": 5.0, "height": 1.0 } */ CScriptVal GetShape_wrapper(); /** * Pick a sensible position to place a newly-spawned entity near this footprint, * such that it won't be in an invalid (obstructed) location regardless of the spawned unit's * orientation. * @return the X and Z coordinates of the spawn point, with Y = 0; or the special value (-1, -1, -1) if there's no space */ virtual CFixedVector3D PickSpawnPoint(entity_id_t spawned) = 0; + /** + * Pick a sensible position to place a newly-spawned entity near this footprint, + * at the intersection between the footprint passability and the entity one. + * @return the X and Z coordinates of the spawn point, with Y = 0; or the special value (-1, -1, -1) if there's no space + */ + virtual CFixedVector3D PickSpawnPointBothPass(entity_id_t spawned) = 0; + DECLARE_INTERFACE_TYPE(Footprint) }; #endif // INCLUDED_ICMPFOOTPRINT