nuclide/src/gs-entbase/server/func_tracktrain.qc

1249 lines
30 KiB
Plaintext

/*
* Copyright (c) 2016-2023 Vera Visions LLC.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
enumflags
{
TRAIN_NOPITCH,
TRAIN_NOUSER,
TRAIN_NONSOLID,
TRAIN_FIXED,
TRAIN_UNUSED1,
TRAIN_SOUNDPITCH,
TRAIN_NOBLOCK
};
enumflags
{
FNCTKTRNET_DRIVER,
FNCTKTRNET_MODELINDEX,
FNCTKTRNET_ORIGIN,
FNCTKTRNET_ANGLES,
FNCTKTRNET_VELOCITY,
FNCTKTRNET_TURNING,
FNCTKTRNET_SOLIDMOVETYPE,
FNCTKTRNET_FLAGS
};
/*!QUAKED func_tracktrain (0 .5 .8) ? NOPITCH NOUSER NOTSOLID
# OVERVIEW
An interactive train that moves along a series of path_track entities.
It turns and can respond to user input. It does not necessarily have to.
# KEYS
- "targetname" : Name
- "target" : First node to target.
- "killtarget" : Target to kill when triggered.
- "dmg" : Damage to inflict upon a person blocking the way.
- "snd_move" : Path to sound sample which plays when it's moving.
- "snd_stop" : Path to sound sample which plays when it stops moving.
# SPAWNFLAGS
- NOPITCH (1) : Stop at each path_track until we're triggered again.
- NOUSER (2) : Train does not pivot up/down at all.
- NOTSOLID (8) : Don't do collision testing against this entity.
- FIXED (16) : Fixed orientation. Will not turn, only move.
# NOTES
Upon level entry, the func_tracktrain will spawn right where its first path_track
node is, as specified in `target`.
This is so you can light the func_tracktrain somewhere else - like a lonely
box somewhere outside the playable area.
If no targetname is specified, the train will move on its own at map-launch.
By default, any func_tracktrain can be interacted with by players.
You can restrict the area in which the train can be used with a func_traincontrols
entity. You can also set the NOUSER (2) spawnflag to disable it entirely.
# TRIVIA
This entity was introduced in Half-Life (1998).
*/
class
func_tracktrain:NSVehicle
{
public:
void func_tracktrain(void);
#ifdef CLIENT
/* overrides */
virtual void PredictPreFrame(void);
virtual void PredictPostFrame(void);
virtual void ReceiveEntity(float, float);
virtual void UpdateView(void);
//virtual void postdraw(void);
#endif
#ifdef SERVER
/** Call to move the tracktrain forward. */
nonvirtual void PathMoveForward(void);
/** Call to move the tracktrain backward. */
nonvirtual void PathMoveBack(void);
/** Called when the current path is cleared, and a stop sound is played. */
nonvirtual void PathClear(void);
/** Called when the current path is cleared, and a stop sound is played. */
nonvirtual void PathDone(void);
/** Returns if the train can pitch up/down. */
nonvirtual bool IgnoresPitch(void);
/** Returns the track node (path_track) ahead of the train. */
nonvirtual path_track GetTrackNodeForward(void);
/** Returns the track node (path_track) back of the train. */
nonvirtual path_track GetTrackNodeBack(void);
/** Returns a 3d position between two nodes at the specified progression. Value between 0.0f - 1.0f, 1.0 being closest to the next node heading forward.*/
nonvirtual vector GetPointBetweenNodes(float);
nonvirtual vector GetTrainPivotPoint(bool);
nonvirtual void RenderDebugInfo(void);
nonvirtual bool ControlCheck(entity);
/* overrides */
virtual void Blocked(entity);
virtual void OnPlayerUse(void);
virtual void Save(float);
virtual void Restore(string,string);
virtual void TransitionComplete(void);
virtual void SpawnKey(string,string);
virtual void Spawned(void);
virtual void Respawn(void);
virtual void Trigger(entity, triggermode_t);
virtual void EvaluateEntity(void);
virtual float SendEntity(entity, float)
;
#endif
virtual void PlayerInput(void);
#ifdef SERVER
private:
float m_flWait;
float m_flSpeed;
float m_flDamage;
float m_flHeight;
float m_flStartSpeed;
float m_flCurrentSpeed;
float m_flBank;
string m_strMoveSnd;
string m_strStopSnd;
vector m_vecRelationTarget;
float m_flUseTime;
int m_iInputDirection;
int m_iOldInputDirection;
int m_iInputLevel;
int m_iOldInputLevel;
NSTimer m_timerAngled;
float m_flTrainLength;
bool _m_bDriving;
nonvirtual void _SoundMove(void);
nonvirtual void _SoundStop(void);
nonvirtual void _AfterSpawn(void);
nonvirtual void _PathArrivedForward(void);
nonvirtual void _PathArrivedBack(void);
nonvirtual void _AngleDone(void);
#endif
};
void
func_tracktrain::func_tracktrain(void)
{
#ifdef SERVER
m_flWait = 0.0f;
m_flSpeed = 100; /* FIXME: This is all decided by the first path_track pretty much */
m_flDamage = 0.0f;
m_flHeight = 0.0f;
m_flStartSpeed = 0.0f;
m_flCurrentSpeed = 0.0f;
m_flBank = 0.0f;
m_strMoveSnd = __NULL__;
m_strStopSnd = __NULL__;
m_vecRelationTarget = g_vec_null;
m_flUseTime = 0.0f;
m_iInputDirection = 0i;
m_iOldInputDirection = 0i;
m_iInputLevel = 0i;
m_iOldInputLevel = 0i;
m_timerAngled = __NULL__;
m_flTrainLength = 0.0f;
_m_bDriving = false;
#endif
}
#ifdef SERVER
vector
func_tracktrain::GetPointBetweenNodes(float percentageValue)
{
vector newPos = g_vec_null;
path_track nextNode = GetTrackNodeForward();
path_track prevNode = GetTrackNodeBack();
newPos[0] = Math_Lerp(prevNode.origin[0], nextNode.origin[0], percentageValue);
newPos[1] = Math_Lerp(prevNode.origin[1], nextNode.origin[1], percentageValue);
newPos[2] = Math_Lerp(prevNode.origin[2], nextNode.origin[2], percentageValue);
return newPos;
}
vector
func_tracktrain::GetTrainPivotPoint(bool drivingBackwards)
{
/* needs more work */
#if 1
return GetOrigin();
#else
vector newPos;
path_track nextNode;
vector currentDifference;
float currentLength;
float shorterLengthTest;
if (drivingBackwards) {
return GetOrigin();
} else {
newPos = GetOrigin();
nextNode = GetTrackNodeForward();
currentDifference = (newPos - nextNode.GetOrigin());
currentLength = vlen(currentDifference);
makevectors(GetAngles());
newPos += v_forward * -m_flTrainLength;
shorterLengthTest = vlen(GetOrigin() - newPos);
/* too short to turn */
if (shorterLengthTest > currentLength) {
return GetOrigin();
}
}
return newPos;
#endif
}
path_track
func_tracktrain::GetTrackNodeForward(void)
{
if (!target) {
return __NULL__;
}
for (entity e = world; (e = find(e, ::targetname, GetTriggerTarget()));) {
if (e.classname == "path_track" || e.classname == "path_corner") {
if (e.classname == "path_track") {
path_track track = (path_track)e;
if (track.m_bTrackEnabled == false) {
continue;
}
}
return e;
}
}
return __NULL__;
}
path_track
func_tracktrain::GetTrackNodeBack(void)
{
path_track forwardNode = GetTrackNodeForward();
if (forwardNode) {
return forwardNode.GetSelfTargetEntity();
}
return __NULL__;
}
void
func_tracktrain::RenderDebugInfo(void)
{
path_track eNode;
path_track oldNode;
vector startPos = GetTrainPivotPoint(false);
/* draw axis lines */
{
makevectors(GetAngles());
R_BeginPolygon("", 0, 0);
R_PolygonVertex(startPos, [0,1], [0,1,0], 1.0);
R_PolygonVertex(startPos + (v_forward * -128), [1,1], [0,1,0], 1.0);
R_EndPolygon();
R_BeginPolygon("", 0, 0);
R_PolygonVertex(startPos, [0,1], [1,0,0], 1.0);
R_PolygonVertex(startPos + (v_up * 128), [1,1], [1,0,0], 1.0);
R_EndPolygon();
R_BeginPolygon("", 0, 0);
R_PolygonVertex(startPos, [0,1], [0,0,1], 1.0);
R_PolygonVertex(startPos + (v_right * -128), [1,1], [0,0,1], 1.0);
R_EndPolygon();
}
/* next node */
eNode = (path_track)GetTrackNodeForward();
/* line to the next node forward (green) */
if (eNode) {
R_BeginPolygon("", 0, 0);
R_PolygonVertex(startPos, [0,1], [0,1,0], 1.0);
R_PolygonVertex(eNode.GetOrigin() + [0,0, m_flHeight], [1,1], [0,1,0], 1.0);
R_EndPolygon();
}
oldNode = eNode;
/* line to the next node forward (green) */
if (eNode) {
eNode = (path_track)eNode.GetPathTargetEntity();
if (eNode) {
R_BeginPolygon("", 0, 0);
R_PolygonVertex(oldNode.GetOrigin() + [0,0, m_flHeight], [0,1], [0,0.75,0], 1.0);
R_PolygonVertex(eNode.GetOrigin() + [0,0, m_flHeight], [1,1], [0,0.75,0], 1.0);
R_EndPolygon();
oldNode = eNode;
eNode = (path_track)eNode.GetPathTargetEntity();
if (eNode) {
R_BeginPolygon("", 0, 0);
R_PolygonVertex(oldNode.GetOrigin() + [0,0, m_flHeight], [0,1], [0,0.75,0], 1.0);
R_PolygonVertex(eNode.GetOrigin() + [0,0, m_flHeight], [1,1], [0,0.75,0], 1.0);
R_EndPolygon();
}
}
}
/* node back */
eNode = (path_track)GetTrackNodeBack();
/* line to the next node forward (green) */
if (eNode) {
R_BeginPolygon("", 0, 0);
R_PolygonVertex(startPos, [0,1], [1,0,0], 1.0);
R_PolygonVertex(eNode.GetOrigin() + [0,0, m_flHeight], [1,1], [1,0,0], 1.0);
R_EndPolygon();
}
oldNode = eNode;
if (eNode) {
eNode = (path_track)eNode.GetSelfTargetEntity();
if (eNode) {
/* line to the next node forward (green) */
R_BeginPolygon("", 0, 0);
R_PolygonVertex(oldNode.GetOrigin() + [0,0, m_flHeight], [0,1], [0.75,0,0], 1.0);
R_PolygonVertex(eNode.GetOrigin() + [0,0, m_flHeight], [1,1], [0.75,0,0], 1.0);
R_EndPolygon();
oldNode = eNode;
eNode = (path_track)eNode.GetSelfTargetEntity();
if (eNode) {
R_BeginPolygon("", 0, 0);
R_PolygonVertex(oldNode.GetOrigin() + [0,0, m_flHeight], [0,1], [0.75,0,0], 1.0);
R_PolygonVertex(eNode.origin + [0,0, m_flHeight], [1,1], [0.75,0,0], 1.0);
R_EndPolygon();
}
}
}
}
bool
func_tracktrain::IgnoresPitch(void)
{
if (HasSpawnFlags(TRAIN_NOPITCH)) {
return (true);
}
return (false);
}
void
func_tracktrain::Save(float handle)
{
super::Save(handle);
SaveFloat(handle, "m_flWait", m_flWait);
SaveFloat(handle, "m_flSpeed", m_flSpeed);
SaveFloat(handle, "m_flDamage", m_flDamage);
SaveFloat(handle, "m_flHeight", m_flHeight);
SaveFloat(handle, "m_flStartSpeed", m_flStartSpeed);
SaveFloat(handle, "m_flCurrentSpeed", m_flCurrentSpeed);
SaveFloat(handle, "m_flBank", m_flBank);
SaveString(handle, "m_strMoveSnd", m_strMoveSnd);
SaveString(handle, "m_strStopSnd", m_strStopSnd);
SaveFloat(handle, "m_flUseTime", m_flUseTime);
SaveInt(handle, "m_iInputDirection", m_iInputDirection);
SaveInt(handle, "m_iOldInputDirection", m_iOldInputDirection);
SaveInt(handle, "m_iInputLevel", m_iInputLevel);
SaveEntity(handle, "m_timerAngled", m_timerAngled);
SaveFloat(handle, "m_flTrainLength", m_flTrainLength);
SaveBool(handle, "_m_bDriving", _m_bDriving);
/* if we are on a track, take the difference in offset so we can smoothly transition */
if (target) {
path_track targetEnt = GetTrackNodeForward();
m_vecRelationTarget = targetEnt.GetOrigin() - GetOrigin();
} else {
m_vecRelationTarget = g_vec_null;
}
SaveVector(handle, "m_vecRelationTarget", m_vecRelationTarget);
}
void
func_tracktrain::Restore(string strKey, string strValue)
{
switch (strKey) {
case "m_flWait":
m_flWait = ReadFloat(strValue);
break;
case "m_flSpeed":
m_flSpeed = ReadFloat(strValue);
break;
case "m_flDamage":
m_flDamage = ReadFloat(strValue);
break;
case "m_flHeight":
m_flHeight = ReadFloat(strValue);
break;
case "m_flStartSpeed":
m_flStartSpeed = ReadFloat(strValue);
break;
case "m_flCurrentSpeed":
m_flCurrentSpeed = ReadFloat(strValue);
break;
case "m_flBank":
m_flBank = ReadFloat(strValue);
break;
case "m_strMoveSnd":
m_strMoveSnd = ReadString(strValue);
break;
case "m_strStopSnd":
m_strStopSnd = ReadString(strValue);
break;
case "m_flUseTime":
m_flUseTime = ReadFloat(strValue);
break;
case "m_iInputDirection":
m_iInputDirection = ReadInt(strValue);
break;
case "m_iOldInputDirection":
m_iOldInputDirection = ReadInt(strValue);
break;
case "m_iInputLevel":
m_iInputLevel = ReadInt(strValue);
break;
case "m_timerAngled":
m_timerAngled = ReadEntity(strValue);
break;
case "m_flTrainLength":
m_flTrainLength = ReadFloat(strValue);
break;
case "_m_bDriving":
_m_bDriving = ReadBool(strValue);
break;
case "m_vecRelationTarget":
m_vecRelationTarget = ReadVector(strValue);
break;
default:
super::Restore(strKey, strValue);
}
}
void
func_tracktrain::TransitionComplete(void)
{
/* targetting something, which was used as a reference point */
if (HasTriggerTarget() == true) {
path_track targetEnt = GetTrackNodeForward();
SetOrigin(targetEnt.GetOrigin() - m_vecRelationTarget);
}
}
void
func_tracktrain::SpawnKey(string strKey, string strValue)
{
switch (strKey) {
case "speed":
m_flSpeed = ReadFloat(strValue);
break;
case "startspeed":
m_flStartSpeed = stof(strValue);
break;
case "height":
m_flHeight = stof(strValue);
break;
case "dmg":
m_flDamage = stof(strValue);
break;
case "snd_move":
case "MoveSound":
m_strMoveSnd = strValue;
break;
case "snd_stop":
case "StopSound":
m_strStopSnd = strValue;
break;
case "wheels":
m_flTrainLength = 0; //ReadFloat(strValue);
break;
/* compatibility */
case "sounds":
case "movesnd":
m_strMoveSnd = sprintf("func_tracktrain.move_%i", stoi(strValue) + 1i);
break;
case "stopsnd":
m_strStopSnd = sprintf("func_tracktrain.stop_%i", stoi(strValue) + 1i);
break;
default:
super::SpawnKey(strKey, strValue);
}
}
void
func_tracktrain::Spawned(void)
{
super::Spawned();
if (m_strMoveSnd)
Sound_Precache(m_strMoveSnd);
if (m_strStopSnd)
Sound_Precache(m_strStopSnd);
Sound_Precache("func_tracktrain.stop_1");
}
void
func_tracktrain::Respawn(void)
{
/* legacy compat */
SetSolid(HasSpawnFlags(TRAIN_NONSOLID) ? SOLID_NOT : SOLID_BSP);
SetMovetype(MOVETYPE_PUSH);
SetModel(GetSpawnModel());
SetOrigin(GetSpawnOrigin());
m_flCurrentSpeed = m_flStartSpeed;
ScheduleThink(_AfterSpawn, 0.0f);
SetTriggerTarget(m_oldstrTarget);
PlayerUse = OnPlayerUse;
if (m_flTrainLength == 0.0f)
m_flTrainLength = 100.0f;
}
void
func_tracktrain::Blocked(entity eBlocker)
{
if (eBlocker.groundentity == this) {
return;
}
if (eBlocker == m_eDriver) {
return;
}
/* HACK: Make corpses gib instantly */
if (eBlocker.solid == SOLID_CORPSE) {
Damage_Apply(eBlocker, this, 500, 0, DMG_EXPLODE);
return;
}
if (eBlocker.takedamage != DAMAGE_NO) {
Damage_Apply(eBlocker, this, m_flDamage, 0, DMG_CRUSH);
} else {
remove(eBlocker);
}
}
void
func_tracktrain::_SoundMove(void)
{
if (m_strMoveSnd) {
StartSoundDef(m_strMoveSnd, CHAN_VOICE, true);
}
}
void
func_tracktrain::_SoundStop(void)
{
if (m_strStopSnd) {
StartSoundDef(m_strStopSnd, CHAN_BODY, true);
}
if (m_strMoveSnd) {
if (!m_strStopSnd) {
StartSoundDef("func_tracktrain.stop_1", CHAN_BODY, true);
}
StopSound(CHAN_VOICE, true);
}
}
void
func_tracktrain::PathMoveForward(void)
{
path_track eNode;
path_track previousNode;
float flTravelTime;
vector vecVelocity;
vector vecAngleDest;
vector vecDiff;
vector vecAngleDiff;
float travelSpeed;
float turnTime;
eNode = GetTrackNodeForward();
previousNode = GetTrackNodeBack();
if (!eNode) {
NSVehicle_Log("No node.");
return;
}
/* determine current speed */
if (m_iInputLevel > 0) {
travelSpeed = (m_flCurrentSpeed * (float)m_iInputLevel) / 2.0f;
} else {
travelSpeed = m_flCurrentSpeed * 1.25f; /* HACK: escape multi_manager rc in c0a0b */
}
vecVelocity = (GetPointBetweenNodes(1.0f) + [0,0,m_flHeight]) - GetTrainPivotPoint(false);
turnTime = (vlen(vecVelocity) / travelSpeed);
vecVelocity = (eNode.GetOrigin() + [0,0,m_flHeight]) - GetOrigin();
flTravelTime = (vlen(vecVelocity) / travelSpeed);
if (flTravelTime <= 0.0) {
NSVehicle_Log("^1func_tracktrain::^3PathMoveForward^7: Distance short, going next");
SetVelocity([1,1,1]);
ScheduleThink(_PathArrivedForward, 0.1f);
return;
}
NSVehicle_Log("^1func_tracktrain::^3PathMoveForward^7: Changing velocity from '%v' to '%v'", GetVelocity(), vecVelocity);
SetVelocity(vecVelocity * (1 / flTravelTime));
if (_m_bDriving == false) {
_SoundMove();
}
/* the direction we're aiming for */
vecDiff = GetOrigin() - (eNode.GetOrigin() + [0, 0, m_flHeight]);
vecAngleDest = vectoangles(vecDiff);
vecAngleDiff = Math_AngleDiff(vecAngleDest, angles);
vecAngleDiff[2] = 0;
if (vecAngleDiff[0] > 180)
vecAngleDiff[0] -= 180;
if (vecAngleDiff[0] < -180)
vecAngleDiff[0] += 180;
if (vecAngleDiff[1] > 180)
vecAngleDiff[1] -= 180;
if (vecAngleDiff[1] < -180)
vecAngleDiff[1] += 180;
if (IgnoresPitch() == true) {
vecAngleDiff[0] = 0;
vecAngleDest[0] = 0;
}
NSVehicle_Log("^1func_tracktrain::^3PathMoveBack^7: angleDelta: '%v'", vecAngleDiff);
if (vecAngleDiff[1] == 0)
SetAngles(vecAngleDest);
else
SetAngularVelocity(vecAngleDiff * (1 / turnTime));
if (!eNode)
SetAngularVelocity([0,0,0]);
_m_bDriving = true;
ScheduleThink(_PathArrivedForward, flTravelTime);
m_timerAngled = m_timerAngled.ScheduleTimer(this, _AngleDone, turnTime, false);
}
void
func_tracktrain::_AngleDone(void)
{
SetAngularVelocity(g_vec_null);
}
void
func_tracktrain::PathMoveBack(void)
{
path_track eNode;
float flTravelTime;
vector vecVelocity;
vector vecAngleDest;
vector vecDiff;
vector vecAngleDiff;
float travelSpeed;
float turnTime;
/* grab the node that's pointing at the current target (back) */
eNode = (path_track)GetTrackNodeBack();
/* None, abort */
if (!eNode) {
NSVehicle_Log("No node.\n");
return;
}
/* determine current speed */
if (m_iInputLevel < 0) {
travelSpeed = (m_flCurrentSpeed * (float)-m_iInputLevel) / 2.0f;
} else {
travelSpeed = m_flCurrentSpeed;
}
vecVelocity = (GetPointBetweenNodes(0.15f) + [0,0,m_flHeight]) - GetTrainPivotPoint(true);
turnTime = (vlen(vecVelocity) / travelSpeed);
vecVelocity = (eNode.GetOrigin() + [0,0,m_flHeight]) - GetOrigin();
flTravelTime = (vlen(vecVelocity) / travelSpeed);
if (flTravelTime <= 0.0) {
NSVehicle_Log("^Distance short, teleporting next.");
ScheduleThink(_PathArrivedBack, 0.0f);
SetVelocity([1,1,1]);
return;
}
NSVehicle_Log("Changing velocity from '%v' to '%v'", GetVelocity(), vecVelocity);
SetVelocity(vecVelocity * (1 / flTravelTime));
_SoundMove();
/* the direction we're aiming for */
vecDiff = (eNode.GetOrigin() + [0, 0, m_flHeight]) - GetOrigin();
vecAngleDest = vectoangles(vecDiff);
vecAngleDiff = Math_AngleDiff(vecAngleDest, angles);
vecAngleDiff[2] = 0;
if (vecAngleDiff[0] > 180)
vecAngleDiff[0] -= 180;
if (vecAngleDiff[0] < -180)
vecAngleDiff[0] += 180;
if (vecAngleDiff[1] > 180)
vecAngleDiff[1] -= 180;
if (vecAngleDiff[1] < -180)
vecAngleDiff[1] += 180;
if (IgnoresPitch() == true) {
vecAngleDiff[0] = 0;
vecAngleDest[0] = 0;
}
NSVehicle_Log("^1func_tracktrain::^3PathMoveBack^7: angleDelta: %v", vecAngleDiff);
if (vecAngleDiff[1] == 0)
SetAngles(vecAngleDest);
else
SetAngularVelocity(vecAngleDiff * (1 / turnTime));
if (!eNode)
SetAngularVelocity([0,0,0]);
_m_bDriving = true;
ScheduleThink(_PathArrivedBack, flTravelTime);
m_timerAngled = m_timerAngled.ScheduleTimer(this, _AngleDone, turnTime, false);
}
void
func_tracktrain::PathDone(void)
{
path_track eNode;
eNode = (path_track)find(world, ::targetname, target);
if (!eNode) {
error("Node we were targetting no longer exists.");
return;
}
NSVehicle_Log("func_tracktrain (%s): Touched base with path_track %S", targetname, target);
/* fire the path_tracks' target */
eNode.PathPassTrigger(this, TRIG_TOGGLE);
}
void
func_tracktrain::_PathArrivedForward(void)
{
path_track eNode;
/* train has been disabled. */
if (HasTriggerTarget() == false) {
return;
}
/* get what's in front of us. */
eNode = (path_track)GetTrackNodeForward();
/* we have to check this _after_ moving... */
if (eNode.DisablesTrain() == true) {
/* first clear velocity, in case our trigger targets our train */
PathClear();
eNode.PathEndTrigger(this, TRIG_TOGGLE);
target = __NULL__; /* makes the train inaccessible */
return;
}
/* we have nothing. */
if (!eNode) {
PathClear();
return;
}
NSVehicle_Log("^1func_tracktrain::^3_PathArrivedForward^7: Going to target %S (%s)", target, eNode.classname);
SetOrigin((eNode.GetOrigin()) + [0,0,m_flHeight]);
PathDone();
if (m_iInputLevel == 0) {
/* if speed is 0, retain current speed */
if (eNode.m_flSpeed > 0) {
m_flCurrentSpeed = eNode.m_flSpeed;
} else {
m_flCurrentSpeed = m_flSpeed;
}
}
m_flWait = eNode.m_flWait;
NSVehicle_Log("^1func_tracktrain::^3_PathArrivedForward^7: Target changed from %S to %S", target, eNode.GetPathTarget());
target = eNode.GetPathTarget();
if (!target || target == eNode.targetname) {
/* first clear velocity, in case our trigger targets our train */
PathClear();
eNode.PathEndTrigger(this, TRIG_TOGGLE);
return;
}
/* warp */
if (eNode.HasSpawnFlags(PC_TELEPORT)) {
SetOrigin((eNode.GetOrigin()) + [0,0,m_flHeight]);
}
/* stop until triggered again */
if (eNode.HasSpawnFlags(PC_WAIT)) {
PathClear();
return;
}
PathMoveForward();
}
void
func_tracktrain::_PathArrivedBack(void)
{
path_track eNode;
/* train has been disabled */
if (HasTriggerTarget() == false) {
return;
}
/* what's the node 'behind' us? */
eNode = (path_track)GetTrackNodeBack();
/* we have none... clear velocity */
if (!eNode) {
PathClear();
return;
}
NSVehicle_Log("^1func_tracktrain::^3_PathArrivedBack^7: Going to target %S (%s)", target, eNode.classname);
SetOrigin((eNode.GetOrigin()) + [0,0,m_flHeight]);
PathDone();
/* if speed is 0, retain current speed */
if (m_iInputLevel == 0) {
if (eNode.m_flSpeed > 0)
m_flCurrentSpeed = eNode.m_flSpeed;
else
m_flCurrentSpeed = m_flSpeed;
}
m_flWait = eNode.m_flWait;
NSVehicle_Log("^1func_tracktrain::^3_PathArrivedBack^7: Target changed from %S to %S", target, eNode.targetname);
target = eNode.targetname;
/* warp */
if (eNode.HasSpawnFlags(PC_TELEPORT)) {
SetOrigin((eNode.GetOrigin()) + [0,0,m_flHeight]);
}
/* stop until triggered again */
if (eNode.HasSpawnFlags(PC_WAIT)) {
PathClear();
return;
}
PathMoveBack();
/* we have to check this _after_ moving... */
if (eNode.DisablesTrain() == true) {
target = __NULL__; /* makes the train inaccessible */
}
}
void
func_tracktrain::PathClear(void)
{
if (vlen(velocity) >= 1.0f) {
_SoundStop();
}
_m_bDriving = false;
ClearVelocity();
ReleaseThink();
}
/* TODO: Handle state? */
void
func_tracktrain::Trigger(entity act, triggermode_t state)
{
switch (state) {
case TRIG_ON:
PathMoveForward();
break;
case TRIG_OFF:
PathClear();
break;
case TRIG_TOGGLE:
default:
if (_m_bDriving == false)
PathMoveForward();
else {
PathClear();
}
}
}
void
func_tracktrain::_AfterSpawn(void)
{
path_track targetNode;
path_track nodeAhead;
/* get the first target */
targetNode = (path_track)GetTrackNodeForward();
/* unpossible */
if (!targetNode || HasTriggerTarget() == false) {
NSVehicle_Log("^1func_tracktrain::^3_AfterSpawn^7: %d has no valid target (%S).", num_for_edict(this), GetTriggerTarget());
return;
}
NSVehicle_Log("^1func_tracktrain::^3_AfterSpawn^7: Start at target %S (%s)", target, targetNode.classname);
/* teleport to the first target for starters. */
SetOrigin((targetNode.GetOrigin()) + [0,0,m_flHeight]);
PathDone();
/* face the train towards the next target. while it may be tempting to use
GetPathTargetEntity() here, that node may be disabled. so look manually. */
nodeAhead = (path_track)find(world, ::targetname, targetNode.target);
/* node found. */
if (nodeAhead) {
vector newAngle = vectoangles(targetNode.GetOrigin() - nodeAhead.GetOrigin());
if (IgnoresPitch() == true) {
newAngle[0] = 0;
} else {
newAngle[0] = -Math_FixDelta(newAngle[0]);
}
SetAngles(newAngle);
}
/* if speed is 0, retain current speed */
if (targetNode.m_flSpeed > 0) {
m_flCurrentSpeed = targetNode.m_flSpeed;
} else {
m_flCurrentSpeed = m_flSpeed;
}
m_flWait = targetNode.m_flWait;
target = targetNode.target;
/* warp */
if (targetNode.HasSpawnFlags(PC_TELEPORT)) {
SetOrigin((targetNode.GetOrigin()) + [0,0,m_flHeight]);
}
/* stop until triggered again */
if (targetNode.HasSpawnFlags(PC_WAIT)) {
_SoundStop();
return;
}
/* auto start */
if (!targetname || m_flStartSpeed > 0.0f) {
PathMoveForward();
}
}
bool
func_tracktrain::ControlCheck(entity toCheck)
{
entity controller = __NULL__;
/* this train can never be used by a player. */
if (HasSpawnFlags(TRAIN_NOUSER)) {
return (false);
}
/* no targetname = no control area possible, auto true */
if (HasTargetname() == false) {
return (true);
}
/* find the traincontrol responsible for us */
for (entity f = world;(f = find(f, ::target, targetname));) {
if (f.classname == "func_traincontrols") {
controller = f;
break;
}
}
/* are we inside the controller? */
if (toCheck.absmin[0] > controller.absmax[0] ||
toCheck.absmin[1] > controller.absmax[1] ||
toCheck.absmin[2] > controller.absmax[2] ||
toCheck.absmax[0] < controller.absmin[0] ||
toCheck.absmax[1] < controller.absmin[1] ||
toCheck.absmax[2] < controller.absmin[2]) {
return (false);
}
return (true);
}
void
func_tracktrain::OnPlayerUse(void)
{
/* it's not been a second */
if (m_flUseTime > time)
{
return;
}
/* we're already the driver, so leave */
if (m_eDriver == eActivator) {
PlayerLeave((NSClientPlayer)eActivator);
} else if (!m_eDriver) {
/* check if they're allowed to control it */
if (ControlCheck(eActivator) == false) {
return;
}
PlayerEnter((NSClientPlayer)eActivator);
}
m_flUseTime = time + 1.0f;
}
void
func_tracktrain::EvaluateEntity(void)
{
angles[0] = Math_FixDelta(angles[0]);
angles[1] = Math_FixDelta(angles[1]);
angles[2] = Math_FixDelta(angles[2]);
PlayerAlign();
EVALUATE_FIELD(modelindex, FNCTKTRNET_MODELINDEX)
EVALUATE_FIELD(origin, FNCTKTRNET_ORIGIN)
EVALUATE_FIELD(angles, FNCTKTRNET_ANGLES)
EVALUATE_FIELD(velocity, FNCTKTRNET_VELOCITY)
EVALUATE_FIELD(flags, FNCTKTRNET_FLAGS)
EVALUATE_FIELD(solid, FNCTKTRNET_SOLIDMOVETYPE)
EVALUATE_FIELD(movetype, FNCTKTRNET_SOLIDMOVETYPE)
EVALUATE_FIELD(mins, FNCTKTRNET_SOLIDMOVETYPE)
EVALUATE_FIELD(maxs, FNCTKTRNET_SOLIDMOVETYPE)
}
float
func_tracktrain::SendEntity(entity ePEnt, float flChanged)
{
if (!modelindex) {
return (false);
}
if (clienttype(ePEnt) != CLIENTTYPE_REAL) {
return (false);
}
WriteByte(MSG_ENTITY, ENT_TRACKTRAIN);
WriteFloat(MSG_ENTITY, flChanged);
SENDENTITY_SHORT(modelindex, FNCTKTRNET_MODELINDEX)
SENDENTITY_COORD(origin[0], FNCTKTRNET_ORIGIN)
SENDENTITY_COORD(origin[1], FNCTKTRNET_ORIGIN)
SENDENTITY_COORD(origin[2], FNCTKTRNET_ORIGIN)
SENDENTITY_ANGLE(angles[0], FNCTKTRNET_ANGLES)
SENDENTITY_ANGLE(angles[1], FNCTKTRNET_ANGLES)
SENDENTITY_ANGLE(angles[2], FNCTKTRNET_ANGLES)
SENDENTITY_COORD(velocity[0], FNCTKTRNET_VELOCITY)
SENDENTITY_COORD(velocity[1], FNCTKTRNET_VELOCITY)
SENDENTITY_COORD(velocity[2], FNCTKTRNET_VELOCITY)
SENDENTITY_INT(flags, FNCTKTRNET_FLAGS)
SENDENTITY_BYTE(solid, FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_BYTE(movetype, FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_INT(flags, FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_COORD(mins[0], FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_COORD(mins[1], FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_COORD(mins[2], FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_COORD(maxs[0], FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_COORD(maxs[1], FNCTKTRNET_SOLIDMOVETYPE)
SENDENTITY_COORD(maxs[2], FNCTKTRNET_SOLIDMOVETYPE)
return (true);
}
#endif
#ifdef CLIENT
void
func_tracktrain::ReceiveEntity(float flNew, float flChanged)
{
READENTITY_SHORT(modelindex, FNCTKTRNET_MODELINDEX)
READENTITY_COORD(origin[0], FNCTKTRNET_ORIGIN)
READENTITY_COORD(origin[1], FNCTKTRNET_ORIGIN)
READENTITY_COORD(origin[2], FNCTKTRNET_ORIGIN)
READENTITY_ANGLE(angles[0], FNCTKTRNET_ANGLES)
READENTITY_ANGLE(angles[1], FNCTKTRNET_ANGLES)
READENTITY_ANGLE(angles[2], FNCTKTRNET_ANGLES)
READENTITY_COORD(velocity[0], FNCTKTRNET_VELOCITY)
READENTITY_COORD(velocity[1], FNCTKTRNET_VELOCITY)
READENTITY_COORD(velocity[2], FNCTKTRNET_VELOCITY)
READENTITY_INT(flags, FNCTKTRNET_FLAGS)
READENTITY_BYTE(solid, FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_BYTE(movetype, FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_INT(flags, FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_COORD(mins[0], FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_COORD(mins[1], FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_COORD(mins[2], FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_COORD(maxs[0], FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_COORD(maxs[1], FNCTKTRNET_SOLIDMOVETYPE)
READENTITY_COORD(maxs[2], FNCTKTRNET_SOLIDMOVETYPE)
if (flChanged & FNCTKTRNET_SOLIDMOVETYPE) {
setsize(this, mins, maxs);
}
if (flChanged & FNCTKTRNET_MODELINDEX) {
//setsize( this, [-50,-50,0], [50,50,64]);
}
if (flChanged & FNCTKTRNET_ORIGIN) {
setorigin(this, origin);
makevectors(angles);
}
if (flNew) {
drawmask = MASK_ENGINE;
SetSolid(SOLID_BSP);
}
PredictPreFrame();
}
void
func_tracktrain::UpdateView(void)
{
}
void
func_tracktrain::PredictPreFrame(void)
{
SAVE_STATE(modelindex)
SAVE_STATE(origin)
SAVE_STATE(angles)
SAVE_STATE(velocity)
SAVE_STATE(flags)
}
void
func_tracktrain::PredictPostFrame(void)
{
ROLL_BACK(modelindex)
ROLL_BACK(angles)
ROLL_BACK(origin)
ROLL_BACK(velocity)
ROLL_BACK(flags)
}
#endif
void
func_tracktrain::PlayerInput(void)
{
#ifdef SERVER
/* we tap forward/back to switch directions. */
if (input_movevalues[0] > 100) {
m_iInputDirection = 1i;
} else if (input_movevalues[0] < -100) {
m_iInputDirection = -1i;
} else if (input_movevalues[0] == 0) {
m_iInputDirection = 0i;
}
/* if the movement keys have been pressed differently... */
if (m_iInputDirection != m_iOldInputDirection) {
/* increase the level if the direction is positive */
if (m_iInputDirection == 1i) {
m_iInputLevel += 1i;
} else if (m_iInputDirection == -1i) {
m_iInputLevel += -1i;
}
/* max speed multiplier is 4 in either dir */
if (m_iInputLevel > 4i) {
m_iInputLevel = 4i;
} else if (m_iInputLevel < -4i) {
m_iInputLevel = -4i;
}
m_iOldInputDirection = m_iInputDirection;
}
/* level differs, adjust speed */
if (m_iOldInputLevel != m_iInputLevel) {
m_iOldInputLevel = m_iInputLevel;
if (m_iInputLevel > 0i) {
PathMoveForward();
} else if (m_iInputLevel < 0i) {
PathMoveBack();
} else {
PathClear();
}
}
/* exit vehicle */
if (input_buttons & INPUT_BUTTON5) {
if (m_flUseTime > time)
{
return;
}
eActivator = m_eDriver;
OnPlayerUse();
input_buttons &= ~INPUT_BUTTON5;
}
#endif
}