func_vehicle: It was 20 years ago today, this entity came out to play. It got everyone to smile, at least for a little while. Happy birthday Counter-Strike 7.0, introducing: func_vehicle!

This commit is contained in:
Marco Cawthorne 2020-08-26 22:31:49 +02:00
parent 641f68c6be
commit f9241d86ea
4 changed files with 680 additions and 0 deletions

View file

@ -11,6 +11,7 @@ server/basetrigger.cpp
server/basemonster.cpp
server/basenpc.cpp
server/basephysics.cpp
server/basevehicle.cpp
server/button_target.cpp
server/ambient_generic.cpp
server/cycler.cpp
@ -44,6 +45,8 @@ server/func_wall_toggle.cpp
server/func_conveyor.cpp
server/func_rotating.cpp
server/func_pendulum.cpp
server/func_vehicle.cpp
server/func_vehiclecontrols.cpp
server/light.cpp
server/stubs.cpp
server/infodecal.cpp

View file

@ -0,0 +1,72 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* 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.
*/
class CBaseVehicle:CBaseEntity
{
entity m_eDriver;
void(void) CBaseVehicle;
vector m_vecPlayerPos;
virtual void(void) PlayerAlign;
virtual void(base_player) PlayerEnter;
virtual void(base_player) PlayerLeave;
};
void
CBaseVehicle::PlayerAlign(void)
{
vector vecPlayerPos;
if (!m_eDriver)
return;
makevectors(angles);
vecPlayerPos = origin + v_forward * m_vecPlayerPos[0];
vecPlayerPos += v_right * m_vecPlayerPos[1];
vecPlayerPos += v_up * m_vecPlayerPos[2];
setorigin(m_eDriver, vecPlayerPos);
}
void
CBaseVehicle::PlayerEnter(base_player pl)
{
vector offs;
/* cache the position */
offs = pl.origin - origin;
makevectors(angles);
m_vecPlayerPos[0] = dotproduct(offs, v_forward);
m_vecPlayerPos[1] = dotproduct(offs, v_right);
m_vecPlayerPos[2] = dotproduct(offs, v_up);
pl.movetype = MOVETYPE_NOCLIP;
m_eDriver = (entity)pl;
}
void
CBaseVehicle::PlayerLeave(base_player pl)
{
pl.movetype = MOVETYPE_WALK;
m_eDriver = __NULL__;
}
void
CBaseVehicle::CBaseVehicle(void)
{
CBaseEntity::CBaseEntity();
}

View file

