Index: ps/trunk/binaries/data/mods/public/maps/scenario.rnc =================================================================== --- ps/trunk/binaries/data/mods/public/maps/scenario.rnc +++ ps/trunk/binaries/data/mods/public/maps/scenario.rnc @@ -108,6 +108,12 @@ attribute uid { xsd:positiveInteger } & } & } & + element Turrets { + element Turret { + attribute turret { text } & + attribute uid { xsd:positiveInteger } & + } & + } & element Actor { attribute seed { xsd:integer } }? Index: ps/trunk/binaries/data/mods/public/maps/scenario.rng =================================================================== --- ps/trunk/binaries/data/mods/public/maps/scenario.rng +++ ps/trunk/binaries/data/mods/public/maps/scenario.rng @@ -268,6 +268,20 @@ + + + + + + + + + + + + + + Index: ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js @@ -13,6 +13,7 @@ let points = this.template.TurretPoints; for (let point in points) this.turretPoints.push({ + "name": point, "offset": { "x": +points[point].X, "y": +points[point].Y, @@ -121,6 +122,16 @@ } /** + * @param {number} entity - The entityID of the entity. + * @param {String} turretName - The name of the turret point to occupy. + * @return {boolean} - Whether the occupation has succeeded. + */ + OccupyNamedTurret(entity, turretName) + { + return this.OccupyTurret(entity, this.turretPoints.find(turret => turret.name == turretName)); + } + + /** * Remove the entity from a turret. * @param {number} entity - The specific entity to eject. * @param {Object} turret - Optionally the turret to abandon. @@ -189,6 +200,44 @@ } /** + * @param {number} entity - The entity's id. + * @return {Object} - The turret this entity is positioned on, if applicable. + */ + GetOccupiedTurretName(entity) + { + return this.GetOccupiedTurret(entity).name || ""; + } + + /** + * @return {number[]} - The turretted entityIDs. + */ + GetEntities() + { + let entities = []; + for (let turretPoint of this.turretPoints) + if (turretPoint.entity) + entities.push(turretPoint.entity); + return entities; + } + + /** + * Sets an init turret, present from game start. (E.g. set in Atlas.) + * @param {String} turretName - The name of the turret point to be used. + * @param {number} entity - The entity-ID to be placed. + */ + SetInitEntity(turretName, entity) + { + if (!this.initTurrets) + this.initTurrets = new Map(); + + if (this.initTurrets.has(turretName)) + warn("The turret position " + turretName + " of entity " + + this.entity + " is already set! Overwriting."); + + this.initTurrets.set(turretName, entity); + } + + /** * We process EntityRenamed here because we need to be sure that we receive * it after it is processed by GarrisonHolder.js. * ToDo: Make this not needed by fully separating TurretHolder from GarrisonHolder. @@ -221,6 +270,29 @@ for (let entity of msg.added) this.OccupyTurret(entity); } + + /** + * Initialise the turreted units. + * Really ugly, but because GarrisonHolder is processed earlier, and also turrets + * entities on init, we can find an entity that already is present. + * In that case we reject and occupy. + */ + OnGlobalInitGame(msg) + { + if (!this.initTurrets) + return; + + for (let [turretPointName, entity] of this.initTurrets) + { + if (this.OccupiesTurret(entity)) + this.LeaveTurret(entity); + if (!this.OccupyNamedTurret(entity, turretPointName)) + warn("Entity " + entity + " could not occupy the turret point " + + turretPointName + " of turret holder " + this.entity + "."); + } + + delete this.initTurrets; + } } TurretHolder.prototype.Schema = Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js +++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js @@ -1,5 +1,3 @@ -Engine.RegisterInterface("TurretHolder"); - /** * Message of the form { "added": number[], "removed": number[] } * sent from the TurretHolder component to the current entity whenever the turrets change. Index: ps/trunk/source/graphics/MapReader.cpp =================================================================== --- ps/trunk/source/graphics/MapReader.cpp +++ ps/trunk/source/graphics/MapReader.cpp @@ -48,6 +48,7 @@ #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTerrain.h" +#include "simulation2/components/ICmpTurretHolder.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" @@ -418,12 +419,14 @@ int el_template, el_player; int el_position, el_orientation, el_obstruction; int el_garrison; + int el_turrets; int el_actor; int at_x, at_y, at_z; int at_group, at_group2; int at_angle; int at_uid; int at_seed; + int at_turret; XMBElementList nodes; // children of root @@ -468,6 +471,7 @@ EL(player); EL(position); EL(garrison); + EL(turrets); EL(orientation); EL(obstruction); EL(actor); @@ -476,6 +480,7 @@ AT(angle); AT(uid); AT(seed); + AT(turret); #undef AT #undef EL @@ -950,6 +955,7 @@ CStrW TemplateName; int PlayerID = 0; std::vector Garrison; + std::vector > Turrets; CFixedVector3D Position; CFixedVector3D Orientation; long Seed = -1; @@ -1009,6 +1015,20 @@ Garrison.push_back(attrs.GetNamedItem(at_uid).ToInt()); } } + // + else if (element_name == el_turrets) + { + XMBElementList turrets = setting.GetChildNodes(); + Turrets.reserve(turrets.size()); + for (const XMBElement& turretPoint : turrets) + { + XMBAttributeList attrs = turretPoint.GetAttributes(); + Turrets.push_back(std::make_pair( + attrs.GetNamedItem(at_turret), + attrs.GetNamedItem(at_uid).ToInt() + )); + } + } // else if (element_name == el_actor) { @@ -1054,6 +1074,16 @@ Garrison.clear(); } + if (!Turrets.empty()) + { + CmpPtr cmpTurretHolder(sim, ent); + if (cmpTurretHolder) + cmpTurretHolder->SetInitEntities(Turrets); + else + LOGERROR("CXMLMapReader::ReadEntities() entity '%d' of player '%d' has no TurretHolder component and thus cannot use turrets.", ent, PlayerID); + Turrets.clear(); + } + CmpPtr cmpObstruction(sim, ent); if (cmpObstruction) { Index: ps/trunk/source/graphics/MapWriter.cpp =================================================================== --- ps/trunk/source/graphics/MapWriter.cpp +++ ps/trunk/source/graphics/MapWriter.cpp @@ -44,6 +44,7 @@ #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpTemplateManager.h" +#include "simulation2/components/ICmpTurretHolder.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpWaterManager.h" @@ -353,7 +354,22 @@ { XMLWriter_Element garrisonedEntityTag(xmlMapFile, "GarrisonedEntity"); garrisonedEntityTag.Attribute("uid", static_cast(garr_ent_id)); - // ToDo: We can store turret position as well. + } + } + } + + CmpPtr cmpTurretHolder(sim, ent); + if (cmpTurretHolder) + { + std::vector > turrets = cmpTurretHolder->GetTurrets(); + if (!turrets.empty()) + { + XMLWriter_Element turretTag(xmlMapFile, "Turrets"); + for (const std::pair& turret : turrets) + { + XMLWriter_Element turretedEntityTag(xmlMapFile, "Turret"); + turretedEntityTag.Attribute("turret", turret.first); + turretedEntityTag.Attribute("uid", static_cast(turret.second)); } } } Index: ps/trunk/source/simulation2/TypeList.h =================================================================== --- ps/trunk/source/simulation2/TypeList.h +++ ps/trunk/source/simulation2/TypeList.h @@ -189,6 +189,9 @@ INTERFACE(TerritoryManager) COMPONENT(TerritoryManager) +INTERFACE(TurretHolder) +COMPONENT(TurretHolderScripted) + INTERFACE(UnitMotion) COMPONENT(UnitMotion) // must be after Obstruction COMPONENT(UnitMotionScripted) Index: ps/trunk/source/simulation2/components/ICmpTurretHolder.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpTurretHolder.h +++ ps/trunk/source/simulation2/components/ICmpTurretHolder.h @@ -0,0 +1,42 @@ +/* Copyright (C) 2020 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_ICMPTURRETHOLDER +#define INCLUDED_ICMPTURRETHOLDER + +#include "simulation2/system/Interface.h" + +#include + +class ICmpTurretHolder : public IComponent +{ +public: + /** + * Returns the correlation between garrisoned turrets (their ID) and which + * turret point they occupy (name). + */ + virtual std::vector > GetTurrets() const = 0; + + /** + * Correlation between entities (ID) and the turret point they ought to occupy (name). + */ + virtual void SetInitEntities(const std::vector > entities) = 0; + + DECLARE_INTERFACE_TYPE(TurretHolder) +}; + +#endif // INCLUDED_ICMPTURRETHOLDER Index: ps/trunk/source/simulation2/components/ICmpTurretHolder.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpTurretHolder.cpp +++ ps/trunk/source/simulation2/components/ICmpTurretHolder.cpp @@ -0,0 +1,60 @@ +/* Copyright (C) 2020 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 "ICmpTurretHolder.h" + +#include "simulation2/scripting/ScriptComponent.h" +#include "simulation2/system/InterfaceScripted.h" + +BEGIN_INTERFACE_WRAPPER(TurretHolder) +END_INTERFACE_WRAPPER(TurretHolder) + +class CCmpTurretHolderScripted : public ICmpTurretHolder +{ +public: + DEFAULT_SCRIPT_WRAPPER(TurretHolderScripted) + + /** + * @return - Correlation between garrisoned turrets (their ID) and which + * turret point they occupy (name). + */ + virtual std::vector > GetTurrets() const + { + std::vector > turrets; + std::vector entities = m_Script.Call >("GetEntities"); + for (entity_id_t entity : entities) + turrets.push_back(std::make_pair( + m_Script.Call("GetOccupiedTurretName", entity), + entity + )); + + return turrets; + } + + /** + * Correlation between entities (ID) and the turret point they ought to occupy (name). + */ + virtual void SetInitEntities(std::vector > entities) + { + for (const std::pair& p : entities) + m_Script.CallVoid("SetInitEntity", p.first, p.second); + } +}; + +REGISTER_COMPONENT_SCRIPT_WRAPPER(TurretHolderScripted)