mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-15 00:41:21 +00:00
502 lines
15 KiB
C
502 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;
|
|
}
|
|
}
|
|
}
|
|
if (self->movewith_next && (self->movewith_next->movewith_ent == self))
|
|
set_child_movement(self);
|
|
}
|
|
|
|
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->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);
|
|
|
|
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;
|
|
}
|