@ -0,0 +1,566 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* 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.
*/
/*QUAKED func_vehicle (0 .5 .8) ?
"targetname" Name
"target" Name of the first path_track/corner (See Notes)
"acceleration" Acceleration multiplier
"speed" Top-speed of the vehicle in q-units per second
"height" Wheel-height in units
"width" Width of the vehicle, used to calculate the wheels
"length" Length of the vehicle, used to calculate the wheels
"bouncefactor" Multiplier for the bouncyness of the vehicle
"skidspeed" At which speed the vehicle starts skidding
"traction" Multiplier for the traction affecting the vehicle
"breakfactor" Multiplier for the breaking mechanics
"steerfactor" Multiplier for the steering speed
"straightenfactor" Multiplier for affecting the straightening mechanism
"gravitydir" Normalized vector setting the direction of gravity
Unimplemented:
"sounds" A sound-set to use
"volume" Volume at which said sounds play at (from 0-10)
"dmg" Damage inflicted upon entities when blocked
Primitive brush-based vehicle entity.
The vehicle's position is set via the 'target' key, which sets the first
path_track/corner. The vehicle is then teleported to the 'target' but
it stays at the same vertical position as originally placed.
The angle is calculated by aiming the 'target' path_track/corner entity
to its own 'target' entity. So yes, you need two of these path_track/corner
entities.
*/
enumflags
{
FUNCVEH_NOPITCH,
FUNCVEH_NOUSER,
FUNCVEH_UNUSED,
FUNCVEH_PASSABLE,
FUNCVEH_FWDRIVE,
FUNCVEH_RWDRIVE
};
class func_vehicle_wheel
{
void() func_vehicle_wheel;
virtual void(void) Move;
virtual void(vector) Bounce;
virtual void(float, float m_flTurn) Accel;
virtual void(float) Physics;
};
class func_vehicle:CBaseVehicle
{
/* map-entity fields */
float m_flBounceFactor;
float m_flAcceleration;
float m_flSkidSpeed;
float m_flTraction;
float m_flBreakFactor;
float m_flSteerFactor;
float m_flStraightenFactor;
vector m_vecGravityDir;
func_vehicle_wheel m_wlFL;
func_vehicle_wheel m_wlFR;
func_vehicle_wheel m_wlBL;
func_vehicle_wheel m_wlBR;
vector m_vecControlMins;
vector m_vecControlMaxs;
float m_flHeight;
float m_flWidth;
float m_flLength;
float m_flTurn;
void(void) func_vehicle;
virtual void(void) customphysics;
virtual void(void) Respawn;
virtual void(void) Realign;
virtual void(void) PlayerUse;
};
void
func_vehicle_wheel::Bounce(vector normal)
{
func_vehicle vehParent;
vehParent = (func_vehicle)owner;
velocity -= (velocity * normal) * normal * vehParent.m_flBounceFactor;
}
void
func_vehicle_wheel::Move(void)
{
vector vecDest;
vector vecSavedNormal;
float flStepped;
float flMovetime;
int i;
/* have a few attempts */
for (i = 3, flMovetime = frametime; flMovetime > 0 && i; i--) {
vecDest = origin + (velocity * flMovetime);
tracebox(origin, mins, maxs, vecDest, MOVE_NOMONSTERS, this);
if (trace_startsolid) {
continue;
}
origin = trace_endpos;
if (trace_fraction < 1) {
vecSavedNormal = trace_plane_normal;
flMovetime -= flMovetime * trace_fraction;
if (flMovetime) {
float roof_fraction;
vector roof_plane_normal;
/* step up if we can */
trace_endpos = origin;
trace_endpos[2] += 4;
tracebox(origin, mins, maxs, trace_endpos, MOVE_NOMONSTERS, this);
flStepped = trace_endpos[2] - origin[2];
roof_fraction = trace_fraction;
roof_plane_normal = trace_plane_normal;
vecDest = trace_endpos + velocity * flMovetime;
/* only horizontally */
vecDest[2] = trace_endpos[2];
/* move forwards */
tracebox(trace_endpos, mins, maxs, vecDest, MOVE_NOMONSTERS, this);
/* if we got anywhere, make this raised-step move count */
if (trace_fraction != 0) {
float fwfrac;
vector fwplane;
fwfrac = trace_fraction;
fwplane = trace_plane_normal;
/* move down */
vecDest = trace_endpos;
vecDest[2] -= flStepped + 1;
tracebox(trace_endpos, mins, maxs, vecDest, MOVE_NOMONSTERS, this);
if (trace_fraction < 1 && trace_plane_normal[2] > 0.7f) {
flMovetime -= flMovetime * fwfrac;
if (roof_fraction < 1) {
Bounce(roof_plane_normal);
}
/* FIXME: do we need velocity < 0? */
if (trace_fraction < 1 && self.velocity[2] < 0) {
Bounce(trace_plane_normal);
} else if (fwfrac < 1) {
Bounce(fwplane);
}
origin = trace_endpos;
continue;
}
}
}
/* stepping failed, assume crash? */
if (trace_ent == world) {
if (vlen(velocity) > 300) {
float impact;
impact = -dotproduct(trace_plane_normal, velocity);
int iImpactDamage = impact / 100;
}
}
Bounce(vecSavedNormal);
/* Physics_DoTouch(this, trace_ent); */
} else {
break;
}
}
}
void
func_vehicle_wheel::Accel(float flMoveTime, float m_flTurn)
{
func_vehicle vehParent;
entity eDriver;
float flTraction;
vector vecAngle;
vehParent = (func_vehicle)owner;
eDriver = vehParent.m_eDriver;
vecAngle = vehParent.angles;
makevectors(vecAngle);
if (m_flTurn) {
/* rotates v_forward */
rotatevectorsbyangle([ 0, m_flTurn * 50, 0]);
}
tracebox(origin, mins, maxs, origin - v_up, MOVE_NOMONSTERS, owner);
/* allow a range, for 1qu's worth of spare tyre pressure. or something */
flTraction = 1 - trace_fraction;
/* air friction, doubles up for general rolling friction, ish */
velocity *= 1 - flMoveTime * 0.1;
if (flTraction) {
if (eDriver) {
velocity -= v_forward * bound(-1, vehParent.m_eDriver.movement[0] / 400, 1) * vehParent.m_flAcceleration * flMoveTime * flTraction;
}
/* nuke sideways velocity. if a wheel is off the ground this probably
means that it'll be pushed further. players should try not to roll
too much. */
/* FIXME: push opposite wheel up slightly to model chassis momentum
not slowing as much as the wheel itself (zomg: race conditions!) */
velocity -= (velocity * v_right) * v_right * vehParent.m_flTraction * flMoveTime * flTraction;
if (eDriver.button2 || eDriver.movement[2] > 0) {
vector t;
/* empty cars are assumed to have their brakes on.
nuke forward velocity. if a wheel is off the ground this probably
means that it'll be pushed further. players should try not to
roll too much.
Note: really we ought to be applying some axel friction even
when not breaking, but we'll just depend on air friction for
that. */
velocity -= (velocity * v_forward) * v_forward * vehParent.m_flBreakFactor * flMoveTime * flTraction;
/* if break is on, nuke the final part of the velocity, so we can
become truely motionless.*/
t = velocity - velocity * dotproduct(velocity, vehParent.m_vecGravityDir);
if (vlen(t) < 15) {
velocity -= t;
}
/* don't bother with gravity if we're already on the ground and
breaking. this avoids weird slides. */
if (!trace_fraction && trace_plane_normal * vehParent.m_vecGravityDir < -0.7f) {
return;
}
}
}
/* apply gravity */
velocity += vehParent.m_vecGravityDir * flMoveTime * serverkeyfloat("phy_gravity") * trace_fraction;
}
void
func_vehicle_wheel::Physics(float turnrate)
{
vector owner_pos;
/* try to correct the wheel's position, in case it got stuck */
owner_pos = owner.origin + (owner.mins + owner.maxs) * 0.5f;
tracebox(owner_pos, mins, maxs, origin, MOVE_NOMONSTERS, owner);
setorigin(this, trace_endpos);
Accel(frametime * 0.5f, turnrate);
Move();
Accel(frametime * 0.5f, turnrate);
}
void
func_vehicle_wheel::func_vehicle_wheel(void)
{
hitcontentsmaski = CONTENTBIT_SOLID | CONTENTBIT_BODY;
mins = '-8 -8 -8';
maxs = '8 8 8';
}
void
func_vehicle::customphysics(void)
{
/* eject the dead */
if (m_eDriver && m_eDriver.health <= 0) {
PlayerLeave((base_player)m_eDriver);
}
if (m_eDriver) {
float y;
y = m_eDriver.movement[1];
y = bound(-200, y, 200) / 200;
y *= m_flSteerFactor;
if (y) {
if (y < 0 && m_flTurn < 0) {
m_flTurn = 0.0f;
} else if (y > 0 && m_flTurn > 0) {
m_flTurn = 0.0f;
} else {
m_flTurn = bound(-1, m_flTurn - y * frametime, 1);
}
} else {
/* straighten wheels forward over time */
if (m_flTurn < 0) {
m_flTurn = min(0, m_flTurn + frametime * m_flStraightenFactor);
} else if (m_flTurn > 0) {
m_flTurn = max(0, m_flTurn - frametime * m_flStraightenFactor);
}
}
m_eDriver.flags |= FL_FROZEN;
}
angles[0] = Math_FixDelta(angles[0]);
angles[1] = Math_FixDelta(angles[1]);
angles[2] = Math_FixDelta(angles[2]);
makevectors(angles);
setorigin(m_wlFR, origin + v_right * m_flWidth + v_forward * m_flLength);
setorigin(m_wlFL, origin - v_right * m_flWidth + v_forward * m_flLength);
setorigin(m_wlBR, origin + v_right * m_flWidth - v_forward * m_flLength);
setorigin(m_wlBL, origin - v_right * m_flWidth - v_forward * m_flLength);
if (spawnflags & FUNCVEH_FWDRIVE) {
m_wlFL.Physics(0);
m_wlFR.Physics(0);
m_wlBL.Physics(m_flTurn);
m_wlBR.Physics(m_flTurn);
} else if (spawnflags & FUNCVEH_RWDRIVE) {
m_wlFL.Physics(-m_flTurn);
m_wlFR.Physics(-m_flTurn);
m_wlBL.Physics(0);
m_wlBR.Physics(0);
} else {
m_wlFL.Physics(-m_flTurn);
m_wlFR.Physics(-m_flTurn);
m_wlBL.Physics(m_flTurn);
m_wlBR.Physics(m_flTurn);
}
velocity = m_wlFL.velocity;
velocity += m_wlFR.velocity;
velocity += m_wlBL.velocity;
velocity += m_wlBR.velocity;
velocity *= 0.25f;
v_right = (m_wlFR.origin - m_wlFL.origin);
v_right += (m_wlBR.origin - m_wlBL.origin);
v_forward = (m_wlFL.origin + m_wlFR.origin);
v_forward -= (m_wlBL.origin + m_wlBR.origin);
v_up = -crossproduct(v_forward, v_right);
angles = vectoangles(v_forward, v_up);
/* figure out the new chassis position */
vector new_origin;
new_origin = m_wlFL.origin;
new_origin += m_wlFR.origin;
new_origin += m_wlBL.origin;
new_origin += m_wlBR.origin;
new_origin *= 0.25f;
setorigin(this, new_origin);
PlayerAlign();
/* support for think/nextthink */
if (think && nextthink > 0.0f) {
if (nextthink < time) {
nextthink = 0.0f;
think();
}
}
}
void
func_vehicle::PlayerUse(void)
{
vector matrix;
vector offs;
offs = eActivator.origin - origin;
makevectors(angles);
matrix[0] = dotproduct(offs, v_forward);
matrix[1] = -dotproduct(offs, v_right);
matrix[2] = dotproduct(offs, v_up);
if not (matrix[0] >= m_vecControlMins[0] && matrix[0] <= m_vecControlMaxs[0])
return;
if not (matrix[1] >= m_vecControlMins[1] && matrix[1] <= m_vecControlMaxs[1])
return;
if not (matrix[2] >= m_vecControlMins[2] && matrix[2] <= m_vecControlMaxs[2])
return;
if (m_eDriver == eActivator) {
PlayerLeave((base_player)eActivator);
} else if (!m_eDriver) {
PlayerEnter((base_player)eActivator);
}
}
void
func_vehicle::Realign(void)
{
entity t;
entity f;
CBaseEntity first, second;
string strFirst, strSecond;
first = second = t = f = __NULL__;
for (f = world; (f = find(f, CBaseEntity::m_strTarget, m_strTargetName));) {
/* we found the right entity */
if (f.classname == "func_vehiclecontrols") {
t = f;
}
}
if (t) {
vector offs;
offs = t.origin - origin;
m_vecControlMins = t.mins + offs;
m_vecControlMaxs = t.maxs + offs;
}
/* we rotate and position ourselves after the first path_track/corner */
strFirst = m_strTarget;
for (f = world; (f = find(f, CBaseEntity::m_strTargetName, strFirst));) {
/* we found the right entity */
if (f.classname == "path_track" || f.classname == "path_corner") {
first = (CBaseEntity)f;
}
}
/* now get the second one... */
strSecond = first.m_strTarget;
for (f = world; (f = find(f, CBaseEntity::m_strTargetName, strSecond));) {
/* we found the right entity */
if (f.classname == "path_track" || f.classname == "path_corner") {
second = (CBaseEntity)f;
}
}
if (t) {
vector end_pos;
first = (CBaseEntity)first;
second = (CBaseEntity)second;
angles = vectoangles(first.origin - second.origin);
end_pos = first.origin;
end_pos[2] = m_oldOrigin[2];
setorigin(this, end_pos);
}
}
void
func_vehicle::Respawn(void)
{
movetype = MOVETYPE_PUSH;
solid = SOLID_BSP;
SetModel(m_oldModel);
SetOrigin(m_oldOrigin);
owner = m_eDriver = __NULL__;
think = Realign;
nextthink = time + 0.1f;
m_wlFL.velocity =
m_wlFR.velocity =
m_wlBL.velocity =
m_wlBR.velocity =
velocity = [0,0,0];
}
void
func_vehicle::func_vehicle(void)
{
m_flBounceFactor = 1.0f;
m_flAcceleration = 200.0f;
m_flSkidSpeed = 256.0f;
m_flTraction = 1.0f;
m_flBreakFactor = 2.0f;
m_flSteerFactor = 1.0f;
m_flStraightenFactor = 1.0f;
m_vecGravityDir = [0,0,-1];
for (int i = 1; i < (tokenize(__fullspawndata) - 1); i += 2) {
switch (argv(i)) {
case "acceleration":
// TODO
break;
case "speed":
m_flAcceleration = stof(argv(i+1));
break;
case "height":
m_flHeight = stof(argv(i+1));
break;
case "width":
m_flWidth = stof(argv(i+1)) / 2;
break;
case "length":
m_flLength = stof(argv(i+1)) / 2;
break;
case "bouncefactor":
m_flBounceFactor = stof(argv(i+1));
break;
case "skidspeed":
m_flSkidSpeed = stof(argv(i+1));
break;
case "traction":
m_flTraction = stof(argv(i+1));
break;
case "breakfactor":
m_flBreakFactor = stof(argv(i+1));
break;
case "steerfactor":
m_flSteerFactor = stof(argv(i+1));
break;
case "straightenfactor":
m_flStraightenFactor = stof(argv(i+1));
break;
case "gravitydir":
m_vecGravityDir = stov(argv(i+1));
break;
case "sounds":
// TODO
break;
case "volume":
// TODO
break;
case "dmg":
// TODO
break;
default:
break;
}
}
m_wlFL = spawn(func_vehicle_wheel);
m_wlFR = spawn(func_vehicle_wheel);
m_wlBL = spawn(func_vehicle_wheel);
m_wlBR = spawn(func_vehicle_wheel);
m_wlFL.owner = m_wlFR.owner = m_wlBL.owner = m_wlBR.owner = this;
if (m_flHeight) {
m_wlFL.mins[2] = m_flHeight * -1;
m_wlFR.mins[2] = m_flHeight * -1;
m_wlBL.mins[2] = m_flHeight * -1;
m_wlBR.mins[2] = m_flHeight * -1;
}
CBaseVehicle::CBaseVehicle();
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2016-2020 Marco Hladik <marco@icculus.org>
*
* 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.
*/
/*QUAKED func_vehiclecontrols (0 .5 .8) ?
"targetname" Name
"target" Name of the func_vehicle to control
Brush that marks the usable region of a func_vehicle, in order
to gain control.
*/
class func_vehiclecontrols:CBaseTrigger
{
void(void) func_vehiclecontrols;
};
void
func_vehiclecontrols::func_vehiclecontrols(void)
{
precache_model(model);
SetModel(model);
SetOrigin(origin);
SetRenderMode(RM_TRIGGER);
SetSolid(SOLID_NOT);
CBaseTrigger::CBaseTrigger();
}