/* =========================================================================== 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, 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, 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; }