[gameplay] Fix chasing range cavalry

This fixes chasing, particularly chasing ranged cavalry.

- Standardise the range of melee cav to 4.
- Decreases the speed of ranged cavalry slightly to make melee cavalry a
better counter & reduce the ability of ranged cavalry to dominate an
area.
- Fix UnitMotion to better chase units, by increasing direct-range
distance and making "from scratch" short paths recompute better paths
(by increasing the search range).
- Gives some free rotation time for slight angles to units. Angles below
30° take no time to rotate towards. Chasing units that recomputed a lot
of paths could be slowed down substantially by minute angle differences.

Fixes #5936

Differential Revision: https://code.wildfiregames.com/D3402
This was SVN commit r24708.
This commit is contained in:
wraitii 2021-01-19 19:09:55 +00:00
parent bc7977946b
commit 9d82ae15af
18 changed files with 80 additions and 15 deletions

View file

@ -3,6 +3,7 @@ const JAV_TEMPLATE = "units/mace/infantry_javelineer_b";
const REG_UNIT_TEMPLATE = "units/athen/infantry_spearman_b";
const FAST_UNIT_TEMPLATE = "units/athen/cavalry_swordsman_b";
const FAST_UNIT_TEMPLATE_2 = "units/athen/cavalry_javelineer_b";
const ATTACKER = 2;
@ -180,6 +181,27 @@ var manual_formation_dance = function(attacker, target, dance_distance, att_dist
};
/**
* This isn't really dancing, but it can still fail.
*/
var avoidance = function(attacker, target, att_distance = 10)
{
return () => {
let dancer = QuickSpawn(200, 300, target);
for (let i = 0; i < 5; ++i)
{
WalkTo(300, 400, true, dancer);
WalkTo(400, 300, true, dancer);
WalkTo(300, 200, true, dancer);
WalkTo(200, 300, true, dancer);
}
let attackers = [];
attackers.push(Attack(dancer, QuickSpawn(200, 290, attacker, ATTACKER), ATTACKER));
return [[dancer], attackers];
};
};
experiments.unit_manual_dance_archer = {
"spawn": manual_dance(ARCHER_TEMPLATE, REG_UNIT_TEMPLATE, 5)
};
@ -280,6 +302,10 @@ experiments.formation_bad_dance_fast_archer = {
"spawn": manual_formation_dance(ARCHER_TEMPLATE, FAST_UNIT_TEMPLATE, 50, 50, 5)
};
experiments.fast_on_fast = {
"spawn": avoidance(FAST_UNIT_TEMPLATE, FAST_UNIT_TEMPLATE_2)
};
var cmpTrigger = Engine.QueryInterface(SYSTEM_ENTITY, IID_Trigger);
Trigger.prototype.SetupUnits = function()

View file

@ -8,7 +8,7 @@
<Pierce>0</Pierce>
<Crush>2.3</Crush>
</Damage>
<MaxRange>3.5</MaxRange>
<MaxRange>4</MaxRange>
<PrepareTime>500</PrepareTime>
<RepeatTime>1000</RepeatTime>
<PreferredClasses datatype="tokens">Unit+!Ship</PreferredClasses>

View file

@ -14,7 +14,7 @@
<Multiplier>1.75</Multiplier>
</BonusCavMelee>
</Bonuses>
<MaxRange>4.5</MaxRange>
<MaxRange>4</MaxRange>
<PrepareTime>625</PrepareTime>
<RepeatTime>1250</RepeatTime>
<PreferredClasses datatype="tokens">Human</PreferredClasses>

View file

@ -8,7 +8,7 @@
<Pierce>0</Pierce>
<Crush>0</Crush>
</Damage>
<MaxRange>3.5</MaxRange>
<MaxRange>4</MaxRange>
<PrepareTime>375</PrepareTime>
<RepeatTime>750</RepeatTime>
<PreferredClasses datatype="tokens">Unit+!Ship</PreferredClasses>

View file

@ -38,4 +38,7 @@
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">0.9</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -38,4 +38,7 @@
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">0.9</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -44,4 +44,7 @@
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">0.9</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -8,7 +8,7 @@
<Pierce>0</Pierce>
<Crush>4.6</Crush>
</Damage>
<MaxRange>3.5</MaxRange>
<MaxRange>4</MaxRange>
<PrepareTime>500</PrepareTime>
<RepeatTime>1000</RepeatTime>
<PreferredClasses datatype="tokens">Unit+!Ship</PreferredClasses>

View file

@ -44,4 +44,7 @@
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">0.9</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -8,7 +8,7 @@
<Pierce>6</Pierce>
<Crush>0</Crush>
</Damage>
<MaxRange>4.5</MaxRange>
<MaxRange>4</MaxRange>
<Bonuses>
<BonusCavMelee>
<Classes>Cavalry</Classes>

View file

@ -8,7 +8,7 @@
<Pierce>0</Pierce>
<Crush>0.0</Crush>
</Damage>
<MaxRange>3.5</MaxRange>
<MaxRange>4</MaxRange>
<PrepareTime>375</PrepareTime>
<RepeatTime>750</RepeatTime>
<PreferredClasses datatype="tokens">Unit+!Ship</PreferredClasses>

View file

