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