Index: ps/trunk/binaries/data/mods/public/simulation/components/Visibility.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/Visibility.js (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/components/Visibility.js (revision 16022)
@@ -1,65 +1,79 @@
const VIS_HIDDEN = 0;
const VIS_FOGGED = 1;
const VIS_VISIBLE = 2;
function Visibility() {}
Visibility.prototype.Schema =
- "";
+ "" +
+ "" +
+ "" +
+ "" +
+ "" +
+ "";
Visibility.prototype.Init = function()
{
-
+
};
/**
* This function is called for entities in explored territory.
* isOutsideFog: true if we're in the vision range of a unit, false otherwise
* forceRetainInFog: useful for previewed entities, see the RangeManager system component documentation
*/
Visibility.prototype.GetLosVisibility = function(player, isOutsideFog, forceRetainInFog)
{
if (isOutsideFog)
{
var cmpMirage = Engine.QueryInterface(this.entity, IID_Mirage);
if (cmpMirage)
return VIS_HIDDEN;
return VIS_VISIBLE;
}
// Fogged if the 'retain in fog' flag is set, and in a non-visible explored region
- var cmpVision = Engine.QueryInterface(this.entity, IID_Vision);
- if (!forceRetainInFog && !(cmpVision && cmpVision.GetRetainInFog()))
+ if (!forceRetainInFog && !this.GetRetainInFog())
return VIS_HIDDEN;
var cmpMirage = Engine.QueryInterface(this.entity, IID_Mirage);
if (cmpMirage && cmpMirage.GetPlayer() == player)
return VIS_FOGGED;
var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
if (!cmpOwnership)
return VIS_FOGGED;
if (cmpOwnership.GetOwner() == player)
{
var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (!cmpFogging)
return VIS_FOGGED;
// Fogged entities must not disappear while the mirage is not ready
if (!cmpFogging.IsMiraged(player))
return VIS_FOGGED;
return VIS_HIDDEN;
}
// Fogged entities must not disappear while the mirage is not ready
var cmpFogging = Engine.QueryInterface(this.entity, IID_Fogging);
if (cmpFogging && cmpFogging.WasSeen(player) && !cmpFogging.IsMiraged(player))
return VIS_FOGGED;
return VIS_HIDDEN;
};
+Visibility.prototype.GetRetainInFog = function()
+{
+ return this.template.RetainInFog == "true";
+};
+
+Visibility.prototype.GetAlwaysVisible = function()
+{
+ return this.template.AlwaysVisible == "true";
+};
+
Engine.RegisterComponentType(IID_Visibility, "Visibility", Visibility);
Index: ps/trunk/binaries/data/mods/public/simulation/templates/gaia/fauna_shark.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/gaia/fauna_shark.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/gaia/fauna_shark.xml (revision 16022)
@@ -1,59 +1,61 @@
7.5150truefalsegaiaGreat WhiteSharkSeaCreaturegaia/fauna_fish.png-1true5.0circle/128x128.pngcircle/128x128_mask.png
-
+ false
+
+ 100fauna/shark.xmlpassive100.060.010000030000012ship4.035.0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/bench.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/bench.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/bench.xml (revision 16022)
@@ -1,48 +1,50 @@
1101Special4.075gaiaBenchWooden BenchA short wooden bench.gaia/special_fence.png1010006.0
+
+ true
+ 4
- trueprops/special/eyecandy/bench_1.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/bridge_hele.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/bridge_hele.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/bridge_hele.xml (revision 16022)
@@ -1,35 +1,37 @@
Special10.0200000gaiaBridgeBridgeRoman engineers constructed bridges using concrete, which they called Opus caementicium.gaia/special_blank.png
+
+ true
+ 72
- trueprops/special/eyecandy/bridge_edge_hele.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/bridge_wooden.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/bridge_wooden.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/bridge_wooden.xml (revision 16022)
@@ -1,35 +1,37 @@
Special10.0200000gaiaWooden BridgeWooden BridgeRoman engineers constructed bridges using concrete, which they called Opus caementicium.gaia/special_blank.png
+
+ true
+ 72
- trueprops/special/eyecandy/bridge_edge_wooden.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric.xml (revision 16022)
@@ -1,45 +1,47 @@
252Fence4.0200heleColumnDoric ColumnA column of the understated Greek Doric Order.gaia/special_fence.png1002008.0
+
+ true
+ 4
- trueprops/special/eyecandy/column_doric.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric_fallen.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric_fallen.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric_fallen.xml (revision 16022)
@@ -1,45 +1,47 @@
252Special4.0200heleColumnFallen Doric ColumnA column fallen off some Greek ruin.gaia/special_fence.png1002006.0
+
+ true
+ 4
- trueprops/special/eyecandy/column_doric_fallen.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric_fallen_b.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric_fallen_b.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/column_doric_fallen_b.xml (revision 16022)
@@ -1,45 +1,47 @@
252Special4.0100heleColumnFallen Doric ColumnColumn drums fallen off some Greek ruin.gaia/special_fence.png1001006.0
+
+ true
+ 4
- trueprops/special/eyecandy/column_doric_fallen_b.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_long.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_long.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_long.xml (revision 16022)
@@ -1,49 +1,51 @@
1101Special4.0100gaiaFenceLong Wooden FenceA split rail wooden fence.gaia/special_fence.png1020006.0
+
+ true
+ 4
- truetemp/fence_wood_long_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_short.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_short.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_short.xml (revision 16022)
@@ -1,49 +1,51 @@
1101Special4.050gaiaFenceShort Wooden FenceA split rail wooden fence.gaia/special_fence.png1010006.0
+
+ true
+ 4
- truetemp/fence_wood_short_a.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_stone.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_stone.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/fence_stone.xml (revision 16022)
@@ -1,46 +1,48 @@
2102Special4.0200gaiaStone FenceStone FenceA stone fence.gaia/special_fence.png1002006.0
+
+ true
+ 4
- trueprops/special/eyecandy/fence_stone_medit.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_propylaea.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_propylaea.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_propylaea.xml (revision 16022)
@@ -1,54 +1,56 @@
Special10200002002008.02000helePorticoPropylaeaA Propylaea was used as a gate into a sacred precinct. The most famous of these is the monumental gate at the top of the Acropolis in Athens.Add +10 to Population Cap.structures/tholos.png10007575false4065536
+
+ true
+ 20
- truestructures/hellenes/propylaea.xmlstructures/fndn_6x6.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_royal_stoa.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_royal_stoa.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_royal_stoa.xml (revision 16022)
@@ -1,67 +1,69 @@
20.040.010.0Stoa101500010015010.01500heleStoaHellenic Royal StoaA structure built for civic purposes. Stoas eventually became meeting places for philosophy and commerce. They were usually built within the Agora, or city center, of a Greek city.Add +10 to Population Cap. Recruit special units.gaia/special_stoa.png10001015false40655360.7
units/mace_thureophoros
units/mace_thorakites
units/thrace_black_cloak
+
+ true
+ 40
- truespecial/greek_stoa_great.xmlstructures/fndn_wall.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_stoa.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_stoa.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/hellenic_stoa.xml (revision 16022)
@@ -1,55 +1,57 @@
Stoa101100010010010.01100heleStoaHellenic StoaA structure built for civic purposes. Stoa eventually became meeting places for philosophy and commerce. They were usually built within the Agora, or city center, of a Greek city.Add +10 to Population Cap.gaia/special_stoa.png10005050false3665536
+
+ true
+ 40
- truespecial/greek_stoa.xmlstructures/fndn_wall.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/obelisk.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/obelisk.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/obelisk.xml (revision 16022)
@@ -1,44 +1,46 @@
2102Special10.01000gaiaObeliskEgyptian ObeliskA monumental ornamental structure built by the Egyptians of old.-ConquestCriticalgaia/special_obelisk.png1000200200
+
+ true
+ 12
- trueprops/special/eyecandy/obelisk.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/pyramid_great.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/pyramid_great.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/pyramid_great.xml (revision 16022)
@@ -1,46 +1,48 @@
10.0gaiaPyramidGreat PyramidA monumental ornamental structure built by the Egyptians of old.gaia/special_pyramid.png200-2.010000stone.ruins40attack/destruction/building_collapse_large.xml6.00.612.0
+
+ true
+ 72
- truespecial/pyramid.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/pyramid_minor.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/pyramid_minor.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/pyramid_minor.xml (revision 16022)
@@ -1,40 +1,42 @@
10.0gaiaPyramidMinor PyramidA monumental ornamental structure built by the Egyptians of old.gaia/special_pyramid.png5000stone.ruins30attack/destruction/building_collapse_large.xml6.00.612.0
+
+ true
+ 40
- truespecial/pyramid_minor.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/table_rectangle.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/table_rectangle.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/table_rectangle.xml (revision 16022)
@@ -1,49 +1,51 @@
1101Special4.0100gaiaTableRectangle TableA wooden table.gaia/special_fence.png1020006.0
+
+ true
+ 4
- trueprops/special/eyecandy/table_rectangle.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/table_square.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/table_square.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/table_square.xml (revision 16022)
@@ -1,49 +1,51 @@
1101Special4.0100gaiaTableSquare TableA wooden table.gaia/special_fence.png1020006.0
+
+ true
+ 4
- trueprops/special/eyecandy/table_square.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/other/unfinished_greek_temple.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/other/unfinished_greek_temple.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/other/unfinished_greek_temple.xml (revision 16022)
@@ -1,37 +1,39 @@
12.0heleRuinsUnfinished Greek TempleThe Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis.This temple is unfinished and has fallen to ruin.structures/temple.png102000306.00.612.0
+
+ true
+ 40
- trueprops/special/eyecandy/greek_temple_unfinished.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/special/actor.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/special/actor.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/special/actor.xml (revision 16022)
@@ -1,21 +1,22 @@
0uprightfalse6.0
-
-
- 0
+ truefalse
+
+
+ 0(should be overridden)falsefalsefalse
Index: ps/trunk/binaries/data/mods/public/simulation/templates/special/marker_object_sound.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/special/marker_object_sound.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/special/marker_object_sound.xml (revision 16022)
@@ -1,33 +1,33 @@
Special5.0200000gaiaMarkerMarkergaia/special_blank.png
-
+ true
-
+
props/special/common/marker_object_sound.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/special/rallypoint.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/special/rallypoint.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/special/rallypoint.xml (revision 16022)
@@ -1,9 +1,9 @@
props/special/common/waypoint_flag.xml
-
+ true
-
+
Index: ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers_wonder.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers_wonder.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/structures/pers_wonder.xml (revision 16022)
@@ -1,37 +1,39 @@
10.04400persHanging Gardens of BabylonA magnificent structure built in the 6th century BC by the Neo-Babylonian king Nebuchadnezzar II in order to please his wife Amytis of Media, who was homesick for the gardens and mountains of her homeland.2006.00.612.0100
+
+ true
+ 72
- truestructures/fndn_8x8.xmlstructures/persians/gardens_struct.xml
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_gaia.xml (revision 16022)
@@ -1,34 +1,35 @@
gaiaGaiatruetruetruetruetruefalsefalse2.00.3335.0
-
-
- 0
+ truefalse
+
+
+ 0falsetruefalse
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure.xml (revision 16022)
@@ -1,116 +1,117 @@
11111100Ranged Infantrylandown001000000.03.09.8corpse0truetrueStructureStructure ConquestCriticalstructuretruetruetruetruetruefalsefalsespecial/rallypointart/textures/misc/rallypoint_line.pngart/textures/misc/rallypoint_line_mask.png0.2squarerounddefaultdefaultoutline_border.pngoutline_border_mask.png0.4interface/complete/building/complete_universal.xmlattack/destruction/building_collapse_large.xmlinterface/alarm/alarm_attackplayer.xmlattack/weapon/arrowfly.xmlattack/impact/arrow_metal.xml6.00.612.05
-
-
- 40
+ truefalse
+
+
+ 40falsetruefalse
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_wonder.xml (revision 16022)
@@ -1,88 +1,90 @@
52552102Wonder1000010001000100010.05000rubble/rubble_stone_6x6WonderBring glory to your civilization and add large tracts of land to your empire.
City
Wonder
structures/wonder.pngphase_city20001001001000.7
pop_wonder
interface/complete/building/complete_wonder.xmlattack/destruction/building_collapse_large.xml6.00.612.0true10065536
+
+ true
+ 72
- truestructures/fndn_6x6.xml300
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_fauna.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_fauna.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_fauna.xml (revision 16022)
@@ -1,48 +1,50 @@
02.25FaunaAnimal Organic -ConquestCriticalgaia/fauna_generic.png10food99false8.024.02000800015000600006.515.0
-
+ true
+
+ 0false
Index: ps/trunk/source/ps/TemplateLoader.cpp
===================================================================
--- ps/trunk/source/ps/TemplateLoader.cpp (revision 16021)
+++ ps/trunk/source/ps/TemplateLoader.cpp (revision 16022)
@@ -1,526 +1,525 @@
/* Copyright (C) 2014 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 "TemplateLoader.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/XML/Xeromyces.h"
#include "lib/utf8.h"
static const wchar_t TEMPLATE_ROOT[] = L"simulation/templates/";
static const wchar_t ACTOR_ROOT[] = L"art/actors/";
static CParamNode NULL_NODE(false);
bool CTemplateLoader::LoadTemplateFile(const std::string& templateName, int depth)
{
// If this file was already loaded, we don't need to do anything
if (m_TemplateFileData.find(templateName) != m_TemplateFileData.end())
return true;
// Handle infinite loops more gracefully than running out of stack space and crashing
if (depth > 100)
{
LOGERROR(L"Probable infinite inheritance loop in entity template '%hs'", templateName.c_str());
return false;
}
// Handle special case "actor|foo"
if (templateName.find("actor|") == 0)
{
ConstructTemplateActor(templateName.substr(6), m_TemplateFileData[templateName]);
return true;
}
// Handle special case "preview|foo"
if (templateName.find("preview|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(8);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], false);
return true;
}
// Handle special case "corpse|foo"
if (templateName.find("corpse|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(7);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyPreviewSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName], true);
return true;
}
// Handle special case "mirage|foo"
if (templateName.find("mirage|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(7);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyMirageSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
return true;
}
// Handle special case "foundation|foo"
if (templateName.find("foundation|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(11);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyFoundationSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
return true;
}
// Handle special case "construction|foo"
if (templateName.find("construction|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(13);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyConstructionSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
return true;
}
// Handle special case "resource|foo"
if (templateName.find("resource|") == 0)
{
// Load the base entity template, if it wasn't already loaded
std::string baseName = templateName.substr(9);
if (!LoadTemplateFile(baseName, depth+1))
{
LOGERROR(L"Failed to load entity template '%hs'", baseName.c_str());
return false;
}
// Copy a subset to the requested template
CopyResourceSubset(m_TemplateFileData[templateName], m_TemplateFileData[baseName]);
return true;
}
// Normal case: templateName is an XML file:
VfsPath path = VfsPath(TEMPLATE_ROOT) / wstring_from_utf8(templateName + ".xml");
CXeromyces xero;
PSRETURN ok = xero.Load(g_VFS, path);
if (ok != PSRETURN_OK)
return false; // (Xeromyces already logged an error with the full filename)
int attr_parent = xero.GetAttributeID("parent");
CStr parentName = xero.GetRoot().GetAttributes().GetNamedItem(attr_parent);
if (!parentName.empty())
{
// To prevent needless complexity in template design, we don't allow |-separated strings as parents
if (parentName.find('|') != parentName.npos)
{
LOGERROR(L"Invalid parent '%hs' in entity template '%hs'", parentName.c_str(), templateName.c_str());
return false;
}
// Ensure the parent is loaded
if (!LoadTemplateFile(parentName, depth+1))
{
LOGERROR(L"Failed to load parent '%hs' of entity template '%hs'", parentName.c_str(), templateName.c_str());
return false;
}
CParamNode& parentData = m_TemplateFileData[parentName];
// Initialise this template with its parent
m_TemplateFileData[templateName] = parentData;
}
// Load the new file into the template data (overriding parent values)
CParamNode::LoadXML(m_TemplateFileData[templateName], xero, wstring_from_utf8(templateName).c_str());
return true;
}
static Status AddToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
std::vector& templates = *(std::vector*)cbData;
// Strip the .xml extension
VfsPath pathstem = pathname.ChangeExtension(L"");
// Strip the root from the path
std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT)-1);
// We want to ignore template_*.xml templates, since they should never be built in the editor
if (name.substr(0, 9) == L"template_")
return INFO::OK;
templates.push_back(std::string(name.begin(), name.end()));
return INFO::OK;
}
static Status AddActorToTemplates(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData)
{
std::vector& templates = *(std::vector*)cbData;
// Strip the root from the path
std::wstring name = pathname.string().substr(ARRAY_SIZE(ACTOR_ROOT)-1);
templates.push_back("actor|" + std::string(name.begin(), name.end()));
return INFO::OK;
}
std::vector CTemplateLoader::FindPlaceableTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType, ScriptInterface& scriptInterface)
{
JSContext* cx = scriptInterface.GetContext();
JSAutoRequest rq(cx);
std::vector templates;
Status ok;
VfsPath templatePath;
if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES)
{
JS::RootedValue placeablesFilter(cx);
scriptInterface.ReadJSONFile("simulation/data/placeablesFilter.json", &placeablesFilter);
std::vector folders;
if (scriptInterface.GetProperty(placeablesFilter, "templates", folders))
{
templatePath = VfsPath(TEMPLATE_ROOT) / path;
//I have every object inside, just run for each
for (std::vector::iterator iterator = folders.begin(); iterator != folders.end();++iterator)
{
JS::RootedValue val(cx, (*iterator).get());
std::string directoryPath;
std::wstring fileFilter;
scriptInterface.GetProperty(val, "directory", directoryPath);
scriptInterface.GetProperty(val, "file", fileFilter);
VfsPaths filenames;
if (vfs::GetPathnames(g_VFS, templatePath / (directoryPath + "/"), fileFilter.c_str(), filenames) != INFO::OK)
continue;
for (VfsPaths::iterator it = filenames.begin(); it != filenames.end(); ++it)
{
VfsPath filename = *it;
// Strip the .xml extension
VfsPath pathstem = filename.ChangeExtension(L"");
// Strip the root from the path
std::wstring name = pathstem.string().substr(ARRAY_SIZE(TEMPLATE_ROOT) - 1);
templates.push_back(std::string(name.begin(), name.end()));
}
}
}
}
if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES)
{
templatePath = VfsPath(ACTOR_ROOT) / path;
if (includeSubdirectories)
ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
else
ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml");
WARN_IF_ERR(ok);
}
if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
LOGERROR(L"Undefined template type (valid: all, simulation, actor)");
return templates;
}
std::vector CTemplateLoader::FindTemplates(const std::string& path, bool includeSubdirectories, ETemplatesType templatesType)
{
std::vector templates;
Status ok;
VfsPath templatePath;
if (templatesType == SIMULATION_TEMPLATES || templatesType == ALL_TEMPLATES)
{
templatePath = VfsPath(TEMPLATE_ROOT) / path;
if (includeSubdirectories)
ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
else
ok = vfs::ForEachFile(g_VFS, templatePath, AddToTemplates, (uintptr_t)&templates, L"*.xml");
WARN_IF_ERR(ok);
}
if (templatesType == ACTOR_TEMPLATES || templatesType == ALL_TEMPLATES)
{
templatePath = VfsPath(ACTOR_ROOT) / path;
if (includeSubdirectories)
ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml", vfs::DIR_RECURSIVE);
else
ok = vfs::ForEachFile(g_VFS, templatePath, AddActorToTemplates, (uintptr_t)&templates, L"*.xml");
WARN_IF_ERR(ok);
}
if (templatesType != SIMULATION_TEMPLATES && templatesType != ACTOR_TEMPLATES && templatesType != ALL_TEMPLATES)
LOGERROR(L"Undefined template type (valid: all, simulation, actor)");
return templates;
}
const CParamNode& CTemplateLoader::GetTemplateFileData(const std::string& templateName)
{
// Load the template if necessary
if (!LoadTemplateFile(templateName, 0))
{
LOGERROR(L"Failed to load entity template '%hs'", templateName.c_str());
return NULL_NODE;
}
return m_TemplateFileData[templateName];
}
void CTemplateLoader::ConstructTemplateActor(const std::string& actorName, CParamNode& out)
{
// Load the base actor template if necessary
const char* templateName = "special/actor";
if (!LoadTemplateFile(templateName, 0))
{
LOGERROR(L"Failed to load entity template '%hs'", templateName);
return;
}
// Copy the actor template
out = m_TemplateFileData[templateName];
// Initialise the actor's name and make it an Atlas selectable entity.
std::wstring actorNameW = wstring_from_utf8(actorName);
std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(actorNameW));
std::string xml = ""
"" + name + ""
// arbitrary-sized Footprint definition to make actors' selection outlines show up in Atlas
"1.0"
""
""
"actor.pngactor_mask.png"
""
"";
CParamNode::LoadXMLString(out, xml.c_str(), actorNameW.c_str());
}
void CTemplateLoader::CopyPreviewSubset(CParamNode& out, const CParamNode& in, bool corpse)
{
// We only want to include components which are necessary (for the visual previewing of an entity)
// and safe (i.e. won't do anything that affects the synchronised simulation state), so additions
// to this list should be carefully considered
std::set permittedComponentTypes;
permittedComponentTypes.insert("Identity");
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("Visibility");
permittedComponentTypes.insert("VisualActor");
permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("Obstruction");
permittedComponentTypes.insert("Decay");
permittedComponentTypes.insert("BuildRestrictions");
// Need these for the Actor Viewer:
permittedComponentTypes.insert("Attack");
permittedComponentTypes.insert("UnitMotion");
permittedComponentTypes.insert("Sound");
// (This set could be initialised once and reused, but it's not worth the effort)
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
// Disable the Obstruction component (if there is one) so it doesn't affect pathfinding
// (but can still be used for testing this entity for collisions against others)
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
CParamNode::LoadXMLString(out, "false");
if (!corpse)
{
// Previews should not cast shadows
if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
CParamNode::LoadXMLString(out, "");
// Previews should always be visible in fog-of-war/etc
- CParamNode::LoadXMLString(out, "0falsetrue");
+ CParamNode::LoadXMLString(out, "falsetrue");
}
if (corpse)
{
// Corpses should include decay components and un-inactivate them
if (out.GetChild("Entity").GetChild("Decay").IsOk())
CParamNode::LoadXMLString(out, "");
// Corpses shouldn't display silhouettes (especially since they're often half underground)
if (out.GetChild("Entity").GetChild("VisualActor").IsOk())
CParamNode::LoadXMLString(out, "false");
// Corpses should remain visible in fog-of-war
- CParamNode::LoadXMLString(out, "0truefalse");
+ CParamNode::LoadXMLString(out, "truefalse");
}
}
void CTemplateLoader::CopyMirageSubset(CParamNode& out, const CParamNode& in)
{
// Currently used for mirage entities replacing real ones in fog-of-war
std::set permittedComponentTypes;
permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("Minimap");
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("Selectable");
- permittedComponentTypes.insert("Visibility");
permittedComponentTypes.insert("VisualActor");
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
// Select a subset of identity data. We don't want to have, for example, a CC mirage
// that has also the CC class and then prevents construction of other CCs
std::set identitySubset;
identitySubset.insert("Civ");
identitySubset.insert("GenericName");
identitySubset.insert("SpecificName");
identitySubset.insert("Tooltip");
identitySubset.insert("History");
identitySubset.insert("Icon");
CParamNode identity;
CParamNode::LoadXMLString(identity, "");
identity.CopyFilteredChildrenOfChild(in.GetChild("Entity"), "Identity", identitySubset);
CParamNode::LoadXMLString(out, (""+utf8_from_wstring(identity.ToXML())+"").c_str());
// Set the entity as mirage entity
CParamNode::LoadXMLString(out, "");
- CParamNode::LoadXMLString(out, "0truefalse");
+ CParamNode::LoadXMLString(out, "truefalse");
}
void CTemplateLoader::CopyFoundationSubset(CParamNode& out, const CParamNode& in)
{
// TODO: this is all kind of yucky and hard-coded; it'd be nice to have a more generic
// extensible scriptable way to define these subsets
std::set permittedComponentTypes;
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");
permittedComponentTypes.insert("Identity");
permittedComponentTypes.insert("BuildRestrictions");
permittedComponentTypes.insert("Obstruction");
permittedComponentTypes.insert("Selectable");
permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("Fogging");
permittedComponentTypes.insert("Armour");
permittedComponentTypes.insert("Health");
permittedComponentTypes.insert("StatusBars");
permittedComponentTypes.insert("OverlayRenderer");
permittedComponentTypes.insert("Decay");
permittedComponentTypes.insert("Cost");
permittedComponentTypes.insert("Sound");
permittedComponentTypes.insert("Visibility");
permittedComponentTypes.insert("Vision");
permittedComponentTypes.insert("AIProxy");
permittedComponentTypes.insert("RallyPoint");
permittedComponentTypes.insert("RallyPointRenderer");
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
// Switch the actor to foundation mode
CParamNode::LoadXMLString(out, "");
// Add the Foundation component, to deal with the construction process
CParamNode::LoadXMLString(out, "");
// Initialise health to 1
CParamNode::LoadXMLString(out, "1");
// Foundations shouldn't initially block unit movement
if (out.GetChild("Entity").GetChild("Obstruction").IsOk())
CParamNode::LoadXMLString(out, "truetrue");
// Don't provide population bonuses yet (but still do take up population cost)
if (out.GetChild("Entity").GetChild("Cost").IsOk())
CParamNode::LoadXMLString(out, "0");
// Foundations should be visible themselves in fog-of-war if their base template is,
// but shouldn't have any vision range
if (out.GetChild("Entity").GetChild("Vision").IsOk())
CParamNode::LoadXMLString(out, "0");
}
void CTemplateLoader::CopyConstructionSubset(CParamNode& out, const CParamNode& in)
{
// Currently used for buildings rising during construction
// Mostly serves to filter out components like Vision, UnitAI, etc.
std::set permittedComponentTypes;
permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
}
void CTemplateLoader::CopyResourceSubset(CParamNode& out, const CParamNode& in)
{
// Currently used for animals which die and leave a gatherable corpse.
// Mostly serves to filter out components like Vision, UnitAI, etc.
std::set permittedComponentTypes;
permittedComponentTypes.insert("Ownership");
permittedComponentTypes.insert("Position");
permittedComponentTypes.insert("VisualActor");
permittedComponentTypes.insert("Identity");
permittedComponentTypes.insert("Obstruction");
permittedComponentTypes.insert("Minimap");
permittedComponentTypes.insert("ResourceSupply");
permittedComponentTypes.insert("Selectable");
permittedComponentTypes.insert("Footprint");
permittedComponentTypes.insert("StatusBars");
permittedComponentTypes.insert("OverlayRenderer");
permittedComponentTypes.insert("Sound");
permittedComponentTypes.insert("AIProxy");
CParamNode::LoadXMLString(out, "");
out.CopyFilteredChildrenOfChild(in, "Entity", permittedComponentTypes);
}
Index: ps/trunk/source/simulation2/components/CCmpVision.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpVision.cpp (revision 16021)
+++ ps/trunk/source/simulation2/components/CCmpVision.cpp (revision 16022)
@@ -1,120 +1,100 @@
-/* Copyright (C) 2012 Wildfire Games.
+/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpVision.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPlayerManager.h"
#include "simulation2/components/ICmpValueModificationManager.h"
class CCmpVision : public ICmpVision
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeGloballyToMessageType(MT_ValueModification);
}
DEFAULT_COMPONENT_ALLOCATOR(Vision)
// Template state:
entity_pos_t m_Range, m_BaseRange;
- bool m_RetainInFog;
- bool m_AlwaysVisible;
static std::string GetSchema()
{
return
""
""
- ""
- ""
- ""
- ""
- ""
- ""
"";
}
virtual void Init(const CParamNode& paramNode)
{
m_BaseRange = m_Range = paramNode.GetChild("Range").ToFixed();
- m_RetainInFog = paramNode.GetChild("RetainInFog").ToBool();
- m_AlwaysVisible = paramNode.GetChild("AlwaysVisible").ToBool();
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
// No dynamic state to serialize
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_ValueModification:
{
const CMessageValueModification& msgData = static_cast (msg);
if (msgData.component == L"Vision")
{
CmpPtr cmpValueModificationManager(GetSystemEntity());
entity_pos_t newRange = cmpValueModificationManager->ApplyModifications(L"Vision/Range", m_BaseRange, GetEntityId());
if (newRange != m_Range)
{
// Update our vision range and broadcast message
entity_pos_t oldRange = m_Range;
m_Range = newRange;
CMessageVisionRangeChanged msg(GetEntityId(), oldRange, newRange);
GetSimContext().GetComponentManager().BroadcastMessage(msg);
}
}
break;
}
}
}
virtual entity_pos_t GetRange()
{
return m_Range;
}
-
- virtual bool GetRetainInFog()
- {
- return m_RetainInFog;
- }
-
- virtual bool GetAlwaysVisible()
- {
- return m_AlwaysVisible;
- }
};
REGISTER_COMPONENT_TYPE(Vision)
Index: ps/trunk/source/simulation2/components/ICmpVisibility.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpVisibility.h (revision 16021)
+++ ps/trunk/source/simulation2/components/ICmpVisibility.h (revision 16022)
@@ -1,33 +1,37 @@
/* Copyright (C) 2014 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_ICMPVISIBILITY
#define INCLUDED_ICMPVISIBILITY
#include "simulation2/system/Interface.h"
#include "simulation2/components/ICmpRangeManager.h"
class ICmpVisibility : public IComponent
{
public:
- virtual ICmpRangeManager::ELosVisibility GetLosVisibility(player_id_t player, bool isOutsideFog, bool forceRetainInFog) = 0;
+ virtual ICmpRangeManager::ELosVisibility GetLosVisibility(player_id_t player, bool isOutsideFog, bool forceRetainInFog) = 0;
+
+ virtual bool GetRetainInFog() = 0;
+
+ virtual bool GetAlwaysVisible() = 0;
DECLARE_INTERFACE_TYPE(Visibility)
};
#endif // INCLUDED_ICMPVISIBILITY
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_rubble.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_rubble.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_rubble.xml (revision 16022)
@@ -1,26 +1,28 @@
15.00.200uprightfalse6.0
-
- 0
+ truefalse
+
+
+ 0structures/rubble_stone_3x3.xmlfalsefalsefalse
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_gaia_settlement.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_gaia_settlement.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_structure_gaia_settlement.xml (revision 16022)
@@ -1,34 +1,36 @@
8.0gaiaSettlementSettlementBuild a Civic Center at this location to expand your territory.gaia/special_settlement.pngsettlementtruefalsefalsetruetruefalsefalse
-
- 0
+ truefalse
+
+
+ 0
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit.xml (revision 16022)
@@ -1,118 +1,119 @@
115101000080.00.010.02.5corpse1000falsefalseUnitUnit ConquestCriticalunittruetruefalsefalsetruefalsefalse2.01.0110101010circle/128x128.pngcircle/128x128_mask.pnginterface/alarm/alarm_attackplayer.xml2.00.3335.02aggressive12.0falsetruefalse915.050.00.00.10.2defaultdefault
-
-
- 10
+ falsefalse
+
+
+ 10truefalsefalse
Index: ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_fauna_decorative.xml
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_fauna_decorative.xml (revision 16021)
+++ ps/trunk/binaries/data/mods/public/simulation/templates/template_unit_fauna_decorative.xml (revision 16022)
@@ -1,51 +1,53 @@
0uprightfalse6.0circle/128x128.pngcircle/128x128_mask.png0passivepassivefalsefalse2010.01000010000012false8.0unrestricteddefault
-
+ true
- 0false
+
+
+ 0falsefalsefalse
Index: ps/trunk/source/simulation2/components/CCmpRangeManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpRangeManager.cpp (revision 16021)
+++ ps/trunk/source/simulation2/components/CCmpRangeManager.cpp (revision 16022)
@@ -1,2053 +1,2053 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpRangeManager.h"
#include "ICmpTerrain.h"
#include "simulation2/system/EntityMap.h"
#include "simulation2/MessageTypes.h"
#include "simulation2/components/ICmpFogging.h"
#include "simulation2/components/ICmpMirage.h"
#include "simulation2/components/ICmpOwnership.h"
#include "simulation2/components/ICmpPosition.h"
#include "simulation2/components/ICmpTerritoryManager.h"
#include "simulation2/components/ICmpVisibility.h"
#include "simulation2/components/ICmpVision.h"
#include "simulation2/components/ICmpWaterManager.h"
#include "simulation2/helpers/Render.h"
#include "simulation2/helpers/Spatial.h"
#include "graphics/Overlay.h"
#include "graphics/Terrain.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Overlay.h"
#include "ps/Profile.h"
#include "renderer/Scene.h"
#include "lib/ps_stl.h"
#define LOS_TILES_RATIO 8
#define DEBUG_RANGE_MANAGER_BOUNDS 0
/**
* Representation of a range query.
*/
struct Query
{
bool enabled;
bool parabolic;
CEntityHandle source; // TODO: this could crash if an entity is destroyed while a Query is still referencing it
entity_pos_t minRange;
entity_pos_t maxRange;
entity_pos_t elevationBonus;
u32 ownersMask;
i32 interface;
std::vector lastMatch;
u8 flagsMask;
};
/**
* Convert an owner ID (-1 = unowned, 0 = gaia, 1..30 = players)
* into a 32-bit mask for quick set-membership tests.
*/
static inline u32 CalcOwnerMask(player_id_t owner)
{
if (owner >= -1 && owner < 31)
return 1 << (1+owner);
else
return 0; // owner was invalid
}
/**
* Returns LOS mask for given player.
*/
static inline u32 CalcPlayerLosMask(player_id_t player)
{
if (player > 0 && player <= 16)
return ICmpRangeManager::LOS_MASK << (2*(player-1));
return 0;
}
/**
* Returns shared LOS mask for given list of players.
*/
static u32 CalcSharedLosMask(std::vector players)
{
u32 playerMask = 0;
for (size_t i = 0; i < players.size(); i++)
playerMask |= CalcPlayerLosMask(players[i]);
return playerMask;
}
/**
* Checks whether v is in a parabolic range of (0,0,0)
* The highest point of the paraboloid is (0,range/2,0)
* and the circle of distance 'range' around (0,0,0) on height y=0 is part of the paraboloid
*
* Avoids sqrting and overflowing.
*/
static bool InParabolicRange(CFixedVector3D v, fixed range)
{
i32 x = v.X.GetInternalValue(); // abs(x) <= 2^31
i32 z = v.Z.GetInternalValue();
u64 xx = (u64)FIXED_MUL_I64_I32_I32(x, x); // xx <= 2^62
u64 zz = (u64)FIXED_MUL_I64_I32_I32(z, z);
i64 d2 = (xx + zz) >> 1; // d2 <= 2^62 (no overflow)
i32 y = v.Y.GetInternalValue();
i32 c = range.GetInternalValue();
i32 c_2 = c >> 1;
i64 c2 = FIXED_MUL_I64_I32_I32(c_2 - y, c);
if (d2 <= c2)
return true;
return false;
}
struct EntityParabolicRangeOutline
{
entity_id_t source;
CFixedVector3D position;
entity_pos_t range;
std::vector outline;
};
static std::map ParabolicRangesOutlines;
/**
* Representation of an entity, with the data needed for queries.
*/
struct EntityData
{
EntityData() : visibilities(0), retainInFog(0), owner(-1), inWorld(0), flags(1) { }
entity_pos_t x, z;
entity_pos_t visionRange;
u32 visibilities; // 2-bit visibility, per player
u8 retainInFog; // boolean
i8 owner;
u8 inWorld; // boolean
u8 flags; // See GetEntityFlagMask
};
cassert(sizeof(EntityData) == 20);
/**
* Serialization helper template for Query
*/
struct SerializeQuery
{
template
void Common(S& serialize, const char* UNUSED(name), Query& value)
{
serialize.Bool("enabled", value.enabled);
serialize.Bool("parabolic",value.parabolic);
serialize.NumberFixed_Unbounded("min range", value.minRange);
serialize.NumberFixed_Unbounded("max range", value.maxRange);
serialize.NumberFixed_Unbounded("elevation bonus", value.elevationBonus);
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
serialize.NumberI32_Unbounded("interface", value.interface);
SerializeVector()(serialize, "last match", value.lastMatch);
serialize.NumberU8_Unbounded("flagsMask", value.flagsMask);
}
void operator()(ISerializer& serialize, const char* name, Query& value, const CSimContext& UNUSED(context))
{
Common(serialize, name, value);
uint32_t id = value.source.GetId();
serialize.NumberU32_Unbounded("source", id);
}
void operator()(IDeserializer& deserialize, const char* name, Query& value, const CSimContext& context)
{
Common(deserialize, name, value);
uint32_t id;
deserialize.NumberU32_Unbounded("source", id);
value.source = context.GetComponentManager().LookupEntityHandle(id, true);
// the referenced entity might not have been deserialized yet,
// so tell LookupEntityHandle to allocate the handle if necessary
}
};
/**
* Serialization helper template for EntityData
*/
struct SerializeEntityData
{
template
void operator()(S& serialize, const char* UNUSED(name), EntityData& value)
{
serialize.NumberFixed_Unbounded("x", value.x);
serialize.NumberFixed_Unbounded("z", value.z);
serialize.NumberFixed_Unbounded("vision", value.visionRange);
serialize.NumberU32_Unbounded("visibilities", value.visibilities);
serialize.NumberU8("retain in fog", value.retainInFog, 0, 1);
serialize.NumberI8_Unbounded("owner", value.owner);
serialize.NumberU8("in world", value.inWorld, 0, 1);
serialize.NumberU8_Unbounded("flags", value.flags);
}
};
/**
* Functor for sorting entities by distance from a source point.
* It must only be passed entities that are in 'entities'
* and are currently in the world.
*/
struct EntityDistanceOrdering
{
EntityDistanceOrdering(const EntityMap& entities, const CFixedVector2D& source) :
m_EntityData(entities), m_Source(source)
{
}
bool operator()(entity_id_t a, entity_id_t b)
{
const EntityData& da = m_EntityData.find(a)->second;
const EntityData& db = m_EntityData.find(b)->second;
CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
return (vecA.CompareLength(vecB) < 0);
}
const EntityMap& m_EntityData;
CFixedVector2D m_Source;
private:
EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
};
/**
* Range manager implementation.
* Maintains a list of all entities (and their positions and owners), which is used for
* queries.
*
* LOS implementation is based on the model described in GPG2.
* (TODO: would be nice to make it cleverer, so e.g. mountains and walls
* can block vision)
*/
class CCmpRangeManager : public ICmpRangeManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeGloballyToMessageType(MT_Create);
componentManager.SubscribeGloballyToMessageType(MT_PositionChanged);
componentManager.SubscribeGloballyToMessageType(MT_OwnershipChanged);
componentManager.SubscribeGloballyToMessageType(MT_Destroy);
componentManager.SubscribeGloballyToMessageType(MT_VisionRangeChanged);
componentManager.SubscribeToMessageType(MT_Update);
componentManager.SubscribeToMessageType(MT_RenderSubmit); // for debug overlays
}
DEFAULT_COMPONENT_ALLOCATOR(RangeManager)
bool m_DebugOverlayEnabled;
bool m_DebugOverlayDirty;
std::vector m_DebugOverlayLines;
// World bounds (entities are expected to be within this range)
entity_pos_t m_WorldX0;
entity_pos_t m_WorldZ0;
entity_pos_t m_WorldX1;
entity_pos_t m_WorldZ1;
// Range query state:
tag_t m_QueryNext; // next allocated id
std::map m_Queries;
EntityMap m_EntityData;
SpatialSubdivision m_Subdivision; // spatial index of m_EntityData
// LOS state:
static const player_id_t MAX_LOS_PLAYER_ID = 16;
std::vector m_LosRevealAll;
bool m_LosCircular;
i32 m_TerrainVerticesPerSide;
// Cache for visibility tracking (not serialized)
i32 m_LosTilesPerSide;
bool m_GlobalVisibilityUpdate;
std::vector m_DirtyVisibility;
std::vector > m_LosTiles;
// List of entities that must be updated, regardless of the status of their tile
std::vector m_ModifiedEntities;
// Counts of units seeing vertex, per vertex, per player (starting with player 0).
// Use u16 to avoid overflows when we have very large (but not infeasibly large) numbers
// of units in a very small area.
// (Note we use vertexes, not tiles, to better match the renderer.)
// Lazily constructed when it's needed, to save memory in smaller games.
std::vector > m_LosPlayerCounts;
// 2-bit ELosState per player, starting with player 1 (not 0!) up to player MAX_LOS_PLAYER_ID (inclusive)
std::vector m_LosState;
// Special static visibility data for the "reveal whole map" mode
// (TODO: this is usually a waste of memory)
std::vector m_LosStateRevealed;
// Shared LOS masks, one per player.
std::vector m_SharedLosMasks;
// Cache explored vertices per player (not serialized)
u32 m_TotalInworldVertices;
std::vector m_ExploredVertices;
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_QueryNext = 1;
m_DebugOverlayEnabled = false;
m_DebugOverlayDirty = true;
m_WorldX0 = m_WorldZ0 = m_WorldX1 = m_WorldZ1 = entity_pos_t::Zero();
// Initialise with bogus values (these will get replaced when
// SetBounds is called)
ResetSubdivisions(entity_pos_t::FromInt(1024), entity_pos_t::FromInt(1024));
// The whole map should be visible to Gaia by default, else e.g. animals
// will get confused when trying to run from enemies
m_LosRevealAll.resize(MAX_LOS_PLAYER_ID+2,false);
m_LosRevealAll[0] = true;
m_SharedLosMasks.resize(MAX_LOS_PLAYER_ID+2,0);
m_LosCircular = false;
m_TerrainVerticesPerSide = 0;
}
virtual void Deinit()
{
}
template
void SerializeCommon(S& serialize)
{
serialize.NumberFixed_Unbounded("world x0", m_WorldX0);
serialize.NumberFixed_Unbounded("world z0", m_WorldZ0);
serialize.NumberFixed_Unbounded("world x1", m_WorldX1);
serialize.NumberFixed_Unbounded("world z1", m_WorldZ1);
serialize.NumberU32_Unbounded("query next", m_QueryNext);
SerializeMap()(serialize, "queries", m_Queries, GetSimContext());
SerializeEntityMap()(serialize, "entity data", m_EntityData);
SerializeVector()(serialize, "los reveal all", m_LosRevealAll);
serialize.Bool("los circular", m_LosCircular);
serialize.NumberI32_Unbounded("terrain verts per side", m_TerrainVerticesPerSide);
SerializeVector()(serialize, "modified entities", m_ModifiedEntities);
// We don't serialize m_Subdivision, m_LosPlayerCounts or m_LosTiles
// since they can be recomputed from the entity data when deserializing;
// m_LosState must be serialized since it depends on the history of exploration
SerializeVector()(serialize, "los state", m_LosState);
SerializeVector()(serialize, "shared los masks", m_SharedLosMasks);
}
virtual void Serialize(ISerializer& serialize)
{
SerializeCommon(serialize);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
SerializeCommon(deserialize);
// Reinitialise subdivisions and LOS data
ResetDerivedData(true);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Create:
{
const CMessageCreate& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
// Ignore local entities - we shouldn't let them influence anything
if (ENTITY_IS_LOCAL(ent))
break;
// Ignore non-positional entities
CmpPtr cmpPosition(GetSimContext(), ent);
if (!cmpPosition)
break;
// The newly-created entity will have owner -1 and position out-of-world
// (any initialisation of those values will happen later), so we can just
// use the default-constructed EntityData here
EntityData entdata;
// Store the LOS data, if any
CmpPtr cmpVision(GetSimContext(), ent);
if (cmpVision)
- {
entdata.visionRange = cmpVision->GetRange();
- entdata.retainInFog = (cmpVision->GetRetainInFog() ? 1 : 0);
- }
+ CmpPtr cmpVisibility(GetSimContext(), ent);
+ if (cmpVisibility)
+ entdata.retainInFog = (cmpVisibility->GetRetainInFog() ? 1 : 0);
// Remember this entity
m_EntityData.insert(ent, entdata);
break;
}
case MT_PositionChanged:
{
const CMessagePositionChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (msgData.inWorld)
{
if (it->second.inWorld)
{
CFixedVector2D from(it->second.x, it->second.z);
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Move(ent, from, to);
LosMove(it->second.owner, it->second.visionRange, from, to);
i32 oldLosTile = PosToLosTilesHelper(it->second.x, it->second.z);
i32 newLosTile = PosToLosTilesHelper(msgData.x, msgData.z);
if (oldLosTile != newLosTile)
{
RemoveFromTile(oldLosTile, ent);
AddToTile(newLosTile, ent);
}
}
else
{
CFixedVector2D to(msgData.x, msgData.z);
m_Subdivision.Add(ent, to);
LosAdd(it->second.owner, it->second.visionRange, to);
AddToTile(PosToLosTilesHelper(msgData.x, msgData.z), ent);
}
it->second.inWorld = 1;
it->second.x = msgData.x;
it->second.z = msgData.z;
}
else
{
if (it->second.inWorld)
{
CFixedVector2D from(it->second.x, it->second.z);
m_Subdivision.Remove(ent, from);
LosRemove(it->second.owner, it->second.visionRange, from);
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
it->second.inWorld = 0;
it->second.x = entity_pos_t::Zero();
it->second.z = entity_pos_t::Zero();
}
m_ModifiedEntities.push_back(ent);
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (it->second.inWorld)
{
CFixedVector2D pos(it->second.x, it->second.z);
LosRemove(it->second.owner, it->second.visionRange, pos);
LosAdd(msgData.to, it->second.visionRange, pos);
}
ENSURE(-128 <= msgData.to && msgData.to <= 127);
it->second.owner = (i8)msgData.to;
break;
}
case MT_Destroy:
{
const CMessageDestroy& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
if (it->second.inWorld)
{
m_Subdivision.Remove(ent, CFixedVector2D(it->second.x, it->second.z));
RemoveFromTile(PosToLosTilesHelper(it->second.x, it->second.z), ent);
}
// This will be called after Ownership's OnDestroy, so ownership will be set
// to -1 already and we don't have to do a LosRemove here
ENSURE(it->second.owner == -1);
m_EntityData.erase(it);
break;
}
case MT_VisionRangeChanged:
{
const CMessageVisionRangeChanged& msgData = static_cast (msg);
entity_id_t ent = msgData.entity;
EntityMap::iterator it = m_EntityData.find(ent);
// Ignore if we're not already tracking this entity
if (it == m_EntityData.end())
break;
CmpPtr cmpVision(GetSimContext(), ent);
if (!cmpVision)
break;
entity_pos_t oldRange = it->second.visionRange;
entity_pos_t newRange = msgData.newRange;
// If the range changed and the entity's in-world, we need to manually adjust it
// but if it's not in-world, we only need to set the new vision range
CFixedVector2D pos(it->second.x, it->second.z);
if (it->second.inWorld)
LosRemove(it->second.owner, oldRange, pos);
it->second.visionRange = newRange;
if (it->second.inWorld)
LosAdd(it->second.owner, newRange, pos);
break;
}
case MT_Update:
{
m_DebugOverlayDirty = true;
ExecuteActiveQueries();
UpdateVisibilityData();
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector);
break;
}
}
}
virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, ssize_t vertices)
{
m_WorldX0 = x0;
m_WorldZ0 = z0;
m_WorldX1 = x1;
m_WorldZ1 = z1;
m_TerrainVerticesPerSide = (i32)vertices;
ResetDerivedData(false);
}
virtual void Verify()
{
// Ignore if map not initialised yet
if (m_WorldX1.IsZero())
return;
// Check that calling ResetDerivedData (i.e. recomputing all the state from scratch)
// does not affect the incrementally-computed state
std::vector > oldPlayerCounts = m_LosPlayerCounts;
std::vector oldStateRevealed = m_LosStateRevealed;
SpatialSubdivision oldSubdivision = m_Subdivision;
std::vector > oldLosTiles = m_LosTiles;
ResetDerivedData(true);
if (oldPlayerCounts != m_LosPlayerCounts)
{
for (size_t i = 0; i < oldPlayerCounts.size(); ++i)
{
debug_printf(L"%d: ", (int)i);
for (size_t j = 0; j < oldPlayerCounts[i].size(); ++j)
debug_printf(L"%d ", oldPlayerCounts[i][j]);
debug_printf(L"\n");
}
for (size_t i = 0; i < m_LosPlayerCounts.size(); ++i)
{
debug_printf(L"%d: ", (int)i);
for (size_t j = 0; j < m_LosPlayerCounts[i].size(); ++j)
debug_printf(L"%d ", m_LosPlayerCounts[i][j]);
debug_printf(L"\n");
}
debug_warn(L"inconsistent player counts");
}
if (oldStateRevealed != m_LosStateRevealed)
debug_warn(L"inconsistent revealed");
if (oldSubdivision != m_Subdivision)
debug_warn(L"inconsistent subdivs");
if (oldLosTiles != m_LosTiles)
debug_warn(L"inconsistent los tiles");
}
SpatialSubdivision* GetSubdivision()
{
return & m_Subdivision;
}
// Reinitialise subdivisions and LOS data, based on entity data
void ResetDerivedData(bool skipLosState)
{
ENSURE(m_WorldX0.IsZero() && m_WorldZ0.IsZero()); // don't bother implementing non-zero offsets yet
ResetSubdivisions(m_WorldX1, m_WorldZ1);
m_LosTilesPerSide = (m_TerrainVerticesPerSide - 1)/LOS_TILES_RATIO;
m_LosPlayerCounts.clear();
m_LosPlayerCounts.resize(MAX_LOS_PLAYER_ID+1);
m_ExploredVertices.clear();
m_ExploredVertices.resize(MAX_LOS_PLAYER_ID+1, 0);
if (skipLosState)
{
// recalc current exploration stats.
for (i32 j = 0; j < m_TerrainVerticesPerSide; j++)
{
for (i32 i = 0; i < m_TerrainVerticesPerSide; i++)
{
if (!LosIsOffWorld(i, j))
{
for (u8 k = 1; k < MAX_LOS_PLAYER_ID+1; ++k)
m_ExploredVertices.at(k) += ((m_LosState[j*m_TerrainVerticesPerSide + i] & (LOS_EXPLORED << (2*(k-1)))) > 0);
}
}
}
}
else
{
m_LosState.clear();
m_LosState.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
}
m_LosStateRevealed.clear();
m_LosStateRevealed.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
m_DirtyVisibility.clear();
m_DirtyVisibility.resize(m_LosTilesPerSide*m_LosTilesPerSide);
m_LosTiles.clear();
m_LosTiles.resize(m_LosTilesPerSide*m_LosTilesPerSide);
m_GlobalVisibilityUpdate = true;
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
if (it->second.inWorld)
{
LosAdd(it->second.owner, it->second.visionRange, CFixedVector2D(it->second.x, it->second.z));
AddToTile(PosToLosTilesHelper(it->second.x, it->second.z), it->first);
}
}
m_TotalInworldVertices = 0;
for (ssize_t j = 0; j < m_TerrainVerticesPerSide; ++j)
for (ssize_t i = 0; i < m_TerrainVerticesPerSide; ++i)
{
if (LosIsOffWorld(i,j))
m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0;
else
{
m_LosStateRevealed[i + j*m_TerrainVerticesPerSide] = 0xFFFFFFFFu;
m_TotalInworldVertices++;
}
}
}
void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1)
{
// Use 8x8 tile subdivisions
// (TODO: find the optimal number instead of blindly guessing)
m_Subdivision.Reset(x1, z1, entity_pos_t::FromInt(8*TERRAIN_TILE_SIZE));
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
if (it->second.inWorld)
m_Subdivision.Add(it->first, CFixedVector2D(it->second.x, it->second.z));
}
}
virtual tag_t CreateActiveQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
std::vector owners, int requiredInterface, u8 flags)
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags);
return id;
}
virtual tag_t CreateActiveParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
std::vector owners, int requiredInterface, u8 flags)
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, elevationBonus, owners, requiredInterface, flags);
return id;
}
virtual void DestroyActiveQuery(tag_t tag)
{
if (m_Queries.find(tag) == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: DestroyActiveQuery called with invalid tag %u", tag);
return;
}
m_Queries.erase(tag);
}
virtual void EnableActiveQuery(tag_t tag)
{
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: EnableActiveQuery called with invalid tag %u", tag);
return;
}
Query& q = it->second;
q.enabled = true;
}
virtual void DisableActiveQuery(tag_t tag)
{
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: DisableActiveQuery called with invalid tag %u", tag);
return;
}
Query& q = it->second;
q.enabled = false;
}
virtual std::vector ExecuteQueryAroundPos(CFixedVector2D pos,
entity_pos_t minRange, entity_pos_t maxRange,
std::vector owners, int requiredInterface)
{
Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
std::vector r;
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector ExecuteQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
std::vector owners, int requiredInterface)
{
PROFILE("ExecuteQuery");
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"));
std::vector r;
CmpPtr cmpSourcePosition(q.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
return r;
}
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector ResetActiveQuery(tag_t tag)
{
PROFILE("ResetActiveQuery");
std::vector r;
std::map::iterator it = m_Queries.find(tag);
if (it == m_Queries.end())
{
LOGERROR(L"CCmpRangeManager: ResetActiveQuery called with invalid tag %u", tag);
return r;
}
Query& q = it->second;
q.enabled = true;
CmpPtr cmpSourcePosition(q.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
{
// If the source doesn't have a position, then the result is just the empty list
q.lastMatch = r;
return r;
}
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
PerformQuery(q, r, pos);
q.lastMatch = r;
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
return r;
}
virtual std::vector GetEntitiesByPlayer(player_id_t player)
{
std::vector entities;
u32 ownerMask = CalcOwnerMask(player);
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
// Check owner and add to list if it matches
if (CalcOwnerMask(it->second.owner) & ownerMask)
entities.push_back(it->first);
}
return entities;
}
virtual void SetDebugOverlay(bool enabled)
{
m_DebugOverlayEnabled = enabled;
m_DebugOverlayDirty = true;
if (!enabled)
m_DebugOverlayLines.clear();
}
/**
* Update all currently-enabled active queries.
*/
void ExecuteActiveQueries()
{
PROFILE3("ExecuteActiveQueries");
// Store a queue of all messages before sending any, so we can assume
// no entities will move until we've finished checking all the ranges
std::vector > messages;
std::vector results;
std::vector added;
std::vector removed;
for (std::map::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& query = it->second;
if (!query.enabled)
continue;
CmpPtr cmpSourcePosition(query.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
continue;
results.clear();
results.reserve(query.lastMatch.size());
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
PerformQuery(query, results, pos);
// Compute the changes vs the last match
added.clear();
removed.clear();
// Return the 'added' list sorted by distance from the entity
// (Don't bother sorting 'removed' because they might not even have positions or exist any more)
std::set_difference(results.begin(), results.end(), query.lastMatch.begin(), query.lastMatch.end(),
std::back_inserter(added));
std::set_difference(query.lastMatch.begin(), query.lastMatch.end(), results.begin(), results.end(),
std::back_inserter(removed));
if (added.empty() && removed.empty())
continue;
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D()));
messages.resize(messages.size() + 1);
std::pair& back = messages.back();
back.first = query.source.GetId();
back.second.tag = it->first;
back.second.added.swap(added);
back.second.removed.swap(removed);
it->second.lastMatch.swap(results);
}
CComponentManager& cmpMgr = GetSimContext().GetComponentManager();
for (size_t i = 0; i < messages.size(); ++i)
cmpMgr.PostMessage(messages[i].first, messages[i].second);
}
/**
* Returns whether the given entity matches the given query (ignoring maxRange)
*/
bool TestEntityQuery(const Query& q, entity_id_t id, const EntityData& entity)
{
// Quick filter to ignore entities with the wrong owner
if (!(CalcOwnerMask(entity.owner) & q.ownersMask))
return false;
// Ignore entities not present in the world
if (!entity.inWorld)
return false;
// Ignore entities that don't match the current flags
if (!(entity.flags & q.flagsMask))
return false;
// Ignore self
if (id == q.source.GetId())
return false;
// Ignore if it's missing the required interface
if (q.interface && !GetSimContext().GetComponentManager().QueryInterface(id, q.interface))
return false;
return true;
}
/**
* Returns a list of distinct entity IDs that match the given query, sorted by ID.
*/
void PerformQuery(const Query& q, std::vector& r, CFixedVector2D pos)
{
// Special case: range -1.0 means check all entities ignoring distance
if (q.maxRange == entity_pos_t::FromInt(-1))
{
for (EntityMap::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
if (!TestEntityQuery(q, it->first, it->second))
continue;
r.push_back(it->first);
}
}
// Not the entire world, so check a parabolic range, or a regular range
else if (q.parabolic)
{
// elevationBonus is part of the 3D position, as the source is really that much heigher
CmpPtr cmpSourcePosition(q.source);
CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+
CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ;
// Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange
std::vector ents;
m_Subdivision.GetNear(ents, pos, q.maxRange*2);
for (size_t i = 0; i < ents.size(); ++i)
{
EntityMap::const_iterator it = m_EntityData.find(ents[i]);
ENSURE(it != m_EntityData.end());
if (!TestEntityQuery(q, it->first, it->second))
continue;
CmpPtr cmpSecondPosition(GetSimContext(), ents[i]);
if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld())
continue;
CFixedVector3D secondPosition = cmpSecondPosition->GetPosition();
// Restrict based on precise distance
if (!InParabolicRange(
CFixedVector3D(it->second.x, secondPosition.Y, it->second.z)
- pos3d,
q.maxRange))
continue;
if (!q.minRange.IsZero())
{
int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
if (distVsMin < 0)
continue;
}
r.push_back(it->first);
}
}
// check a regular range (i.e. not the entire world, and not parabolic)
else
{
// Get a quick list of entities that are potentially in range
std::vector ents;
m_Subdivision.GetNear(ents, pos, q.maxRange);
for (size_t i = 0; i < ents.size(); ++i)
{
EntityMap::const_iterator it = m_EntityData.find(ents[i]);
ENSURE(it != m_EntityData.end());
if (!TestEntityQuery(q, it->first, it->second))
continue;
// Restrict based on precise distance
int distVsMax = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.maxRange);
if (distVsMax > 0)
continue;
if (!q.minRange.IsZero())
{
int distVsMin = (CFixedVector2D(it->second.x, it->second.z) - pos).CompareLength(q.minRange);
if (distVsMin < 0)
continue;
}
r.push_back(it->first);
}
}
}
virtual entity_pos_t GetElevationAdaptedRange(CFixedVector3D pos, CFixedVector3D rot, entity_pos_t range, entity_pos_t elevationBonus, entity_pos_t angle)
{
entity_pos_t r = entity_pos_t::Zero() ;
pos.Y += elevationBonus;
entity_pos_t orientation = rot.Y;
entity_pos_t maxAngle = orientation + angle/2;
entity_pos_t minAngle = orientation - angle/2;
int numberOfSteps = 16;
if (angle == entity_pos_t::Zero())
numberOfSteps = 1;
std::vector coords = getParabolicRangeForm(pos, range, range*2, minAngle, maxAngle, numberOfSteps);
entity_pos_t part = entity_pos_t::FromInt(numberOfSteps);
for (int i = 0; i < numberOfSteps; i++)
{
r = r + CFixedVector2D(coords[2*i],coords[2*i+1]).Length() / part;
}
return r;
}
virtual std::vector getParabolicRangeForm(CFixedVector3D pos, entity_pos_t maxRange, entity_pos_t cutoff, entity_pos_t minAngle, entity_pos_t maxAngle, int numberOfSteps)
{
// angle = 0 goes in the positive Z direction
entity_pos_t precision = entity_pos_t::FromInt((int)TERRAIN_TILE_SIZE)/8;
std::vector r;
CmpPtr cmpTerrain(GetSystemEntity());
CmpPtr cmpWaterManager(GetSystemEntity());
entity_pos_t waterLevel = cmpWaterManager->GetWaterLevel(pos.X,pos.Z);
entity_pos_t thisHeight = pos.Y > waterLevel ? pos.Y : waterLevel;
if (cmpTerrain)
{
for (int i = 0; i < numberOfSteps; i++)
{
entity_pos_t angle = minAngle + (maxAngle - minAngle) / numberOfSteps * i;
entity_pos_t sin;
entity_pos_t cos;
entity_pos_t minDistance = entity_pos_t::Zero();
entity_pos_t maxDistance = cutoff;
sincos_approx(angle,sin,cos);
CFixedVector2D minVector = CFixedVector2D(entity_pos_t::Zero(),entity_pos_t::Zero());
CFixedVector2D maxVector = CFixedVector2D(sin,cos).Multiply(cutoff);
entity_pos_t targetHeight = cmpTerrain->GetGroundLevel(pos.X+maxVector.X,pos.Z+maxVector.Y);
// use water level to display range on water
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
if (InParabolicRange(CFixedVector3D(maxVector.X,targetHeight-thisHeight,maxVector.Y),maxRange))
{
r.push_back(maxVector.X);
r.push_back(maxVector.Y);
continue;
}
// Loop until vectors come close enough
while ((maxVector - minVector).CompareLength(precision) > 0)
{
// difference still bigger than precision, bisect to get smaller difference
entity_pos_t newDistance = (minDistance+maxDistance)/entity_pos_t::FromInt(2);
CFixedVector2D newVector = CFixedVector2D(sin,cos).Multiply(newDistance);
// get the height of the ground
targetHeight = cmpTerrain->GetGroundLevel(pos.X+newVector.X,pos.Z+newVector.Y);
targetHeight = targetHeight > waterLevel ? targetHeight : waterLevel;
if (InParabolicRange(CFixedVector3D(newVector.X,targetHeight-thisHeight,newVector.Y),maxRange))
{
// new vector is in parabolic range, so this is a new minVector
minVector = newVector;
minDistance = newDistance;
}
else
{
// new vector is out parabolic range, so this is a new maxVector
maxVector = newVector;
maxDistance = newDistance;
}
}
r.push_back(maxVector.X);
r.push_back(maxVector.Y);
}
r.push_back(r[0]);
r.push_back(r[1]);
}
return r;
}
Query ConstructQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector& owners, int requiredInterface, u8 flagsMask)
{
// Min range must be non-negative
if (minRange < entity_pos_t::Zero())
LOGWARNING(L"CCmpRangeManager: Invalid min range %f in query for entity %u", minRange.ToDouble(), source);
// Max range must be non-negative, or else -1
if (maxRange < entity_pos_t::Zero() && maxRange != entity_pos_t::FromInt(-1))
LOGWARNING(L"CCmpRangeManager: Invalid max range %f in query for entity %u", maxRange.ToDouble(), source);
Query q;
q.enabled = false;
q.parabolic = false;
q.source = GetSimContext().GetComponentManager().LookupEntityHandle(source);
q.minRange = minRange;
q.maxRange = maxRange;
q.elevationBonus = entity_pos_t::Zero();
q.ownersMask = 0;
for (size_t i = 0; i < owners.size(); ++i)
q.ownersMask |= CalcOwnerMask(owners[i]);
q.interface = requiredInterface;
q.flagsMask = flagsMask;
return q;
}
Query ConstructParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t elevationBonus,
const std::vector& owners, int requiredInterface, u8 flagsMask)
{
Query q = ConstructQuery(source,minRange,maxRange,owners,requiredInterface,flagsMask);
q.parabolic = true;
q.elevationBonus = elevationBonus;
return q;
}
void RenderSubmit(SceneCollector& collector)
{
if (!m_DebugOverlayEnabled)
return;
static CColor disabledRingColour(1, 0, 0, 1); // red
static CColor enabledRingColour(0, 1, 0, 1); // green
static CColor subdivColour(0, 0, 1, 1); // blue
static CColor rayColour(1, 1, 0, 0.2f);
if (m_DebugOverlayDirty)
{
m_DebugOverlayLines.clear();
for (std::map::iterator it = m_Queries.begin(); it != m_Queries.end(); ++it)
{
Query& q = it->second;
CmpPtr cmpSourcePosition(q.source);
if (!cmpSourcePosition || !cmpSourcePosition->IsInWorld())
continue;
CFixedVector2D pos = cmpSourcePosition->GetPosition2D();
// Draw the max range circle
if (!q.parabolic)
{
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = (q.enabled ? enabledRingColour : disabledRingColour);
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.maxRange.ToFloat(), m_DebugOverlayLines.back(), true);
}
else
{
// elevation bonus is part of the 3D position. As if the unit is really that much higher
CFixedVector3D pos = cmpSourcePosition->GetPosition();
pos.Y += q.elevationBonus;
std::vector coords;
// Get the outline from cache if possible
if (ParabolicRangesOutlines.find(q.source.GetId()) != ParabolicRangesOutlines.end())
{
EntityParabolicRangeOutline e = ParabolicRangesOutlines[q.source.GetId()];
if (e.position == pos && e.range == q.maxRange)
{
// outline is cached correctly, use it
coords = e.outline;
}
else
{
// outline was cached, but important parameters changed
// (position, elevation, range)
// update it
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
e.outline = coords;
e.range = q.maxRange;
e.position = pos;
ParabolicRangesOutlines[q.source.GetId()] = e;
}
}
else
{
// outline wasn't cached (first time you enable the range overlay
// or you created a new entiy)
// cache a new outline
coords = getParabolicRangeForm(pos,q.maxRange,q.maxRange*2, entity_pos_t::Zero(), entity_pos_t::FromFloat(2.0f*3.14f),70);
EntityParabolicRangeOutline e;
e.source = q.source.GetId();
e.range = q.maxRange;
e.position = pos;
e.outline = coords;
ParabolicRangesOutlines[q.source.GetId()] = e;
}
CColor thiscolor = q.enabled ? enabledRingColour : disabledRingColour;
// draw the outline (piece by piece)
for (size_t i = 3; i < coords.size(); i += 2)
{
std::vector c;
c.push_back((coords[i-3]+pos.X).ToFloat());
c.push_back((coords[i-2]+pos.Z).ToFloat());
c.push_back((coords[i-1]+pos.X).ToFloat());
c.push_back((coords[i]+pos.Z).ToFloat());
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = thiscolor;
SimRender::ConstructLineOnGround(GetSimContext(), c, m_DebugOverlayLines.back(), true);
}
}
// Draw the min range circle
if (!q.minRange.IsZero())
{
SimRender::ConstructCircleOnGround(GetSimContext(), pos.X.ToFloat(), pos.Y.ToFloat(), q.minRange.ToFloat(), m_DebugOverlayLines.back(), true);
}
// Draw a ray from the source to each matched entity
for (size_t i = 0; i < q.lastMatch.size(); ++i)
{
CmpPtr cmpTargetPosition(GetSimContext(), q.lastMatch[i]);
if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
continue;
CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
std::vector coords;
coords.push_back(pos.X.ToFloat());
coords.push_back(pos.Y.ToFloat());
coords.push_back(targetPos.X.ToFloat());
coords.push_back(targetPos.Y.ToFloat());
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = rayColour;
SimRender::ConstructLineOnGround(GetSimContext(), coords, m_DebugOverlayLines.back(), true);
}
}
// render subdivision grid
float divSize = m_Subdivision.GetDivisionSize().ToFloat();
int width = m_Subdivision.GetWidth();
int height = m_Subdivision.GetHeight();
for (int x = 0; x < width; ++x)
{
for (int y = 0; y < height; ++y)
{
m_DebugOverlayLines.push_back(SOverlayLine());
m_DebugOverlayLines.back().m_Color = subdivColour;
float xpos = x*divSize + divSize/2;
float zpos = y*divSize + divSize/2;
SimRender::ConstructSquareOnGround(GetSimContext(), xpos, zpos, divSize, divSize, 0.0f,
m_DebugOverlayLines.back(), false, 1.0f);
}
}
m_DebugOverlayDirty = false;
}
for (size_t i = 0; i < m_DebugOverlayLines.size(); ++i)
collector.Submit(&m_DebugOverlayLines[i]);
}
virtual u8 GetEntityFlagMask(std::string identifier)
{
if (identifier == "normal")
return 1;
if (identifier == "injured")
return 2;
LOGWARNING(L"CCmpRangeManager: Invalid flag identifier %hs", identifier.c_str());
return 0;
}
virtual void SetEntityFlag(entity_id_t ent, std::string identifier, bool value)
{
EntityMap::iterator it = m_EntityData.find(ent);
// We don't have this entity
if (it == m_EntityData.end())
return;
u8 flag = GetEntityFlagMask(identifier);
// We don't have a flag set
if (flag == 0)
{
LOGWARNING(L"CCmpRangeManager: Invalid flag identifier %hs for entity %u", identifier.c_str(), ent);
return;
}
if (value)
it->second.flags |= flag;
else
it->second.flags &= ~flag;
}
// ****************************************************************
// LOS implementation:
virtual CLosQuerier GetLosQuerier(player_id_t player)
{
if (GetLosRevealAll(player))
return CLosQuerier(0xFFFFFFFFu, m_LosStateRevealed, m_TerrainVerticesPerSide);
else
return CLosQuerier(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
}
virtual ELosVisibility GetLosVisibility(CEntityHandle ent, player_id_t player, bool forceRetainInFog)
{
// Entities not with positions in the world are never visible
if (ent.GetId() == INVALID_ENTITY)
return VIS_HIDDEN;
CmpPtr cmpPosition(ent);
if (!cmpPosition || !cmpPosition->IsInWorld())
return VIS_HIDDEN;
// Mirage entities, whatever the situation, are visible for one specific player
CmpPtr cmpMirage(ent);
if (cmpMirage && cmpMirage->GetPlayer() != player)
return VIS_HIDDEN;
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
// Reveal flag makes all positioned entities visible and all mirages useless
if (GetLosRevealAll(player))
{
if (LosIsOffWorld(i, j) || cmpMirage)
return VIS_HIDDEN;
else
return VIS_VISIBLE;
}
// Visible if within a visible region
CLosQuerier los(GetSharedLosMask(player), m_LosState, m_TerrainVerticesPerSide);
if (!los.IsExplored(i, j))
return VIS_HIDDEN;
// Try to ask the Visibility component of the entity, if any
CmpPtr cmpVisibility(ent);
if (cmpVisibility)
return cmpVisibility->GetLosVisibility(player, los.IsVisible(i, j), forceRetainInFog);
// Default behaviour
if (los.IsVisible(i, j))
return VIS_VISIBLE;
if (forceRetainInFog)
return VIS_FOGGED;
return VIS_HIDDEN;
}
virtual ELosVisibility GetLosVisibility(entity_id_t ent, player_id_t player, bool forceRetainInFog)
{
CEntityHandle handle = GetSimContext().GetComponentManager().LookupEntityHandle(ent);
return GetLosVisibility(handle, player, forceRetainInFog);
}
i32 PosToLosTilesHelper(entity_pos_t x, entity_pos_t z)
{
i32 i = Clamp(
(x/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
0,
m_LosTilesPerSide - 1);
i32 j = Clamp(
(z/(entity_pos_t::FromInt(TERRAIN_TILE_SIZE * LOS_TILES_RATIO))).ToInt_RoundToZero(),
0,
m_LosTilesPerSide - 1);
return j*m_LosTilesPerSide + i;
}
void AddToTile(i32 tile, entity_id_t ent)
{
m_LosTiles[tile].insert(ent);
}
void RemoveFromTile(i32 tile, entity_id_t ent)
{
for (std::set::iterator tileIt = m_LosTiles[tile].begin();
tileIt != m_LosTiles[tile].end();
++tileIt)
{
if (*tileIt == ent)
{
m_LosTiles[tile].erase(tileIt);
return;
}
}
}
void UpdateVisibilityData()
{
PROFILE("UpdateVisibilityData");
for (i32 n = 0; n < m_LosTilesPerSide*m_LosTilesPerSide; ++n)
{
if (m_DirtyVisibility[n] == 1 || m_GlobalVisibilityUpdate)
{
for (std::set::iterator it = m_LosTiles[n].begin();
it != m_LosTiles[n].end();
++it)
{
UpdateVisibility(*it);
}
m_DirtyVisibility[n] = 0;
}
}
for (std::vector::iterator it = m_ModifiedEntities.begin(); it != m_ModifiedEntities.end(); ++it)
{
UpdateVisibility(*it);
}
m_ModifiedEntities.clear();
m_GlobalVisibilityUpdate = false;
}
void UpdateVisibility(entity_id_t ent)
{
// Warning: Code related to fogging (like posting VisibilityChanged messages)
// shouldn't be invoked while keeping an iterator to an element of m_EntityData.
// Otherwise, by deleting mirage entities and so on,
// that code will change the indexes in the map, leading to segfaults.
EntityMap::iterator itEnts = m_EntityData.find(ent);
if (itEnts == m_EntityData.end())
return;
// So we just remember what visibilities to update and do that later.
std::vector oldVisibilities;
std::vector newVisibilities;
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID+1; ++player)
{
u8 oldVis = (itEnts->second.visibilities >> (2*(player-1))) & 0x3;
u8 newVis = GetLosVisibility(itEnts->first, player, false);
oldVisibilities.push_back(oldVis);
newVisibilities.push_back(newVis);
if (oldVis != newVis)
itEnts->second.visibilities = (itEnts->second.visibilities & ~(0x3 << 2*(player-1))) | (newVis << 2*(player-1));
}
for (player_id_t player = 1; player < MAX_LOS_PLAYER_ID+1; ++player)
{
if (oldVisibilities[player-1] == newVisibilities[player-1])
continue;
// Another visibility update can be necessary to take in account new mirages
if (std::find(m_ModifiedEntities.begin(), m_ModifiedEntities.end(), ent) == m_ModifiedEntities.end())
m_ModifiedEntities.push_back(ent);
CMessageVisibilityChanged msg(player, ent, oldVisibilities[player-1], newVisibilities[player-1]);
GetSimContext().GetComponentManager().PostMessage(ent, msg);
}
}
virtual void SetLosRevealAll(player_id_t player, bool enabled)
{
if (player == -1)
m_LosRevealAll[MAX_LOS_PLAYER_ID+1] = enabled;
else
{
ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID);
m_LosRevealAll[player] = enabled;
}
// On next update, update the visibility of every entity in the world
m_GlobalVisibilityUpdate = true;
}
virtual bool GetLosRevealAll(player_id_t player)
{
// Special player value can force reveal-all for every player
if (m_LosRevealAll[MAX_LOS_PLAYER_ID+1] || player == -1)
return true;
ENSURE(player >= 0 && player <= MAX_LOS_PLAYER_ID+1);
// Otherwise check the player-specific flag
if (m_LosRevealAll[player])
return true;
return false;
}
virtual void SetLosCircular(bool enabled)
{
m_LosCircular = enabled;
ResetDerivedData(false);
}
virtual bool GetLosCircular()
{
return m_LosCircular;
}
virtual void SetSharedLos(player_id_t player, std::vector players)
{
m_SharedLosMasks[player] = CalcSharedLosMask(players);
}
virtual u32 GetSharedLosMask(player_id_t player)
{
return m_SharedLosMasks[player];
}
void ExploreAllTiles(player_id_t p)
{
for (u16 j = 0; j < m_TerrainVerticesPerSide; ++j)
{
for (u16 i = 0; i < m_TerrainVerticesPerSide; ++i)
{
if (LosIsOffWorld(i,j))
continue;
u32 &explored = m_ExploredVertices.at(p);
explored += !(m_LosState[i + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
}
}
SeeExploredEntities(p);
}
virtual void ExploreTerritories()
{
CmpPtr cmpTerritoryManager(GetSystemEntity());
const Grid& grid = cmpTerritoryManager->GetTerritoryGrid();
ENSURE(grid.m_W == m_TerrainVerticesPerSide-1 && grid.m_H == m_TerrainVerticesPerSide-1);
// For each tile, if it is owned by a valid player then update the LOS
// for every vertex around that tile, to mark them as explored
for (u16 j = 0; j < grid.m_H; ++j)
{
for (u16 i = 0; i < grid.m_W; ++i)
{
u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
if (p > 0 && p <= MAX_LOS_PLAYER_ID)
{
u32 &explored = m_ExploredVertices.at(p);
explored += !(m_LosState[i + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
explored += !(m_LosState[i+1 + j*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
m_LosState[i+1 + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
explored += !(m_LosState[i + (j+1)*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
m_LosState[i + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
explored += !(m_LosState[i+1 + (j+1)*m_TerrainVerticesPerSide] & (LOS_EXPLORED << (2*(p-1))));
m_LosState[i+1 + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
}
}
}
for (player_id_t p = 1; p < MAX_LOS_PLAYER_ID+1; ++p)
SeeExploredEntities(p);
}
/**
* Force any entity in explored territory to appear for player p.
* This is useful for miraging entities inside the territory borders at the beginning of a game,
* or if the "Explore Map" option has been set.
*/
void SeeExploredEntities(player_id_t p)
{
// Warning: Code related to fogging (like ForceMiraging) shouldn't be
// invoked while iterating through m_EntityData.
// Otherwise, by deleting mirage entities and so on, that code will
// change the indexes in the map, leading to segfaults.
// So we just remember what entities to mirage and do that later.
std::vector miragableEntities;
for (EntityMap::iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it)
{
CmpPtr cmpPosition(GetSimContext(), it->first);
if (!cmpPosition || !cmpPosition->IsInWorld())
continue;
CFixedVector2D pos = cmpPosition->GetPosition2D();
int i = (pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
int j = (pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
CLosQuerier los(GetSharedLosMask(p), m_LosState, m_TerrainVerticesPerSide);
if (!los.IsExplored(i,j) || los.IsVisible(i,j))
continue;
CmpPtr cmpFogging(GetSimContext(), it->first);
if (cmpFogging)
miragableEntities.push_back(it->first);
}
for (std::vector::iterator it = miragableEntities.begin(); it != miragableEntities.end(); ++it)
{
CmpPtr cmpFogging(GetSimContext(), *it);
ENSURE(cmpFogging && "Impossible to retrieve Fogging component, previously achieved");
cmpFogging->ForceMiraging(p);
}
}
/**
* Returns whether the given vertex is outside the normal bounds of the world
* (i.e. outside the range of a circular map)
*/
inline bool LosIsOffWorld(ssize_t i, ssize_t j)
{
// WARNING: CCmpObstructionManager::Rasterise needs to be kept in sync with this
const ssize_t edgeSize = 3; // number of vertexes around the edge that will be off-world
if (m_LosCircular)
{
// With a circular map, vertex is off-world if hypot(i - size/2, j - size/2) >= size/2:
ssize_t dist2 = (i - m_TerrainVerticesPerSide/2)*(i - m_TerrainVerticesPerSide/2)
+ (j - m_TerrainVerticesPerSide/2)*(j - m_TerrainVerticesPerSide/2);
ssize_t r = m_TerrainVerticesPerSide/2 - edgeSize + 1;
// subtract a bit from the radius to ensure nice
// SoD blurring around the edges of the map
return (dist2 >= r*r);
}
else
{
// With a square map, the outermost edge of the map should be off-world,
// so the SoD texture blends out nicely
return (i < edgeSize || j < edgeSize || i >= m_TerrainVerticesPerSide-edgeSize || j >= m_TerrainVerticesPerSide-edgeSize);
}
}
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
*/
inline void LosAddStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
{
if (i1 < i0)
return;
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
u32 &explored = m_ExploredVertices.at(owner);
for (i32 idx = idx0; idx <= idx1; ++idx)
{
// Increasing from zero to non-zero - move from unexplored/explored to visible+explored
if (counts[idx] == 0)
{
i32 i = i0 + idx - idx0;
if (!LosIsOffWorld(i, j))
{
explored += !(m_LosState[idx] & (LOS_EXPLORED << (2*(owner-1))));
m_LosState[idx] |= ((LOS_VISIBLE | LOS_EXPLORED) << (2*(owner-1)));
}
// Mark the LoS tiles around the updated vertex
// 1: left-up, 2: right-up, 3: left-down, 4: right-down
int n1 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
int n2 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
int n3 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
int n4 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
if (j > 0 && i > 0)
m_DirtyVisibility[n1] = 1;
if (n2 != n1 && j > 0 && i < m_TerrainVerticesPerSide)
m_DirtyVisibility[n2] = 1;
if (n3 != n1 && j < m_TerrainVerticesPerSide && i > 0)
m_DirtyVisibility[n3] = 1;
if (n4 != n1 && j < m_TerrainVerticesPerSide && i < m_TerrainVerticesPerSide)
m_DirtyVisibility[n4] = 1;
}
ASSERT(counts[idx] < 65535);
counts[idx] = (u16)(counts[idx] + 1); // ignore overflow; the player should never have 64K units
}
}
/**
* Update the LOS state of tiles within a given horizontal strip (i0,j) to (i1,j) (inclusive).
*/
inline void LosRemoveStripHelper(u8 owner, i32 i0, i32 i1, i32 j, u16* counts)
{
if (i1 < i0)
return;
i32 idx0 = j*m_TerrainVerticesPerSide + i0;
i32 idx1 = j*m_TerrainVerticesPerSide + i1;
for (i32 idx = idx0; idx <= idx1; ++idx)
{
ASSERT(counts[idx] > 0);
counts[idx] = (u16)(counts[idx] - 1);
// Decreasing from non-zero to zero - move from visible+explored to explored
if (counts[idx] == 0)
{
// (If LosIsOffWorld then this is a no-op, so don't bother doing the check)
m_LosState[idx] &= ~(LOS_VISIBLE << (2*(owner-1)));
i32 i = i0 + idx - idx0;
// Mark the LoS tiles around the updated vertex
// 1: left-up, 2: right-up, 3: left-down, 4: right-down
int n1 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
int n2 = ((j-1)/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
int n3 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + (i-1)/LOS_TILES_RATIO;
int n4 = (j/LOS_TILES_RATIO)*m_LosTilesPerSide + i/LOS_TILES_RATIO;
if (j > 0 && i > 0)
m_DirtyVisibility[n1] = 1;
if (n2 != n1 && j > 0 && i < m_TerrainVerticesPerSide)
m_DirtyVisibility[n2] = 1;
if (n3 != n1 && j < m_TerrainVerticesPerSide && i > 0)
m_DirtyVisibility[n3] = 1;
if (n4 != n1 && j < m_TerrainVerticesPerSide && i < m_TerrainVerticesPerSide)
m_DirtyVisibility[n4] = 1;
}
}
}
/**
* Update the LOS state of tiles within a given circular range,
* either adding or removing visibility depending on the template parameter.
* Assumes owner is in the valid range.
*/
template
void LosUpdateHelper(u8 owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelper");
std::vector& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
if (counts.empty())
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
u16* countsData = &counts[0];
// Compute the circular region as a series of strips.
// Rather than quantise pos to vertexes, we do more precise sub-tile computations
// to get smoother behaviour as a unit moves rather than jumping a whole tile
// at once.
// To avoid the cost of sqrt when computing the outline of the circle,
// we loop from the bottom to the top and estimate the width of the current
// strip based on the previous strip, then adjust each end of the strip
// inwards or outwards until it's the widest that still falls within the circle.
// Compute top/bottom coordinates, and clamp to exclude the 1-tile border around the map
// (so that we never render the sharp edge of the map)
i32 j0 = ((pos.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1 = ((pos.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0clamp = std::max(j0, 1);
i32 j1clamp = std::min(j1, m_TerrainVerticesPerSide-2);
// Translate world coordinates into fractional tile-space coordinates
entity_pos_t x = pos.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y = pos.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
entity_pos_t r2 = r.Square();
// Compute the integers on either side of x
i32 xfloor = (x - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil = (x + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
// Initialise the strip (i0, i1) to a rough guess
i32 i0 = xfloor;
i32 i1 = xceil;
for (i32 j = j0clamp; j <= j1clamp; ++j)
{
// Adjust i0 and i1 to be the outermost values that don't exceed
// the circle's radius (i.e. require dy^2 + dx^2 <= r^2).
// When moving the points inwards, clamp them to xceil+1 or xfloor-1
// so they don't accidentally shoot off in the wrong direction forever.
entity_pos_t dy = entity_pos_t::FromInt(j) - y;
entity_pos_t dy2 = dy.Square();
while (dy2 + (entity_pos_t::FromInt(i0-1) - x).Square() <= r2)
--i0;
while (i0 < xceil && dy2 + (entity_pos_t::FromInt(i0) - x).Square() > r2)
++i0;
while (dy2 + (entity_pos_t::FromInt(i1+1) - x).Square() <= r2)
++i1;
while (i1 > xfloor && dy2 + (entity_pos_t::FromInt(i1) - x).Square() > r2)
--i1;
#if DEBUG_RANGE_MANAGER_BOUNDS
if (i0 <= i1)
{
ENSURE(dy2 + (entity_pos_t::FromInt(i0) - x).Square() <= r2);
ENSURE(dy2 + (entity_pos_t::FromInt(i1) - x).Square() <= r2);
}
ENSURE(dy2 + (entity_pos_t::FromInt(i0 - 1) - x).Square() > r2);
ENSURE(dy2 + (entity_pos_t::FromInt(i1 + 1) - x).Square() > r2);
#endif
// Clamp the strip to exclude the 1-tile border,
// then add or remove the strip as requested
i32 i0clamp = std::max(i0, 1);
i32 i1clamp = std::min(i1, m_TerrainVerticesPerSide-2);
if (adding)
LosAddStripHelper(owner, i0clamp, i1clamp, j, countsData);
else
LosRemoveStripHelper(owner, i0clamp, i1clamp, j, countsData);
}
}
/**
* Update the LOS state of tiles within a given circular range,
* by removing visibility around the 'from' position
* and then adding visibility around the 'to' position.
*/
void LosUpdateHelperIncremental(u8 owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (m_TerrainVerticesPerSide == 0) // do nothing if not initialised yet
return;
PROFILE("LosUpdateHelperIncremental");
std::vector& counts = m_LosPlayerCounts.at(owner);
// Lazy initialisation of counts:
if (counts.empty())
counts.resize(m_TerrainVerticesPerSide*m_TerrainVerticesPerSide);
u16* countsData = &counts[0];
// See comments in LosUpdateHelper.
// This does exactly the same, except computing the strips for
// both circles simultaneously.
// (The idea is that the circles will be heavily overlapping,
// so we can compute the difference between the removed/added strips
// and only have to touch tiles that have a net change.)
i32 j0_from = ((from.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_from = ((from.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0_to = ((to.Y - visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToInfinity();
i32 j1_to = ((to.Y + visionRange)/(int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity();
i32 j0clamp = std::max(std::min(j0_from, j0_to), 1);
i32 j1clamp = std::min(std::max(j1_from, j1_to), m_TerrainVerticesPerSide-2);
entity_pos_t x_from = from.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y_from = from.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t x_to = to.X / (int)TERRAIN_TILE_SIZE;
entity_pos_t y_to = to.Y / (int)TERRAIN_TILE_SIZE;
entity_pos_t r = visionRange / (int)TERRAIN_TILE_SIZE;
entity_pos_t r2 = r.Square();
i32 xfloor_from = (x_from - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil_from = (x_from + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
i32 xfloor_to = (x_to - entity_pos_t::Epsilon()).ToInt_RoundToNegInfinity();
i32 xceil_to = (x_to + entity_pos_t::Epsilon()).ToInt_RoundToInfinity();
i32 i0_from = xfloor_from;
i32 i1_from = xceil_from;
i32 i0_to = xfloor_to;
i32 i1_to = xceil_to;
for (i32 j = j0clamp; j <= j1clamp; ++j)
{
entity_pos_t dy_from = entity_pos_t::FromInt(j) - y_from;
entity_pos_t dy2_from = dy_from.Square();
while (dy2_from + (entity_pos_t::FromInt(i0_from-1) - x_from).Square() <= r2)
--i0_from;
while (i0_from < xceil_from && dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() > r2)
++i0_from;
while (dy2_from + (entity_pos_t::FromInt(i1_from+1) - x_from).Square() <= r2)
++i1_from;
while (i1_from > xfloor_from && dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() > r2)
--i1_from;
entity_pos_t dy_to = entity_pos_t::FromInt(j) - y_to;
entity_pos_t dy2_to = dy_to.Square();
while (dy2_to + (entity_pos_t::FromInt(i0_to-1) - x_to).Square() <= r2)
--i0_to;
while (i0_to < xceil_to && dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() > r2)
++i0_to;
while (dy2_to + (entity_pos_t::FromInt(i1_to+1) - x_to).Square() <= r2)
++i1_to;
while (i1_to > xfloor_to && dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() > r2)
--i1_to;
#if DEBUG_RANGE_MANAGER_BOUNDS
if (i0_from <= i1_from)
{
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from) - x_from).Square() <= r2);
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from) - x_from).Square() <= r2);
}
ENSURE(dy2_from + (entity_pos_t::FromInt(i0_from - 1) - x_from).Square() > r2);
ENSURE(dy2_from + (entity_pos_t::FromInt(i1_from + 1) - x_from).Square() > r2);
if (i0_to <= i1_to)
{
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to) - x_to).Square() <= r2);
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to) - x_to).Square() <= r2);
}
ENSURE(dy2_to + (entity_pos_t::FromInt(i0_to - 1) - x_to).Square() > r2);
ENSURE(dy2_to + (entity_pos_t::FromInt(i1_to + 1) - x_to).Square() > r2);
#endif
// Check whether this strip moved at all
if (!(i0_to == i0_from && i1_to == i1_from))
{
i32 i0clamp_from = std::max(i0_from, 1);
i32 i1clamp_from = std::min(i1_from, m_TerrainVerticesPerSide-2);
i32 i0clamp_to = std::max(i0_to, 1);
i32 i1clamp_to = std::min(i1_to, m_TerrainVerticesPerSide-2);
// Check whether one strip is negative width,
// and we can just add/remove the entire other strip
if (i1clamp_from < i0clamp_from)
{
LosAddStripHelper(owner, i0clamp_to, i1clamp_to, j, countsData);
}
else if (i1clamp_to < i0clamp_to)
{
LosRemoveStripHelper(owner, i0clamp_from, i1clamp_from, j, countsData);
}
else
{
// There are four possible regions of overlap between the two strips
// (remove before add, remove after add, add before remove, add after remove).
// Process each of the regions as its own strip.
// (If this produces negative-width strips then they'll just get ignored
// which is fine.)
// (If the strips don't actually overlap (which is very rare with normal unit
// movement speeds), the region between them will be both added and removed,
// so we have to do the add first to avoid overflowing to -1 and triggering
// assertion failures.)
LosAddStripHelper(owner, i0clamp_to, i0clamp_from-1, j, countsData);
LosAddStripHelper(owner, i1clamp_from+1, i1clamp_to, j, countsData);
LosRemoveStripHelper(owner, i0clamp_from, i0clamp_to-1, j, countsData);
LosRemoveStripHelper(owner, i1clamp_to+1, i1clamp_from, j, countsData);
}
}
}
}
void LosAdd(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper((u8)owner, visionRange, pos);
}
void LosRemove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D pos)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
LosUpdateHelper((u8)owner, visionRange, pos);
}
void LosMove(player_id_t owner, entity_pos_t visionRange, CFixedVector2D from, CFixedVector2D to)
{
if (visionRange.IsZero() || owner <= 0 || owner > MAX_LOS_PLAYER_ID)
return;
if ((from - to).CompareLength(visionRange) > 0)
{
// If it's a very large move, then simply remove and add to the new position
LosUpdateHelper((u8)owner, visionRange, from);
LosUpdateHelper((u8)owner, visionRange, to);
}
else
{
// Otherwise use the version optimised for mostly-overlapping circles
LosUpdateHelperIncremental((u8)owner, visionRange, from, to);
}
}
virtual u8 GetPercentMapExplored(player_id_t player)
{
return m_ExploredVertices.at((u8)player) * 100 / m_TotalInworldVertices;
}
};
REGISTER_COMPONENT_TYPE(RangeManager)
Index: ps/trunk/source/simulation2/components/CCmpVisualActor.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 16021)
+++ ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 16022)
@@ -1,770 +1,770 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpVisual.h"
#include "simulation2/MessageTypes.h"
#include "ICmpFootprint.h"
#include "ICmpUnitRenderer.h"
#include "ICmpOwnership.h"
#include "ICmpPosition.h"
#include "ICmpTemplateManager.h"
#include "ICmpTerrain.h"
#include "ICmpUnitMotion.h"
#include "ICmpValueModificationManager.h"
-#include "ICmpVision.h"
+#include "ICmpVisibility.h"
#include "graphics/Decal.h"
#include "graphics/Frustum.h"
#include "graphics/Model.h"
#include "graphics/ObjectBase.h"
#include "graphics/ObjectEntry.h"
#include "graphics/Unit.h"
#include "graphics/UnitAnimation.h"
#include "graphics/UnitManager.h"
#include "maths/BoundingSphere.h"
#include "maths/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "ps/GameSetup/Config.h"
#include "renderer/Scene.h"
class CCmpVisualActor : public ICmpVisual
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Update_Final);
componentManager.SubscribeToMessageType(MT_InterpolatedPositionChanged);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
componentManager.SubscribeToMessageType(MT_ValueModification);
componentManager.SubscribeToMessageType(MT_TerrainChanged);
componentManager.SubscribeToMessageType(MT_Destroy);
}
DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
private:
std::wstring m_BaseActorName, m_ActorName;
bool m_IsFoundationActor;
CUnit* m_Unit;
fixed m_R, m_G, m_B; // shading colour
std::map m_AnimOverride;
// Current animation state
fixed m_AnimRunThreshold; // if non-zero this is the special walk/run mode
std::string m_AnimName;
bool m_AnimOnce;
fixed m_AnimSpeed;
std::wstring m_SoundGroup;
fixed m_AnimDesync;
fixed m_AnimSyncRepeatTime; // 0.0 if not synced
u32 m_Seed; // seed used for random variations
bool m_ConstructionPreview;
bool m_VisibleInAtlasOnly;
bool m_IsActorOnly; // an in-world entity should not have this or it might not be rendered.
ICmpUnitRenderer::tag_t m_ModelTag;
public:
static std::string GetSchema()
{
return
"Display the unit using the engine's actor system."
""
"units/hellenes/infantry_spearman_b.xml"
""
""
"structures/hellenes/barracks.xml"
"structures/fndn_4x4.xml"
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
""
"";
}
virtual void Init(const CParamNode& paramNode)
{
m_Unit = NULL;
m_R = m_G = m_B = fixed::FromInt(1);
m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk();
m_Seed = GetEntityId();
m_IsFoundationActor = paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk();
if (m_IsFoundationActor)
m_BaseActorName = m_ActorName = paramNode.GetChild("FoundationActor").ToString();
else
m_BaseActorName = m_ActorName = paramNode.GetChild("Actor").ToString();
m_VisibleInAtlasOnly = paramNode.GetChild("VisibleInAtlasOnly").ToBool();
m_IsActorOnly = paramNode.GetChild("ActorOnly").IsOk();
InitModel(paramNode);
// We need to select animation even if graphics are disabled, as this modifies serialized state
SelectAnimation("idle", false, fixed::FromInt(1), L"");
}
virtual void Deinit()
{
if (m_Unit)
{
GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
m_Unit = NULL;
}
}
template
void SerializeCommon(S& serialize)
{
serialize.NumberFixed_Unbounded("r", m_R);
serialize.NumberFixed_Unbounded("g", m_G);
serialize.NumberFixed_Unbounded("b", m_B);
serialize.NumberFixed_Unbounded("anim run threshold", m_AnimRunThreshold);
serialize.StringASCII("anim name", m_AnimName, 0, 256);
serialize.Bool("anim once", m_AnimOnce);
serialize.NumberFixed_Unbounded("anim speed", m_AnimSpeed);
serialize.String("sound group", m_SoundGroup, 0, 256);
serialize.NumberFixed_Unbounded("anim desync", m_AnimDesync);
serialize.NumberFixed_Unbounded("anim sync repeat time", m_AnimSyncRepeatTime);
serialize.NumberU32_Unbounded("seed", m_Seed);
// TODO: variation/selection strings
serialize.String("actor", m_ActorName, 0, 256);
// TODO: store actor variables?
}
virtual void Serialize(ISerializer& serialize)
{
// TODO: store the actor name, if !debug and it differs from the template
if (serialize.IsDebug())
{
serialize.String("base actor", m_BaseActorName, 0, 256);
}
SerializeCommon(serialize);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
u32 oldSeed = GetActorSeed();
SerializeCommon(deserialize);
// If we serialized a different seed or different actor, reload actor
if (oldSeed != GetActorSeed() || m_BaseActorName != m_ActorName)
ReloadActor();
fixed repeattime = m_AnimSyncRepeatTime; // save because SelectAnimation overwrites it
if (m_AnimRunThreshold.IsZero())
SelectAnimation(m_AnimName, m_AnimOnce, m_AnimSpeed, m_SoundGroup);
else
SelectMovementAnimation(m_AnimRunThreshold);
SetAnimationSyncRepeat(repeattime);
if (m_Unit)
{
CmpPtr cmpOwnership(GetEntityHandle());
if (cmpOwnership)
m_Unit->GetModel().SetPlayerID(cmpOwnership->GetOwner());
}
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
// Quick exit for running in non-graphical mode
if (m_Unit == NULL)
return;
switch (msg.GetType())
{
case MT_Update_Final:
{
const CMessageUpdate_Final& msgData = static_cast (msg);
Update(msgData.turnLength);
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast (msg);
m_Unit->GetModel().SetPlayerID(msgData.to);
break;
}
case MT_TerrainChanged:
{
const CMessageTerrainChanged& msgData = static_cast (msg);
m_Unit->GetModel().SetTerrainDirty(msgData.i0, msgData.j0, msgData.i1, msgData.j1);
break;
}
case MT_ValueModification:
{
const CMessageValueModification& msgData = static_cast (msg);
if (msgData.component != L"VisualActor")
break;
CmpPtr cmpValueModificationManager(GetSystemEntity());
std::wstring newActorName;
if (m_IsFoundationActor)
newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/FoundationActor", m_BaseActorName, GetEntityId());
else
newActorName = cmpValueModificationManager->ApplyModifications(L"VisualActor/Actor", m_BaseActorName, GetEntityId());
if (newActorName != m_ActorName)
{
m_ActorName = newActorName;
ReloadActor();
}
break;
}
case MT_InterpolatedPositionChanged:
{
const CMessageInterpolatedPositionChanged& msgData = static_cast (msg);
if (m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
cmpModelRenderer->UpdateUnitPos(m_ModelTag, msgData.inWorld, msgData.pos0, msgData.pos1);
}
break;
}
case MT_Destroy:
{
if (m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
cmpModelRenderer->RemoveUnit(m_ModelTag);
m_ModelTag = ICmpUnitRenderer::tag_t();
}
break;
}
}
}
virtual CBoundingBoxAligned GetBounds()
{
if (!m_Unit)
return CBoundingBoxAligned::EMPTY;
return m_Unit->GetModel().GetWorldBounds();
}
virtual CUnit* GetUnit()
{
return m_Unit;
}
virtual CBoundingBoxOriented GetSelectionBox()
{
if (!m_Unit)
return CBoundingBoxOriented::EMPTY;
return m_Unit->GetModel().GetSelectionBox();
}
virtual CVector3D GetPosition()
{
if (!m_Unit)
return CVector3D(0, 0, 0);
return m_Unit->GetModel().GetTransform().GetTranslation();
}
virtual std::wstring GetActorShortName()
{
if (!m_Unit)
return L"";
return m_Unit->GetObject().m_Base->m_ShortName;
}
virtual std::wstring GetProjectileActor()
{
if (!m_Unit)
return L"";
return m_Unit->GetObject().m_ProjectileModelName;
}
virtual CVector3D GetProjectileLaunchPoint()
{
if (!m_Unit)
return CVector3D();
if (m_Unit->GetModel().ToCModel())
{
// Ensure the prop transforms are correct
CmpPtr cmpUnitRenderer(GetSystemEntity());
CmpPtr cmpPosition(GetEntityHandle());
if (cmpUnitRenderer && cmpPosition)
{
float frameOffset = cmpUnitRenderer->GetFrameOffset();
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset));
m_Unit->GetModel().SetTransform(transform);
m_Unit->GetModel().ValidatePosition();
}
CModelAbstract* ammo = m_Unit->GetModel().ToCModel()->FindFirstAmmoProp();
if (ammo)
return ammo->GetTransform().GetTranslation();
}
return CVector3D();
}
virtual void SelectAnimation(std::string name, bool once, fixed speed, std::wstring soundgroup)
{
m_AnimRunThreshold = fixed::Zero();
m_AnimName = name;
m_AnimOnce = once;
m_AnimSpeed = speed;
m_SoundGroup = soundgroup;
m_AnimDesync = fixed::FromInt(1)/20; // TODO: make this an argument
m_AnimSyncRepeatTime = fixed::Zero();
if (m_Unit)
{
m_Unit->SetEntitySelection(m_AnimName);
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
}
}
virtual void ReplaceMoveAnimation(std::string name, std::string replace)
{
m_AnimOverride[name] = replace;
}
virtual void ResetMoveAnimation(std::string name)
{
std::map::const_iterator it = m_AnimOverride.find(name);
if (it != m_AnimOverride.end())
m_AnimOverride.erase(name);
}
virtual void SetUnitEntitySelection(const CStr& selection)
{
if (m_Unit)
{
m_Unit->SetEntitySelection(selection);
}
}
virtual void SelectMovementAnimation(fixed runThreshold)
{
m_AnimRunThreshold = runThreshold;
if (m_Unit)
{
m_Unit->SetEntitySelection("walk");
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationState("walk", false, 1.f, 0.f, L"");
}
}
virtual void SetAnimationSyncRepeat(fixed repeattime)
{
m_AnimSyncRepeatTime = repeattime;
if (m_Unit)
{
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
}
}
virtual void SetAnimationSyncOffset(fixed actiontime)
{
if (m_Unit)
{
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationSyncOffset(actiontime.ToFloat());
}
}
virtual void SetShadingColour(fixed r, fixed g, fixed b, fixed a)
{
m_R = r;
m_G = g;
m_B = b;
UNUSED2(a); // TODO: why is this even an argument?
if (m_Unit)
{
CModelAbstract& model = m_Unit->GetModel();
model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f));
}
}
virtual void SetVariable(std::string name, float value)
{
if (m_Unit)
{
m_Unit->GetModel().SetEntityVariable(name, value);
}
}
virtual u32 GetActorSeed()
{
return m_Seed;
}
virtual void SetActorSeed(u32 seed)
{
if (seed == m_Seed)
return;
m_Seed = seed;
ReloadActor();
}
virtual bool HasConstructionPreview()
{
return m_ConstructionPreview;
}
virtual void Hotload(const VfsPath& name)
{
if (!m_Unit)
return;
if (name != m_ActorName)
return;
ReloadActor();
}
private:
/// Helper function shared by component init and actor reloading
void InitModel(const CParamNode& paramNode);
/// Helper method; initializes the model selection shape descriptor from XML. Factored out for readability of @ref Init.
void InitSelectionShapeDescriptor(const CParamNode& paramNode);
void ReloadActor();
void Update(fixed turnLength);
};
REGISTER_COMPONENT_TYPE(VisualActor)
// ------------------------------------------------------------------------------------------------------------------
void CCmpVisualActor::InitModel(const CParamNode& paramNode)
{
if (!GetSimContext().HasUnitManager())
return;
std::set selections;
std::wstring actorName = m_ActorName;
if (actorName.find(L".xml") == std::wstring::npos)
actorName += L".xml";
m_Unit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed(), selections);
if (!m_Unit)
return;
CModelAbstract& model = m_Unit->GetModel();
if (model.ToCModel())
{
u32 modelFlags = 0;
if (paramNode.GetChild("SilhouetteDisplay").ToBool())
modelFlags |= MODELFLAG_SILHOUETTE_DISPLAY;
if (paramNode.GetChild("SilhouetteOccluder").ToBool())
modelFlags |= MODELFLAG_SILHOUETTE_OCCLUDER;
- CmpPtr cmpVision(GetEntityHandle());
- if (cmpVision && cmpVision->GetAlwaysVisible())
+ CmpPtr cmpVisibility(GetEntityHandle());
+ if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
modelFlags |= MODELFLAG_IGNORE_LOS;
model.ToCModel()->AddFlagsRec(modelFlags);
}
if (paramNode.GetChild("DisableShadows").IsOk())
{
if (model.ToCModel())
model.ToCModel()->RemoveShadowsRec();
else if (model.ToCModelDecal())
model.ToCModelDecal()->RemoveShadows();
}
// Initialize the model's selection shape descriptor. This currently relies on the component initialization order; the
// Footprint component must be initialized before this component (VisualActor) to support the ability to use the footprint
// shape for the selection box (instead of the default recursive bounding box). See TypeList.h for the order in
// which components are initialized; if for whatever reason you need to get rid of this dependency, you can always just
// initialize the selection shape descriptor on-demand.
InitSelectionShapeDescriptor(paramNode);
m_Unit->SetID(GetEntityId());
bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
CmpPtr cmpPosition(GetEntityHandle());
if (cmpPosition)
cmpPosition->SetActorFloating(floating);
if (!m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
if (cmpModelRenderer)
{
// TODO: this should account for all possible props, animations, etc,
// else we might accidentally cull the unit when it should be visible
CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
int flags = 0;
if (m_IsActorOnly)
flags |= ICmpUnitRenderer::ACTOR_ONLY;
if (m_VisibleInAtlasOnly)
flags |= ICmpUnitRenderer::VISIBLE_IN_ATLAS_ONLY;
m_ModelTag = cmpModelRenderer->AddUnit(GetEntityHandle(), m_Unit, boundSphere, flags);
}
}
}
void CCmpVisualActor::InitSelectionShapeDescriptor(const CParamNode& paramNode)
{
// by default, we don't need a custom selection shape and we can just keep the default behaviour
CModelAbstract::CustomSelectionShape* shapeDescriptor = NULL;
const CParamNode& shapeNode = paramNode.GetChild("SelectionShape");
if (shapeNode.IsOk())
{
if (shapeNode.GetChild("Bounds").IsOk())
{
// default; no need to take action
}
else if (shapeNode.GetChild("Footprint").IsOk())
{
CmpPtr cmpFootprint(GetEntityHandle());
if (cmpFootprint)
{
ICmpFootprint::EShape fpShape; // fp stands for "footprint"
entity_pos_t fpSize0, fpSize1, fpHeight; // fp stands for "footprint"
cmpFootprint->GetShape(fpShape, fpSize0, fpSize1, fpHeight);
float size0 = fpSize0.ToFloat();
float size1 = fpSize1.ToFloat();
// TODO: we should properly distinguish between CIRCLE and SQUARE footprint shapes here, but since cylinders
// aren't implemented yet and are almost indistinguishable from boxes for small enough sizes anyway,
// we'll just use boxes for either case. However, for circular footprints the size0 and size1 values both
// represent the radius, so we do have to adjust them to match the size1 and size0's of square footprints
// (which represent the full width and depth).
if (fpShape == ICmpFootprint::CIRCLE)
{
size0 *= 2;
size1 *= 2;
}
shapeDescriptor = new CModelAbstract::CustomSelectionShape;
shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
shapeDescriptor->m_Size0 = size0;
shapeDescriptor->m_Size1 = size1;
shapeDescriptor->m_Height = fpHeight.ToFloat();
}
else
{
LOGERROR(L"[VisualActor] Cannot apply footprint-based SelectionShape; Footprint component not initialized.");
}
}
else if (shapeNode.GetChild("Box").IsOk())
{
// TODO: we might need to support the ability to specify a different box center in the future
shapeDescriptor = new CModelAbstract::CustomSelectionShape;
shapeDescriptor->m_Type = CModelAbstract::CustomSelectionShape::BOX;
shapeDescriptor->m_Size0 = shapeNode.GetChild("Box").GetChild("@width").ToFixed().ToFloat();
shapeDescriptor->m_Size1 = shapeNode.GetChild("Box").GetChild("@depth").ToFixed().ToFloat();
shapeDescriptor->m_Height = shapeNode.GetChild("Box").GetChild("@height").ToFixed().ToFloat();
}
else if (shapeNode.GetChild("Cylinder").IsOk())
{
LOGWARNING(L"[VisualActor] TODO: Cylinder selection shapes are not yet implemented; defaulting to recursive bounding boxes");
}
else
{
// shouldn't happen by virtue of validation against schema
LOGERROR(L"[VisualActor] No selection shape specified");
}
}
ENSURE(m_Unit);
// the model is now responsible for cleaning up the descriptor
m_Unit->GetModel().SetCustomSelectionShape(shapeDescriptor);
}
void CCmpVisualActor::ReloadActor()
{
if (!m_Unit)
return;
// Save some data from the old unit
CColor shading = m_Unit->GetModel().GetShadingColor();
player_id_t playerID = m_Unit->GetModel().GetPlayerID();
// Replace with the new unit
GetSimContext().GetUnitManager().DeleteUnit(m_Unit);
// HACK: selection shape needs template data, but rather than storing all that data
// in the component, we load the template here and pass it into a helper function
CmpPtr cmpTemplateManager(GetSystemEntity());
const CParamNode* node = cmpTemplateManager->LoadLatestTemplate(GetEntityId());
ENSURE(node && node->GetChild("VisualActor").IsOk());
InitModel(node->GetChild("VisualActor"));
m_Unit->SetEntitySelection(m_AnimName);
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str());
// We'll lose the exact synchronisation but we should at least make sure it's going at the correct rate
if (!m_AnimSyncRepeatTime.IsZero())
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationSyncRepeat(m_AnimSyncRepeatTime.ToFloat());
m_Unit->GetModel().SetShadingColor(shading);
m_Unit->GetModel().SetPlayerID(playerID);
if (m_ModelTag.valid())
{
CmpPtr cmpModelRenderer(GetSystemEntity());
CBoundingBoxAligned bounds = m_Unit->GetModel().GetWorldBoundsRec();
CBoundingSphere boundSphere = CBoundingSphere::FromSweptBox(bounds);
cmpModelRenderer->UpdateUnit(m_ModelTag, m_Unit, boundSphere);
}
}
void CCmpVisualActor::Update(fixed UNUSED(turnLength))
{
if (m_Unit == NULL)
return;
// If we're in the special movement mode, select an appropriate animation
if (!m_AnimRunThreshold.IsZero())
{
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return;
CmpPtr cmpUnitMotion(GetEntityHandle());
if (!cmpUnitMotion)
return;
float speed = cmpUnitMotion->GetCurrentSpeed().ToFloat();
std::string name;
if (speed == 0.0f)
name = "idle";
else
name = (speed < m_AnimRunThreshold.ToFloat()) ? "walk" : "run";
std::map::const_iterator it = m_AnimOverride.find(name);
if (it != m_AnimOverride.end())
name = it->second;
m_Unit->SetEntitySelection(name);
if (speed == 0.0f)
{
m_Unit->SetEntitySelection(name);
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationState(name, false, 1.f, 0.f, L"");
}
else
{
m_Unit->SetEntitySelection(name);
if (m_Unit->GetAnimation())
m_Unit->GetAnimation()->SetAnimationState(name, false, speed, 0.f, L"");
}
}
}
Index: ps/trunk/source/simulation2/components/ICmpVision.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpVision.cpp (revision 16021)
+++ ps/trunk/source/simulation2/components/ICmpVision.cpp (revision 16022)
@@ -1,28 +1,26 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2014 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 "ICmpVision.h"
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(Vision)
DEFINE_INTERFACE_METHOD_0("GetRange", entity_pos_t, ICmpVision, GetRange)
-DEFINE_INTERFACE_METHOD_0("GetRetainInFog", bool, ICmpVision, GetRetainInFog)
-DEFINE_INTERFACE_METHOD_0("GetAlwaysVisible", bool, ICmpVision, GetAlwaysVisible)
END_INTERFACE_WRAPPER(Vision)
Index: ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp (revision 16021)
+++ ps/trunk/source/simulation2/components/CCmpUnitRenderer.cpp (revision 16022)
@@ -1,412 +1,412 @@
/* Copyright (C) 2014 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpUnitRenderer.h"
#include "simulation2/MessageTypes.h"
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpSelectable.h"
-#include "ICmpVision.h"
+#include "ICmpVisibility.h"
#include "graphics/Frustum.h"
#include "graphics/ModelAbstract.h"
#include "graphics/ObjectEntry.h"
#include "graphics/Overlay.h"
#include "graphics/Unit.h"
#include "maths/BoundingSphere.h"
#include "maths/Matrix3D.h"
#include "renderer/Scene.h"
#include "tools/atlas/GameInterface/GameLoop.h"
/**
* Efficiently(ish) renders all the units in the world.
*
* The class maintains a list of all units that currently exist, and the data
* needed for frustum-culling them. To minimise the amount of work done per
* frame (despite a unit's interpolated position changing every frame), the
* culling data is only updated once per turn: we store the position at the
* start of the turn, and the position at the end of the turn, and assume the
* unit might be anywhere between those two points (linearly).
*
* (Note this is a slightly invalid assumption: units don't always move linearly,
* since their interpolated position depends on terrain and water. But over a
* single turn it's probably going to be a good enough approximation, and will
* only break for units that both start and end the turn off-screen.)
*
* We want to ignore rotation entirely, since it's a complex function of
* interpolated position and terrain. So we store a bounding sphere, which
* is rotation-independent, instead of a bounding box.
*/
class CCmpUnitRenderer : public ICmpUnitRenderer
{
public:
struct SUnit
{
CEntityHandle entity;
CUnit* actor;
int flags;
/**
* m_FrameNumber from when the model's transform was last updated.
* This is used to avoid recomputing it multiple times per frame
* if a model is visible in multiple cull groups.
*/
int lastTransformFrame;
/**
* Worst-case bounding shape, relative to position. Needs to account
* for all possible animations, orientations, etc.
*/
CBoundingSphere boundsApprox;
/**
* Cached LOS visibility status.
*/
ICmpRangeManager::ELosVisibility visibility;
bool visibilityDirty;
/**
* Whether the unit has a valid position. If false, pos0 and pos1
* are meaningless.
*/
bool inWorld;
/**
* World-space positions to interpolate between.
*/
CVector3D pos0;
CVector3D pos1;
/**
* Bounds encompassing the unit's bounds when it is anywhere between
* pos0 and pos1.
*/
CBoundingSphere sweptBounds;
/**
* For debug overlay.
*/
bool culled;
};
std::vector m_Units;
std::vector m_UnitTagsFree;
int m_FrameNumber;
float m_FrameOffset;
bool m_EnableDebugOverlays;
std::vector m_DebugSpheres;
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_TurnStart);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(UnitRenderer)
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_FrameNumber = 0;
m_FrameOffset = 0.0f;
m_EnableDebugOverlays = false;
}
virtual void Deinit()
{
}
virtual void Serialize(ISerializer& UNUSED(serialize))
{
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
{
Init(paramNode);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_TurnStart:
{
TurnStart();
break;
}
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast (msg);
Interpolate(msgData.deltaSimTime, msgData.offset);
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
break;
}
}
}
SUnit* LookupUnit(tag_t tag)
{
if (tag.n < 1 || tag.n - 1 >= m_Units.size())
return NULL;
return &m_Units[tag.n - 1];
}
virtual tag_t AddUnit(CEntityHandle entity, CUnit* actor, const CBoundingSphere& boundsApprox, int flags)
{
ENSURE(actor != NULL);
tag_t tag;
if (!m_UnitTagsFree.empty())
{
tag = m_UnitTagsFree.back();
m_UnitTagsFree.pop_back();
}
else
{
m_Units.push_back(SUnit());
tag.n = m_Units.size();
}
SUnit* unit = LookupUnit(tag);
unit->entity = entity;
unit->actor = actor;
unit->lastTransformFrame = -1;
unit->flags = flags;
unit->boundsApprox = boundsApprox;
unit->inWorld = false;
unit->visibilityDirty = true;
unit->pos0 = unit->pos1 = CVector3D();
return tag;
}
virtual void RemoveUnit(tag_t tag)
{
SUnit* unit = LookupUnit(tag);
unit->actor = NULL;
unit->inWorld = false;
m_UnitTagsFree.push_back(tag);
}
void RecomputeSweptBounds(SUnit* unit)
{
// Compute the bounding sphere of the capsule formed by
// sweeping boundsApprox from pos0 to pos1
CVector3D mid = (unit->pos0 + unit->pos1) * 0.5f + unit->boundsApprox.GetCenter();
float radius = (unit->pos1 - unit->pos0).Length() * 0.5f + unit->boundsApprox.GetRadius();
unit->sweptBounds = CBoundingSphere(mid, radius);
}
virtual void UpdateUnit(tag_t tag, CUnit* actor, const CBoundingSphere& boundsApprox)
{
SUnit* unit = LookupUnit(tag);
unit->actor = actor;
unit->boundsApprox = boundsApprox;
RecomputeSweptBounds(unit);
}
virtual void UpdateUnitPos(tag_t tag, bool inWorld, const CVector3D& pos0, const CVector3D& pos1)
{
SUnit* unit = LookupUnit(tag);
unit->inWorld = inWorld;
unit->pos0 = pos0;
unit->pos1 = pos1;
unit->visibilityDirty = true;
RecomputeSweptBounds(unit);
}
void TurnStart();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
void UpdateVisibility(SUnit& unit);
virtual float GetFrameOffset()
{
return m_FrameOffset;
}
virtual void SetDebugOverlay(bool enabled)
{
m_EnableDebugOverlays = enabled;
}
};
void CCmpUnitRenderer::TurnStart()
{
PROFILE3("UnitRenderer::TurnStart");
// Assume units have stopped moving after the previous turn. If that assumption is not
// correct, we will get a UpdateUnitPos to tell us about its movement in the new turn.
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
unit.pos0 = unit.pos1;
unit.sweptBounds = CBoundingSphere(unit.pos1, unit.boundsApprox.GetRadius());
// Visibility must be recomputed on the first frame during this turn
unit.visibilityDirty = true;
}
}
void CCmpUnitRenderer::Interpolate(float frameTime, float frameOffset)
{
PROFILE3("UnitRenderer::Interpolate");
++m_FrameNumber;
m_FrameOffset = frameOffset;
// TODO: we shouldn't update all the animations etc for units that are off-screen
// (but need to be careful about e.g. sounds triggered by animations of off-screen
// units)
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (unit.actor)
unit.actor->UpdateModel(frameTime);
}
m_DebugSpheres.clear();
if (m_EnableDebugOverlays)
{
for (size_t i = 0; i < m_Units.size(); i++)
{
SUnit& unit = m_Units[i];
if (!(unit.actor && unit.inWorld))
continue;
SOverlaySphere sphere;
sphere.m_Center = unit.sweptBounds.GetCenter();
sphere.m_Radius = unit.sweptBounds.GetRadius();
if (unit.culled)
sphere.m_Color = CColor(1.0f, 0.5f, 0.5f, 0.5f);
else
sphere.m_Color = CColor(0.5f, 0.5f, 1.0f, 0.5f);
m_DebugSpheres.push_back(sphere);
}
}
}
void CCmpUnitRenderer::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
// TODO: need a coarse culling pass based on some kind of spatial data
// structure - that's the main point of this design. Once we've got a
// rough list of possibly-visible units, then we can do the more precise
// culling. (And once it's cheap enough, we can do multiple culling passes
// per frame - one for shadow generation, one for water reflections, etc.)
PROFILE3("UnitRenderer::RenderSubmit");
for (size_t i = 0; i < m_Units.size(); ++i)
{
SUnit& unit = m_Units[i];
unit.culled = true;
if (!(unit.actor && unit.inWorld))
continue;
if (!g_AtlasGameLoop->running && !g_RenderActors && (unit.flags & ACTOR_ONLY))
continue;
if (!g_AtlasGameLoop->running && (unit.flags & VISIBLE_IN_ATLAS_ONLY))
continue;
if (culling && !frustum.IsSphereVisible(unit.sweptBounds.GetCenter(), unit.sweptBounds.GetRadius()))
continue;
if (unit.visibilityDirty)
UpdateVisibility(unit);
if (unit.visibility == ICmpRangeManager::VIS_HIDDEN)
continue;
unit.culled = false;
CModelAbstract& unitModel = unit.actor->GetModel();
if (unit.lastTransformFrame != m_FrameNumber)
{
CmpPtr cmpPosition(unit.entity);
if (!cmpPosition)
continue;
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(m_FrameOffset));
unitModel.SetTransform(transform);
unit.lastTransformFrame = m_FrameNumber;
}
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), unitModel.GetWorldBoundsRec()))
continue;
collector.SubmitRecursive(&unitModel);
}
for (size_t i = 0; i < m_DebugSpheres.size(); ++i)
collector.Submit(&m_DebugSpheres[i]);
}
void CCmpUnitRenderer::UpdateVisibility(SUnit& unit)
{
if (unit.inWorld)
{
// The 'always visible' flag means we should always render the unit
// (regardless of whether the LOS system thinks it's visible)
- CmpPtr cmpVision(unit.entity);
- if (cmpVision && cmpVision->GetAlwaysVisible())
+ CmpPtr cmpVisibility(unit.entity);
+ if (cmpVisibility && cmpVisibility->GetAlwaysVisible())
unit.visibility = ICmpRangeManager::VIS_VISIBLE;
else
{
CmpPtr cmpRangeManager(GetSystemEntity());
unit.visibility = cmpRangeManager->GetLosVisibility(unit.entity,
GetSimContext().GetCurrentDisplayedPlayer());
}
}
else
unit.visibility = ICmpRangeManager::VIS_HIDDEN;
// Change the visibility of the visual actor's selectable if it has one.
CmpPtr cmpSelectable(unit.entity);
if (cmpSelectable)
cmpSelectable->SetVisibility(unit.visibility != ICmpRangeManager::VIS_HIDDEN);
unit.visibilityDirty = false;
}
REGISTER_COMPONENT_TYPE(UnitRenderer)
Index: ps/trunk/source/simulation2/components/ICmpVisibility.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpVisibility.cpp (revision 16021)
+++ ps/trunk/source/simulation2/components/ICmpVisibility.cpp (revision 16022)
@@ -1,52 +1,62 @@
/* Copyright (C) 2014 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 "ICmpVisibility.h"
#include "simulation2/scripting/ScriptComponent.h"
#include "simulation2/system/InterfaceScripted.h"
BEGIN_INTERFACE_WRAPPER(Visibility)
END_INTERFACE_WRAPPER(Visibility)
class CCmpVisibilityScripted : public ICmpVisibility
{
public:
DEFAULT_SCRIPT_WRAPPER(VisibilityScripted)
virtual ICmpRangeManager::ELosVisibility GetLosVisibility(player_id_t player, bool isOutsideFog, bool forceRetainInFog)
{
int visibility = m_Script.Call("GetLosVisibility", player, isOutsideFog, forceRetainInFog);
switch (visibility)
{
case ICmpRangeManager::VIS_HIDDEN:
return ICmpRangeManager::VIS_HIDDEN;
case ICmpRangeManager::VIS_FOGGED:
return ICmpRangeManager::VIS_FOGGED;
case ICmpRangeManager::VIS_VISIBLE:
return ICmpRangeManager::VIS_VISIBLE;
default:
LOGERROR(L"Received the invalid visibility value %d from the Visibility scripted component!", visibility);
return ICmpRangeManager::VIS_HIDDEN;
}
}
+
+ virtual bool GetRetainInFog()
+ {
+ return m_Script.Call("GetRetainInFog");
+ }
+
+ virtual bool GetAlwaysVisible()
+ {
+ return m_Script.Call("GetAlwaysVisible");
+ }
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(VisibilityScripted)
Index: ps/trunk/source/simulation2/components/ICmpVision.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpVision.h (revision 16021)
+++ ps/trunk/source/simulation2/components/ICmpVision.h (revision 16022)
@@ -1,40 +1,36 @@
-/* Copyright (C) 2010 Wildfire Games.
+/* Copyright (C) 2014 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_ICMPVISION
#define INCLUDED_ICMPVISION
#include "simulation2/system/Interface.h"
#include "simulation2/helpers/Position.h"
/**
- * Vision (LOS etc) interface (typically implemented by a scripted component).
+ * Vision range interface
*/
class ICmpVision : public IComponent
{
public:
virtual entity_pos_t GetRange() = 0;
- virtual bool GetRetainInFog() = 0;
-
- virtual bool GetAlwaysVisible() = 0;
-
DECLARE_INTERFACE_TYPE(Vision)
};
#endif // INCLUDED_ICMPVISION