Index: ps/trunk/binaries/data/mods/public/maps/scenario.rnc
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scenario.rnc (revision 24160)
+++ ps/trunk/binaries/data/mods/public/maps/scenario.rnc (revision 24161)
@@ -1,135 +1,141 @@
namespace a = "http://relaxng.org/ns/compatibility/annotations/1.0"
##
# NOTE: To modify this Relax NG grammar, edit the Relax NG Compact (.rnc) file
# and use a converter tool like trang to generate the Relax NG XML (.rng) file
##
start = Scenario
##
# Defines #
##
color_rgb =
attribute r { xsd:decimal { minInclusive = "0" maxInclusive = "1" } },
attribute g { xsd:decimal { minInclusive = "0" maxInclusive = "1" } },
attribute b { xsd:decimal { minInclusive = "0" maxInclusive = "1" } }
suncolor_rgb = # RGB can be > 1 due to overbrightness
attribute r { xsd:decimal { minInclusive = "0" } },
attribute g { xsd:decimal { minInclusive = "0" } },
attribute b { xsd:decimal { minInclusive = "0" } }
pos_xyz =
attribute x { xsd:float },
attribute y { xsd:float },
attribute z { xsd:float }
pos_xz =
attribute x { xsd:decimal },
attribute z { xsd:decimal }
angle = attribute angle { xsd:float }
##
# Scenario #
##
# TODO: bounds are not documented for many of these
Scenario = element Scenario {
attribute version { xsd:positiveInteger } &
element Environment {
element SkySet { text } &
element SunColor { suncolor_rgb } &
element SunElevation { angle } &
element SunRotation { angle } &
element TerrainAmbientColor { color_rgb } &
element UnitsAmbientColor { color_rgb } &
element Fog {
element FogFactor {
xsd:decimal { minInclusive = "0" } # TODO: what is the max?
} &
element FogThickness {
xsd:decimal { minInclusive = "0" } # TODO: what is the max?
} &
element FogColor { color_rgb }
}? &
element Water {
element WaterBody {
element Type { text } & # Not implemented
element Color { color_rgb } &
element Height { xsd:decimal } &
element Waviness { xsd:decimal } &
element Murkiness { xsd:decimal } &
element Tint { color_rgb } &
element WindAngle { xsd:decimal }?
}
} &
element Postproc {
element Brightness { xsd:decimal },
element Contrast { xsd:decimal },
element Saturation { xsd:decimal },
element Bloom { xsd:decimal },
element PostEffect { text }
}?
} &
element Terrain {
attribute patches { xsd:positiveInteger }?,
attribute texture { text }?,
attribute priority { xsd:nonNegativeInteger }?,
attribute height { xsd:positiveInteger }?
}? &
element Script {
text
}? &
element Camera { # Camera can have weird float values
element Position { pos_xyz },
element Rotation { angle },
element Declination { angle }
}? &
element ScriptSettings {
text
} &
element Entities {
element Entity {
attribute uid { xsd:positiveInteger } &
element Template { text } &
element Player { xsd:nonNegativeInteger }? &
element Position {
pos_xz
} &
element Orientation {
attribute y { xsd:decimal }
} &
element Obstruction {
attribute group { xsd:positiveInteger },
attribute group2 { xsd:positiveInteger }?
}? &
element Garrison {
element GarrisonedEntity {
attribute uid { xsd:positiveInteger } &
} &
} &
+ element Turrets {
+ element Turret {
+ attribute turret { text } &
+ attribute uid { xsd:positiveInteger } &
+ } &
+ } &
element Actor {
attribute seed { xsd:integer }
}?
}*
}? &
element Paths {
element Path {
attribute name { text } &
attribute timescale { xsd:decimal } &
attribute orientation { text } &
attribute mode { text } &
attribute style { text } &
element Node {
attribute deltatime {
xsd:decimal { minInclusive = "0" }
} &
element Position { pos_xyz }? &
element Target { pos_xyz }?
}*
}*
}? &
element Triggers { # Unused
empty
}?
}
Index: ps/trunk/binaries/data/mods/public/maps/scenario.rng
===================================================================
--- ps/trunk/binaries/data/mods/public/maps/scenario.rng (revision 24160)
+++ ps/trunk/binaries/data/mods/public/maps/scenario.rng (revision 24161)
@@ -1,329 +1,343 @@
0
1
0
1
0
1
0
0
0
0
0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
0
Index: ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js (revision 24160)
+++ ps/trunk/binaries/data/mods/public/simulation/components/TurretHolder.js (revision 24161)
@@ -1,259 +1,331 @@
/**
* This class holds the functions regarding entities being visible on
* another entity, but tied to their parents location.
* Currently renaming and changing ownership are still managed by GarrisonHolder.js,
* but in the future these components should be independent.
*/
class TurretHolder
{
Init()
{
this.turretPoints = [];
let points = this.template.TurretPoints;
for (let point in points)
this.turretPoints.push({
+ "name": point,
"offset": {
"x": +points[point].X,
"y": +points[point].Y,
"z": +points[point].Z
},
"allowedClasses": points[point].AllowedClasses,
"angle": points[point].Angle ? +points[point].Angle * Math.PI / 180 : null,
"entity": null
});
}
/**
* @return {Object[]} - An array of the turret points this entity has.
*/
GetTurretPoints()
{
return this.turretPoints;
}
/**
* @param {number} entity - The entity to check for.
* @param {Object} turretPoint - The turret point to use.
*
* @return {boolean} - Whether the entity is allowed to occupy the specified turret point.
*/
AllowedToOccupyTurret(entity, turretPoint)
{
if (!turretPoint || turretPoint.entity)
return false;
if (!IsOwnedByMutualAllyOfEntity(entity, this.entity))
return false;
if (!turretPoint.allowedClasses)
return true;
let cmpIdentity = Engine.QueryInterface(entity, IID_Identity);
return cmpIdentity && MatchesClassList(cmpIdentity.GetClassesList(), turretPoint.allowedClasses._string);
}
/**
* Occupy a turret point with the given entity.
* @param {number} entity - The entity to use.
* @param {Object} requestedTurretPoint - Optionally the specific turret point to occupy.
*
* @return {boolean} - Whether the occupation was successful.
*/
OccupyTurret(entity, requestedTurretPoint)
{
let cmpPositionOccupant = Engine.QueryInterface(entity, IID_Position);
if (!cmpPositionOccupant)
return false;
let cmpPositionSelf = Engine.QueryInterface(this.entity, IID_Position);
if (!cmpPositionSelf)
return false;
if (this.OccupiesTurret(entity))
return false;
let turretPoint;
if (requestedTurretPoint)
{
if (this.AllowedToOccupyTurret(entity, requestedTurretPoint))
turretPoint = requestedTurretPoint;
}
else
turretPoint = this.turretPoints.find(turret => !turret.entity && this.AllowedToOccupyTurret(entity, turret));
if (!turretPoint)
return false;
turretPoint.entity = entity;
// Angle of turrets:
// Renamed entities (turretPoint != undefined) should keep their angle.
// Otherwise if an angle is given in the turretPoint, use it.
// If no such angle given (usually walls for which outside/inside not well defined), we keep
// the current angle as it was used for garrisoning and thus quite often was from inside to
// outside, except when garrisoning from outWorld where we take as default PI.
if (!turretPoint && turretPoint.angle != null)
cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + turretPoint.angle);
else if (!turretPoint && !cmpPosition.IsInWorld())
cmpPositionOccupant.SetYRotation(cmpPositionSelf.GetRotation().y + Math.PI);
cmpPositionOccupant.SetTurretParent(this.entity, turretPoint.offset);
let cmpUnitMotion = Engine.QueryInterface(entity, IID_UnitMotion);
if (cmpUnitMotion)
cmpUnitMotion.SetFacePointAfterMove(false);
let cmpUnitAI = Engine.QueryInterface(entity, IID_UnitAI);
if (cmpUnitAI)
cmpUnitAI.SetTurretStance();
// Remove the unit's obstruction to avoid interfering with pathing.
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
if (cmpObstruction)
cmpObstruction.SetActive(false);
Engine.PostMessage(this.entity, MT_TurretsChanged, {
"added": [entity],
"removed": []
});
return true;
}
/**
+ * @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.
*
* @return {boolean} - Whether the entity was occupying a/the turret before.
*/
LeaveTurret(entity, requestedTurretPoint)
{
let turretPoint;
if (requestedTurretPoint)
{
if (requestedTurretPoint.entity == entity)
turretPoint = requestedTurretPoint;
}
else
turretPoint = this.turretPoints.find(turret => turret.entity == entity);
if (!turretPoint)
return false;
let cmpPositionEntity = Engine.QueryInterface(entity, IID_Position);
cmpPositionEntity.SetTurretParent(INVALID_ENTITY, new Vector3D());
let cmpUnitMotionEntity = Engine.QueryInterface(entity, IID_UnitMotion);
if (cmpUnitMotionEntity)
cmpUnitMotionEntity.SetFacePointAfterMove(true);
let cmpUnitAIEntity = Engine.QueryInterface(entity, IID_UnitAI);
if (cmpUnitAIEntity)
cmpUnitAIEntity.ResetTurretStance();
turretPoint.entity = null;
// Reset the obstruction flags to template defaults.
let cmpObstruction = Engine.QueryInterface(entity, IID_Obstruction);
if (cmpObstruction)
cmpObstruction.SetActive(true);
Engine.PostMessage(this.entity, MT_TurretsChanged, {
"added": [],
"removed": [entity]
});
return true;
}
/**
* @param {number} entity - The entity's id.
* @param {Object} turret - Optionally the turret to check.
*
* @return {boolean} - Whether the entity is positioned on a turret of this entity.
*/
OccupiesTurret(entity, requestedTurretPoint)
{
return requestedTurretPoint ? requestedTurretPoint.entity == entity :
this.turretPoints.some(turretPoint => turretPoint.entity == entity);
}
/**
* @param {number} entity - The entity's id.
* @return {Object} - The turret this entity is positioned on, if applicable.
*/
GetOccupiedTurret(entity)
{
return this.turretPoints.find(turretPoint => turretPoint.entity == entity);
}
/**
+ * @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.
* That means an entity with TurretHolder should not need a GarrisonHolder
* for e.g. the garrisoning logic.
*
* @param {number} from - The entity to substitute.
* @param {number} to - The entity to subtitute with.
*/
SwapEntities(from, to)
{
let turretPoint = this.GetOccupiedTurret(from);
if (turretPoint)
this.LeaveTurret(from, turretPoint);
let cmpGarrisonHolder = Engine.QueryInterface(this.entity, IID_GarrisonHolder);
if (cmpGarrisonHolder && cmpGarrisonHolder.IsGarrisoned(to))
this.OccupyTurret(to, turretPoint);
}
OnGarrisonedUnitsChanged(msg)
{
// Ignore renaming for that is handled seperately
// (i.e. called directly from GarrisonHolder.js).
if (msg.renamed)
return;
for (let entity of msg.removed)
this.LeaveTurret(entity);
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 =
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"tokens" +
"" +
"" +
"" +
""+
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"" +
"";
Engine.RegisterComponentType(IID_TurretHolder, "TurretHolder", TurretHolder);
Index: ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js (revision 24160)
+++ ps/trunk/binaries/data/mods/public/simulation/components/interfaces/TurretHolder.js (revision 24161)
@@ -1,7 +1,5 @@
-Engine.RegisterInterface("TurretHolder");
-
/**
* Message of the form { "added": number[], "removed": number[] }
* sent from the TurretHolder component to the current entity whenever the turrets change.
*/
Engine.RegisterMessageType("TurretsChanged");
Index: ps/trunk/source/graphics/MapReader.cpp
===================================================================
--- ps/trunk/source/graphics/MapReader.cpp (revision 24160)
+++ ps/trunk/source/graphics/MapReader.cpp (revision 24161)
@@ -1,1585 +1,1615 @@
/* 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 "MapReader.h"
#include "graphics/Camera.h"
#include "graphics/CinemaManager.h"
#include "graphics/Entity.h"
#include "graphics/GameView.h"
#include "graphics/MapGenerator.h"
#include "graphics/Patch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "lib/timer.h"
#include "lib/external_libraries/libsdl.h"
#include "maths/MathUtil.h"
#include "ps/CLogger.h"
#include "ps/Loader.h"
#include "ps/LoaderThunks.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/PostprocManager.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpCinemaManager.h"
#include "simulation2/components/ICmpGarrisonHolder.h"
#include "simulation2/components/ICmpObstruction.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayer.h"
#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"
#include
CMapReader::CMapReader()
: xml_reader(0), m_PatchesPerSide(0), m_MapGen(0)
{
cur_terrain_tex = 0; // important - resets generator state
}
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
void CMapReader::LoadMap(const VfsPath& pathname, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_,
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities)
{
pTerrain = pTerrain_;
pLightEnv = pLightEnv_;
pGameView = pGameView_;
pWaterMan = pWaterMan_;
pSkyMan = pSkyMan_;
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pPostproc = pPostproc_;
pSimulation2 = pSimulation2_;
pSimContext = pSimContext_;
m_PlayerID = playerID_;
m_SkipEntities = skipEntities;
m_StartingCameraTarget = INVALID_ENTITY;
m_ScriptSettings.init(rt, settings);
filename_xml = pathname.ChangeExtension(L".xml");
// In some cases (particularly tests) we don't want to bother storing a large
// mostly-empty .pmp file, so we let the XML file specify basic terrain instead.
// If there's an .xml file and no .pmp, then we're probably in this XML-only mode
only_xml = false;
if (!VfsFileExists(pathname) && VfsFileExists(filename_xml))
{
only_xml = true;
}
file_format_version = CMapIO::FILE_VERSION; // default if there's no .pmp
if (!only_xml)
{
// [25ms]
unpacker.Read(pathname, "PSMP");
file_format_version = unpacker.GetVersion();
}
// check oldest supported version
if (file_format_version < FILE_READ_VERSION)
throw PSERROR_Game_World_MapLoadFailed("Could not load terrain file - too old version!");
// delete all existing entities
if (pSimulation2)
pSimulation2->ResetState();
// reset post effects
if (pPostproc)
pPostproc->SetPostEffect(L"default");
// load map or script settings script
if (settings.isUndefined())
RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 50);
else
RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
// load player settings script (must be done before reading map)
RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
// unpack the data
if (!only_xml)
RegMemFun(this, &CMapReader::UnpackMap, L"CMapReader::UnpackMap", 1200);
// read the corresponding XML file
RegMemFun(this, &CMapReader::ReadXML, L"CMapReader::ReadXML", 50);
// apply terrain data to the world
RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
// read entities
RegMemFun(this, &CMapReader::ReadXMLEntities, L"CMapReader::ReadXMLEntities", 5800);
// apply misc data to the world
RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
// load map settings script (must be done after reading map)
RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
}
// LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
void CMapReader::LoadRandomMap(const CStrW& scriptFile, JSRuntime* rt, JS::HandleValue settings, CTerrain *pTerrain_,
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_, CPostprocManager* pPostproc_,
CSimulation2 *pSimulation2_, int playerID_)
{
m_ScriptFile = scriptFile;
pSimulation2 = pSimulation2_;
pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL;
m_ScriptSettings.init(rt, settings);
pTerrain = pTerrain_;
pLightEnv = pLightEnv_;
pGameView = pGameView_;
pWaterMan = pWaterMan_;
pSkyMan = pSkyMan_;
pCinema = pCinema_;
pTrigMan = pTrigMan_;
pPostproc = pPostproc_;
m_PlayerID = playerID_;
m_SkipEntities = false;
m_StartingCameraTarget = INVALID_ENTITY;
// delete all existing entities
if (pSimulation2)
pSimulation2->ResetState();
only_xml = false;
// copy random map settings (before entity creation)
RegMemFun(this, &CMapReader::LoadRMSettings, L"CMapReader::LoadRMSettings", 50);
// load player settings script (must be done before reading map)
RegMemFun(this, &CMapReader::LoadPlayerSettings, L"CMapReader::LoadPlayerSettings", 50);
// load map generator with random map script
RegMemFun(this, &CMapReader::GenerateMap, L"CMapReader::GenerateMap", 20000);
// parse RMS results into terrain structure
RegMemFun(this, &CMapReader::ParseTerrain, L"CMapReader::ParseTerrain", 500);
// parse RMS results into environment settings
RegMemFun(this, &CMapReader::ParseEnvironment, L"CMapReader::ParseEnvironment", 5);
// parse RMS results into camera settings
RegMemFun(this, &CMapReader::ParseCamera, L"CMapReader::ParseCamera", 5);
// apply terrain data to the world
RegMemFun(this, &CMapReader::ApplyTerrainData, L"CMapReader::ApplyTerrainData", 5);
// parse RMS results into entities
RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000);
// apply misc data to the world
RegMemFun(this, &CMapReader::ApplyData, L"CMapReader::ApplyData", 5);
// load map settings script (must be done after reading map)
RegMemFun(this, &CMapReader::LoadMapSettings, L"CMapReader::LoadMapSettings", 5);
}
// UnpackMap: unpack the given data from the raw data stream into local variables
int CMapReader::UnpackMap()
{
return UnpackTerrain();
}
// UnpackTerrain: unpack the terrain from the end of the input data stream
// - data: map size, heightmap, list of textures used by map, texture tile assignments
int CMapReader::UnpackTerrain()
{
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 200e-3;
// first call to generator (this is skipped after first call,
// i.e. when the loop below was interrupted)
if (cur_terrain_tex == 0)
{
m_PatchesPerSide = (ssize_t)unpacker.UnpackSize();
// unpack heightmap [600us]
size_t verticesPerSide = m_PatchesPerSide*PATCH_SIZE+1;
m_Heightmap.resize(SQR(verticesPerSide));
unpacker.UnpackRaw(&m_Heightmap[0], SQR(verticesPerSide)*sizeof(u16));
// unpack # textures
num_terrain_tex = unpacker.UnpackSize();
m_TerrainTextures.reserve(num_terrain_tex);
}
// unpack texture names; find handle for each texture.
// interruptible.
while (cur_terrain_tex < num_terrain_tex)
{
CStr texturename;
unpacker.UnpackString(texturename);
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texturename);
m_TerrainTextures.push_back(texentry);
cur_terrain_tex++;
LDR_CHECK_TIMEOUT(cur_terrain_tex, num_terrain_tex);
}
// unpack tile data [3ms]
ssize_t tilesPerSide = m_PatchesPerSide*PATCH_SIZE;
m_Tiles.resize(size_t(SQR(tilesPerSide)));
unpacker.UnpackRaw(&m_Tiles[0], sizeof(STileDesc)*m_Tiles.size());
// reset generator state.
cur_terrain_tex = 0;
return 0;
}
int CMapReader::ApplyTerrainData()
{
if (m_PatchesPerSide == 0)
{
// we'll probably crash when trying to use this map later
throw PSERROR_Game_World_MapLoadFailed("Error loading map: no terrain data.\nCheck application log for details.");
}
if (!only_xml)
{
// initialise the terrain
pTerrain->Initialize(m_PatchesPerSide, &m_Heightmap[0]);
// setup the textures on the minipatches
STileDesc* tileptr = &m_Tiles[0];
for (ssize_t j=0; jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
mp.Priority = tileptr->m_Priority;
tileptr++;
}
}
}
}
}
CmpPtr cmpTerrain(*pSimContext, SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->ReloadTerrain();
return 0;
}
// ApplyData: take all the input data, and rebuild the scene from it
int CMapReader::ApplyData()
{
// copy over the lighting parameters
if (pLightEnv)
*pLightEnv = m_LightEnv;
CmpPtr cmpPlayerManager(*pSimContext, SYSTEM_ENTITY);
if (pGameView && cmpPlayerManager)
{
// Default to global camera (with constraints)
pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
// TODO: Starting rotation?
CmpPtr cmpPlayer(*pSimContext, cmpPlayerManager->GetPlayerByID(m_PlayerID));
if (cmpPlayer && cmpPlayer->HasStartingCamera())
{
// Use player starting camera
CFixedVector3D pos = cmpPlayer->GetStartingCameraPos();
pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
}
else if (m_StartingCameraTarget != INVALID_ENTITY)
{
// Point camera at entity
CmpPtr cmpPosition(*pSimContext, m_StartingCameraTarget);
if (cmpPosition)
{
CFixedVector3D pos = cmpPosition->GetPosition();
pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
}
}
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
PSRETURN CMapSummaryReader::LoadMap(const VfsPath& pathname)
{
VfsPath filename_xml = pathname.ChangeExtension(L".xml");
CXeromyces xmb_file;
if (xmb_file.Load(g_VFS, filename_xml, "scenario") != PSRETURN_OK)
return PSRETURN_File_ReadFailed;
// Define all the relevant elements used in the XML file
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(scenario);
EL(scriptsettings);
#undef AT
#undef EL
XMBElement root = xmb_file.GetRoot();
ENSURE(root.GetNodeName() == el_scenario);
XERO_ITER_EL(root, child)
{
int child_name = child.GetNodeName();
if (child_name == el_scriptsettings)
{
m_ScriptSettings = child.GetText();
}
}
return PSRETURN_OK;
}
void CMapSummaryReader::GetMapSettings(const ScriptInterface& scriptInterface, JS::MutableHandleValue ret)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
ScriptInterface::CreateObject(cx, ret);
if (m_ScriptSettings.empty())
return;
JS::RootedValue scriptSettingsVal(cx);
scriptInterface.ParseJSON(m_ScriptSettings, &scriptSettingsVal);
scriptInterface.SetProperty(ret, "settings", scriptSettingsVal, false);
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Holds various state data while reading maps, so that loading can be
// interrupted (e.g. to update the progress display) then later resumed.
class CXMLReader
{
NONCOPYABLE(CXMLReader);
public:
CXMLReader(const VfsPath& xml_filename, CMapReader& mapReader)
: m_MapReader(mapReader), nodes(NULL, 0, NULL)
{
Init(xml_filename);
}
CStr ReadScriptSettings();
// read everything except for entities
void ReadXML();
// return semantics: see Loader.cpp!LoadFunc.
int ProgressiveReadEntities();
private:
CXeromyces xmb_file;
CMapReader& m_MapReader;
int el_entity;
int el_tracks;
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
// loop counters
size_t node_idx;
size_t entity_idx;
// # entities+nonentities processed and total (for progress calc)
int completed_jobs, total_jobs;
// maximum used entity ID, so we can safely allocate new ones
entity_id_t max_uid;
void Init(const VfsPath& xml_filename);
void ReadTerrain(XMBElement parent);
void ReadEnvironment(XMBElement parent);
void ReadCamera(XMBElement parent);
void ReadPaths(XMBElement parent);
void ReadTriggers(XMBElement parent);
int ReadEntities(XMBElement parent, double end_time);
};
void CXMLReader::Init(const VfsPath& xml_filename)
{
// must only assign once, so do it here
node_idx = entity_idx = 0;
if (xmb_file.Load(g_VFS, xml_filename, "scenario") != PSRETURN_OK)
throw PSERROR_Game_World_MapLoadFailed("Could not read map XML file!");
// define the elements and attributes that are frequently used in the XML file,
// so we don't need to do lots of string construction and comparison when
// reading the data.
// (Needs to be synchronised with the list in CXMLReader - ugh)
#define EL(x) el_##x = xmb_file.GetElementID(#x)
#define AT(x) at_##x = xmb_file.GetAttributeID(#x)
EL(entity);
EL(tracks);
EL(template);
EL(player);
EL(position);
EL(garrison);
+ EL(turrets);
EL(orientation);
EL(obstruction);
EL(actor);
AT(x); AT(y); AT(z);
AT(group); AT(group2);
AT(angle);
AT(uid);
AT(seed);
+ AT(turret);
#undef AT
#undef EL
XMBElement root = xmb_file.GetRoot();
ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
nodes = root.GetChildNodes();
// find out total number of entities+nonentities
// (used when calculating progress)
completed_jobs = 0;
total_jobs = 0;
for (XMBElement node : nodes)
total_jobs += node.GetChildNodes().size();
// Find the maximum entity ID, so we can safely allocate new IDs without conflicts
max_uid = SYSTEM_ENTITY;
XMBElement ents = nodes.GetFirstNamedItem(xmb_file.GetElementID("Entities"));
XERO_ITER_EL(ents, ent)
{
CStr uid = ent.GetAttributes().GetNamedItem(at_uid);
max_uid = std::max(max_uid, (entity_id_t)uid.ToUInt());
}
}
CStr CXMLReader::ReadScriptSettings()
{
XMBElement root = xmb_file.GetRoot();
ENSURE(xmb_file.GetElementString(root.GetNodeName()) == "Scenario");
nodes = root.GetChildNodes();
XMBElement settings = nodes.GetFirstNamedItem(xmb_file.GetElementID("ScriptSettings"));
return settings.GetText();
}
void CXMLReader::ReadTerrain(XMBElement parent)
{
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
AT(patches);
AT(texture);
AT(priority);
AT(height);
#undef AT
ssize_t patches = 9;
CStr texture = "grass1_spring";
int priority = 0;
u16 height = 16384;
XERO_ITER_ATTR(parent, attr)
{
if (attr.Name == at_patches)
patches = attr.Value.ToInt();
else if (attr.Name == at_texture)
texture = attr.Value;
else if (attr.Name == at_priority)
priority = attr.Value.ToInt();
else if (attr.Name == at_height)
height = (u16)attr.Value.ToInt();
}
m_MapReader.m_PatchesPerSide = patches;
// Load the texture
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(texture);
m_MapReader.pTerrain->Initialize(patches, NULL);
// Fill the heightmap
u16* heightmap = m_MapReader.pTerrain->GetHeightMap();
ssize_t verticesPerSide = m_MapReader.pTerrain->GetVerticesPerSide();
for (ssize_t i = 0; i < SQR(verticesPerSide); ++i)
heightmap[i] = height;
// Fill the texture map
for (ssize_t pz = 0; pz < patches; ++pz)
{
for (ssize_t px = 0; px < patches; ++px)
{
CPatch* patch = m_MapReader.pTerrain->GetPatch(px, pz); // can't fail
for (ssize_t z = 0; z < PATCH_SIZE; ++z)
{
for (ssize_t x = 0; x < PATCH_SIZE; ++x)
{
patch->m_MiniPatches[z][x].Tex = texentry;
patch->m_MiniPatches[z][x].Priority = priority;
}
}
}
}
}
void CXMLReader::ReadEnvironment(XMBElement parent)
{
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(posteffect);
EL(skyset);
EL(suncolor);
EL(sunelevation);
EL(sunrotation);
EL(terrainambientcolor);
EL(unitsambientcolor);
EL(water);
EL(waterbody);
EL(type);
EL(color);
EL(tint);
EL(height);
EL(waviness);
EL(murkiness);
EL(windangle);
EL(fog);
EL(fogcolor);
EL(fogfactor);
EL(fogthickness);
EL(postproc);
EL(brightness);
EL(contrast);
EL(saturation);
EL(bloom);
AT(r); AT(g); AT(b);
#undef AT
#undef EL
XERO_ITER_EL(parent, element)
{
int element_name = element.GetNodeName();
XMBAttributeList attrs = element.GetAttributes();
if (element_name == el_skyset)
{
if (m_MapReader.pSkyMan)
m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8());
}
else if (element_name == el_suncolor)
{
m_MapReader.m_LightEnv.m_SunColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_sunelevation)
{
m_MapReader.m_LightEnv.m_Elevation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_sunrotation)
{
m_MapReader.m_LightEnv.m_Rotation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_terrainambientcolor)
{
m_MapReader.m_LightEnv.m_TerrainAmbientColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_unitsambientcolor)
{
m_MapReader.m_LightEnv.m_UnitsAmbientColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_fog)
{
XERO_ITER_EL(element, fog)
{
int element_name = fog.GetNodeName();
if (element_name == el_fogcolor)
{
XMBAttributeList attrs = fog.GetAttributes();
m_MapReader.m_LightEnv.m_FogColor = RGBColor(
attrs.GetNamedItem(at_r).ToFloat(),
attrs.GetNamedItem(at_g).ToFloat(),
attrs.GetNamedItem(at_b).ToFloat());
}
else if (element_name == el_fogfactor)
{
m_MapReader.m_LightEnv.m_FogFactor = fog.GetText().ToFloat();
}
else if (element_name == el_fogthickness)
{
m_MapReader.m_LightEnv.m_FogMax = fog.GetText().ToFloat();
}
}
}
else if (element_name == el_postproc)
{
XERO_ITER_EL(element, postproc)
{
int element_name = postproc.GetNodeName();
if (element_name == el_brightness)
{
m_MapReader.m_LightEnv.m_Brightness = postproc.GetText().ToFloat();
}
else if (element_name == el_contrast)
{
m_MapReader.m_LightEnv.m_Contrast = postproc.GetText().ToFloat();
}
else if (element_name == el_saturation)
{
m_MapReader.m_LightEnv.m_Saturation = postproc.GetText().ToFloat();
}
else if (element_name == el_bloom)
{
m_MapReader.m_LightEnv.m_Bloom = postproc.GetText().ToFloat();
}
else if (element_name == el_posteffect)
{
if (m_MapReader.pPostproc)
m_MapReader.pPostproc->SetPostEffect(postproc.GetText().FromUTF8());
}
}
}
else if (element_name == el_water)
{
XERO_ITER_EL(element, waterbody)
{
ENSURE(waterbody.GetNodeName() == el_waterbody);
XERO_ITER_EL(waterbody, waterelement)
{
int element_name = waterelement.GetNodeName();
if (element_name == el_height)
{
CmpPtr cmpWaterManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
cmpWaterManager->SetWaterLevel(entity_pos_t::FromString(waterelement.GetText()));
continue;
}
// The rest are purely graphical effects, and should be ignored if
// graphics are disabled
if (!m_MapReader.pWaterMan)
continue;
if (element_name == el_type)
{
if (waterelement.GetText() == "default")
m_MapReader.pWaterMan->m_WaterType = L"ocean";
else
m_MapReader.pWaterMan->m_WaterType = waterelement.GetText().FromUTF8();
}
#define READ_COLOR(el, out) \
else if (element_name == el) \
{ \
XMBAttributeList attrs = waterelement.GetAttributes(); \
out = CColor( \
attrs.GetNamedItem(at_r).ToFloat(), \
attrs.GetNamedItem(at_g).ToFloat(), \
attrs.GetNamedItem(at_b).ToFloat(), \
1.f); \
}
#define READ_FLOAT(el, out) \
else if (element_name == el) \
{ \
out = waterelement.GetText().ToFloat(); \
} \
READ_COLOR(el_color, m_MapReader.pWaterMan->m_WaterColor)
READ_COLOR(el_tint, m_MapReader.pWaterMan->m_WaterTint)
READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness)
READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness)
READ_FLOAT(el_windangle, m_MapReader.pWaterMan->m_WindAngle)
#undef READ_FLOAT
#undef READ_COLOR
else
debug_warn(L"Invalid map XML data");
}
}
}
else
debug_warn(L"Invalid map XML data");
}
m_MapReader.m_LightEnv.CalculateSunDirection();
}
void CXMLReader::ReadCamera(XMBElement parent)
{
// defaults if we don't find player starting camera
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(declination);
EL(rotation);
EL(position);
AT(angle);
AT(x); AT(y); AT(z);
#undef AT
#undef EL
float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
CVector3D translation = CVector3D(100, 150, -100);
XERO_ITER_EL(parent, element)
{
int element_name = element.GetNodeName();
XMBAttributeList attrs = element.GetAttributes();
if (element_name == el_declination)
{
declination = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_rotation)
{
rotation = attrs.GetNamedItem(at_angle).ToFloat();
}
else if (element_name == el_position)
{
translation = CVector3D(
attrs.GetNamedItem(at_x).ToFloat(),
attrs.GetNamedItem(at_y).ToFloat(),
attrs.GetNamedItem(at_z).ToFloat());
}
else
debug_warn(L"Invalid map XML data");
}
if (m_MapReader.pGameView)
{
m_MapReader.pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
m_MapReader.pGameView->GetCamera()->m_Orientation.RotateY(rotation);
m_MapReader.pGameView->GetCamera()->m_Orientation.Translate(translation);
m_MapReader.pGameView->GetCamera()->UpdateFrustum();
}
}
void CXMLReader::ReadPaths(XMBElement parent)
{
#define EL(x) int el_##x = xmb_file.GetElementID(#x)
#define AT(x) int at_##x = xmb_file.GetAttributeID(#x)
EL(path);
EL(rotation);
EL(node);
EL(position);
EL(target);
AT(name);
AT(timescale);
AT(orientation);
AT(mode);
AT(style);
AT(x);
AT(y);
AT(z);
AT(deltatime);
#undef EL
#undef AT
CmpPtr cmpCinemaManager(*m_MapReader.pSimContext, SYSTEM_ENTITY);
XERO_ITER_EL(parent, element)
{
int elementName = element.GetNodeName();
if (elementName == el_path)
{
CCinemaData pathData;
XMBAttributeList attrs = element.GetAttributes();
CStrW pathName(attrs.GetNamedItem(at_name).FromUTF8());
pathData.m_Name = pathName;
pathData.m_Timescale = fixed::FromString(attrs.GetNamedItem(at_timescale));
pathData.m_Orientation = attrs.GetNamedItem(at_orientation).FromUTF8();
pathData.m_Mode = attrs.GetNamedItem(at_mode).FromUTF8();
pathData.m_Style = attrs.GetNamedItem(at_style).FromUTF8();
TNSpline positionSpline, targetSpline;
fixed lastPositionTime = fixed::Zero();
fixed lastTargetTime = fixed::Zero();
XERO_ITER_EL(element, pathChild)
{
elementName = pathChild.GetNodeName();
attrs = pathChild.GetAttributes();
// Load node data used for spline
if (elementName == el_node)
{
lastPositionTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
lastTargetTime += fixed::FromString(attrs.GetNamedItem(at_deltatime));
XERO_ITER_EL(pathChild, nodeChild)
{
elementName = nodeChild.GetNodeName();
attrs = nodeChild.GetAttributes();
if (elementName == el_position)
{
CFixedVector3D position(fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
positionSpline.AddNode(position, CFixedVector3D(), lastPositionTime);
lastPositionTime = fixed::Zero();
}
else if (elementName == el_rotation)
{
// TODO: Implement rotation slerp/spline as another object
}
else if (elementName == el_target)
{
CFixedVector3D targetPosition(fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
targetSpline.AddNode(targetPosition, CFixedVector3D(), lastTargetTime);
lastTargetTime = fixed::Zero();
}
else
LOGWARNING("Invalid cinematic element for node child");
}
}
else
LOGWARNING("Invalid cinematic element for path child");
}
// Construct cinema path with data gathered
CCinemaPath path(pathData, positionSpline, targetSpline);
if (path.Empty())
{
LOGWARNING("Path with name '%s' is empty", pathName.ToUTF8());
return;
}
if (!cmpCinemaManager)
continue;
if (!cmpCinemaManager->HasPath(pathName))
cmpCinemaManager->AddPath(path);
else
LOGWARNING("Path with name '%s' already exists", pathName.ToUTF8());
}
else
LOGWARNING("Invalid path child with name '%s'", element.GetText());
}
}
void CXMLReader::ReadTriggers(XMBElement UNUSED(parent))
{
}
int CXMLReader::ReadEntities(XMBElement parent, double end_time)
{
XMBElementList entities = parent.GetChildNodes();
ENSURE(m_MapReader.pSimulation2);
CSimulation2& sim = *m_MapReader.pSimulation2;
CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY);
while (entity_idx < entities.size())
{
// all new state at this scope and below doesn't need to be
// wrapped, since we only yield after a complete iteration.
XMBElement entity = entities[entity_idx++];
ENSURE(entity.GetNodeName() == el_entity);
XMBAttributeList attrs = entity.GetAttributes();
CStr uid = attrs.GetNamedItem(at_uid);
ENSURE(!uid.empty());
int EntityUid = uid.ToInt();
CStrW TemplateName;
int PlayerID = 0;
std::vector Garrison;
+ std::vector > Turrets;
CFixedVector3D Position;
CFixedVector3D Orientation;
long Seed = -1;
// Obstruction control groups.
entity_id_t ControlGroup = INVALID_ENTITY;
entity_id_t ControlGroup2 = INVALID_ENTITY;
XERO_ITER_EL(entity, setting)
{
int element_name = setting.GetNodeName();
//
if (element_name == el_template)
{
TemplateName = setting.GetText().FromUTF8();
}
//
else if (element_name == el_player)
{
PlayerID = setting.GetText().ToInt();
}
//
else if (element_name == el_position)
{
XMBAttributeList attrs = setting.GetAttributes();
Position = CFixedVector3D(
fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
}
//
else if (element_name == el_orientation)
{
XMBAttributeList attrs = setting.GetAttributes();
Orientation = CFixedVector3D(
fixed::FromString(attrs.GetNamedItem(at_x)),
fixed::FromString(attrs.GetNamedItem(at_y)),
fixed::FromString(attrs.GetNamedItem(at_z)));
// TODO: what happens if some attributes are missing?
}
//
else if (element_name == el_obstruction)
{
XMBAttributeList attrs = setting.GetAttributes();
ControlGroup = attrs.GetNamedItem(at_group).ToInt();
ControlGroup2 = attrs.GetNamedItem(at_group2).ToInt();
}
//
else if (element_name == el_garrison)
{
XMBElementList garrison = setting.GetChildNodes();
Garrison.reserve(garrison.size());
for (const XMBElement& garr_ent : garrison)
{
XMBAttributeList attrs = garr_ent.GetAttributes();
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)
{
XMBAttributeList attrs = setting.GetAttributes();
CStr seedStr = attrs.GetNamedItem(at_seed);
if (!seedStr.empty())
{
Seed = seedStr.ToLong();
ENSURE(Seed >= 0);
}
}
else
debug_warn(L"Invalid map XML data");
}
entity_id_t ent = sim.AddEntity(TemplateName, EntityUid);
entity_id_t player = cmpPlayerManager->GetPlayerByID(PlayerID);
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
{ // Don't add entities with invalid player IDs
LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(TemplateName));
}
else
{
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
cmpPosition->JumpTo(Position.X, Position.Z);
cmpPosition->SetYRotation(Orientation.Y);
// TODO: other parts of the position
}
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
cmpOwnership->SetOwner(PlayerID);
if (!Garrison.empty())
{
CmpPtr cmpGarrisonHolder(sim, ent);
if (cmpGarrisonHolder)
cmpGarrisonHolder->SetInitEntities(Garrison);
else
LOGERROR("CXMLMapReader::ReadEntities() entity '%d' of player '%d' has no GarrisonHolder component and thus cannot garrison units.", ent, PlayerID);
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)
{
if (ControlGroup != INVALID_ENTITY)
cmpObstruction->SetControlGroup(ControlGroup);
if (ControlGroup2 != INVALID_ENTITY)
cmpObstruction->SetControlGroup2(ControlGroup2);
cmpObstruction->ResolveFoundationCollisions();
}
CmpPtr cmpVisual(sim, ent);
if (cmpVisual)
{
if (Seed != -1)
cmpVisual->SetActorSeed((u32)Seed);
// TODO: variation/selection strings
}
if (PlayerID == m_MapReader.m_PlayerID && (boost::algorithm::ends_with(TemplateName, L"civil_centre") || m_MapReader.m_StartingCameraTarget == INVALID_ENTITY))
{
// Focus on civil centre or first entity owned by player
m_MapReader.m_StartingCameraTarget = ent;
}
}
completed_jobs++;
LDR_CHECK_TIMEOUT(completed_jobs, total_jobs);
}
return 0;
}
void CXMLReader::ReadXML()
{
for (XMBElement node : nodes)
{
CStr name = xmb_file.GetElementString(node.GetNodeName());
if (name == "Terrain")
{
ReadTerrain(node);
}
else if (name == "Environment")
{
ReadEnvironment(node);
}
else if (name == "Camera")
{
ReadCamera(node);
}
else if (name == "ScriptSettings")
{
// Already loaded - this is to prevent an assertion
}
else if (name == "Entities")
{
// Handled by ProgressiveReadEntities instead
}
else if (name == "Paths")
{
ReadPaths(node);
}
else if (name == "Triggers")
{
ReadTriggers(node);
}
else if (name == "Script")
{
if (m_MapReader.pSimulation2)
m_MapReader.pSimulation2->SetStartupScript(node.GetText());
}
else
{
debug_printf("Invalid XML element in map file: %s\n", name.c_str());
debug_warn(L"Invalid map XML data");
}
}
}
int CXMLReader::ProgressiveReadEntities()
{
// yield after this time is reached. balances increased progress bar
// smoothness vs. slowing down loading.
const double end_time = timer_Time() + 200e-3;
int ret;
while (node_idx < nodes.size())
{
XMBElement node = nodes[node_idx];
CStr name = xmb_file.GetElementString(node.GetNodeName());
if (name == "Entities")
{
if (!m_MapReader.m_SkipEntities)
{
ret = ReadEntities(node, end_time);
if (ret != 0) // error or timed out
return ret;
}
}
node_idx++;
}
return 0;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// load script settings from map
int CMapReader::LoadScriptSettings()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
// parse the script settings
if (pSimulation2)
pSimulation2->SetMapSettings(xml_reader->ReadScriptSettings());
return 0;
}
// load player settings script
int CMapReader::LoadPlayerSettings()
{
if (pSimulation2)
pSimulation2->LoadPlayerSettings(true);
return 0;
}
// load map settings script
int CMapReader::LoadMapSettings()
{
if (pSimulation2)
pSimulation2->LoadMapSettings();
return 0;
}
int CMapReader::ReadXML()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
xml_reader->ReadXML();
return 0;
}
// progressive
int CMapReader::ReadXMLEntities()
{
if (!xml_reader)
xml_reader = new CXMLReader(filename_xml, *this);
int ret = xml_reader->ProgressiveReadEntities();
// finished or failed
if (ret <= 0)
{
SAFE_DELETE(xml_reader);
}
return ret;
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int CMapReader::LoadRMSettings()
{
// copy random map settings over to sim
ENSURE(pSimulation2);
pSimulation2->SetMapSettings(m_ScriptSettings);
return 0;
}
int CMapReader::GenerateMap()
{
JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
if (!m_MapGen)
{
// Initialize map generator
m_MapGen = new CMapGenerator();
VfsPath scriptPath;
if (m_ScriptFile.length())
scriptPath = L"maps/random/"+m_ScriptFile;
// Stringify settings to pass across threads
std::string scriptSettings = pSimulation2->GetScriptInterface().StringifyJSON(&m_ScriptSettings);
// Try to generate map
m_MapGen->GenerateMap(scriptPath, scriptSettings);
}
// Check status
int progress = m_MapGen->GetProgress();
if (progress < 0)
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else if (progress == 0)
{
// Finished, get results as StructuredClone object, which must be read to obtain the JS::Value
shared_ptr results = m_MapGen->GetResults();
// Parse data into simulation context
JS::RootedValue data(cx);
pSimulation2->GetScriptInterface().ReadStructuredClone(results, &data);
if (data.isUndefined())
{
// RMS failed - return to main menu
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
}
else
{
m_MapData.init(cx, data);
}
}
else
{
// Still working
// Sleep for a while, slowing down the rendering thread
// to allow more CPU for the map generator thread
SDL_Delay(100);
}
// return progress
return progress;
};
int CMapReader::ParseTerrain()
{
TIMER(L"ParseTerrain");
JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// parse terrain from map data
// an error here should stop the loading process
#define GET_TERRAIN_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
{ LOGERROR("CMapReader::ParseTerrain() failed to get '%s' property", #prop);\
throw PSERROR_Game_World_MapLoadFailed("Error parsing terrain data.\nCheck application log for details"); }
u32 size;
GET_TERRAIN_PROPERTY(m_MapData, size, size)
m_PatchesPerSide = size / PATCH_SIZE;
// flat heightmap of u16 data
GET_TERRAIN_PROPERTY(m_MapData, height, m_Heightmap)
// load textures
std::vector textureNames;
GET_TERRAIN_PROPERTY(m_MapData, textureNames, textureNames)
num_terrain_tex = textureNames.size();
while (cur_terrain_tex < num_terrain_tex)
{
ENSURE(CTerrainTextureManager::IsInitialised()); // we need this for the terrain properties (even when graphics are disabled)
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(textureNames[cur_terrain_tex]);
m_TerrainTextures.push_back(texentry);
cur_terrain_tex++;
}
// build tile data
m_Tiles.resize(SQR(size));
JS::RootedValue tileData(cx);
GET_TERRAIN_PROPERTY(m_MapData, tileData, &tileData)
// parse tile data object into flat arrays
std::vector tileIndex;
std::vector tilePriority;
GET_TERRAIN_PROPERTY(tileData, index, tileIndex);
GET_TERRAIN_PROPERTY(tileData, priority, tilePriority);
ENSURE(SQR(size) == tileIndex.size() && SQR(size) == tilePriority.size());
// reorder by patches and store
for (size_t x = 0; x < size; ++x)
{
size_t patchX = x / PATCH_SIZE;
size_t offX = x % PATCH_SIZE;
for (size_t y = 0; y < size; ++y)
{
size_t patchY = y / PATCH_SIZE;
size_t offY = y % PATCH_SIZE;
STileDesc tile;
tile.m_Tex1Index = tileIndex[y*size + x];
tile.m_Tex2Index = 0xFFFF;
tile.m_Priority = tilePriority[y*size + x];
m_Tiles[(patchY * m_PatchesPerSide + patchX) * SQR(PATCH_SIZE) + (offY * PATCH_SIZE + offX)] = tile;
}
}
// reset generator state
cur_terrain_tex = 0;
#undef GET_TERRAIN_PROPERTY
return 0;
}
int CMapReader::ParseEntities()
{
TIMER(L"ParseEntities");
JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// parse entities from map data
std::vector entities;
if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData, "entities", entities))
LOGWARNING("CMapReader::ParseEntities() failed to get 'entities' property");
CSimulation2& sim = *pSimulation2;
CmpPtr cmpPlayerManager(sim, SYSTEM_ENTITY);
size_t entity_idx = 0;
size_t num_entities = entities.size();
Entity currEnt;
while (entity_idx < num_entities)
{
// Get current entity struct
currEnt = entities[entity_idx];
entity_id_t ent = pSimulation2->AddEntity(currEnt.templateName, currEnt.entityID);
entity_id_t player = cmpPlayerManager->GetPlayerByID(currEnt.playerID);
if (ent == INVALID_ENTITY || player == INVALID_ENTITY)
{ // Don't add entities with invalid player IDs
LOGERROR("Failed to load entity template '%s'", utf8_from_wstring(currEnt.templateName));
}
else
{
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
cmpPosition->JumpTo(currEnt.position.X * (int)TERRAIN_TILE_SIZE, currEnt.position.Z * (int)TERRAIN_TILE_SIZE);
cmpPosition->SetYRotation(currEnt.rotation.Y);
// TODO: other parts of the position
}
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
cmpOwnership->SetOwner(currEnt.playerID);
// Detect and fix collisions between foundation-blocking entities.
// This presently serves to copy wall tower control groups to wall
// segments, allowing players to expand RMS-generated walls.
CmpPtr cmpObstruction(sim, ent);
if (cmpObstruction)
cmpObstruction->ResolveFoundationCollisions();
if (currEnt.playerID == m_PlayerID && (boost::algorithm::ends_with(currEnt.templateName, L"civil_centre") || m_StartingCameraTarget == INVALID_ENTITY))
{
// Focus on civil centre or first entity owned by player
m_StartingCameraTarget = currEnt.entityID;
}
}
entity_idx++;
}
return 0;
}
int CMapReader::ParseEnvironment()
{
// parse environment settings from map data
JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
#define GET_ENVIRONMENT_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
LOGWARNING("CMapReader::ParseEnvironment() failed to get '%s' property", #prop);
JS::RootedValue envObj(cx);
GET_ENVIRONMENT_PROPERTY(m_MapData, Environment, &envObj)
if (envObj.isUndefined())
{
LOGWARNING("CMapReader::ParseEnvironment(): Environment settings not found");
return 0;
}
if (pPostproc)
pPostproc->SetPostEffect(L"default");
std::wstring skySet;
GET_ENVIRONMENT_PROPERTY(envObj, SkySet, skySet)
if (pSkyMan)
pSkyMan->SetSkySet(skySet);
CColor sunColor;
GET_ENVIRONMENT_PROPERTY(envObj, SunColor, sunColor)
m_LightEnv.m_SunColor = RGBColor(sunColor.r, sunColor.g, sunColor.b);
GET_ENVIRONMENT_PROPERTY(envObj, SunElevation, m_LightEnv.m_Elevation)
GET_ENVIRONMENT_PROPERTY(envObj, SunRotation, m_LightEnv.m_Rotation)
CColor terrainAmbientColor;
GET_ENVIRONMENT_PROPERTY(envObj, TerrainAmbientColor, terrainAmbientColor)
m_LightEnv.m_TerrainAmbientColor = RGBColor(terrainAmbientColor.r, terrainAmbientColor.g, terrainAmbientColor.b);
CColor unitsAmbientColor;
GET_ENVIRONMENT_PROPERTY(envObj, UnitsAmbientColor, unitsAmbientColor)
m_LightEnv.m_UnitsAmbientColor = RGBColor(unitsAmbientColor.r, unitsAmbientColor.g, unitsAmbientColor.b);
// Water properties
JS::RootedValue waterObj(cx);
GET_ENVIRONMENT_PROPERTY(envObj, Water, &waterObj)
JS::RootedValue waterBodyObj(cx);
GET_ENVIRONMENT_PROPERTY(waterObj, WaterBody, &waterBodyObj)
// Water level - necessary
float waterHeight;
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Height, waterHeight)
CmpPtr cmpWaterManager(*pSimulation2, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
cmpWaterManager->SetWaterLevel(entity_pos_t::FromFloat(waterHeight));
// If we have graphics, get rest of settings
if (pWaterMan)
{
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Type, pWaterMan->m_WaterType)
if (pWaterMan->m_WaterType == L"default")
pWaterMan->m_WaterType = L"ocean";
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Color, pWaterMan->m_WaterColor)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Tint, pWaterMan->m_WaterTint)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Waviness, pWaterMan->m_Waviness)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, Murkiness, pWaterMan->m_Murkiness)
GET_ENVIRONMENT_PROPERTY(waterBodyObj, WindAngle, pWaterMan->m_WindAngle)
}
JS::RootedValue fogObject(cx);
GET_ENVIRONMENT_PROPERTY(envObj, Fog, &fogObject);
GET_ENVIRONMENT_PROPERTY(fogObject, FogFactor, m_LightEnv.m_FogFactor);
GET_ENVIRONMENT_PROPERTY(fogObject, FogThickness, m_LightEnv.m_FogMax);
CColor fogColor;
GET_ENVIRONMENT_PROPERTY(fogObject, FogColor, fogColor);
m_LightEnv.m_FogColor = RGBColor(fogColor.r, fogColor.g, fogColor.b);
JS::RootedValue postprocObject(cx);
GET_ENVIRONMENT_PROPERTY(envObj, Postproc, &postprocObject);
std::wstring postProcEffect;
GET_ENVIRONMENT_PROPERTY(postprocObject, PostprocEffect, postProcEffect);
if (pPostproc)
pPostproc->SetPostEffect(postProcEffect);
GET_ENVIRONMENT_PROPERTY(postprocObject, Brightness, m_LightEnv.m_Brightness);
GET_ENVIRONMENT_PROPERTY(postprocObject, Contrast, m_LightEnv.m_Contrast);
GET_ENVIRONMENT_PROPERTY(postprocObject, Saturation, m_LightEnv.m_Saturation);
GET_ENVIRONMENT_PROPERTY(postprocObject, Bloom, m_LightEnv.m_Bloom);
m_LightEnv.CalculateSunDirection();
#undef GET_ENVIRONMENT_PROPERTY
return 0;
}
int CMapReader::ParseCamera()
{
JSContext* cx = pSimulation2->GetScriptInterface().GetContext();
JSAutoRequest rq(cx);
// parse camera settings from map data
// defaults if we don't find player starting camera
float declination = DEGTORAD(30.f), rotation = DEGTORAD(-45.f);
CVector3D translation = CVector3D(100, 150, -100);
#define GET_CAMERA_PROPERTY(val, prop, out)\
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
LOGWARNING("CMapReader::ParseCamera() failed to get '%s' property", #prop);
JS::RootedValue cameraObj(cx);
GET_CAMERA_PROPERTY(m_MapData, Camera, &cameraObj)
if (!cameraObj.isUndefined())
{ // If camera property exists, read values
CFixedVector3D pos;
GET_CAMERA_PROPERTY(cameraObj, Position, pos)
translation = pos;
GET_CAMERA_PROPERTY(cameraObj, Rotation, rotation)
GET_CAMERA_PROPERTY(cameraObj, Declination, declination)
}
#undef GET_CAMERA_PROPERTY
if (pGameView)
{
pGameView->GetCamera()->m_Orientation.SetXRotation(declination);
pGameView->GetCamera()->m_Orientation.RotateY(rotation);
pGameView->GetCamera()->m_Orientation.Translate(translation);
pGameView->GetCamera()->UpdateFrustum();
}
return 0;
}
CMapReader::~CMapReader()
{
// Cleaup objects
delete xml_reader;
delete m_MapGen;
}
Index: ps/trunk/source/graphics/MapWriter.cpp
===================================================================
--- ps/trunk/source/graphics/MapWriter.cpp (revision 24160)
+++ ps/trunk/source/graphics/MapWriter.cpp (revision 24161)
@@ -1,495 +1,511 @@
/* 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 "Camera.h"
#include "CinemaManager.h"
#include "GameView.h"
#include "LightEnv.h"
#include "MapReader.h"
#include "MapWriter.h"
#include "Patch.h"
#include "Terrain.h"
#include "TerrainTextureEntry.h"
#include "TerrainTextureManager.h"
#include "maths/MathUtil.h"
#include "maths/NUSpline.h"
#include "ps/CLogger.h"
#include "ps/Loader.h"
#include "ps/Filesystem.h"
#include "ps/XML/XMLWriter.h"
#include "renderer/PostprocManager.h"
#include "renderer/SkyManager.h"
#include "renderer/WaterManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpCinemaManager.h"
#include "simulation2/components/ICmpGarrisonHolder.h"
#include "simulation2/components/ICmpObstruction.h"
#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"
///////////////////////////////////////////////////////////////////////////////////////////////////
// CMapWriter constructor: nothing to do at the minute
CMapWriter::CMapWriter()
{
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// SaveMap: try to save the current map to the given file
void CMapWriter::SaveMap(const VfsPath& pathname, CTerrain* pTerrain,
WaterManager* pWaterMan, SkyManager* pSkyMan,
CLightEnv* pLightEnv, CCamera* pCamera, CCinemaManager* UNUSED(pCinema),
CPostprocManager* pPostproc,
CSimulation2* pSimulation2)
{
CFilePacker packer(FILE_VERSION, "PSMP");
// build necessary data
PackMap(packer, pTerrain);
try
{
// write it out
packer.Write(pathname);
}
catch (PSERROR_File_WriteFailed&)
{
LOGERROR("Failed to write map '%s'", pathname.string8());
return;
}
VfsPath pathnameXML = pathname.ChangeExtension(L".xml");
WriteXML(pathnameXML, pWaterMan, pSkyMan, pLightEnv, pCamera, pPostproc, pSimulation2);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// GetHandleIndex: return the index of the given handle in the given list; or 0xFFFF if
// handle isn't in list
static u16 GetEntryIndex(const CTerrainTextureEntry* entry, const std::vector& entries)
{
const size_t limit = std::min(entries.size(), size_t(0xFFFEu)); // paranoia
for (size_t i=0;i& textures,
std::vector& tiles)
{
// the list of all handles in use
std::vector entries;
// resize tile array to required size
tiles.resize(SQR(pTerrain->GetVerticesPerSide()-1));
STileDesc* tileptr=&tiles[0];
// now iterate through all the tiles
const ssize_t patchesPerSide=pTerrain->GetPatchesPerSide();
for (ssize_t j=0;jGetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
u16 index=u16(GetEntryIndex(mp.GetTextureEntry(),entries));
if (index==0xFFFF) {
index=(u16)entries.size();
entries.push_back(mp.GetTextureEntry());
}
tileptr->m_Tex1Index=index;
tileptr->m_Tex2Index=0xFFFF;
tileptr->m_Priority=mp.GetPriority();
tileptr++;
}
}
}
}
// now find the texture names for each handle
for (size_t i=0;iGetTag();
}
textures.push_back(texturename);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PackMap: pack the current world into a raw data stream
void CMapWriter::PackMap(CFilePacker& packer, CTerrain* pTerrain)
{
// now pack everything up
PackTerrain(packer, pTerrain);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// PackTerrain: pack the terrain onto the end of the output data stream
// - data: map size, heightmap, list of textures used by map, texture tile assignments
void CMapWriter::PackTerrain(CFilePacker& packer, CTerrain* pTerrain)
{
// pack map size
const ssize_t mapsize = pTerrain->GetPatchesPerSide();
packer.PackSize(mapsize);
// pack heightmap
packer.PackRaw(pTerrain->GetHeightMap(),sizeof(u16)*SQR(pTerrain->GetVerticesPerSide()));
// the list of textures used by map
std::vector terrainTextures;
// descriptions of each tile
std::vector tiles;
// build lists by scanning through the terrain
EnumTerrainTextures(pTerrain, terrainTextures, tiles);
// pack texture names
const size_t numTextures = terrainTextures.size();
packer.PackSize(numTextures);
for (size_t i=0;i(FILE_VERSION));
ENSURE(pSimulation2);
CSimulation2& sim = *pSimulation2;
if (!sim.GetStartupScript().empty())
{
XMLWriter_Element scriptTag(xmlMapFile, "Script");
scriptTag.Text(sim.GetStartupScript().c_str(), true);
}
{
XMLWriter_Element environmentTag(xmlMapFile, "Environment");
environmentTag.Setting("SkySet", pSkyMan->GetSkySet());
{
XMLWriter_Element sunColorTag(xmlMapFile, "SunColor");
sunColorTag.Attribute("r", pLightEnv->m_SunColor.X); // yes, it's X/Y/Z...
sunColorTag.Attribute("g", pLightEnv->m_SunColor.Y);
sunColorTag.Attribute("b", pLightEnv->m_SunColor.Z);
}
{
XMLWriter_Element SunElevationTag(xmlMapFile, "SunElevation");
SunElevationTag.Attribute("angle", pLightEnv->m_Elevation);
}
{
XMLWriter_Element sunRotationTag(xmlMapFile, "SunRotation");
sunRotationTag.Attribute("angle", pLightEnv->m_Rotation);
}
{
XMLWriter_Element terrainAmbientColorTag(xmlMapFile, "TerrainAmbientColor");
terrainAmbientColorTag.Attribute("r", pLightEnv->m_TerrainAmbientColor.X);
terrainAmbientColorTag.Attribute("g", pLightEnv->m_TerrainAmbientColor.Y);
terrainAmbientColorTag.Attribute("b", pLightEnv->m_TerrainAmbientColor.Z);
}
{
XMLWriter_Element unitsAmbientColorTag(xmlMapFile, "UnitsAmbientColor");
unitsAmbientColorTag.Attribute("r", pLightEnv->m_UnitsAmbientColor.X);
unitsAmbientColorTag.Attribute("g", pLightEnv->m_UnitsAmbientColor.Y);
unitsAmbientColorTag.Attribute("b", pLightEnv->m_UnitsAmbientColor.Z);
}
{
XMLWriter_Element fogTag(xmlMapFile, "Fog");
fogTag.Setting("FogFactor", pLightEnv->m_FogFactor);
fogTag.Setting("FogThickness", pLightEnv->m_FogMax);
{
XMLWriter_Element fogColorTag(xmlMapFile, "FogColor");
fogColorTag.Attribute("r", pLightEnv->m_FogColor.X);
fogColorTag.Attribute("g", pLightEnv->m_FogColor.Y);
fogColorTag.Attribute("b", pLightEnv->m_FogColor.Z);
}
}
{
XMLWriter_Element waterTag(xmlMapFile, "Water");
{
XMLWriter_Element waterBodyTag(xmlMapFile, "WaterBody");
CmpPtr cmpWaterManager(sim, SYSTEM_ENTITY);
ENSURE(cmpWaterManager);
waterBodyTag.Setting("Type", pWaterMan->m_WaterType);
{
XMLWriter_Element colorTag(xmlMapFile, "Color");
colorTag.Attribute("r", pWaterMan->m_WaterColor.r);
colorTag.Attribute("g", pWaterMan->m_WaterColor.g);
colorTag.Attribute("b", pWaterMan->m_WaterColor.b);
}
{
XMLWriter_Element tintTag(xmlMapFile, "Tint");
tintTag.Attribute("r", pWaterMan->m_WaterTint.r);
tintTag.Attribute("g", pWaterMan->m_WaterTint.g);
tintTag.Attribute("b", pWaterMan->m_WaterTint.b);
}
waterBodyTag.Setting("Height", cmpWaterManager->GetExactWaterLevel(0, 0));
waterBodyTag.Setting("Waviness", pWaterMan->m_Waviness);
waterBodyTag.Setting("Murkiness", pWaterMan->m_Murkiness);
waterBodyTag.Setting("WindAngle", pWaterMan->m_WindAngle);
}
}
{
XMLWriter_Element postProcTag(xmlMapFile, "Postproc");
{
postProcTag.Setting("Brightness", pLightEnv->m_Brightness);
postProcTag.Setting("Contrast", pLightEnv->m_Contrast);
postProcTag.Setting("Saturation", pLightEnv->m_Saturation);
postProcTag.Setting("Bloom", pLightEnv->m_Bloom);
postProcTag.Setting("PostEffect", pPostproc->GetPostEffect());
}
}
}
{
XMLWriter_Element cameraTag(xmlMapFile, "Camera");
{
XMLWriter_Element positionTag(xmlMapFile, "Position");
CVector3D pos = pCamera->GetOrientation().GetTranslation();
positionTag.Attribute("x", pos.X);
positionTag.Attribute("y", pos.Y);
positionTag.Attribute("z", pos.Z);
}
CVector3D in = pCamera->GetOrientation().GetIn();
// Convert to spherical coordinates
float rotation = atan2(in.X, in.Z);
float declination = atan2(sqrt(in.X*in.X + in.Z*in.Z), in.Y) - static_cast(M_PI / 2);
{
XMLWriter_Element rotationTag(xmlMapFile, "Rotation");
rotationTag.Attribute("angle", rotation);
}
{
XMLWriter_Element declinationTag(xmlMapFile, "Declination");
declinationTag.Attribute("angle", declination);
}
}
{
std::string settings = sim.GetMapSettingsString();
if (!settings.empty())
{
XMLWriter_Element scriptSettingsTag(xmlMapFile, "ScriptSettings");
scriptSettingsTag.Text(("\n" + settings + "\n").c_str(), true);
}
}
{
XMLWriter_Element entitiesTag(xmlMapFile, "Entities");
CmpPtr cmpTemplateManager(sim, SYSTEM_ENTITY);
ENSURE(cmpTemplateManager);
// This will probably need to be changed in the future, but for now we'll
// just save all entities that have a position
CSimulation2::InterfaceList ents = sim.GetEntitiesWithInterface(IID_Position);
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
entity_id_t ent = it->first;
// Don't save local entities (placement previews etc)
if (ENTITY_IS_LOCAL(ent))
continue;
XMLWriter_Element entityTag(xmlMapFile, "Entity");
entityTag.Attribute("uid", ent);
entityTag.Setting("Template", cmpTemplateManager->GetCurrentTemplateName(ent));
CmpPtr cmpOwnership(sim, ent);
if (cmpOwnership)
entityTag.Setting("Player", static_cast(cmpOwnership->GetOwner()));
CmpPtr cmpGarrisonHolder(sim, ent);
if (cmpGarrisonHolder)
{
std::vector garrison = cmpGarrisonHolder->GetEntities();
if (!garrison.empty())
{
XMLWriter_Element garrisonTag(xmlMapFile, "Garrison");
for (const entity_id_t garr_ent_id : garrison)
{
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));
}
}
}
CmpPtr cmpPosition(sim, ent);
if (cmpPosition)
{
CFixedVector3D pos;
if (cmpPosition->IsInWorld())
pos = cmpPosition->GetPosition();
CFixedVector3D rot = cmpPosition->GetRotation();
{
XMLWriter_Element positionTag(xmlMapFile, "Position");
positionTag.Attribute("x", pos.X);
positionTag.Attribute("z", pos.Z);
// TODO: height offset etc
}
{
XMLWriter_Element orientationTag(xmlMapFile, "Orientation");
orientationTag.Attribute("y", rot.Y);
// TODO: X, Z maybe
}
}
CmpPtr cmpObstruction(sim, ent);
if (cmpObstruction)
{
// TODO: Currently only necessary because Atlas
// does not set up control groups for its walls.
cmpObstruction->ResolveFoundationCollisions();
entity_id_t group = cmpObstruction->GetControlGroup();
entity_id_t group2 = cmpObstruction->GetControlGroup2();
// Don't waste space writing the default control groups.
if (group != ent || group2 != INVALID_ENTITY)
{
XMLWriter_Element obstructionTag(xmlMapFile, "Obstruction");
if (group != ent)
obstructionTag.Attribute("group", group);
if (group2 != INVALID_ENTITY)
obstructionTag.Attribute("group2", group2);
}
}
CmpPtr cmpVisual(sim, ent);
if (cmpVisual)
{
entity_id_t seed = static_cast(cmpVisual->GetActorSeed());
if (seed != ent)
{
XMLWriter_Element actorTag(xmlMapFile, "Actor");
actorTag.Attribute("seed",seed);
}
// TODO: variation/selection strings
}
}
}
CmpPtr cmpCinemaManager(sim, SYSTEM_ENTITY);
if (cmpCinemaManager)
{
const std::map& paths = cmpCinemaManager->GetPaths();
std::map::const_iterator it = paths.begin();
XMLWriter_Element pathsTag(xmlMapFile, "Paths");
for ( ; it != paths.end(); ++it )
{
fixed timescale = it->second.GetTimescale();
const std::vector& position_nodes = it->second.GetAllNodes();
const std::vector& target_nodes = it->second.GetTargetSpline().GetAllNodes();
const CCinemaData* data = it->second.GetData();
XMLWriter_Element pathTag(xmlMapFile, "Path");
pathTag.Attribute("name", data->m_Name);
pathTag.Attribute("timescale", timescale);
pathTag.Attribute("orientation", data->m_Orientation);
pathTag.Attribute("mode", data->m_Mode);
pathTag.Attribute("style", data->m_Style);
struct SEvent
{
fixed time;
const char* type;
CFixedVector3D value;
SEvent(fixed time, const char* type, CFixedVector3D value)
: time(time), type(type), value(value)
{}
bool operator<(const SEvent& another) const
{
return time < another.time;
}
};
// All events of a manipulating of camera (position/rotation/target)
std::vector events;
events.reserve(position_nodes.size() + target_nodes.size());
fixed last_position = fixed::Zero();
for (size_t i = 0; i < position_nodes.size(); ++i)
{
fixed distance = i > 0 ? position_nodes[i - 1].Distance : fixed::Zero();
last_position += distance;
events.emplace_back(last_position, "Position", position_nodes[i].Position);
}
fixed last_target = fixed::Zero();
for (size_t i = 0; i < target_nodes.size(); ++i)
{
fixed distance = i > 0 ? target_nodes[i - 1].Distance : fixed::Zero();
last_target += distance;
events.emplace_back(last_target, "Target", target_nodes[i].Position);
}
std::sort(events.begin(), events.end());
for (size_t i = 0; i < events.size();)
{
XMLWriter_Element nodeTag(xmlMapFile, "Node");
fixed deltatime = i > 0 ? (events[i].time - events[i - 1].time) : fixed::Zero();
nodeTag.Attribute("deltatime", deltatime);
size_t j = i;
for (; j < events.size() && events[j].time == events[i].time; ++j)
{
// Types: Position/Rotation/Target
XMLWriter_Element eventTypeTag(xmlMapFile, events[j].type);
eventTypeTag.Attribute("x", events[j].value.X);
eventTypeTag.Attribute("y", events[j].value.Y);
eventTypeTag.Attribute("z", events[j].value.Z);
}
i = j;
}
}
}
}
if (!xmlMapFile.StoreVFS(g_VFS, filename))
LOGERROR("Failed to write map '%s'", filename.string8());
}
Index: ps/trunk/source/simulation2/TypeList.h
===================================================================
--- ps/trunk/source/simulation2/TypeList.h (revision 24160)
+++ ps/trunk/source/simulation2/TypeList.h (revision 24161)
@@ -1,211 +1,214 @@
/* 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 .
*/
// MESSAGE: message types
// INTERFACE: component interface types
// COMPONENT: component types
// Components intended only for use in test cases:
// (The tests rely on the enum IDs, so don't change the order of these)
INTERFACE(Test1)
COMPONENT(Test1A)
COMPONENT(Test1B)
COMPONENT(Test1Scripted)
INTERFACE(Test2)
COMPONENT(Test2A)
COMPONENT(Test2Scripted)
// Message types:
MESSAGE(TurnStart)
MESSAGE(Update)
MESSAGE(Update_MotionFormation)
MESSAGE(Update_MotionUnit)
MESSAGE(Update_Final)
MESSAGE(Interpolate) // non-deterministic (use with caution)
MESSAGE(RenderSubmit) // non-deterministic (use with caution)
MESSAGE(ProgressiveLoad) // non-deterministic (use with caution)
MESSAGE(Deserialized) // non-deterministic (use with caution)
MESSAGE(Create)
MESSAGE(Destroy)
MESSAGE(OwnershipChanged)
MESSAGE(PositionChanged)
MESSAGE(InterpolatedPositionChanged)
MESSAGE(TerritoryPositionChanged)
MESSAGE(MotionUpdate)
MESSAGE(RangeUpdate)
MESSAGE(TerrainChanged)
MESSAGE(VisibilityChanged)
MESSAGE(WaterChanged)
MESSAGE(ObstructionMapShapeChanged)
MESSAGE(TerritoriesChanged)
MESSAGE(PathResult)
MESSAGE(ValueModification)
MESSAGE(TemplateModification)
MESSAGE(VisionRangeChanged)
MESSAGE(VisionSharingChanged)
MESSAGE(MinimapPing)
MESSAGE(CinemaPathEnded)
MESSAGE(CinemaQueueEnded)
MESSAGE(PlayerColorChanged)
// TemplateManager must come before all other (non-test) components,
// so that it is the first to be (de)serialized
INTERFACE(TemplateManager)
COMPONENT(TemplateManager)
// Special component for script component types with no native interface
INTERFACE(UnknownScript)
COMPONENT(UnknownScript)
// In alphabetical order:
INTERFACE(AIInterface)
COMPONENT(AIInterfaceScripted)
INTERFACE(AIManager)
COMPONENT(AIManager)
INTERFACE(Attack)
COMPONENT(AttackScripted)
INTERFACE(CinemaManager)
COMPONENT(CinemaManager)
INTERFACE(CommandQueue)
COMPONENT(CommandQueue)
INTERFACE(Decay)
COMPONENT(Decay)
INTERFACE(Fogging)
COMPONENT(FoggingScripted)
// Note: The VisualActor component relies on this component being initialized before itself, in order to support using
// an entity's footprint shape for the selection boxes. This dependency is not strictly necessary, but it does avoid
// some extra plumbing code to set up on-demand initialization. If you find yourself forced to break this dependency,
// see VisualActor's Init method for a description of how you can avoid it.
INTERFACE(Footprint)
COMPONENT(Footprint)
INTERFACE(GarrisonHolder)
COMPONENT(GarrisonHolderScripted)
INTERFACE(GuiInterface)
COMPONENT(GuiInterfaceScripted)
INTERFACE(Identity)
COMPONENT(IdentityScripted)
INTERFACE(Minimap)
COMPONENT(Minimap)
INTERFACE(Mirage)
COMPONENT(MirageScripted)
INTERFACE(Motion)
COMPONENT(MotionBall)
COMPONENT(MotionScripted)
INTERFACE(Obstruction)
COMPONENT(Obstruction)
INTERFACE(ObstructionManager)
COMPONENT(ObstructionManager)
INTERFACE(OverlayRenderer)
COMPONENT(OverlayRenderer)
INTERFACE(Ownership)
COMPONENT(Ownership)
INTERFACE(ParticleManager)
COMPONENT(ParticleManager)
INTERFACE(Pathfinder)
COMPONENT(Pathfinder)
INTERFACE(Player)
COMPONENT(PlayerScripted)
INTERFACE(PlayerManager)
COMPONENT(PlayerManagerScripted)
INTERFACE(Position)
COMPONENT(Position) // must be before VisualActor
INTERFACE(ProjectileManager)
COMPONENT(ProjectileManager)
INTERFACE(RallyPoint)
COMPONENT(RallyPointScripted)
INTERFACE(RallyPointRenderer)
COMPONENT(RallyPointRenderer)
INTERFACE(RangeManager)
COMPONENT(RangeManager)
INTERFACE(RangeOverlayRenderer)
COMPONENT(RangeOverlayRenderer)
INTERFACE(Selectable)
COMPONENT(Selectable)
INTERFACE(Settlement)
COMPONENT(SettlementScripted)
INTERFACE(Sound)
COMPONENT(SoundScripted)
INTERFACE(SoundManager)
COMPONENT(SoundManager)
INTERFACE(ValueModificationManager)
COMPONENT(ValueModificationManagerScripted)
INTERFACE(Terrain)
COMPONENT(Terrain)
INTERFACE(TerritoryDecayManager)
COMPONENT(TerritoryDecayManagerScripted)
INTERFACE(TerritoryInfluence)
COMPONENT(TerritoryInfluence)
INTERFACE(TerritoryManager)
COMPONENT(TerritoryManager)
+INTERFACE(TurretHolder)
+COMPONENT(TurretHolderScripted)
+
INTERFACE(UnitMotion)
COMPONENT(UnitMotion) // must be after Obstruction
COMPONENT(UnitMotionScripted)
INTERFACE(UnitRenderer)
COMPONENT(UnitRenderer)
INTERFACE(Visibility)
COMPONENT(VisibilityScripted)
INTERFACE(Vision)
COMPONENT(Vision)
// Note: this component relies on the Footprint component being initialized before itself. See the comments above for
// the Footprint component to find out why.
INTERFACE(Visual)
COMPONENT(VisualActor) // must be after Ownership (dependency in Deserialize) and Vision (dependency in Init)
INTERFACE(WaterManager)
COMPONENT(WaterManager)
Index: ps/trunk/source/simulation2/components/ICmpTurretHolder.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTurretHolder.cpp (nonexistent)
+++ ps/trunk/source/simulation2/components/ICmpTurretHolder.cpp (revision 24161)
@@ -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)
Index: ps/trunk/source/simulation2/components/ICmpTurretHolder.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpTurretHolder.h (nonexistent)
+++ ps/trunk/source/simulation2/components/ICmpTurretHolder.h (revision 24161)
@@ -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