Oh wow, thank you. I honestly thought this might be too big an ask.
However, I've come up with a couple of problems. The first one is probably easy:
I've never used mixins before, so I'm not sure about any peculiarities of them. I tried putting your code into a files called smoothturn.zs and then loading it from my main zscript file like so:
GZDoom keeps telling me it can't find the file. I've checked and double checked the path, and filename, put it in different locations with different filenames and so on but no joy. Can mixins be loaded this way?
Anyway, I've worked around that by just pasting the code into my main zscript file for the moment.
The second problem involves a VM Abort. Just to see if I can get the code working, I have tried to use it on a simple zombieman clone that replaces the normal one (made to look smaller so I know it has loaded). When I fire up GZDoom, I see the replacement mini-zombies standing there with their backs to me at the start of MAP01 (as usual) but, when I fire my weapon, GZDoom gives me the following message.
Code: Select all
VM execution aborted: tried to read from address zero.
Called from ZombieTest.ChaseSmooth at zscript.zs, line 75
Called from state ZombieTest.2 in ZombieTest
Called from state ZombieTest.1 in ZombieTest
Here's the ZScript. What I added is nothing fancy. (The zombie replacement starts after the mixin code.)
Code: Select all
Mixin class SmoothMoveTurnMixin
{
//ChaseSmooth works by storing the state ChaseSmooth was called from, and using that information along with
//the actor's tics value to determine if the ChaseSmoth call each tic should have the chance to result in an attack, or just moving & turning.
//The smooth turning functions work in a similar way.
Actor smtActor;//the actor instance this mixin belongs to
State smtChaseState;
bool smtChasing;//controls wether to call A_Chase or A_Wander in ChaseSmooth
int smtChaseFlags;//stores any A_Chase flags for use during Tick()
State smtTurnState;
double smtAngleTarget;
double smtAngleTurnSpeed;//the difference between starting angle and smtAngleTarget divided by smtTurnState.tics
double smtAngleTurnSpeedMax;//if > 0, if smtLimitAngleTurnSpeed is true and when chasing or wandering, limit smtAngleTurnSpeed to to this
bool smtLimitAngleTurnSpeed;//set to true when chasing or wandering
bool affectPitch;
double smtPitchTarget;
double smtPitchTurnSpeed;//the difference between starting Pitch and smtPitchTarget divided by smtTurnState.tics
double smtPitchTurnSpeedMax;//if > 0, if smtLimitPitchTurnSpeed is true and when chasing or wandering, limit smtPitchTurnSpeed to to this
bool smtLimitPitchTurnSpeed;//set to true when chasing or wandering
property ChaseAffectsPitch : affectPitch;
void InitSmoothMoveTurnMixin(Actor other, double AngleTurnSpeedMax = 0, double PitchTurnSpeedMax = 0)
{
smtActor = other;
smtAngleTurnSpeedMax = AngleTurnSpeedMax;
smtPitchTurnSpeedMax = PitchTurnSpeedMax;
}
//To be called from the smtActor's Tick()
void UpdateSmoothMoveTurn()
{
if(smtActor == null)
{
return;
}
//this chase behavior is called when the monster is in a state that called A_ChaseSmooth,
//but is not the first tic in that state
if(smtChaseState && smtActor.curState && smtActor.tics != smtActor.curState.tics)
{
if (smtActor.curState == smtChaseState)
{
ChaseSmooth(melee : null, missile : null, chaseFlags : smtChaseFlags, chase : smtChasing);
}
else
{
smtChaseState = null;
smtChasing = false;
smtChaseFlags = 0;
}
}
//this is used for turning smoothly.
if (smtTurnState && smtActor.curState && smtActor.tics != smtActor.curState.tics)
{
if(smtActor.curState == smtTurnState)
{
TurnTowards();
}
else
{
smtTurnState = null;
smtLimitAngleTurnSpeed = false;
smtLimitPitchTurnSpeed = false;
}
}
}
//Smooth chasing code by Boondorl
//Smooth turning code by phantombeta
void ChaseSmooth(StateLabel melee = '_a_chase_default', StateLabel missile = '_a_chase_default', int chaseFlags = 0, bool chase = true)
{
smtChasing = chase;
smtChaseState = smtActor.curState;
smtChaseFlags = chaseFlags;
Vector3 tempPos = smtActor.pos;
double angleTemp = smtActor.angle;
smtLimitAngleTurnSpeed = true;
double pitchTemp = smtActor.pitch;
if (affectPitch)
smtLimitpitchTurnSpeed = true;
if(chase)
{
if(smtActor.target == null)
{
return;
}
A_Chase(melee,missile,chaseFlags);
}
else
{
A_Wander(chaseFlags);
}
Vector3 diff = (smtActor.pos - tempPos);
Vector3 dir = !(diff ~== (0, 0, 0)) ? diff.Unit() : (0, 0, 0);
smtActor.SetOrigin(tempPos + dir * smtActor.speed,true);
smtActor.A_SetAngle(angleTemp);
if (affectPitch)
{
smtActor.A_SetPitch(pitchTemp);
}
//only rotate for the initial ChaseSmooth call, and not the automatic ones from Tick()
if(smtActor.tics == smtActor.curState.tics)
{
FaceAngleSmooth(smtActor.moveDir * 45, smtActor.moveDir * 45);
}
}
//Shortcut for smooth wandering.
void WanderSmooth(int flags = 0)
{
ChaseSmooth(null, null, flags, false);
}
//Smooth variant of A_Face(). Pass target as a parameter to replicate A_FaceTarget.
void FaceSmooth(Actor other = null,int flags = 0)
{
if(other == null)
{
return;
}
double angleTemp = smtActor.angle;
smtActor.A_Face(other,0,0,0,0,flags);//for pitch setting
smtActor.A_SetAngle(angleTemp);
FacePosSmooth(other.pos + (0,0,flags == FAF_MIDDLE ? double(other.height) / 2 : 0));
}
//for facing a position
void FacePosSmooth(Vector3 targetPos)
{
Vector3 sCoords = LevelLocals.SphericalCoords(smtActor.pos,targetPos,(smtActor.angle,smtActor.pitch));
FaceAngleSmooth(smtActor.angle - sCoords.x, smtActor.pitch - sCoords.y);
}
void FaceAngleSmooth(double a, double p = 0)
{
smtTurnState = smtActor.curState;
smtAngleTarget = a;
double angDiff = DeltaAngle(smtActor.angle,smtAngleTarget);
smtAngleTurnSpeed = abs(angDiff / (smtTurnState.tics > 0 ? smtTurnState.tics : 1));
smtPitchTarget = p;
double pitchDiff = DeltaAngle(smtActor.Pitch,smtPitchTarget);
smtPitchTurnSpeed = abs(pitchDiff / (smtTurnState.tics > 0 ? smtTurnState.tics : 1));
if(smtLimitAngleTurnSpeed && smtAngleTurnSpeedMax > 0)
{
smtAngleTurnSpeed = Clamp(smtAngleTurnSpeed,0,smtAngleTurnSpeedMax);
smtPitchTurnSpeed = Clamp(smtPitchTurnSpeed,0,smtPitchTurnSpeedMax);
}
TurnTowards();
}
//the base smooth turning function, turns smoothly towards any angle.
//This is only to be called from Tick(), ChaseSmooth, or the other smooth rotating functions.
protected void TurnTowards()
{
double angDiff = DeltaAngle(smtActor.angle,smtAngleTarget);
double angleChange = angDiff < 0 ? max(-smtAngleTurnSpeed, angDiff) : min(smtAngleTurnSpeed, angDiff);
smtActor.A_SetAngle(smtActor.angle + angleChange);
if (affectPitch)
{
double pitDiff = DeltaAngle(smtActor.pitch,smtpitchTarget);
double pitchChange = pitDiff < 0 ? max(-smtpitchTurnSpeed, pitDiff) : min(smtpitchTurnSpeed, pitDiff);
smtActor.A_Setpitch(smtActor.pitch + pitchChange);
}
//console.printf("[SmoothMoveTurnMixin/TurnTowards] smtAngleTarget = %f, angDiff = %f, smtAngleTurnSpeed = %f, angleChange = %f, angle = %f",smtAngleTarget,angDiff,smtAngleTurnSpeed,angleChange,smtActor.angle);
}
}
class ZombieTest : Actor replaces Zombieman
{
mixin SmoothMoveTurnMixin;
Default
{
Scale 0.8;
Health 20;
Radius 20;
Height 56;
Speed 8;
PainChance 200;
Monster;
+FLOORCLIP
SeeSound "grunt/sight";
AttackSound "grunt/attack";
PainSound "grunt/pain";
DeathSound "grunt/death";
ActiveSound "grunt/active";
Obituary "$OB_ZOMBIE";
Tag "$FN_ZOMBIE";
DropItem "Clip";
}
States
{
Spawn:
POSS AB 10 A_Look;
Loop;
See:
POSS AABBCCDD 4 ChaseSmooth();
Loop;
Missile:
POSS E 10 A_FaceTarget;
POSS F 8 A_PosAttack;
POSS E 8;
Goto See;
Pain:
POSS G 3;
POSS G 3 A_Pain;
Goto See;
Death:
POSS H 5;
POSS I 5 A_Scream;
POSS J 5 A_NoBlocking;
POSS K 5;
POSS L -1;
Stop;
XDeath:
POSS M 5;
POSS N 5 A_XScream;
POSS O 5 A_NoBlocking;
POSS PQRST 5;
POSS U -1;
Stop;
Raise:
POSS K 5;
POSS JIH 5;
Goto See;
}
}
Any ideas?