@ -44,4 +44,7 @@
<attack_ranged>attack/weapon/bow_attack.xml</attack_ranged>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">0.9</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -8,7 +8,7 @@
<Pierce>0</Pierce>
<Crush>9.2</Crush>
</Damage>
<MaxRange>3.5</MaxRange>
<MaxRange>4</MaxRange>
<PrepareTime>500</PrepareTime>
<RepeatTime>1000</RepeatTime>
<PreferredClasses datatype="tokens">Unit+!Ship</PreferredClasses>
@ -30,4 +30,7 @@
<attack_melee>attack/weapon/sword_attack.xml</attack_melee>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">1.1</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -44,4 +44,7 @@
<attack_impact_ranged>attack/impact/javelin_impact.xml</attack_impact_ranged>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">0.9</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -22,4 +22,7 @@
<attack_melee>attack/weapon/sword_attack.xml</attack_melee>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">1.1</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -8,7 +8,7 @@
<Pierce>12</Pierce>
<Crush>0</Crush>
</Damage>
<MaxRange>4.5</MaxRange>
<MaxRange>4</MaxRange>
<Bonuses>
<BonusCavMelee>
<Classes>Cavalry</Classes>
@ -38,4 +38,7 @@
<attack_melee>attack/weapon/spear_attack.xml</attack_melee>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">1.1</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -8,7 +8,7 @@
<Pierce>0.0</Pierce>
<Crush>0.0</Crush>
</Damage>
<MaxRange>3.5</MaxRange>
<MaxRange>4</MaxRange>
<PrepareTime>375</PrepareTime>
<RepeatTime>750</RepeatTime>
<PreferredClasses datatype="tokens">Unit+!Ship</PreferredClasses>
@ -37,4 +37,7 @@
<attack_melee>attack/weapon/sword_attack.xml</attack_melee>
</SoundGroups>
</Sound>
<UnitMotion>
<WalkSpeed op="mul">1.1</WalkSpeed>
</UnitMotion>
</Entity>

View file

@ -69,7 +69,7 @@ static const entity_pos_t LONG_PATH_MIN_DIST = entity_pos_t::FromInt(TERRAIN_TIL
* If we are this close to our target entity/point, then think about heading
* for it in a straight line instead of pathfinding.
*/
static const entity_pos_t DIRECT_PATH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*4);
static const entity_pos_t DIRECT_PATH_RANGE = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*6);
/**
* To avoid recomputing paths too often, have some leeway for target range checks
@ -731,8 +731,9 @@ private:
/**
* Start an asynchronous short path query.
* @param extendRange - if true, extend the search range to at least the distance to the goal.
*/
void RequestShortPath(const CFixedVector2D& from, const PathGoal& goal, bool avoidMovingUnits);
void RequestShortPath(const CFixedVector2D& from, const PathGoal& goal, bool extendRange);
/**
* General handler for MoveTo interface functions.
@ -1028,7 +1029,8 @@ bool CCmpUnitMotion::PerformMove(fixed dt, const fixed& turnRate, WaypointPath&
}
// Rotate towards the next waypoint and continue moving.
angle = atan2_approx(offset.X, offset.Y);
timeLeft = (maxRotation - absoluteAngleDiff) / turnRate;
// Give some 'free' rotation for angles below 0.5 radians.
timeLeft = (std::min(maxRotation, maxRotation - absoluteAngleDiff + fixed::FromInt(1)/2)) / turnRate;
}
}
@ -1159,7 +1161,7 @@ bool CCmpUnitMotion::HandleObstructedMove(bool moved)
// The goal here is to manage to move in the general direction of our target, not to be super accurate.
fixed radius = Clamp(skipbeyond/3, fixed::FromInt(TERRAIN_TILE_SIZE), fixed::FromInt(TERRAIN_TILE_SIZE*3));
PathGoal subgoal = { PathGoal::CIRCLE, m_LongPath.m_Waypoints.back().x, m_LongPath.m_Waypoints.back().z, radius };
RequestShortPath(pos, subgoal, true);
RequestShortPath(pos, subgoal, false);
return true;
}()) return true;
@ -1529,6 +1531,7 @@ void CCmpUnitMotion::ComputePathToGoal(const CFixedVector2D& from, const PathGoa
if (shortPath)
{
m_LongPath.m_Waypoints.clear();
// Extend the range so that our first path is probably valid.
RequestShortPath(from, goal, true);
}
else
@ -1555,16 +1558,22 @@ void CCmpUnitMotion::RequestLongPath(const CFixedVector2D& from, const PathGoal&
m_ExpectedPathTicket.m_Ticket = cmpPathfinder->ComputePathAsync(from.X, from.Y, improvedGoal, m_PassClass, GetEntityId());
}
void CCmpUnitMotion::RequestShortPath(const CFixedVector2D &from, const PathGoal& goal, bool avoidMovingUnits)
void CCmpUnitMotion::RequestShortPath(const CFixedVector2D &from, const PathGoal& goal, bool extendRange)
{
CmpPtr<ICmpPathfinder> cmpPathfinder(GetSystemEntity());
if (!cmpPathfinder)
return;
entity_pos_t searchRange = ShortPathSearchRange();
if (extendRange)
{
CFixedVector2D dist(from.X - goal.x, from.Y - goal.z);
if (dist.CompareLength(searchRange - entity_pos_t::FromInt(1)) >= 0)
searchRange = dist.Length() + fixed::FromInt(1);
}
m_ExpectedPathTicket.m_Type = Ticket::SHORT_PATH;
m_ExpectedPathTicket.m_Ticket = cmpPathfinder->ComputeShortPathAsync(from.X, from.Y, m_Clearance, searchRange, goal, m_PassClass, avoidMovingUnits, GetGroup(), GetEntityId());
m_ExpectedPathTicket.m_Ticket = cmpPathfinder->ComputeShortPathAsync(from.X, from.Y, m_Clearance, searchRange, goal, m_PassClass, true, GetGroup(), GetEntityId());
}
bool CCmpUnitMotion::MoveTo(MoveRequest request)