thirtyflightsofloving/game/g_vehicle.c
Knightmare66 d16b46e3cf Added re-implementation of func_plat2 and func_door2 from rogue to default Lazarus DLL.
Overhauled child entity movement in default Lazarus DLL.
Added bbox versions of various triggers to default Lazarus DLL.
Added level.maptype field to default Lazarus DLL.
Added entity class IDs to default Lazarus DLL.
Incremented savegame version for default Lazarus DLL.
2020-10-27 02:00:05 -04:00

511 lines
15 KiB
C

/*
===========================================================================
Copyright (C) 1997-2001 Id Software, Inc.
Copyright (C) 2000-2002 Mr. Hyde and Mad Dog
This file is part of Lazarus Quake 2 Mod source code.
Lazarus Quake 2 Mod source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Lazarus Quake 2 Mod source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Lazarus Quake 2 Mod source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "g_local.h"
#define RFAST -3
#define RMEDIUM -2
#define RSLOW -1
#define STOP 0
#define SLOW 1
#define MEDIUM 2
#define FAST 3
#define VEHICLE_BLOCK_STOPS 4
void func_vehicle_explode (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
{
vec3_t origin;
vec3_t chunkorigin;
vec3_t size;
int count;
int mass;
if (self->deathtarget)
{
self->target = self->deathtarget;
G_UseTargets (self, attacker);
}
// bmodel origins are (0 0 0), we need to adjust that here
VectorScale (self->size, 0.5, size);
VectorAdd (self->absmin, size, origin);
VectorCopy (origin, self->s.origin);
self->takedamage = DAMAGE_NO;
if (self->dmg)
T_RadiusDamage (self, attacker, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE, -0.5);
VectorSubtract (self->s.origin, inflictor->s.origin, self->velocity);
VectorNormalize (self->velocity);
VectorScale (self->velocity, 150, self->velocity);
// start chunks towards the center
VectorScale (size, 0.5, size);
mass = self->mass;
// big chunks
if (mass >= 100)
{
count = mass / 100;
if (count > 8)
count = 8;
while (count--)
{
chunkorigin[0] = origin[0] + crandom() * size[0];
chunkorigin[1] = origin[1] + crandom() * size[1];
chunkorigin[2] = origin[2] + crandom() * size[2];
ThrowDebris (self, "models/objects/debris1/tris.md2", 1, chunkorigin, 0, 0);
}
}
// small chunks
count = mass / 25;
if (count > 16)
count = 16;
while (count--)
{
chunkorigin[0] = origin[0] + crandom() * size[0];
chunkorigin[1] = origin[1] + crandom() * size[1];
chunkorigin[2] = origin[2] + crandom() * size[2];
ThrowDebris (self, "models/objects/debris2/tris.md2", 2, chunkorigin, 0, 0);
}
if (self->dmg)
BecomeExplosion1 (self);
else
G_FreeEdict (self);
}
void vehicle_blocked (edict_t *self, edict_t *other)
{
edict_t *attacker;
if ((self->spawnflags & VEHICLE_BLOCK_STOPS) || (other == world))
{
VectorClear(self->velocity);
VectorClear(self->avelocity);
self->moveinfo.current_speed = 0;
gi.linkentity(self);
return;
}
if (other->takedamage)
{
if (self->teammaster->owner)
attacker = self->teammaster->owner;
else
attacker = self->owner;
T_Damage (other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH);
}
else
{
VectorClear(self->velocity);
VectorClear(self->avelocity);
self->moveinfo.current_speed = 0;
self->moveinfo.state = STOP;
gi.linkentity(self);
}
if (!(other->svflags & SVF_MONSTER) && (!other->client))
{
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, 0, MOD_CRUSH);
if (other)
BecomeExplosion1 (other);
return;
}
}
// Not needed, because collisions are effectively prevented by the vehicle physics in
// g_phys.c.
void vehicle_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf)
{
vec3_t dir, v;
vec3_t new_origin, new_velocity;
vec_t points;
vec_t vspeed, mspeed;
vec_t knockback;
vec3_t end;
trace_t tr;
if (other == world || (self->spawnflags & VEHICLE_BLOCK_STOPS) )
{
VectorClear(self->velocity);
VectorClear(self->avelocity);
self->moveinfo.current_speed = 0;
gi.linkentity(self);
}
if (!self->owner) return; // if vehicle isn't being driven, it can't hurt anybody
if (other == self->owner) return; // can't hurt the driver
if (other->takedamage == DAMAGE_NO) return;
// we damage func_explosives elsewhere. About all that's left to hurt are
// players and monsters
if (!other->client && !(other->svflags & SVF_MONSTER)) return;
vspeed = VectorLength(self->velocity);
if (!vspeed) return;
VectorSubtract(other->s.origin,self->s.origin,dir);
dir[2] = 0;
VectorNormalize(dir);
VectorCopy(self->velocity,v);
VectorNormalize(v);
// damage and knockback are proportional to square of velocity * mass of vehicle.
// Lessee... with a mass=2000 vehicle traveling 400 units/sec, give 100 points
// damage and a velocity of 160 to a 200 mass monster.
vspeed *= DotProduct(dir,v);
mspeed = VectorLength(other->velocity) * DotProduct(dir,v);
vspeed -= mspeed;
if (vspeed <= 0.0) return;
// for speed < 200, don't do damage but move monster
if (vspeed < 200) {
if (other->mass > self->mass) vspeed *= (float)self->mass/other->mass;
VectorMA(other->velocity,vspeed,dir,new_velocity);
VectorMA(other->s.origin,FRAMETIME,new_velocity,new_origin);
new_origin[2] += 2;
// if the move would place the monster in a solid, make him go splat
VectorCopy(new_origin,end);
end[2] -= 1;
tr = gi.trace(new_origin,other->mins,other->maxs,end,self,CONTENTS_SOLID);
if (tr.startsolid)
// splat
T_Damage (other, self, self->owner, dir, self->s.origin, vec3_origin,
other->health - other->gib_health + 1, 0, 0, MOD_VEHICLE);
else
{
// go ahead and move the bastard
VectorCopy(new_velocity,other->velocity);
VectorCopy(new_origin,other->s.origin);
gi.linkentity(other);
}
return;
}
if (other->damage_debounce_time > level.time) return;
other->damage_debounce_time = level.time + 0.2;
points = 100. * (self->mass/2000.0f * vspeed*vspeed/160000);
// knockback takes too long to take effect. If we can move him w/o throwing him
// into a solid, do so NOW
dir[2] = 0.2; // make knockback slightly upward
VectorMA(other->velocity,vspeed,dir,new_velocity);
VectorMA(other->s.origin,FRAMETIME,new_velocity,new_origin);
if (gi.pointcontents(new_origin) & CONTENTS_SOLID)
knockback = (160.0f/500.0f) * 200.0f * (self->mass/2000.0f * vspeed*vspeed/160000);
else {
knockback = 0;
VectorCopy(new_velocity,other->velocity);
VectorCopy(new_origin, other->s.origin);
}
T_Damage (other, self, self->owner, dir, self->s.origin, vec3_origin,
(int)points, (int)knockback, 0, MOD_VEHICLE);
gi.linkentity(other);
}
void vehicle_disengage (edict_t *vehicle)
{
edict_t *driver;
vec3_t forward, left, f1, l1;
driver = vehicle->owner;
if (!driver) return;
AngleVectors(vehicle->s.angles, forward, left, NULL);
VectorCopy(vehicle->velocity,driver->velocity);
VectorScale(forward,vehicle->move_origin[0],f1);
VectorScale(left,-vehicle->move_origin[1],l1);
VectorAdd(vehicle->s.origin,f1,driver->s.origin);
VectorAdd(driver->s.origin,l1,driver->s.origin);
driver->s.origin[2] += vehicle->move_origin[2];
driver->vehicle = NULL;
driver->client->vehicle_framenum = level.framenum;
driver->movetype = MOVETYPE_WALK;
driver->gravity = 1;
// turn ON client side prediction for this player
driver->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
vehicle->s.sound = 0;
gi.linkentity (driver);
vehicle->owner = NULL;
}
void vehicle_think (edict_t *self)
{
float aspeed, speed, newspeed;
vec3_t forward, left, f1, l1, v;
self->nextthink = level.time + FRAMETIME;
VectorCopy(self->oldvelocity,v);
v[2] = 0;
speed = VectorLength(v);
if (speed > 0)
self->s.effects |= EF_ANIM_ALL;
else
self->s.effects &= ~EF_ANIM_ALL;
AngleVectors(self->s.angles, forward, left, NULL);
if (DotProduct(forward,self->oldvelocity) < 0) speed = -speed;
self->moveinfo.current_speed = speed;
if (self->owner)
{
// ... then we have a driver
if (self->owner->health <= 0)
{
vehicle_disengage(self);
return;
}
if (self->owner->client->use)
{
// if he's pressing the use key, and he didn't just
// get on or off, disengage
if (level.framenum - self->owner->client->vehicle_framenum > 2)
{
VectorCopy(self->velocity,self->oldvelocity);
vehicle_disengage(self);
return;
}
}
if (self->owner->client->ucmd.forwardmove != 0 && level.time > self->moveinfo.wait)
{
if (self->owner->client->ucmd.forwardmove > 0)
{
if (self->moveinfo.state < FAST)
{
self->moveinfo.state++;
self->moveinfo.next_speed = self->moveinfo.state * self->speed/3;
self->moveinfo.wait = level.time + FRAMETIME;
}
}
else
{
if (self->moveinfo.state > RFAST)
{
self->moveinfo.state--;
self->moveinfo.next_speed = self->moveinfo.state * self->speed/3;
self->moveinfo.wait = level.time + FRAMETIME;
}
}
}
if (self->moveinfo.current_speed < self->moveinfo.next_speed)
{
speed = self->moveinfo.current_speed + self->accel/10;
if (speed > self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
}
else if (self->moveinfo.current_speed > self->moveinfo.next_speed)
{
speed = self->moveinfo.current_speed - self->decel/10;
if (speed < self->moveinfo.next_speed) speed = self->moveinfo.next_speed;
}
VectorScale(forward,speed,self->velocity);
if (self->owner->client->ucmd.sidemove != 0 && speed != 0 )
{
aspeed = 180.*speed/(M_PI*self->radius);
if (self->owner->client->ucmd.sidemove > 0) aspeed = -aspeed;
self->avelocity[1] = aspeed;
}
else
self->avelocity[1] = 0;
if (speed != 0)
self->s.sound = self->noise_index;
else
self->s.sound = self->noise_index2;
#ifdef LOOP_SOUND_ATTENUATION
self->s.attenuation = self->attenuation;
#endif
gi.linkentity(self);
// Copy velocities and set position of driver
VectorCopy(self->velocity,self->owner->velocity);
VectorScale(forward,self->move_origin[0],f1);
VectorScale(left,-self->move_origin[1],l1);
VectorAdd(self->s.origin,f1,self->owner->s.origin);
VectorAdd(self->owner->s.origin,l1,self->owner->s.origin);
self->owner->s.origin[2] += self->move_origin[2];
// If moving, turn driver
if (speed != 0)
{
float yaw;
yaw = self->avelocity[1]*FRAMETIME;
self->owner->s.angles[YAW] += yaw;
self->owner->client->ps.pmove.delta_angles[YAW] += ANGLE2SHORT(yaw);
self->owner->client->ps.pmove.pm_type = PM_FREEZE;
}
VectorCopy(self->velocity,self->oldvelocity);
gi.linkentity(self->owner);
}
else
{
int i;
edict_t *ent;
vec3_t dir, drive;
//
// No driver
//
// if vehicle has stopped, drop it to ground.
// otherwise slow it down
if (speed==0)
{
if (!self->groundentity)
SV_AddGravity (self);
}
else
{
// no driver... slow to an eventual stop in no more than 5 sec.
self->moveinfo.next_speed = 0;
self->moveinfo.state = STOP;
if (speed > 0)
newspeed = max(0.,speed - self->speed/50);
else
newspeed = min(0.,speed + self->speed/50);
VectorScale(forward,newspeed,self->velocity);
VectorScale(self->avelocity,newspeed/speed,self->avelocity);
VectorCopy(self->velocity,self->oldvelocity);
gi.linkentity(self);
}
// check if a player has mounted the vehicle
// first get driving position
VectorScale(forward,self->move_origin[0],f1);
VectorScale(left,-self->move_origin[1],l1);
VectorAdd(self->s.origin,f1,drive);
VectorAdd(drive,l1,drive);
drive[2] += self->move_origin[2];
// find a player
for (i=1, ent=&g_edicts[1] ; i<=maxclients->value ; i++, ent++)
{
if (!ent->inuse) continue;
if (ent->movetype == MOVETYPE_NOCLIP) continue;
if (!ent->client->use) continue;
if (level.framenum - ent->client->vehicle_framenum <= 2) continue;
// determine distance from vehicle "move_origin"
VectorSubtract(drive,ent->s.origin,dir);
if (fabs(dir[2]) < 64)
dir[2] = 0;
if (VectorLength(dir) < 16) {
ent->client->vehicle_framenum = level.framenum;
// player has taken control of vehicle
// move vehicle up slightly to avoid roundoff collisions
self->s.origin[2] += 1;
gi.linkentity(self);
if (self->message)
safe_centerprintf(ent,self->message);
self->owner = ent;
ent->movetype = MOVETYPE_PUSH;
ent->gravity = 0;
ent->vehicle = self;
// turn off client side prediction for this player
ent->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
// force a good driving position
VectorCopy(drive,self->owner->s.origin);
gi.linkentity(ent);
// vehicle idle noise
self->s.sound = self->noise_index2;
#ifdef LOOP_SOUND_ATTENUATION
self->s.attenuation = self->attenuation;
#endif
// reset wait time so we can start accelerating
self->moveinfo.wait = 0;
}
}
}
#ifndef POSTTHINK_CHILD_MOVEMENT
if (self->movewith_next && (self->movewith_next->movewith_ent == self))
set_child_movement (self);
#endif // POSTTHINK_CHILD_MOVEMENT
}
void turn_vehicle (edict_t *self)
{
self->s.angles[YAW] = self->ideal_yaw;
gi.linkentity(self);
self->prethink = NULL;
}
void SP_func_vehicle (edict_t *self)
{
self->class_id = ENTITY_FUNC_VEHICLE;
self->ideal_yaw = self->s.angles[YAW];
VectorClear (self->s.angles);
self->solid = SOLID_BSP;
gi.setmodel (self, self->model);
// usermodel (for alias model vehicles) goes on modelindex2
if (self->usermodel && strlen(self->usermodel)) {
char modelname[256];
// check for "models/" already in path
if ( !strncmp(self->usermodel, "models/", 7) )
Com_sprintf(modelname, sizeof(modelname), "%s", self->usermodel);
else
Com_sprintf(modelname, sizeof(modelname), "models/%s", self->usermodel);
self->s.modelindex2 = gi.modelindex (modelname);
}
self->movetype = MOVETYPE_VEHICLE;
if (!self->speed)
self->speed = 200;
if (!self->accel)
self->accel = self->speed; // accelerates to full speed in 1 second (approximate).
if (!self->decel)
self->decel = self->accel;
if (!self->mass)
self->mass = 2000;
if (!self->radius)
self->radius = 256;
self->blocked = vehicle_blocked;
self->touch = vehicle_touch;
self->think = vehicle_think;
self->nextthink = level.time + FRAMETIME;
self->noise_index = gi.soundindex("engine/engine.wav");
self->noise_index2 = gi.soundindex("engine/idle.wav");
#ifdef LOOP_SOUND_ATTENUATION
if (self->attenuation <= 0)
self->attenuation = ATTN_IDLE;
#endif
VectorClear(self->velocity);
VectorClear(self->avelocity);
self->moveinfo.current_speed = 0;
self->moveinfo.state = STOP;
gi.linkentity (self);
VectorCopy(self->size,self->org_size);
#ifdef POSTTHINK_CHILD_MOVEMENT
self->postthink = set_child_movement; // Knightmare- supports movewith
#endif // POSTTHINK_CHILD_MOVEMENT
if (self->ideal_yaw != 0)
self->prethink = turn_vehicle;
if (self->health) {
self->die = func_vehicle_explode;
self->takedamage = DAMAGE_YES;
} else
self->takedamage = DAMAGE_NO;
}