I suspect the max speed will be in effect if delays cause the vehicle to fall behind schedule for reaching its destination. In a convoy there are considerations of the slowest vehicle's max speed, I should imagine.
From ChiefGround.recomputeUnitsProperties():
public void recomputeUnitsProperties()
{
if(unitsPacked.size() > 0)
{
recomputeUnitsProperties_Packed();
return;
}
Object aobj[] = getOwnerAttached();
if(aobj.length <= 0)
ERR_NO_UNITS("recomputeUnitsProperties");
int i = aobj.length;
groupSpeed = 10000F;
maxSpace = -1F;
weaponsMask = 0;
hitbyMask = 0;
withPreys = false;
for(int j = 0; j < i; j++)
{
UnitInterface unitinterface = (UnitInterface)aobj[j];
float f = unitinterface.SpeedAverage(); //<<<------------------- look here
if(f < groupSpeed)
groupSpeed = f;
f = unitinterface.BestSpace();
if(f > maxSpace)
maxSpace = f;
if(unitinterface instanceof Predator)
weaponsMask |= ((Predator)unitinterface).WeaponsMask();
if(!(unitinterface instanceof Prey))
continue;
hitbyMask |= ((Prey)unitinterface).HitbyMask();
if(!(unitinterface instanceof Predator))
withPreys = true;
}
if(groupSpeed <= 0.001F)
ERR("group speed is too small"); //<<<------------------ and here
if(maxSpace <= 0.01F)
ERR("maxSpace is too small");
}
To supply some more stuff to chew on:
The relevant class/method from TankGeneric.class (the full class is MUCH bigger). I've identified the key line of code, which calls method set() in Moving.class. Which in turn determines if dontrun is true or false in UnitMove.class. If not reversing, or if dontrun is false, then SPEED_MAX is considered.
class Move extends Interpolate
{
public boolean tick()
{
if(dying != 0)
{
neverDust();
if(dying == 2)
return false;
if(dyingDelay-- <= 0L)
{
ShowExplode();
MakeCrush();
return false;
}
if(mov.rotatCurTime > 0L)
{
mov.rotatCurTime--;
float f = 1.0F - (float)mov.rotatCurTime / (float)mov.rotatTotTime;
pos.getAbs(TankGeneric.o);
TankGeneric.o.setYaw(mov.angles.getDeg(f));
if(mov.normal.z < 0.0F)
{
Engine.land().N(mov.srcPos.x, mov.srcPos.y, TankGeneric.n);
TankGeneric.o.orient(TankGeneric.n);
} else
{
TankGeneric.o.orient(mov.normal);
}
pos.setAbs(TankGeneric.o);
}
return true;
}
boolean flag = mov.moveCurTime < 0L && mov.rotatCurTime < 0L;
if(isNetMirror() && flag)
{
mov.switchToStay(30F);
flag = false;
}
if(flag)
{
ChiefGround chiefground = (ChiefGround)getOwner();
float f2 = -1F;
UnitMove unitmove;
if(collisionStage == 0)
{
if(prop.meshName2 != null)
{
TankGeneric.p.x = TankGeneric.Rnd(-0.3D, 0.3D);
TankGeneric.p.y = TankGeneric.Rnd(-0.3D, 0.3D);
TankGeneric.p.z = 1.0D;
unitmove = chiefground.AskMoveCommand(actor, TankGeneric.p, obs);
} else
{
unitmove = chiefground.AskMoveCommand(actor, null, obs);
}
} else
if(collisionStage == 1)
{
obs.collision(collidee, chiefground, udata);
collidee = null;
float f3 = TankGeneric.Rnd(-70F, 70F);
Vector2d vector2d = TankGeneric.Rotate(collisVector, f3);
vector2d.scale((double)prop.AFTER_COLLISION_DIST * TankGeneric.Rnd(0.87D, 1.75D));
TankGeneric.p.set(vector2d.x, vector2d.y, -1D);
unitmove = chiefground.AskMoveCommand(actor, TankGeneric.p, obs);
collisionStage = 2;
f2 = prop.SPEED_BACK;
} else
{
float f4 = TankGeneric.Rnd(0.0F, 359.99F);
Vector2d vector2d1 = TankGeneric.Rotate(collisVector, f4);
vector2d1.scale((double)prop.AFTER_COLLISION_DIST * TankGeneric.Rnd(0.2D, 0.6D));
TankGeneric.p.set(vector2d1.x, vector2d1.y, 1.0D);
unitmove = chiefground.AskMoveCommand(actor, TankGeneric.p, obs);
collisionStage = 0;
}
mov.set(unitmove, actor, prop.SPEED_MAX, f2, prop.ROT_SPEED_MAX, prop.ROT_INVIS_ANG); //<<<----------- look here
for(int j = 0; j < arms.length; j++)
{
if(!StayWhenFire(arms[j].aim))
continue;
if(Head360(prop.gunProperties[j]))
{
if(!arms[j].aim.isInFiringMode())
continue;
mov.switchToStay(1.3F);
break;
}
if(!arms[j].aim.isInAimingMode())
continue;
mov.switchToStay(1.3F);
break;
}
if(isNetMaster())
send_MoveCommand(mov, f2);
}
for(int i = 0; i < arms.length; i++)
arms[i].aim.tick_();
if(dust != null)
dust._setIntesity(mov.movingForward ? 1.0F : 0.0F);
if(mov.dstPos == null)
{
mov.moveCurTime--;
if(engineSFX != null && engineSTimer > 0 && --engineSTimer == 0)
engineSFX.stop();
return true;
}
if(engineSFX != null)
if(engineSTimer == 0)
{
engineSFX.play();
engineSTimer = (int)TankGeneric.SecsToTicks(TankGeneric.Rnd(10F, 12F));
} else
if(engineSTimer < ticksIn8secs)
engineSTimer = (int)TankGeneric.SecsToTicks(TankGeneric.Rnd(10F, 12F));
pos.getAbs(TankGeneric.o);
boolean flag1 = false;
if(mov.rotatCurTime > 0L)
{
mov.rotatCurTime--;
float f1 = 1.0F - (float)mov.rotatCurTime / (float)mov.rotatTotTime;
TankGeneric.o.setYaw(mov.angles.getDeg(f1));
flag1 = true;
if(mov.rotatCurTime <= 0L)
{
mov.rotatCurTime = -1L;
mov.rotatingInPlace = false;
}
}
if(!mov.rotatingInPlace && mov.moveCurTime > 0L)
{
mov.moveCurTime--;
double d = 1.0D - (double)mov.moveCurTime / (double)mov.moveTotTime;
TankGeneric.p.x = mov.srcPos.x * (1.0D - d) + mov.dstPos.x * d;
TankGeneric.p.y = mov.srcPos.y * (1.0D - d) + mov.dstPos.y * d;
if(mov.normal.z < 0.0F)
{
TankGeneric.p.z = Engine.land().HQ(TankGeneric.p.x, TankGeneric.p.y) + (double)HeightAboveLandSurface();
Engine.land().N(TankGeneric.p.x, TankGeneric.p.y, TankGeneric.n);
} else
{
TankGeneric.p.z = mov.srcPos.z * (1.0D - d) + mov.dstPos.z * d;
}
flag1 = false;
pos.setAbs(TankGeneric.p);
if(mov.moveCurTime <= 0L)
mov.moveCurTime = -1L;
}
if(mov.normal.z < 0.0F)
{
if(flag1)
Engine.land().N(mov.srcPos.x, mov.srcPos.y, TankGeneric.n);
TankGeneric.o.orient(TankGeneric.n);
} else
{
TankGeneric.o.orient(mov.normal);
}
pos.setAbs(TankGeneric.o);
return true;
}
Move()
{
}
}
The entirety of Moving.class:
package com.maddox.il2.ai.ground;
import com.maddox.JGP.Point3d;
import com.maddox.JGP.Vector3f;
import com.maddox.il2.ai.AnglesFork;
import com.maddox.il2.engine.*;
import com.maddox.rts.Time;
// Referenced classes of package com.maddox.il2.ai.ground:
// UnitInterface, UnitMove
public class Moving
{
private static final long SecsToTicks(float f)
{
long l = (long)(0.5D + (double)(f / Time.tickLenFs()));
return l >= 1L ? l : 1L;
}
public boolean IsLandAligned()
{
return normal.z < 0.0F;
}
public static final float distance2D(Point3d point3d, Point3d point3d1)
{
return (float)Math.sqrt((point3d.x - point3d1.x) * (point3d.x - point3d1.x) + (point3d.y - point3d1.y) * (point3d.y - point3d1.y));
}
public Moving()
{
moveCurTime = rotatCurTime = -1L;
srcPos = new Point3d();
dstPos = new Point3d();
normal = null;
angles = new AnglesFork();
rotatingInPlace = false;
movingForward = false;
}
public void switchToStay(float f)
{
dstPos = null;
moveTotTime = moveCurTime = SecsToTicks(f);
rotatCurTime = -1L;
rotatingInPlace = false;
movingForward = false;
}
public void switchToAsk()
{
switchToStay(0.0F);
moveCurTime = rotatCurTime = -1L;
}
public void set(UnitMove unitmove, Actor actor, float f, float f1, float f2, float f3) //f=SPEED_MAX, f1=SPEED_BACK, f2=ROT_SPEED_MAX, f3=ROT_INVIS_ANG
//from TankGeneric: mov.set(unitmove, actor, prop.SPEED_MAX, f2, prop.ROT_SPEED_MAX, prop.ROT_INVIS_ANG);
{
dstPos = new Point3d();
normal = new Vector3f();
normal.set(unitmove.normal);
srcPos.set(actor.pos.getAbsPoint());
moveTotTime = moveCurTime = SecsToTicks(unitmove.totalTime);
if(unitmove.pos == null)
{
switchToStay(unitmove.totalTime);
return;
}
dstPos.set(unitmove.pos);
movingForward = true;
angles.setDeg(actor.pos.getAbsOrient().getYaw());
double d = dstPos.x - srcPos.x;
double d1 = dstPos.y - srcPos.y;
if(Math.abs(d) + Math.abs(d1) > 1.0000000000000001E-005D)
angles.setDstRad((float)Math.atan2(d1, d));
boolean flag = false;
if(f1 > 0.0F && angles.getAbsDiffDeg() > 90F)
{
flag = true;
angles.reverseDst();
movingForward = false;
}
rotatingInPlace = false;
float f4 = 0.0F;
d1 = 0.0F;
float f5 = unitmove.totalTime;
if(angles.getAbsDiffDeg() > 0.02F)
if(angles.getAbsDiffDeg() <= f3)
{
f4 = angles.getAbsDiffDeg() / (f2 * 0.2F);
if(f4 > f5)
{
float f6 = f4 * 0.2F;
if(unitmove.dontrun)
f6 *= 0.6F;
if(f6 > f5)
f4 = f5 = f6;
else
f4 = f5;
}
} else
{
rotatingInPlace = true;
f4 = angles.getAbsDiffDeg() / f2;
if(f4 > f5)
f5 = f4;
f5 -= f4;
}
d1 = f5;
float f7 = distance2D(srcPos, dstPos);
float f8 = flag ? f1 : unitmove.dontrun ? unitmove.walkSpeed : f; //if not flag=true if reversing, and if not unitmove.walkSpeed, then f8=SPEED_MAX
if(f7 / f8 > d1)
d1 = f7 / f8;
rotatTotTime = SecsToTicks(f4);
moveTotTime = SecsToTicks(d1);
moveCurTime = d1 <= 0.0F ? -1L : moveTotTime;
rotatCurTime = f4 <= 0.0F ? -1L : rotatTotTime;
if(rotatCurTime < 0L && moveCurTime <= 1L || distance2D(srcPos, dstPos) < 0.05F)
switchToStay(((UnitInterface)actor).StayInterval());
}
public void switchToRotate(Actor actor, float f, float f1)
{
if(normal == null)
{
movingForward = false;
moveTotTime = moveCurTime = -1L;
rotatTotTime = rotatCurTime = -1L;
return;
} else
{
dstPos = new Point3d();
srcPos.set(actor.pos.getAbsPoint());
dstPos.set(srcPos);
rotatingInPlace = true;
angles.setDeg(actor.pos.getAbsOrient().getYaw());
angles.setDstDeg(angles.getSrcDeg() + f);
float f2 = angles.getAbsDiffDeg() / f1;
rotatTotTime = SecsToTicks(f2);
rotatCurTime = rotatTotTime <= 0L ? -1L : rotatTotTime;
movingForward = true;
moveTotTime = -1L;
moveCurTime = -1L;
return;
}
}
public Point3d srcPos;
public Point3d dstPos;
public AnglesFork angles;
public long moveTotTime;
public long moveCurTime;
public long rotatTotTime;
public long rotatCurTime;
public Vector3f normal;
public boolean rotatingInPlace;
public boolean movingForward;
}
The entirety of UnitMove.class:
package com.maddox.il2.ai.ground;
import com.maddox.JGP.Point3d;
import com.maddox.JGP.Vector3f;
public class UnitMove
{
UnitMove(float f, Vector3f vector3f)
{
pos = null;
totalTime = f;
normal = new Vector3f(vector3f);
dontrun = false;
}
public UnitMove(float f, Point3d point3d, float f1, Vector3f vector3f, float f2)
{
pos = new Point3d(point3d);
pos.z += f;
totalTime = f1;
normal = new Vector3f(vector3f);
if(f2 > 0.0F)
{
dontrun = true;
walkSpeed = f2;
} else
{
dontrun = false;
}
}
public boolean IsLandAligned()
{
return normal.z < 0.0F;
}
public Point3d pos;
public float totalTime;
public Vector3f normal;
public boolean dontrun;
public float walkSpeed;
}