mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2025-01-18 14:31:55 +00:00
d16b46e3cf
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.
511 lines
15 KiB
C
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;
|
|
}
|