1426 lines
40 KiB
C++
1426 lines
40 KiB
C++
/* Copyright (C) 2012 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "precompiled.h"
|
|
|
|
#include "MapReader.h"
|
|
|
|
#include "graphics/Camera.h"
|
|
#include "graphics/CinemaTrack.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/SkyManager.h"
|
|
#include "renderer/WaterManager.h"
|
|
#include "simulation2/Simulation2.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/ICmpWaterManager.h"
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
|
|
|
|
CMapReader::CMapReader()
|
|
: xml_reader(0), m_PatchesPerSide(0), m_MapGen(0)
|
|
{
|
|
cur_terrain_tex = 0; // important - resets generator state
|
|
|
|
// Maps that don't override the default probably want the old lighting model
|
|
m_LightEnv.SetLightingModel("old");
|
|
}
|
|
|
|
// LoadMap: try to load the map from given file; reinitialise the scene to new data if successful
|
|
void CMapReader::LoadMap(const VfsPath& pathname, CTerrain *pTerrain_,
|
|
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
|
|
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_,
|
|
CSimulation2 *pSimulation2_, const CSimContext* pSimContext_, int playerID_, bool skipEntities)
|
|
{
|
|
// latch parameters (held until DelayedLoadFinished)
|
|
pTerrain = pTerrain_;
|
|
pLightEnv = pLightEnv_;
|
|
pGameView = pGameView_;
|
|
pWaterMan = pWaterMan_;
|
|
pSkyMan = pSkyMan_;
|
|
pCinema = pCinema_;
|
|
pTrigMan = pTrigMan_;
|
|
pSimulation2 = pSimulation2_;
|
|
pSimContext = pSimContext_;
|
|
m_PlayerID = playerID_;
|
|
m_SkipEntities = skipEntities;
|
|
m_StartingCameraTarget = INVALID_ENTITY;
|
|
|
|
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_File_InvalidVersion();
|
|
|
|
// delete all existing entities
|
|
if (pSimulation2)
|
|
pSimulation2->ResetState();
|
|
|
|
// load map settings script
|
|
RegMemFun(this, &CMapReader::LoadScriptSettings, L"CMapReader::LoadScriptSettings", 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", 5800);
|
|
|
|
// apply 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);
|
|
|
|
RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5);
|
|
}
|
|
|
|
|
|
// LoadRandomMap: try to load the map data; reinitialise the scene to new data if successful
|
|
void CMapReader::LoadRandomMap(const CStrW& scriptFile, const CScriptValRooted& settings, CTerrain *pTerrain_,
|
|
WaterManager* pWaterMan_, SkyManager* pSkyMan_,
|
|
CLightEnv *pLightEnv_, CGameView *pGameView_, CCinemaManager* pCinema_, CTriggerManager* pTrigMan_,
|
|
CSimulation2 *pSimulation2_, int playerID_)
|
|
{
|
|
// latch parameters (held until DelayedLoadFinished)
|
|
m_ScriptFile = scriptFile;
|
|
m_ScriptSettings = settings;
|
|
pTerrain = pTerrain_;
|
|
pLightEnv = pLightEnv_;
|
|
pGameView = pGameView_;
|
|
pWaterMan = pWaterMan_;
|
|
pSkyMan = pSkyMan_;
|
|
pCinema = pCinema_;
|
|
pTrigMan = pTrigMan_;
|
|
pSimulation2 = pSimulation2_;
|
|
pSimContext = pSimulation2 ? &pSimulation2->GetSimContext() : NULL;
|
|
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", 5000);
|
|
|
|
// 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);
|
|
|
|
// parse RMS results into entities
|
|
RegMemFun(this, &CMapReader::ParseEntities, L"CMapReader::ParseEntities", 1000);
|
|
|
|
// apply 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);
|
|
|
|
RegMemFun(this, &CMapReader::DelayLoadFinished, L"CMapReader::DelayLoadFinished", 5);
|
|
}
|
|
|
|
// UnpackMap: unpack the given data from the raw data stream into local variables
|
|
int CMapReader::UnpackMap()
|
|
{
|
|
// now unpack everything into local data
|
|
int ret = UnpackTerrain();
|
|
if (ret != 0) // failed or timed out
|
|
{
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// ApplyData: take all the input data, and rebuild the scene from it
|
|
int CMapReader::ApplyData()
|
|
{
|
|
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; j<m_PatchesPerSide; j++) {
|
|
for (ssize_t i=0; i<m_PatchesPerSide; i++) {
|
|
for (ssize_t m=0; m<PATCH_SIZE; m++) {
|
|
for (ssize_t k=0; k<PATCH_SIZE; k++) {
|
|
CMiniPatch& mp = pTerrain->GetPatch(i,j)->m_MiniPatches[m][k]; // can't fail
|
|
|
|
mp.Tex = m_TerrainTextures[tileptr->m_Tex1Index];
|
|
mp.Priority = tileptr->m_Priority;
|
|
|
|
tileptr++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// copy over the lighting parameters
|
|
if (pLightEnv)
|
|
*pLightEnv = m_LightEnv;
|
|
|
|
CmpPtr<ICmpPlayerManager> cmpPlayerManager(*pSimContext, SYSTEM_ENTITY);
|
|
|
|
if (pGameView && cmpPlayerManager)
|
|
{
|
|
// Default to global camera (with constraints)
|
|
pGameView->ResetCameraTarget(pGameView->GetCamera()->GetFocus());
|
|
|
|
// TODO: Starting rotation?
|
|
CmpPtr<ICmpPlayer> 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<ICmpPosition> cmpPosition(*pSimContext, m_StartingCameraTarget);
|
|
if (cmpPosition)
|
|
{
|
|
CFixedVector3D pos = cmpPosition->GetPosition();
|
|
pGameView->ResetCameraTarget(CVector3D(pos.X.ToFloat(), pos.Y.ToFloat(), pos.Z.ToFloat()));
|
|
}
|
|
}
|
|
}
|
|
|
|
CmpPtr<ICmpTerrain> cmpTerrain(*pSimContext, SYSTEM_ENTITY);
|
|
if (cmpTerrain)
|
|
cmpTerrain->ReloadTerrain();
|
|
|
|
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) != 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;
|
|
}
|
|
|
|
CScriptValRooted CMapSummaryReader::GetMapSettings(ScriptInterface& scriptInterface)
|
|
{
|
|
CScriptValRooted data;
|
|
scriptInterface.Eval("({})", data);
|
|
if (!m_ScriptSettings.empty())
|
|
scriptInterface.SetProperty(data.get(), "settings", scriptInterface.ParseJSON(m_ScriptSettings), false);
|
|
return data;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
// 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)
|
|
{
|
|
Init(xml_filename);
|
|
}
|
|
|
|
CStr ReadScriptSettings();
|
|
|
|
// return semantics: see Loader.cpp!LoadFunc.
|
|
int ProgressiveRead();
|
|
|
|
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_nonentity;
|
|
int el_actor;
|
|
int at_x, at_y, at_z;
|
|
int at_group, at_group2;
|
|
int at_id;
|
|
int at_angle;
|
|
int at_uid;
|
|
|
|
XMBElementList nodes; // children of root
|
|
|
|
// loop counters
|
|
int node_idx;
|
|
int entity_idx, nonentity_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 ReadCinema(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 = nonentity_idx = 0;
|
|
|
|
if (xmb_file.Load(g_VFS, xml_filename) != PSRETURN_OK)
|
|
throw PSERROR_File_ReadFailed();
|
|
|
|
// 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(orientation);
|
|
EL(obstruction);
|
|
EL(nonentity);
|
|
EL(actor);
|
|
AT(x); AT(y); AT(z);
|
|
AT(group); AT(group2);
|
|
AT(angle);
|
|
AT(uid);
|
|
#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 (int i = 0; i < nodes.Count; i++)
|
|
total_jobs += nodes.Item(i).GetChildNodes().Count;
|
|
|
|
// 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(lightingmodel);
|
|
EL(skyset);
|
|
EL(suncolour);
|
|
EL(sunelevation);
|
|
EL(sunrotation);
|
|
EL(terrainambientcolour);
|
|
EL(unitsambientcolour);
|
|
EL(water);
|
|
EL(waterbody);
|
|
EL(type);
|
|
EL(colour);
|
|
EL(height);
|
|
EL(shininess);
|
|
EL(waviness);
|
|
EL(murkiness);
|
|
EL(tint);
|
|
EL(reflectiontint);
|
|
EL(reflectiontintstrength);
|
|
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_lightingmodel)
|
|
{
|
|
m_MapReader.m_LightEnv.SetLightingModel(element.GetText());
|
|
}
|
|
else if (element_name == el_skyset)
|
|
{
|
|
if (m_MapReader.pSkyMan)
|
|
m_MapReader.pSkyMan->SetSkySet(element.GetText().FromUTF8());
|
|
}
|
|
else if (element_name == el_suncolour)
|
|
{
|
|
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_terrainambientcolour)
|
|
{
|
|
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_unitsambientcolour)
|
|
{
|
|
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_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<ICmpWaterManager> 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)
|
|
{
|
|
// TODO: implement this, when WaterManager supports it
|
|
}
|
|
#define READ_COLOUR(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_COLOUR(el_colour, m_MapReader.pWaterMan->m_WaterColor)
|
|
READ_FLOAT(el_shininess, m_MapReader.pWaterMan->m_Shininess)
|
|
READ_FLOAT(el_waviness, m_MapReader.pWaterMan->m_Waviness)
|
|
READ_FLOAT(el_murkiness, m_MapReader.pWaterMan->m_Murkiness)
|
|
READ_COLOUR(el_tint, m_MapReader.pWaterMan->m_WaterTint)
|
|
READ_COLOUR(el_reflectiontint, m_MapReader.pWaterMan->m_ReflectionTint)
|
|
READ_FLOAT(el_reflectiontintstrength, m_MapReader.pWaterMan->m_ReflectionTintStrength)
|
|
|
|
#undef READ_FLOAT
|
|
#undef READ_COLOUR
|
|
|
|
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::ReadCinema(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(distortion);
|
|
EL(node);
|
|
EL(position);
|
|
EL(time);
|
|
AT(name);
|
|
AT(timescale);
|
|
AT(mode);
|
|
AT(style);
|
|
AT(growth);
|
|
AT(switch);
|
|
AT(x);
|
|
AT(y);
|
|
AT(z);
|
|
|
|
#undef EL
|
|
#undef AT
|
|
|
|
std::map<CStrW, CCinemaPath> pathList;
|
|
XERO_ITER_EL(parent, element)
|
|
{
|
|
int elementName = element.GetNodeName();
|
|
|
|
if ( elementName == el_path )
|
|
{
|
|
XMBAttributeList attrs = element.GetAttributes();
|
|
CStrW name(attrs.GetNamedItem(at_name).FromUTF8());
|
|
float timescale = attrs.GetNamedItem(at_timescale).ToFloat();
|
|
CCinemaData pathData;
|
|
pathData.m_Timescale = timescale;
|
|
TNSpline spline, backwardSpline;
|
|
|
|
XERO_ITER_EL(element, pathChild)
|
|
{
|
|
elementName = pathChild.GetNodeName();
|
|
attrs = pathChild.GetAttributes();
|
|
|
|
//Load distortion attributes
|
|
if ( elementName == el_distortion )
|
|
{
|
|
pathData.m_Mode = attrs.GetNamedItem(at_mode).ToInt();
|
|
pathData.m_Style = attrs.GetNamedItem(at_style).ToInt();
|
|
pathData.m_Growth = attrs.GetNamedItem(at_growth).ToInt();
|
|
pathData.m_Switch = attrs.GetNamedItem(at_switch).ToInt();
|
|
}
|
|
|
|
//Load node data used for spline
|
|
else if ( elementName == el_node )
|
|
{
|
|
SplineData data;
|
|
XERO_ITER_EL(pathChild, nodeChild)
|
|
{
|
|
elementName = nodeChild.GetNodeName();
|
|
attrs = nodeChild.GetAttributes();
|
|
|
|
//Fix?: assumes that time is last element
|
|
if ( elementName == el_position )
|
|
{
|
|
data.Position.X = attrs.GetNamedItem(at_x).ToFloat();
|
|
data.Position.Y = attrs.GetNamedItem(at_y).ToFloat();
|
|
data.Position.Z = attrs.GetNamedItem(at_z).ToFloat();
|
|
continue;
|
|
}
|
|
else if ( elementName == el_rotation )
|
|
{
|
|
data.Rotation.X = attrs.GetNamedItem(at_x).ToFloat();
|
|
data.Rotation.Y = attrs.GetNamedItem(at_y).ToFloat();
|
|
data.Rotation.Z = attrs.GetNamedItem(at_z).ToFloat();
|
|
continue;
|
|
}
|
|
else if ( elementName == el_time )
|
|
data.Distance = nodeChild.GetText().ToFloat();
|
|
else
|
|
debug_warn(L"Invalid cinematic element for node child");
|
|
|
|
backwardSpline.AddNode(data.Position, data.Rotation, data.Distance);
|
|
}
|
|
}
|
|
else
|
|
debug_warn(L"Invalid cinematic element for path child");
|
|
|
|
|
|
}
|
|
|
|
//Construct cinema path with data gathered
|
|
CCinemaPath temp(pathData, backwardSpline);
|
|
const std::vector<SplineData>& nodes = temp.GetAllNodes();
|
|
if ( nodes.empty() )
|
|
{
|
|
debug_warn(L"Failure loading cinematics");
|
|
return;
|
|
}
|
|
|
|
for ( std::vector<SplineData>::const_reverse_iterator it = nodes.rbegin();
|
|
it != nodes.rend(); ++it )
|
|
{
|
|
spline.AddNode(it->Position, it->Rotation, it->Distance);
|
|
}
|
|
|
|
CCinemaPath path(pathData, spline);
|
|
pathList[name] = path;
|
|
}
|
|
else
|
|
ENSURE("Invalid cinema child");
|
|
}
|
|
|
|
if (m_MapReader.pCinema)
|
|
m_MapReader.pCinema->SetAllPaths(pathList);
|
|
}
|
|
|
|
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<ICmpPlayerManager> cmpPlayerManager(sim, SYSTEM_ENTITY);
|
|
|
|
while (entity_idx < entities.Count)
|
|
{
|
|
// 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.Item(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;
|
|
CFixedVector3D Position;
|
|
CFixedVector3D Orientation;
|
|
|
|
// 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();
|
|
|
|
// <template>
|
|
if (element_name == el_template)
|
|
{
|
|
TemplateName = setting.GetText().FromUTF8();
|
|
}
|
|
// <player>
|
|
else if (element_name == el_player)
|
|
{
|
|
PlayerID = setting.GetText().ToInt();
|
|
}
|
|
// <position>
|
|
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)));
|
|
}
|
|
// <orientation>
|
|
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?
|
|
}
|
|
// <obstruction>
|
|
else if (element_name == el_obstruction)
|
|
{
|
|
XMBAttributeList attrs = setting.GetAttributes();
|
|
ControlGroup = attrs.GetNamedItem(at_group).ToInt();
|
|
ControlGroup2 = attrs.GetNamedItem(at_group2).ToInt();
|
|
}
|
|
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(L"Failed to load entity template '%ls'", TemplateName.c_str());
|
|
}
|
|
else
|
|
{
|
|
CmpPtr<ICmpPosition> cmpPosition(sim, ent);
|
|
if (cmpPosition)
|
|
{
|
|
cmpPosition->JumpTo(Position.X, Position.Z);
|
|
cmpPosition->SetYRotation(Orientation.Y);
|
|
// TODO: other parts of the position
|
|
}
|
|
|
|
CmpPtr<ICmpOwnership> cmpOwnership(sim, ent);
|
|
if (cmpOwnership)
|
|
cmpOwnership->SetOwner(PlayerID);
|
|
|
|
CmpPtr<ICmpObstruction> cmpObstruction(sim, ent);
|
|
if (cmpObstruction)
|
|
{
|
|
if (ControlGroup != INVALID_ENTITY)
|
|
cmpObstruction->SetControlGroup(ControlGroup);
|
|
if (ControlGroup2 != INVALID_ENTITY)
|
|
cmpObstruction->SetControlGroup2(ControlGroup2);
|
|
|
|
cmpObstruction->ResolveFoundationCollisions();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
int CXMLReader::ProgressiveRead()
|
|
{
|
|
// 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.Count)
|
|
{
|
|
XMBElement node = nodes.Item(node_idx);
|
|
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")
|
|
{
|
|
if (!m_MapReader.m_SkipEntities)
|
|
{
|
|
ret = ReadEntities(node, end_time);
|
|
if (ret != 0) // error or timed out
|
|
return ret;
|
|
}
|
|
}
|
|
else if (name == "Paths")
|
|
{
|
|
ReadCinema(node);
|
|
}
|
|
else if (name == "Triggers")
|
|
{
|
|
ReadTriggers(node);
|
|
}
|
|
else if (name == "Script")
|
|
{
|
|
if (m_MapReader.pSimulation2)
|
|
m_MapReader.pSimulation2->SetStartupScript(node.GetText());
|
|
}
|
|
else
|
|
{
|
|
debug_printf(L"Invalid XML element in map file: %hs\n", name.c_str());
|
|
debug_warn(L"Invalid map XML data");
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// progressive
|
|
int CMapReader::ReadXML()
|
|
{
|
|
if (!xml_reader)
|
|
xml_reader = new CXMLReader(filename_xml, *this);
|
|
|
|
int ret = xml_reader->ProgressiveRead();
|
|
// finished or failed
|
|
if (ret <= 0)
|
|
{
|
|
SAFE_DELETE(xml_reader);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int CMapReader::DelayLoadFinished()
|
|
{
|
|
// we were dynamically allocated by CWorld::Initialize
|
|
delete this;
|
|
|
|
return 0;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
int CMapReader::LoadRMSettings()
|
|
{
|
|
// copy random map settings over to sim
|
|
pSimulation2->SetMapSettings(m_ScriptSettings);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CMapReader::GenerateMap()
|
|
{
|
|
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.get());
|
|
|
|
// 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 val
|
|
shared_ptr<ScriptInterface::StructuredClone> results = m_MapGen->GetResults();
|
|
|
|
// Parse data into simulation context
|
|
CScriptValRooted data(pSimulation2->GetScriptInterface().GetContext(), pSimulation2->GetScriptInterface().ReadStructuredClone(results));
|
|
if (data.undefined())
|
|
{
|
|
// RMS failed - return to main menu
|
|
throw PSERROR_Game_World_MapLoadFailed("Error generating random map.\nCheck application log for details.");
|
|
}
|
|
else
|
|
{
|
|
m_MapData = 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");
|
|
|
|
// 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(L"CMapReader::ParseTerrain() failed to get '%hs' property", #prop);\
|
|
throw PSERROR_Game_World_MapLoadFailed("Error parsing terrain data.\nCheck application log for details"); }
|
|
|
|
u32 size;
|
|
GET_TERRAIN_PROPERTY(m_MapData.get(), size, size)
|
|
|
|
m_PatchesPerSide = size / PATCH_SIZE;
|
|
|
|
// flat heightmap of u16 data
|
|
GET_TERRAIN_PROPERTY(m_MapData.get(), height, m_Heightmap)
|
|
|
|
// load textures
|
|
std::vector<std::string> textureNames;
|
|
GET_TERRAIN_PROPERTY(m_MapData.get(), 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));
|
|
|
|
CScriptValRooted tileData;
|
|
GET_TERRAIN_PROPERTY(m_MapData.get(), tileData, tileData)
|
|
|
|
// parse tile data object into flat arrays
|
|
std::vector<u16> tileIndex;
|
|
std::vector<u16> tilePriority;
|
|
GET_TERRAIN_PROPERTY(tileData.get(), index, tileIndex);
|
|
GET_TERRAIN_PROPERTY(tileData.get(), 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");
|
|
|
|
// parse entities from map data
|
|
std::vector<Entity> entities;
|
|
|
|
if (!pSimulation2->GetScriptInterface().GetProperty(m_MapData.get(), "entities", entities))
|
|
LOGWARNING(L"CMapReader::ParseEntities() failed to get 'entities' property");
|
|
|
|
CSimulation2& sim = *pSimulation2;
|
|
CmpPtr<ICmpPlayerManager> 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(L"Failed to load entity template '%ls'", currEnt.templateName.c_str());
|
|
}
|
|
else
|
|
{
|
|
CmpPtr<ICmpPosition> cmpPosition(sim, ent);
|
|
if (cmpPosition)
|
|
{
|
|
cmpPosition->JumpTo(currEnt.position.X, currEnt.position.Z);
|
|
cmpPosition->SetYRotation(currEnt.rotation.Y);
|
|
// TODO: other parts of the position
|
|
}
|
|
|
|
CmpPtr<ICmpOwnership> 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<ICmpObstruction> 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
|
|
|
|
#define GET_ENVIRONMENT_PROPERTY(val, prop, out)\
|
|
if (!pSimulation2->GetScriptInterface().GetProperty(val, #prop, out))\
|
|
LOGWARNING(L"CMapReader::ParseEnvironment() failed to get '%hs' property", #prop);
|
|
|
|
CScriptValRooted envObj;
|
|
GET_ENVIRONMENT_PROPERTY(m_MapData.get(), Environment, envObj)
|
|
|
|
if (envObj.undefined())
|
|
{
|
|
LOGWARNING(L"CMapReader::ParseEnvironment(): Environment settings not found");
|
|
return 0;
|
|
}
|
|
|
|
m_LightEnv.SetLightingModel("standard");
|
|
|
|
std::wstring skySet;
|
|
GET_ENVIRONMENT_PROPERTY(envObj.get(), SkySet, skySet)
|
|
if (pSkyMan)
|
|
pSkyMan->SetSkySet(skySet);
|
|
|
|
CColor sunColor;
|
|
GET_ENVIRONMENT_PROPERTY(envObj.get(), SunColour, sunColor)
|
|
m_LightEnv.m_SunColor = RGBColor(sunColor.r, sunColor.g, sunColor.b);
|
|
|
|
GET_ENVIRONMENT_PROPERTY(envObj.get(), SunElevation, m_LightEnv.m_Elevation)
|
|
GET_ENVIRONMENT_PROPERTY(envObj.get(), SunRotation, m_LightEnv.m_Rotation)
|
|
|
|
CColor terrainAmbientColor;
|
|
GET_ENVIRONMENT_PROPERTY(envObj.get(), TerrainAmbientColour, terrainAmbientColor)
|
|
m_LightEnv.m_TerrainAmbientColor = RGBColor(terrainAmbientColor.r, terrainAmbientColor.g, terrainAmbientColor.b);
|
|
|
|
CColor unitsAmbientColor;
|
|
GET_ENVIRONMENT_PROPERTY(envObj.get(), UnitsAmbientColour, unitsAmbientColor)
|
|
m_LightEnv.m_UnitsAmbientColor = RGBColor(unitsAmbientColor.r, unitsAmbientColor.g, unitsAmbientColor.b);
|
|
|
|
// Water properties
|
|
CScriptValRooted waterObj;
|
|
GET_ENVIRONMENT_PROPERTY(envObj.get(), Water, waterObj)
|
|
|
|
CScriptValRooted waterBodyObj;
|
|
GET_ENVIRONMENT_PROPERTY(waterObj.get(), WaterBody, waterBodyObj)
|
|
|
|
// Water level - necessary
|
|
float waterHeight;
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Height, waterHeight)
|
|
|
|
CmpPtr<ICmpWaterManager> cmpWaterManager(*pSimulation2, SYSTEM_ENTITY);
|
|
ENSURE(cmpWaterManager);
|
|
cmpWaterManager->SetWaterLevel(entity_pos_t::FromFloat(waterHeight));
|
|
|
|
// If we have graphics, get rest of settings
|
|
if (pWaterMan)
|
|
{
|
|
std::wstring waterType;
|
|
// TODO: Water type not implemented
|
|
//GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Type, waterType)
|
|
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Colour, pWaterMan->m_WaterColor)
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Shininess, pWaterMan->m_Shininess)
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Waviness, pWaterMan->m_Waviness)
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Murkiness, pWaterMan->m_Murkiness)
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), Tint, pWaterMan->m_WaterTint)
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), ReflectionTint, pWaterMan->m_ReflectionTint)
|
|
GET_ENVIRONMENT_PROPERTY(waterBodyObj.get(), ReflectionTintStrength, pWaterMan->m_ReflectionTintStrength)
|
|
}
|
|
|
|
m_LightEnv.CalculateSunDirection();
|
|
|
|
#undef GET_ENVIRONMENT_PROPERTY
|
|
|
|
return 0;
|
|
}
|
|
|
|
int CMapReader::ParseCamera()
|
|
{
|
|
// 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(L"CMapReader::ParseCamera() failed to get '%hs' property", #prop);
|
|
|
|
CScriptValRooted cameraObj;
|
|
GET_CAMERA_PROPERTY(m_MapData.get(), Camera, cameraObj)
|
|
|
|
if (!cameraObj.undefined())
|
|
{ // If camera property exists, read values
|
|
CFixedVector3D pos;
|
|
GET_CAMERA_PROPERTY(cameraObj.get(), Position, pos)
|
|
translation = pos;
|
|
|
|
GET_CAMERA_PROPERTY(cameraObj.get(), Rotation, rotation)
|
|
GET_CAMERA_PROPERTY(cameraObj.get(), 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;
|
|
}
|