From 31be9cd0de82d34d28bb87f8254919881a0cfa7f Mon Sep 17 00:00:00 2001 From: historic_bruno Date: Sun, 3 Feb 2013 02:08:20 +0000 Subject: [PATCH] Adds building previews that rise during construction, fixes #1174, refs #21. Adds scaffold support for foundations, includes two examples for 3x3 and 4x4, fixes #1581. Extends CmpVisualActor and CUnit to support random variant seeds. Fixes bug in actor hotloading. Fixes serialization failure caused by destroying entities in OnDestroy handlers. This was SVN commit r13143. --- .../structures/construction/scaf_3x3.xml | 12 + .../structures/construction/scaf_4x4.xml | 12 + .../public/art/actors/structures/fndn_3x3.xml | 9 +- .../public/art/actors/structures/fndn_4x4.xml | 11 +- .../mods/public/art/meshes/props/scaf_3x3.dae | 3 + .../mods/public/art/meshes/props/scaf_4x4.dae | 3 + .../simulation/components/Foundation.js | 50 +++- .../templates/template_structure.xml | 1 + .../template_structure_resource_field.xml | 1 + source/graphics/Unit.cpp | 10 +- source/graphics/Unit.h | 7 +- .../components/CCmpTemplateManager.cpp | 36 ++- .../components/CCmpVisualActor.cpp | 258 ++++++++++++------ source/simulation2/components/ICmpVisual.cpp | 6 +- source/simulation2/components/ICmpVisual.h | 23 +- .../simulation2/system/ComponentManager.cpp | 51 ++-- 16 files changed, 366 insertions(+), 127 deletions(-) create mode 100644 binaries/data/mods/public/art/actors/props/structures/construction/scaf_3x3.xml create mode 100644 binaries/data/mods/public/art/actors/props/structures/construction/scaf_4x4.xml create mode 100644 binaries/data/mods/public/art/meshes/props/scaf_3x3.dae create mode 100644 binaries/data/mods/public/art/meshes/props/scaf_4x4.dae diff --git a/binaries/data/mods/public/art/actors/props/structures/construction/scaf_3x3.xml b/binaries/data/mods/public/art/actors/props/structures/construction/scaf_3x3.xml new file mode 100644 index 0000000000..ef6f471055 --- /dev/null +++ b/binaries/data/mods/public/art/actors/props/structures/construction/scaf_3x3.xml @@ -0,0 +1,12 @@ + + + + + + props/scaf_3x3.dae + + + + + + diff --git a/binaries/data/mods/public/art/actors/props/structures/construction/scaf_4x4.xml b/binaries/data/mods/public/art/actors/props/structures/construction/scaf_4x4.xml new file mode 100644 index 0000000000..89c56f33ab --- /dev/null +++ b/binaries/data/mods/public/art/actors/props/structures/construction/scaf_4x4.xml @@ -0,0 +1,12 @@ + + + + + + props/scaf_4x4.dae + + + + + + diff --git a/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml b/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml index 0bce90ca1a..a83f31b3c3 100644 --- a/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml +++ b/binaries/data/mods/public/art/actors/structures/fndn_3x3.xml @@ -13,12 +13,19 @@ - + + + + + + + + diff --git a/binaries/data/mods/public/art/actors/structures/fndn_4x4.xml b/binaries/data/mods/public/art/actors/structures/fndn_4x4.xml index 7c91d58dbb..f4614b5ef5 100644 --- a/binaries/data/mods/public/art/actors/structures/fndn_4x4.xml +++ b/binaries/data/mods/public/art/actors/structures/fndn_4x4.xml @@ -4,14 +4,21 @@ structural/found_4x4_a.dae + + + structural/found_4x4_b.dae + + + + - - structural/found_4x4_b.dae + + diff --git a/binaries/data/mods/public/art/meshes/props/scaf_3x3.dae b/binaries/data/mods/public/art/meshes/props/scaf_3x3.dae new file mode 100644 index 0000000000..c775d68a93 --- /dev/null +++ b/binaries/data/mods/public/art/meshes/props/scaf_3x3.dae @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:008ebe79930d2d2e924252dcbecd733b1eade1cbf1ac8faa2ef67ab07d28ee13 +size 96176 diff --git a/binaries/data/mods/public/art/meshes/props/scaf_4x4.dae b/binaries/data/mods/public/art/meshes/props/scaf_4x4.dae new file mode 100644 index 0000000000..0a41e7a9a3 --- /dev/null +++ b/binaries/data/mods/public/art/meshes/props/scaf_4x4.dae @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c830834928a212d8ba334f33db982b47177bc6eebd71da8d70b519e9034a0972 +size 452509 diff --git a/binaries/data/mods/public/simulation/components/Foundation.js b/binaries/data/mods/public/simulation/components/Foundation.js index 75d91e61a4..5012ca142e 100644 --- a/binaries/data/mods/public/simulation/components/Foundation.js +++ b/binaries/data/mods/public/simulation/components/Foundation.js @@ -22,6 +22,8 @@ Foundation.prototype.Init = function() this.recentBuilders = []; // builder entities since the last timeout this.numRecentBuilders = 0; // number of builder entities as of the last timeout this.buildMultiplier = 1; // Multiplier for the amount of work builders do. + + this.previewEntity = INVALID_ENTITY; }; Foundation.prototype.UpdateTimeout = function() @@ -69,6 +71,12 @@ Foundation.prototype.OnDestroy = function() if (!this.initialised) // this happens if the foundation was destroyed because the player had insufficient resources return; + if (this.previewEntity != INVALID_ENTITY) + { + Engine.DestroyEntity(this.previewEntity); + this.previewEntity = INVALID_ENTITY; + } + if (this.buildProgress == 1.0) return; @@ -175,6 +183,36 @@ Foundation.prototype.Build = function(builderEnt, work) cmpObstruction.SetDisableBlockMovementPathfinding(false, false, -1); } + // Switch foundation to scaffold variant + var cmpFoundationVisual = Engine.QueryInterface(this.entity, IID_Visual); + if (cmpFoundationVisual) + cmpFoundationVisual.SelectAnimation("scaffold", false, 1.0, ""); + + // Create preview entity and copy various parameters from the foundation + if (cmpFoundationVisual && cmpFoundationVisual.HasConstructionPreview()) + { + this.previewEntity = Engine.AddEntity("construction|"+this.finalTemplateName); + var cmpFoundationOwnership = Engine.QueryInterface(this.entity, IID_Ownership); + var cmpPreviewOwnership = Engine.QueryInterface(this.previewEntity, IID_Ownership); + cmpPreviewOwnership.SetOwner(cmpFoundationOwnership.GetOwner()); + + // Initially hide the preview underground + var cmpPreviewVisual = Engine.QueryInterface(this.previewEntity, IID_Visual); + if (cmpPreviewVisual) + { + cmpPreviewVisual.SetConstructionProgress(0.0); + cmpPreviewVisual.SetActorSeed(cmpFoundationVisual.GetActorSeed()); + } + + var cmpFoundationPosition = Engine.QueryInterface(this.entity, IID_Position); + var pos = cmpFoundationPosition.GetPosition(); + var rot = cmpFoundationPosition.GetRotation(); + var cmpPreviewPosition = Engine.QueryInterface(this.previewEntity, IID_Position); + cmpPreviewPosition.SetYRotation(rot.y); + cmpPreviewPosition.SetXZRotation(rot.x, rot.z); + cmpPreviewPosition.JumpTo(pos.x, pos.z); + } + this.committed = true; } @@ -188,9 +226,14 @@ Foundation.prototype.Build = function(builderEnt, work) this.buildProgress += amount * this.buildMultiplier; if (this.buildProgress > 1.0) this.buildProgress = 1.0; - + Engine.PostMessage(this.entity, MT_FoundationProgressChanged, { "to": this.GetBuildPercentage() }); + // Gradually reveal the final building preview + var cmpPreviewVisual = Engine.QueryInterface(this.previewEntity, IID_Visual); + if (cmpPreviewVisual) + cmpPreviewVisual.SetConstructionProgress(this.buildProgress); + // Add an appropriate proportion of hitpoints var cmpHealth = Engine.QueryInterface(this.entity, IID_Health); var maxHealth = cmpHealth.GetMaxHitpoints(); @@ -211,6 +254,11 @@ Foundation.prototype.Build = function(builderEnt, work) // Copy various parameters from the foundation + var cmpVisual = Engine.QueryInterface(this.entity, IID_Visual); + var cmpBuildingVisual = Engine.QueryInterface(building, IID_Visual) + if (cmpVisual && cmpBuildingVisual) + cmpBuildingVisual.SetActorSeed(cmpVisual.GetActorSeed()); + var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); var cmpBuildingPosition = Engine.QueryInterface(building, IID_Position); var pos = cmpPosition.GetPosition(); diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml index 6b7bee3d63..bfe0e5ec91 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure.xml @@ -101,6 +101,7 @@ false + false true diff --git a/binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml b/binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml index 8d7c8be381..f2e183d65e 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure_resource_field.xml @@ -67,6 +67,7 @@ structures/plot_field_new.xml + structures/plot_field_found.xml diff --git a/source/graphics/Unit.cpp b/source/graphics/Unit.cpp index 3d4316d918..bd5501927c 100644 --- a/source/graphics/Unit.cpp +++ b/source/graphics/Unit.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* 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 @@ -27,10 +27,10 @@ #include "UnitAnimation.h" CUnit::CUnit(CObjectEntry* object, CObjectManager& objectManager, - const std::set& actorSelections) + const std::set& actorSelections, uint32_t seed) : m_Object(object), m_Model(object->m_Model->Clone()), m_ID(INVALID_ENTITY), m_ActorSelections(actorSelections), - m_ObjectManager(objectManager) + m_ObjectManager(objectManager), m_Seed(seed) { if (m_Model->ToCModel()) m_Animation = new CUnitAnimation(m_ID, m_Model->ToCModel(), m_Object); @@ -61,7 +61,7 @@ CUnit* CUnit::Create(const CStrW& actorName, uint32_t seed, const std::set if (! obj) return NULL; - return new CUnit(obj, objectManager, actorSelections); + return new CUnit(obj, objectManager, actorSelections, seed); } void CUnit::UpdateModel(float frameTime) @@ -111,7 +111,7 @@ void CUnit::ReloadObject() // see http://trac.wildfiregames.com/ticket/979 // Use the entity ID as randomization seed (same as when the unit was first created) - std::set remainingSelections = m_Object->m_Base->CalculateRandomRemainingSelections(m_ID, selections); + std::set remainingSelections = m_Object->m_Base->CalculateRandomRemainingSelections(m_Seed, selections); if (remainingSelections.size() > 0) selections.push_back(remainingSelections); diff --git a/source/graphics/Unit.h b/source/graphics/Unit.h index 0b7a4fcb13..32e67d48c9 100644 --- a/source/graphics/Unit.h +++ b/source/graphics/Unit.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* 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 @@ -38,7 +38,7 @@ class CUnit private: // Private constructor. Needs complete list of selections for the variation. CUnit(CObjectEntry* object, CObjectManager& objectManager, - const std::set& actorSelections); + const std::set& actorSelections, uint32_t seed); public: // Attempt to create a unit with the given actor, with a set of @@ -89,6 +89,9 @@ private: // permanent way of referencing them. entity_id_t m_ID; + // seed used when creating unit + uint32_t m_Seed; + // actor-level selections for this unit std::set m_ActorSelections; // entity-level selections for this unit diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp index 60b087b8d6..65098cec46 100644 --- a/source/simulation2/components/CCmpTemplateManager.cpp +++ b/source/simulation2/components/CCmpTemplateManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* 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 @@ -175,6 +175,10 @@ private: // (position, actor, armour, health, etc) into a new entity template void CopyFoundationSubset(CParamNode& out, const CParamNode& in); + // Copy the components of an entity necessary for a non-foundation construction entity + // into a new entity template + void CopyConstructionSubset(CParamNode& out, const CParamNode& in); + // Copy the components of an entity necessary for a gatherable resource // into a new entity template void CopyResourceSubset(CParamNode& out, const CParamNode& in); @@ -328,6 +332,21 @@ bool CCmpTemplateManager::LoadTemplateFile(const std::string& templateName, int 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) { @@ -573,6 +592,21 @@ void CCmpTemplateManager::CopyFoundationSubset(CParamNode& out, const CParamNode CParamNode::LoadXMLString(out, "0"); } +void CCmpTemplateManager::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 CCmpTemplateManager::CopyResourceSubset(CParamNode& out, const CParamNode& in) { // Currently used for animals which die and leave a gatherable corpse. diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index 02db4fbb40..376ddbbee2 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -20,14 +20,17 @@ #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 "ICmpVision.h" -#include "simulation2/MessageTypes.h" -#include "simulation2/components/ICmpFootprint.h" -#include "simulation2/components/ICmpSelectable.h" #include "graphics/Frustum.h" #include "graphics/Model.h" @@ -73,6 +76,11 @@ public: 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; + static std::string GetSchema() { return @@ -93,7 +101,12 @@ public: "" "" "" - "" + "" + "" + "" + "" + "" + "" "" "" "" @@ -141,50 +154,19 @@ public: m_PreviouslyRendered = false; m_Unit = NULL; m_Visibility = ICmpRangeManager::VIS_HIDDEN; - m_R = m_G = m_B = fixed::FromInt(1); - // TODO: we should do some fancy animation of under-construction buildings rising from the ground, - // but for now we'll just use the foundation actor and ignore the normal one + 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(); else m_ActorName = paramNode.GetChild("Actor").ToString(); - if (GetSimContext().HasUnitManager()) - { - std::set selections; - m_Unit = GetSimContext().GetUnitManager().CreateUnit(m_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(GetSimContext(), GetEntityId()); - if (cmpVision && cmpVision->GetAlwaysVisible()) - modelFlags |= MODELFLAG_IGNORE_LOS; - - model.ToCModel()->AddFlagsRec(modelFlags); - } - - // 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(model, paramNode); - - m_Unit->SetID(GetEntityId()); - } - } + InitModel(paramNode); // We need to select animation even if graphics are disabled, as this modifies serialized state SelectAnimation("idle", false, fixed::FromInt(1), L""); @@ -202,13 +184,6 @@ public: template void SerializeCommon(S& serialize) { - // TODO: store random variation. This ought to be synchronised across saved games - // and networks, so everyone sees the same thing. Saving the list of selection strings - // would be awfully inefficient, so actors should be changed to (by default) represent - // variations with a 16-bit RNG seed (selected randomly when creating new units, or - // when someone hits the "randomise" button in the map editor), only overridden with - // a list of strings if it really needs to be a specific variation. - serialize.NumberFixed_Unbounded("r", m_R); serialize.NumberFixed_Unbounded("g", m_G); serialize.NumberFixed_Unbounded("b", m_B); @@ -221,6 +196,11 @@ public: 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.NumberFixed_Unbounded("constructionprogress", m_ConstructionProgress); + // TODO: store actor variables? } @@ -240,8 +220,14 @@ public: { Init(paramNode); + u32 oldSeed = GetActorSeed(); + SerializeCommon(deserialize); + // If we serialized a different seed, reload actor + if (oldSeed != GetActorSeed()) + ReloadActor(); + fixed repeattime = m_AnimSyncRepeatTime; // save because SelectAnimation overwrites it if (m_AnimRunThreshold.IsZero()) @@ -444,6 +430,30 @@ public: } } + 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) @@ -452,36 +462,7 @@ public: if (name != m_ActorName) return; - std::set selections; - CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(m_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); - m_Unit = newUnit; - - m_Unit->SetID(GetEntityId()); - - 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); - - // TODO: should copy/reset silhouette flags + ReloadActor(); } private: @@ -490,17 +471,13 @@ private: /// which may not occur immediately if the game starts paused. bool m_PreviouslyRendered; - int32_t GetActorSeed() - { - return GetEntityId(); - } + /// 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. - /// The @p model argument is technically not really necessary since naturally this method is intended to initialize this - /// visual actor's model (I wouldn't know which other one you'd pass), but it's included here to enforce that the - /// component's model must have been created before using this method (i.e. to prevent accidentally calls to this method - /// before the model was constructed). - void InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode); + void InitSelectionShapeDescriptor(const CParamNode& paramNode); + + void ReloadActor(); void Update(fixed turnLength); void UpdateVisibility(); @@ -512,7 +489,45 @@ REGISTER_COMPONENT_TYPE(VisualActor) // ------------------------------------------------------------------------------------------------------------------ -void CCmpVisualActor::InitSelectionShapeDescriptor(CModelAbstract& model, const CParamNode& paramNode) +void CCmpVisualActor::InitModel(const CParamNode& paramNode) +{ + if (GetSimContext().HasUnitManager()) + { + std::set selections; + m_Unit = GetSimContext().GetUnitManager().CreateUnit(m_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(GetSimContext(), GetEntityId()); + if (cmpVision && cmpVision->GetAlwaysVisible()) + modelFlags |= MODELFLAG_IGNORE_LOS; + + model.ToCModel()->AddFlagsRec(modelFlags); + } + + // 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; @@ -578,8 +593,49 @@ void CCmpVisualActor::InitSelectionShapeDescriptor(CModelAbstract& model, const } } + ENSURE(m_Unit); // the model is now responsible for cleaning up the descriptor - model.SetCustomSelectionShape(shapeDescriptor); + m_Unit->GetModel().SetCustomSelectionShape(shapeDescriptor); +} + +void CCmpVisualActor::ReloadActor() +{ + if (!m_Unit) + return; + + std::set selections; + CUnit* newUnit = GetSimContext().GetUnitManager().CreateUnit(m_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(GetSimContext(), SYSTEM_ENTITY); + 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)) @@ -685,6 +741,30 @@ void CCmpVisualActor::Interpolate(float frameTime, float frameOffset) 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(GetSimContext(), SYSTEM_ENTITY); + 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); diff --git a/source/simulation2/components/ICmpVisual.cpp b/source/simulation2/components/ICmpVisual.cpp index bc1e1c74f9..7a4d52e713 100644 --- a/source/simulation2/components/ICmpVisual.cpp +++ b/source/simulation2/components/ICmpVisual.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* 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 @@ -30,4 +30,8 @@ DEFINE_INTERFACE_METHOD_1("SetAnimationSyncRepeat", void, ICmpVisual, SetAnimati DEFINE_INTERFACE_METHOD_1("SetAnimationSyncOffset", void, ICmpVisual, SetAnimationSyncOffset, fixed) DEFINE_INTERFACE_METHOD_4("SetShadingColour", void, ICmpVisual, SetShadingColour, fixed, fixed, fixed, fixed) DEFINE_INTERFACE_METHOD_2("SetVariable", void, ICmpVisual, SetVariable, std::string, float) +DEFINE_INTERFACE_METHOD_0("GetActorSeed", u32, ICmpVisual, GetActorSeed) +DEFINE_INTERFACE_METHOD_1("SetActorSeed", void, ICmpVisual, SetActorSeed, u32) +DEFINE_INTERFACE_METHOD_0("HasConstructionPreview", bool, ICmpVisual, HasConstructionPreview) +DEFINE_INTERFACE_METHOD_1("SetConstructionProgress", void, ICmpVisual, SetConstructionProgress, fixed) END_INTERFACE_WRAPPER(Visual) diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h index 134b58a82e..fb945517e4 100644 --- a/source/simulation2/components/ICmpVisual.h +++ b/source/simulation2/components/ICmpVisual.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* 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 @@ -146,6 +146,27 @@ public: */ virtual void SetVariable(std::string name, float value) = 0; + /** + * Get actor seed used for random variations + */ + virtual u32 GetActorSeed() = 0; + + /** + * Set actor seed for random variations and reload model + */ + virtual void SetActorSeed(u32 seed) = 0; + + /** + * Returns true if this entity should have a construction preview + */ + virtual bool HasConstructionPreview() = 0; + + /** + * Set construction progress of the model, this affects the rendered position of the model. + * 0.0 will be fully underground, 1.0 will be fully visible, 0.5 will be half underground. + */ + virtual void SetConstructionProgress(fixed progress) = 0; + /** * Called when an actor file has been modified and reloaded dynamically. * If this component uses the named actor file, it should regenerate its actor diff --git a/source/simulation2/system/ComponentManager.cpp b/source/simulation2/system/ComponentManager.cpp index 427e09b8fd..2105d370f5 100644 --- a/source/simulation2/system/ComponentManager.cpp +++ b/source/simulation2/system/ComponentManager.cpp @@ -698,36 +698,39 @@ void CComponentManager::DestroyComponentsSoon(entity_id_t ent) void CComponentManager::FlushDestroyedComponents() { - // Make a copy of the destruction queue, so that the iterators won't be invalidated if the - // CMessageDestroy handlers try to destroy more entities themselves - std::vector queue; - queue.swap(m_DestructionQueue); - - for (std::vector::iterator it = queue.begin(); it != queue.end(); ++it) + while(!m_DestructionQueue.empty()) { - entity_id_t ent = *it; + // Make a copy of the destruction queue, so that the iterators won't be invalidated if the + // CMessageDestroy handlers try to destroy more entities themselves + std::vector queue; + queue.swap(m_DestructionQueue); - CMessageDestroy msg(ent); - PostMessage(ent, msg); - - // Destroy the components, and remove from m_ComponentsByTypeId: - std::map >::iterator iit = m_ComponentsByTypeId.begin(); - for (; iit != m_ComponentsByTypeId.end(); ++iit) + for (std::vector::iterator it = queue.begin(); it != queue.end(); ++it) { - std::map::iterator eit = iit->second.find(ent); - if (eit != iit->second.end()) + entity_id_t ent = *it; + + CMessageDestroy msg(ent); + PostMessage(ent, msg); + + // Destroy the components, and remove from m_ComponentsByTypeId: + std::map >::iterator iit = m_ComponentsByTypeId.begin(); + for (; iit != m_ComponentsByTypeId.end(); ++iit) { - eit->second->Deinit(); - m_ComponentTypesById[iit->first].dealloc(eit->second); - iit->second.erase(ent); + std::map::iterator eit = iit->second.find(ent); + if (eit != iit->second.end()) + { + eit->second->Deinit(); + m_ComponentTypesById[iit->first].dealloc(eit->second); + iit->second.erase(ent); + } } - } - // Remove from m_ComponentsByInterface - std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); - for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) - { - ifcit->erase(ent); + // Remove from m_ComponentsByInterface + std::vector >::iterator ifcit = m_ComponentsByInterface.begin(); + for (; ifcit != m_ComponentsByInterface.end(); ++ifcit) + { + ifcit->erase(ent); + } } } }