diff --git a/binaries/data/mods/mod/art/actors/actor.rng b/binaries/data/mods/mod/art/actors/actor.rng index c4216e83ac..8a5d9dd486 100644 --- a/binaries/data/mods/mod/art/actors/actor.rng +++ b/binaries/data/mods/mod/art/actors/actor.rng @@ -214,6 +214,16 @@ + + + + upright + pitch + roll + pitch-roll + + + diff --git a/source/graphics/ObjectBase.cpp b/source/graphics/ObjectBase.cpp index 89650a6750..719b675a0f 100644 --- a/source/graphics/ObjectBase.cpp +++ b/source/graphics/ObjectBase.cpp @@ -60,6 +60,7 @@ CObjectBase::CObjectBase(CObjectManager& objectManager, CActorDef& actorDef, u8 : m_ObjectManager(objectManager), m_ActorDef(actorDef) { m_QualityLevel = qualityLevel; + m_Properties.m_AnchorType = ""; m_Properties.m_CastShadows = false; m_Properties.m_FloatOnWater = false; @@ -82,6 +83,7 @@ bool CObjectBase::Load(const CXeromyces& XeroFile, const XMBElement& root) // Define all the elements used in the XML file #define EL(x) int el_##x = XeroFile.GetElementID(#x) #define AT(x) int at_##x = XeroFile.GetAttributeID(#x) + EL(anchor); EL(castshadow); EL(float); EL(group); @@ -143,6 +145,8 @@ bool CObjectBase::Load(const CXeromyces& XeroFile, const XMBElement& root) return false; } } + else if (child_name == el_anchor) + m_Properties.m_AnchorType = child.GetText(); else if (child_name == el_castshadow) m_Properties.m_CastShadows = true; else if (child_name == el_float) diff --git a/source/graphics/ObjectBase.h b/source/graphics/ObjectBase.h index fecda0cfd0..e0a23d18ca 100644 --- a/source/graphics/ObjectBase.h +++ b/source/graphics/ObjectBase.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2021 Wildfire Games. +/* Copyright (C) 2025 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -159,6 +159,8 @@ public: struct { + // whether and how to adapt the rotation to the terrrain slope below + CStr m_AnchorType; // cast shadows from this object bool m_CastShadows; // float on top of water diff --git a/source/simulation2/components/CCmpPosition.cpp b/source/simulation2/components/CCmpPosition.cpp index 66643ea66a..baeb14b869 100644 --- a/source/simulation2/components/CCmpPosition.cpp +++ b/source/simulation2/components/CCmpPosition.cpp @@ -59,13 +59,16 @@ public: // Template state: - enum + enum class AnchorType { + UNDEFINED = -1, // Used for m_ActorAnchorType only since it's optional. UPRIGHT = 0, PITCH = 1, PITCH_ROLL = 2, ROLL = 3, - } m_AnchorType; + }; + + AnchorType m_AnchorType; bool m_Floating; entity_pos_t m_FloatDepth; @@ -94,6 +97,7 @@ public: std::set m_Turrets; // Not serialized: + AnchorType m_ActorAnchorType; float m_InterpolatedRotX, m_InterpolatedRotY, m_InterpolatedRotZ; float m_LastInterpolatedRotX, m_LastInterpolatedRotZ; bool m_ActorFloating; @@ -135,15 +139,8 @@ public: void Init(const CParamNode& paramNode) override { - const std::string& anchor = paramNode.GetChild("Anchor").ToString(); - if (anchor == "pitch") - m_AnchorType = PITCH; - else if (anchor == "pitch-roll") - m_AnchorType = PITCH_ROLL; - else if (anchor == "roll") - m_AnchorType = ROLL; - else - m_AnchorType = UPRIGHT; + m_AnchorType = ParseAnchorString(paramNode.GetChild("Anchor").ToString()); + m_ActorAnchorType = AnchorType::UNDEFINED; m_InWorld = false; @@ -199,19 +196,19 @@ public: const char* anchor = "???"; switch (m_AnchorType) { - case PITCH: + case AnchorType::PITCH: anchor = "pitch"; break; - case PITCH_ROLL: + case AnchorType::PITCH_ROLL: anchor = "pitch-roll"; break; - case ROLL: + case AnchorType::ROLL: anchor = "roll"; break; - case UPRIGHT: // upright is the default + case AnchorType::UPRIGHT: // upright is the default default: anchor = "upright"; break; @@ -493,6 +490,11 @@ public: AdvertiseInterpolatedPositionChanges(); } + void SetActorAnchor(const CStr& anchor) override + { + m_ActorAnchorType = ParseAnchorString(anchor); + } + void SetConstructionProgress(fixed progress) override { m_ConstructionProgress = progress; @@ -854,6 +856,17 @@ public: private: + AnchorType ParseAnchorString(const CStr& anchor) + { + if (anchor == "pitch") + return AnchorType::PITCH; + if (anchor == "roll") + return AnchorType::ROLL; + if (anchor == "pitch-roll") + return AnchorType::PITCH_ROLL; + return AnchorType::UPRIGHT; + } + /* * Must be called whenever m_RotY or m_InterpolatedRotY change, * to determine whether we need to call Interpolate to make the unit rotate. @@ -935,7 +948,8 @@ private: return; } - if (m_AnchorType == UPRIGHT || !m_RotZ.IsZero() || !m_RotX.IsZero()) + AnchorType anchor = m_ActorAnchorType == AnchorType::UNDEFINED ? m_AnchorType : m_ActorAnchorType; + if (anchor == AnchorType::UPRIGHT || !m_RotZ.IsZero() || !m_RotX.IsZero()) { // set the visual rotations to the ones fixed by the interface m_InterpolatedRotX = m_RotX.ToFloat(); @@ -961,10 +975,10 @@ private: normal.Z = projected.Y; // project and calculate the angles - if (m_AnchorType == PITCH || m_AnchorType == PITCH_ROLL) + if (anchor == AnchorType::PITCH || anchor == AnchorType::PITCH_ROLL) m_InterpolatedRotX = -atan2(normal.Z, normal.Y); - if (m_AnchorType == ROLL || m_AnchorType == PITCH_ROLL) + if (anchor == AnchorType::ROLL || anchor == AnchorType::PITCH_ROLL) m_InterpolatedRotZ = atan2(normal.X, normal.Y); } }; diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp index 1694ce79bf..8cb19c3206 100644 --- a/source/simulation2/components/CCmpVisualActor.cpp +++ b/source/simulation2/components/CCmpVisualActor.cpp @@ -464,7 +464,7 @@ public: if (!m_Unit || !m_Unit->GetAnimation() || !m_Unit->GetID()) return; - m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); + m_Unit->GetAnimation()->SetAnimationState(m_AnimName, m_AnimOnce, m_AnimSpeed.ToFloat(), m_AnimDesync.ToFloat(), m_SoundGroup.c_str()); } void SelectMovementAnimation(const std::string& name, fixed speed) override @@ -638,10 +638,15 @@ void CCmpVisualActor::InitModel() model.ToCModelDecal()->RemoveShadowsReceive(); } + CStr anchor = m_Unit->GetObject().m_Base->m_Properties.m_AnchorType; bool floating = m_Unit->GetObject().m_Base->m_Properties.m_FloatOnWater; CmpPtr cmpPosition(GetEntityHandle()); if (cmpPosition) + { + if (!anchor.empty()) + cmpPosition->SetActorAnchor(anchor); cmpPosition->SetActorFloating(floating); + } if (!m_ModelTag.valid()) { diff --git a/source/simulation2/components/ICmpPosition.h b/source/simulation2/components/ICmpPosition.h index 3ed1337a2b..ec7e8f5f19 100644 --- a/source/simulation2/components/ICmpPosition.h +++ b/source/simulation2/components/ICmpPosition.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020 Wildfire Games. +/* Copyright (C) 2025 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -23,6 +23,7 @@ #include "simulation2/helpers/Position.h" #include "maths/FixedVector3D.h" #include "maths/FixedVector2D.h" +#include "ps/CStr.h" #include @@ -158,6 +159,13 @@ public: */ virtual void SetActorFloating(bool flag) = 0; + + /** + * Set the entity's anchor type, in a non-network-synchronised visual-only way. + * (This is to support the 'anchor' flag in actor XMLs.) + */ + virtual void SetActorAnchor(const CStr& anchor) = 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. diff --git a/source/simulation2/components/tests/test_RangeManager.h b/source/simulation2/components/tests/test_RangeManager.h index 075e656d0e..7001102673 100644 --- a/source/simulation2/components/tests/test_RangeManager.h +++ b/source/simulation2/components/tests/test_RangeManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2024 Wildfire Games. +/* Copyright (C) 2025 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -60,6 +60,7 @@ public: bool CanFloat() const override { return false; } void SetFloating(bool UNUSED(flag)) override { } void SetActorFloating(bool UNUSED(flag)) override { } + void SetActorAnchor(const CStr& UNUSED(anchor)) override { } void SetConstructionProgress(fixed UNUSED(progress)) override { } CFixedVector3D GetPosition() const override { return m_Pos; } CFixedVector2D GetPosition2D() const override { return CFixedVector2D(m_Pos.X, m_Pos.Z); } diff --git a/source/simulation2/components/tests/test_TerritoryManager.h b/source/simulation2/components/tests/test_TerritoryManager.h index 5c85ded49b..d50b14d42c 100644 --- a/source/simulation2/components/tests/test_TerritoryManager.h +++ b/source/simulation2/components/tests/test_TerritoryManager.h @@ -123,6 +123,7 @@ public: bool CanFloat() const override { return false; } void SetFloating(bool) override {} void SetActorFloating(bool) override {} + void SetActorAnchor(const CStr&) override {} void SetConstructionProgress(fixed) override {} CFixedVector3D GetPosition() const override { return m_Pos; } CFixedVector2D GetPosition2D() const override { return CFixedVector2D(m_Pos.X, m_Pos.Z); }