Index: ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js
===================================================================
--- ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js (revision 14927)
+++ ps/trunk/binaries/data/mods/public/simulation/components/TechnologyManager.js (revision 14928)
@@ -1,461 +1,461 @@
function TechnologyManager() {}
TechnologyManager.prototype.Schema =
"";
TechnologyManager.prototype.Serialize = function()
{
// The modifications cache will be affected by property reads from the GUI and other places so we shouldn't
// serialize it.
var ret = {};
for (var i in this)
{
if (this.hasOwnProperty(i))
ret[i] = this[i];
}
ret.modificationCache = {};
return ret;
};
TechnologyManager.prototype.Init = function ()
{
var cmpTechTempMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_TechnologyTemplateManager);
this.allTechs = cmpTechTempMan.GetAllTechs();
this.researchedTechs = {}; // technologies which have been researched
this.researchQueued = {}; // technologies which are queued for research
this.researchStarted = {}; // technologies which are being researched currently (non-queued)
// This stores the modifications to unit stats from researched technologies
// Example data: {"ResourceGatherer/Rates/food.grain": [
// {"multiply": 1.15, "affects": ["Female", "Infantry Swordsman"]},
// {"add": 2}
// ]}
this.modifications = {};
this.modificationCache = {}; // Caches the values after technologies have been applied
// e.g. { "Attack/Melee/Hack" : {5: {"origValue": 8, "newValue": 10}, 7: {"origValue": 9, "newValue": 12}, ...}, ...}
// where 5 and 7 are entity id's
this.typeCounts = {}; // stores the number of entities of each type
this.classCounts = {}; // stores the number of entities of each Class
this.typeCountsByClass = {}; // stores the number of entities of each type for each class i.e.
// {"someClass": {"unit/spearman": 2, "unit/cav": 5} "someOtherClass":...}
// Some technologies are automatically researched when their conditions are met. They have no cost and are
// researched instantly. This allows civ bonuses and more complicated technologies.
this.autoResearchTech = {};
for (var key in this.allTechs)
{
if (this.allTechs[key].autoResearch || this.allTechs[key].top)
this.autoResearchTech[key] = this.allTechs[key];
}
};
TechnologyManager.prototype.OnUpdate = function ()
{
this.UpdateAutoResearch();
}
// This function checks if the requirements of any autoresearch techs are met and if they are it researches them
TechnologyManager.prototype.UpdateAutoResearch = function ()
{
for (var key in this.autoResearchTech)
{
if ((this.allTechs[key].autoResearch && this.CanResearch(key))
|| (this.allTechs[key].top && (this.IsTechnologyResearched(this.allTechs[key].top) || this.IsTechnologyResearched(this.allTechs[key].bottom))))
{
delete this.autoResearchTech[key];
this.ResearchTechnology(key);
return; // We will have recursively handled any knock-on effects so can just return
}
}
}
TechnologyManager.prototype.GetTechnologyTemplate = function (tech)
{
if (!(tech in this.allTechs))
return undefined;
return this.allTechs[tech];
};
// Checks an entity template to see if its technology requirements have been met
TechnologyManager.prototype.CanProduce = function (templateName)
{
var cmpTempManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTempManager.GetTemplate(templateName);
if (template.Identity && template.Identity.RequiredTechnology)
return this.IsTechnologyResearched(template.Identity.RequiredTechnology);
else
return true; // If there is no required technology then this entity can be produced
};
TechnologyManager.prototype.IsTechnologyResearched = function (tech)
{
return (this.researchedTechs[tech] !== undefined);
};
// Checks the requirements for a technology to see if it can be researched at the current time
TechnologyManager.prototype.CanResearch = function (tech)
{
var template = this.GetTechnologyTemplate(tech);
if (!template)
{
warn("Technology \"" + tech + "\" does not exist");
return false;
}
// The technology which this technology supersedes is required
if (template.supersedes && !this.IsTechnologyResearched(template.supersedes))
return false;
if (template.top && this.IsInProgress(template.top) ||
template.bottom && this.IsInProgress(template.bottom))
return false;
if (template.pair && !this.CanResearch(template.pair))
return false;
if (this.IsInProgress(tech))
return false;
return this.CheckTechnologyRequirements(template.requirements || null);
};
// Private function for checking a set of requirements is met
TechnologyManager.prototype.CheckTechnologyRequirements = function (reqs)
{
// If there are no requirements then all requirements are met
if (!reqs)
return true;
if (reqs.tech)
{
return this.IsTechnologyResearched(reqs.tech);
}
else if (reqs.all)
{
for (var i = 0; i < reqs.all.length; i++)
{
if (!this.CheckTechnologyRequirements(reqs.all[i]))
return false;
}
return true;
}
else if (reqs.any)
{
for (var i = 0; i < reqs.any.length; i++)
{
if (this.CheckTechnologyRequirements(reqs.any[i]))
return true;
}
return false;
}
else if (reqs.class)
{
if (reqs.numberOfTypes)
{
if (this.typeCountsByClass[reqs.class])
return (reqs.numberOfTypes <= Object.keys(this.typeCountsByClass[reqs.class]).length);
else
return false;
}
else if (reqs.number)
{
if (this.classCounts[reqs.class])
return (reqs.number <= this.classCounts[reqs.class]);
else
return false;
}
}
else if (reqs.civ)
{
var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
if (cmpPlayer && cmpPlayer.GetCiv() == reqs.civ)
return true;
else
return false;
}
// The technologies requirements are not a recognised format
error("Bad requirements " + uneval(reqs));
return false;
};
TechnologyManager.prototype.OnGlobalOwnershipChanged = function (msg)
{
// This automatically updates typeCounts, classCounts and typeCountsByClass
var playerID = (Engine.QueryInterface(this.entity, IID_Player)).GetPlayerID();
if (msg.to == playerID)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
this.typeCounts[template] = this.typeCounts[template] || 0;
this.typeCounts[template] += 1;
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
if (!cmpIdentity)
return;
var classes = cmpIdentity.GetClassesList();
// don't use foundations for the class counts but check if techs apply (e.g. health increase)
if (!Engine.QueryInterface(msg.entity, IID_Foundation))
{
for (var i in classes)
{
this.classCounts[classes[i]] = this.classCounts[classes[i]] || 0;
this.classCounts[classes[i]] += 1;
this.typeCountsByClass[classes[i]] = this.typeCountsByClass[classes[i]] || {};
this.typeCountsByClass[classes[i]][template] = this.typeCountsByClass[classes[i]][template] || 0;
this.typeCountsByClass[classes[i]][template] += 1;
}
}
// Newly created entity, check if any researched techs might apply
// (only do this for new entities because even if an entity is converted or captured,
// we want it to maintain whatever technologies previously applied)
if (msg.from == -1)
{
var modifiedComponents = {};
for (var name in this.modifications)
{
// We only need to find one one tech per component for a match
var modifications = this.modifications[name];
var component = name.split("/")[0];
for (var i in modifications)
if (DoesModificationApply(modifications[i], classes))
{
if (!modifiedComponents[component])
modifiedComponents[component] = [];
modifiedComponents[component].push(name);
}
}
// Send mesage(s) to the entity so it knows about researched techs
for (var component in modifiedComponents)
Engine.PostMessage(msg.entity, MT_ValueModification, { "entities": [msg.entity], "component": component, "valueNames": modifiedComponents[component] });
}
}
if (msg.from == playerID)
{
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var template = cmpTemplateManager.GetCurrentTemplateName(msg.entity);
this.typeCounts[template] -= 1;
if (this.typeCounts[template] <= 0)
delete this.typeCounts[template];
// don't use foundations for the class counts
if (!Engine.QueryInterface(msg.entity, IID_Foundation))
{
var cmpIdentity = Engine.QueryInterface(msg.entity, IID_Identity);
if (cmpIdentity)
{
var classes = cmpIdentity.GetClassesList();
for (var i in classes)
{
this.classCounts[classes[i]] -= 1;
if (this.classCounts[classes[i]] <= 0)
delete this.classCounts[classes[i]];
this.typeCountsByClass[classes[i]][template] -= 1;
if (this.typeCountsByClass[classes[i]][template] <= 0)
delete this.typeCountsByClass[classes[i]][template];
}
}
}
this.clearModificationCache(msg.entity);
}
};
// Marks a technology as researched. Note that this does not verify that the requirements are met.
TechnologyManager.prototype.ResearchTechnology = function (tech)
{
this.StoppedResearch(tech); // The tech is no longer being currently researched
var template = this.GetTechnologyTemplate(tech);
if (!template)
{
error("Tried to research invalid techonology: " + uneval(tech));
return;
}
var modifiedComponents = {};
this.researchedTechs[tech] = template;
// store the modifications in an easy to access structure
if (template.modifications)
{
var affects = [];
if (template.affects && template.affects.length > 0)
{
for (var i in template.affects)
{
// Put the list of classes into an array for convenient access
affects.push(template.affects[i].split(/\s+/));
}
}
else
{
affects.push([]);
}
// We add an item to this.modifications for every modification in the template.modifications array
for (var i in template.modifications)
{
var modification = template.modifications[i];
if (!this.modifications[modification.value])
this.modifications[modification.value] = [];
var modAffects = [];
if (modification.affects)
{
for (var j in modification.affects)
modAffects.push(modification.affects[j].split(/\s+/));
}
var mod = {"affects": affects.concat(modAffects)};
// copy the modification data into our new data structure
for (var j in modification)
if (j !== "value" && j !== "affects")
mod[j] = modification[j];
this.modifications[modification.value].push(mod);
var component = modification.value.split("/")[0];
if (!modifiedComponents[component])
modifiedComponents[component] = [];
modifiedComponents[component].push(modification.value);
this.modificationCache[modification.value] = {};
}
}
this.UpdateAutoResearch();
var cmpPlayer = Engine.QueryInterface(this.entity, IID_Player);
if (!cmpPlayer || cmpPlayer.GetPlayerID() === undefined)
return;
var playerID = cmpPlayer.GetPlayerID();
var cmpRangeManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager);
var ents = cmpRangeManager.GetEntitiesByPlayer(playerID);
for (var component in modifiedComponents)
{
Engine.PostMessage(SYSTEM_ENTITY, MT_TemplateModification, { "player": playerID, "component": component, "valueNames": modifiedComponents[component]});
Engine.BroadcastMessage(MT_ValueModification, { "entities": ents, "component": component, "valueNames": modifiedComponents[component]});
}
};
// Clears the cached data for an entity from the modifications cache
TechnologyManager.prototype.clearModificationCache = function(ent)
{
for (var valueName in this.modificationCache)
delete this.modificationCache[valueName][ent];
};
// Caching layer in front of ApplyModificationsWorker
// Note: be careful with the type of curValue, if it should be a numerical
// value and is derived from template data, you must convert the string
// from the template to a number using the + operator, before calling
// this function!
TechnologyManager.prototype.ApplyModifications = function(valueName, curValue, ent)
{
if (!this.modificationCache[valueName])
this.modificationCache[valueName] = {};
if (!this.modificationCache[valueName][ent] || this.modificationCache[valueName][ent].origValue != curValue)
{
this.modificationCache[valueName][ent] = {"origValue": curValue};
var cmpTemplateManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_TemplateManager);
var templateName = cmpTemplateManager.GetCurrentTemplateName(ent);
- // Ensure that preview entites have the same properties as the final building
- if (templateName.indexOf("preview|") != -1)
- templateName = templateName.slice(8);
+ // Ensure that preview or construction entites have the same properties as the final building
+ if (templateName.indexOf("preview|") > -1 || templateName.indexOf("construction|") > -1 )
+ templateName = templateName.slice(templateName.indexOf("|") + 1);
this.modificationCache[valueName][ent].newValue = GetTechModifiedProperty(this.modifications, cmpTemplateManager.GetTemplate(templateName), valueName, curValue);
}
return this.modificationCache[valueName][ent].newValue;
};
// Alternative version of ApplyModifications, applies to templates instead of entities
TechnologyManager.prototype.ApplyModificationsTemplate = function(valueName, curValue, template)
{
return GetTechModifiedProperty(this.modifications, template, valueName, curValue);
};
// Marks a technology as being queued for research
TechnologyManager.prototype.QueuedResearch = function (tech, researcher)
{
this.researchQueued[tech] = researcher;
};
// Marks a technology as actively being researched
TechnologyManager.prototype.StartedResearch = function (tech)
{
this.researchStarted[tech] = true;
};
// Marks a technology as not being currently researched
TechnologyManager.prototype.StoppedResearch = function (tech)
{
delete this.researchQueued[tech];
delete this.researchStarted[tech];
};
// Checks whether a technology is set to be researched
TechnologyManager.prototype.IsInProgress = function(tech)
{
if (this.researchQueued[tech])
return true;
else
return false;
};
// Get all techs that are currently being researched
TechnologyManager.prototype.GetTechsStarted = function()
{
return this.researchStarted;
};
// Gets the entity currently researching a technology
TechnologyManager.prototype.GetResearcher = function(tech)
{
if (this.researchQueued[tech])
return this.researchQueued[tech];
return undefined;
};
// Get helper data for tech modifications
TechnologyManager.prototype.GetTechModifications = function()
{
return this.modifications;
};
// called by GUIInterface for PlayerData. AI use.
TechnologyManager.prototype.GetQueuedResearch = function()
{
return this.researchQueued;
};
TechnologyManager.prototype.GetStartedResearch = function()
{
return this.researchStarted;
};
TechnologyManager.prototype.GetResearchedTechs = function()
{
return this.researchedTechs;
};
TechnologyManager.prototype.GetClassCounts = function()
{
return this.classCounts;
};
TechnologyManager.prototype.GetTypeCountsByClass = function()
{
return this.typeCountsByClass;
};
Engine.RegisterComponentType(IID_TechnologyManager, "TechnologyManager", TechnologyManager);
Index: ps/trunk/source/simulation2/components/CCmpVisualActor.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 14927)
+++ ps/trunk/source/simulation2/components/CCmpVisualActor.cpp (revision 14928)
@@ -1,834 +1,863 @@
/* Copyright (C) 2013 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 "ICmpOwnership.h"
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpSelectable.h"
#include "ICmpTemplateManager.h"
#include "ICmpTerrain.h"
#include "ICmpUnitMotion.h"
+#include "ICmpValueModificationManager.h"
#include "ICmpVision.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/Matrix3D.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "ps/GameSetup/Config.h"
#include "renderer/Scene.h"
#include "tools/atlas/GameInterface/GameLoop.h"
class CCmpVisualActor : public ICmpVisual
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Update_Final);
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+ componentManager.SubscribeToMessageType(MT_ValueModification);
componentManager.SubscribeGloballyToMessageType(MT_TerrainChanged);
}
DEFAULT_COMPONENT_ALLOCATOR(VisualActor)
private:
- std::wstring m_ActorName;
+ 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;
ICmpRangeManager::ELosVisibility m_Visibility; // only valid between Interpolate and RenderSubmit
// 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;
fixed m_ConstructionProgress;
bool m_VisibleInAtlasOnly;
bool m_IsActorOnly; // an in-world entity should not have this or it might not be rendered.
/// Whether the visual actor has been rendered at least once.
/// Necessary because the visibility update runs on simulation update,
/// which may not occur immediately if the game starts paused.
bool m_PreviouslyRendered;
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_PreviouslyRendered = false;
m_Unit = NULL;
m_Visibility = ICmpRangeManager::VIS_HIDDEN;
m_R = m_G = m_B = fixed::FromInt(1);
m_ConstructionPreview = paramNode.GetChild("ConstructionPreview").IsOk();
m_ConstructionProgress = fixed::Zero();
m_Seed = GetEntityId();
- if (paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk())
- m_ActorName = paramNode.GetChild("FoundationActor").ToString();
+ m_IsFoundationActor = paramNode.GetChild("Foundation").IsOk() && paramNode.GetChild("FoundationActor").IsOk();
+ if (m_IsFoundationActor)
+ m_BaseActorName = m_ActorName = paramNode.GetChild("FoundationActor").ToString();
else
- m_ActorName = paramNode.GetChild("Actor").ToString();
+ 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);
serialize.NumberFixed_Unbounded("constructionprogress", m_ConstructionProgress);
// 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("actor", m_ActorName, 0, 256);
+ 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, reload actor
- if (oldSeed != GetActorSeed())
+ // 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_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;
}
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;
+ }
}
}
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
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?
}
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 SetConstructionProgress(fixed progress)
{
m_ConstructionProgress = progress;
}
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);
void UpdateVisibility();
void Interpolate(float frameTime, float frameOffset);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling);
};
REGISTER_COMPONENT_TYPE(VisualActor)
// ------------------------------------------------------------------------------------------------------------------
void CCmpVisualActor::InitModel(const CParamNode& paramNode)
{
if (GetSimContext().HasUnitManager())
{
std::set selections;
- m_Unit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, GetActorSeed(), 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)
{
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())
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());
}
}
}
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;
std::set selections;
- CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(m_ActorName, GetActorSeed(), selections);
+ std::wstring actorName = m_ActorName;
+ if (actorName.find(L".xml") == std::wstring::npos)
+ actorName += L".xml";
+ CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(actorName, GetActorSeed(), selections);
if (!newUnit)
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);
}
void CCmpVisualActor::Update(fixed UNUSED(turnLength))
{
if (m_Unit == NULL)
return;
UpdateVisibility();
// 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"");
}
}
}
void CCmpVisualActor::UpdateVisibility()
{
ICmpRangeManager::ELosVisibility oldVisibility = m_Visibility;
CmpPtr cmpPosition(GetEntityHandle());
if (cmpPosition && cmpPosition->IsInWorld())
{
// The 'always visible' flag means we should always render the unit
// (regardless of whether the LOS system thinks it's visible)
CmpPtr cmpVision(GetEntityHandle());
if (cmpVision && cmpVision->GetAlwaysVisible())
m_Visibility = ICmpRangeManager::VIS_VISIBLE;
else
{
CmpPtr cmpRangeManager(GetSystemEntity());
// Uncomment the following lines to prevent the models from popping into existence
// near the LOS boundary. Is rather resource intensive.
//if (cmpVision->GetRetainInFog())
// m_Visibility = ICmpRangeManager::VIS_VISIBLE;
//else
m_Visibility = cmpRangeManager->GetLosVisibility(GetEntityHandle(),
GetSimContext().GetCurrentDisplayedPlayer());
}
}
else
m_Visibility = ICmpRangeManager::VIS_HIDDEN;
if (m_Visibility != oldVisibility)
{
// Change the visibility of the visual actor's selectable if it has one.
CmpPtr cmpSelectable(GetEntityHandle());
if (cmpSelectable)
cmpSelectable->SetVisibility(m_Visibility == ICmpRangeManager::VIS_HIDDEN ? false : true);
}
}
void CCmpVisualActor::Interpolate(float frameTime, float frameOffset)
{
if (m_Unit == NULL)
return;
// Disable rendering of the unit if it has no position
CmpPtr cmpPosition(GetEntityHandle());
if (!cmpPosition || !cmpPosition->IsInWorld())
return;
else if (!m_PreviouslyRendered)
{
UpdateVisibility();
m_PreviouslyRendered = true;
}
// Even if HIDDEN due to LOS, we need to set up the transforms
// so that projectiles will be launched from the right place
bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater;
CMatrix3D transform(cmpPosition->GetInterpolatedTransform(frameOffset, floating));
if (!m_ConstructionProgress.IsZero())
{
// We use selection boxes to calculate the model size, since the model could be offset
// TODO: this annoyingly shows decals, would be nice to hide them
CBoundingBoxOriented bounds = GetSelectionBox();
if (!bounds.IsEmpty())
{
float dy = 2.0f * bounds.m_HalfSizes.Y;
// If this is a floating unit, we want it to start all the way under the terrain,
// so find the difference between its current position and the terrain
CmpPtr cmpTerrain(GetSystemEntity());
if (floating && cmpTerrain)
{
CVector3D pos = transform.GetTranslation();
float ground = cmpTerrain->GetExactGroundLevel(pos.X, pos.Z);
dy += std::max(0.f, pos.Y - ground);
}
transform.Translate(0.0f, (m_ConstructionProgress.ToFloat() - 1.0f) * dy, 0.0f);
}
}
CModelAbstract& model = m_Unit->GetModel();
model.SetTransform(transform);
m_Unit->UpdateModel(frameTime);
// If not hidden, then we need to set up some extra state for rendering
if (m_Visibility != ICmpRangeManager::VIS_HIDDEN)
{
model.ValidatePosition();
model.SetShadingColor(CColor(m_R.ToFloat(), m_G.ToFloat(), m_B.ToFloat(), 1.0f));
}
}
void CCmpVisualActor::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling)
{
if (m_Unit == NULL)
return;
if (m_Visibility == ICmpRangeManager::VIS_HIDDEN)
return;
CModelAbstract& model = m_Unit->GetModel();
if (!g_AtlasGameLoop->running && !g_RenderActors && m_IsActorOnly)
return;
if (culling && !frustum.IsBoxVisible(CVector3D(0, 0, 0), model.GetWorldBoundsRec()))
return;
if (!g_AtlasGameLoop->running && m_VisibleInAtlasOnly)
return;
collector.SubmitRecursive(&model);
}
Index: ps/trunk/source/simulation2/components/ICmpValueModificationManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/ICmpValueModificationManager.cpp (revision 14927)
+++ ps/trunk/source/simulation2/components/ICmpValueModificationManager.cpp (revision 14928)
@@ -1,44 +1,49 @@
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "ICmpValueModificationManager.h"
#include "simulation2/system/InterfaceScripted.h"
#include "simulation2/scripting/ScriptComponent.h"
BEGIN_INTERFACE_WRAPPER(ValueModificationManager)
END_INTERFACE_WRAPPER(ValueModificationManager)
class CCmpValueModificationManagerScripted : public ICmpValueModificationManager
{
public:
DEFAULT_SCRIPT_WRAPPER(ValueModificationManagerScripted)
virtual fixed ApplyModifications(std::wstring valueName, fixed currentValue, entity_id_t entity)
{
return m_Script.Call("ApplyModifications", valueName, currentValue, entity);
}
virtual u32 ApplyModifications(std::wstring valueName, u32 currentValue, entity_id_t entity)
{
return m_Script.Call("ApplyModifications", valueName, currentValue, entity);
}
+
+ virtual std::wstring ApplyModifications(std::wstring valueName, std::wstring currentValue, entity_id_t entity)
+ {
+ return m_Script.Call("ApplyModifications", valueName, currentValue, entity);
+ }
};
REGISTER_COMPONENT_SCRIPT_WRAPPER(ValueModificationManagerScripted)
Index: ps/trunk/source/simulation2/components/ICmpValueModificationManager.h
===================================================================
--- ps/trunk/source/simulation2/components/ICmpValueModificationManager.h (revision 14927)
+++ ps/trunk/source/simulation2/components/ICmpValueModificationManager.h (revision 14928)
@@ -1,39 +1,40 @@
/* Copyright (C) 2012 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_ICMPVALUEMODIFICATIONMANAGER
#define INCLUDED_ICMPVALUEMODIFICATIONMANAGER
#include "simulation2/system/Interface.h"
#include "maths/Fixed.h"
/**
* value modification manager interface.
* (This interface only includes the functions needed by native code for accessing
* value modification data, the associated logic is handled in scripts)
*/
class ICmpValueModificationManager : public IComponent
{
public:
virtual fixed ApplyModifications(std::wstring valueName, fixed currentValue, entity_id_t entity) = 0;
virtual u32 ApplyModifications(std::wstring valueName, u32 currentValue, entity_id_t entity) = 0;
+ virtual std::wstring ApplyModifications(std::wstring valueName, std::wstring currentValue, entity_id_t entity) = 0;
DECLARE_INTERFACE_TYPE(ValueModificationManager)
};
#endif // INCLUDED_ICMPVALUEMODIFICATIONMANAGER