[Gameplay] Implement a rough comparison function to sort entities within a template-determined percent error:https://wildfiregames.com/forum/topic/118677-less-precise-sorting-in-range-manager/#comment-575838
Some checks failed
checkrefs / checkrefs (push) Has been cancelled
pre-commit / build (push) Has been cancelled

This commit is contained in:
real_tabasco_sauce 2024-09-07 14:10:22 -07:00 committed by real_tabasco_sauce
parent 668ae8a20e
commit 4dec13d232
8 changed files with 66 additions and 27 deletions

View File

@ -42,6 +42,11 @@ UnitAI.prototype.Schema =
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<element name='RangeError'>" +
"<data type='nonNegativeInteger'/>" +
"</element>" +
"</optional>" +
"<optional>" +
"<interleave>" +
"<element name='RoamDistance'>" +
@ -3462,6 +3467,7 @@ UnitAI.prototype.Init = function()
this.formationAnimationVariant = undefined;
this.cheeringTime = +(this.template.CheeringTime || 0);
this.rangeError = +(this.template.RangeError || 0);
this.SetStance(this.template.DefaultStance);
};
@ -3776,7 +3782,7 @@ UnitAI.prototype.SetupLOSRangeQuery = function(enable = true)
// Do not compensate for entity sizes: LOS doesn't, and UnitAI relies on that.
this.losRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity,
range.min, range.max, players, IID_Identity,
cmpRangeManager.GetEntityFlagMask("normal"), false);
cmpRangeManager.GetEntityFlagMask("normal"), false, this.rangeError);
if (enable)
cmpRangeManager.EnableActiveQuery(this.losRangeQuery);
@ -3841,7 +3847,7 @@ UnitAI.prototype.SetupAttackRangeQuery = function(enable = true)
// Do not compensate for entity sizes: LOS doesn't, and UnitAI relies on that.
this.losAttackRangeQuery = cmpRangeManager.CreateActiveQuery(this.entity,
range.min, range.max, players, IID_Resistance,
cmpRangeManager.GetEntityFlagMask("normal"), false);
cmpRangeManager.GetEntityFlagMask("normal"), false, this.rangeError);
if (enable)
cmpRangeManager.EnableActiveQuery(this.losAttackRangeQuery);

View File

@ -109,6 +109,7 @@
<CanPatrol>true</CanPatrol>
<PatrolWaitTime>1</PatrolWaitTime>
<CheeringTime>2800</CheeringTime>
<RangeError>10</RangeError>
<Formations datatype="tokens">
special/formations/null
special/formations/box

View File

@ -42,6 +42,7 @@
</Sound>
<Turretable/>
<UnitAI>
<RangeError>15</RangeError>
<Formations datatype="tokens">
special/formations/skirmish
</Formations>

View File

@ -45,6 +45,9 @@
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
</SoundGroups>
</Sound>
<UnitAI>
<RangeError>20</RangeError>
</UnitAI>
<UnitMotion>
<WalkSpeed op="add">0.6</WalkSpeed>
<Acceleration op="add">1.2</Acceleration>

View File

