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.5 150 true false gaia Great White Shark SeaCreature gaia/fauna_fish.png -1 true 5.0 circle/128x128.png circle/128x128_mask.png - + false + + 100 fauna/shark.xml passive 100.0 60.0 100000 300000 1 2 ship 4.0 35.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 @@ 1 10 1 Special 4.0 75 gaia Bench Wooden Bench A short wooden bench. gaia/special_fence.png 1 0 10 0 0 6.0 + + true + 4 - true props/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 @@ Special 10.0 200000 gaia Bridge Bridge Roman engineers constructed bridges using concrete, which they called Opus caementicium. gaia/special_blank.png + + true + 72 - true props/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 @@ Special 10.0 200000 gaia Wooden Bridge Wooden Bridge Roman engineers constructed bridges using concrete, which they called Opus caementicium. gaia/special_blank.png + + true + 72 - true props/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 @@ 2 5 2 Fence 4.0 200 hele Column Doric Column A column of the understated Greek Doric Order. gaia/special_fence.png 1 0 0 20 0 8.0 + + true + 4 - true props/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 @@ 2 5 2 Special 4.0 200 hele Column Fallen Doric Column A column fallen off some Greek ruin. gaia/special_fence.png 1 0 0 20 0 6.0 + + true + 4 - true props/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 @@ 2 5 2 Special 4.0 100 hele Column Fallen Doric Column Column drums fallen off some Greek ruin. gaia/special_fence.png 1 0 0 10 0 6.0 + + true + 4 - true props/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 @@ 1 10 1 Special 4.0 100 gaia Fence Long Wooden Fence A split rail wooden fence. gaia/special_fence.png 1 0 20 0 0 6.0 + + true + 4 - true temp/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 @@ 1 10 1 Special 4.0 50 gaia Fence Short Wooden Fence A split rail wooden fence. gaia/special_fence.png 1 0 10 0 0 6.0 + + true + 4 - true temp/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 @@ 2 10 2 Special 4.0 200 gaia Stone Fence Stone Fence A stone fence. gaia/special_fence.png 1 0 0 20 0 6.0 + + true + 4 - true props/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 @@ Special 10 200 0 0 200 200 8.0 2000 hele Portico Propylaea A 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.png 10 0 0 75 75 false 40 65536 + + true + 20 - true structures/hellenes/propylaea.xml structures/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.0 40.0 10.0 Stoa 10 150 0 0 100 150 10.0 1500 hele Stoa Hellenic Royal Stoa A 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.png 10 0 0 10 15 false 40 65536 0.7 units/mace_thureophoros units/mace_thorakites units/thrace_black_cloak + + true + 40 - true special/greek_stoa_great.xml structures/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 @@ Stoa 10 110 0 0 100 100 10.0 1100 hele Stoa Hellenic Stoa A 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.png 10 0 0 50 50 false 36 65536 + + true + 40 - true special/greek_stoa.xml structures/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 @@ 2 10 2 Special 10.0 1000 gaia Obelisk Egyptian Obelisk A monumental ornamental structure built by the Egyptians of old. -ConquestCritical gaia/special_obelisk.png 10 0 0 200 200 + + true + 12 - true props/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.0 gaia Pyramid Great Pyramid A monumental ornamental structure built by the Egyptians of old. gaia/special_pyramid.png 200 -2.0 10000 stone.ruins 40 attack/destruction/building_collapse_large.xml 6.0 0.6 12.0 + + true + 72 - true special/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.0 gaia Pyramid Minor Pyramid A monumental ornamental structure built by the Egyptians of old. gaia/special_pyramid.png 5000 stone.ruins 30 attack/destruction/building_collapse_large.xml 6.0 0.6 12.0 + + true + 40 - true special/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 @@ 1 10 1 Special 4.0 100 gaia Table Rectangle Table A wooden table. gaia/special_fence.png 1 0 20 0 0 6.0 + + true + 4 - true props/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 @@ 1 10 1 Special 4.0 100 gaia Table Square Table A wooden table. gaia/special_fence.png 1 0 20 0 0 6.0 + + true + 4 - true props/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.0 hele Ruins Unfinished Greek Temple The Hellenes built marvelous temples in order to honour their polytheistic pantheon. While all gods were venerated, a specific patron deity was supposed to watch over each polis. This temple is unfinished and has fallen to ruin. structures/temple.png 10 2000 30 6.0 0.6 12.0 + + true + 40 - true props/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 @@ 0 upright false 6.0 - - - 0 + true false + + + 0 (should be overridden) false false false 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 @@ Special 5.0 200000 gaia Marker Marker gaia/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.0 4400 pers Hanging Gardens of Babylon A 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. 200 6.0 0.6 12.0 100 + + true + 72 - true structures/fndn_8x8.xml structures/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 @@ gaia Gaia true true true true true false false 2.0 0.333 5.0 - - - 0 + true false + + + 0 false true false 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 @@ 1 1 1 1 1 1 0 0 Ranged Infantry land own 0 0 10 0 0 0 0 0.0 3.0 9.8 corpse 0 true true Structure Structure ConquestCritical structure true true true true true false false special/rallypoint art/textures/misc/rallypoint_line.png art/textures/misc/rallypoint_line_mask.png 0.2 square round default default outline_border.png outline_border_mask.png 0.4 interface/complete/building/complete_universal.xml attack/destruction/building_collapse_large.xml interface/alarm/alarm_attackplayer.xml attack/weapon/arrowfly.xml attack/impact/arrow_metal.xml 6.0 0.6 12.0 5 - - - 40 + true false + + + 40 false true false 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 @@ 5 25 5 2 10 2 Wonder 1000 0 1000 1000 1000 10.0 5000 rubble/rubble_stone_6x6 Wonder Bring glory to your civilization and add large tracts of land to your empire. City Wonder structures/wonder.png phase_city 200 0 100 100 100 0.7 pop_wonder interface/complete/building/complete_wonder.xml attack/destruction/building_collapse_large.xml 6.0 0.6 12.0 true 100 65536 + + true + 72 - true structures/fndn_6x6.xml 300 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 @@ 0 2.25 Fauna Animal Organic -ConquestCritical gaia/fauna_generic.png 10 food 99 false 8.0 24.0 2000 8000 15000 60000 6.5 15.0 - + true + + 0 false 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.0 0.2 0 0 upright false 6.0 - - 0 + true false + + + 0 structures/rubble_stone_3x3.xml false false false 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.0 gaia Settlement Settlement Build a Civic Center at this location to expand your territory. gaia/special_settlement.png settlement true false false true true false false - - 0 + true false + + + 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 @@ 1 1 5 1 0 1 0 0 0 0 80.0 0.01 0.0 2.5 corpse 100 0 false false Unit Unit ConquestCritical unit true true false false true false false 2.0 1.0 1 10 10 10 10 circle/128x128.png circle/128x128_mask.png interface/alarm/alarm_attackplayer.xml 2.0 0.333 5.0 2 aggressive 12.0 false true false 9 15.0 50.0 0.0 0.1 0.2 default default - - - 10 + false false + + + 10 true false false 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 @@ 0 upright false 6.0 circle/128x128.png circle/128x128_mask.png 0 passive passive false false 20 10.0 10000 100000 1 2 false 8.0 unrestricted default - + true - 0 false + + + 0 false false false 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