Allow actors to override the entity's anchor type

Optionally let actor templates define their own anchor types.
These override the default value defined in the template.
Useful for actors without their own entity template.

Reviewed-By: Itms
Reviewed-On: https://gitea.wildfiregames.com/0ad/0ad/pulls/7542
This commit is contained in:
Vantha 2025-01-23 21:47:26 +01:00 committed by Nicolas Auvray
parent 0ce889ca6d
commit eb2f59eeaa
8 changed files with 67 additions and 22 deletions

View file

@ -214,6 +214,16 @@
<empty/>
</element>
</optional>
<optional>
<element name="anchor">
<choice>
<value>upright</value>
<value>pitch</value>
<value>roll</value>
<value>pitch-roll</value>
</choice>
</element>
</optional>
<optional>
<element name="material">
<ref name="qualitySettings"/>

View file

@ -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)

View file

@ -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

View file

@ -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<entity_id_t> 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);
}
};

View file

@ -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<ICmpPosition> cmpPosition(GetEntityHandle());
if (cmpPosition)
{
if (!anchor.empty())
cmpPosition->SetActorAnchor(anchor);
cmpPosition->SetActorFloating(floating);
}
if (!m_ModelTag.valid())
{

View file

@ -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 <set>
@ -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.

View file

@ -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); }

View file

@ -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); }