@ -67,6 +67,7 @@
</SoundGroups>
</Sound>
<UnitAI>
<RangeError>3</RangeError>
<DefaultStance>standground</DefaultStance>
</UnitAI>
<UnitMotion>

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2020 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -169,6 +169,29 @@ public:
return 0;
}
/**
* Returns -1, 0, +1 depending on whether length estimate is less/equal/greater
* than the argument's length.
* Uses a percent error parameter to compare lengths with that percent error.
*/
int CompareLengthRough(const CFixedVector2D& other, u8 rangeError) const
{
u64 d2 = SQUARE_U64_FIXED(X) + SQUARE_U64_FIXED(Y);
u64 od2 = SQUARE_U64_FIXED(other.X) + SQUARE_U64_FIXED(other.Y);
//overflow risk with long ranges (designed for unit ranges)
CheckMultiplicationOverflow(u64, d2, 100+rangeError, "Overflow in CFixedVector2D::CompareLengthRough()","Underflow in CFixedVector2D::CompareLengthRough()")
d2 = d2 * (100-rangeError + d2 % (1+(rangeError*2)));
CheckMultiplicationOverflow(u64, od2, 100, "Overflow in CFixedVector2D::CompareLengthRough()", "Underflow in CFixedVector2D::CompareLengthRough()")
od2 = od2 * 100;
if (d2 < od2)
return -1;
if (d2 > od2)
return +1;
return 0;
}
bool IsZero() const
{
return X.IsZero() && Y.IsZero();

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -162,6 +162,7 @@ struct Query
u32 ownersMask;
i32 interface;
u8 flagsMask;
u8 rangeError;
bool enabled;
bool parabolic;
bool accountForSize; // If true, the query accounts for unit sizes, otherwise it treats all entities as points.
@ -255,8 +256,8 @@ static_assert(sizeof(EntityData) == 24);
class EntityDistanceOrdering
{
public:
EntityDistanceOrdering(const EntityMap<EntityData>& entities, const CFixedVector2D& source) :
m_EntityData(entities), m_Source(source)
EntityDistanceOrdering(const EntityMap<EntityData>& entities, const CFixedVector2D& source, u8 rangeError = 0) :
m_EntityData(entities), m_Source(source), m_RangeError(rangeError)
{
}
@ -268,11 +269,12 @@ public:
const EntityData& db = m_EntityData.find(b)->second;
CFixedVector2D vecA = CFixedVector2D(da.x, da.z) - m_Source;
CFixedVector2D vecB = CFixedVector2D(db.x, db.z) - m_Source;
return (vecA.CompareLength(vecB) < 0);
return m_RangeError > 0 ? vecA.CompareLengthRough(vecB, m_RangeError) < 0 : vecA.CompareLength(vecB) < 0;
}
const EntityMap<EntityData>& m_EntityData;
CFixedVector2D m_Source;
u8 m_RangeError;
private:
EntityDistanceOrdering& operator=(const EntityDistanceOrdering&);
@ -294,6 +296,7 @@ struct SerializeHelper<Query>
serialize.NumberU32_Unbounded("owners mask", value.ownersMask);
serialize.NumberI32_Unbounded("interface", value.interface);
Serializer(serialize, "last match", value.lastMatch);
serialize.NumberU8_Unbounded("range percent error", value.rangeError);
serialize.NumberU8_Unbounded("flagsMask", value.flagsMask);
serialize.Bool("enabled", value.enabled);
serialize.Bool("parabolic",value.parabolic);
@ -923,20 +926,20 @@ public:
tag_t CreateActiveQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize) override
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize, u8 rangeError) override
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags, accountForSize);
m_Queries[id] = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flags, accountForSize, rangeError);
return id;
}
tag_t CreateActiveParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
const std::vector<int>& owners, int requiredInterface, u8 flags) override
const std::vector<int>& owners, int requiredInterface, u8 flags, u8 rangeError) override
{
tag_t id = m_QueryNext++;
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, yOrigin, owners, requiredInterface, flags, true);
m_Queries[id] = ConstructParabolicQuery(source, minRange, maxRange, yOrigin, owners, requiredInterface, flags, true, rangeError);
return id;
}
@ -993,25 +996,25 @@ public:
std::vector<entity_id_t> ExecuteQueryAroundPos(const CFixedVector2D& pos,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) override
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError) override
{
Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize);
Query q = ConstructQuery(INVALID_ENTITY, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize, rangeError);
std::vector<entity_id_t> r;
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos, q.rangeError));
return r;
}
std::vector<entity_id_t> ExecuteQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) override
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError) override
{
PROFILE("ExecuteQuery");
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize);
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, GetEntityFlagMask("normal"), accountForSize, rangeError);
std::vector<entity_id_t> r;
@ -1026,7 +1029,7 @@ public:
PerformQuery(q, r, pos);
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos, q.rangeError));
return r;
}
@ -1061,7 +1064,7 @@ public:
q.lastMatch = r;
// Return the list sorted by distance from the entity
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos));
std::stable_sort(r.begin(), r.end(), EntityDistanceOrdering(m_EntityData, pos, q.rangeError));
return r;
}
@ -1145,7 +1148,7 @@ public:
continue;
if (cmpSourcePosition && cmpSourcePosition->IsInWorld())
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D()));
std::stable_sort(added.begin(), added.end(), EntityDistanceOrdering(m_EntityData, cmpSourcePosition->GetPosition2D(),query.rangeError));
messages.resize(messages.size() + 1);
std::pair<entity_id_t, CMessageRangeUpdate>& back = messages.back();
@ -1404,7 +1407,7 @@ public:
Query ConstructQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize) const
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize, u8 rangeError = 0) const
{
// Min range must be non-negative.
if (minRange < entity_pos_t::Zero())
@ -1453,6 +1456,7 @@ public:
LOGWARNING("CCmpRangeManager: No owners in query for entity %u", source);
q.interface = requiredInterface;
q.rangeError = rangeError;
q.flagsMask = flagsMask;
return q;
@ -1460,9 +1464,9 @@ public:
Query ConstructParabolicQuery(entity_id_t source,
entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize) const
const std::vector<int>& owners, int requiredInterface, u8 flagsMask, bool accountForSize, u8 rangeError = 0) const
{
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flagsMask, accountForSize);
Query q = ConstructQuery(source, minRange, maxRange, owners, requiredInterface, flagsMask, accountForSize, rangeError);
q.parabolic = true;
q.yOrigin = yOrigin;
return q;

View File

@ -1,4 +1,4 @@
/* Copyright (C) 2022 Wildfire Games.
/* Copyright (C) 2024 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@ -132,7 +132,7 @@ public:
* @return list of entities matching the query, ordered by increasing distance from the source entity.
*/
virtual std::vector<entity_id_t> ExecuteQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) = 0;
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError = 0) = 0;
/**
* Execute a passive query.
@ -145,7 +145,7 @@ public:
* @return list of entities matching the query, ordered by increasing distance from the source entity.
*/
virtual std::vector<entity_id_t> ExecuteQueryAroundPos(const CFixedVector2D& pos, entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, bool accountForSize) = 0;
const std::vector<int>& owners, int requiredInterface, bool accountForSize, u8 rangeError = 0) = 0;
/**
* Construct an active query. The query will be disabled by default.
@ -159,7 +159,7 @@ public:
* @return unique non-zero identifier of query.
*/
virtual tag_t CreateActiveQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange,
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize) = 0;
const std::vector<int>& owners, int requiredInterface, u8 flags, bool accountForSize, u8 rangeError = 0) = 0;
/**
* Construct an active query of a paraboloic form around the unit.
@ -177,7 +177,7 @@ public:
* @return unique non-zero identifier of query.
*/
virtual tag_t CreateActiveParabolicQuery(entity_id_t source, entity_pos_t minRange, entity_pos_t maxRange, entity_pos_t yOrigin,
const std::vector<int>& owners, int requiredInterface, u8 flags) = 0;
const std::vector<int>& owners, int requiredInterface, u8 flags, u8 rangeError = 0) = 0;
/**