mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Fix formation reshuffling after entity rename
When entities in formations were renamed (e.g., during promotion), the formation would immediately recalculate all member positions, and queue movement orders causing visible shuffling. Changes: 1. Transfer existing offsets movement to the renamed entity to maintain current formation structure 2. Schedule offset recalculation for the next tick to allow proper reordering after all systems have updated This preserves formation integrity during renames while allowing eventual optimal position recalculation. Fixes #8656
This commit is contained in:
parent
6cdbdae87c
commit
99e3799883
6 changed files with 98 additions and 18 deletions
|
|
@ -389,7 +389,9 @@ Formation.prototype.RemoveMembers = function(ents, renamed = false)
|
|||
if (!ents.length)
|
||||
return;
|
||||
|
||||
this.offsets = undefined;
|
||||
if (!renamed)
|
||||
this.offsets = undefined;
|
||||
|
||||
this.members = this.members.filter(ent => !ents.includes(ent));
|
||||
|
||||
for (const ent of ents)
|
||||
|
|
@ -441,9 +443,10 @@ Formation.prototype.RemoveMembers = function(ents, renamed = false)
|
|||
*
|
||||
* @see ArrangeFormation - To update formation layout after adding members
|
||||
*/
|
||||
Formation.prototype.AddMembers = function(ents)
|
||||
Formation.prototype.AddMembers = function(ents, renamed = false)
|
||||
{
|
||||
this.offsets = undefined;
|
||||
if (!renamed)
|
||||
this.offsets = undefined;
|
||||
|
||||
for (const ent of this.formationMembersWithAura)
|
||||
{
|
||||
|
|
@ -1048,11 +1051,24 @@ Formation.prototype.OnGlobalEntityRenamed = function(msg)
|
|||
|
||||
// First remove the old member to be able to reuse its position.
|
||||
this.RemoveMembers([msg.entity], true);
|
||||
this.AddMembers([msg.newentity]);
|
||||
this.AddMembers([msg.newentity], true);
|
||||
this.memberPositions[msg.newentity] = this.memberPositions[msg.entity];
|
||||
delete this.memberPositions[msg.entity];
|
||||
|
||||
// Update Formation
|
||||
// to make sure added (renamed) members will move with the controller if applicable.
|
||||
if (this.resetOffsetsScheduled === undefined)
|
||||
{
|
||||
this.resetOffsetsScheduled = true;
|
||||
|
||||
// Schedule offset reset for the next tick so that it reorders if necessary.
|
||||
const cmpTimer = Engine.QueryInterface(SYSTEM_ENTITY, IID_Timer);
|
||||
cmpTimer.SetTimeout(this.entity, IID_Formation, "ResetOffsetsAndUpdate", 0, null);
|
||||
}
|
||||
};
|
||||
|
||||
Formation.prototype.ResetOffsetsAndUpdate = function()
|
||||
{
|
||||
this.resetOffsetsScheduled = undefined;
|
||||
this.offsets = undefined;
|
||||
this.UpdateFormation(false, false);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
|
|||
|
||||
const cmpUnitMotion = Engine.QueryInterface(oldEnt, IID_UnitMotion);
|
||||
const cmpNewUnitMotion = Engine.QueryInterface(newEnt, IID_UnitMotion);
|
||||
const cmpOldUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
|
||||
if (cmpUnitMotion && cmpNewUnitMotion)
|
||||
{
|
||||
const currentSpeed = cmpUnitMotion.GetCurrentSpeed();
|
||||
|
|
@ -69,6 +70,13 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
|
|||
|
||||
const acceleration = cmpUnitMotion.GetAcceleration();
|
||||
cmpNewUnitMotion.SetAcceleration(acceleration);
|
||||
if (cmpOldUnitAI)
|
||||
{
|
||||
const formationControllerID = cmpOldUnitAI.GetFormationController();
|
||||
const formationMemberOffsetPosition = cmpUnitMotion.GetFormationOffset();
|
||||
if (formationControllerID && formationMemberOffsetPosition && cmpUnitMotion.IsMovingAsFormation())
|
||||
cmpNewUnitMotion.MoveToFormationOffset(formationControllerID, formationMemberOffsetPosition.x, formationMemberOffsetPosition.y);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent spawning subunits on occupied positions.
|
||||
|
|
@ -172,16 +180,15 @@ function ChangeEntityTemplate(oldEnt, newTemplate)
|
|||
Engine.PostMessage(oldEnt, MT_EntityRenamed, { "entity": oldEnt, "newentity": newEnt });
|
||||
|
||||
// UnitAI generally needs other components to be properly initialised.
|
||||
const cmpUnitAI = Engine.QueryInterface(oldEnt, IID_UnitAI);
|
||||
const cmpNewUnitAI = Engine.QueryInterface(newEnt, IID_UnitAI);
|
||||
if (cmpUnitAI && cmpNewUnitAI)
|
||||
if (cmpOldUnitAI && cmpNewUnitAI)
|
||||
{
|
||||
const pos = cmpUnitAI.GetHeldPosition();
|
||||
const pos = cmpOldUnitAI.GetHeldPosition();
|
||||
if (pos)
|
||||
cmpNewUnitAI.SetHeldPosition(pos.x, pos.z);
|
||||
cmpNewUnitAI.SwitchToStance(cmpUnitAI.GetStanceName());
|
||||
cmpNewUnitAI.AddOrders(cmpUnitAI.GetOrders());
|
||||
const guarded = cmpUnitAI.IsGuardOf();
|
||||
cmpNewUnitAI.SwitchToStance(cmpOldUnitAI.GetStanceName());
|
||||
cmpNewUnitAI.AddOrders(cmpOldUnitAI.GetOrders());
|
||||
const guarded = cmpOldUnitAI.IsGuardOf();
|
||||
if (guarded)
|
||||
{
|
||||
const cmpGuarded = Engine.QueryInterface(guarded, IID_Guard);
|
||||
|
|
|
|||
|
|
@ -456,6 +456,11 @@ public:
|
|||
return m_MoveRequest.m_Type != MoveRequest::NONE;
|
||||
}
|
||||
|
||||
bool IsMovingAsFormation() const override
|
||||
{
|
||||
return IsFormationMember() && m_MoveRequest.m_Type == MoveRequest::OFFSET;
|
||||
}
|
||||
|
||||
fixed GetSpeedMultiplier() const override
|
||||
{
|
||||
return m_SpeedMultiplier;
|
||||
|
|
@ -588,6 +593,14 @@ public:
|
|||
m_FormationController = controller;
|
||||
}
|
||||
|
||||
std::optional<CFixedVector2D> GetFormationOffset() const override
|
||||
{
|
||||
if (m_MoveRequest.m_Type != MoveRequest::OFFSET)
|
||||
return std::nullopt;
|
||||
|
||||
return m_MoveRequest.m_Position;
|
||||
}
|
||||
|
||||
bool IsTargetRangeReachable(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) override;
|
||||
|
||||
void FaceTowardsPoint(entity_pos_t x, entity_pos_t z) override;
|
||||
|
|
@ -627,11 +640,6 @@ private:
|
|||
return m_FormationController != INVALID_ENTITY;
|
||||
}
|
||||
|
||||
bool IsMovingAsFormation() const
|
||||
{
|
||||
return IsFormationMember() && m_MoveRequest.m_Type == MoveRequest::OFFSET;
|
||||
}
|
||||
|
||||
bool IsFormationControllerMoving() const
|
||||
{
|
||||
CmpPtr<ICmpUnitMotion> cmpControllerMotion(GetSimContext(), m_FormationController);
|
||||
|
|
|
|||
|
|
@ -32,8 +32,10 @@ DEFINE_INTERFACE_METHOD("PossiblyAtDestination", ICmpUnitMotion, PossiblyAtDesti
|
|||
DEFINE_INTERFACE_METHOD("FaceTowardsPoint", ICmpUnitMotion, FaceTowardsPoint)
|
||||
DEFINE_INTERFACE_METHOD("StopMoving", ICmpUnitMotion, StopMoving)
|
||||
DEFINE_INTERFACE_METHOD("GetCurrentSpeed", ICmpUnitMotion, GetCurrentSpeed)
|
||||
DEFINE_INTERFACE_METHOD("GetFormationOffset", ICmpUnitMotion, GetFormationOffset)
|
||||
DEFINE_INTERFACE_METHOD("SetCurrentSpeed", ICmpUnitMotion, SetCurrentSpeed)
|
||||
DEFINE_INTERFACE_METHOD("IsMoveRequested", ICmpUnitMotion, IsMoveRequested)
|
||||
DEFINE_INTERFACE_METHOD("IsMovingAsFormation", ICmpUnitMotion, IsMovingAsFormation)
|
||||
DEFINE_INTERFACE_METHOD("GetSpeed", ICmpUnitMotion, GetSpeed)
|
||||
DEFINE_INTERFACE_METHOD("GetWalkSpeed", ICmpUnitMotion, GetWalkSpeed)
|
||||
DEFINE_INTERFACE_METHOD("GetRunMultiplier", ICmpUnitMotion, GetRunMultiplier)
|
||||
|
|
@ -104,11 +106,21 @@ public:
|
|||
m_Script.CallVoid("SetCurrentSpeed", speed);
|
||||
}
|
||||
|
||||
std::optional<CFixedVector2D> GetFormationOffset() const override
|
||||
{
|
||||
return m_Script.Call<std::optional<CFixedVector2D>>("GetFormationOffset");
|
||||
}
|
||||
|
||||
bool IsMoveRequested() const override
|
||||
{
|
||||
return m_Script.Call<bool>("IsMoveRequested");
|
||||
}
|
||||
|
||||
bool IsMovingAsFormation() const override
|
||||
{
|
||||
return m_Script.Call<bool>("IsMovingAsFormation");
|
||||
}
|
||||
|
||||
fixed GetSpeed() const override
|
||||
{
|
||||
return m_Script.Call<fixed>("GetSpeed");
|
||||
|
|
|
|||
|
|
@ -109,11 +109,23 @@ public:
|
|||
*/
|
||||
virtual void SetCurrentSpeed(const fixed& speed) = 0;
|
||||
|
||||
/**
|
||||
* Get the current formation offset if this unit is moving as a formation member.
|
||||
* @returns std::nullopt if the unit is not in formation movement mode,
|
||||
* otherwise returns the formation offset.
|
||||
*/
|
||||
virtual std::optional<CFixedVector2D> GetFormationOffset() const = 0;
|
||||
|
||||
/**
|
||||
* @returns true if the unit has a destination.
|
||||
*/
|
||||
virtual bool IsMoveRequested() const = 0;
|
||||
|
||||
/**
|
||||
* @returns true if the unit is moving orderly in it's Formation.
|
||||
*/
|
||||
virtual bool IsMovingAsFormation() const = 0;
|
||||
|
||||
/**
|
||||
* Get the unit template walk speed after modifications.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2025 Wildfire Games.
|
||||
/* Copyright (C) 2026 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
|
|
@ -48,6 +48,7 @@
|
|||
#include <js/experimental/TypedData.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
#define FAIL(msg) STMT(LOGERROR(msg); return false)
|
||||
#define FAIL_VOID(msg) STMT(ScriptException::Raise(rq, msg); return)
|
||||
|
|
@ -210,6 +211,30 @@ template<> void Script::ToJSVal<CFixedVector2D>(const ScriptRequest& rq, JS::Mu
|
|||
ret.setObject(*objVec);
|
||||
}
|
||||
|
||||
template<> bool Script::FromJSVal<std::optional<CFixedVector2D>>(const ScriptRequest& rq, JS::HandleValue v, std::optional<CFixedVector2D>& out)
|
||||
{
|
||||
if (v.isNullOrUndefined())
|
||||
{
|
||||
out = std::nullopt;
|
||||
return true;
|
||||
}
|
||||
|
||||
CFixedVector2D vec;
|
||||
if (!FromJSVal(rq, v, vec))
|
||||
return false;
|
||||
|
||||
out = vec;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<> void Script::ToJSVal<std::optional<CFixedVector2D>>(const ScriptRequest& rq, JS::MutableHandleValue ret, const std::optional<CFixedVector2D>& val)
|
||||
{
|
||||
if (!val.has_value())
|
||||
ret.setNull();
|
||||
else
|
||||
ToJSVal(rq, ret, val.value());
|
||||
}
|
||||
|
||||
template<> void Script::ToJSVal<Grid<u8> >(const ScriptRequest& rq, JS::MutableHandleValue ret, const Grid<u8>& val)
|
||||
{
|
||||
u32 length = (u32)(val.m_W * val.m_H);
|
||||
|
|
|
|||
Loading…
Reference in a new issue