diff --git a/Makefile b/Makefile index b20de4b4..965edaa6 100644 --- a/Makefile +++ b/Makefile @@ -1469,11 +1469,11 @@ ROGUE_OBJS_ = \ src/rogue/g_chase.o \ src/game/g_cmds.o \ src/rogue/g_combat.o \ - src/rogue/g_func.o \ + src/game/g_func.o \ src/rogue/g_items.o \ src/game/g_main.o \ - src/rogue/g_misc.o \ - src/rogue/g_monster.o \ + src/game/g_misc.o \ + src/game/g_monster.o \ src/game/g_newai.o \ src/game/g_newdm.o \ src/game/g_newfnc.o \ @@ -1481,13 +1481,13 @@ ROGUE_OBJS_ = \ src/game/g_newtrig.o \ src/game/g_newweap.o \ src/game/g_phys.o \ - src/rogue/g_spawn.o \ + src/game/g_spawn.o \ src/game/g_sphere.o \ - src/rogue/g_svcmds.o \ - src/rogue/g_target.o \ - src/rogue/g_trigger.o \ - src/rogue/g_turret.o \ - src/rogue/g_utils.o \ + src/game/g_svcmds.o \ + src/game/g_target.o \ + src/game/g_trigger.o \ + src/game/g_turret.o \ + src/game/g_utils.o \ src/game/g_weapon.o \ src/game/dm/ball.o \ src/game/dm/tag.o \ @@ -1496,17 +1496,21 @@ ROGUE_OBJS_ = \ src/game/monster/boss3/boss3.o \ src/game/monster/boss3/boss31.o \ src/game/monster/boss3/boss32.o \ + src/game/monster/boss5/boss5.o \ src/rogue/monster/brain/brain.o \ src/game/monster/carrier/carrier.o \ - src/rogue/monster/chick/chick.o \ + src/game/monster/chick/chick.o \ src/rogue/monster/flipper/flipper.o \ src/rogue/monster/float/float.o \ src/game/monster/flyer/flyer.o \ src/rogue/monster/gladiator/gladiator.o \ + src/game/monster/gladiator/gladb.o \ + src/game/monster/gekk/gekk.o \ src/rogue/monster/gunner/gunner.o \ src/rogue/monster/hover/hover.o \ src/game/monster/infantry/infantry.o \ src/rogue/monster/insane/insane.o \ + src/game/monster/fixbot/fixbot.o \ src/rogue/monster/medic/medic.o \ src/rogue/monster/misc/move.o \ src/rogue/monster/mutant/mutant.o \ diff --git a/src/game/g_func.c b/src/game/g_func.c index ae289f3b..12a0a819 100644 --- a/src/game/g_func.c +++ b/src/game/g_func.c @@ -27,6 +27,58 @@ #include "header/local.h" +#define PLAT_LOW_TRIGGER 1 +#define PLAT2_TOGGLE 2 +#define PLAT2_TOP 4 +#define PLAT2_TRIGGER_TOP 8 +#define PLAT2_TRIGGER_BOTTOM 16 +#define PLAT2_BOX_LIFT 32 + +#define STATE_TOP 0 +#define STATE_BOTTOM 1 +#define STATE_UP 2 +#define STATE_DOWN 3 + +#define DOOR_START_OPEN 1 +#define DOOR_REVERSE 2 +#define DOOR_CRUSHER 4 +#define DOOR_NOMONSTER 8 +#define DOOR_TOGGLE 32 +#define DOOR_X_AXIS 64 +#define DOOR_Y_AXIS 128 +#define DOOR_INACTIVE 8192 + +#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) + +#define PLAT2_CALLED 1 +#define PLAT2_MOVING 2 +#define PLAT2_WAITING 4 + +#define TRAIN_START_ON 1 +#define TRAIN_TOGGLE 2 +#define TRAIN_BLOCK_STOPS 4 + +#define SECRET_ALWAYS_SHOOT 1 +#define SECRET_1ST_LEFT 2 +#define SECRET_1ST_DOWN 4 + +void door_secret_move1(edict_t *self); +void door_secret_move2(edict_t *self); +void door_secret_move3(edict_t *self); +void door_secret_move4(edict_t *self); +void door_secret_move5(edict_t *self); +void door_secret_move6(edict_t *self); +void door_secret_done(edict_t *self); + +void train_next(edict_t *self); +void door_go_down(edict_t *self); +void plat2_go_down(edict_t *ent); +void plat2_go_up(edict_t *ent); +void plat2_spawn_danger_area(edict_t *ent); +void plat2_kill_danger_area(edict_t *ent); +void Think_AccelMove(edict_t *ent); +void plat_go_down(edict_t *ent); + /* * ========================================================= * @@ -61,26 +113,8 @@ * ========================================================= */ -#define PLAT_LOW_TRIGGER 1 - -#define STATE_TOP 0 -#define STATE_BOTTOM 1 -#define STATE_UP 2 -#define STATE_DOWN 3 - -#define DOOR_START_OPEN 1 -#define DOOR_REVERSE 2 -#define DOOR_CRUSHER 4 -#define DOOR_NOMONSTER 8 -#define DOOR_TOGGLE 32 -#define DOOR_X_AXIS 64 -#define DOOR_Y_AXIS 128 - /* Support routines for movement (changes in origin using velocity) */ -void Think_AccelMove(edict_t *ent); -void plat_go_down(edict_t *ent); - void Move_Done(edict_t *ent) { @@ -144,7 +178,7 @@ Move_Begin(edict_t *ent) void Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *)) { - if (!ent || !func) + if (!ent || !func) { return; } @@ -237,6 +271,17 @@ AngleMove_Begin(edict_t *ent) return; } + /* accelerate as needed */ + if (ent->moveinfo.speed < ent->speed) + { + ent->moveinfo.speed += ent->accel; + + if (ent->moveinfo.speed > ent->speed) + { + ent->moveinfo.speed = ent->speed; + } + } + /* set destdelta to the vector needed to move */ if (ent->moveinfo.state == STATE_UP) { @@ -264,15 +309,24 @@ AngleMove_Begin(edict_t *ent) /* scale the destdelta vector by the time spent traveling to get velocity */ VectorScale(destdelta, 1.0 / traveltime, ent->avelocity); - /* set nextthink to trigger a think when dest is reached */ - ent->nextthink = level.time + frames * FRAMETIME; - ent->think = AngleMove_Final; + /* if we're done accelerating, act as a normal rotation */ + if (ent->moveinfo.speed >= ent->speed) + { + /* set nextthink to trigger a think when dest is reached */ + ent->nextthink = level.time + frames * FRAMETIME; + ent->think = AngleMove_Final; + } + else + { + ent->nextthink = level.time + FRAMETIME; + ent->think = AngleMove_Begin; + } } void AngleMove_Calc(edict_t *ent, void (*func)(edict_t *)) { - if (!ent || !func) + if (!ent || !func) { return; } @@ -280,6 +334,13 @@ AngleMove_Calc(edict_t *ent, void (*func)(edict_t *)) VectorClear(ent->avelocity); ent->moveinfo.endfunc = func; + /* if we're supposed to accelerate, this will + tell anglemove_begin to do so */ + if (ent->accel != ent->speed) + { + ent->moveinfo.speed = 0; + } + if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent)) { @@ -297,8 +358,6 @@ AngleMove_Calc(edict_t *ent, void (*func)(edict_t *)) * change the speed for the next frame */ -#define AccelerationDistance(target, rate) (target * ((target / rate) + 1) / 2) - void plat_CalcAcceleratedMove(moveinfo_t *moveinfo) { @@ -513,6 +572,8 @@ plat_hit_bottom(edict_t *ent) } ent->moveinfo.state = STATE_BOTTOM; + + plat2_kill_danger_area(ent); } void @@ -528,7 +589,8 @@ plat_go_down(edict_t *ent) if (ent->moveinfo.sound_start) { gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, - ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->moveinfo.sound_start, 1, + ATTN_STATIC, 0); } ent->s.sound = ent->moveinfo.sound_middle; @@ -551,7 +613,8 @@ plat_go_up(edict_t *ent) if (ent->moveinfo.sound_start) { gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, - ent->moveinfo.sound_start, 1, ATTN_STATIC, 0); + ent->moveinfo.sound_start, 1, + ATTN_STATIC, 0); } ent->s.sound = ent->moveinfo.sound_middle; @@ -559,6 +622,8 @@ plat_go_up(edict_t *ent) ent->moveinfo.state = STATE_UP; Move_Calc(ent, ent->moveinfo.start_origin, plat_hit_top); + + plat2_spawn_danger_area(ent); } void @@ -579,13 +644,20 @@ plat_blocked(edict_t *self, edict_t *other) if (other->inuse) { /* Hack for entity without it's origin near the model */ - VectorMA (other->absmin, 0.5, other->size, other->s.origin); + VectorMA(other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } return; } + /* gib dead things */ + if (other->health < 1) + { + T_Damage(other, self, self, vec3_origin, other->s.origin, + vec3_origin, 100, 1, 0, MOD_CRUSH); + } + T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, 0, MOD_CRUSH); @@ -600,13 +672,28 @@ plat_blocked(edict_t *self, edict_t *other) } void -Use_Plat(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) +Use_Plat(edict_t *ent, edict_t *other, edict_t *activator /* unused */) { - if (!ent) + if (!ent || !other) { return; } + /* if a monster is using us, then allow the activity when stopped. */ + if (other->svflags & SVF_MONSTER) + { + if (ent->moveinfo.state == STATE_TOP) + { + plat_go_down(ent); + } + else if (ent->moveinfo.state == STATE_BOTTOM) + { + plat_go_up(ent); + } + + return; + } + if (ent->think) { return; /* already down */ @@ -648,7 +735,7 @@ wait_and_change(edict_t* ent, void (*afterwaitfunc)(edict_t *)) } void -Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, +Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unsed */, csurface_t *surf /* unused */) { if (!ent || !other) @@ -679,17 +766,17 @@ Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, } } -void +edict_t * plat_spawn_inside_trigger(edict_t *ent) { - if (!ent) - { - return; - } - edict_t *trigger; vec3_t tmin, tmax; + if (!ent) + { + return NULL; + } + /* middle trigger */ trigger = G_Spawn(); trigger->touch = Touch_Plat_Center; @@ -727,6 +814,8 @@ plat_spawn_inside_trigger(edict_t *ent) VectorCopy(tmax, trigger->maxs); gi.linkentity(trigger); + + return trigger; } /* @@ -848,23 +937,645 @@ SP_func_plat(edict_t *ent) ent->moveinfo.sound_end = gi.soundindex("plats/pt1_end.wav"); } +void +plat2_spawn_danger_area(edict_t *ent) +{ + if (!ent) + { + return; + } + + vec3_t mins, maxs; + + VectorCopy(ent->mins, mins); + VectorCopy(ent->maxs, maxs); + maxs[2] = ent->mins[2] + 64; + + SpawnBadArea(mins, maxs, 0, ent); +} + +void +plat2_kill_danger_area(edict_t *ent) +{ + edict_t *t; + + if (!ent) + { + return; + } + + t = NULL; + + while ((t = G_Find(t, FOFS(classname), "bad_area"))) + { + if (t->owner == ent) + { + G_FreeEdict(t); + } + } +} + +void +plat2_hit_top(edict_t *ent) +{ + if (!ent) + { + return; + } + + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + { + gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end, + 1, ATTN_STATIC, 0); + } + + ent->s.sound = 0; + } + + ent->moveinfo.state = STATE_TOP; + + if (ent->plat2flags & PLAT2_CALLED) + { + ent->plat2flags = PLAT2_WAITING; + + if (!(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->think = plat2_go_down; + ent->nextthink = level.time + 5.0; + } + + if (deathmatch->value) + { + ent->last_move_time = level.time - 1.0; + } + else + { + ent->last_move_time = level.time - 2.0; + } + } + else if (!(ent->spawnflags & PLAT2_TOP) && + !(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->plat2flags = 0; + ent->think = plat2_go_down; + ent->nextthink = level.time + 2.0; + ent->last_move_time = level.time; + } + else + { + ent->plat2flags = 0; + ent->last_move_time = level.time; + } + + G_UseTargets(ent, ent); +} + +void +plat2_hit_bottom(edict_t *ent) +{ + if (!ent) + { + return; + } + + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_end) + { + gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, + ent->moveinfo.sound_end, 1, + ATTN_STATIC, 0); + } + + ent->s.sound = 0; + } + + ent->moveinfo.state = STATE_BOTTOM; + + if (ent->plat2flags & PLAT2_CALLED) + { + ent->plat2flags = PLAT2_WAITING; + + if (!(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->think = plat2_go_up; + ent->nextthink = level.time + 5.0; + } + + if (deathmatch->value) + { + ent->last_move_time = level.time - 1.0; + } + else + { + ent->last_move_time = level.time - 2.0; + } + } + else if ((ent->spawnflags & PLAT2_TOP) && !(ent->spawnflags & PLAT2_TOGGLE)) + { + ent->plat2flags = 0; + ent->think = plat2_go_up; + ent->nextthink = level.time + 2.0; + ent->last_move_time = level.time; + } + else + { + ent->plat2flags = 0; + ent->last_move_time = level.time; + } + + plat2_kill_danger_area(ent); + G_UseTargets(ent, ent); +} + +void +plat2_go_down(edict_t *ent) +{ + if (!ent) + { + return; + } + + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + { + gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, + ent->moveinfo.sound_start, 1, + ATTN_STATIC, 0); + } + + ent->s.sound = ent->moveinfo.sound_middle; + } + + ent->moveinfo.state = STATE_DOWN; + ent->plat2flags |= PLAT2_MOVING; + + Move_Calc(ent, ent->moveinfo.end_origin, plat2_hit_bottom); +} + +void +plat2_go_up(edict_t *ent) +{ + if (!ent) + { + return; + } + + if (!(ent->flags & FL_TEAMSLAVE)) + { + if (ent->moveinfo.sound_start) + { + gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, + ent->moveinfo.sound_start, 1, + ATTN_STATIC, 0); + } + + ent->s.sound = ent->moveinfo.sound_middle; + } + + ent->moveinfo.state = STATE_UP; + ent->plat2flags |= PLAT2_MOVING; + + plat2_spawn_danger_area(ent); + + Move_Calc(ent, ent->moveinfo.start_origin, plat2_hit_top); +} + +void +plat2_operate(edict_t *ent, edict_t *other) +{ + int otherState; + float pauseTime; + float platCenter; + edict_t *trigger; + + if (!ent || !other) + { + return; + } + + trigger = ent; + ent = ent->enemy; /* now point at the plat, not the trigger */ + + if (ent->plat2flags & PLAT2_MOVING) + { + return; + } + + if ((ent->last_move_time + 2) > level.time) + { + return; + } + + platCenter = (trigger->absmin[2] + trigger->absmax[2]) / 2; + + if (ent->moveinfo.state == STATE_TOP) + { + otherState = STATE_TOP; + + if (ent->spawnflags & PLAT2_BOX_LIFT) + { + if (platCenter > other->s.origin[2]) + { + otherState = STATE_BOTTOM; + } + } + else + { + if (trigger->absmax[2] > other->s.origin[2]) + { + otherState = STATE_BOTTOM; + } + } + } + else + { + otherState = STATE_BOTTOM; + + if (other->s.origin[2] > platCenter) + { + otherState = STATE_TOP; + } + } + + ent->plat2flags = PLAT2_MOVING; + + if (deathmatch->value) + { + pauseTime = 0.3; + } + else + { + pauseTime = 0.5; + } + + if (ent->moveinfo.state != otherState) + { + ent->plat2flags |= PLAT2_CALLED; + pauseTime = 0.1; + } + + ent->last_move_time = level.time; + + if (ent->moveinfo.state == STATE_BOTTOM) + { + ent->think = plat2_go_up; + ent->nextthink = level.time + pauseTime; + } + else + { + ent->think = plat2_go_down; + ent->nextthink = level.time + pauseTime; + } +} + +void +Touch_Plat_Center2(edict_t *ent, edict_t *other, + cplane_t *plane /* unused */, csurface_t *surf /* unused */) +{ + if (!ent || !other) + { + return; + } + + /* this requires monsters to actively trigger plats, not just step on them. */ + if (other->health <= 0) + { + return; + } + + /* don't let non-monsters activate plat2s */ + if ((!(other->svflags & SVF_MONSTER)) && (!other->client)) + { + return; + } + + plat2_operate(ent, other); +} + +void +plat2_blocked(edict_t *self, edict_t *other) +{ + if (!self || !other) + { + return; + } + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + /* give it a chance to go away on it's own terms (like gibs) */ + T_Damage(other, self, self, vec3_origin, other->s.origin, + vec3_origin, 100000, 1, 0, MOD_CRUSH); + + /* if it's still there, nuke it */ + if (other->inuse) + { + BecomeExplosion1(other); + } + + return; + } + + /* gib dead things */ + if (other->health < 1) + { + T_Damage(other, self, self, vec3_origin, other->s.origin, + vec3_origin, 100, 1, 0, MOD_CRUSH); + } + + T_Damage(other, self, self, vec3_origin, other->s.origin, + vec3_origin, self->dmg, 1, 0, MOD_CRUSH); + + if (self->moveinfo.state == STATE_UP) + { + plat2_go_down(self); + } + else if (self->moveinfo.state == STATE_DOWN) + { + plat2_go_up(self); + } +} + +void +Use_Plat2(edict_t *ent, edict_t *other /* unused */, + edict_t *activator) +{ + edict_t *trigger; + int i; + + if (!ent || !activator) + { + return; + } + + if (ent->moveinfo.state > STATE_BOTTOM) + { + return; + } + + if ((ent->last_move_time + 2) > level.time) + { + return; + } + + for (i = 1, trigger = g_edicts + 1; i < globals.num_edicts; i++, trigger++) + { + if (!trigger->inuse) + { + continue; + } + + if (trigger->touch == Touch_Plat_Center2) + { + if (trigger->enemy == ent) + { + plat2_operate(trigger, activator); + return; + } + } + } +} + +void +plat2_activate(edict_t *ent, edict_t *other /* unused */, + edict_t *activator /* unused */) +{ + edict_t *trigger; + + if (!ent) + { + return; + } + + ent->use = Use_Plat2; + trigger = plat_spawn_inside_trigger(ent); /* the "start moving" trigger */ + + trigger->maxs[0] += 10; + trigger->maxs[1] += 10; + trigger->mins[0] -= 10; + trigger->mins[1] -= 10; + + gi.linkentity(trigger); + + trigger->touch = Touch_Plat_Center2; /* Override trigger touch function */ + + plat2_go_down(ent); +} + +/* QUAKED func_plat2 (0 .5 .8) ? PLAT_LOW_TRIGGER PLAT2_TOGGLE PLAT2_TOP PLAT2_TRIGGER_TOP PLAT2_TRIGGER_BOTTOM BOX_LIFT + * speed default 150 + * + * PLAT_LOW_TRIGGER - creates a short trigger field at the bottom + * PLAT2_TOGGLE - plat will not return to default position. + * PLAT2_TOP - plat's default position will the the top. + * PLAT2_TRIGGER_TOP - plat will trigger it's targets each time it hits top + * PLAT2_TRIGGER_BOTTOM - plat will trigger it's targets each time it hits bottom + * BOX_LIFT - this indicates that the lift is a box, rather than just a platform + * + * Plats are always drawn in the extended position, so they will light correctly. + * + * If the plat is the target of another trigger or button, it will start out + * disabled in the extended position until it is trigger, when it will lower + * and become a normal plat. + * + * "speed" overrides default 200. + * "accel" overrides default 500 + * "lip" no default + * + * If the "height" key is set, that will determine the amount the plat moves, + * instead of being implicitly determoveinfoned by the model's height. + * + */ +void +SP_func_plat2(edict_t *ent) +{ + edict_t *trigger; + + if (!ent) + { + return; + } + + VectorClear(ent->s.angles); + ent->solid = SOLID_BSP; + ent->movetype = MOVETYPE_PUSH; + + gi.setmodel(ent, ent->model); + + ent->blocked = plat2_blocked; + + if (!ent->speed) + { + ent->speed = 20; + } + else + { + ent->speed *= 0.1; + } + + if (!ent->accel) + { + ent->accel = 5; + } + else + { + ent->accel *= 0.1; + } + + if (!ent->decel) + { + ent->decel = 5; + } + else + { + ent->decel *= 0.1; + } + + if (deathmatch->value) + { + ent->speed *= 2; + ent->accel *= 2; + ent->decel *= 2; + } + + /* Added to kill things it's being blocked by */ + if (!ent->dmg) + { + ent->dmg = 2; + } + + /* pos1 is the top position, pos2 is the bottom */ + VectorCopy(ent->s.origin, ent->pos1); + VectorCopy(ent->s.origin, ent->pos2); + + if (st.height) + { + ent->pos2[2] -= (st.height - st.lip); + } + else + { + ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip; + } + + ent->moveinfo.state = STATE_TOP; + + if (ent->targetname) + { + ent->use = plat2_activate; + } + else + { + ent->use = Use_Plat2; + + trigger = plat_spawn_inside_trigger(ent); /* the "start moving" trigger */ + + trigger->maxs[0] += 10; + trigger->maxs[1] += 10; + trigger->mins[0] -= 10; + trigger->mins[1] -= 10; + + gi.linkentity(trigger); + trigger->touch = Touch_Plat_Center2; /* Override trigger touch function */ + + if (!(ent->spawnflags & PLAT2_TOP)) + { + VectorCopy(ent->pos2, ent->s.origin); + ent->moveinfo.state = STATE_BOTTOM; + } + } + + gi.linkentity(ent); + + ent->moveinfo.speed = ent->speed; + ent->moveinfo.accel = ent->accel; + ent->moveinfo.decel = ent->decel; + ent->moveinfo.wait = ent->wait; + VectorCopy(ent->pos1, ent->moveinfo.start_origin); + VectorCopy(ent->s.angles, ent->moveinfo.start_angles); + VectorCopy(ent->pos2, ent->moveinfo.end_origin); + VectorCopy(ent->s.angles, ent->moveinfo.end_angles); + + ent->moveinfo.sound_start = gi.soundindex("plats/pt1_strt.wav"); + ent->moveinfo.sound_middle = gi.soundindex("plats/pt1_mid.wav"); + ent->moveinfo.sound_end = gi.soundindex("plats/pt1_end.wav"); +} + /* ==================================================================== */ /* - * QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS - * TOUCH_PAIN STOP ANIMATED ANIMATED_FAST + * QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL * * You need to have an origin brush as part of this entity. * The center of that brush will be the point around which it * is rotated. It will rotate around the Z axis by default. * You can check either the X_AXIS or Y_AXIS box to change that. * - * "speed" determines how fast it moves; default value is 100. - * "dmg" damage to inflict when blocked (2 default) + * func_rotating will use it's targets when it stops and starts. + * + * "speed" determines how fast it moves; default value is 100. + * "dmg" damage to inflict when blocked (2 default) + * "accel" if specified, is how much the rotation speed will increase per .1sec. * * REVERSE will cause the it to rotate in the opposite direction. * STOP mean it will stop moving instead of pushing entities + * ACCEL means it will accelerate to it's final speed and decelerate when shutting down. */ +void +rotating_accel(edict_t *self) +{ + float current_speed; + + if (!self) + { + return; + } + + current_speed = VectorLength(self->avelocity); + + if (current_speed >= (self->speed - self->accel)) /* done */ + { + VectorScale(self->movedir, self->speed, self->avelocity); + G_UseTargets(self, self); + } + else + { + current_speed += self->accel; + VectorScale(self->movedir, current_speed, self->avelocity); + self->think = rotating_accel; + self->nextthink = level.time + FRAMETIME; + } +} + +void +rotating_decel(edict_t *self) +{ + float current_speed; + + if (!self) + { + return; + } + + current_speed = VectorLength(self->avelocity); + + if (current_speed <= self->decel) /* done */ + { + VectorClear(self->avelocity); + G_UseTargets(self, self); + self->touch = NULL; + } + else + { + current_speed -= self->decel; + VectorScale(self->movedir, current_speed, self->avelocity); + self->think = rotating_decel; + self->nextthink = level.time + FRAMETIME; + } +} + void rotating_blocked(edict_t *self, edict_t *other) { @@ -878,7 +1589,8 @@ rotating_blocked(edict_t *self, edict_t *other) } void -rotating_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) +rotating_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, + csurface_t *surf /* unused */) { if (!self || !other) { @@ -893,7 +1605,8 @@ rotating_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csur } void -rotating_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +rotating_use(edict_t *self, edict_t *other /* unused */, + edict_t *activator /* unused */) { if (!self) { @@ -903,13 +1616,31 @@ rotating_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* u if (!VectorCompare(self->avelocity, vec3_origin)) { self->s.sound = 0; - VectorClear(self->avelocity); - self->touch = NULL; + + if (self->spawnflags & 8192) /* Decelerate */ + { + rotating_decel(self); + } + else + { + VectorClear(self->avelocity); + G_UseTargets(self, self); + self->touch = NULL; + } } else { self->s.sound = self->moveinfo.sound_middle; - VectorScale(self->movedir, self->speed, self->avelocity); + + if (self->spawnflags & 8192) /* accelerate */ + { + rotating_accel(self); + } + else + { + VectorScale(self->movedir, self->speed, self->avelocity); + G_UseTargets(self, self); + } if (self->spawnflags & 16) { @@ -987,6 +1718,27 @@ SP_func_rotating(edict_t *ent) ent->s.effects |= EF_ANIM_ALLFAST; } + if (ent->spawnflags & 8192) /* Accelerate / Decelerate */ + { + if (!ent->accel) + { + ent->accel = 1; + } + else if (ent->accel > ent->speed) + { + ent->accel = ent->speed; + } + + if (!ent->decel) + { + ent->decel = 1; + } + else if (ent->decel > ent->speed) + { + ent->decel = ent->speed; + } + } + gi.setmodel(ent, ent->model); gi.linkentity(ent); } @@ -1090,8 +1842,8 @@ button_fire(edict_t *self) if (self->moveinfo.sound_start && !(self->flags & FL_TEAMSLAVE)) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, - self->moveinfo.sound_start, 1, ATTN_STATIC, - 0); + self->moveinfo.sound_start, + 1, ATTN_STATIC, 0); } Move_Calc(self, self->moveinfo.end_origin, button_wait); @@ -1100,7 +1852,7 @@ button_fire(edict_t *self) void button_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { - if (!self ||!activator) + if (!self || !activator) { return; } @@ -1110,7 +1862,8 @@ button_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) } void -button_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) +button_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, + csurface_t *surf /* unused */) { if (!self || !other) { @@ -1132,10 +1885,11 @@ button_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurfa } void -button_killed(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unsued */, - int damage /* unused */, vec3_t point /* unused */) +button_killed(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker, int damage /* unused */, + vec3_t point /* unused */) { - if (!self) + if (!self || !attacker) { return; } @@ -1237,8 +1991,6 @@ SP_func_button(edict_t *ent) * unless it is already targeted by another */ -void door_go_down(edict_t *self); - /* * QUAKED func_door (0 .5 .8) ? START_OPEN x CRUSHER NOMONSTER ANIMATED TOGGLE ANIMATED_FAST * @@ -1299,8 +2051,9 @@ door_hit_top(edict_t *self) { if (self->moveinfo.sound_end) { - gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, self->moveinfo.sound_end, - 1, ATTN_STATIC, 0); + gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, + self->moveinfo.sound_end, 1, + ATTN_STATIC, 0); } self->s.sound = 0; @@ -1357,8 +2110,8 @@ door_go_down(edict_t *self) if (self->moveinfo.sound_start) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, - self->moveinfo.sound_start, 1, - ATTN_STATIC, 0); + self->moveinfo.sound_start, + 1, ATTN_STATIC, 0); } self->s.sound = self->moveinfo.sound_middle; @@ -1434,14 +2187,126 @@ door_go_up(edict_t *self, edict_t *activator) } void -door_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) +smart_water_go_up(edict_t *self) { - if (!self || !activator) + float distance; + edict_t *lowestPlayer; + edict_t *ent; + float lowestPlayerPt; + int i; + + if (!self) { return; } + if (self->moveinfo.state == STATE_TOP) + { + /* reset top wait time */ + if (self->moveinfo.wait >= 0) + { + self->nextthink = level.time + self->moveinfo.wait; + } + + return; + } + + if (self->health) + { + if (self->absmax[2] >= self->health) + { + VectorClear(self->velocity); + self->nextthink = 0; + self->moveinfo.state = STATE_TOP; + return; + } + } + + if (!(self->flags & FL_TEAMSLAVE)) + { + if (self->moveinfo.sound_start) + { + gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, + self->moveinfo.sound_start, 1, + ATTN_STATIC, 0); + } + + self->s.sound = self->moveinfo.sound_middle; + } + + /* find the lowest player point. */ + lowestPlayerPt = 999999; + lowestPlayer = NULL; + + for (i = 0; i < game.maxclients; i++) + { + ent = &g_edicts[1 + i]; + + /* don't count dead or unused player slots */ + if ((ent->inuse) && (ent->health > 0)) + { + if (ent->absmin[2] < lowestPlayerPt) + { + lowestPlayerPt = ent->absmin[2]; + lowestPlayer = ent; + } + } + } + + if (!lowestPlayer) + { + return; + } + + distance = lowestPlayerPt - self->absmax[2]; + + /* for the calculations, make sure we + intend to go up at least a little. */ + if (distance < self->accel) + { + distance = 100; + self->moveinfo.speed = 5; + } + else + { + self->moveinfo.speed = distance / self->accel; + } + + if (self->moveinfo.speed < 5) + { + self->moveinfo.speed = 5; + } + else if (self->moveinfo.speed > self->speed) + { + self->moveinfo.speed = self->speed; + } + + /* should this allow any movement other than straight up? */ + VectorSet(self->moveinfo.dir, 0, 0, 1); + VectorScale(self->moveinfo.dir, self->moveinfo.speed, self->velocity); + self->moveinfo.remaining_distance = distance; + + if (self->moveinfo.state != STATE_UP) + { + G_UseTargets(self, lowestPlayer); + door_use_areaportals(self, true); + self->moveinfo.state = STATE_UP; + } + + self->think = smart_water_go_up; + self->nextthink = level.time + FRAMETIME; +} + +void +door_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) +{ edict_t *ent; + vec3_t center; + + if (!self || !activator) + { + return; + } if (self->flags & FL_TEAMSLAVE) { @@ -1465,6 +2330,19 @@ door_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) } } + /* smart water is different */ + VectorAdd(self->mins, self->maxs, center); + VectorScale(center, 0.5, center); + + if ((gi.pointcontents(center) & MASK_WATER) && self->spawnflags & 2) + { + self->message = NULL; + self->touch = NULL; + self->enemy = activator; + smart_water_go_up(self); + return; + } + /* trigger all paired doors */ for (ent = self; ent; ent = ent->teamchain) { @@ -1476,7 +2354,7 @@ door_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) void Touch_DoorTrigger(edict_t *self, edict_t *other, cplane_t *plane /* unused */, - csurface_t *surf /* unused */) + csurface_t *surf /* unused */) { if (!self || !other) { @@ -1640,7 +2518,7 @@ door_blocked(edict_t *self, edict_t *other) if (other->inuse) { /* Hack for entitiy without their origin near the model */ - VectorMA (other->absmin, 0.5, other->size, other->s.origin); + VectorMA(other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } @@ -1678,8 +2556,9 @@ door_blocked(edict_t *self, edict_t *other) } void -door_killed(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker, - int damage /* unused */, vec3_t point /* unused */) +door_killed(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker, int damage /* unused */, + vec3_t point /* unused */) { edict_t *ent; @@ -1698,9 +2577,10 @@ door_killed(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker, } void -door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) +door_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, + csurface_t *surf /* unused */) { - if (!self || !other) + if (!self || !other) { return; } @@ -1832,7 +2712,8 @@ SP_func_door(edict_t *ent) ent->s.effects |= EF_ANIM_ALLFAST; } - /* to simplify logic elsewhere, make non-teamed doors into a team of one */ + /* to simplify logic elsewhere, make + non-teamed doors into a team of one */ if (!ent->team) { ent->teammaster = ent; @@ -1852,6 +2733,36 @@ SP_func_door(edict_t *ent) } } +void +Door_Activate(edict_t *self, edict_t *other /* unused */, + edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + self->use = NULL; + + if (self->health) + { + self->takedamage = DAMAGE_YES; + self->die = door_killed; + self->max_health = self->health; + } + + if (self->health) + { + self->think = Think_CalcMoveSpeed; + } + else + { + self->think = Think_SpawnDoorTrigger; + } + + self->nextthink = level.time + FRAMETIME; +} + /* * QUAKED func_door_rotating (0 .5 .8) ? START_OPEN REVERSE CRUSHER NOMONSTER ANIMATED TOGGLE X_AXIS Y_AXIS * @@ -2021,6 +2932,42 @@ SP_func_door_rotating(edict_t *ent) { ent->think = Think_SpawnDoorTrigger; } + + if (ent->spawnflags & DOOR_INACTIVE) + { + ent->takedamage = DAMAGE_NO; + ent->die = NULL; + ent->think = NULL; + ent->nextthink = 0; + ent->use = Door_Activate; + } +} + +void +smart_water_blocked(edict_t *self, edict_t *other) +{ + if (!self || !other) + { + return; + } + + if (!(other->svflags & SVF_MONSTER) && (!other->client)) + { + /* give it a chance to go away on it's own terms (like gibs) */ + T_Damage(other, self, self, vec3_origin, other->s.origin, + vec3_origin, 100000, 1, 0, MOD_LAVA); + + /* if it's still there, nuke it */ + if (other->inuse) + { + BecomeExplosion1(other); + } + + return; + } + + T_Damage(other, self, self, vec3_origin, other->s.origin, + vec3_origin, 100, 1, 0, MOD_LAVA); } /* ==================================================================== */ @@ -2101,7 +3048,17 @@ SP_func_water(edict_t *self) } self->moveinfo.accel = self->moveinfo.decel = - self->moveinfo.speed = self->speed; + self->moveinfo.speed = self->speed; + + if (self->spawnflags & 2) /* smart water */ + { + if (!self->accel) + { + self->accel = 20; + } + + self->blocked = smart_water_blocked; + } if (!self->wait) { @@ -2124,12 +3081,6 @@ SP_func_water(edict_t *self) /* ==================================================================== */ -#define TRAIN_START_ON 1 -#define TRAIN_TOGGLE 2 -#define TRAIN_BLOCK_STOPS 4 - -void train_next(edict_t *self); - /* * QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS * @@ -2162,7 +3113,7 @@ train_blocked(edict_t *self, edict_t *other) if (other->inuse) { /* Hack for entity without an origin near the model */ - VectorMA (other->absmin, 0.5, other->size, other->s.origin); + VectorMA(other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } @@ -2219,6 +3170,7 @@ train_wait(edict_t *self) } else if (self->spawnflags & TRAIN_TOGGLE) { + self->target_ent = NULL; train_next(self); self->spawnflags &= ~TRAIN_START_ON; VectorClear(self->velocity); @@ -2243,18 +3195,23 @@ train_wait(edict_t *self) } } +void +train_piece_wait(edict_t *self) +{ +} + void train_next(edict_t *self) { + edict_t *ent; + vec3_t dest; + qboolean first; + if (!self) { return; } - edict_t *ent; - vec3_t dest; - qboolean first; - first = true; again: @@ -2292,6 +3249,32 @@ again: goto again; } + if (ent->speed) + { + self->speed = ent->speed; + self->moveinfo.speed = ent->speed; + + if (ent->accel) + { + self->moveinfo.accel = ent->accel; + } + else + { + self->moveinfo.accel = ent->speed; + } + + if (ent->decel) + { + self->moveinfo.decel = ent->decel; + } + else + { + self->moveinfo.decel = ent->speed; + } + + self->moveinfo.current_speed = 0; + } + self->moveinfo.wait = ent->wait; self->target_ent = ent; @@ -2313,6 +3296,29 @@ again: VectorCopy(dest, self->moveinfo.end_origin); Move_Calc(self, dest, train_wait); self->spawnflags |= TRAIN_START_ON; + + if (self->team) + { + edict_t *e; + vec3_t dir, dst; + + VectorSubtract(dest, self->s.origin, dir); + + for (e = self->teamchain; e; e = e->teamchain) + { + VectorAdd(dir, e->s.origin, dst); + VectorCopy(e->s.origin, e->moveinfo.start_origin); + VectorCopy(dst, e->moveinfo.end_origin); + + e->moveinfo.state = STATE_TOP; + e->speed = self->speed; + e->moveinfo.speed = self->moveinfo.speed; + e->moveinfo.accel = self->moveinfo.accel; + e->moveinfo.decel = self->moveinfo.decel; + e->movetype = MOVETYPE_PUSH; + Move_Calc(e, dst, train_piece_wait); + } + } } void @@ -2380,7 +3386,8 @@ func_train_find(edict_t *self) } void -train_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) +train_use(edict_t *self, edict_t *other /* unused */, + edict_t *activator) { if (!self || !activator) { @@ -2477,7 +3484,8 @@ SP_func_train(edict_t *self) * QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) */ void -trigger_elevator_use(edict_t *self, edict_t *other, edict_t *activator /* unused */) +trigger_elevator_use(edict_t *self, edict_t *other, + edict_t *activator /* unused */) { edict_t *target; @@ -2501,7 +3509,8 @@ trigger_elevator_use(edict_t *self, edict_t *other, edict_t *activator /* unused if (!target) { - gi.dprintf("elevator used with bad pathtarget: %s\n", other->pathtarget); + gi.dprintf("elevator used with bad pathtarget: %s\n", + other->pathtarget); return; } @@ -2629,7 +3638,8 @@ SP_func_timer(edict_t *self) if (self->random >= self->wait) { self->random = self->wait - FRAMETIME; - gi.dprintf("func_timer at %s has random >= wait\n", vtos(self->s.origin)); + gi.dprintf("func_timer at %s has random >= wait\n", + vtos(self->s.origin)); } if (self->spawnflags & 1) @@ -2654,7 +3664,8 @@ SP_func_timer(edict_t *self) * speed default 100 */ void -func_conveyor_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +func_conveyor_use(edict_t *self, edict_t *other /* unused */, + edict_t *activator /* unused */) { if (!self) { @@ -2719,21 +3730,9 @@ SP_func_conveyor(edict_t *self) * "dmg" damage to inflic when blocked (default 2) * "wait" how long to hold in the open position (default 5, -1 means hold) */ - -#define SECRET_ALWAYS_SHOOT 1 -#define SECRET_1ST_LEFT 2 -#define SECRET_1ST_DOWN 4 - -void door_secret_move1(edict_t *self); -void door_secret_move2(edict_t *self); -void door_secret_move3(edict_t *self); -void door_secret_move4(edict_t *self); -void door_secret_move5(edict_t *self); -void door_secret_move6(edict_t *self); -void door_secret_done(edict_t *self); - void -door_secret_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +door_secret_use(edict_t *self, edict_t *other /* unused */, + edict_t *activator /*unused */) { if (!self) { @@ -2859,7 +3858,7 @@ door_secret_blocked(edict_t *self, edict_t *other) if (other->inuse) { /* Hack for entities without their origin near the model */ - VectorMA (other->absmin, 0.5, other->size, other->s.origin); + VectorMA(other->absmin, 0.5, other->size, other->s.origin); BecomeExplosion1(other); } @@ -2878,8 +3877,9 @@ door_secret_blocked(edict_t *self, edict_t *other) } void -door_secret_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker, - int damage /* unused */, vec3_t point /* unused */) +door_secret_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker, int damage /* unused */, + vec3_t point /* unused */) { if (!self || !attacker) { @@ -2932,7 +3932,7 @@ SP_func_door_secret(edict_t *ent) } ent->moveinfo.accel = ent->moveinfo.decel = - ent->moveinfo.speed = 50; + ent->moveinfo.speed = 50; /* calculate positions */ AngleVectors(ent->s.angles, forward, right, up); @@ -2987,7 +3987,8 @@ SP_func_door_secret(edict_t *ent) * irrespective of protection. */ void -use_killbox(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +use_killbox(edict_t *self, edict_t *other /* unused */, + edict_t *activator /* unused */) { if (!self) { diff --git a/src/game/g_items.c b/src/game/g_items.c index 367eb0d0..3ec51726 100644 --- a/src/game/g_items.c +++ b/src/game/g_items.c @@ -46,6 +46,13 @@ void Weapon_Grenade(edict_t *ent); void Weapon_GrenadeLauncher(edict_t *ent); void Weapon_Railgun(edict_t *ent); void Weapon_BFG(edict_t *ent); +void Weapon_ChainFist(edict_t *ent); +void Weapon_Disintegrator(edict_t *ent); +void Weapon_ETF_Rifle(edict_t *ent); +void Weapon_Heatbeam(edict_t *ent); +void Weapon_Prox(edict_t *ent); +void Weapon_Tesla(edict_t *ent); +void Weapon_ProxLauncher(edict_t *ent); void Weapon_Ionripper(edict_t *ent); void Weapon_Phalanx(edict_t *ent); @@ -167,6 +174,21 @@ DoRespawn(edict_t *ent) } } + if (randomrespawn && randomrespawn->value) + { + edict_t *newEnt; + + newEnt = DoRandomRespawn(ent); + + /* if we've changed entities, then do some sleight + * of hand. otherwise, the old entity will respawn */ + if (newEnt) + { + G_FreeEdict(ent); + ent = newEnt; + } + } + ent->svflags &= ~SVF_NOCLIENT; ent->solid = SOLID_TRIGGER; gi.linkentity(ent); @@ -324,6 +346,19 @@ Pickup_Bandolier(edict_t *ent, edict_t *other) other->client->pers.max_magslug = 75; } + if (other->client->pers.max_flechettes < 250) + { + other->client->pers.max_flechettes = 250; + } + + if (g_disruptor->value) + { + if (other->client->pers.max_rounds < 150) + { + other->client->pers.max_rounds = 150; + } + } + item = FindItem("Bullets"); if (item) @@ -408,6 +443,19 @@ Pickup_Pack(edict_t *ent, edict_t *other) other->client->pers.max_magslug = 100; } + if (other->client->pers.max_flechettes < 200) + { + other->client->pers.max_flechettes = 200; + } + + if (g_disruptor->value) + { + if (other->client->pers.max_rounds < 200) + { + other->client->pers.max_rounds = 200; + } + } + item = FindItem("Bullets"); if (item) @@ -513,6 +561,36 @@ Pickup_Pack(edict_t *ent, edict_t *other) } } + item = FindItem("Flechettes"); + + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + + if (other->client->pers.inventory[index] > + other->client->pers.max_flechettes) + { + other->client->pers.inventory[index] = + other->client->pers.max_flechettes; + } + } + + item = FindItem("Rounds"); + + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + + if (other->client->pers.inventory[index] > + other->client->pers.max_rounds) + { + other->client->pers.inventory[index] = + other->client->pers.max_rounds; + } + } + if (!(ent->spawnflags & DROPPED_ITEM) && (deathmatch->value)) { SetRespawn(ent, ent->item->quantity); @@ -523,6 +601,294 @@ Pickup_Pack(edict_t *ent, edict_t *other) /* ====================================================================== */ +qboolean +Pickup_Nuke(edict_t *ent, edict_t *other) +{ + int quantity; + + if (!ent || !other) + { + return false; + } + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (quantity >= 1) + { + return false; + } + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP)) + { + return false; + } + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM)) + { + SetRespawn(ent, ent->item->quantity); + } + } + + return true; +} + +void +Use_IR(edict_t *ent, gitem_t *item) +{ + if (!ent || !item) + { + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + if (ent->client->ir_framenum > level.framenum) + { + ent->client->ir_framenum += 600; + } + else + { + ent->client->ir_framenum = level.framenum + 600; + } + + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ir_start.wav"), 1, ATTN_NORM, 0); +} + +void +Use_Double(edict_t *ent, gitem_t *item) +{ + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + if (ent->client->double_framenum > level.framenum) + { + ent->client->double_framenum += 300; + } + else + { + ent->client->double_framenum = level.framenum + 300; + } + + gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage1.wav"), 1, ATTN_NORM, 0); +} + +void +Use_Compass(edict_t *ent, gitem_t *item) +{ + int ang; + + if (!ent || !item) + { + return; + } + + ang = (int)(ent->client->v_angle[1]); + + if (ang < 0) + { + ang += 360; + } + + gi.cprintf(ent, PRINT_HIGH, "Origin: %0.0f,%0.0f,%0.0f Dir: %d\n", + ent->s.origin[0], ent->s.origin[1], ent->s.origin[2], ang); +} + +void +Use_Nuke(edict_t *ent, gitem_t *item) +{ + vec3_t forward, right, start; + float speed; + + if (!ent || !item) + { + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + AngleVectors(ent->client->v_angle, forward, right, NULL); + + VectorCopy(ent->s.origin, start); + speed = 100; + fire_nuke(ent, start, forward, speed); +} + +void +Use_Doppleganger(edict_t *ent, gitem_t *item) +{ + vec3_t forward, right; + vec3_t createPt, spawnPt; + vec3_t ang; + + if (!ent || !item) + { + return; + } + + VectorClear(ang); + ang[YAW] = ent->client->v_angle[YAW]; + AngleVectors(ang, forward, right, NULL); + + VectorMA(ent->s.origin, 48, forward, createPt); + + if (!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32)) + { + return; + } + + if (!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1)) + { + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + SpawnGrow_Spawn(spawnPt, 0); + fire_doppleganger(ent, spawnPt, forward); +} + +qboolean +Pickup_Doppleganger(edict_t *ent, edict_t *other) +{ + int quantity; + + if (!ent || !other) + { + return false; + } + + if (!(deathmatch->value)) + { + return false; + } + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (quantity >= 1) + { + return false; + } + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (!(ent->spawnflags & DROPPED_ITEM)) + { + SetRespawn(ent, ent->item->quantity); + } + + return true; +} + +qboolean +Pickup_Sphere(edict_t *ent, edict_t *other) +{ + int quantity; + + if (!ent || !other) + { + return false; + } + + if (other->client && other->client->owned_sphere) + { + return false; + } + + quantity = other->client->pers.inventory[ITEM_INDEX(ent->item)]; + + if (((skill->value == SKILL_MEDIUM) && + (quantity >= 2)) || ((skill->value >= SKILL_HARD) && (quantity >= 1))) + { + return false; + } + + if ((coop->value) && (ent->item->flags & IT_STAY_COOP) && (quantity > 0)) + { + return false; + } + + other->client->pers.inventory[ITEM_INDEX(ent->item)]++; + + if (deathmatch->value) + { + if (!(ent->spawnflags & DROPPED_ITEM)) + { + SetRespawn(ent, ent->item->quantity); + } + } + + return true; +} + +void +Use_Defender(edict_t *ent, gitem_t *item) +{ + if (!ent || !item) + { + return; + } + + if (ent->client && ent->client->owned_sphere) + { + gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n"); + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + Defender_Launch(ent); +} + +void +Use_Hunter(edict_t *ent, gitem_t *item) +{ + if (!ent || !item) + { + return; + } + + if (ent->client && ent->client->owned_sphere) + { + gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n"); + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + Hunter_Launch(ent); +} + +void +Use_Vengeance(edict_t *ent, gitem_t *item) +{ + if (!ent || !item) + { + return; + } + + if (ent->client && ent->client->owned_sphere) + { + gi.cprintf(ent, PRINT_HIGH, "Only one sphere at a time!\n"); + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + Vengeance_Launch(ent); +} + +/* ====================================================================== */ + void Use_Quad(edict_t *ent, gitem_t *item) { @@ -555,7 +921,8 @@ Use_Quad(edict_t *ent, gitem_t *item) ent->client->quad_framenum = level.framenum + timeout; } - gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, + 0); } /* ===================================================================== */ @@ -772,8 +1139,25 @@ Add_Ammo(edict_t *ent, gitem_t *item, int count) { max = ent->client->pers.max_trap; } + else if (item->tag == AMMO_FLECHETTES) + { + max = ent->client->pers.max_flechettes; + } + else if (item->tag == AMMO_PROX) + { + max = ent->client->pers.max_prox; + } + else if (item->tag == AMMO_TESLA) + { + max = ent->client->pers.max_tesla; + } + else if (item->tag == AMMO_DISRUPTOR) + { + max = ent->client->pers.max_rounds; + } else { + gi.dprintf("undefined ammo type\n"); return false; } @@ -830,9 +1214,10 @@ Pickup_Ammo(edict_t *ent, edict_t *other) if (weapon && !oldcount) { + /* don't switch to tesla */ if ((other->client->pers.weapon != ent->item) && - (!deathmatch->value || - (other->client->pers.weapon == FindItem("blaster")))) + (!deathmatch->value || (other->client->pers.weapon == FindItem("blaster"))) && + (strcmp(ent->classname, "ammo_tesla"))) { other->client->newweapon = ent->item; } @@ -1316,6 +1701,10 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_ { ent->item->use(other, ent->item); } + else + { + gi.dprintf("Powerup has no use function!\n"); + } } } } @@ -1499,7 +1888,7 @@ droptofloor(edict_t *ent) { gi.setmodel(ent, ent->model); } - else + else if (ent->item->world_model) { gi.setmodel(ent, ent->item->world_model); } @@ -1668,6 +2057,47 @@ PrecacheItem(gitem_t *it) } } +/* + * Create the item marked for spawn creation + */ +void +Item_TriggeredSpawn(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +{ + self->svflags &= ~SVF_NOCLIENT; + self->use = NULL; + + if (strcmp(self->classname, "key_power_cube")) + { + self->spawnflags = 0; + } + + droptofloor(self); +} + +/* + * Set up an item to spawn in later. + */ +void +SetTriggeredSpawn(edict_t *ent) +{ + if (!ent) + { + return; + } + + /* don't do anything on key_power_cubes. */ + if (!strcmp(ent->classname, "key_power_cube")) + { + return; + } + + ent->think = NULL; + ent->nextthink = 0; + ent->use = Item_TriggeredSpawn; + ent->svflags |= SVF_NOCLIENT; + ent->solid = SOLID_NOT; +} + /* * ============ * Sets the clipping size and @@ -1718,6 +2148,18 @@ SpawnItem(edict_t *ent, gitem_t *item) G_FreeEdict(ent); return; } + + if (item->pickup == Pickup_Sphere) + { + G_FreeEdict(ent); + return; + } + + if (item->pickup == Pickup_Doppleganger) + { + G_FreeEdict(ent); + return; + } } if ((int)dmflags->value & DF_NO_HEALTH) @@ -1740,6 +2182,34 @@ SpawnItem(edict_t *ent, gitem_t *item) return; } } + + if ((int)dmflags->value & DF_NO_MINES) + { + if (!strcmp(ent->classname, "ammo_prox") || + !strcmp(ent->classname, "ammo_tesla")) + { + G_FreeEdict(ent); + return; + } + } + + if ((int)dmflags->value & DF_NO_NUKES) + { + if (!strcmp(ent->classname, "ammo_nuke")) + { + G_FreeEdict(ent); + return; + } + } + + if ((int)dmflags->value & DF_NO_SPHERES) + { + if (item->pickup == Pickup_Sphere) + { + G_FreeEdict(ent); + return; + } + } } if (coop->value && !(ent->spawnflags & ITEM_NO_TOUCH) && (strcmp(ent->classname, "key_power_cube") == 0)) @@ -1764,6 +2234,11 @@ SpawnItem(edict_t *ent, gitem_t *item) { gi.modelindex(ent->model); } + + if (ent->spawnflags & 1) + { + SetTriggeredSpawn(ent); + } } /* ====================================================================== */ @@ -1775,7 +2250,7 @@ static const gitem_t gameitemlist[] = { /* - * QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) + * QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */ { "item_armor_body", @@ -2931,7 +3406,7 @@ static const gitem_t gameitemlist[] = { gitem_t itemlist[MAX_ITEMS]; /* - * QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) + * QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */ void SP_item_health(edict_t *self) @@ -3075,3 +3550,73 @@ SetItemNames(void) power_screen_index = ITEM_INDEX(FindItem("Power Screen")); power_shield_index = ITEM_INDEX(FindItem("Power Shield")); } + +void +SP_xatrix_item(edict_t *self) +{ + gitem_t *item; + int i; + char *spawnClass = NULL; + + if (!self) + { + return; + } + + if (!self->classname) + { + return; + } + + if (!strcmp(self->classname, "ammo_magslug")) + { + spawnClass = "ammo_flechettes"; + } + else if (!strcmp(self->classname, "ammo_trap")) + { + spawnClass = "weapon_proxlauncher"; + } + else if (!strcmp(self->classname, "item_quadfire")) + { + float chance; + + chance = random(); + + if (chance < 0.2) + { + spawnClass = "item_sphere_hunter"; + } + else if (chance < 0.6) + { + spawnClass = "item_sphere_vengeance"; + } + else + { + spawnClass = "item_sphere_defender"; + } + } + else if (!strcmp(self->classname, "weapon_boomer")) + { + spawnClass = "weapon_etf_rifle"; + } + else if (!strcmp(self->classname, "weapon_phalanx")) + { + spawnClass = "weapon_plasmabeam"; + } + + /* check item spawn functions */ + for (i = 0, item = itemlist; i < game.num_items; i++, item++) + { + if (!item->classname) + { + continue; + } + + if (!strcmp(item->classname, spawnClass)) + { + /* found it */ + SpawnItem(self, item); + return; + } + } +} diff --git a/src/game/g_misc.c b/src/game/g_misc.c index 919f4df2..ba6152c5 100644 --- a/src/game/g_misc.c +++ b/src/game/g_misc.c @@ -30,6 +30,8 @@ int debristhisframe; int gibsthisframe; +extern void M_WorldEffects(edict_t *ent); + /* * QUAKED func_group (0 0 0) ? * Used to group brushes together just for editor convenience. @@ -254,6 +256,7 @@ ThrowGib(edict_t *self, char *gibname, int damage, int type) gib->think = G_FreeEdict; gib->nextthink = level.time + 10 + random() * 10; + gib->s.renderfx |= RF_IR_VISIBLE; gi.linkentity(gib); } @@ -591,6 +594,7 @@ BecomeExplosion2(edict_t *self) /* * QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT + * * Target: next path corner * Pathtarget: gets used when an entity that has * this path_corner targeted touches it @@ -802,6 +806,7 @@ SP_point_combat(edict_t *self) /* * QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) + * * Just for the debugging level. Don't use */ void @@ -842,6 +847,7 @@ SP_viewthing(edict_t *ent) /* * QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) + * * Used as a positional target for spotlights, etc. */ void @@ -857,6 +863,7 @@ SP_info_null(edict_t *self) /* * QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) + * * Used as a positional target for lighting. */ void @@ -875,6 +882,7 @@ SP_info_notnull(edict_t *self) /* * QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF + * * Non-displayed light. * Default light value is 300. * Default style is 0. @@ -947,6 +955,7 @@ SP_light(edict_t *self) * START_ON only valid for TRIGGER_SPAWN walls * the wall will initially be present */ + void func_wall_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { @@ -1039,8 +1048,10 @@ SP_func_wall(edict_t *self) /* * QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST + * * This is solid bmodel that will fall if it's support it removed. */ + void func_object_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf /* unused */) @@ -1052,7 +1063,8 @@ func_object_touch(edict_t *self, edict_t *other, cplane_t *plane, return; } - if (plane->normal[2] < 1.0) + /* only squash thing we fall on top of */ + if (plane && plane->normal[2] < 1.0) { return; } @@ -1148,13 +1160,17 @@ SP_func_object(edict_t *self) /* ===================================================== */ /* - * QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST - * Any brush that you want to explode or break apart. If you want an + * QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE + * + * Any brush that you want to explode or break apart. If you want an * explosion, set dmg and it will do a radius explosion of that amount * at the center of the bursh. * * If targeted it will not be shootable. * + * INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must + * target the entity you want to trigger it. This is the only entity approved to activate it. + * * health defaults to 100. * * mass defaults to 75. This determines how much debris is emitted when @@ -1170,6 +1186,7 @@ func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker, vec3_t size; int count; int mass; + edict_t *master; if (!self || !inflictor || !attacker) { @@ -1238,6 +1255,23 @@ func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker, ThrowDebris(self, "models/objects/debris2/tris.md2", 2, chunkorigin); } + if (self->flags & FL_TEAMSLAVE) + { + master = self->teammaster; + + /* because mappers (other than jim (usually)) are stupid.... */ + while (master) + { + if (master->teamchain == self) + { + master->teamchain = self->teamchain; + break; + } + + master = master->teamchain; + } + } + G_UseTargets(self, attacker); if (self->dmg) @@ -1251,14 +1285,43 @@ func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker, } void -func_explosive_use(edict_t *self, edict_t *other, edict_t *activator) +func_explosive_use(edict_t *self, edict_t *other, edict_t *activator /* unused */) { + if (!self || !other) + { + return; + } + func_explosive_explode(self, self, other, self->health, vec3_origin); } +void +func_explosive_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + self->use = func_explosive_use; + + if (!self->health) + { + self->health = 100; + } + + self->die = func_explosive_explode; + self->takedamage = DAMAGE_YES; +} + void func_explosive_spawn(edict_t *self, edict_t *other, edict_t *activator) { + if (!self) + { + return; + } + self->solid = SOLID_BSP; self->svflags &= ~SVF_NOCLIENT; self->use = NULL; @@ -1294,6 +1357,15 @@ SP_func_explosive(edict_t *self) self->solid = SOLID_NOT; self->use = func_explosive_spawn; } + else if (self->spawnflags & 8) + { + self->solid = SOLID_BSP; + + if (self->targetname) + { + self->use = func_explosive_activate; + } + } else { self->solid = SOLID_BSP; @@ -1314,7 +1386,8 @@ SP_func_explosive(edict_t *self) self->s.effects |= EF_ANIM_ALLFAST; } - if (self->use != func_explosive_use) + if ((self->use != func_explosive_use) && + (self->use != func_explosive_activate)) { if (!self->health) { @@ -1332,6 +1405,7 @@ SP_func_explosive(edict_t *self) /* * QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) + * * Large exploding box. You can override its mass (100), * health (80), and dmg (150). */ @@ -1401,7 +1475,7 @@ barrel_explode(edict_t *self) ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org); /* a bunch of little chunks */ - spd = 2 * self->dmg / 200; + spd = 2.0 * (float)self->dmg / 200.0; org[0] = self->s.origin[0] + crandom() * self->size[0]; org[1] = self->s.origin[1] + crandom() * self->size[1]; org[2] = self->s.origin[2] + crandom() * self->size[2]; @@ -1462,6 +1536,37 @@ barrel_delay(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker, self->activator = attacker; } +void +barrel_think(edict_t *self) +{ + if (!self) + { + return; + } + + /* the think needs to be first since later stuff may override. */ + self->think = barrel_think; + self->nextthink = level.time + FRAMETIME; + + M_CatagorizePosition(self); + self->flags |= FL_IMMUNE_SLIME; + self->air_finished = level.time + 100; + M_WorldEffects(self); +} + +void +barrel_start(edict_t *self) +{ + if (!self) + { + return; + } + + M_droptofloor(self); + self->think = barrel_think; + self->nextthink = level.time + FRAMETIME; +} + void SP_misc_explobox(edict_t *self) { @@ -1509,8 +1614,7 @@ SP_misc_explobox(edict_t *self) self->monsterinfo.aiflags = AI_NOSTEP; self->touch = barrel_touch; - - self->think = M_droptofloor; + self->think = barrel_start; self->nextthink = level.time + 2 * FRAMETIME; gi.linkentity(self); @@ -1575,7 +1679,7 @@ SP_misc_blackhole(edict_t *ent) ent->s.renderfx = RF_TRANSLUCENT; ent->use = misc_blackhole_use; ent->think = misc_blackhole_think; - ent->prethink = misc_blackhole_transparent; + ent->prethink = misc_blackhole_transparent; ent->nextthink = level.time + 2 * FRAMETIME; gi.linkentity(ent); } @@ -1711,6 +1815,7 @@ SP_misc_easterchick2(edict_t *ent) /* * QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) + * * Not really a monster, this is the Tank Commander's decapitated body. * There should be a item_commander_head that has this as it's target. */ @@ -1836,6 +1941,7 @@ SP_monster_commander_body(edict_t *self) /* * QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) + * * The origin is the bottom of the banner. * The banner is 128 tall. */ @@ -1873,6 +1979,7 @@ SP_misc_banner(edict_t *ent) /* * QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED + * * This is the dead player model. Comes in 6 exciting different poses! */ void @@ -1891,7 +1998,8 @@ misc_deadsoldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *at return; } - gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + gi.sound(self, CHAN_BODY, gi.soundindex( + "misc/udeath.wav"), 1, ATTN_NORM, 0); for (n = 0; n < 4; n++) { @@ -1964,6 +2072,7 @@ SP_misc_deadsoldier(edict_t *ent) /* * QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) + * * This is the Viper for the flyby bombing. * It is trigger_spawned, so you must have something use it for it to show up. * There must be a path for it to follow once it is activated. @@ -2064,11 +2173,17 @@ SP_misc_crashviper(edict_t *ent) /* * QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) + * * This is a large stationary viper as seen in Paul's intro */ void SP_misc_bigviper(edict_t *ent) { + if (!ent) + { + return; + } + ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; VectorSet(ent->mins, -176, -120, -24); @@ -2081,6 +2196,7 @@ SP_misc_bigviper(edict_t *ent) /* * QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) + * * "dmg" how much boom should the bomb make? */ void @@ -2240,6 +2356,7 @@ SP_misc_viper_missile(edict_t *self) /* * QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) + * * This is a Storgg ship for the flybys. * It is trigger_spawned, so you must have something use it for it to show up. * There must be a path for it to follow once it is activated. @@ -2412,7 +2529,8 @@ SP_light_mine1(edict_t *ent) ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; - ent->s.modelindex = gi.modelindex("models/objects/minelite/light1/tris.md2"); + ent->s.modelindex = + gi.modelindex("models/objects/minelite/light1/tris.md2"); gi.linkentity(ent); } @@ -2429,7 +2547,8 @@ SP_light_mine2(edict_t *ent) ent->movetype = MOVETYPE_NONE; ent->solid = SOLID_BBOX; - ent->s.modelindex = gi.modelindex("models/objects/minelite/light2/tris.md2"); + ent->s.modelindex = + gi.modelindex("models/objects/minelite/light2/tris.md2"); gi.linkentity(ent); } @@ -2437,6 +2556,7 @@ SP_light_mine2(edict_t *ent) /* * QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) + * * Intended for use with the target_spawner */ void @@ -2523,9 +2643,11 @@ SP_misc_gib_head(edict_t *ent) /* * QUAKED target_character (0 0 1) ? + * * used with target_string (must be on same "team") * "count" is position in the string (starts at 1) */ + void SP_target_character(edict_t *self) { @@ -2600,6 +2722,11 @@ target_string_use(edict_t *self, edict_t *other /* unused */, edict_t *activator void SP_target_string(edict_t *self) { + if (!self) + { + return; + } + if (!self->message) { self->message = ""; @@ -2612,6 +2739,7 @@ SP_target_string(edict_t *self) /* * QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE + * * target a target_string with this * * The default is to be a time of day clock @@ -2936,6 +3064,7 @@ teleporter_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, /* * QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) + * * Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. */ void @@ -2978,6 +3107,7 @@ SP_misc_teleporter(edict_t *ent) /* * QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) + * * Point teleporters at these. */ void @@ -3072,3 +3202,40 @@ SP_misc_nuke(edict_t *ent) ent->use = use_nuke; } + +void +misc_nuke_core_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + if (self->svflags & SVF_NOCLIENT) + { + self->svflags &= ~SVF_NOCLIENT; + } + else + { + self->svflags |= SVF_NOCLIENT; + } +} + +/* + * QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16) + * + * toggles visible/not visible. starts visible. + */ +void +SP_misc_nuke_core(edict_t *ent) +{ + if (!ent) + { + return; + } + + gi.setmodel(ent, "models/objects/core/tris.md2"); + gi.linkentity(ent); + + ent->use = misc_nuke_core_use; +} diff --git a/src/game/g_monster.c b/src/game/g_monster.c index 5f9d346d..13da7fda 100644 --- a/src/game/g_monster.c +++ b/src/game/g_monster.c @@ -475,7 +475,7 @@ M_CheckGround(edict_t *ent) return; } - if (ent->velocity[2] > 100) + if ((ent->velocity[2] * ent->gravityVector[2]) < -100) { ent->groundentity = NULL; return; @@ -485,7 +485,7 @@ M_CheckGround(edict_t *ent) is solid the entity is on ground */ point[0] = ent->s.origin[0]; point[1] = ent->s.origin[1]; - point[2] = ent->s.origin[2] - 0.25; + point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]); trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, point, ent, MASK_MONSTERSOLID); @@ -689,9 +689,18 @@ M_droptofloor(edict_t *ent) return; } - ent->s.origin[2] += 1; - VectorCopy(ent->s.origin, end); - end[2] -= 256; + if (ent->gravityVector[2] < 0) + { + ent->s.origin[2] += 1; + VectorCopy(ent->s.origin, end); + end[2] -= 256; + } + else + { + ent->s.origin[2] -= 1; + VectorCopy(ent->s.origin, end); + end[2] += 256; + } trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, end, ent, MASK_MONSTERSOLID); @@ -1140,6 +1149,11 @@ monster_start(edict_t *self) self->monsterinfo.currentmove->firstframe + 1)); } + self->monsterinfo.base_height = self->maxs[2]; + self->monsterinfo.quad_framenum = 0; + self->monsterinfo.double_framenum = 0; + self->monsterinfo.invincible_framenum = 0; + return true; } diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c index cc87ef88..0e3f6c2e 100644 --- a/src/game/g_spawn.c +++ b/src/game/g_spawn.c @@ -346,6 +346,43 @@ static spawn_t spawns[] = { {"turret_base", SP_turret_base}, {"turret_driver", SP_turret_driver}, + {"func_plat2", SP_func_plat2}, + {"func_door_secret2", SP_func_door_secret2}, + {"func_force_wall", SP_func_force_wall}, + {"trigger_teleport", SP_trigger_teleport}, + {"trigger_disguise", SP_trigger_disguise}, + {"info_teleport_destination", SP_info_teleport_destination}, + {"info_player_coop_lava", SP_info_player_coop_lava}, + {"monster_stalker", SP_monster_stalker}, + {"monster_turret", SP_monster_turret}, + {"target_steam", SP_target_steam}, + {"target_anger", SP_target_anger}, + {"target_killplayers", SP_target_killplayers}, + {"target_blacklight", SP_target_blacklight}, + {"target_orb", SP_target_orb}, + {"monster_daedalus", SP_monster_hover}, + {"hint_path", SP_hint_path}, + {"monster_carrier", SP_monster_carrier}, + {"monster_widow", SP_monster_widow}, + {"monster_widow2", SP_monster_widow2}, + {"monster_medic_commander", SP_monster_medic}, + {"dm_tag_token", SP_dm_tag_token}, + {"dm_dball_goal", SP_dm_dball_goal}, + {"dm_dball_ball", SP_dm_dball_ball}, + {"dm_dball_team1_start", SP_dm_dball_team1_start}, + {"dm_dball_team2_start", SP_dm_dball_team2_start}, + {"dm_dball_ball_start", SP_dm_dball_ball_start}, + {"dm_dball_speed_change", SP_dm_dball_speed_change}, + {"monster_kamikaze", SP_monster_kamikaze}, + {"turret_invisible_brain", SP_turret_invisible_brain}, + {"misc_nuke_core", SP_misc_nuke_core}, + + {"ammo_magslug", SP_xatrix_item}, + {"ammo_trap", SP_xatrix_item}, + {"item_quadfire", SP_xatrix_item}, + {"weapon_boomer", SP_xatrix_item}, + {"weapon_phalanx", SP_xatrix_item}, + {NULL, NULL} }; @@ -401,6 +438,25 @@ ED_CallSpawn(edict_t *ent) return; } + ent->gravityVector[0] = 0.0; + ent->gravityVector[1] = 0.0; + ent->gravityVector[2] = -1.0; + + if (!strcmp(ent->classname, "weapon_nailgun")) + { + ent->classname = (FindItem("ETF Rifle"))->classname; + } + + if (!strcmp(ent->classname, "ammo_nails")) + { + ent->classname = (FindItem("Flechettes"))->classname; + } + + if (!strcmp(ent->classname, "weapon_heatbeam")) + { + ent->classname = (FindItem("Plasma Beam"))->classname; + } + /* check item spawn functions */ for (i = 0, item = itemlist; i < game.num_items; i++, item++) { @@ -878,6 +934,30 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint) continue; } } + else if (coop->value && !coop_baseq2->value) + { + if (ent->spawnflags & SPAWNFLAG_NOT_COOP) + { + G_FreeEdict(ent); + inhibit++; + continue; + } + + /* stuff marked !easy & !med & !hard are coop only, all levels */ + if (!((ent->spawnflags & SPAWNFLAG_NOT_EASY) && + (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM) && + (ent->spawnflags & SPAWNFLAG_NOT_HARD))) + { + if (((skill->value == SKILL_EASY) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || + ((skill->value == SKILL_MEDIUM) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || + (((skill->value == SKILL_HARD) || (skill->value == SKILL_HARDPLUS)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))) + { + G_FreeEdict(ent); + inhibit++; + continue; + } + } + } else { if (Spawn_CheckCoop_MapHacks(ent) || ( @@ -902,7 +982,13 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint) SPAWNFLAG_NOT_COOP | SPAWNFLAG_NOT_DEATHMATCH); } + ent->gravityVector[0] = 0.0; + ent->gravityVector[1] = 0.0; + ent->gravityVector[2] = -1.0; + ED_CallSpawn(ent); + + ent->s.renderfx |= RF_IR_VISIBLE; } gi.dprintf("%i entities inhibited.\n", inhibit); @@ -910,6 +996,26 @@ SpawnEntities(const char *mapname, char *entities, const char *spawnpoint) G_FindTeams(); PlayerTrail_Init(); + + if (deathmatch->value) + { + if (randomrespawn && randomrespawn->value) + { + PrecacheForRandomRespawn(); + } + } + else + { + InitHintPaths(); + } + + if (deathmatch->value && gamerules && gamerules->value) + { + if (DMGame.PostInitSetup) + { + DMGame.PostInitSetup(); + } + } } /* =================================================================== */ diff --git a/src/game/g_sphere.c b/src/game/g_sphere.c index fe348020..8b811d11 100644 --- a/src/game/g_sphere.c +++ b/src/game/g_sphere.c @@ -18,7 +18,7 @@ #define VENGEANCE_LIFESPAN 30 #define MINIMUM_FLY_TIME 15 -extern char *ED_NewString(char *string); +extern char *ED_NewString(const char *string); void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker); void defender_think(edict_t *self); diff --git a/src/game/g_target.c b/src/game/g_target.c index cb1fdaed..17127d1d 100644 --- a/src/game/g_target.c +++ b/src/game/g_target.c @@ -27,11 +27,22 @@ #include "header/local.h" +#define LASER_ON 0x0001 +#define LASER_RED 0x0002 +#define LASER_GREEN 0x0004 +#define LASER_BLUE 0x0008 +#define LASER_YELLOW 0x0010 +#define LASER_ORANGE 0x0020 +#define LASER_FAT 0x0040 +#define LASER_STOPWINDOW 0x0080 + +void ED_CallSpawn(edict_t *ent); + /* * QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) - * Fire an origin based temp entity event to the clients. * - * "style" type byte + * Fire an origin based temp entity event to the clients. + * "style" type byte */ void Use_Target_Tent(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) @@ -199,6 +210,7 @@ Use_Target_Help(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* /* * QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 + * * When fired, the "message" key becomes the current personal computer string, * and the message light will be set on all clients status bars. */ @@ -411,6 +423,7 @@ SP_target_explosion(edict_t *ent) /* * QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) + * * Changes level to "map" when fired */ void @@ -588,6 +601,8 @@ use_target_spawner(edict_t *self, edict_t *other /* unused */, edict_t *activato { VectorCopy(self->movedir, ent->velocity); } + + ent->s.renderfx |= RF_IR_VISIBLE; /* PGM */ } void @@ -672,10 +687,11 @@ SP_target_blaster(edict_t *self) /* * QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 - * Once this trigger is touched/used, any trigger_crosslevel_target with - * the same trigger number is automatically used when a level is started - * within the same unit. It is OK to check multiple triggers. Message, - * delay, target, and killtarget also work. + * + * Once this trigger is touched/used, any trigger_crosslevel_target + * with the same trigger number is automatically used when a level + * is started within the same unit. It is OK to check multiple triggers. + * Message, delay, target, and killtarget also work. */ void trigger_crosslevel_trigger_use(edict_t *self, edict_t *other /* unused */, @@ -705,6 +721,7 @@ SP_target_crosslevel_trigger(edict_t *self) /* * QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 + * * Triggered by a trigger_crosslevel elsewhere within a unit. * If multiple triggers are checked, all must be true. Delay, * target and killtarget also work. @@ -750,9 +767,11 @@ SP_target_crosslevel_target(edict_t *self) /* ========================================================== */ /* - * QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT + * QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP * When triggered, fires a laser. You can either set a target * or a direction. + * + * WINDOWSTOP - stops at CONTENTS_WINDOW */ void target_laser_think(edict_t *self) @@ -798,8 +817,15 @@ target_laser_think(edict_t *self) while (1) { - tr = gi.trace(start, NULL, NULL, end, ignore, - CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER); + if (self->spawnflags & LASER_STOPWINDOW) + { + tr = gi.trace(start, NULL, NULL, end, ignore, MASK_SHOT); + } + else + { + tr = gi.trace(start, NULL, NULL, end, ignore, + CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER); + } if (!tr.ent) { @@ -823,7 +849,8 @@ target_laser_think(edict_t *self) /* if we hit something that's not a monster or player or is immune to lasers, we're done */ - if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client)) + if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) && + !(tr.ent->svflags & SVF_DAMAGEABLE)) { if (self->spawnflags & 0x80000000) { @@ -1156,9 +1183,11 @@ SP_target_mal_laser(edict_t *self) /* * QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE - * speed How many seconds the ramping will take - * message two letters; starting lightlevel and ending lightlevel + * + * speed How many seconds the ramping will take + * message two letters; starting lightlevel and ending lightlevel */ + void target_lightramp_think(edict_t *self) { @@ -1285,11 +1314,12 @@ SP_target_lightramp(edict_t *self) /* ========================================================== */ /* - * QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) + * QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT + * * When triggered, this initiates a level-wide earthquake. * All players and monsters are affected. - * "speed" severity of the quake (default:200) - * "count" duration of the quake (default:5) + * "speed" severity of the quake (default:200) + * "count" duration of the quake (default:5) */ void target_earthquake_think(edict_t *self) @@ -1302,16 +1332,19 @@ target_earthquake_think(edict_t *self) return; } - if (self->last_move_time < level.time) + if (!(self->spawnflags & 1)) { - gi.positioned_sound(self->s.origin, - self, - CHAN_AUTO, - self->noise_index, - 1.0, - ATTN_NONE, - 0); - self->last_move_time = level.time + 0.5; + if (self->last_move_time < level.time) + { + gi.positioned_sound(self->s.origin, + self, + CHAN_AUTO, + self->noise_index, + 1.0, + ATTN_NONE, + 0); + self->last_move_time = level.time + 0.5; + } } for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) @@ -1385,5 +1418,8 @@ SP_target_earthquake(edict_t *self) self->think = target_earthquake_think; self->use = target_earthquake_use; - self->noise_index = gi.soundindex("world/quake.wav"); + if (!(self->spawnflags & 1)) + { + self->noise_index = gi.soundindex("world/quake.wav"); + } } diff --git a/src/game/g_trigger.c b/src/game/g_trigger.c index 5cc68545..0a915b19 100644 --- a/src/game/g_trigger.c +++ b/src/game/g_trigger.c @@ -27,12 +27,21 @@ #include "header/local.h" -#define PUSH_ONCE 1 +#define TRIGGER_MONSTER 0x01 +#define TRIGGER_NOT_PLAYER 0x02 +#define TRIGGER_TRIGGERED 0x04 +#define TRIGGER_TOGGLE 0x08 -void trigger_push_active(edict_t *self); +#define PUSH_ONCE 0x01 +#define PUSH_START_OFF 0x02 +#define PUSH_SILENT 0x04 static int windsound; +void trigger_push_active(edict_t *self); +void hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, + csurface_t *surf /* unused */); + void InitTrigger(edict_t *self) { @@ -114,8 +123,24 @@ Use_Multi(edict_t *ent, edict_t *other /* unused */, edict_t *activator) return; } - ent->activator = activator; - multi_trigger(ent); + if (ent->spawnflags & TRIGGER_TOGGLE) + { + if (ent->solid == SOLID_TRIGGER) + { + ent->solid = SOLID_NOT; + } + else + { + ent->solid = SOLID_TRIGGER; + } + + gi.linkentity(ent); + } + else + { + ent->activator = activator; + multi_trigger(ent); + } } void @@ -163,13 +188,15 @@ Touch_Multi(edict_t *self, edict_t *other, cplane_t *plane /* unused */, } /* - * QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED + * QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE * Variable sized repeatable trigger. Must be targeted at one or more * entities. If "delay" is set, the trigger waits some time after * activating before firing. * * "wait" : Seconds between triggerings. (.2 default) * + * TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive. + * * sounds * 1) secret * 2) beep beep @@ -222,7 +249,7 @@ SP_trigger_multiple(edict_t *ent) ent->movetype = MOVETYPE_NONE; ent->svflags |= SVF_NOCLIENT; - if (ent->spawnflags & 4) + if (ent->spawnflags & (TRIGGER_TRIGGERED | TRIGGER_TOGGLE)) { ent->solid = SOLID_NOT; ent->use = trigger_enable; @@ -244,6 +271,7 @@ SP_trigger_multiple(edict_t *ent) /* * QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED + * * Triggers once, then removes itself. * * You must set the key "target" to the name of another @@ -463,6 +491,7 @@ SP_trigger_key(edict_t *self) /* * QUAKED trigger_counter (.5 .5 .5) ? nomessage + * * Acts as an intermediary for an action that takes multiple inputs. * * If nomessage is not set, it will print "1 more.. " etc when @@ -471,6 +500,7 @@ SP_trigger_key(edict_t *self) * After the counter has been triggered "count" times (default 2), * it will fire all of it's targets and remove itself. */ + void trigger_counter_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) @@ -530,6 +560,7 @@ SP_trigger_counter(edict_t *self) /* * QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) + * * This trigger will always fire. It is activated by the world. */ void @@ -573,7 +604,8 @@ trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, immediately from this */ VectorCopy(other->velocity, other->client->oldvelocity); - if (other->fly_sound_debounce_time < level.time) + if (!(self->spawnflags & PUSH_SILENT) && + (other->fly_sound_debounce_time < level.time)) { other->fly_sound_debounce_time = level.time + 1.5; gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); @@ -664,6 +696,36 @@ trigger_push_active(edict_t *self) } } +void +trigger_push_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_TRIGGER; + } + else + { + self->solid = SOLID_NOT; + } + + gi.linkentity(self); +} + +/* + * QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE START_OFF SILENT + * Pushes the player + * "speed" defaults to 1000 + * + * If targeted, it will toggle on and off when used. + * + * START_OFF - toggled trigger_push begins in off setting + * SILENT - doesn't make wind noise + */ void SP_trigger_push(edict_t *self) { @@ -676,7 +738,12 @@ SP_trigger_push(edict_t *self) windsound = gi.soundindex("misc/windfly.wav"); self->touch = trigger_push_touch; - if (self->spawnflags & 2) + if (!self->speed) + { + self->speed = 1000; + } + + if (self->spawnflags & PUSH_START_OFF) { if (!self->wait) { @@ -688,9 +755,22 @@ SP_trigger_push(edict_t *self) self->delay = self->nextthink + self->wait; } - if (!self->speed) + if (self->targetname) /* toggleable */ { - self->speed = 1000; + self->use = trigger_push_use; + + if (self->spawnflags & PUSH_START_OFF) + { + self->solid = SOLID_NOT; + } + } + else if (self->spawnflags & PUSH_START_OFF) + { + gi.dprintf("trigger_push is START_OFF but not targeted.\n"); + self->svflags = 0; + self->touch = NULL; + self->solid = SOLID_BSP; + self->movetype = MOVETYPE_PUSH; } gi.linkentity(self); @@ -698,16 +778,56 @@ SP_trigger_push(edict_t *self) /* * QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW + * * Any entity that touches this will be hurt. * * It does dmg points of damage each server frame * * SILENT supresses playing the sound - * SLOW changes the damage rate to once per second + * SLOW changes the damage rate to once per second * NO_PROTECTION *nothing* stops the damage * * "dmg" default 5 (whole numbers only) + * */ +void +hurt_use(edict_t *self, edict_t *other /* unused */, + edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + if (self->solid == SOLID_NOT) + { + int i, num; + edict_t *touch[MAX_EDICTS], *hurtme; + + self->solid = SOLID_TRIGGER; + num = gi.BoxEdicts (self->absmin, self->absmax, + touch, MAX_EDICTS, AREA_SOLID); + + /* Check for idle monsters in + trigger hurt */ + for (i = 0 ; i < num ; i++) + { + hurtme = touch[i]; + hurt_touch (self, hurtme, NULL, NULL); + } + } + else + { + self->solid = SOLID_NOT; + } + + gi.linkentity(self); + + if (!(self->spawnflags & 2)) + { + self->use = NULL; + } +} void hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, @@ -760,45 +880,6 @@ hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); } -void -hurt_use(edict_t *self, edict_t *other /* unused */, - edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (self->solid == SOLID_NOT) - { - int i, num; - edict_t *touch[MAX_EDICTS], *hurtme; - - self->solid = SOLID_TRIGGER; - num = gi.BoxEdicts (self->absmin, self->absmax, - touch, MAX_EDICTS, AREA_SOLID); - - /* Check for idle monsters in - trigger hurt */ - for (i = 0 ; i < num ; i++) - { - hurtme = touch[i]; - hurt_touch (self, hurtme, NULL, NULL); - } - } - else - { - self->solid = SOLID_NOT; - } - - gi.linkentity(self); - - if (!(self->spawnflags & 2)) - { - self->use = NULL; - } -} - void SP_trigger_hurt(edict_t *self) { @@ -834,6 +915,26 @@ SP_trigger_hurt(edict_t *self) gi.linkentity(self); } +void +trigger_gravity_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + if (self->solid == SOLID_NOT) + { + self->solid = SOLID_TRIGGER; + } + else + { + self->solid = SOLID_NOT; + } + + gi.linkentity(self); +} + /* * QUAKED trigger_gravity (.5 .5 .5) ? * Changes the touching entites gravity to @@ -852,6 +953,15 @@ trigger_gravity_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused * other->gravity = self->gravity; } +/* + * QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF + * Changes the touching entites gravity to + * the value of "gravity". 1.0 is standard + * gravity for the level. + * + * TOGGLE - trigger_gravity can be turned on and off + * START_OFF - trigger_gravity starts turned off (implies TOGGLE) + */ void SP_trigger_gravity(edict_t *self) { @@ -870,7 +980,20 @@ SP_trigger_gravity(edict_t *self) InitTrigger(self); self->gravity = (int)strtol(st.gravity, (char **)NULL, 10); + + if (self->spawnflags & 1) /* TOGGLE */ + { + self->use = trigger_gravity_use; + } + + if (self->spawnflags & 2) /* START_OFF */ + { + self->use = trigger_gravity_use; + self->solid = SOLID_NOT; + } + self->touch = trigger_gravity_touch; + gi.linkentity(self); } /* @@ -880,6 +1003,7 @@ SP_trigger_gravity(edict_t *self) * "speed" default to 200, the speed thrown forward * "height" default to 200, the speed thrown upwards */ + void trigger_monsterjump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) diff --git a/src/game/g_turret.c b/src/game/g_turret.c index 5bc817c4..d513bae0 100644 --- a/src/game/g_turret.c +++ b/src/game/g_turret.c @@ -32,6 +32,7 @@ void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker, void infantry_stand(edict_t *self); void monster_use(edict_t *self, edict_t *other, edict_t *activator); qboolean FindTarget(edict_t *self); +void SpawnTargetingSystem(edict_t *turret); void AnglesNormalize(vec3_t vec) @@ -110,12 +111,13 @@ turret_blocked(edict_t *self, edict_t *other) * "speed" default 50 * "dmg" default 10 * "angle" point this forward - * "target" point this at an info_notnull at the muzzle tip + * "target" point this at an info_notnull at the muzzle tip * "minpitch" min acceptable pitch angle : default -30 * "maxpitch" max acceptable pitch angle : default 30 - * "minyaw" min acceptable yaw angle : default 0 - * "maxyaw" max acceptable yaw angle : default 360 + * "minyaw" min acceptable yaw angle : default 0 + * "maxyaw" max acceptable yaw angle : default 360 */ + void turret_breach_fire(edict_t *self) { @@ -321,9 +323,17 @@ turret_breach_finish_init(edict_t *self) else { self->target_ent = G_PickTarget(self->target); - VectorSubtract(self->target_ent->s.origin, - self->s.origin, self->move_origin); - G_FreeEdict(self->target_ent); + + if (self->target_ent) + { + VectorSubtract(self->target_ent->s.origin, + self->s.origin, self->move_origin); + G_FreeEdict(self->target_ent); + } + else + { + gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin)); + } } self->teammaster->dmg = self->dmg; @@ -388,6 +398,7 @@ SP_turret_breach(edict_t *self) * This portion of the turret changes yaw only. * MUST be teamed with a turret_breach. */ + void SP_turret_base(edict_t *self) { @@ -607,3 +618,228 @@ SP_turret_driver(edict_t *self) gi.linkentity(self); } + +/* + * invisible turret drivers so we can have unmanned turrets. + * originally designed to shoot at func_trains and such, so they + * fire at the center of the bounding box, rather than the entity's + * origin. */ + +void +turret_brain_think(edict_t *self) +{ + vec3_t dir; + vec3_t endpos; + float reaction_time; + trace_t trace; + + if (!self) + { + return; + } + + self->nextthink = level.time + FRAMETIME; + + if (self->enemy) + { + if (!self->enemy->inuse) + { + self->enemy = NULL; + } + else if (self->enemy->takedamage && (self->enemy->health <= 0)) + { + self->enemy = NULL; + } + } + + if (!self->enemy) + { + if (!FindTarget(self)) + { + return; + } + + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + + VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos); + VectorScale(endpos, 0.5, endpos); + } + else + { + VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos); + VectorScale(endpos, 0.5, endpos); + + trace = gi.trace(self->target_ent->s.origin, vec3_origin, vec3_origin, + endpos, self->target_ent, MASK_SHOT); + + if ((trace.fraction == 1) || (trace.ent == self->enemy)) + { + if (self->monsterinfo.aiflags & AI_LOST_SIGHT) + { + self->monsterinfo.trail_time = level.time; + self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; + } + } + else + { + self->monsterinfo.aiflags |= AI_LOST_SIGHT; + return; + } + } + + /* let the turret know where we want it to aim */ + VectorSubtract(endpos, self->target_ent->s.origin, dir); + vectoangles(dir, self->target_ent->move_angles); + + /* decide if we should shoot */ + if (level.time < self->monsterinfo.attack_finished) + { + return; + } + + if (self->delay) + { + reaction_time = self->delay; + } + else + { + reaction_time = (3 - skill->value) * 1.0; + } + + if ((level.time - self->monsterinfo.trail_time) < reaction_time) + { + return; + } + + self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; + self->target_ent->spawnflags |= 65536; +} + +void +turret_brain_link(edict_t *self) +{ + vec3_t vec; + edict_t *ent; + + if (!self) + { + return; + } + + if (self->killtarget) + { + self->enemy = G_PickTarget(self->killtarget); + } + + self->think = turret_brain_think; + self->nextthink = level.time + FRAMETIME; + + self->target_ent = G_PickTarget(self->target); + self->target_ent->owner = self; + self->target_ent->teammaster->owner = self; + VectorCopy(self->target_ent->s.angles, self->s.angles); + + vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; + vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; + vec[2] = 0; + self->move_origin[0] = VectorLength(vec); + + VectorSubtract(self->s.origin, self->target_ent->s.origin, vec); + vectoangles(vec, vec); + AnglesNormalize(vec); + self->move_origin[1] = vec[1]; + + self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; + + /* add the driver to the end of them team chain */ + for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) + { + } + + ent->teamchain = self; + self->teammaster = self->target_ent->teammaster; + self->flags |= FL_TEAMSLAVE; +} + +void +turret_brain_deactivate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + self->think = NULL; + self->nextthink = 0; +} + +void +turret_brain_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator) +{ + if (!self || !activator) + { + return; + } + + if (!self->enemy) + { + self->enemy = activator; + } + + /* wait at least 3 seconds to fire. */ + self->monsterinfo.attack_finished = level.time + 3; + self->use = turret_brain_deactivate; + + self->think = turret_brain_link; + self->nextthink = level.time + FRAMETIME; +} + +/* + * QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16) + * Invisible brain to drive the turret. + * + * Does not search for targets. If targeted, can only be turned on once + * and then off once. After that they are completely disabled. + * + * "delay" the delay between firing (default ramps for skill level) + * "Target" the turret breach + * "Killtarget" the item you want it to attack. + * Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds + * before firing to acquire the target. + */ +void +SP_turret_invisible_brain(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->killtarget) + { + gi.dprintf("turret_invisible_brain with no killtarget!\n"); + G_FreeEdict(self); + return; + } + + if (!self->target) + { + gi.dprintf("turret_invisible_brain with no target!\n"); + G_FreeEdict(self); + return; + } + + if (self->targetname) + { + self->use = turret_brain_activate; + } + else + { + self->think = turret_brain_link; + self->nextthink = level.time + FRAMETIME; + } + + self->movetype = MOVETYPE_PUSH; + gi.linkentity(self); +} diff --git a/src/game/g_utils.c b/src/game/g_utils.c index 48fc0d99..c136951e 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -290,6 +290,7 @@ void G_UseTargets(edict_t *ent, edict_t *activator) { edict_t *t; + edict_t *master; if (!ent) { @@ -345,6 +346,7 @@ G_UseTargets(edict_t *ent, edict_t *activator) { level.total_secrets--; } + /* same deal with target_goal, but also turn off CD music if applicable */ else if (!Q_stricmp(t->classname,"target_goal")) { @@ -356,6 +358,30 @@ G_UseTargets(edict_t *ent, edict_t *activator) } } + /* if this entity is part of a train, cleanly remove it */ + if (t->flags & FL_TEAMSLAVE) + { + master = t->teammaster; + + while (master) + { + if (master->teamchain == t) + { + master->teamchain = t->teamchain; + break; + } + + master = master->teamchain; + } + } + + /* correct killcounter if a living monster gets killtargeted */ + if ((t->monsterinfo.checkattack || strcmp (t->classname, "turret_driver") == 0) && + !(t->monsterinfo.aiflags & (AI_GOOD_GUY|AI_DO_NOT_COUNT)) && t->deadflag != DEAD_DEAD) + { + level.killed_monsters++; + } + G_FreeEdict(t); if (!ent->inuse) diff --git a/src/game/monster/chick/chick.c b/src/game/monster/chick/chick.c index 8bf7d30d..3046a398 100644 --- a/src/game/monster/chick/chick.c +++ b/src/game/monster/chick/chick.c @@ -690,28 +690,105 @@ ChickRocket(edict_t *self) vec3_t start; vec3_t dir; vec3_t vec; + trace_t trace; /* check target */ + int rocketSpeed; + float dist; + vec3_t target; + qboolean blindfire = false; if (!self) { return; } + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + blindfire = true; + } + else + { + blindfire = false; + } + + if (!self->enemy || !self->enemy->inuse) + { + return; + } + AngleVectors(self->s.angles, forward, right, NULL); G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right, start); - VectorCopy(self->enemy->s.origin, vec); - vec[2] += self->enemy->viewheight; - VectorSubtract(vec, start, dir); - VectorNormalize(dir); + rocketSpeed = 500 + (100 * skill->value); /* rock & roll.... :) */ - if (!strcmp(self->classname, "monster_chick_heat")) + if (blindfire) { - monster_fire_heat(self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1); + VectorCopy(self->monsterinfo.blind_fire_target, target); } else { - monster_fire_rocket(self, start, dir, 50, 500, MZ2_CHICK_ROCKET_1); + VectorCopy(self->enemy->s.origin, target); + } + + /* blindfire shooting */ + if (blindfire) + { + VectorCopy(target, vec); + VectorSubtract(vec, start, dir); + } + /* don't shoot at feet if they're above where i'm shooting from. */ + else if ((random() < 0.33) || (start[2] < self->enemy->absmin[2])) + { + VectorCopy(target, vec); + vec[2] += self->enemy->viewheight; + VectorSubtract(vec, start, dir); + } + else + { + VectorCopy(target, vec); + vec[2] = self->enemy->absmin[2]; + VectorSubtract(vec, start, dir); + } + + /* lead target (not when blindfiring) */ + if ((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15))))) + { + float time; + + dist = VectorLength(dir); + time = dist / rocketSpeed; + VectorMA(vec, time, self->enemy->velocity, vec); + VectorSubtract(vec, start, dir); + } + + VectorNormalize(dir); + + if (blindfire) + { + /* blindfire has different fail criteria for the trace */ + if (!blind_rocket_ok(self, start, right, target, 10.0f, dir)) + { + return; + } + } + else + { + trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); + + if (((trace.ent != self->enemy) && (trace.ent != world)) || + ((trace.fraction <= 0.5f) && !trace.ent->client)) + { + return; + } + } + + if (!strcmp(self->classname, "monster_chick_heat")) + { + monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); + } + else + { + monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); } } @@ -1157,6 +1234,7 @@ SP_monster_chick(edict_t *self) self->monsterinfo.currentmove = &chick_move_stand; self->monsterinfo.scale = MODEL_SCALE; + self->monsterinfo.blindfire = true; walkmonster_start(self); } diff --git a/src/game/monster/soldier/soldier.c b/src/game/monster/soldier/soldier.c index 624dc67c..2a974e54 100644 --- a/src/game/monster/soldier/soldier.c +++ b/src/game/monster/soldier/soldier.c @@ -22,8 +22,9 @@ * * Soldier aka "Guard". This is the most complex enemy in Quake 2, since * it uses all AI features (dodging, sight, crouching, etc) and comes - * in a myriad of variants. In Xatrix it's even more complex due to - * another 4 variants added. + * in a myriad of variants. + * In Rogue it's even more complex due to the blindfire stuff. + * In Xatrix it's even more complex due to another 4 variants added. * * ======================================================================= */ @@ -47,8 +48,11 @@ static int sound_step2; static int sound_step3; static int sound_step4; +void soldier_duck_up(edict_t *self); void soldier_stand(edict_t *self); void soldier_run(edict_t *self); +void soldier_fire(edict_t *self, int); +void soldier_blind(edict_t *self); void soldierh_stand(edict_t *self); void soldierh_run(edict_t *self); extern void brain_dabeam(edict_t *self); @@ -89,6 +93,27 @@ soldier_footstep(edict_t *self) } } +void +soldier_start_charge(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags |= AI_CHARGING; +} + +void +soldier_stop_charge(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_CHARGING; +} void soldier_idle(edict_t *self) @@ -347,6 +372,20 @@ mmove_t soldier_move_start_run = soldier_run }; +void +soldier_fire_run(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->s.skinnum <= 1) && (self->enemy) && visible(self, self->enemy)) + { + soldier_fire(self, 0); + } +} + static mframe_t soldier_frames_run[] = { {ai_run, 10, NULL}, {ai_run, 11, soldier_footstep}, @@ -372,6 +411,8 @@ soldier_run(edict_t *self) return; } + monster_done_dodge(self); + if (self->monsterinfo.aiflags & AI_STAND_GROUND) { self->monsterinfo.currentmove = &soldier_move_stand1; @@ -498,6 +539,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */, self->s.skinnum |= 1; } + monster_done_dodge(self); + soldier_stop_charge(self); + + /* if we're blind firing, this needs to be turned off here */ + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + if (level.time < self->pain_debounce_time) { if ((self->velocity[2] > 100) && @@ -505,6 +552,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */, (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3))) { + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } + self->monsterinfo.currentmove = &soldier_move_pain4; } @@ -530,6 +583,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */, if (self->velocity[2] > 100) { + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } + self->monsterinfo.currentmove = &soldier_move_pain4; return; } @@ -553,6 +612,12 @@ soldier_pain(edict_t *self, edict_t *other /* unused */, { self->monsterinfo.currentmove = &soldier_move_pain3; } + + /* clear duck flag */ + if (self->monsterinfo.aiflags & AI_DUCKED) + { + monster_duck_up(self); + } } static int blaster_flash[] = @@ -592,7 +657,7 @@ static int machinegun_flash[] = }; void -soldier_fire(edict_t *self, int flash_number) +soldier_fire(edict_t *self, int in_flash_number) { vec3_t start; vec3_t forward, right, up; @@ -601,12 +666,33 @@ soldier_fire(edict_t *self, int flash_number) vec3_t end; float r, u; int flash_index; + int flash_number; if (!self) { return; } + vec3_t aim_norm; + float angle; + trace_t tr; + vec3_t aim_good; + + if ((!self->enemy) || (!self->enemy->inuse)) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + return; + } + + if (in_flash_number < 0) + { + flash_number = -1 * in_flash_number; + } + else + { + flash_number = in_flash_number; + } + if (self->s.skinnum < 2) { flash_index = blaster_flash[flash_number]; @@ -624,7 +710,7 @@ soldier_fire(edict_t *self, int flash_number) G_ProjectSource(self->s.origin, monster_flash_offset[flash_index], forward, right, start); - if ((flash_number == 5) || (flash_number == 6)) + if ((flash_number == 5) || (flash_number == 6)) /* he's dead */ { VectorCopy(forward, aim); } @@ -633,11 +719,34 @@ soldier_fire(edict_t *self, int flash_number) VectorCopy(self->enemy->s.origin, end); end[2] += self->enemy->viewheight; VectorSubtract(end, start, aim); + VectorCopy(end, aim_good); + + if (in_flash_number < 0) + { + VectorCopy(aim, aim_norm); + VectorNormalize(aim_norm); + angle = DotProduct(aim_norm, forward); + + if (angle < 0.9) /* ~25 degree angle */ + { + return; + } + } + vectoangles(aim, dir); AngleVectors(dir, forward, right, up); - r = crandom() * 1000; - u = crandom() * 500; + if (skill->value < SKILL_HARD) + { + r = crandom() * 1000; + u = crandom() * 500; + } + else + { + r = crandom() * 500; + u = crandom() * 250; + } + VectorMA(start, 8192, forward, end); VectorMA(end, r, right, end); VectorMA(end, u, up, end); @@ -646,6 +755,16 @@ soldier_fire(edict_t *self, int flash_number) VectorNormalize(aim); } + if (!((flash_number == 5) || (flash_number == 6))) /* he's dead */ + { + tr = gi.trace(start, NULL, NULL, aim_good, self, MASK_SHOT); + + if ((tr.ent != self->enemy) && (tr.ent != world)) + { + return; + } + } + if (self->s.skinnum <= 1) { monster_fire_blaster(self, start, aim, 5, 600, flash_index, EF_BLASTER); @@ -658,6 +777,7 @@ soldier_fire(edict_t *self, int flash_number) } else { + /* changed to wait from pausetime to not interfere with dodge code */ if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) { self->monsterinfo.pausetime = level.time + (3 + randk() % 8) * FRAMETIME; @@ -698,6 +818,17 @@ soldier_attack1_refire1(edict_t *self) return; } + if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) + { + self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; + return; + } + + if (!self->enemy) + { + return; + } + if (self->s.skinnum > 1) { return; @@ -727,6 +858,11 @@ soldier_attack1_refire2(edict_t *self) return; } + if (!self->enemy) + { + return; + } + if (self->s.skinnum < 2) { return; @@ -787,6 +923,11 @@ soldier_attack2_refire1(edict_t *self) return; } + if (!self->enemy) + { + return; + } + if (self->s.skinnum > 1) { return; @@ -816,6 +957,11 @@ soldier_attack2_refire2(edict_t *self) return; } + if (!self->enemy) + { + return; + } + if (self->s.skinnum < 2) { return; @@ -992,24 +1138,33 @@ soldier_attack6_refire(edict_t *self) return; } + /* make sure dodge & charge bits are cleared */ + monster_done_dodge(self); + soldier_stop_charge(self); + + if (!self->enemy) + { + return; + } + if (self->enemy->health <= 0) { return; } - if (range(self, self->enemy) < RANGE_MID) + if (range(self, self->enemy) < RANGE_NEAR) { return; } - if (skill->value == SKILL_HARDPLUS) + if ((skill->value == SKILL_HARDPLUS) || ((random() < (0.25 * ((float)skill->value))))) { self->monsterinfo.nextframe = FRAME_runs03; } } static mframe_t soldier_frames_attack6[] = { - {ai_charge, 10, NULL}, + {ai_charge, 10, soldier_start_charge}, {ai_charge, 4, NULL}, {ai_charge, 12, soldier_footstep}, {ai_charge, 11, soldier_fire8}, @@ -1036,25 +1191,82 @@ mmove_t soldier_move_attack6 = void soldier_attack(edict_t *self) { + float r, chance; + if (!self) { return; } - if (self->s.skinnum < 4) + monster_done_dodge(self); + + /* blindfire! */ + if (self->monsterinfo.attack_state == AS_BLIND) { - if (random() < 0.5) + /* setup shot probabilities */ + if (self->monsterinfo.blind_fire_delay < 1.0) { - self->monsterinfo.currentmove = &soldier_move_attack1; + chance = 1.0; + } + else if (self->monsterinfo.blind_fire_delay < 7.5) + { + chance = 0.4; } else { - self->monsterinfo.currentmove = &soldier_move_attack2; + chance = 0.1; } + + r = random(); + + /* minimum of 2 seconds, plus 0-3, after the shots are done */ + self->monsterinfo.blind_fire_delay += 2.1 + 2.0 + random() * 3.0; + + /* don't shoot at the origin */ + if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin)) + { + return; + } + + /* don't shoot if the dice say not to */ + if (r > chance) + { + return; + } + + /* turn on manual steering to signal both manual steering and blindfire */ + self->monsterinfo.aiflags |= AI_MANUAL_STEERING; + self->monsterinfo.currentmove = &soldier_move_attack1; + self->monsterinfo.attack_finished = level.time + 1.5 + random(); + return; + } + + r = random(); + + if ((!(self->monsterinfo.aiflags & (AI_BLOCKED | AI_STAND_GROUND))) && + (range(self, self->enemy) >= RANGE_NEAR) && + ((r < (skill->value * 0.25)) && + (self->s.skinnum <= 3))) + { + self->monsterinfo.currentmove = &soldier_move_attack6; } else { - self->monsterinfo.currentmove = &soldier_move_attack4; + if (self->s.skinnum < 4) + { + if (random() < 0.5) + { + self->monsterinfo.currentmove = &soldier_move_attack1; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack2; + } + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack4; + } } } @@ -1075,9 +1287,11 @@ soldier_sight(edict_t *self, edict_t *other /* unused */) gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); } - if ((skill->value > SKILL_EASY) && (range(self, self->enemy) >= RANGE_MID)) + if ((skill->value > SKILL_EASY) && (self->enemy) && + (range(self, self->enemy) >= RANGE_NEAR)) { - if (random() > 0.5) + /* don't let machinegunners run & shoot */ + if ((random() > 0.75) && (self->s.skinnum <= 3)) { self->monsterinfo.currentmove = &soldier_move_attack6; } @@ -1118,6 +1332,29 @@ mmove_t soldier_move_duck = soldier_run }; +qboolean +soldier_blocked(edict_t *self, float dist) +{ + if (!self) + { + return false; + } + + /* don't do anything if you're dodging */ + if ((self->monsterinfo.aiflags & AI_DODGING) || + (self->monsterinfo.aiflags & AI_DUCKED)) + { + return false; + } + + if (blocked_checkplat(self, dist)) + { + return true; + } + + return false; +} + void soldier_dodge(edict_t *self, edict_t *attacker, float eta, trace_t *tr /* unused */) @@ -1220,6 +1457,46 @@ soldier_dead(edict_t *self) gi.linkentity(self); } +void +soldier_dead2(edict_t *self) +{ + vec3_t tempmins, tempmaxs, temporg; + trace_t tr; + + if (!self) + { + return; + } + + VectorCopy(self->s.origin, temporg); + /* this is because location traces done at the + floor are guaranteed to hit the floor (inside + the sv_trace code it grows the bbox by 1 in + all directions) */ + temporg[2] += 1; + + VectorSet(tempmins, -32, -32, -24); + VectorSet(tempmaxs, 32, 32, -8); + + tr = gi.trace(temporg, tempmins, tempmaxs, temporg, self, MASK_SOLID); + + if (tr.startsolid || tr.allsolid) + { + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + } + else + { + VectorCopy(tempmins, self->mins); + VectorCopy(tempmaxs, self->maxs); + } + + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + static mframe_t soldier_frames_death1[] = { {ai_move, 0, NULL}, {ai_move, -10, NULL}, @@ -1445,7 +1722,7 @@ mmove_t soldier_move_death4 = FRAME_death401, FRAME_death453, soldier_frames_death4, - soldier_dead + soldier_dead2 }; static mframe_t soldier_frames_death5[] = { @@ -1591,6 +1868,117 @@ soldier_die(edict_t *self, edict_t *inflictor /* unused */, } } +void +soldier_sidestep(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum <= 3) + { + if (self->monsterinfo.currentmove != &soldier_move_attack6) + { + self->monsterinfo.currentmove = &soldier_move_attack6; + } + } + else + { + if (self->monsterinfo.currentmove != &soldier_move_start_run) + { + self->monsterinfo.currentmove = &soldier_move_start_run; + } + } +} + +void +soldier_duck(edict_t *self, float eta) +{ + float r; + + if (!self) + { + return; + } + + /* has to be done immediately otherwise he can get stuck */ + monster_duck_down(self); + + if (skill->value == SKILL_EASY) + { + self->monsterinfo.currentmove = &soldier_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + 1; + return; + } + + r = random(); + + if (r > (skill->value * 0.3)) + { + self->monsterinfo.currentmove = &soldier_move_duck; + self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack3; + self->monsterinfo.duck_wait_time = level.time + eta + 1; + } +} + +static mframe_t soldier_frames_blind[] = { + {ai_move, 0, soldier_idle}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldier_move_blind = { + FRAME_stand101, + FRAME_stand130, + soldier_frames_blind, + soldier_blind +}; + +void +soldier_blind(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.currentmove = &soldier_move_blind; +} + void SP_monster_soldier_x(edict_t *self) { @@ -1631,6 +2019,16 @@ SP_monster_soldier_x(edict_t *self) self->monsterinfo.melee = NULL; self->monsterinfo.sight = soldier_sight; + self->monsterinfo.blocked = soldier_blocked; + self->monsterinfo.duck = soldier_duck; + self->monsterinfo.unduck = monster_duck_up; + self->monsterinfo.sidestep = soldier_sidestep; + + if (self->spawnflags & 8) /* blind */ + { + self->monsterinfo.stand = soldier_blind; + } + gi.linkentity(self); self->monsterinfo.stand(self); @@ -1639,7 +2037,9 @@ SP_monster_soldier_x(edict_t *self) } /* - * QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + * QUAKED monster_soldier_light (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + * + * Blind - monster will just stand there until triggered */ void SP_monster_soldier_light(edict_t *self) @@ -1671,10 +2071,13 @@ SP_monster_soldier_light(edict_t *self) gi.soundindex("soldier/solatck2.wav"); self->s.skinnum = 0; + self->monsterinfo.blindfire = true; } /* - * QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + * QUAKED monster_soldier (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + * + * Blind - monster will just stand there until triggered */ void SP_monster_soldier(edict_t *self) @@ -1707,7 +2110,9 @@ SP_monster_soldier(edict_t *self) } /* - * QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + * QUAKED monster_soldier_ss (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Blind + * + * Blind - monster will just stand there until triggered */ void SP_monster_soldier_ss(edict_t *self) diff --git a/src/game/savegame/tables/gamefunc_decs.h b/src/game/savegame/tables/gamefunc_decs.h index 0b47cbcd..541554e3 100644 --- a/src/game/savegame/tables/gamefunc_decs.h +++ b/src/game/savegame/tables/gamefunc_decs.h @@ -1188,7 +1188,7 @@ extern void rotating_use ( edict_t * self , edict_t * other , edict_t * activato extern void rotating_touch ( edict_t * self , edict_t * other , cplane_t * plane , csurface_t * surf ) ; extern void rotating_blocked ( edict_t * self , edict_t * other ) ; extern void SP_func_plat ( edict_t * ent ) ; -extern void plat_spawn_inside_trigger ( edict_t * ent ) ; +extern edict_t * plat_spawn_inside_trigger ( edict_t * ent ) ; extern void Touch_Plat_Center ( edict_t * ent , edict_t * other , cplane_t * plane , csurface_t * surf ) ; extern void Use_Plat ( edict_t * ent , edict_t * other , edict_t * activator ) ; extern void plat_blocked ( edict_t * self , edict_t * other ) ; diff --git a/src/rogue/g_func.c b/src/rogue/g_func.c index ea991951..12a0a819 100644 --- a/src/rogue/g_func.c +++ b/src/rogue/g_func.c @@ -1,9 +1,23 @@ /* + * Copyright (C) 1997-2001 Id Software, Inc. * Copyright (c) ZeniMax Media Inc. - * Licensed under the GNU General Public License 2.0. - */ - -/* + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * * ======================================================================= * * Level functions. Platforms, buttons, dooors and so on. @@ -138,13 +152,13 @@ Move_Final(edict_t *ent) void Move_Begin(edict_t *ent) { + float frames; + if (!ent) { return; } - float frames; - if ((ent->moveinfo.speed * FRAMETIME) >= ent->moveinfo.remaining_distance) { Move_Final(ent); @@ -152,9 +166,11 @@ Move_Begin(edict_t *ent) } VectorScale(ent->moveinfo.dir, ent->moveinfo.speed, ent->velocity); - frames = floor( (ent->moveinfo.remaining_distance / + frames = floor( + (ent->moveinfo.remaining_distance / ent->moveinfo.speed) / FRAMETIME); - ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * FRAMETIME; + ent->moveinfo.remaining_distance -= frames * ent->moveinfo.speed * + FRAMETIME; ent->nextthink = level.time + (frames * FRAMETIME); ent->think = Move_Final; } @@ -195,8 +211,10 @@ Move_Calc(edict_t *ent, vec3_t dest, void (*func)(edict_t *)) } } -/* Support routines for angular movement - (changes in angle using avelocity) */ +/* + * Support routines for angular movement + * (changes in angle using avelocity) + */ void AngleMove_Done(edict_t *ent) { @@ -366,8 +384,12 @@ plat_CalcAcceleratedMove(moveinfo_t *moveinfo) { float f; - f = (moveinfo->accel + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); - moveinfo->move_speed = (-2 + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); + f = + (moveinfo->accel + + moveinfo->decel) / (moveinfo->accel * moveinfo->decel); + moveinfo->move_speed = + (-2 + + sqrt(4 - 4 * f * (-2 * moveinfo->remaining_distance))) / (2 * f); decel_dist = AccelerationDistance(moveinfo->move_speed, moveinfo->decel); } @@ -458,8 +480,10 @@ plat_Accelerate(moveinfo_t *moveinfo) p1_speed = (old_speed + moveinfo->move_speed) / 2.0; p2_distance = moveinfo->move_speed * (1.0 - (p1_distance / p1_speed)); distance = p1_distance + p2_distance; - moveinfo->current_speed = (p1_speed * (p1_distance / - distance)) + (moveinfo->move_speed * (p2_distance / distance)); + moveinfo->current_speed = + (p1_speed * + (p1_distance / + distance)) + (moveinfo->move_speed * (p2_distance / distance)); moveinfo->next_speed = moveinfo->move_speed - moveinfo->decel * (p2_distance / distance); return; @@ -469,6 +493,10 @@ plat_Accelerate(moveinfo_t *moveinfo) return; } +/* + * The team has completed a frame of movement, + * so change the speed for the next frame + */ void Think_AccelMove(edict_t *ent) { @@ -478,7 +506,12 @@ Think_AccelMove(edict_t *ent) } ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed; - plat_CalcAcceleratedMove(&ent->moveinfo); + + if (ent->moveinfo.current_speed == 0) /* starting or blocked */ + { + plat_CalcAcceleratedMove(&ent->moveinfo); + } + plat_Accelerate(&ent->moveinfo); /* will the entire move complete on next frame? */ @@ -506,8 +539,8 @@ plat_hit_top(edict_t *ent) { if (ent->moveinfo.sound_end) { - gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, ent->moveinfo.sound_end, - 1, ATTN_STATIC, 0); + gi.sound(ent, CHAN_NO_PHS_ADD + CHAN_VOICE, + ent->moveinfo.sound_end, 1, ATTN_STATIC, 0); } ent->s.sound = 0; @@ -698,7 +731,6 @@ wait_and_change(edict_t* ent, void (*afterwaitfunc)(edict_t *)) else { afterwaitfunc(ent); - } } @@ -729,7 +761,8 @@ Touch_Plat_Center(edict_t *ent, edict_t *other, cplane_t *plane /* unsed */, } else if (ent->moveinfo.state == STATE_TOP) { - ent->nextthink = level.time + 1; /* the player is still on the plat, so delay going down */ + /* the player is still on the plat, so delay going down */ + ent->nextthink = level.time + 1; } } @@ -1472,12 +1505,13 @@ SP_func_plat2(edict_t *ent) /* ==================================================================== */ -/* QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL +/* + * QUAKED func_rotating (0 .5 .8) ? START_ON REVERSE X_AXIS Y_AXIS TOUCH_PAIN STOP ANIMATED ANIMATED_FAST EAST MED HARD DM COOP ACCEL * - * You need to have an origin brush as part of this entity. The center - * of that brush will bethe point around which it is rotated. It will - * rotate around the Z axis by default. You can check either the - * X_AXIS or Y_AXIS box to change that. + * You need to have an origin brush as part of this entity. + * The center of that brush will be the point around which it + * is rotated. It will rotate around the Z axis by default. + * You can check either the X_AXIS or Y_AXIS box to change that. * * func_rotating will use it's targets when it stops and starts. * @@ -1667,7 +1701,6 @@ SP_func_rotating(edict_t *ent) } ent->use = rotating_use; - ent->blocked = rotating_blocked; if (ent->spawnflags & 1) @@ -1949,7 +1982,6 @@ SP_func_button(edict_t *ent) gi.linkentity(ent); } - /* ==================================================================== */ /* @@ -2501,9 +2533,9 @@ door_blocked(edict_t *self, edict_t *other) return; } - /* if a door has a negative wait, it would never come - back if blocked, so let it just squash the object - to death real fast */ + /* if a door has a negative wait, it would never + come back if blocked, so let it just squash the + object to death real fast */ if (self->moveinfo.wait >= 0) { if (self->moveinfo.state == STATE_DOWN) @@ -2762,6 +2794,7 @@ Door_Activate(edict_t *self, edict_t *other /* unused */, * 3) medium * 4) heavy */ + void SP_func_door_rotating(edict_t *ent) { @@ -3014,7 +3047,8 @@ SP_func_water(edict_t *self) self->speed = 25; } - self->moveinfo.accel = self->moveinfo.decel = self->moveinfo.speed = self->speed; + self->moveinfo.accel = self->moveinfo.decel = + self->moveinfo.speed = self->speed; if (self->spawnflags & 2) /* smart water */ { @@ -3045,6 +3079,8 @@ SP_func_water(edict_t *self) gi.linkentity(self); } +/* ==================================================================== */ + /* * QUAKED func_train (0 .5 .8) ? START_ON TOGGLE BLOCK_STOPS * @@ -3135,6 +3171,7 @@ train_wait(edict_t *self) else if (self->spawnflags & TRAIN_TOGGLE) { self->target_ent = NULL; + train_next(self); self->spawnflags &= ~TRAIN_START_ON; VectorClear(self->velocity); self->nextthink = 0; @@ -3145,8 +3182,8 @@ train_wait(edict_t *self) if (self->moveinfo.sound_end) { gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, - self->moveinfo.sound_end, - 1, ATTN_STATIC, 0); + self->moveinfo.sound_end, 1, + ATTN_STATIC, 0); } self->s.sound = 0; @@ -3178,6 +3215,7 @@ train_next(edict_t *self) first = true; again: + if (!self->target) { return; @@ -3430,7 +3468,7 @@ SP_func_train(edict_t *self) if (self->target) { /* start trains on the second frame, to make - sure their targets have had a chance to spawn */ + * sure their targets have had a chance to spawn */ self->nextthink = level.time + FRAMETIME; self->think = func_train_find; } @@ -3440,6 +3478,8 @@ SP_func_train(edict_t *self) } } +/* ==================================================================== */ + /* * QUAKED trigger_elevator (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) */ @@ -3554,7 +3594,7 @@ func_timer_think(edict_t *self) void func_timer_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { - if (!self) + if (!self || !activator) { return; } @@ -3853,16 +3893,16 @@ door_secret_die(edict_t *self, edict_t *inflictor /* unused */, void SP_func_door_secret(edict_t *ent) { - if (!ent) - { - return; - } - vec3_t forward, right, up; float side; float width; float length; + if (!ent) + { + return; + } + ent->moveinfo.sound_start = gi.soundindex("doors/dr1_strt.wav"); ent->moveinfo.sound_middle = gi.soundindex("doors/dr1_mid.wav"); ent->moveinfo.sound_end = gi.soundindex("doors/dr1_end.wav"); @@ -3979,3 +4019,231 @@ SP_func_killbox(edict_t *ent) ent->use = use_killbox; ent->svflags = SVF_NOCLIENT; } + +/* + * QUAKED rotating_light (0 .5 .8) (-8 -8 -8) (8 8 8) START_OFF ALARM + * "health" if set, the light may be killed. + */ + +#define START_OFF 1 + +void +rotating_light_alarm(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->spawnflags & START_OFF) + { + self->think = NULL; + self->nextthink = 0; + } + else + { + gi.sound(self, CHAN_NO_PHS_ADD + CHAN_VOICE, + self->moveinfo.sound_start, 1, + ATTN_STATIC, 0); + self->nextthink = level.time + 1; + } +} + +void +rotating_light_killed(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage /* unused */, + vec3_t point /* unused */) +{ + if (!self) + { + return; + } + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(30); + gi.WritePosition(self->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(0xe0 + (rand() & 7)); + gi.multicast(self->s.origin, MULTICAST_PVS); + + self->s.effects &= ~EF_SPINNINGLIGHTS; + self->use = NULL; + + self->think = G_FreeEdict; + self->nextthink = level.time + 0.1; +} + +void +rotating_light_use(edict_t *self, edict_t *other /* unused */, + edict_t *activator /* unused */) +{ + if (!self) + { + return; + } + + if (self->spawnflags & START_OFF) + { + self->spawnflags &= ~START_OFF; + self->s.effects |= EF_SPINNINGLIGHTS; + + if (self->spawnflags & 2) + { + self->think = rotating_light_alarm; + self->nextthink = level.time + 0.1; + } + } + else + { + self->spawnflags |= START_OFF; + self->s.effects &= ~EF_SPINNINGLIGHTS; + } +} + +void +SP_rotating_light(edict_t *self) +{ + if (!self) + { + return; + } + + self->movetype = MOVETYPE_STOP; + self->solid = SOLID_BBOX; + + self->s.modelindex = gi.modelindex("models/objects/light/tris.md2"); + + self->s.frame = 0; + + self->use = rotating_light_use; + + if (self->spawnflags & START_OFF) + { + self->s.effects &= ~EF_SPINNINGLIGHTS; + } + else + { + self->s.effects |= EF_SPINNINGLIGHTS; + } + + if (!self->speed) + { + self->speed = 32; + } + + if (!self->health) + { + self->health = 10; + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + else + { + self->max_health = self->health; + self->die = rotating_light_killed; + self->takedamage = DAMAGE_YES; + } + + if (self->spawnflags & 2) + { + self->moveinfo.sound_start = gi.soundindex("misc/alarm.wav"); + } + + gi.linkentity(self); +} + +/* + * QUAKED func_object_repair (1 .5 0) (-8 -8 -8) (8 8 8) + * object to be repaired. + * The default delay is 1 second + * "delay" the delay in seconds for spark to occur + */ +void +object_repair_fx(edict_t *ent) +{ + if (!ent) + { + return; + } + + ent->nextthink = level.time + ent->delay; + + if (ent->health <= 100) + { + ent->health++; + } + else + { + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(10); + gi.WritePosition(ent->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(0xe0 + (rand() & 7)); + gi.multicast(ent->s.origin, MULTICAST_PVS); + } +} + +void +object_repair_dead(edict_t *ent) +{ + if (!ent) + { + return; + } + + G_UseTargets(ent, ent); + ent->nextthink = level.time + 0.1; + ent->think = object_repair_fx; +} + +void +object_repair_sparks(edict_t *ent) +{ + if (!ent) + { + return; + } + + if (ent->health < 0) + { + ent->nextthink = level.time + 0.1; + ent->think = object_repair_dead; + return; + } + + ent->nextthink = level.time + ent->delay; + + gi.WriteByte(svc_temp_entity); + gi.WriteByte(TE_WELDING_SPARKS); + gi.WriteByte(10); + gi.WritePosition(ent->s.origin); + gi.WriteDir(vec3_origin); + gi.WriteByte(0xe0 + (rand() & 7)); + gi.multicast(ent->s.origin, MULTICAST_PVS); +} + +void +SP_object_repair(edict_t *ent) +{ + if (!ent) + { + return; + } + + ent->movetype = MOVETYPE_NONE; + ent->solid = SOLID_BBOX; + ent->classname = "object_repair"; + ent->think = object_repair_sparks; + ent->nextthink = level.time + 1.0; + ent->health = 100; + + if (!ent->delay) + { + ent->delay = 1.0; + } + + gi.linkentity(ent); +} diff --git a/src/rogue/g_items.c b/src/rogue/g_items.c index 093e006f..902cd19a 100644 --- a/src/rogue/g_items.c +++ b/src/rogue/g_items.c @@ -1,9 +1,23 @@ /* + * Copyright (C) 1997-2001 Id Software, Inc. * Copyright (c) ZeniMax Media Inc. - * Licensed under the GNU General Public License 2.0. - */ - -/* + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * * ======================================================================= * * Item handling and item definitions. @@ -40,18 +54,25 @@ void Weapon_Prox(edict_t *ent); void Weapon_Tesla(edict_t *ent); void Weapon_ProxLauncher(edict_t *ent); -gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET}; -gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT}; -gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; +void Weapon_Ionripper(edict_t *ent); +void Weapon_Phalanx(edict_t *ent); +void Weapon_Trap(edict_t *ent); -int jacket_armor_index; -int combat_armor_index; -int body_armor_index; +static gitem_armor_t jacketarmor_info = {25, 50, .30, .00, ARMOR_JACKET}; +static gitem_armor_t combatarmor_info = {50, 100, .60, .30, ARMOR_COMBAT}; +static gitem_armor_t bodyarmor_info = {100, 200, .80, .60, ARMOR_BODY}; + +static int jacket_armor_index; +static int combat_armor_index; +static int body_armor_index; static int power_screen_index; static int power_shield_index; void Use_Quad(edict_t *ent, gitem_t *item); +void Use_QuadFire(edict_t *ent, gitem_t *item); + static int quad_drop_timeout_hack; +static int quad_fire_drop_timeout_hack; /* ====================================================================== */ @@ -199,7 +220,7 @@ Pickup_Powerup(edict_t *ent, edict_t *other) { int quantity; - if (!ent || !other) + if (!ent || !other) { return false; } @@ -237,6 +258,7 @@ Drop_General(edict_t *ent, gitem_t *item) { return; } + Drop_Item(ent, item); ent->client->pers.inventory[ITEM_INDEX(item)]--; ValidateSelectedItem(ent); @@ -273,7 +295,7 @@ Pickup_Adrenaline(edict_t *ent, edict_t *other) qboolean Pickup_AncientHead(edict_t *ent, edict_t *other) { - if (!ent || !other) + if (!ent || !other) { return false; } @@ -291,14 +313,14 @@ Pickup_AncientHead(edict_t *ent, edict_t *other) qboolean Pickup_Bandolier(edict_t *ent, edict_t *other) { - if (!ent || !other) + gitem_t *item; + int index; + + if (!ent || !other) { return false; } - gitem_t *item; - int index; - if (other->client->pers.max_bullets < 250) { other->client->pers.max_bullets = 250; @@ -319,6 +341,11 @@ Pickup_Bandolier(edict_t *ent, edict_t *other) other->client->pers.max_slugs = 75; } + if (other->client->pers.max_magslug < 75) + { + other->client->pers.max_magslug = 75; + } + if (other->client->pers.max_flechettes < 250) { other->client->pers.max_flechettes = 250; @@ -376,7 +403,7 @@ Pickup_Pack(edict_t *ent, edict_t *other) gitem_t *item; int index; - if (!ent || !other) + if (!ent || !other) { return false; } @@ -411,6 +438,11 @@ Pickup_Pack(edict_t *ent, edict_t *other) other->client->pers.max_slugs = 100; } + if (other->client->pers.max_magslug < 100) + { + other->client->pers.max_magslug = 100; + } + if (other->client->pers.max_flechettes < 200) { other->client->pers.max_flechettes = 200; @@ -514,6 +546,21 @@ Pickup_Pack(edict_t *ent, edict_t *other) } } + item = FindItem("Mag Slug"); + + if (item) + { + index = ITEM_INDEX(item); + other->client->pers.inventory[index] += item->quantity; + + if (other->client->pers.inventory[index] > + other->client->pers.max_magslug) + { + other->client->pers.inventory[index] = + other->client->pers.max_magslug; + } + } + item = FindItem("Flechettes"); if (item) @@ -552,6 +599,8 @@ Pickup_Pack(edict_t *ent, edict_t *other) return true; } +/* ====================================================================== */ + qboolean Pickup_Nuke(edict_t *ent, edict_t *other) { @@ -872,8 +921,44 @@ Use_Quad(edict_t *ent, gitem_t *item) ent->client->quad_framenum = level.framenum + timeout; } - gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, - 0); + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/damage.wav"), 1, ATTN_NORM, 0); +} + +/* ===================================================================== */ + +void +Use_QuadFire(edict_t *ent, gitem_t *item) +{ + int timeout; + + if (!ent || !item) + { + return; + } + + ent->client->pers.inventory[ITEM_INDEX(item)]--; + ValidateSelectedItem(ent); + + if (quad_fire_drop_timeout_hack) + { + timeout = quad_fire_drop_timeout_hack; + quad_fire_drop_timeout_hack = 0; + } + else + { + timeout = 300; + } + + if (ent->client->quadfire_framenum > level.framenum) + { + ent->client->quadfire_framenum += timeout; + } + else + { + ent->client->quadfire_framenum = level.framenum + timeout; + } + + gi.sound(ent, CHAN_ITEM, gi.soundindex("items/quadfire1.wav"), 1, ATTN_NORM, 0); } /* ====================================================================== */ @@ -1021,7 +1106,6 @@ Add_Ammo(edict_t *ent, gitem_t *item, int count) return false; } - if (item->tag == AMMO_BULLETS) { max = ent->client->pers.max_bullets; @@ -1046,6 +1130,14 @@ Add_Ammo(edict_t *ent, gitem_t *item, int count) { max = ent->client->pers.max_slugs; } + else if (item->tag == AMMO_MAGSLUG) + { + max = ent->client->pers.max_magslug; + } + else if (item->tag == AMMO_TRAP) + { + max = ent->client->pers.max_trap; + } else if (item->tag == AMMO_FLECHETTES) { max = ent->client->pers.max_flechettes; @@ -1130,7 +1222,8 @@ Pickup_Ammo(edict_t *ent, edict_t *other) } } - if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && (deathmatch->value)) + if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM)) && + (deathmatch->value)) { SetRespawn(ent, 30); } @@ -1333,7 +1426,7 @@ Pickup_Armor(edict_t *ent, edict_t *other) { oldinfo = &combatarmor_info; } - else + else /* (old_armor_index == body_armor_index) */ { oldinfo = &bodyarmor_info; } @@ -1342,7 +1435,8 @@ Pickup_Armor(edict_t *ent, edict_t *other) { /* calc new armor values */ salvage = oldinfo->normal_protection / newinfo->normal_protection; - salvagecount = salvage * other->client->pers.inventory[old_armor_index]; + salvagecount = salvage * + other->client->pers.inventory[old_armor_index]; newcount = newinfo->base_count + salvagecount; if (newcount > newinfo->max_count) @@ -1361,7 +1455,8 @@ Pickup_Armor(edict_t *ent, edict_t *other) /* calc new armor values */ salvage = newinfo->normal_protection / oldinfo->normal_protection; salvagecount = salvage * newinfo->base_count; - newcount = other->client->pers.inventory[old_armor_index] + salvagecount; + newcount = other->client->pers.inventory[old_armor_index] + + salvagecount; if (newcount > oldinfo->max_count) { @@ -1425,10 +1520,16 @@ Use_PowerArmor(edict_t *ent, gitem_t *item) { int index; + if (!ent || !item) + { + return; + } + if (ent->flags & FL_POWER_ARMOR) { ent->flags &= ~FL_POWER_ARMOR; - gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power2.wav"), 1, ATTN_NORM, 0); + gi.sound(ent, CHAN_AUTO, gi.soundindex( + "misc/power2.wav"), 1, ATTN_NORM, 0); } else { @@ -1441,7 +1542,8 @@ Use_PowerArmor(edict_t *ent, gitem_t *item) } ent->flags |= FL_POWER_ARMOR; - gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/power1.wav"), 1, ATTN_NORM, 0); + gi.sound(ent, CHAN_AUTO, gi.soundindex( + "misc/power1.wav"), 1, ATTN_NORM, 0); } } @@ -1528,39 +1630,47 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_ other->client->bonus_alpha = 0.25; /* show icon and name on status bar */ - other->client->ps.stats[STAT_PICKUP_ICON] = gi.imageindex(ent->item->icon); - other->client->ps.stats[STAT_PICKUP_STRING] = CS_ITEMS + ITEM_INDEX(ent->item); + other->client->ps.stats[STAT_PICKUP_ICON] = + gi.imageindex(ent->item->icon); + other->client->ps.stats[STAT_PICKUP_STRING] = + CS_ITEMS + ITEM_INDEX(ent->item); other->client->pickup_msg_time = level.time + 3.0; /* change selected item */ if (ent->item->use) { other->client->pers.selected_item = - other->client->ps.stats[STAT_SELECTED_ITEM] = ITEM_INDEX(ent->item); + other->client->ps.stats[STAT_SELECTED_ITEM] = + ITEM_INDEX(ent->item); } if (ent->item->pickup == Pickup_Health) { if (ent->count == 2) { - gi.sound(other, CHAN_ITEM, gi.soundindex("items/s_health.wav"), 1, ATTN_NORM, 0); + gi.sound(other, CHAN_ITEM, gi.soundindex( + "items/s_health.wav"), 1, ATTN_NORM, 0); } else if (ent->count == 10) { - gi.sound(other, CHAN_ITEM, gi.soundindex("items/n_health.wav"), 1, ATTN_NORM, 0); + gi.sound(other, CHAN_ITEM, gi.soundindex( + "items/n_health.wav"), 1, ATTN_NORM, 0); } else if (ent->count == 25) { - gi.sound(other, CHAN_ITEM, gi.soundindex("items/l_health.wav"), 1, ATTN_NORM, 0); + gi.sound(other, CHAN_ITEM, gi.soundindex( + "items/l_health.wav"), 1, ATTN_NORM, 0); } else /* (ent->count == 100) */ { - gi.sound(other, CHAN_ITEM, gi.soundindex("items/m_health.wav"), 1, ATTN_NORM, 0); + gi.sound(other, CHAN_ITEM, gi.soundindex( + "items/m_health.wav"), 1, ATTN_NORM, 0); } } else if (ent->item->pickup_sound) { - gi.sound(other, CHAN_ITEM, gi.soundindex(ent->item->pickup_sound), 1, ATTN_NORM, 0); + gi.sound(other, CHAN_ITEM, gi.soundindex( + ent->item->pickup_sound), 1, ATTN_NORM, 0); } /* activate item instantly if appropriate */ @@ -1602,7 +1712,8 @@ Touch_Item(edict_t *ent, edict_t *other, cplane_t *plane /* unused */, csurface_ return; } - if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || + if (!((coop->value) && + (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) { if (ent->flags & FL_RESPAWN) @@ -1631,6 +1742,10 @@ drop_temp_touch(edict_t *ent, edict_t *other, cplane_t *plane, csurface_t *surf) return; } + /* plane and surf are unused in Touch_Item + but since the function is part of the + game <-> client interface dropping + them is too much pain. */ Touch_Item(ent, other, plane, surf); } @@ -1684,7 +1799,8 @@ Drop_Item(edict_t *ent, gitem_t *item) AngleVectors(ent->client->v_angle, forward, right, NULL); VectorSet(offset, 24, 0, -16); - G_ProjectSource(ent->s.origin, offset, forward, right, dropped->s.origin); + G_ProjectSource(ent->s.origin, offset, forward, right, + dropped->s.origin); trace = gi.trace(ent->s.origin, dropped->mins, dropped->maxs, dropped->s.origin, ent, CONTENTS_SOLID); VectorCopy(trace.endpos, dropped->s.origin); @@ -1736,15 +1852,15 @@ Use_Item(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused void droptofloor(edict_t *ent) { + trace_t tr; + vec3_t dest; + float *v; + if (!ent) { return; } - trace_t tr; - vec3_t dest; - float *v; - v = tv(-15, -15, -15); VectorCopy(v, ent->mins); v = tv(15, 15, 15); @@ -1770,10 +1886,19 @@ droptofloor(edict_t *ent) if (tr.startsolid) { - gi.dprintf("droptofloor: %s startsolid at %s\n", ent->classname, - vtos(ent->s.origin)); - G_FreeEdict(ent); - return; + if (strcmp(ent->classname, "foodcube") == 0) + { + VectorCopy(ent->s.origin, tr.endpos); + ent->velocity[2] = 0; + } + else + { + gi.dprintf("droptofloor: %s startsolid at %s\n", + ent->classname, + vtos(ent->s.origin)); + G_FreeEdict(ent); + return; + } } VectorCopy(tr.endpos, ent->s.origin); @@ -2127,12 +2252,15 @@ SpawnItem(edict_t *ent, gitem_t *item) /* ====================================================================== */ -gitem_t itemlist[] = { +static const gitem_t gameitemlist[] = { { NULL }, /* leave index 0 alone */ - /* QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */ + + /* + * QUAKED item_armor_body (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN + */ { "item_armor_body", Pickup_Armor, @@ -3497,6 +3625,8 @@ gitem_t itemlist[] = { {NULL} }; +gitem_t itemlist[MAX_ITEMS]; + /* * QUAKED item_health (.3 .3 1) (-16 -16 -16) (16 16 16) TRIGGER_SPAWN */ @@ -3616,7 +3746,9 @@ SP_item_foodcube(edict_t *self) void InitItems(void) { - game.num_items = sizeof(itemlist) / sizeof(itemlist[0]) - 1; + memset(itemlist, 0, sizeof(itemlist)); + memcpy(itemlist, gameitemlist, sizeof(gameitemlist)); + game.num_items = sizeof(gameitemlist) / sizeof(gameitemlist[0]) - 1; } /* diff --git a/src/rogue/g_misc.c b/src/rogue/g_misc.c deleted file mode 100644 index 434df7c4..00000000 --- a/src/rogue/g_misc.c +++ /dev/null @@ -1,2733 +0,0 @@ -/* - * Copyright (c) ZeniMax Media Inc. - * Licensed under the GNU General Public License 2.0. - */ - -/* - * ======================================================================= - * - * Miscellaneos entities, functs and functions. - * - * ======================================================================= - */ - -#include "header/local.h" - -int debristhisframe; -int gibsthisframe; - -extern void M_WorldEffects(edict_t *ent); - -/* ===================================================== */ - -void -Use_Areaportal(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!ent) - { - return; - } - - ent->count ^= 1; /* toggle state */ - gi.SetAreaPortalState(ent->style, ent->count); -} - -/* - * QUAKED func_areaportal (0 0 0) ? - * - * This is a non-visible object that divides the world into - * areas that are seperated when this portal is not activated. - * Usually enclosed in the middle of a door. - */ -void -SP_func_areaportal(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->use = Use_Areaportal; - ent->count = 0; /* always start closed; */ -} - -/* ===================================================== */ - -void -VelocityForDamage(int damage, vec3_t v) -{ - v[0] = 100.0 * crandom(); - v[1] = 100.0 * crandom(); - v[2] = 200.0 + 100.0 * random(); - - if (damage < 50) - { - VectorScale(v, 0.7, v); - } - else - { - VectorScale(v, 1.2, v); - } -} - -void -ClipGibVelocity(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (ent->velocity[0] < -300) - { - ent->velocity[0] = -300; - } - else if (ent->velocity[0] > 300) - { - ent->velocity[0] = 300; - } - - if (ent->velocity[1] < -300) - { - ent->velocity[1] = -300; - } - else if (ent->velocity[1] > 300) - { - ent->velocity[1] = 300; - } - - if (ent->velocity[2] < 200) - { - ent->velocity[2] = 200; /* always some upwards */ - } - else if (ent->velocity[2] > 500) - { - ent->velocity[2] = 500; - } -} - -void -gib_think(edict_t *self) -{ - if (!self) - { - return; - } - - self->s.frame++; - self->nextthink = level.time + FRAMETIME; - - if (self->s.frame == 10) - { - self->think = G_FreeEdict; - self->nextthink = level.time + 8 + random() * 10; - } -} - -void -gib_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane, csurface_t *surf /* unused */) -{ - vec3_t normal_angles, right; - - if (!self) - { - return; - } - - if (!self->groundentity) - { - return; - } - - self->touch = NULL; - - if (plane) - { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/fhit3.wav"), 1, ATTN_NORM, 0); - - vectoangles(plane->normal, normal_angles); - AngleVectors(normal_angles, NULL, right, NULL); - vectoangles(right, self->s.angles); - - if (self->s.modelindex == sm_meat_index) - { - self->s.frame++; - self->think = gib_think; - self->nextthink = level.time + FRAMETIME; - } - } -} - -void -gib_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, int damage /* unused */, vec3_t point /* unused */) -{ - if (!self) - { - return; - } - - G_FreeEdict(self); -} - -void -ThrowGib(edict_t *self, char *gibname, int damage, int type) -{ - edict_t *gib; - vec3_t vd; - vec3_t origin; - vec3_t size; - float vscale; - - if (!self || !gibname) - { - return; - } - - if (gibsthisframe > MAX_GIBS) - { - return; - } - - gib = G_SpawnOptional(); - - if (!gib) - { - return; - } - - gibsthisframe++; - - VectorScale(self->size, 0.5, size); - VectorAdd(self->absmin, size, origin); - gib->s.origin[0] = origin[0] + crandom() * size[0]; - gib->s.origin[1] = origin[1] + crandom() * size[1]; - gib->s.origin[2] = origin[2] + crandom() * size[2]; - - gi.setmodel(gib, gibname); - gib->solid = SOLID_BBOX; - gib->svflags = SVF_DEADMONSTER; - gib->s.effects |= EF_GIB; - gib->flags |= FL_NO_KNOCKBACK; - gib->takedamage = DAMAGE_YES; - gib->die = gib_die; - gib->health = 250; - - if (type == GIB_ORGANIC) - { - gib->movetype = MOVETYPE_TOSS; - gib->touch = gib_touch; - vscale = 0.5; - } - else - { - gib->movetype = MOVETYPE_BOUNCE; - vscale = 1.0; - } - - VelocityForDamage(damage, vd); - VectorMA(self->velocity, vscale, vd, gib->velocity); - ClipGibVelocity(gib); - gib->avelocity[0] = random() * 600; - gib->avelocity[1] = random() * 600; - gib->avelocity[2] = random() * 600; - - gib->think = G_FreeEdict; - gib->nextthink = level.time + 10 + random() * 10; - gib->s.renderfx |= RF_IR_VISIBLE; - - gi.linkentity(gib); -} - -void -ThrowHead(edict_t *self, char *gibname, int damage, int type) -{ - vec3_t vd; - float vscale; - - if (!self || !gibname) - { - return; - } - - self->s.skinnum = 0; - self->s.frame = 0; - VectorClear(self->mins); - VectorClear(self->maxs); - - self->s.modelindex2 = 0; - gi.setmodel(self, gibname); - self->solid = SOLID_BBOX; - self->s.effects |= EF_GIB; - self->s.effects &= ~EF_FLIES; - self->s.sound = 0; - self->flags |= FL_NO_KNOCKBACK; - self->svflags &= ~SVF_MONSTER; - self->takedamage = DAMAGE_YES; - self->targetname = NULL; - self->die = gib_die; - - // The entity still has the monsters clipmaks. - // Reset it to MASK_SHOT to be on the save side. - self->clipmask = MASK_SHOT; - - if (type == GIB_ORGANIC) - { - self->movetype = MOVETYPE_TOSS; - self->touch = gib_touch; - vscale = 0.5; - } - else - { - self->movetype = MOVETYPE_BOUNCE; - vscale = 1.0; - } - - VelocityForDamage(damage, vd); - VectorMA(self->velocity, vscale, vd, self->velocity); - ClipGibVelocity(self); - - self->avelocity[YAW] = crandom() * 600; - - self->think = G_FreeEdict; - self->nextthink = level.time + 10 + random() * 10; - - gi.linkentity(self); -} - -void -ThrowClientHead(edict_t *self, int damage) -{ - vec3_t vd; - char *gibname; - - if (!self) - { - return; - } - - if (rand() & 1) - { - gibname = "models/objects/gibs/head2/tris.md2"; - self->s.skinnum = 1; /* second skin is player */ - } - else - { - gibname = "models/objects/gibs/skull/tris.md2"; - self->s.skinnum = 0; - } - - self->s.origin[2] += 32; - self->s.frame = 0; - gi.setmodel(self, gibname); - VectorSet(self->mins, -16, -16, 0); - VectorSet(self->maxs, 16, 16, 16); - - self->takedamage = DAMAGE_NO; - self->solid = SOLID_BBOX; - self->s.effects = EF_GIB; - self->s.sound = 0; - self->flags |= FL_NO_KNOCKBACK; - - // The entity still has the monsters clipmaks. - // Reset it to MASK_SHOT to be on the save side. - self->clipmask = MASK_SHOT; - - self->movetype = MOVETYPE_BOUNCE; - VelocityForDamage(damage, vd); - VectorAdd(self->velocity, vd, self->velocity); - - if (self->client) /* bodies in the queue don't have a client anymore */ - { - self->client->anim_priority = ANIM_DEATH; - self->client->anim_end = self->s.frame; - } - else - { - self->think = NULL; - self->nextthink = 0; - } - - gi.linkentity(self); -} - -void -debris_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, int damage /* unused */, vec3_t point /* unused */) -{ - if (!self) - { - return; - } - - G_FreeEdict(self); -} - -void -ThrowDebris(edict_t *self, char *modelname, float speed, vec3_t origin) -{ - edict_t *chunk; - vec3_t v; - - if (!self || !modelname) - { - return; - } - - if (debristhisframe > MAX_DEBRIS) - { - return; - } - - chunk = G_SpawnOptional(); - - if (!chunk) - { - return; - } - - debristhisframe++; - - VectorCopy(origin, chunk->s.origin); - gi.setmodel(chunk, modelname); - v[0] = 100 * crandom(); - v[1] = 100 * crandom(); - v[2] = 100 + 100 * crandom(); - VectorMA(self->velocity, speed, v, chunk->velocity); - chunk->movetype = MOVETYPE_BOUNCE; - chunk->solid = SOLID_NOT; - chunk->avelocity[0] = random() * 600; - chunk->avelocity[1] = random() * 600; - chunk->avelocity[2] = random() * 600; - chunk->think = G_FreeEdict; - chunk->nextthink = level.time + 5 + random() * 5; - chunk->s.frame = 0; - chunk->flags = 0; - chunk->classname = "debris"; - chunk->takedamage = DAMAGE_YES; - chunk->die = debris_die; - chunk->health = 250; - gi.linkentity(chunk); -} - -void -BecomeExplosion1(edict_t *self) -{ - if (!self) - { - return; - } - - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(self->s.origin); - gi.multicast(self->s.origin, MULTICAST_PVS); - - G_FreeEdict(self); -} - -void -BecomeExplosion2(edict_t *self) -{ - if (!self) - { - return; - } - - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION2); - gi.WritePosition(self->s.origin); - gi.multicast(self->s.origin, MULTICAST_PVS); - - G_FreeEdict(self); -} - -/* - * QUAKED path_corner (.5 .3 0) (-8 -8 -8) (8 8 8) TELEPORT - * - * Target: next path corner - * Pathtarget: gets used when an entity that has - * this path_corner targeted touches it - */ -void -path_corner_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - vec3_t v; - edict_t *next; - - if (!self || !other) - { - return; - } - - if (other->movetarget != self) - { - return; - } - - if (other->enemy) - { - return; - } - - if (self->pathtarget) - { - char *savetarget; - - savetarget = self->target; - self->target = self->pathtarget; - G_UseTargets(self, other); - self->target = savetarget; - } - - if (self->target) - { - next = G_PickTarget(self->target); - } - else - { - next = NULL; - } - - if ((next) && (next->spawnflags & 1)) - { - VectorCopy(next->s.origin, v); - v[2] += next->mins[2]; - v[2] -= other->mins[2]; - VectorCopy(v, other->s.origin); - next = G_PickTarget(next->target); - other->s.event = EV_OTHER_TELEPORT; - } - - other->goalentity = other->movetarget = next; - - if (self->wait) - { - other->monsterinfo.pausetime = level.time + self->wait; - other->monsterinfo.stand(other); - return; - } - - if (!other->movetarget) - { - other->monsterinfo.pausetime = level.time + 100000000; - other->monsterinfo.stand(other); - } - else - { - VectorSubtract(other->goalentity->s.origin, other->s.origin, v); - other->ideal_yaw = vectoyaw(v); - } -} - -void -SP_path_corner(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->targetname) - { - gi.dprintf("path_corner with no targetname at %s\n", vtos( - self->s.origin)); - G_FreeEdict(self); - return; - } - - self->solid = SOLID_TRIGGER; - self->touch = path_corner_touch; - VectorSet(self->mins, -8, -8, -8); - VectorSet(self->maxs, 8, 8, 8); - self->svflags |= SVF_NOCLIENT; - gi.linkentity(self); -} - -/* - * QUAKED point_combat (0.5 0.3 0) (-8 -8 -8) (8 8 8) Hold - * - * Makes this the target of a monster and it will head here - * when first activated before going after the activator. If - * hold is selected, it will stay here. - */ -void -point_combat_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - edict_t *activator; - - if (!self || !other) - { - return; - } - - if (other->movetarget != self) - { - return; - } - - if (self->target) - { - other->target = self->target; - other->goalentity = other->movetarget = G_PickTarget(other->target); - - if (!other->goalentity) - { - gi.dprintf("%s at %s target %s does not exist\n", self->classname, - vtos(self->s.origin), self->target); - other->movetarget = self; - } - - self->target = NULL; - } - else if ((self->spawnflags & 1) && !(other->flags & (FL_SWIM | FL_FLY))) - { - other->monsterinfo.pausetime = level.time + 100000000; - other->monsterinfo.aiflags |= AI_STAND_GROUND; - other->monsterinfo.stand(other); - } - - if (other->movetarget == self) - { - other->target = NULL; - other->movetarget = NULL; - other->goalentity = other->enemy; - other->monsterinfo.aiflags &= ~AI_COMBAT_POINT; - } - - if (self->pathtarget) - { - char *savetarget; - - savetarget = self->target; - self->target = self->pathtarget; - - if (other->enemy && other->enemy->client) - { - activator = other->enemy; - } - else if (other->oldenemy && other->oldenemy->client) - { - activator = other->oldenemy; - } - else if (other->activator && other->activator->client) - { - activator = other->activator; - } - else - { - activator = other; - } - - G_UseTargets(self, activator); - self->target = savetarget; - } -} - -void -SP_point_combat(edict_t *self) -{ - if (!self) - { - return; - } - - if (deathmatch->value) - { - G_FreeEdict(self); - return; - } - - self->solid = SOLID_TRIGGER; - self->touch = point_combat_touch; - VectorSet(self->mins, -8, -8, -16); - VectorSet(self->maxs, 8, 8, 16); - self->svflags = SVF_NOCLIENT; - gi.linkentity(self); -} - -/* - * QUAKED viewthing (0 .5 .8) (-8 -8 -8) (8 8 8) - * - * Just for the debugging level. Don't use - */ -void -TH_viewthing(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->s.frame = (ent->s.frame + 1) % 7; - ent->nextthink = level.time + FRAMETIME; -} - -void -SP_viewthing(edict_t *ent) -{ - if (!ent) - { - return; - } - - gi.dprintf("viewthing spawned\n"); - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - ent->s.renderfx = RF_FRAMELERP; - VectorSet(ent->mins, -16, -16, -24); - VectorSet(ent->maxs, 16, 16, 32); - ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2"); - gi.linkentity(ent); - ent->nextthink = level.time + 0.5; - ent->think = TH_viewthing; - return; -} - -/* - * QUAKED info_null (0 0.5 0) (-4 -4 -4) (4 4 4) - * - * Used as a positional target for spotlights, etc. - */ -void -SP_info_null(edict_t *self) -{ - if (!self) - { - return; - } - - G_FreeEdict(self); -} - -/* - * QUAKED info_notnull (0 0.5 0) (-4 -4 -4) (4 4 4) - * - * Used as a positional target for lighting. - */ -void -SP_info_notnull(edict_t *self) -{ - if (!self) - { - return; - } - - VectorCopy(self->s.origin, self->absmin); - VectorCopy(self->s.origin, self->absmax); -} - -#define START_OFF 1 - -/* - * QUAKED light (0 1 0) (-8 -8 -8) (8 8 8) START_OFF - * - * Non-displayed light. - * Default light value is 300. - * Default style is 0. - * If targeted, will toggle between on and off. - * Default _cone value is 10 (used to set size of light for spotlights) - */ -void -light_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (self->spawnflags & START_OFF) - { - gi.configstring(CS_LIGHTS + self->style, "m"); - self->spawnflags &= ~START_OFF; - } - else - { - gi.configstring(CS_LIGHTS + self->style, "a"); - self->spawnflags |= START_OFF; - } -} - -void -SP_light(edict_t *self) -{ - if (!self) - { - return; - } - - /* no targeted lights in deathmatch, because they cause global messages */ - if (!self->targetname || deathmatch->value) - { - G_FreeEdict(self); - return; - } - - if (self->style >= 32) - { - self->use = light_use; - - if (self->spawnflags & START_OFF) - { - gi.configstring(CS_LIGHTS + self->style, "a"); - } - else - { - gi.configstring(CS_LIGHTS + self->style, "m"); - } - } -} - -/* - * QUAKED func_wall (0 .5 .8) ? TRIGGER_SPAWN TOGGLE START_ON ANIMATED ANIMATED_FAST - * - * This is just a solid wall if not inhibited - * TRIGGER_SPAWN the wall will not be present until triggered - * it will then blink in to existance; it will - * kill anything that was in it's way - * - * TOGGLE only valid for TRIGGER_SPAWN walls - * this allows the wall to be turned on and off - * - * START_ON only valid for TRIGGER_SPAWN walls - * the wall will initially be present - */ - -void -func_wall_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (self->solid == SOLID_NOT) - { - self->solid = SOLID_BSP; - self->svflags &= ~SVF_NOCLIENT; - KillBox(self); - } - else - { - self->solid = SOLID_NOT; - self->svflags |= SVF_NOCLIENT; - } - - gi.linkentity(self); - - if (!(self->spawnflags & 2)) - { - self->use = NULL; - } -} - -void -SP_func_wall(edict_t *self) -{ - if (!self) - { - return; - } - - self->movetype = MOVETYPE_PUSH; - gi.setmodel(self, self->model); - - if (self->spawnflags & 8) - { - self->s.effects |= EF_ANIM_ALL; - } - - if (self->spawnflags & 16) - { - self->s.effects |= EF_ANIM_ALLFAST; - } - - /* just a wall */ - if ((self->spawnflags & 7) == 0) - { - self->solid = SOLID_BSP; - gi.linkentity(self); - return; - } - - /* it must be TRIGGER_SPAWN */ - if (!(self->spawnflags & 1)) - { - self->spawnflags |= 1; - } - - /* yell if the spawnflags are odd */ - if (self->spawnflags & 4) - { - if (!(self->spawnflags & 2)) - { - gi.dprintf("func_wall START_ON without TOGGLE\n"); - self->spawnflags |= 2; - } - } - - self->use = func_wall_use; - - if (self->spawnflags & 4) - { - self->solid = SOLID_BSP; - } - else - { - self->solid = SOLID_NOT; - self->svflags |= SVF_NOCLIENT; - } - - gi.linkentity(self); -} - -/* - * QUAKED func_object (0 .5 .8) ? TRIGGER_SPAWN ANIMATED ANIMATED_FAST - * - * This is solid bmodel that will fall if it's support it removed. - */ - -void -func_object_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf /* unused */) -{ - if (!self || !other) - { - return; - } - - /* only squash thing we fall on top of */ - if (plane && plane->normal[2] < 1.0) - { - return; - } - - if (other->takedamage == DAMAGE_NO) - { - return; - } - - T_Damage(other, self, self, vec3_origin, self->s.origin, vec3_origin, - self->dmg, 1, 0, MOD_CRUSH); -} - -void -func_object_release(edict_t *self) -{ - if (!self) - { - return; - } - - self->movetype = MOVETYPE_TOSS; - self->touch = func_object_touch; -} - -void -func_object_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - self->solid = SOLID_BSP; - self->svflags &= ~SVF_NOCLIENT; - self->use = NULL; - KillBox(self); - func_object_release(self); -} - -void -SP_func_object(edict_t *self) -{ - if (!self) - { - return; - } - - gi.setmodel(self, self->model); - - self->mins[0] += 1; - self->mins[1] += 1; - self->mins[2] += 1; - self->maxs[0] -= 1; - self->maxs[1] -= 1; - self->maxs[2] -= 1; - - if (!self->dmg) - { - self->dmg = 100; - } - - if (self->spawnflags == 0) - { - self->solid = SOLID_BSP; - self->movetype = MOVETYPE_PUSH; - self->think = func_object_release; - self->nextthink = level.time + 2 * FRAMETIME; - } - else - { - self->solid = SOLID_NOT; - self->movetype = MOVETYPE_PUSH; - self->use = func_object_use; - self->svflags |= SVF_NOCLIENT; - } - - if (self->spawnflags & 2) - { - self->s.effects |= EF_ANIM_ALL; - } - - if (self->spawnflags & 4) - { - self->s.effects |= EF_ANIM_ALLFAST; - } - - self->clipmask = MASK_MONSTERSOLID; - - gi.linkentity(self); -} - -/* - * QUAKED func_explosive (0 .5 .8) ? Trigger_Spawn ANIMATED ANIMATED_FAST INACTIVE - * - * Any brush that you want to explode or break apart. If you want an - * ex0plosion, set dmg and it will do a radius explosion of that amount - * at the center of the bursh. - * - * If targeted it will not be shootable. - * - * INACTIVE - specifies that the entity is not explodable until triggered. If you use this you must - * target the entity you want to trigger it. This is the only entity approved to activate it. - * - * health defaults to 100. - * - * mass defaults to 75. This determines how much debris is emitted when - * it explodes. You get one large chunk per 100 of mass (up to 8) and - * one small chunk per 25 of mass (up to 16). So 800 gives the most. - */ -void -func_explosive_explode(edict_t *self, edict_t *inflictor, edict_t *attacker, - int damage /* unused */, vec3_t point /* unused */) -{ - vec3_t origin; - vec3_t chunkorigin; - vec3_t size; - int count; - int mass; - edict_t *master; - - if (!self || !inflictor || !attacker) - { - return; - } - - /* 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); - } - - 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; - - if (!mass) - { - mass = 75; - } - - /* 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); - } - } - - /* 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); - } - - if (self->flags & FL_TEAMSLAVE) - { - master = self->teammaster; - - /* because mappers (other than jim (usually)) are stupid.... */ - while (master) - { - if (master->teamchain == self) - { - master->teamchain = self->teamchain; - break; - } - - master = master->teamchain; - } - } - - G_UseTargets(self, attacker); - - if (self->dmg) - { - BecomeExplosion1(self); - } - else - { - G_FreeEdict(self); - } -} - -void -func_explosive_use(edict_t *self, edict_t *other, edict_t *activator /* unused */) -{ - if (!self || !other) - { - return; - } - - func_explosive_explode(self, self, other, self->health, vec3_origin); -} - -void -func_explosive_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - self->use = func_explosive_use; - - if (!self->health) - { - self->health = 100; - } - - self->die = func_explosive_explode; - self->takedamage = DAMAGE_YES; -} - -void -func_explosive_spawn(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - self->solid = SOLID_BSP; - self->svflags &= ~SVF_NOCLIENT; - self->use = NULL; - KillBox(self); - gi.linkentity(self); -} - -void -SP_func_explosive(edict_t *self) -{ - if (!self) - { - return; - } - - if (deathmatch->value) - { - /* auto-remove for deathmatch */ - G_FreeEdict(self); - return; - } - - self->movetype = MOVETYPE_PUSH; - - gi.modelindex("models/objects/debris1/tris.md2"); - gi.modelindex("models/objects/debris2/tris.md2"); - - gi.setmodel(self, self->model); - - if (self->spawnflags & 1) - { - self->svflags |= SVF_NOCLIENT; - self->solid = SOLID_NOT; - self->use = func_explosive_spawn; - } - else if (self->spawnflags & 8) - { - self->solid = SOLID_BSP; - - if (self->targetname) - { - self->use = func_explosive_activate; - } - } - else - { - self->solid = SOLID_BSP; - - if (self->targetname) - { - self->use = func_explosive_use; - } - } - - if (self->spawnflags & 2) - { - self->s.effects |= EF_ANIM_ALL; - } - - if (self->spawnflags & 4) - { - self->s.effects |= EF_ANIM_ALLFAST; - } - - if ((self->use != func_explosive_use) && - (self->use != func_explosive_activate)) - { - if (!self->health) - { - self->health = 100; - } - - self->die = func_explosive_explode; - self->takedamage = DAMAGE_YES; - } - - gi.linkentity(self); -} - -/* - * QUAKED misc_explobox (0 .5 .8) (-16 -16 0) (16 16 40) - * - * Large exploding box. You can override its mass (100), - * health (80), and dmg (150). - */ - -void -barrel_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) - -{ - float ratio; - vec3_t v; - - if (!self || !other) - { - return; - } - - if ((!other->groundentity) || (other->groundentity == self)) - { - return; - } - - ratio = (float)other->mass / (float)self->mass; - VectorSubtract(self->s.origin, other->s.origin, v); - M_walkmove(self, vectoyaw(v), 20 * ratio * FRAMETIME); -} - -void -barrel_explode(edict_t *self) -{ - vec3_t org; - float spd; - vec3_t save; - - if (!self) - { - return; - } - - T_RadiusDamage(self, self->activator, self->dmg, - NULL, self->dmg + 40, MOD_BARREL); - - VectorCopy(self->s.origin, save); - VectorMA(self->absmin, 0.5, self->size, self->s.origin); - - /* a few big chunks */ - spd = 1.5 * (float)self->dmg / 200.0; - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris1/tris.md2", spd, org); - - /* bottom corners */ - spd = 1.75 * (float)self->dmg / 200.0; - VectorCopy(self->absmin, org); - ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org); - VectorCopy(self->absmin, org); - org[0] += self->size[0]; - ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org); - VectorCopy(self->absmin, org); - org[1] += self->size[1]; - ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org); - VectorCopy(self->absmin, org); - org[0] += self->size[0]; - org[1] += self->size[1]; - ThrowDebris(self, "models/objects/debris3/tris.md2", spd, org); - - /* a bunch of little chunks */ - spd = 2.0 * (float)self->dmg / 200.0; - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - org[0] = self->s.origin[0] + crandom() * self->size[0]; - org[1] = self->s.origin[1] + crandom() * self->size[1]; - org[2] = self->s.origin[2] + crandom() * self->size[2]; - ThrowDebris(self, "models/objects/debris2/tris.md2", spd, org); - - VectorCopy(save, self->s.origin); - - if (self->groundentity) - { - BecomeExplosion2(self); - } - else - { - BecomeExplosion1(self); - } -} - -void -barrel_delay(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker, - int damage /* unused */, vec3_t point /* unused */) -{ - if (!self || !attacker) - { - return; - } - - self->takedamage = DAMAGE_NO; - self->nextthink = level.time + 2 * FRAMETIME; - self->think = barrel_explode; - self->activator = attacker; -} - -void -barrel_think(edict_t *self) -{ - if (!self) - { - return; - } - - /* the think needs to be first since later stuff may override. */ - self->think = barrel_think; - self->nextthink = level.time + FRAMETIME; - - M_CatagorizePosition(self); - self->flags |= FL_IMMUNE_SLIME; - self->air_finished = level.time + 100; - M_WorldEffects(self); -} - -void -barrel_start(edict_t *self) -{ - if (!self) - { - return; - } - - M_droptofloor(self); - self->think = barrel_think; - self->nextthink = level.time + FRAMETIME; -} - -void -SP_misc_explobox(edict_t *self) -{ - if (!self) - { - return; - } - - if (deathmatch->value) - { /* auto-remove for deathmatch */ - G_FreeEdict(self); - return; - } - - gi.modelindex("models/objects/debris1/tris.md2"); - gi.modelindex("models/objects/debris2/tris.md2"); - gi.modelindex("models/objects/debris3/tris.md2"); - - self->solid = SOLID_BBOX; - self->movetype = MOVETYPE_STEP; - - self->model = "models/objects/barrels/tris.md2"; - self->s.modelindex = gi.modelindex(self->model); - VectorSet(self->mins, -16, -16, 0); - VectorSet(self->maxs, 16, 16, 40); - - if (!self->mass) - { - self->mass = 400; - } - - if (!self->health) - { - self->health = 10; - } - - if (!self->dmg) - { - self->dmg = 150; - } - - self->die = barrel_delay; - self->takedamage = DAMAGE_YES; - self->monsterinfo.aiflags = AI_NOSTEP; - - self->touch = barrel_touch; - self->think = barrel_start; - self->nextthink = level.time + 2 * FRAMETIME; - - gi.linkentity(self); -} - -/* - * QUAKED misc_blackhole (1 .5 0) (-8 -8 -8) (8 8 8) - */ -void -misc_blackhole_use(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!ent) - { - return; - } - - G_FreeEdict(ent); -} - -void -misc_blackhole_think(edict_t *self) -{ - if (!self) - { - return; - } - - if (++self->s.frame < 19) - { - self->nextthink = level.time + FRAMETIME; - } - else - { - self->s.frame = 0; - self->nextthink = level.time + FRAMETIME; - } -} - -void -SP_misc_blackhole(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_NOT; - VectorSet(ent->mins, -64, -64, 0); - VectorSet(ent->maxs, 64, 64, 8); - ent->s.modelindex = gi.modelindex("models/objects/black/tris.md2"); - ent->s.renderfx = RF_TRANSLUCENT; - ent->use = misc_blackhole_use; - ent->think = misc_blackhole_think; - ent->nextthink = level.time + 2 * FRAMETIME; - gi.linkentity(ent); -} - -/* - * QUAKED misc_eastertank (1 .5 0) (-32 -32 -16) (32 32 32) - */ -void -misc_eastertank_think(edict_t *self) -{ - if (!self) - { - return; - } - - if (++self->s.frame < 293) - { - self->nextthink = level.time + FRAMETIME; - } - else - { - self->s.frame = 254; - self->nextthink = level.time + FRAMETIME; - } -} - -void -SP_misc_eastertank(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - VectorSet(ent->mins, -32, -32, -16); - VectorSet(ent->maxs, 32, 32, 32); - ent->s.modelindex = gi.modelindex("models/monsters/tank/tris.md2"); - ent->s.frame = 254; - ent->think = misc_eastertank_think; - ent->nextthink = level.time + 2 * FRAMETIME; - gi.linkentity(ent); -} - -/* - * QUAKED misc_easterchick (1 .5 0) (-32 -32 0) (32 32 32) - */ -void -misc_easterchick_think(edict_t *self) -{ - if (!self) - { - return; - } - - if (++self->s.frame < 247) - { - self->nextthink = level.time + FRAMETIME; - } - else - { - self->s.frame = 208; - self->nextthink = level.time + FRAMETIME; - } -} - -void -SP_misc_easterchick(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - VectorSet(ent->mins, -32, -32, 0); - VectorSet(ent->maxs, 32, 32, 32); - ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2"); - ent->s.frame = 208; - ent->think = misc_easterchick_think; - ent->nextthink = level.time + 2 * FRAMETIME; - gi.linkentity(ent); -} - -/* - * QUAKED misc_easterchick2 (1 .5 0) (-32 -32 0) (32 32 32) - */ -void -misc_easterchick2_think(edict_t *self) -{ - if (!self) - { - return; - } - - if (++self->s.frame < 287) - { - self->nextthink = level.time + FRAMETIME; - } - else - { - self->s.frame = 248; - self->nextthink = level.time + FRAMETIME; - } -} - -void -SP_misc_easterchick2(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - VectorSet(ent->mins, -32, -32, 0); - VectorSet(ent->maxs, 32, 32, 32); - ent->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2"); - ent->s.frame = 248; - ent->think = misc_easterchick2_think; - ent->nextthink = level.time + 2 * FRAMETIME; - gi.linkentity(ent); -} - -/* - * QUAKED monster_commander_body (1 .5 0) (-32 -32 0) (32 32 48) - * - * Not really a monster, this is the Tank Commander's decapitated body. - * There should be a item_commander_head that has this as it's target. - */ -void -commander_body_think(edict_t *self) -{ - if (!self) - { - return; - } - - if (++self->s.frame < 24) - { - self->nextthink = level.time + FRAMETIME; - } - else - { - self->nextthink = 0; - } - - if (self->s.frame == 22) - { - gi.sound(self, CHAN_BODY, gi.soundindex("tank/thud.wav"), 1, ATTN_NORM, 0); - } -} - -void -commander_body_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - self->think = commander_body_think; - self->nextthink = level.time + FRAMETIME; - gi.sound(self, CHAN_BODY, gi.soundindex("tank/pain.wav"), 1, ATTN_NORM, 0); -} - -void -commander_body_drop(edict_t *self) -{ - if (!self) - { - return; - } - - self->movetype = MOVETYPE_TOSS; - self->s.origin[2] += 2; -} - -void -SP_monster_commander_body(edict_t *self) -{ - if (!self) - { - return; - } - - self->movetype = MOVETYPE_NONE; - self->solid = SOLID_BBOX; - self->model = "models/monsters/commandr/tris.md2"; - self->s.modelindex = gi.modelindex(self->model); - VectorSet(self->mins, -32, -32, 0); - VectorSet(self->maxs, 32, 32, 48); - self->use = commander_body_use; - self->takedamage = DAMAGE_YES; - self->flags = FL_GODMODE; - self->s.renderfx |= RF_FRAMELERP; - gi.linkentity(self); - - gi.soundindex("tank/thud.wav"); - gi.soundindex("tank/pain.wav"); - - self->think = commander_body_drop; - self->nextthink = level.time + 5 * FRAMETIME; -} - -/* - * QUAKED misc_banner (1 .5 0) (-4 -4 -4) (4 4 4) - * - * The origin is the bottom of the banner. - * The banner is 128 tall. - */ -void -misc_banner_think(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->s.frame = (ent->s.frame + 1) % 16; - ent->nextthink = level.time + FRAMETIME; -} - -void -SP_misc_banner(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_NOT; - ent->s.modelindex = gi.modelindex("models/objects/banner/tris.md2"); - ent->s.frame = rand() % 16; - gi.linkentity(ent); - - ent->think = misc_banner_think; - ent->nextthink = level.time + FRAMETIME; -} - -/* - * QUAKED misc_deadsoldier (1 .5 0) (-16 -16 0) (16 16 16) ON_BACK ON_STOMACH BACK_DECAP FETAL_POS SIT_DECAP IMPALED - * - * This is the dead player model. Comes in 6 exciting different poses! - */ -void -misc_deadsoldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, - int damage, vec3_t point /* unused */) -{ - int n; - - if (!self) - { - return; - } - - if (self->health > -30) - { - return; - } - - gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, - 0); - - for (n = 0; n < 4; n++) - { - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - } - - ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); -} - -void -SP_misc_deadsoldier(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (deathmatch->value) - { - /* auto-remove for deathmatch */ - G_FreeEdict(ent); - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - ent->s.modelindex = gi.modelindex("models/deadbods/dude/tris.md2"); - - /* Defaults to frame 0 */ - if (ent->spawnflags & 2) - { - ent->s.frame = 1; - } - else if (ent->spawnflags & 4) - { - ent->s.frame = 2; - } - else if (ent->spawnflags & 8) - { - ent->s.frame = 3; - } - else if (ent->spawnflags & 16) - { - ent->s.frame = 4; - } - else if (ent->spawnflags & 32) - { - ent->s.frame = 5; - } - else - { - ent->s.frame = 0; - } - - VectorSet(ent->mins, -16, -16, 0); - VectorSet(ent->maxs, 16, 16, 16); - ent->deadflag = DEAD_DEAD; - ent->takedamage = DAMAGE_YES; - ent->svflags |= SVF_MONSTER | SVF_DEADMONSTER; - ent->die = misc_deadsoldier_die; - ent->monsterinfo.aiflags |= AI_GOOD_GUY; - - gi.linkentity(ent); -} - -extern void train_use(edict_t *self, edict_t *other, edict_t *activator); -extern void func_train_find(edict_t *self); - -/* - * QUAKED misc_viper (1 .5 0) (-16 -16 0) (16 16 32) - * - * This is the Viper for the flyby bombing. - * It is trigger_spawned, so you must have something use it for it to show up. - * There must be a path for it to follow once it is activated. - * - * "speed" How fast the Viper should fly - */ -void -misc_viper_use(edict_t *self, edict_t *other, edict_t *activator) -{ - if (!self || !other || !activator) - { - return; - } - - self->svflags &= ~SVF_NOCLIENT; - self->use = train_use; - train_use(self, other, activator); -} - -void -SP_misc_viper(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (!ent->target) - { - gi.dprintf("misc_viper without a target at %s\n", vtos(ent->absmin)); - G_FreeEdict(ent); - return; - } - - if (!ent->speed) - { - ent->speed = 300; - } - - ent->movetype = MOVETYPE_PUSH; - ent->solid = SOLID_NOT; - ent->s.modelindex = gi.modelindex("models/ships/viper/tris.md2"); - VectorSet(ent->mins, -16, -16, 0); - VectorSet(ent->maxs, 16, 16, 32); - - ent->think = func_train_find; - ent->nextthink = level.time + FRAMETIME; - ent->use = misc_viper_use; - ent->svflags |= SVF_NOCLIENT; - ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; - - gi.linkentity(ent); -} - -/* - * QUAKED misc_bigviper (1 .5 0) (-176 -120 -24) (176 120 72) - * - * This is a large stationary viper as seen in Paul's intro - */ -void -SP_misc_bigviper(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - VectorSet(ent->mins, -176, -120, -24); - VectorSet(ent->maxs, 176, 120, 72); - ent->s.modelindex = gi.modelindex("models/ships/bigviper/tris.md2"); - gi.linkentity(ent); -} - -/* - * QUAKED misc_viper_bomb (1 0 0) (-8 -8 -8) (8 8 8) - * - * "dmg" how much boom should the bomb make? - */ -void -misc_viper_bomb_touch(edict_t *self, edict_t *other /* unused */, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - if (!self) - { - return; - } - - G_UseTargets(self, self->activator); - - self->s.origin[2] = self->absmin[2] + 1; - T_RadiusDamage(self, self, self->dmg, NULL, self->dmg + 40, MOD_BOMB); - BecomeExplosion2(self); -} - -void -misc_viper_bomb_prethink(edict_t *self) -{ - vec3_t v; - float diff; - - if (!self) - { - return; - } - - self->groundentity = NULL; - - diff = self->timestamp - level.time; - - if (diff < -1.0) - { - diff = -1.0; - } - - VectorScale(self->moveinfo.dir, 1.0 + diff, v); - v[2] = diff; - - diff = self->s.angles[2]; - vectoangles(v, self->s.angles); - self->s.angles[2] = diff + 10; -} - -void -misc_viper_bomb_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - edict_t *viper; - - if (!self || !activator) - { - return; - } - - self->solid = SOLID_BBOX; - self->svflags &= ~SVF_NOCLIENT; - self->s.effects |= EF_ROCKET; - self->use = NULL; - self->movetype = MOVETYPE_TOSS; - self->prethink = misc_viper_bomb_prethink; - self->touch = misc_viper_bomb_touch; - self->activator = activator; - - viper = G_Find(NULL, FOFS(classname), "misc_viper"); - VectorScale(viper->moveinfo.dir, viper->moveinfo.speed, self->velocity); - - self->timestamp = level.time; - VectorCopy(viper->moveinfo.dir, self->moveinfo.dir); -} - -void -SP_misc_viper_bomb(edict_t *self) -{ - if (!self) - { - return; - } - - self->movetype = MOVETYPE_NONE; - self->solid = SOLID_NOT; - VectorSet(self->mins, -8, -8, -8); - VectorSet(self->maxs, 8, 8, 8); - - self->s.modelindex = gi.modelindex("models/objects/bomb/tris.md2"); - - if (!self->dmg) - { - self->dmg = 1000; - } - - self->use = misc_viper_bomb_use; - self->svflags |= SVF_NOCLIENT; - - gi.linkentity(self); -} - -extern void train_use(edict_t *self, edict_t *other, edict_t *activator); -extern void func_train_find(edict_t *self); - -/* - * QUAKED misc_strogg_ship (1 .5 0) (-16 -16 0) (16 16 32) - * - * This is a Storgg ship for the flybys. - * It is trigger_spawned, so you must have something use it for it to show up. - * There must be a path for it to follow once it is activated. - * - * "speed" How fast it should fly - */ -void -misc_strogg_ship_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - self->svflags &= ~SVF_NOCLIENT; - self->use = train_use; - train_use(self, other, activator); -} - -void -SP_misc_strogg_ship(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (!ent->target) - { - gi.dprintf("%s without a target at %s\n", ent->classname, - vtos(ent->absmin)); - G_FreeEdict(ent); - return; - } - - if (!ent->speed) - { - ent->speed = 300; - } - - ent->movetype = MOVETYPE_PUSH; - ent->solid = SOLID_NOT; - ent->s.modelindex = gi.modelindex("models/ships/strogg1/tris.md2"); - VectorSet(ent->mins, -16, -16, 0); - VectorSet(ent->maxs, 16, 16, 32); - - ent->think = func_train_find; - ent->nextthink = level.time + FRAMETIME; - ent->use = misc_strogg_ship_use; - ent->svflags |= SVF_NOCLIENT; - ent->moveinfo.accel = ent->moveinfo.decel = ent->moveinfo.speed = ent->speed; - - gi.linkentity(ent); -} - -/* - * QUAKED misc_satellite_dish (1 .5 0) (-64 -64 0) (64 64 128) - */ -void -misc_satellite_dish_think(edict_t *self) -{ - if (!self) - { - return; - } - - self->s.frame++; - - if (self->s.frame < 38) - { - self->nextthink = level.time + FRAMETIME; - } -} - -void -misc_satellite_dish_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - self->s.frame = 0; - self->think = misc_satellite_dish_think; - self->nextthink = level.time + FRAMETIME; -} - -void -SP_misc_satellite_dish(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - VectorSet(ent->mins, -64, -64, 0); - VectorSet(ent->maxs, 64, 64, 128); - ent->s.modelindex = gi.modelindex("models/objects/satellite/tris.md2"); - ent->use = misc_satellite_dish_use; - gi.linkentity(ent); -} - -/* - * QUAKED light_mine1 (0 1 0) (-2 -2 -12) (2 2 12) - */ -void -SP_light_mine1(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - ent->s.modelindex = - gi.modelindex("models/objects/minelite/light1/tris.md2"); - gi.linkentity(ent); -} - -/* - * QUAKED light_mine2 (0 1 0) (-2 -2 -12) (2 2 12) - */ -void -SP_light_mine2(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_NONE; - ent->solid = SOLID_BBOX; - ent->s.modelindex = - gi.modelindex("models/objects/minelite/light2/tris.md2"); - gi.linkentity(ent); -} - -/* - * QUAKED misc_gib_arm (1 0 0) (-8 -8 -8) (8 8 8) - * - * Intended for use with the target_spawner - */ -void -SP_misc_gib_arm(edict_t *ent) -{ - if (!ent) - { - return; - } - - gi.setmodel(ent, "models/objects/gibs/arm/tris.md2"); - ent->solid = SOLID_BBOX; - ent->s.effects |= EF_GIB; - ent->takedamage = DAMAGE_YES; - ent->die = gib_die; - ent->movetype = MOVETYPE_TOSS; - ent->svflags |= SVF_MONSTER; - ent->deadflag = DEAD_DEAD; - ent->avelocity[0] = random() * 200; - ent->avelocity[1] = random() * 200; - ent->avelocity[2] = random() * 200; - ent->think = G_FreeEdict; - ent->nextthink = level.time + 30; - gi.linkentity(ent); -} - -/* - * QUAKED misc_gib_leg (1 0 0) (-8 -8 -8) (8 8 8) - * Intended for use with the target_spawner - */ -void -SP_misc_gib_leg(edict_t *ent) -{ - if (!ent) - { - return; - } - - gi.setmodel(ent, "models/objects/gibs/leg/tris.md2"); - ent->solid = SOLID_BBOX; - ent->s.effects |= EF_GIB; - ent->takedamage = DAMAGE_YES; - ent->die = gib_die; - ent->movetype = MOVETYPE_TOSS; - ent->svflags |= SVF_MONSTER; - ent->deadflag = DEAD_DEAD; - ent->avelocity[0] = random() * 200; - ent->avelocity[1] = random() * 200; - ent->avelocity[2] = random() * 200; - ent->think = G_FreeEdict; - ent->nextthink = level.time + 30; - gi.linkentity(ent); -} - -/* - * QUAKED misc_gib_head (1 0 0) (-8 -8 -8) (8 8 8) - * Intended for use with the target_spawner - */ -void -SP_misc_gib_head(edict_t *ent) -{ - if (!ent) - { - return; - } - - gi.setmodel(ent, "models/objects/gibs/head/tris.md2"); - ent->solid = SOLID_BBOX; - ent->s.effects |= EF_GIB; - ent->takedamage = DAMAGE_YES; - ent->die = gib_die; - ent->movetype = MOVETYPE_TOSS; - ent->svflags |= SVF_MONSTER; - ent->deadflag = DEAD_DEAD; - ent->avelocity[0] = random() * 200; - ent->avelocity[1] = random() * 200; - ent->avelocity[2] = random() * 200; - ent->think = G_FreeEdict; - ent->nextthink = level.time + 30; - gi.linkentity(ent); -} - -/* ===================================================== */ - -/* - * QUAKED target_character (0 0 1) ? - * - * used with target_string (must be on same "team") - * "count" is position in the string (starts at 1) - */ - -void -SP_target_character(edict_t *self) -{ - if (!self) - { - return; - } - - self->movetype = MOVETYPE_PUSH; - gi.setmodel(self, self->model); - self->solid = SOLID_BSP; - self->s.frame = 12; - gi.linkentity(self); - return; -} - -/* - * QUAKED target_string (0 0 1) (-8 -8 -8) (8 8 8) - */ -void -target_string_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - edict_t *e; - int n, l; - char c; - - if (!self) - { - return; - } - - l = strlen(self->message); - - for (e = self->teammaster; e; e = e->teamchain) - { - if (!e->count) - { - continue; - } - - n = e->count - 1; - - if (n > l) - { - e->s.frame = 12; - continue; - } - - c = self->message[n]; - - if ((c >= '0') && (c <= '9')) - { - e->s.frame = c - '0'; - } - else if (c == '-') - { - e->s.frame = 10; - } - else if (c == ':') - { - e->s.frame = 11; - } - else - { - e->s.frame = 12; - } - } -} - -void -SP_target_string(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->message) - { - self->message = ""; - } - - self->use = target_string_use; -} - -#define CLOCK_MESSAGE_SIZE 16 - -/* - * QUAKED func_clock (0 0 1) (-8 -8 -8) (8 8 8) TIMER_UP TIMER_DOWN START_OFF MULTI_USE - * - * target a target_string with this - * - * The default is to be a time of day clock - * - * TIMER_UP and TIMER_DOWN run for "count" seconds and the fire "pathtarget" - * If START_OFF, this entity must be used before it starts - * - * "style" 0 "xx" - * 1 "xx:xx" - * 2 "xx:xx:xx" - */ -void -func_clock_reset(edict_t *self) -{ - if (!self) - { - return; - } - - self->activator = NULL; - - if (self->spawnflags & 1) - { - self->health = 0; - self->wait = self->count; - } - else if (self->spawnflags & 2) - { - self->health = self->count; - self->wait = 0; - } -} - -void -func_clock_format_countdown(edict_t *self) -{ - if (!self) - { - return; - } - - if (self->style == 0) - { - Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i", self->health); - return; - } - - if (self->style == 1) - { - Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i", - self->health / 60, self->health % 60); - - if (self->message[3] == ' ') - { - self->message[3] = '0'; - } - - return; - } - - if (self->style == 2) - { - Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", - self->health / 3600, (self->health - (self->health / 3600) * 3600) / 60, - self->health % 60); - - if (self->message[3] == ' ') - { - self->message[3] = '0'; - } - - if (self->message[6] == ' ') - { - self->message[6] = '0'; - } - - return; - } -} - -void -func_clock_think(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->enemy) - { - self->enemy = G_Find(NULL, FOFS(targetname), self->target); - - if (!self->enemy) - { - return; - } - } - - if (self->spawnflags & 1) - { - func_clock_format_countdown(self); - self->health++; - } - else if (self->spawnflags & 2) - { - func_clock_format_countdown(self); - self->health--; - } - else - { - struct tm *ltime; - time_t gmtime; - - time(&gmtime); - ltime = localtime(&gmtime); - Com_sprintf(self->message, CLOCK_MESSAGE_SIZE, "%2i:%2i:%2i", - ltime->tm_hour, ltime->tm_min, ltime->tm_sec); - - if (self->message[3] == ' ') - { - self->message[3] = '0'; - } - - if (self->message[6] == ' ') - { - self->message[6] = '0'; - } - } - - self->enemy->message = self->message; - self->enemy->use(self->enemy, self, self); - - if (((self->spawnflags & 1) && (self->health > self->wait)) || - ((self->spawnflags & 2) && (self->health < self->wait))) - { - if (self->pathtarget) - { - char *savetarget; - char *savemessage; - - savetarget = self->target; - savemessage = self->message; - self->target = self->pathtarget; - self->message = NULL; - G_UseTargets(self, self->activator); - self->target = savetarget; - self->message = savemessage; - } - - if (!(self->spawnflags & 8)) - { - return; - } - - func_clock_reset(self); - - if (self->spawnflags & 4) - { - return; - } - } - - self->nextthink = level.time + 1; -} - -void -func_clock_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - if (!(self->spawnflags & 8)) - { - self->use = NULL; - } - - if (self->activator) - { - return; - } - - self->activator = activator; - self->think(self); -} - -void -SP_func_clock(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->target) - { - gi.dprintf("%s with no target at %s\n", self->classname, - vtos(self->s.origin)); - G_FreeEdict(self); - return; - } - - if ((self->spawnflags & 2) && (!self->count)) - { - gi.dprintf("%s with no count at %s\n", self->classname, - vtos(self->s.origin)); - G_FreeEdict(self); - return; - } - - if ((self->spawnflags & 1) && (!self->count)) - { - self->count = 60 * 60; - } - - func_clock_reset(self); - - self->message = gi.TagMalloc(CLOCK_MESSAGE_SIZE, TAG_LEVEL); - - self->think = func_clock_think; - - if (self->spawnflags & 4) - { - self->use = func_clock_use; - } - else - { - self->nextthink = level.time + 1; - } -} - -/* ================================================================================= */ - -void -teleporter_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - edict_t *dest; - int i; - - if (!self || !other) - { - return; - } - - if (!other->client) - { - return; - } - - dest = G_Find(NULL, FOFS(targetname), self->target); - - if (!dest) - { - gi.dprintf("Couldn't find destination\n"); - return; - } - - /* unlink to make sure it can't possibly interfere with KillBox */ - gi.unlinkentity(other); - - VectorCopy(dest->s.origin, other->s.origin); - VectorCopy(dest->s.origin, other->s.old_origin); - other->s.origin[2] += 10; - - /* clear the velocity and hold them in place briefly */ - VectorClear(other->velocity); - other->client->ps.pmove.pm_time = 160 >> 3; /* hold time */ - other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT; - - /* draw the teleport splash at source and on the player */ - self->owner->s.event = EV_PLAYER_TELEPORT; - other->s.event = EV_PLAYER_TELEPORT; - - /* set angles */ - for (i = 0; i < 3; i++) - { - other->client->ps.pmove.delta_angles[i] = ANGLE2SHORT( - dest->s.angles[i] - other->client->resp.cmd_angles[i]); - } - - VectorClear(other->s.angles); - VectorClear(other->client->ps.viewangles); - VectorClear(other->client->v_angle); - - /* kill anything at the destination */ - KillBox(other); - - gi.linkentity(other); -} - -/* - * QUAKED misc_teleporter (1 0 0) (-32 -32 -24) (32 32 -16) - * - * Stepping onto this disc will teleport players to the targeted misc_teleporter_dest object. - */ -void -SP_misc_teleporter(edict_t *ent) -{ - if (!ent) - { - return; - } - - edict_t *trig; - - if (!ent->target) - { - gi.dprintf("teleporter without a target.\n"); - G_FreeEdict(ent); - return; - } - - gi.setmodel(ent, "models/objects/dmspot/tris.md2"); - ent->s.skinnum = 1; - ent->s.effects = EF_TELEPORTER; - ent->s.sound = gi.soundindex("world/amb10.wav"); - ent->solid = SOLID_BBOX; - - VectorSet(ent->mins, -32, -32, -24); - VectorSet(ent->maxs, 32, 32, -16); - gi.linkentity(ent); - - trig = G_Spawn(); - trig->touch = teleporter_touch; - trig->solid = SOLID_TRIGGER; - trig->target = ent->target; - trig->owner = ent; - VectorCopy(ent->s.origin, trig->s.origin); - VectorSet(trig->mins, -8, -8, 8); - VectorSet(trig->maxs, 8, 8, 24); - gi.linkentity(trig); -} - -/* - * QUAKED misc_teleporter_dest (1 0 0) (-32 -32 -24) (32 32 -16) - * - * Point teleporters at these. - */ -void -SP_misc_teleporter_dest(edict_t *ent) -{ - if (!ent) - { - return; - } - - gi.setmodel(ent, "models/objects/dmspot/tris.md2"); - ent->s.skinnum = 0; - ent->solid = SOLID_BBOX; - VectorSet(ent->mins, -32, -32, -24); - VectorSet(ent->maxs, 32, 32, -16); - gi.linkentity(ent); -} - -void -misc_nuke_core_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (self->svflags & SVF_NOCLIENT) - { - self->svflags &= ~SVF_NOCLIENT; - } - else - { - self->svflags |= SVF_NOCLIENT; - } -} - -/* - * QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16) - * - * toggles visible/not visible. starts visible. - */ -void -SP_misc_nuke_core(edict_t *ent) -{ - if (!ent) - { - return; - } - - gi.setmodel(ent, "models/objects/core/tris.md2"); - gi.linkentity(ent); - - ent->use = misc_nuke_core_use; -} diff --git a/src/rogue/g_monster.c b/src/rogue/g_monster.c deleted file mode 100644 index 66477109..00000000 --- a/src/rogue/g_monster.c +++ /dev/null @@ -1,1347 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Monster utility functions. - * - * ======================================================================= - */ - -#include "header/local.h" - -void monster_start_go(edict_t *self); - -/* Monster weapons */ - -void -monster_fire_bullet(edict_t *self, vec3_t start, vec3_t dir, int damage, - int kick, int hspread, int vspread, int flashtype) -{ - if (!self) - { - return; - } - - fire_bullet(self, start, dir, damage, kick, hspread, vspread, MOD_UNKNOWN); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_shotgun(edict_t *self, vec3_t start, vec3_t aimdir, int damage, - int kick, int hspread, int vspread, int count, int flashtype) -{ - if (!self) - { - return; - } - - fire_shotgun(self, start, aimdir, damage, kick, hspread, vspread, - count, MOD_UNKNOWN); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_blaster(edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, - int flashtype, int effect) -{ - if (!self) - { - return; - } - - fire_blaster(self, start, dir, damage, speed, effect, false); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_blaster2(edict_t *self, vec3_t start, vec3_t dir, int damage, - int speed, int flashtype, int effect) -{ - if (!self) - { - return; - } - - fire_blaster2(self, start, dir, damage, speed, effect, false); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_tracker(edict_t *self, vec3_t start, vec3_t dir, int damage, - int speed, edict_t *enemy, int flashtype) -{ - if (!self || !enemy) - { - return; - } - - fire_tracker(self, start, dir, damage, speed, enemy); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_heatbeam(edict_t *self, vec3_t start, vec3_t dir, vec3_t offset, - int damage, int kick, int flashtype) -{ - if (!self) - { - return; - } - - fire_heatbeam(self, start, dir, offset, damage, kick, true); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_grenade(edict_t *self, vec3_t start, vec3_t aimdir, - int damage, int speed, int flashtype) -{ - if (!self) - { - return; - } - - fire_grenade(self, start, aimdir, damage, speed, 2.5, damage + 40); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_rocket(edict_t *self, vec3_t start, vec3_t dir, - int damage, int speed, int flashtype) -{ - if (!self) - { - return; - } - - fire_rocket(self, start, dir, damage, speed, damage + 20, damage); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_railgun(edict_t *self, vec3_t start, vec3_t aimdir, - int damage, int kick, int flashtype) -{ - if (!self) - { - return; - } - - if (!(gi.pointcontents(start) & MASK_SOLID)) - { - fire_rail(self, start, aimdir, damage, kick); - } - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -void -monster_fire_bfg(edict_t *self, vec3_t start, vec3_t aimdir, - int damage, int speed, int kick, float damage_radius, - int flashtype) -{ - if (!self) - { - return; - } - - fire_bfg(self, start, aimdir, damage, speed, damage_radius); - - gi.WriteByte(svc_muzzleflash2); - gi.WriteShort(self - g_edicts); - gi.WriteByte(flashtype); - gi.multicast(start, MULTICAST_PVS); -} - -/* ================================================================== */ - -/* Monster utility functions */ - -void -M_FliesOff(edict_t *self) -{ - if (!self) - { - return; - } - - self->s.effects &= ~EF_FLIES; - self->s.sound = 0; -} - -void -M_FliesOn(edict_t *self) -{ - if (!self) - { - return; - } - - if (self->waterlevel) - { - return; - } - - self->s.effects |= EF_FLIES; - self->s.sound = gi.soundindex("infantry/inflies1.wav"); - self->think = M_FliesOff; - self->nextthink = level.time + 60; -} - -void -M_FlyCheck(edict_t *self) -{ - if (!self) - { - return; - } - - if (self->waterlevel) - { - return; - } - - if (random() > 0.5) - { - return; - } - - self->think = M_FliesOn; - self->nextthink = level.time + 5 + 10 * random(); -} - -void -AttackFinished(edict_t *self, float time) -{ - if (!self) - { - return; - } - - self->monsterinfo.attack_finished = level.time + time; -} - -void -M_CheckGround(edict_t *ent) -{ - vec3_t point; - trace_t trace; - - if (!ent) - { - return; - } - - if (ent->flags & (FL_SWIM | FL_FLY)) - { - return; - } - - if ((ent->velocity[2] * ent->gravityVector[2]) < -100) - { - ent->groundentity = NULL; - return; - } - - /* if the hull point one-quarter unit down is solid the entity is on ground */ - point[0] = ent->s.origin[0]; - point[1] = ent->s.origin[1]; - point[2] = ent->s.origin[2] + (0.25 * ent->gravityVector[2]); - - trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, - point, ent, MASK_MONSTERSOLID); - - /* check steepness */ - if (ent->gravityVector[2] < 0) /* normal gravity */ - { - if ((trace.plane.normal[2] < 0.7) && !trace.startsolid) - { - ent->groundentity = NULL; - return; - } - } - else /* inverted gravity */ - { - if ((trace.plane.normal[2] > -0.7) && !trace.startsolid) - { - ent->groundentity = NULL; - return; - } - } - - if (!trace.startsolid && !trace.allsolid) - { - VectorCopy(trace.endpos, ent->s.origin); - ent->groundentity = trace.ent; - ent->groundentity_linkcount = trace.ent->linkcount; - ent->velocity[2] = 0; - } -} - -void -M_CatagorizePosition(edict_t *ent) -{ - vec3_t point; - int cont; - - if (!ent) - { - return; - } - - /* get waterlevel */ - point[0] = (ent->absmax[0] + ent->absmin[0])/2; - point[1] = (ent->absmax[1] + ent->absmin[1])/2; - point[2] = ent->absmin[2] + 2; - cont = gi.pointcontents(point); - - if (!(cont & MASK_WATER)) - { - ent->waterlevel = 0; - ent->watertype = 0; - return; - } - - ent->watertype = cont; - ent->waterlevel = 1; - point[2] += 26; - cont = gi.pointcontents(point); - - if (!(cont & MASK_WATER)) - { - return; - } - - ent->waterlevel = 2; - point[2] += 22; - cont = gi.pointcontents(point); - - if (cont & MASK_WATER) - { - ent->waterlevel = 3; - } -} - -void -M_WorldEffects(edict_t *ent) -{ - int dmg; - - if (!ent) - { - return; - } - - if (ent->health > 0) - { - if (!(ent->flags & FL_SWIM)) - { - if (ent->waterlevel < 3) - { - ent->air_finished = level.time + 12; - } - else if (ent->air_finished < level.time) - { - /* drown! */ - if (ent->pain_debounce_time < level.time) - { - dmg = 2 + 2 * floor(level.time - ent->air_finished); - - if (dmg > 15) - { - dmg = 15; - } - - T_Damage(ent, world, world, vec3_origin, ent->s.origin, - vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); - ent->pain_debounce_time = level.time + 1; - } - } - } - else - { - if (ent->waterlevel > 0) - { - ent->air_finished = level.time + 9; - } - else if (ent->air_finished < level.time) - { - /* suffocate! */ - if (ent->pain_debounce_time < level.time) - { - dmg = 2 + 2 * floor(level.time - ent->air_finished); - - if (dmg > 15) - { - dmg = 15; - } - - T_Damage(ent, world, world, vec3_origin, ent->s.origin, - vec3_origin, dmg, 0, DAMAGE_NO_ARMOR, MOD_WATER); - ent->pain_debounce_time = level.time + 1; - } - } - } - } - - if (ent->waterlevel == 0) - { - if (ent->flags & FL_INWATER) - { - gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_out.wav"), 1, ATTN_NORM, 0); - ent->flags &= ~FL_INWATER; - } - - return; - } - - if ((ent->watertype & CONTENTS_LAVA) && !(ent->flags & FL_IMMUNE_LAVA)) - { - if (ent->damage_debounce_time < level.time) - { - ent->damage_debounce_time = level.time + 0.2; - T_Damage(ent, world, world, vec3_origin, ent->s.origin, vec3_origin, - 10 * ent->waterlevel, 0, 0, MOD_LAVA); - } - } - - if ((ent->watertype & CONTENTS_SLIME) && !(ent->flags & FL_IMMUNE_SLIME)) - { - if (ent->damage_debounce_time < level.time) - { - ent->damage_debounce_time = level.time + 1; - T_Damage(ent, world, world, vec3_origin, ent->s.origin, - vec3_origin, 4 * ent->waterlevel, 0, 0, MOD_SLIME); - } - } - - if (!(ent->flags & FL_INWATER)) - { - if (!(ent->svflags & SVF_DEADMONSTER)) - { - if (ent->watertype & CONTENTS_LAVA) - { - if (random() <= 0.5) - { - gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava1.wav"), 1, ATTN_NORM, 0); - } - else - { - gi.sound(ent, CHAN_BODY, gi.soundindex("player/lava2.wav"), 1, ATTN_NORM, 0); - } - } - else if (ent->watertype & CONTENTS_SLIME) - { - gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); - } - else if (ent->watertype & CONTENTS_WATER) - { - gi.sound(ent, CHAN_BODY, gi.soundindex("player/watr_in.wav"), 1, ATTN_NORM, 0); - } - } - - ent->flags |= FL_INWATER; - ent->damage_debounce_time = 0; - } -} - -void -M_droptofloor(edict_t *ent) -{ - vec3_t end; - trace_t trace; - - if (!ent) - { - return; - } - - if (ent->gravityVector[2] < 0) - { - ent->s.origin[2] += 1; - VectorCopy(ent->s.origin, end); - end[2] -= 256; - } - else - { - ent->s.origin[2] -= 1; - VectorCopy(ent->s.origin, end); - end[2] += 256; - } - - trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, - end, ent, MASK_MONSTERSOLID); - - if ((trace.fraction == 1) || trace.allsolid) - { - return; - } - - VectorCopy(trace.endpos, ent->s.origin); - - gi.linkentity(ent); - M_CheckGround(ent); - M_CatagorizePosition(ent); -} - -void -M_SetEffects(edict_t *ent) -{ - int remaining; - - if (!ent) - { - return; - } - - ent->s.effects &= ~(EF_COLOR_SHELL | EF_POWERSCREEN | EF_DOUBLE | EF_QUAD | EF_PENT); - ent->s.renderfx &= ~(RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE | RF_SHELL_DOUBLE); - - if (ent->monsterinfo.aiflags & AI_RESURRECTING) - { - ent->s.effects |= EF_COLOR_SHELL; - ent->s.renderfx |= RF_SHELL_RED; - } - - if (ent->health <= 0) - { - return; - } - - if (ent->powerarmor_time > level.time) - { - if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SCREEN) - { - ent->s.effects |= EF_POWERSCREEN; - } - else if (ent->monsterinfo.power_armor_type == POWER_ARMOR_SHIELD) - { - ent->s.effects |= EF_COLOR_SHELL; - ent->s.renderfx |= RF_SHELL_GREEN; - } - } - - if (ent->monsterinfo.quad_framenum > level.framenum) - { - remaining = ent->monsterinfo.quad_framenum - level.framenum; - - if ((remaining > 30) || (remaining & 4)) - { - ent->s.effects |= EF_QUAD; - } - } - else - { - ent->s.effects &= ~EF_QUAD; - } - - if (ent->monsterinfo.double_framenum > level.framenum) - { - remaining = ent->monsterinfo.double_framenum - level.framenum; - - if ((remaining > 30) || (remaining & 4)) - { - ent->s.effects |= EF_DOUBLE; - } - } - else - { - ent->s.effects &= ~EF_DOUBLE; - } - - if (ent->monsterinfo.invincible_framenum > level.framenum) - { - remaining = ent->monsterinfo.invincible_framenum - level.framenum; - - if ((remaining > 30) || (remaining & 4)) - { - ent->s.effects |= EF_PENT; - } - } - else - { - ent->s.effects &= ~EF_PENT; - } -} - -void -M_MoveFrame(edict_t *self) -{ - mmove_t *move; - int index; - - if (!self) - { - return; - } - - move = self->monsterinfo.currentmove; - self->nextthink = level.time + FRAMETIME; - - if ((self->monsterinfo.nextframe) && - (self->monsterinfo.nextframe >= move->firstframe) && - (self->monsterinfo.nextframe <= move->lastframe)) - { - if (self->s.frame != self->monsterinfo.nextframe) - { - self->s.frame = self->monsterinfo.nextframe; - self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; - } - - self->monsterinfo.nextframe = 0; - } - else - { - /* prevent nextframe from leaking into a future move */ - self->monsterinfo.nextframe = 0; - - if (self->s.frame == move->lastframe) - { - if (move->endfunc) - { - move->endfunc(self); - - /* regrab move, endfunc is very likely to change it */ - move = self->monsterinfo.currentmove; - - /* check for death */ - if (self->svflags & SVF_DEADMONSTER) - { - return; - } - } - } - - if ((self->s.frame < move->firstframe) || - (self->s.frame > move->lastframe)) - { - self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; - self->s.frame = move->firstframe; - } - else - { - if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) - { - self->s.frame++; - - if (self->s.frame > move->lastframe) - { - self->s.frame = move->firstframe; - } - } - } - } - - index = self->s.frame - move->firstframe; - - if (move->frame[index].aifunc) - { - if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) - { - move->frame[index].aifunc(self, - move->frame[index].dist * self->monsterinfo.scale); - } - else - { - move->frame[index].aifunc(self, 0); - } - } - - if (move->frame[index].thinkfunc) - { - move->frame[index].thinkfunc(self); - } -} - -void -monster_think(edict_t *self) -{ - if (!self) - { - return; - } - - M_MoveFrame(self); - - if (self->linkcount != self->monsterinfo.linkcount) - { - self->monsterinfo.linkcount = self->linkcount; - M_CheckGround(self); - } - - M_CatagorizePosition(self); - M_WorldEffects(self); - M_SetEffects(self); -} - -/* - * Using a monster makes it angry - * at the current activator - */ -void -monster_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - if (self->enemy) - { - return; - } - - if (self->health <= 0) - { - return; - } - - if (activator->flags & FL_NOTARGET) - { - return; - } - - if (!(activator->client) && !(activator->monsterinfo.aiflags & AI_GOOD_GUY)) - { - return; - } - - if (activator->flags & FL_DISGUISED) - { - return; - } - - /* delay reaction so if the monster is - teleported, its sound is still heard */ - self->enemy = activator; - FoundTarget(self); -} - -void -monster_triggered_spawn(edict_t *self) -{ - if (!self) - { - return; - } - - self->s.origin[2] += 1; - KillBox(self); - - self->solid = SOLID_BBOX; - self->movetype = MOVETYPE_STEP; - self->svflags &= ~SVF_NOCLIENT; - self->air_finished = level.time + 12; - gi.linkentity(self); - - monster_start_go(self); - - if (strcmp(self->classname, "monster_fixbot") == 0) - { - if (self->spawnflags & 16 || self->spawnflags & 8 || self->spawnflags & - 4) - { - self->enemy = NULL; - return; - } - } - - if (self->enemy && !(self->spawnflags & 1) && - !(self->enemy->flags & FL_NOTARGET)) - { - if (!(self->enemy->flags & FL_DISGUISED)) - { - FoundTarget(self); - } - else - { - self->enemy = NULL; - } - } - else - { - self->enemy = NULL; - } -} - -void -monster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - /* we have a one frame delay here so we - don't telefrag the guy who activated us */ - self->think = monster_triggered_spawn; - self->nextthink = level.time + FRAMETIME; - - if (activator->client) - { - self->enemy = activator; - } - - self->use = monster_use; -} - -void -monster_triggered_start(edict_t *self) -{ - if (!self) - { - return; - } - - self->solid = SOLID_NOT; - self->movetype = MOVETYPE_NONE; - self->svflags |= SVF_NOCLIENT; - self->nextthink = 0; - self->use = monster_triggered_spawn_use; -} - -/* - * When a monster dies, it fires all of its targets - * with the current enemy as activator. - */ -void -monster_death_use(edict_t *self) -{ - if (!self) - { - return; - } - - self->flags &= ~(FL_FLY | FL_SWIM); - self->monsterinfo.aiflags &= AI_GOOD_GUY; - - if (self->item) - { - Drop_Item(self, self->item); - self->item = NULL; - } - - if (self->deathtarget) - { - self->target = self->deathtarget; - } - - if (!self->target) - { - return; - } - - G_UseTargets(self, self->enemy); -} - -/* ================================================================== */ - -qboolean -monster_start(edict_t *self) -{ - if (!self) - { - return false; - } - - if (deathmatch->value) - { - G_FreeEdict(self); - return false; - } - - if ((self->spawnflags & 4) && !(self->monsterinfo.aiflags & AI_GOOD_GUY)) - { - self->spawnflags &= ~4; - self->spawnflags |= 1; - } - - if ((self->spawnflags & 2) && !self->targetname) - { - if (g_fix_triggered->value) - { - self->spawnflags &= ~2; - } - - gi.dprintf ("triggered %s at %s has no targetname\n", self->classname, vtos (self->s.origin)); - } - - if ((!(self->monsterinfo.aiflags & AI_GOOD_GUY)) && - (!(self->monsterinfo.aiflags & AI_DO_NOT_COUNT))) - { - level.total_monsters++; - } - - self->nextthink = level.time + FRAMETIME; - self->svflags |= SVF_MONSTER; - self->s.renderfx |= RF_FRAMELERP; - self->takedamage = DAMAGE_AIM; - self->air_finished = level.time + 12; - self->use = monster_use; - - if(!self->max_health) - { - self->max_health = self->health; - } - - self->clipmask = MASK_MONSTERSOLID; - - self->s.skinnum = 0; - self->deadflag = DEAD_NO; - self->svflags &= ~SVF_DEADMONSTER; - - if (!self->monsterinfo.checkattack) - { - self->monsterinfo.checkattack = M_CheckAttack; - } - - VectorCopy(self->s.origin, self->s.old_origin); - - if (st.item) - { - self->item = FindItemByClassname(st.item); - - if (!self->item) - { - gi.dprintf("%s at %s has bad item: %s\n", self->classname, - vtos(self->s.origin), st.item); - } - } - - /* randomize what frame they start on */ - if (self->monsterinfo.currentmove) - { - self->s.frame = self->monsterinfo.currentmove->firstframe + - (randk() % (self->monsterinfo.currentmove->lastframe - - self->monsterinfo.currentmove->firstframe + 1)); - } - - self->monsterinfo.base_height = self->maxs[2]; - self->monsterinfo.quad_framenum = 0; - self->monsterinfo.double_framenum = 0; - self->monsterinfo.invincible_framenum = 0; - - return true; -} - -void -monster_start_go(edict_t *self) -{ - vec3_t v; - - if (!self) - { - return; - } - - if (self->health <= 0) - { - return; - } - - /* check for target to combat_point and change to combattarget */ - if (self->target) - { - qboolean notcombat; - qboolean fixup; - edict_t *target; - - target = NULL; - notcombat = false; - fixup = false; - - while ((target = G_Find(target, FOFS(targetname), self->target)) != NULL) - { - if (strcmp(target->classname, "point_combat") == 0) - { - self->combattarget = self->target; - fixup = true; - } - else - { - notcombat = true; - } - } - - if (notcombat && self->combattarget) - { - gi.dprintf("%s at %s has target with mixed types\n", - self->classname, vtos(self->s.origin)); - } - - if (fixup) - { - self->target = NULL; - } - } - - /* validate combattarget */ - if (self->combattarget) - { - edict_t *target; - - target = NULL; - - while ((target = G_Find(target, FOFS(targetname), - self->combattarget)) != NULL) - { - if (strcmp(target->classname, "point_combat") != 0) - { - gi.dprintf( "%s at (%i %i %i) has a bad combattarget %s : %s at (%i %i %i)\n", - self->classname, (int)self->s.origin[0], (int)self->s.origin[1], - (int)self->s.origin[2], self->combattarget, target->classname, - (int)target->s.origin[0], (int)target->s.origin[1], - (int)target->s.origin[2]); - } - } - } - - if (self->target) - { - self->goalentity = self->movetarget = G_PickTarget(self->target); - - if (!self->movetarget) - { - gi.dprintf("%s can't find target %s at %s\n", self->classname, - self->target, vtos(self->s.origin)); - self->target = NULL; - self->monsterinfo.pausetime = 100000000; - self->monsterinfo.stand(self); - } - else if (strcmp(self->movetarget->classname, "path_corner") == 0) - { - VectorSubtract(self->goalentity->s.origin, self->s.origin, v); - self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v); - self->monsterinfo.walk(self); - self->target = NULL; - } - else - { - self->goalentity = self->movetarget = NULL; - self->monsterinfo.pausetime = 100000000; - self->monsterinfo.stand(self); - } - } - else - { - self->monsterinfo.pausetime = 100000000; - self->monsterinfo.stand(self); - } - - self->think = monster_think; - self->nextthink = level.time + FRAMETIME; -} - -void -walkmonster_start_go(edict_t *self) -{ - if (!self) - { - return; - } - - if (!(self->spawnflags & 2) && (level.time < 1)) - { - M_droptofloor(self); - - if (self->groundentity) - { - if (!M_walkmove(self, 0, 0)) - { - gi.dprintf("%s in solid at %s\n", self->classname, - vtos(self->s.origin)); - } - } - } - - if (!self->yaw_speed) - { - self->yaw_speed = 20; - } - - if (!self->viewheight) - { - self->viewheight = 25; - } - - if (self->spawnflags & 2) - { - monster_triggered_start(self); - } - else - { - monster_start_go(self); - } -} - -void -walkmonster_start(edict_t *self) -{ - if (!self) - { - return; - } - - self->think = walkmonster_start_go; - monster_start(self); -} - -void -flymonster_start_go(edict_t *self) -{ - if (!self) - { - return; - } - - if (!M_walkmove(self, 0, 0)) - { - gi.dprintf("%s in solid at %s\n", self->classname, vtos(self->s.origin)); - } - - if (!self->yaw_speed) - { - self->yaw_speed = 10; - } - - if (!self->viewheight) - { - self->viewheight = 25; - } - - if (self->spawnflags & 2) - { - monster_triggered_start(self); - } - else - { - monster_start_go(self); - } -} - -void -flymonster_start(edict_t *self) -{ - if (!self) - { - return; - } - - self->flags |= FL_FLY; - self->think = flymonster_start_go; - monster_start(self); -} - -void -swimmonster_start_go(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->yaw_speed) - { - self->yaw_speed = 10; - } - - if (!self->viewheight) - { - self->viewheight = 10; - } - - if (self->spawnflags & 2) - { - monster_triggered_start(self); - } - else - { - monster_start_go(self); - } -} - -void -swimmonster_start(edict_t *self) -{ - if (!self) - { - return; - } - - self->flags |= FL_SWIM; - self->think = swimmonster_start_go; - monster_start(self); -} - -void stationarymonster_start_go(edict_t *self); - -void -stationarymonster_triggered_spawn(edict_t *self) -{ - if (!self) - { - return; - } - - KillBox(self); - - self->solid = SOLID_BBOX; - self->movetype = MOVETYPE_NONE; - self->svflags &= ~SVF_NOCLIENT; - self->air_finished = level.time + 12; - gi.linkentity(self); - - monster_start_go(self); - - if (self->enemy && !(self->spawnflags & 1) && - !(self->enemy->flags & FL_NOTARGET)) - { - if (!(self->enemy->flags & FL_DISGUISED)) - { - FoundTarget(self); - } - else - { - self->enemy = NULL; - } - } - else - { - self->enemy = NULL; - } -} - -void -stationarymonster_triggered_spawn_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - /* we have a one frame delay here so we don't telefrag the guy who activated us */ - self->think = stationarymonster_triggered_spawn; - self->nextthink = level.time + FRAMETIME; - - if (activator->client) - { - self->enemy = activator; - } - - self->use = monster_use; -} - -void -stationarymonster_triggered_start(edict_t *self) -{ - if (!self) - { - return; - } - - self->solid = SOLID_NOT; - self->movetype = MOVETYPE_NONE; - self->svflags |= SVF_NOCLIENT; - self->nextthink = 0; - self->use = stationarymonster_triggered_spawn_use; -} - -void -stationarymonster_start_go(edict_t *self) -{ - - if (!self) - { - return; - } - - if (!self->yaw_speed) - { - self->yaw_speed = 20; - } - - if (self->spawnflags & 2) - { - stationarymonster_triggered_start(self); - } - else - { - monster_start_go(self); - } -} - -void -stationarymonster_start(edict_t *self) -{ - if (!self) - { - return; - } - - self->think = stationarymonster_start_go; - monster_start(self); -} - -void -monster_done_dodge(edict_t *self) -{ - if (!self) - { - return; - } - - self->monsterinfo.aiflags &= ~AI_DODGING; -} diff --git a/src/rogue/g_spawn.c b/src/rogue/g_spawn.c deleted file mode 100644 index ec9b476c..00000000 --- a/src/rogue/g_spawn.c +++ /dev/null @@ -1,1842 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Item spawning. - * - * ======================================================================= - */ - -#include "header/local.h" - -#define LEG_WAIT_TIME 1 -#define MAX_LEGSFRAME 23 - -#define SPAWNGROW_LIFESPAN 0.3 -#define STEPSIZE 18 - -typedef struct -{ - char *name; - void (*spawn)(edict_t *ent); -} spawn_t; - -void SP_item_health(edict_t *self); -void SP_item_health_small(edict_t *self); -void SP_item_health_large(edict_t *self); -void SP_item_health_mega(edict_t *self); - -void SP_info_player_start(edict_t *ent); -void SP_info_player_deathmatch(edict_t *ent); -void SP_info_player_coop(edict_t *ent); -void SP_info_player_intermission(edict_t *ent); - -void SP_func_plat(edict_t *ent); -void SP_func_rotating(edict_t *ent); -void SP_func_button(edict_t *ent); -void SP_func_door(edict_t *ent); -void SP_func_door_secret(edict_t *ent); -void SP_func_door_rotating(edict_t *ent); -void SP_func_water(edict_t *ent); -void SP_func_train(edict_t *ent); -void SP_func_conveyor(edict_t *self); -void SP_func_wall(edict_t *self); -void SP_func_object(edict_t *self); -void SP_func_explosive(edict_t *self); -void SP_func_timer(edict_t *self); -void SP_func_areaportal(edict_t *ent); -void SP_func_clock(edict_t *ent); -void SP_func_killbox(edict_t *ent); - -void SP_trigger_always(edict_t *ent); -void SP_trigger_once(edict_t *ent); -void SP_trigger_multiple(edict_t *ent); -void SP_trigger_relay(edict_t *ent); -void SP_trigger_push(edict_t *ent); -void SP_trigger_hurt(edict_t *ent); -void SP_trigger_key(edict_t *ent); -void SP_trigger_counter(edict_t *ent); -void SP_trigger_elevator(edict_t *ent); -void SP_trigger_gravity(edict_t *ent); -void SP_trigger_monsterjump(edict_t *ent); - -void SP_target_temp_entity(edict_t *ent); -void SP_target_speaker(edict_t *ent); -void SP_target_explosion(edict_t *ent); -void SP_target_changelevel(edict_t *ent); -void SP_target_secret(edict_t *ent); -void SP_target_goal(edict_t *ent); -void SP_target_splash(edict_t *ent); -void SP_target_spawner(edict_t *ent); -void SP_target_blaster(edict_t *ent); -void SP_target_crosslevel_trigger(edict_t *ent); -void SP_target_crosslevel_target(edict_t *ent); -void SP_target_laser(edict_t *self); -void SP_target_help(edict_t *ent); -void SP_target_lightramp(edict_t *self); -void SP_target_earthquake(edict_t *ent); -void SP_target_character(edict_t *ent); -void SP_target_string(edict_t *ent); - -void SP_worldspawn(edict_t *ent); -void SP_viewthing(edict_t *ent); - -void SP_light(edict_t *self); -void SP_light_mine1(edict_t *ent); -void SP_light_mine2(edict_t *ent); -void SP_info_null(edict_t *self); -void SP_info_notnull(edict_t *self); -void SP_path_corner(edict_t *self); -void SP_point_combat(edict_t *self); - -void SP_misc_explobox(edict_t *self); -void SP_misc_banner(edict_t *self); -void SP_misc_satellite_dish(edict_t *self); -void SP_misc_gib_arm(edict_t *self); -void SP_misc_gib_leg(edict_t *self); -void SP_misc_gib_head(edict_t *self); -void SP_misc_insane(edict_t *self); -void SP_misc_deadsoldier(edict_t *self); -void SP_misc_viper(edict_t *self); -void SP_misc_viper_bomb(edict_t *self); -void SP_misc_bigviper(edict_t *self); -void SP_misc_strogg_ship(edict_t *self); -void SP_misc_teleporter(edict_t *self); -void SP_misc_teleporter_dest(edict_t *self); -void SP_misc_blackhole(edict_t *self); -void SP_misc_eastertank(edict_t *self); -void SP_misc_easterchick(edict_t *self); -void SP_misc_easterchick2(edict_t *self); - -void SP_monster_berserk(edict_t *self); -void SP_monster_gladiator(edict_t *self); -void SP_monster_gunner(edict_t *self); -void SP_monster_infantry(edict_t *self); -void SP_monster_soldier_light(edict_t *self); -void SP_monster_soldier(edict_t *self); -void SP_monster_soldier_ss(edict_t *self); -void SP_monster_tank(edict_t *self); -void SP_monster_medic(edict_t *self); -void SP_monster_flipper(edict_t *self); -void SP_monster_chick(edict_t *self); -void SP_monster_parasite(edict_t *self); -void SP_monster_flyer(edict_t *self); -void SP_monster_brain(edict_t *self); -void SP_monster_floater(edict_t *self); -void SP_monster_hover(edict_t *self); -void SP_monster_mutant(edict_t *self); -void SP_monster_supertank(edict_t *self); -void SP_monster_boss2(edict_t *self); -void SP_monster_jorg(edict_t *self); -void SP_monster_makron(edict_t *self); -void SP_monster_boss3_stand(edict_t *self); - -void SP_monster_commander_body(edict_t *self); - -void SP_turret_breach(edict_t *self); -void SP_turret_base(edict_t *self); -void SP_turret_driver(edict_t *self); - -void SP_func_plat2(edict_t *ent); -void SP_func_door_secret2(edict_t *ent); -void SP_func_force_wall(edict_t *ent); -void SP_info_player_coop_lava(edict_t *self); -void SP_info_teleport_destination(edict_t *self); -void SP_trigger_teleport(edict_t *self); -void SP_trigger_disguise(edict_t *self); -void SP_monster_stalker(edict_t *self); -void SP_monster_turret(edict_t *self); -void SP_target_steam(edict_t *self); -void SP_target_anger(edict_t *self); -void SP_target_killplayers(edict_t *self); - -void SP_target_blacklight(edict_t *self); -void SP_target_orb(edict_t *self); - -void SP_hint_path(edict_t *self); -void SP_monster_carrier(edict_t *self); -void SP_monster_widow(edict_t *self); -void SP_monster_widow2(edict_t *self); -void SP_dm_tag_token(edict_t *self); -void SP_dm_dball_goal(edict_t *self); -void SP_dm_dball_ball(edict_t *self); -void SP_dm_dball_team1_start(edict_t *self); -void SP_dm_dball_team2_start(edict_t *self); -void SP_dm_dball_ball_start(edict_t *self); -void SP_dm_dball_speed_change(edict_t *self); -void SP_monster_kamikaze(edict_t *self); -void SP_turret_invisible_brain(edict_t *self); -void SP_xatrix_item(edict_t *self); -void SP_misc_nuke_core(edict_t *self); - -void ThrowMoreStuff(edict_t *self, vec3_t point); -void ThrowSmallStuff(edict_t *self, vec3_t point); -void ThrowWidowGibLoc(edict_t *self, char *gibname, int damage, - int type, vec3_t startpos, qboolean fade); -void ThrowWidowGibSized(edict_t *self, char *gibname, int damage, int type, - vec3_t startpos, int hitsound, qboolean fade); - -static spawn_t spawns[] = { - {"item_health", SP_item_health}, - {"item_health_small", SP_item_health_small}, - {"item_health_large", SP_item_health_large}, - {"item_health_mega", SP_item_health_mega}, - - {"info_player_start", SP_info_player_start}, - {"info_player_deathmatch", SP_info_player_deathmatch}, - {"info_player_coop", SP_info_player_coop}, - {"info_player_intermission", SP_info_player_intermission}, - - {"func_plat", SP_func_plat}, - {"func_button", SP_func_button}, - {"func_door", SP_func_door}, - {"func_door_secret", SP_func_door_secret}, - {"func_door_rotating", SP_func_door_rotating}, - {"func_rotating", SP_func_rotating}, - {"func_train", SP_func_train}, - {"func_water", SP_func_water}, - {"func_conveyor", SP_func_conveyor}, - {"func_areaportal", SP_func_areaportal}, - {"func_clock", SP_func_clock}, - {"func_wall", SP_func_wall}, - {"func_object", SP_func_object}, - {"func_timer", SP_func_timer}, - {"func_explosive", SP_func_explosive}, - {"func_killbox", SP_func_killbox}, - - {"trigger_always", SP_trigger_always}, - {"trigger_once", SP_trigger_once}, - {"trigger_multiple", SP_trigger_multiple}, - {"trigger_relay", SP_trigger_relay}, - {"trigger_push", SP_trigger_push}, - {"trigger_hurt", SP_trigger_hurt}, - {"trigger_key", SP_trigger_key}, - {"trigger_counter", SP_trigger_counter}, - {"trigger_elevator", SP_trigger_elevator}, - {"trigger_gravity", SP_trigger_gravity}, - {"trigger_monsterjump", SP_trigger_monsterjump}, - - {"target_temp_entity", SP_target_temp_entity}, - {"target_speaker", SP_target_speaker}, - {"target_explosion", SP_target_explosion}, - {"target_changelevel", SP_target_changelevel}, - {"target_secret", SP_target_secret}, - {"target_goal", SP_target_goal}, - {"target_splash", SP_target_splash}, - {"target_spawner", SP_target_spawner}, - {"target_blaster", SP_target_blaster}, - {"target_crosslevel_trigger", SP_target_crosslevel_trigger}, - {"target_crosslevel_target", SP_target_crosslevel_target}, - {"target_laser", SP_target_laser}, - {"target_help", SP_target_help}, - {"target_lightramp", SP_target_lightramp}, - {"target_earthquake", SP_target_earthquake}, - {"target_character", SP_target_character}, - {"target_string", SP_target_string}, - - {"worldspawn", SP_worldspawn}, - {"viewthing", SP_viewthing}, - - {"light", SP_light}, - {"light_mine1", SP_light_mine1}, - {"light_mine2", SP_light_mine2}, - {"info_null", SP_info_null}, - {"func_group", SP_info_null}, - {"info_notnull", SP_info_notnull}, - {"path_corner", SP_path_corner}, - {"point_combat", SP_point_combat}, - - {"misc_explobox", SP_misc_explobox}, - {"misc_banner", SP_misc_banner}, - {"misc_satellite_dish", SP_misc_satellite_dish}, - {"misc_gib_arm", SP_misc_gib_arm}, - {"misc_gib_leg", SP_misc_gib_leg}, - {"misc_gib_head", SP_misc_gib_head}, - {"misc_insane", SP_misc_insane}, - {"misc_deadsoldier", SP_misc_deadsoldier}, - {"misc_viper", SP_misc_viper}, - {"misc_viper_bomb", SP_misc_viper_bomb}, - {"misc_bigviper", SP_misc_bigviper}, - {"misc_strogg_ship", SP_misc_strogg_ship}, - {"misc_teleporter", SP_misc_teleporter}, - {"misc_teleporter_dest", SP_misc_teleporter_dest}, - {"misc_blackhole", SP_misc_blackhole}, - {"misc_eastertank", SP_misc_eastertank}, - {"misc_easterchick", SP_misc_easterchick}, - {"misc_easterchick2", SP_misc_easterchick2}, - - {"monster_berserk", SP_monster_berserk}, - {"monster_gladiator", SP_monster_gladiator}, - {"monster_gunner", SP_monster_gunner}, - {"monster_infantry", SP_monster_infantry}, - {"monster_soldier_light", SP_monster_soldier_light}, - {"monster_soldier", SP_monster_soldier}, - {"monster_soldier_ss", SP_monster_soldier_ss}, - {"monster_tank", SP_monster_tank}, - {"monster_tank_commander", SP_monster_tank}, - {"monster_medic", SP_monster_medic}, - {"monster_flipper", SP_monster_flipper}, - {"monster_chick", SP_monster_chick}, - {"monster_parasite", SP_monster_parasite}, - {"monster_flyer", SP_monster_flyer}, - {"monster_brain", SP_monster_brain}, - {"monster_floater", SP_monster_floater}, - {"monster_hover", SP_monster_hover}, - {"monster_mutant", SP_monster_mutant}, - {"monster_supertank", SP_monster_supertank}, - {"monster_boss2", SP_monster_boss2}, - {"monster_boss3_stand", SP_monster_boss3_stand}, - {"monster_makron", SP_monster_makron}, - {"monster_jorg", SP_monster_jorg}, - - {"monster_commander_body", SP_monster_commander_body}, - - {"turret_breach", SP_turret_breach}, - {"turret_base", SP_turret_base}, - {"turret_driver", SP_turret_driver}, - - {"func_plat2", SP_func_plat2}, - {"func_door_secret2", SP_func_door_secret2}, - {"func_force_wall", SP_func_force_wall}, - {"trigger_teleport", SP_trigger_teleport}, - {"trigger_disguise", SP_trigger_disguise}, - {"info_teleport_destination", SP_info_teleport_destination}, - {"info_player_coop_lava", SP_info_player_coop_lava}, - {"monster_stalker", SP_monster_stalker}, - {"monster_turret", SP_monster_turret}, - {"target_steam", SP_target_steam}, - {"target_anger", SP_target_anger}, - {"target_killplayers", SP_target_killplayers}, - {"target_blacklight", SP_target_blacklight}, - {"target_orb", SP_target_orb}, - {"monster_daedalus", SP_monster_hover}, - {"hint_path", SP_hint_path}, - {"monster_carrier", SP_monster_carrier}, - {"monster_widow", SP_monster_widow}, - {"monster_widow2", SP_monster_widow2}, - {"monster_medic_commander", SP_monster_medic}, - {"dm_tag_token", SP_dm_tag_token}, - {"dm_dball_goal", SP_dm_dball_goal}, - {"dm_dball_ball", SP_dm_dball_ball}, - {"dm_dball_team1_start", SP_dm_dball_team1_start}, - {"dm_dball_team2_start", SP_dm_dball_team2_start}, - {"dm_dball_ball_start", SP_dm_dball_ball_start}, - {"dm_dball_speed_change", SP_dm_dball_speed_change}, - {"monster_kamikaze", SP_monster_kamikaze}, - {"turret_invisible_brain", SP_turret_invisible_brain}, - {"misc_nuke_core", SP_misc_nuke_core}, - - {"ammo_magslug", SP_xatrix_item}, - {"ammo_trap", SP_xatrix_item}, - {"item_quadfire", SP_xatrix_item}, - {"weapon_boomer", SP_xatrix_item}, - {"weapon_phalanx", SP_xatrix_item}, - - {NULL, NULL} -}; - -/* - * Finds the spawn function for - * the entity and calls it - */ -void -ED_CallSpawn(edict_t *ent) -{ - spawn_t *s; - gitem_t *item; - int i; - - if (!ent) - { - return; - } - - if (!ent->classname) - { - gi.dprintf("ED_CallSpawn: NULL classname\n"); - G_FreeEdict(ent); - return; - } - - ent->gravityVector[0] = 0.0; - ent->gravityVector[1] = 0.0; - ent->gravityVector[2] = -1.0; - - if (!strcmp(ent->classname, "weapon_nailgun")) - { - ent->classname = (FindItem("ETF Rifle"))->classname; - } - - if (!strcmp(ent->classname, "ammo_nails")) - { - ent->classname = (FindItem("Flechettes"))->classname; - } - - if (!strcmp(ent->classname, "weapon_heatbeam")) - { - ent->classname = (FindItem("Plasma Beam"))->classname; - } - - /* check item spawn functions */ - for (i = 0, item = itemlist; i < game.num_items; i++, item++) - { - if (!item->classname) - { - continue; - } - - if (!strcmp(item->classname, ent->classname)) - { - /* found it */ - SpawnItem(ent, item); - return; - } - } - - /* check normal spawn functions */ - for (s = spawns; s->name; s++) - { - if (!strcmp(s->name, ent->classname)) - { - /* found it */ - s->spawn(ent); - return; - } - } - - gi.dprintf("%s doesn't have a spawn function\n", ent->classname); -} - -char * -ED_NewString(const char *string) -{ - char *newb, *new_p; - int i, l; - - if (!string) - { - return NULL; - } - - l = strlen(string) + 1; - - newb = gi.TagMalloc(l, TAG_LEVEL); - - new_p = newb; - - for (i = 0; i < l; i++) - { - if ((string[i] == '\\') && (i < l - 1)) - { - i++; - - if (string[i] == 'n') - { - *new_p++ = '\n'; - } - else - { - *new_p++ = '\\'; - } - } - else - { - *new_p++ = string[i]; - } - } - - return newb; -} - -/* - * Takes a key/value pair and sets - * the binary values in an edict - */ -void -ED_ParseField(const char *key, const char *value, edict_t *ent) -{ - field_t *f; - byte *b; - float v; - vec3_t vec; - - if (!ent || !value || !key) - { - return; - } - - for (f = fields; f->name; f++) - { - if (!(f->flags & FFL_NOSPAWN) && !Q_strcasecmp(f->name, (char *)key)) - { - /* found it */ - if (f->flags & FFL_SPAWNTEMP) - { - b = (byte *)&st; - } - else - { - b = (byte *)ent; - } - - switch (f->type) - { - case F_LSTRING: - *(char **)(b + f->ofs) = ED_NewString(value); - break; - case F_VECTOR: - sscanf(value, "%f %f %f", &vec[0], &vec[1], &vec[2]); - ((float *)(b + f->ofs))[0] = vec[0]; - ((float *)(b + f->ofs))[1] = vec[1]; - ((float *)(b + f->ofs))[2] = vec[2]; - break; - case F_INT: - *(int *)(b + f->ofs) = (int)strtol(value, (char **)NULL, 10); - break; - case F_FLOAT: - *(float *)(b + f->ofs) = strtof(value, (char **)NULL);; - break; - case F_ANGLEHACK: - v = strtof(value, (char **)NULL);; - ((float *)(b + f->ofs))[0] = 0; - ((float *)(b + f->ofs))[1] = v; - ((float *)(b + f->ofs))[2] = 0; - break; - case F_IGNORE: - break; - default: - break; - } - - return; - } - } - - gi.dprintf("%s is not a field\n", key); -} - -/* - * Parses an edict out of the given string, - * returning the new position ed should be - * a properly initialized empty edict. - */ -char * -ED_ParseEdict(char *data, edict_t *ent) -{ - qboolean init; - char keyname[256]; - const char *com_token; - - if (!ent) - { - return NULL; - } - - init = false; - memset(&st, 0, sizeof(st)); - st.skyautorotate = 1; - - /* go through all the dictionary pairs */ - while (1) - { - /* parse key */ - com_token = COM_Parse(&data); - - if (com_token[0] == '}') - { - break; - } - - if (!data) - { - gi.error("ED_ParseEntity: EOF without closing brace"); - } - - Q_strlcpy(keyname, com_token, sizeof(keyname)); - - /* parse value */ - com_token = COM_Parse(&data); - - if (!data) - { - gi.error("ED_ParseEntity: EOF without closing brace"); - } - - if (com_token[0] == '}') - { - gi.error("ED_ParseEntity: closing brace without data"); - } - - init = true; - - /* keynames with a leading underscore are - used for utility comments, and are - immediately discarded by quake */ - if (keyname[0] == '_') - { - continue; - } - - ED_ParseField(keyname, com_token, ent); - } - - if (!init) - { - memset(ent, 0, sizeof(*ent)); - } - - return data; -} - -/* - * Chain together all entities with a matching team field. - * - * All but the first will have the FL_TEAMSLAVE flag set. - * All but the last will have the teamchain field set to the next one - */ -static void -G_FixTeams(void) -{ - edict_t *e, *e2, *chain; - int i, j; - int c, c2; - - c = 0; - c2 = 0; - - for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) - { - if (!e->inuse) - { - continue; - } - - if (!e->team) - { - continue; - } - - if (!strcmp(e->classname, "func_train")) - { - if (e->flags & FL_TEAMSLAVE) - { - chain = e; - e->teammaster = e; - e->teamchain = NULL; - e->flags &= ~FL_TEAMSLAVE; - c++; - c2++; - - for (j = 1, e2 = g_edicts + j; - j < globals.num_edicts; - j++, e2++) - { - if (e2 == e) - { - continue; - } - - if (!e2->inuse) - { - continue; - } - - if (!e2->team) - { - continue; - } - - if (!strcmp(e->team, e2->team)) - { - c2++; - chain->teamchain = e2; - e2->teammaster = e; - e2->teamchain = NULL; - chain = e2; - e2->flags |= FL_TEAMSLAVE; - e2->movetype = MOVETYPE_PUSH; - e2->speed = e->speed; - } - } - } - } - } - - gi.dprintf("%i teams repaired\n", c); -} - -void -G_FindTeams(void) -{ - edict_t *e, *e2, *chain; - int i, j; - int c, c2; - - c = 0; - c2 = 0; - - for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) - { - if (!e->inuse) - { - continue; - } - - if (!e->team) - { - continue; - } - - if (e->flags & FL_TEAMSLAVE) - { - continue; - } - - chain = e; - e->teammaster = e; - c++; - c2++; - - for (j = i + 1, e2 = e + 1; j < globals.num_edicts; j++, e2++) - { - if (!e2->inuse) - { - continue; - } - - if (!e2->team) - { - continue; - } - - if (e2->flags & FL_TEAMSLAVE) - { - continue; - } - - if (!strcmp(e->team, e2->team)) - { - c2++; - chain->teamchain = e2; - e2->teammaster = e; - chain = e2; - e2->flags |= FL_TEAMSLAVE; - } - } - } - - G_FixTeams(); - - gi.dprintf("%i teams with %i entities.\n", c, c2); -} - -/* - * Creates a server's entity / program execution context by - * parsing textual entity definitions out of an ent file. - */ -void -SpawnEntities(const char *mapname, char *entities, const char *spawnpoint) -{ - edict_t *ent; - int inhibit; - const char *com_token; - int i; - float skill_level; - - if (!mapname || !entities || !spawnpoint) - { - return; - } - - skill_level = floor(skill->value); - - if (skill_level < 0) - { - skill_level = 0; - } - - if (skill_level > 3) - { - skill_level = 3; - } - - if (skill->value != skill_level) - { - gi.cvar_forceset("skill", va("%f", skill_level)); - } - - SaveClientData(); - - gi.FreeTags(TAG_LEVEL); - - memset(&level, 0, sizeof(level)); - memset(g_edicts, 0, game.maxentities * sizeof(g_edicts[0])); - - Q_strlcpy(level.mapname, mapname, sizeof(level.mapname)); - Q_strlcpy(game.spawnpoint, spawnpoint, sizeof(game.spawnpoint)); - - /* set client fields on player ents */ - for (i = 0; i < game.maxclients; i++) - { - g_edicts[i + 1].client = game.clients + i; - } - - ent = NULL; - inhibit = 0; - - /* parse ents */ - while (1) - { - /* parse the opening brace */ - com_token = COM_Parse(&entities); - - if (!entities) - { - break; - } - - if (com_token[0] != '{') - { - gi.error("ED_LoadFromFile: found %s when expecting {", com_token); - } - - if (!ent) - { - ent = g_edicts; - } - else - { - ent = G_Spawn(); - } - - entities = ED_ParseEdict(entities, ent); - - /* yet another map hack */ - if (!Q_stricmp(level.mapname, "command") && - !Q_stricmp(ent->classname, "trigger_once") && - !Q_stricmp(ent->model, "*27")) - { - ent->spawnflags &= ~SPAWNFLAG_NOT_HARD; - } - - /* ahh, the joys of map hacks .. */ - if (!Q_stricmp(level.mapname, "rhangar2") && - !Q_stricmp(ent->classname, "func_door_rotating") && - ent->targetname && !Q_stricmp(ent->targetname, "t265")) - { - ent->spawnflags &= ~SPAWNFLAG_NOT_COOP; - } - - if (!Q_stricmp(level.mapname, "rhangar2") && - !Q_stricmp(ent->classname, "trigger_always") && - ent->target && !Q_stricmp(ent->target, "t265")) - { - ent->spawnflags |= SPAWNFLAG_NOT_COOP; - } - - if (!Q_stricmp(level.mapname, "rhangar2") && - !Q_stricmp(ent->classname, "func_wall") && - !Q_stricmp(ent->model, "*15")) - { - ent->spawnflags |= SPAWNFLAG_NOT_COOP; - } - - /* remove things (except the world) from - different skill levels or deathmatch */ - if (ent != g_edicts) - { - if (deathmatch->value) - { - if (ent->spawnflags & SPAWNFLAG_NOT_DEATHMATCH) - { - G_FreeEdict(ent); - inhibit++; - continue; - } - } - else if (coop->value && !coop_baseq2->value) - { - if (ent->spawnflags & SPAWNFLAG_NOT_COOP) - { - G_FreeEdict(ent); - inhibit++; - continue; - } - - /* stuff marked !easy & !med & !hard are coop only, all levels */ - if (!((ent->spawnflags & SPAWNFLAG_NOT_EASY) && - (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM) && - (ent->spawnflags & SPAWNFLAG_NOT_HARD))) - { - if (((skill->value == SKILL_EASY) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || - ((skill->value == SKILL_MEDIUM) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || - (((skill->value == SKILL_HARD) || (skill->value == SKILL_HARDPLUS)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))) - { - G_FreeEdict(ent); - inhibit++; - continue; - } - } - } - else - { - if (((skill->value == SKILL_EASY) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || - ((skill->value == SKILL_MEDIUM) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || - (((skill->value == SKILL_HARD) || (skill->value == SKILL_HARDPLUS)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD))) - { - G_FreeEdict(ent); - inhibit++; - continue; - } - } - - ent->spawnflags &= ~(SPAWNFLAG_NOT_EASY | SPAWNFLAG_NOT_MEDIUM | - SPAWNFLAG_NOT_HARD | SPAWNFLAG_NOT_COOP | SPAWNFLAG_NOT_DEATHMATCH); - } - - ent->gravityVector[0] = 0.0; - ent->gravityVector[1] = 0.0; - ent->gravityVector[2] = -1.0; - - ED_CallSpawn(ent); - - ent->s.renderfx |= RF_IR_VISIBLE; - } - - gi.dprintf("%i entities inhibited.\n", inhibit); - - G_FindTeams(); - - PlayerTrail_Init(); - - if (deathmatch->value) - { - if (randomrespawn && randomrespawn->value) - { - PrecacheForRandomRespawn(); - } - } - else - { - InitHintPaths(); - } - - if (deathmatch->value && gamerules && gamerules->value) - { - if (DMGame.PostInitSetup) - { - DMGame.PostInitSetup(); - } - } -} - -/* =================================================================== */ - -static char *single_statusbar = - "yb -24 " - -/* health */ - "xv 0 " - "hnum " - "xv 50 " - "pic 0 " - -/* ammo */ - "if 2 " - " xv 100 " - " anum " - " xv 150 " - " pic 2 " - "endif " - -/* armor */ - "if 4 " - " xv 200 " - " rnum " - " xv 250 " - " pic 4 " - "endif " - -/* selected item */ - "if 6 " - " xv 296 " - " pic 6 " - "endif " - - "yb -50 " - -/* picked up item */ - "if 7 " - " xv 0 " - " pic 7 " - " xv 26 " - " yb -42 " - " stat_string 8 " - " yb -50 " - "endif " - -/* timer */ - "if 9 " - " xv 262 " - " num 2 10 " - " xv 296 " - " pic 9 " - "endif " - -/* help / weapon icon */ - "if 11 " - " xv 148 " - " pic 11 " - "endif " -; - -static char *dm_statusbar = - "yb -24 " - -/* health */ - "xv 0 " - "hnum " - "xv 50 " - "pic 0 " - -/* ammo */ - "if 2 " - " xv 100 " - " anum " - " xv 150 " - " pic 2 " - "endif " - -/* armor */ - "if 4 " - " xv 200 " - " rnum " - " xv 250 " - " pic 4 " - "endif " - -/* selected item */ - "if 6 " - " xv 296 " - " pic 6 " - "endif " - - "yb -50 " - -/* picked up item */ - "if 7 " - " xv 0 " - " pic 7 " - " xv 26 " - " yb -42 " - " stat_string 8 " - " yb -50 " - "endif " - -/* timer */ - "if 9 " - " xv 246 " - " num 2 10 " - " xv 296 " - " pic 9 " - "endif " - -/* help / weapon icon */ - "if 11 " - " xv 148 " - " pic 11 " - "endif " - -/* frags */ - "xr -50 " - "yt 2 " - "num 3 14 " - -/* spectator */ - "if 17 " - "xv 0 " - "yb -58 " - "string2 \"SPECTATOR MODE\" " - "endif " - -/* chase camera */ - "if 16 " - "xv 0 " - "yb -68 " - "string \"Chasing\" " - "xv 64 " - "stat_string 16 " - "endif " -; - -/* - * QUAKED worldspawn (0 0 0) ? - * - * Only used for the world. - * "sky" environment map name - * "skyaxis" vector axis for rotating sky - * "skyrotate" speed of rotation in degrees/second - * "sounds" music cd track number - * "gravity" 800 is default gravity - * "message" text to print at user logon - */ -void -SP_worldspawn(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->movetype = MOVETYPE_PUSH; - ent->solid = SOLID_BSP; - ent->inuse = true; /* since the world doesn't use G_Spawn() */ - ent->s.modelindex = 1; /* world model is always index 1 */ - - /* --------------- */ - - /* reserve some spots for dead - player bodies for coop / deathmatch */ - InitBodyQue(); - - /* set configstrings for items */ - SetItemNames(); - - if (st.nextmap) - { - strcpy(level.nextmap, st.nextmap); - } - - /* make some data visible to the server */ - if (ent->message && ent->message[0]) - { - gi.configstring(CS_NAME, ent->message); - Q_strlcpy(level.level_name, ent->message, sizeof(level.level_name)); - } - else - { - Q_strlcpy(level.level_name, level.mapname, sizeof(level.level_name)); - } - - if (st.sky && st.sky[0]) - { - gi.configstring(CS_SKY, st.sky); - } - else - { - gi.configstring(CS_SKY, "unit1_"); - } - - gi.configstring(CS_SKYROTATE, va("%f %d", st.skyrotate, st.skyautorotate)); - - gi.configstring(CS_SKYAXIS, va("%f %f %f", - st.skyaxis[0], st.skyaxis[1], st.skyaxis[2])); - - gi.configstring(CS_CDTRACK, va("%i", ent->sounds)); - - gi.configstring(CS_MAXCLIENTS, va("%i", (int)(maxclients->value))); - - /* status bar program */ - if (deathmatch->value) - { - gi.configstring(CS_STATUSBAR, dm_statusbar); - } - else - { - gi.configstring(CS_STATUSBAR, single_statusbar); - } - - /* --------------- */ - - /* help icon for statusbar */ - gi.imageindex("i_help"); - level.pic_health = gi.imageindex("i_health"); - gi.imageindex("help"); - gi.imageindex("field_3"); - - if (!st.gravity) - { - gi.cvar_set("sv_gravity", "800"); - } - else - { - gi.cvar_set("sv_gravity", st.gravity); - } - - snd_fry = gi.soundindex("player/fry.wav"); /* standing in lava / slime */ - - PrecacheItem(FindItem("Blaster")); - - gi.soundindex("player/lava1.wav"); - gi.soundindex("player/lava2.wav"); - - gi.soundindex("misc/pc_up.wav"); - gi.soundindex("misc/talk1.wav"); - - gi.soundindex("misc/udeath.wav"); - - /* gibs */ - gi.soundindex("items/respawn1.wav"); - - /* sexed sounds */ - gi.soundindex("*death1.wav"); - gi.soundindex("*death2.wav"); - gi.soundindex("*death3.wav"); - gi.soundindex("*death4.wav"); - gi.soundindex("*fall1.wav"); - gi.soundindex("*fall2.wav"); - gi.soundindex("*gurp1.wav"); /* drowning damage */ - gi.soundindex("*gurp2.wav"); - gi.soundindex("*jump1.wav"); /* player jump */ - gi.soundindex("*pain25_1.wav"); - gi.soundindex("*pain25_2.wav"); - gi.soundindex("*pain50_1.wav"); - gi.soundindex("*pain50_2.wav"); - gi.soundindex("*pain75_1.wav"); - gi.soundindex("*pain75_2.wav"); - gi.soundindex("*pain100_1.wav"); - gi.soundindex("*pain100_2.wav"); - - /* sexed models: THIS ORDER MUST MATCH THE DEFINES IN g_local.h - you can add more, max 19 (pete change)these models are only - loaded in coop or deathmatch. not singleplayer. */ - if (coop->value || deathmatch->value) - { - gi.modelindex("#w_blaster.md2"); - gi.modelindex("#w_shotgun.md2"); - gi.modelindex("#w_sshotgun.md2"); - gi.modelindex("#w_machinegun.md2"); - gi.modelindex("#w_chaingun.md2"); - gi.modelindex("#a_grenades.md2"); - gi.modelindex("#w_glauncher.md2"); - gi.modelindex("#w_rlauncher.md2"); - gi.modelindex("#w_hyperblaster.md2"); - gi.modelindex("#w_railgun.md2"); - gi.modelindex("#w_bfg.md2"); - gi.modelindex("#w_disrupt.md2"); - gi.modelindex("#w_etfrifle.md2"); - gi.modelindex("#w_plasma.md2"); - gi.modelindex("#w_plauncher.md2"); - gi.modelindex("#w_chainfist.md2"); - } - - /* ------------------- */ - - gi.soundindex("player/gasp1.wav"); /* gasping for air */ - gi.soundindex("player/gasp2.wav"); /* head breaking surface, not gasping */ - - gi.soundindex("player/watr_in.wav"); /* feet hitting water */ - gi.soundindex("player/watr_out.wav"); /* feet leaving water */ - - gi.soundindex("player/watr_un.wav"); /* head going underwater */ - - gi.soundindex("player/u_breath1.wav"); - gi.soundindex("player/u_breath2.wav"); - - gi.soundindex("items/pkup.wav"); /* bonus item pickup */ - gi.soundindex("world/land.wav"); /* landing thud */ - gi.soundindex("misc/h2ohit1.wav"); /* landing splash */ - - gi.soundindex("items/damage.wav"); - gi.soundindex("misc/ddamage1.wav"); - gi.soundindex("items/protect.wav"); - gi.soundindex("items/protect4.wav"); - gi.soundindex("weapons/noammo.wav"); - - gi.soundindex("infantry/inflies1.wav"); - - sm_meat_index = gi.modelindex("models/objects/gibs/sm_meat/tris.md2"); - gi.modelindex("models/objects/gibs/arm/tris.md2"); - gi.modelindex("models/objects/gibs/bone/tris.md2"); - gi.modelindex("models/objects/gibs/bone2/tris.md2"); - gi.modelindex("models/objects/gibs/chest/tris.md2"); - gi.modelindex("models/objects/gibs/skull/tris.md2"); - gi.modelindex("models/objects/gibs/head2/tris.md2"); - - /* Setup light animation tables. 'a' - is total darkness, 'z' is doublebright. */ - - /* 0 normal */ - gi.configstring(CS_LIGHTS + 0, "m"); - - /* 1 FLICKER (first variety) */ - gi.configstring(CS_LIGHTS + 1, "mmnmmommommnonmmonqnmmo"); - - /* 2 SLOW STRONG PULSE */ - gi.configstring(CS_LIGHTS + 2, "abcdefghijklmnopqrstuvwxyzyxwvutsrqponmlkjihgfedcba"); - - /* 3 CANDLE (first variety) */ - gi.configstring(CS_LIGHTS + 3, "mmmmmaaaaammmmmaaaaaabcdefgabcdefg"); - - /* 4 FAST STROBE */ - gi.configstring(CS_LIGHTS + 4, "mamamamamama"); - - /* 5 GENTLE PULSE 1 */ - gi.configstring(CS_LIGHTS + 5, "jklmnopqrstuvwxyzyxwvutsrqponmlkj"); - - /* 6 FLICKER (second variety) */ - gi.configstring(CS_LIGHTS + 6, "nmonqnmomnmomomno"); - - /* 7 CANDLE (second variety) */ - gi.configstring(CS_LIGHTS + 7, "mmmaaaabcdefgmmmmaaaammmaamm"); - - /* 8 CANDLE (third variety) */ - gi.configstring(CS_LIGHTS + 8, "mmmaaammmaaammmabcdefaaaammmmabcdefmmmaaaa"); - - /* 9 SLOW STROBE (fourth variety) */ - gi.configstring(CS_LIGHTS + 9, "aaaaaaaazzzzzzzz"); - - /* 10 FLUORESCENT FLICKER */ - gi.configstring(CS_LIGHTS + 10, "mmamammmmammamamaaamammma"); - - /* 11 SLOW PULSE NOT FADE TO BLACK */ - gi.configstring(CS_LIGHTS + 11, "abcdefghijklmnopqrrqponmlkjihgfedcba"); - - /* styles 32-62 are assigned by the light program for switchable lights */ - - /* 63 testing */ - gi.configstring(CS_LIGHTS + 63, "a"); -} - -/* - * Monster spawning code: - * Used by the carrier, the medic_commander, and the black widow - * - * The sequence to create a flying monster is: - * FindSpawnPoint - tries to find suitable spot to spawn the monster in - * CreateFlyMonster - this verifies the point as good and creates the monster - * - * To create a ground walking monster: - * FindSpawnPoint - same thing - * CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable - */ - -edict_t * -CreateMonster(vec3_t origin, vec3_t angles, char *classname) -{ - edict_t *newEnt; - - if (!classname) - { - return NULL; - } - - newEnt = G_Spawn(); - - VectorCopy(origin, newEnt->s.origin); - VectorCopy(angles, newEnt->s.angles); - newEnt->classname = ED_NewString(classname); - newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; - - VectorSet(newEnt->gravityVector, 0, 0, -1); - ED_CallSpawn(newEnt); - newEnt->s.renderfx |= RF_IR_VISIBLE; - - return newEnt; -} - -edict_t * -CreateFlyMonster(vec3_t origin, vec3_t angles, vec3_t mins, - vec3_t maxs, char *classname) -{ - if (!classname) - { - return NULL; - } - - if (!mins || !maxs || - VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin)) - { - DetermineBBox(classname, mins, maxs); - } - - if (!CheckSpawnPoint(origin, mins, maxs)) - { - return NULL; - } - - return CreateMonster(origin, angles, classname); -} - -edict_t * -CreateGroundMonster(vec3_t origin, vec3_t angles, vec3_t entMins, - vec3_t entMaxs, char *classname, int height) -{ - edict_t *newEnt; - vec3_t mins, maxs; - - if (!classname) - { - return NULL; - } - - /* if they don't provide us a bounding box, figure it out */ - if (!entMins || !entMaxs || VectorCompare(entMins, - vec3_origin) || VectorCompare(entMaxs, vec3_origin)) - { - DetermineBBox(classname, mins, maxs); - } - else - { - VectorCopy(entMins, mins); - VectorCopy(entMaxs, maxs); - } - - /* check the ground to make sure it's there, it's relatively flat, and it's not toxic */ - if (!CheckGroundSpawnPoint(origin, mins, maxs, height, -1)) - { - return NULL; - } - - newEnt = CreateMonster(origin, angles, classname); - - if (!newEnt) - { - return NULL; - } - - return newEnt; -} - -qboolean -FindSpawnPoint(vec3_t startpoint, vec3_t mins, vec3_t maxs, - vec3_t spawnpoint, float maxMoveUp) -{ - trace_t tr; - vec3_t top; - - tr = gi.trace(startpoint, mins, maxs, startpoint, - NULL, MASK_MONSTERSOLID | CONTENTS_PLAYERCLIP); - - if ((tr.startsolid || tr.allsolid) || (tr.ent != world)) - { - VectorCopy(startpoint, top); - top[2] += maxMoveUp; - - tr = gi.trace(top, mins, maxs, startpoint, NULL, MASK_MONSTERSOLID); - - if (tr.startsolid || tr.allsolid) - { - return false; - } - else - { - VectorCopy(tr.endpos, spawnpoint); - return true; - } - } - else - { - VectorCopy(startpoint, spawnpoint); - return true; - } -} - -qboolean -CheckSpawnPoint(vec3_t origin, vec3_t mins, vec3_t maxs) -{ - trace_t tr; - - if (!mins || !maxs || - VectorCompare(mins, vec3_origin) || VectorCompare(maxs, vec3_origin)) - { - return false; - } - - tr = gi.trace(origin, mins, maxs, origin, NULL, MASK_MONSTERSOLID); - - if (tr.startsolid || tr.allsolid) - { - return false; - } - - if (tr.ent != world) - { - return false; - } - - return true; -} - -qboolean -CheckGroundSpawnPoint(vec3_t origin, vec3_t entMins, vec3_t entMaxs, - float height, float gravity) -{ - trace_t tr; - vec3_t start, stop; - vec3_t mins, maxs; - int x, y; - float mid, bottom; - - if (!CheckSpawnPoint(origin, entMins, entMaxs)) - { - return false; - } - - - VectorCopy(origin, stop); - stop[2] = origin[2] + entMins[2] - height; - - tr = gi.trace(origin, entMins, entMaxs, stop, - NULL, MASK_MONSTERSOLID | MASK_WATER); - - if ((tr.fraction < 1) && (tr.contents & MASK_MONSTERSOLID)) - { - /* first, do the midpoint trace */ - VectorAdd(tr.endpos, entMins, mins); - VectorAdd(tr.endpos, entMaxs, maxs); - - /* first, do the easy flat check */ - if (gravity > 0) - { - start[2] = maxs[2] + 1; - } - else - { - start[2] = mins[2] - 1; - } - - for (x = 0; x <= 1; x++) - { - for (y = 0; y <= 1; y++) - { - start[0] = x ? maxs[0] : mins[0]; - start[1] = y ? maxs[1] : mins[1]; - - if (gi.pointcontents(start) != CONTENTS_SOLID) - { - goto realcheck; - } - } - } - - /* if it passed all four above checks, we're done */ - return true; - - realcheck: - - /* check it for real */ - start[0] = stop[0] = (mins[0] + maxs[0]) * 0.5; - start[1] = stop[1] = (mins[1] + maxs[1]) * 0.5; - start[2] = mins[2]; - - tr = gi.trace(start, vec3_origin, vec3_origin, - stop, NULL, MASK_MONSTERSOLID); - - if (tr.fraction == 1.0) - { - return false; - } - - if (gravity < 0) - { - start[2] = mins[2]; - stop[2] = start[2] - STEPSIZE - STEPSIZE; - mid = bottom = tr.endpos[2] + entMins[2]; - } - else - { - start[2] = maxs[2]; - stop[2] = start[2] + STEPSIZE + STEPSIZE; - mid = bottom = tr.endpos[2] - entMaxs[2]; - } - - for (x = 0; x <= 1; x++) - { - for (y = 0; y <= 1; y++) - { - start[0] = stop[0] = x ? maxs[0] : mins[0]; - start[1] = stop[1] = y ? maxs[1] : mins[1]; - - tr = gi.trace(start, vec3_origin, vec3_origin, - stop, NULL, MASK_MONSTERSOLID); - - if (gravity > 0) - { - if ((tr.fraction != 1.0) && (tr.endpos[2] < bottom)) - { - bottom = tr.endpos[2]; - } - - if ((tr.fraction == 1.0) || (tr.endpos[2] - mid > STEPSIZE)) - { - return false; - } - } - else - { - if ((tr.fraction != 1.0) && (tr.endpos[2] > bottom)) - { - bottom = tr.endpos[2]; - } - - if ((tr.fraction == 1.0) || (mid - tr.endpos[2] > STEPSIZE)) - { - return false; - } - } - } - } - - return true; /* we can land on it, it's ok */ - } - - /* otherwise, it's either water (bad) or not - * there (too far) if we're here, it's bad below */ - return false; -} - -void -DetermineBBox(char *classname, vec3_t mins, vec3_t maxs) -{ - edict_t *newEnt; - - if (!classname) - { - return; - } - - newEnt = G_Spawn(); - - VectorCopy(vec3_origin, newEnt->s.origin); - VectorCopy(vec3_origin, newEnt->s.angles); - newEnt->classname = ED_NewString(classname); - newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT; - - ED_CallSpawn(newEnt); - - VectorCopy(newEnt->mins, mins); - VectorCopy(newEnt->maxs, maxs); - - G_FreeEdict(newEnt); -} - - -void -spawngrow_think(edict_t *self) -{ - int i; - - if (!self) - { - return; - } - - for (i = 0; i < 2; i++) - { - self->s.angles[0] = rand() % 360; - self->s.angles[1] = rand() % 360; - self->s.angles[2] = rand() % 360; - } - - if ((level.time < self->wait) && (self->s.frame < 2)) - { - self->s.frame++; - } - - if (level.time >= self->wait) - { - if (self->s.effects & EF_SPHERETRANS) - { - G_FreeEdict(self); - return; - } - else if (self->s.frame > 0) - { - self->s.frame--; - } - else - { - G_FreeEdict(self); - return; - } - } - - self->nextthink += FRAMETIME; -} - -void -SpawnGrow_Spawn(vec3_t startpos, int size) -{ - edict_t *ent; - int i; - float lifespan; - - ent = G_Spawn(); - VectorCopy(startpos, ent->s.origin); - - for (i = 0; i < 2; i++) - { - ent->s.angles[0] = rand() % 360; - ent->s.angles[1] = rand() % 360; - ent->s.angles[2] = rand() % 360; - } - - ent->solid = SOLID_NOT; - ent->s.renderfx = RF_IR_VISIBLE; - ent->movetype = MOVETYPE_NONE; - ent->classname = "spawngro"; - - if (size <= 1) - { - lifespan = SPAWNGROW_LIFESPAN; - ent->s.modelindex = gi.modelindex("models/items/spawngro2/tris.md2"); - } - else if (size == 2) - { - ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2"); - lifespan = 2; - } - else - { - ent->s.modelindex = gi.modelindex("models/items/spawngro/tris.md2"); - lifespan = SPAWNGROW_LIFESPAN; - } - - ent->think = spawngrow_think; - - ent->wait = level.time + lifespan; - ent->nextthink = level.time + FRAMETIME; - - if (size != 2) - { - ent->s.effects |= EF_SPHERETRANS; - } - - gi.linkentity(ent); -} - -void -widowlegs_think(edict_t *self) -{ - vec3_t offset; - vec3_t point; - vec3_t f, r, u; - - if (!self) - { - return; - } - - if (self->s.frame == 17) - { - VectorSet(offset, 11.77, -7.24, 23.31); - AngleVectors(self->s.angles, f, r, u); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - ThrowSmallStuff(self, point); - } - - if (self->s.frame < MAX_LEGSFRAME) - { - self->s.frame++; - self->nextthink = level.time + FRAMETIME; - return; - } - else if (self->wait == 0) - { - self->wait = level.time + LEG_WAIT_TIME; - } - - if (level.time > self->wait) - { - AngleVectors(self->s.angles, f, r, u); - - VectorSet(offset, -65.6, -8.44, 28.59); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - ThrowSmallStuff(self, point); - - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - - VectorSet(offset, -1.04, -51.18, 7.04); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - ThrowSmallStuff(self, point); - - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", - 80 + (int)(random() * 20.0), GIB_METALLIC, point, 0, true); - - G_FreeEdict(self); - return; - } - - if ((level.time > (self->wait - 0.5)) && (self->count == 0)) - { - self->count = 1; - AngleVectors(self->s.angles, f, r, u); - - VectorSet(offset, 31, -88.7, 10.96); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - - VectorSet(offset, -12.67, -4.39, 15.68); - G_ProjectSource2(self->s.origin, offset, f, r, u, point); - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(point); - gi.multicast(point, MULTICAST_ALL); - - self->nextthink = level.time + FRAMETIME; - return; - } - - self->nextthink = level.time + FRAMETIME; -} - -void -Widowlegs_Spawn(vec3_t startpos, vec3_t angles) -{ - edict_t *ent; - - ent = G_Spawn(); - VectorCopy(startpos, ent->s.origin); - VectorCopy(angles, ent->s.angles); - ent->solid = SOLID_NOT; - ent->s.renderfx = RF_IR_VISIBLE; - ent->movetype = MOVETYPE_NONE; - ent->classname = "widowlegs"; - - ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2"); - ent->think = widowlegs_think; - - ent->nextthink = level.time + FRAMETIME; - gi.linkentity(ent); -} diff --git a/src/rogue/g_svcmds.c b/src/rogue/g_svcmds.c deleted file mode 100644 index e00f9ef9..00000000 --- a/src/rogue/g_svcmds.c +++ /dev/null @@ -1,354 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Game side of server CMDs. At this time only the ipfilter. - * - * ======================================================================= - */ - -#include "header/local.h" - -#define MAX_IPFILTERS 1024 - -/* - * ============================================================================== - * - * PACKET FILTERING - * - * - * You can add or remove addresses from the filter list with: - * - * addip - * removeip - * - * The ip address is specified in dot format, and any unspecified digits will match - * any value, so you can specify an entire class C network with "addip 192.246.40". - * - * Removeip will only remove an address specified exactly the same way. You cannot - * addip a subnet, then removeip a single host. - * - * listip - * Prints the current list of filters. - * - * writeip - * Dumps "addip " commands to listip.cfg so it can be execed at a later date. - * The filter lists are not saved and restored by default, because I beleive it - * would cause too much confusion. - * - * filterban <0 or 1> - * - * If 1 (the default), then ip addresses matching the current list will be prohibited - * from entering the game. This is the default setting. - * - * If 0, then only addresses matching the list will be allowed. This lets you easily - * set up a private game, or a game that only allows players from your local network. - * - * ============================================================================== - */ - -typedef struct -{ - unsigned mask; - unsigned compare; -} ipfilter_t; - -ipfilter_t ipfilters[MAX_IPFILTERS]; -int numipfilters; - -void -Svcmd_Test_f(void) -{ - gi.cprintf(NULL, PRINT_HIGH, "Svcmd_Test_f()\n"); -} - -qboolean -StringToFilter(char *s, ipfilter_t *f) -{ - char num[128]; - int i, j; - byte b[4]; - byte m[4]; - - if (!s || !f) - { - return false; - } - - for (i = 0; i < 4; i++) - { - b[i] = 0; - m[i] = 0; - } - - for (i = 0; i < 4; i++) - { - if ((*s < '0') || (*s > '9')) - { - gi.cprintf(NULL, PRINT_HIGH, "Bad filter address: %s\n", s); - return false; - } - - j = 0; - - while (*s >= '0' && *s <= '9') - { - num[j++] = *s++; - } - - num[j] = 0; - b[i] = atoi(num); - - if (b[i] != 0) - { - m[i] = 255; - } - - if (!*s) - { - break; - } - - s++; - } - - /* PVS NOTE: maybe use memcpy here? */ - f->mask = *(unsigned *)m; - f->compare = *(unsigned *)b; - - return true; -} - -qboolean -SV_FilterPacket(char *from) -{ - int i; - unsigned in; - byte m[4]; - char *p; - - if (!from) - { - return false; - } - - i = 0; - p = from; - - while (*p && i < 4) - { - m[i] = 0; - - while (*p >= '0' && *p <= '9') - { - m[i] = m[i] * 10 + (*p - '0'); - p++; - } - - if (!*p || (*p == ':')) - { - break; - } - - i++, p++; - } - - /* PVS NOTE: maybe use memcpy here? */ - in = *(unsigned *)m; - - for (i = 0; i < numipfilters; i++) - { - if ((in & ipfilters[i].mask) == ipfilters[i].compare) - { - return (int)filterban->value; - } - } - - return (int)!filterban->value; -} - -void -SVCmd_AddIP_f(void) -{ - int i; - - if (gi.argc() < 3) - { - gi.cprintf(NULL, PRINT_HIGH, "Usage: addip \n"); - return; - } - - for (i = 0; i < numipfilters; i++) - { - if (ipfilters[i].compare == 0xffffffff) - { - break; /* free spot */ - } - } - - if (i == numipfilters) - { - if (numipfilters == MAX_IPFILTERS) - { - gi.cprintf(NULL, PRINT_HIGH, "IP filter list is full\n"); - return; - } - - numipfilters++; - } - - if (!StringToFilter(gi.argv(2), &ipfilters[i])) - { - ipfilters[i].compare = 0xffffffff; - } -} - -void -SVCmd_RemoveIP_f(void) -{ - ipfilter_t f; - int i, j; - - if (gi.argc() < 3) - { - gi.cprintf(NULL, PRINT_HIGH, "Usage: sv removeip \n"); - return; - } - - if (!StringToFilter(gi.argv(2), &f)) - { - return; - } - - for (i = 0; i < numipfilters; i++) - { - if ((ipfilters[i].mask == f.mask) && - (ipfilters[i].compare == f.compare)) - { - for (j = i + 1; j < numipfilters; j++) - { - ipfilters[j - 1] = ipfilters[j]; - } - - numipfilters--; - gi.cprintf(NULL, PRINT_HIGH, "Removed.\n"); - return; - } - } - - gi.cprintf(NULL, PRINT_HIGH, "Didn't find %s.\n", gi.argv(2)); -} - -void -SVCmd_ListIP_f(void) -{ - int i; - byte b[4]; - - gi.cprintf(NULL, PRINT_HIGH, "Filter list:\n"); - - for (i = 0; i < numipfilters; i++) - { - /* PVS NOTE: maybe use memcpy here? */ - *(unsigned *)b = ipfilters[i].compare; - gi.cprintf(NULL, PRINT_HIGH, "%3i.%3i.%3i.%3i\n", b[0], b[1], b[2], b[3]); - } -} - -void -SVCmd_WriteIP_f(void) -{ - FILE *f; - char name[MAX_OSPATH]; - byte b[4]; - int i; - cvar_t *game; - - game = gi.cvar("game", "", 0); - - if (!*game->string) - { - sprintf(name, "%s/listip.cfg", GAMEVERSION); - } - else - { - sprintf(name, "%s/listip.cfg", game->string); - } - - gi.cprintf(NULL, PRINT_HIGH, "Writing %s.\n", name); - - f = fopen(name, "wb"); - - if (!f) - { - gi.cprintf(NULL, PRINT_HIGH, "Couldn't open %s\n", name); - return; - } - - fprintf(f, "set filterban %d\n", (int)filterban->value); - - for (i = 0; i < numipfilters; i++) - { - /* PVS NOTE: maybe use memcpy here? */ - *(unsigned *)b = ipfilters[i].compare; - fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]); - } - - fclose(f); -} - -/* - * ServerCommand will be called when an "sv" command is issued. - * The game can issue gi.argc() / gi.argv() commands to get the - * rest of the parameters - */ -void -ServerCommand(void) -{ - char *cmd; - - cmd = gi.argv(1); - - if (Q_stricmp(cmd, "test") == 0) - { - Svcmd_Test_f(); - } - else if (Q_stricmp(cmd, "addip") == 0) - { - SVCmd_AddIP_f(); - } - else if (Q_stricmp(cmd, "removeip") == 0) - { - SVCmd_RemoveIP_f(); - } - else if (Q_stricmp(cmd, "listip") == 0) - { - SVCmd_ListIP_f(); - } - else if (Q_stricmp(cmd, "writeip") == 0) - { - SVCmd_WriteIP_f(); - } - else - { - gi.cprintf(NULL, PRINT_HIGH, "Unknown server command \"%s\"\n", cmd); - } -} diff --git a/src/rogue/g_target.c b/src/rogue/g_target.c deleted file mode 100644 index d16d6d44..00000000 --- a/src/rogue/g_target.c +++ /dev/null @@ -1,1220 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Targets. - * - * ======================================================================= - */ - -#include "header/local.h" - -#define LASER_ON 0x0001 -#define LASER_RED 0x0002 -#define LASER_GREEN 0x0004 -#define LASER_BLUE 0x0008 -#define LASER_YELLOW 0x0010 -#define LASER_ORANGE 0x0020 -#define LASER_FAT 0x0040 -#define LASER_STOPWINDOW 0x0080 - -void ED_CallSpawn(edict_t *ent); - -/* - * QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8) - * - * Fire an origin based temp entity event to the clients. - * "style" type byte - */ -void -Use_Target_Tent(edict_t *ent, edict_t *other, edict_t *activator) -{ - gi.WriteByte(svc_temp_entity); - gi.WriteByte(ent->style); - gi.WritePosition(ent->s.origin); - gi.multicast(ent->s.origin, MULTICAST_PVS); -} - -void -SP_target_temp_entity(edict_t *ent) -{ - ent->use = Use_Target_Tent; -} - -/* - * QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable - * - * "noise" wav file to play - * "attenuation" - * -1 = none, send to whole level - * 1 = normal fighting sounds - * 2 = idle sound level - * 3 = ambient sound level - * "volume" 0.0 to 1.0 - * - * Normal sounds play each time the target is used. The reliable flag can be set for - * crucial voiceovers. - * - * Looped sounds are always atten 3 / vol 1, and the use function toggles it on/off. - * Multiple identical looping sounds will just increase volume without any speed cost. - */ -void -Use_Target_Speaker(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - int chan; - - if (!ent) - { - return; - } - - if (ent->spawnflags & 3) - { - /* looping sound toggles */ - if (ent->s.sound) - { - ent->s.sound = 0; /* turn it off */ - } - else - { - ent->s.sound = ent->noise_index; /* start it */ - } - } - else - { - /* normal sound */ - if (ent->spawnflags & 4) - { - chan = CHAN_VOICE | CHAN_RELIABLE; - } - else - { - chan = CHAN_VOICE; - } - - /* use a positioned_sound, because this entity won't - normally be sent to any clients because it is invisible */ - gi.positioned_sound(ent->s.origin, ent, chan, ent->noise_index, - ent->volume, ent->attenuation, 0); - } -} - -void -SP_target_speaker(edict_t *ent) -{ - char buffer[MAX_QPATH]; - - if (!ent) - { - return; - } - - if (!st.noise) - { - gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin)); - return; - } - - if (!strstr(st.noise, ".wav")) - { - Com_sprintf(buffer, sizeof(buffer), "%s.wav", st.noise); - } - else - { - Q_strlcpy(buffer, st.noise, sizeof(buffer)); - } - - ent->noise_index = gi.soundindex(buffer); - - if (!ent->volume) - { - ent->volume = 1.0; - } - - if (!ent->attenuation) - { - ent->attenuation = 1.0; - } - else if (ent->attenuation == -1) /* use -1 so 0 defaults to 1 */ - { - ent->attenuation = 0; - } - - /* check for prestarted looping sound */ - if (ent->spawnflags & 1) - { - ent->s.sound = ent->noise_index; - } - - ent->use = Use_Target_Speaker; - - /* must link the entity so we get areas and clusters so - the server can determine who to send updates to */ - gi.linkentity(ent); -} - -/* ========================================================== */ - -void -Use_Target_Help(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!ent) - { - return; - } - - if (ent->spawnflags & 1) - { - strncpy(game.helpmessage1, ent->message, sizeof(game.helpmessage2) - 1); - } - else - { - strncpy(game.helpmessage2, ent->message, sizeof(game.helpmessage1) - 1); - } - - game.helpchanged++; -} - -/* - * QUAKED target_help (1 0 1) (-16 -16 -24) (16 16 24) help1 - * - * When fired, the "message" key becomes the current personal computer string, - * and the message light will be set on all clients status bars. - */ -void -SP_target_help(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (deathmatch->value) - { - /* auto-remove for deathmatch */ - G_FreeEdict(ent); - return; - } - - if (!ent->message) - { - gi.dprintf("%s with no message at %s\n", ent->classname, vtos(ent->s.origin)); - G_FreeEdict(ent); - return; - } - - ent->use = Use_Target_Help; -} - -/* ========================================================== */ - -/* - * QUAKED target_secret (1 0 1) (-8 -8 -8) (8 8 8) - * - * Counts a secret found. - * These are single use targets. - */ -void -use_target_secret(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!ent) - { - return; - } - - gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); - - level.found_secrets++; - - G_UseTargets(ent, activator); - G_FreeEdict(ent); -} - -void -SP_target_secret(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (deathmatch->value) - { - /* auto-remove for deathmatch */ - G_FreeEdict(ent); - return; - } - - ent->use = use_target_secret; - - if (!st.noise) - { - st.noise = "misc/secret.wav"; - } - - ent->noise_index = gi.soundindex(st.noise); - ent->svflags = SVF_NOCLIENT; - level.total_secrets++; - - /* map bug hack */ - if (!Q_stricmp(level.mapname, "mine3") && (ent->s.origin[0] == 280) && - (ent->s.origin[1] == -2048) && (ent->s.origin[2] == -624)) - { - ent->message = "You have found a secret area."; - } -} - -/* ========================================================== */ - -/* - * QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8) - * Counts a goal completed. - * These are single use targets. - */ -void -use_target_goal(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!ent) - { - return; - } - - gi.sound(ent, CHAN_VOICE, ent->noise_index, 1, ATTN_NORM, 0); - - level.found_goals++; - - if (level.found_goals == level.total_goals) - { - gi.configstring(CS_CDTRACK, "0"); - } - - G_UseTargets(ent, activator); - G_FreeEdict(ent); -} - -void -SP_target_goal(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (deathmatch->value) - { - /* auto-remove for deathmatch */ - G_FreeEdict(ent); - return; - } - - ent->use = use_target_goal; - - if (!st.noise) - { - st.noise = "misc/secret.wav"; - } - - ent->noise_index = gi.soundindex(st.noise); - ent->svflags = SVF_NOCLIENT; - level.total_goals++; -} - -/* ========================================================== */ - -/* - * QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) - * Spawns an explosion temporary entity when used. - * - * "delay" wait this long before going off - * "dmg" how much radius damage should be done, defaults to 0 - */ -void -target_explosion_explode(edict_t *self) -{ - float save; - - if (!self) - { - return; - } - - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_EXPLOSION1); - gi.WritePosition(self->s.origin); - gi.multicast(self->s.origin, MULTICAST_PHS); - - T_RadiusDamage(self, self->activator, self->dmg, - NULL, self->dmg + 40, MOD_EXPLOSIVE); - - save = self->delay; - self->delay = 0; - G_UseTargets(self, self->activator); - self->delay = save; -} - -void -use_target_explosion(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - self->activator = activator; - - if (!self->delay) - { - target_explosion_explode(self); - return; - } - - self->think = target_explosion_explode; - self->nextthink = level.time + self->delay; -} - -void -SP_target_explosion(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->use = use_target_explosion; - ent->svflags = SVF_NOCLIENT; -} - -/* ========================================================== */ - -/* - * QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) - * - * Changes level to "map" when fired - */ -void -use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator) -{ - if (!self || !other) - { - return; - } - - if (level.intermissiontime) - { - return; /* already activated */ - } - - if (!deathmatch->value && !coop->value) - { - if (g_edicts[1].health <= 0) - { - return; - } - } - - /* if noexit, do a ton of damage to other */ - if (deathmatch->value && !((int)dmflags->value & DF_ALLOW_EXIT) && - (other != world)) - { - T_Damage(other, self, self, vec3_origin, other->s.origin, - vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT); - return; - } - - /* if multiplayer, let everyone know who hit the exit */ - if (deathmatch->value) - { - if (activator && activator->client) - { - gi.bprintf(PRINT_HIGH, "%s exited the level.\n", - activator->client->pers.netname); - } - } - - /* if going to a new unit, clear cross triggers */ - if (strstr(self->map, "*")) - { - game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK); - } - - BeginIntermission(self); -} - -void -SP_target_changelevel(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (!ent->map) - { - gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin)); - G_FreeEdict(ent); - return; - } - - /* ugly hack because *SOMEBODY* screwed up their map */ - if ((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0)) - { - ent->map = "fact3$secret1"; - } - - ent->use = use_target_changelevel; - ent->svflags = SVF_NOCLIENT; -} - -/* ========================================================== */ - -/* - * QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8) - * Creates a particle splash effect when used. - * - * Set "sounds" to one of the following: - * 1) sparks - * 2) blue water - * 3) brown water - * 4) slime - * 5) lava - * 6) blood - * - * "count" how many pixels in the splash - * "dmg" if set, does a radius damage at this location when it splashes - * useful for lava/sparks - */ - -void -use_target_splash(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_SPLASH); - gi.WriteByte(self->count); - gi.WritePosition(self->s.origin); - gi.WriteDir(self->movedir); - gi.WriteByte(self->sounds); - gi.multicast(self->s.origin, MULTICAST_PVS); - - if (self->dmg) - { - T_RadiusDamage(self, activator, self->dmg, NULL, - self->dmg + 40, MOD_SPLASH); - } -} - -void -SP_target_splash(edict_t *self) -{ - if (!self) - { - return; - } - - self->use = use_target_splash; - G_SetMovedir(self->s.angles, self->movedir); - - if (!self->count) - { - self->count = 32; - } - - self->svflags = SVF_NOCLIENT; -} - -/* ========================================================== */ - -/* - * QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8) - * Set target to the type of entity you want spawned. - * Useful for spawning monsters and gibs in the factory levels. - * - * For monsters: - * Set direction to the facing you want it to have. - * - * For gibs: - * Set direction if you want it moving and - * speed how fast it should be moving otherwise it - * will just be dropped - */ - -void -use_target_spawner(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - edict_t *ent; - - if (!self) - { - return; - } - - ent = G_Spawn(); - ent->classname = self->target; - VectorCopy(self->s.origin, ent->s.origin); - VectorCopy(self->s.angles, ent->s.angles); - ED_CallSpawn(ent); - gi.unlinkentity(ent); - KillBox(ent); - gi.linkentity(ent); - - if (self->speed) - { - VectorCopy(self->movedir, ent->velocity); - } - - ent->s.renderfx |= RF_IR_VISIBLE; /* PGM */ -} - -void -SP_target_spawner(edict_t *self) -{ - if (!self) - { - return; - } - - self->use = use_target_spawner; - self->svflags = SVF_NOCLIENT; - - if (self->speed) - { - G_SetMovedir(self->s.angles, self->movedir); - VectorScale(self->movedir, self->speed, self->movedir); - } -} - -/* ========================================================== */ - -/* - * QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS - * Fires a blaster bolt in the set direction when triggered. - * - * dmg default is 15 - * speed default is 1000 - */ - -void -use_target_blaster(edict_t *self, edict_t *other, edict_t *activator) -{ - if (!self) - { - return; - } - - fire_blaster(self, self->s.origin, self->movedir, - self->dmg, self->speed, EF_BLASTER, MOD_TARGET_BLASTER); - gi.sound(self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); -} - -void -SP_target_blaster(edict_t *self) -{ - if (!self) - { - return; - } - - self->use = use_target_blaster; - G_SetMovedir(self->s.angles, self->movedir); - self->noise_index = gi.soundindex("weapons/laser2.wav"); - - if (!self->dmg) - { - self->dmg = 15; - } - - if (!self->speed) - { - self->speed = 1000; - } - - self->svflags = SVF_NOCLIENT; -} - -/* ========================================================== */ - -/* - * QUAKED target_crosslevel_trigger (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 - * - * Once this trigger is touched/used, any trigger_crosslevel_target - * with the same trigger number is automatically used when a level - * is started within the same unit. It is OK to check multiple triggers. - * Message, delay, target, and killtarget also work. - */ -void -trigger_crosslevel_trigger_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - game.serverflags |= self->spawnflags; - G_FreeEdict(self); -} - -void -SP_target_crosslevel_trigger(edict_t *self) -{ - if (!self) - { - return; - } - - self->svflags = SVF_NOCLIENT; - self->use = trigger_crosslevel_trigger_use; -} - -/* - * QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8 - * - * Triggered by a trigger_crosslevel elsewhere within a unit. - * If multiple triggers are checked, all must be true. - * Delay, target and killtarget also work. - * - * "delay" delay before using targets if the trigger has been activated (default 1) - */ -void -target_crosslevel_target_think(edict_t *self) -{ - if (!self) - { - return; - } - - if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) - { - G_UseTargets(self, self); - G_FreeEdict(self); - } -} - -void -SP_target_crosslevel_target(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->delay) - { - self->delay = 1; - } - - self->svflags = SVF_NOCLIENT; - - self->think = target_crosslevel_target_think; - self->nextthink = level.time + self->delay; -} - -/* ========================================================== */ - -/* - * QUAKED target_laser (0 .5 .8) (-8 -8 -8) (8 8 8) START_ON RED GREEN BLUE YELLOW ORANGE FAT WINDOWSTOP - * When triggered, fires a laser. You can either set a target - * or a direction. - * - * WINDOWSTOP - stops at CONTENTS_WINDOW - */ -void -target_laser_think(edict_t *self) -{ - edict_t *ignore; - vec3_t start; - vec3_t end; - trace_t tr; - vec3_t point; - vec3_t last_movedir; - int count; - - if (!self) - { - return; - } - - if (self->spawnflags & 0x80000000) - { - count = 8; - } - else - { - count = 4; - } - - if (self->enemy) - { - VectorCopy(self->movedir, last_movedir); - VectorMA(self->enemy->absmin, 0.5, self->enemy->size, point); - VectorSubtract(point, self->s.origin, self->movedir); - VectorNormalize(self->movedir); - - if (!VectorCompare(self->movedir, last_movedir)) - { - self->spawnflags |= 0x80000000; - } - } - - ignore = self; - VectorCopy(self->s.origin, start); - VectorMA(start, 2048, self->movedir, end); - - while (1) - { - if (self->spawnflags & LASER_STOPWINDOW) - { - tr = gi.trace(start, NULL, NULL, end, ignore, MASK_SHOT); - } - else - { - tr = gi.trace(start, NULL, NULL, end, ignore, - CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_DEADMONSTER); - } - - if (!tr.ent) - { - break; - } - - /* hurt it if we can */ - if ((tr.ent->takedamage) && !(tr.ent->flags & FL_IMMUNE_LASER)) - { - T_Damage(tr.ent, self, self->activator, self->movedir, tr.endpos, - vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER); - } - - /* if we hit something that's not a monster or player or is immune to lasers, we're done */ - if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) && - !(tr.ent->svflags & SVF_DAMAGEABLE)) - { - if (self->spawnflags & 0x80000000) - { - self->spawnflags &= ~0x80000000; - gi.WriteByte(svc_temp_entity); - gi.WriteByte(TE_LASER_SPARKS); - gi.WriteByte(count); - gi.WritePosition(tr.endpos); - gi.WriteDir(tr.plane.normal); - gi.WriteByte(self->s.skinnum); - gi.multicast(tr.endpos, MULTICAST_PVS); - } - - break; - } - - ignore = tr.ent; - VectorCopy(tr.endpos, start); - } - - VectorCopy(tr.endpos, self->s.old_origin); - - self->nextthink = level.time + FRAMETIME; -} - -void -target_laser_on(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->activator) - { - self->activator = self; - } - - self->spawnflags |= 0x80000001; - self->svflags &= ~SVF_NOCLIENT; - target_laser_think(self); -} - -void -target_laser_off(edict_t *self) -{ - if (!self) - { - return; - } - - self->spawnflags &= ~1; - self->svflags |= SVF_NOCLIENT; - self->nextthink = 0; -} - -void -target_laser_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - self->activator = activator; - - if (self->spawnflags & 1) - { - target_laser_off(self); - } - else - { - target_laser_on(self); - } -} - -void -target_laser_start(edict_t *self) -{ - edict_t *ent; - - if (!self) - { - return; - } - - self->movetype = MOVETYPE_NONE; - self->solid = SOLID_NOT; - self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT; - self->s.modelindex = 1; /* must be non-zero */ - - /* set the beam diameter */ - if (self->spawnflags & 64) - { - self->s.frame = 16; - } - else - { - self->s.frame = 4; - } - - /* set the color */ - if (self->spawnflags & 2) - { - self->s.skinnum = 0xf2f2f0f0; - } - else if (self->spawnflags & 4) - { - self->s.skinnum = 0xd0d1d2d3; - } - else if (self->spawnflags & 8) - { - self->s.skinnum = 0xf3f3f1f1; - } - else if (self->spawnflags & 16) - { - self->s.skinnum = 0xdcdddedf; - } - else if (self->spawnflags & 32) - { - self->s.skinnum = 0xe0e1e2e3; - } - - if (!self->enemy) - { - if (self->target) - { - ent = G_Find(NULL, FOFS(targetname), self->target); - - if (!ent) - { - gi.dprintf("%s at %s: %s is a bad target\n", self->classname, - vtos(self->s.origin), self->target); - } - - self->enemy = ent; - } - else - { - G_SetMovedir(self->s.angles, self->movedir); - } - } - - self->use = target_laser_use; - self->think = target_laser_think; - - if (!self->dmg) - { - self->dmg = 1; - } - - VectorSet(self->mins, -8, -8, -8); - VectorSet(self->maxs, 8, 8, 8); - gi.linkentity(self); - - if (self->spawnflags & 1) - { - target_laser_on(self); - } - else - { - target_laser_off(self); - } -} - -void -SP_target_laser(edict_t *self) -{ - if (!self) - { - return; - } - - self->think = target_laser_start; - self->nextthink = level.time + 1; -} - -/* ========================================================== */ - -/* - * QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE - * - * speed How many seconds the ramping will take - * message two letters; starting lightlevel and ending lightlevel - */ - -void -target_lightramp_think(edict_t *self) -{ - char style[2]; - - if (!self) - { - return; - } - - style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; - style[1] = 0; - gi.configstring(CS_LIGHTS + self->enemy->style, style); - - if ((level.time - self->timestamp) < self->speed) - { - self->nextthink = level.time + FRAMETIME; - } - else if (self->spawnflags & 1) - { - char temp; - - temp = self->movedir[0]; - self->movedir[0] = self->movedir[1]; - self->movedir[1] = temp; - self->movedir[2] *= -1; - } -} - -void -target_lightramp_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (!self->enemy) - { - edict_t *e; - - /* check all the targets */ - e = NULL; - - while (1) - { - e = G_Find(e, FOFS(targetname), self->target); - - if (!e) - { - break; - } - - if (strcmp(e->classname, "light") != 0) - { - gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin)); - gi.dprintf("target %s (%s at %s) is not a light\n", self->target, - e->classname, vtos(e->s.origin)); - } - else - { - self->enemy = e; - } - } - - if (!self->enemy) - { - gi.dprintf("%s target %s not found at %s\n", self->classname, - self->target, vtos(self->s.origin)); - G_FreeEdict(self); - return; - } - } - - self->timestamp = level.time; - target_lightramp_think(self); -} - -void -SP_target_lightramp(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->message || (strlen(self->message) != 2) || - (self->message[0] < 'a') || (self->message[0] > 'z') || - (self->message[1] < 'a') || (self->message[1] > 'z') || - (self->message[0] == self->message[1])) - { - gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); - G_FreeEdict(self); - return; - } - - if (deathmatch->value) - { - G_FreeEdict(self); - return; - } - - if (!self->target) - { - gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin)); - G_FreeEdict(self); - return; - } - - self->svflags |= SVF_NOCLIENT; - self->use = target_lightramp_use; - self->think = target_lightramp_think; - - self->movedir[0] = self->message[0] - 'a'; - self->movedir[1] = self->message[1] - 'a'; - self->movedir[2] = (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); -} - -/* ========================================================== */ - -/* - * QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) SILENT - * - * When triggered, this initiates a level-wide earthquake. - * All players and monsters are affected. - * "speed" severity of the quake (default:200) - * "count" duration of the quake (default:5) - */ -void -target_earthquake_think(edict_t *self) -{ - int i; - edict_t *e; - - if (!self) - { - return; - } - - if (!(self->spawnflags & 1)) - { - if (self->last_move_time < level.time) - { - gi.positioned_sound(self->s.origin, self, CHAN_AUTO, self->noise_index, - 1.0, ATTN_NONE, 0); - self->last_move_time = level.time + 0.5; - } - } - - for (i = 1, e = g_edicts + i; i < globals.num_edicts; i++, e++) - { - if (!e->inuse) - { - continue; - } - - if (!e->client) - { - continue; - } - - if (!e->groundentity) - { - continue; - } - - e->groundentity = NULL; - e->velocity[0] += crandom() * 150; - e->velocity[1] += crandom() * 150; - e->velocity[2] = self->speed * (100.0 / e->mass); - } - - if (level.time < self->timestamp) - { - self->nextthink = level.time + FRAMETIME; - } -} - -void -target_earthquake_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - self->timestamp = level.time + self->count; - self->nextthink = level.time + FRAMETIME; - self->activator = activator; - self->last_move_time = 0; -} - -void -SP_target_earthquake(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->targetname) - { - gi.dprintf("untargeted %s at %s\n", self->classname, vtos( self->s.origin)); - } - - if (!self->count) - { - self->count = 5; - } - - if (!self->speed) - { - self->speed = 200; - } - - self->svflags |= SVF_NOCLIENT; - self->think = target_earthquake_think; - self->use = target_earthquake_use; - - if (!(self->spawnflags & 1)) - { - self->noise_index = gi.soundindex("world/quake.wav"); - } -} diff --git a/src/rogue/g_trigger.c b/src/rogue/g_trigger.c deleted file mode 100644 index 44e7e578..00000000 --- a/src/rogue/g_trigger.c +++ /dev/null @@ -1,931 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Trigger. - * - * ======================================================================= - */ - -#include "header/local.h" - -#define TRIGGER_MONSTER 0x01 -#define TRIGGER_NOT_PLAYER 0x02 -#define TRIGGER_TRIGGERED 0x04 -#define TRIGGER_TOGGLE 0x08 - -#define PUSH_ONCE 0x01 -#define PUSH_START_OFF 0x02 -#define PUSH_SILENT 0x04 - -static int windsound; - -void -InitTrigger(edict_t *self) -{ - if (!self) - { - return; - } - - if (!VectorCompare(self->s.angles, vec3_origin)) - { - G_SetMovedir(self->s.angles, self->movedir); - } - - self->solid = SOLID_TRIGGER; - self->movetype = MOVETYPE_NONE; - gi.setmodel(self, self->model); - self->svflags = SVF_NOCLIENT; -} - -/* - * the wait time has passed, so set - * back up for another activation - */ -void -multi_wait(edict_t *ent) -{ - if (!ent) - { - return; - } - - ent->nextthink = 0; -} - -void -multi_trigger(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (ent->nextthink) - { - return; /* already been triggered */ - } - - G_UseTargets(ent, ent->activator); - - if (ent->wait > 0) - { - ent->think = multi_wait; - ent->nextthink = level.time + ent->wait; - } - else - { - /* we can't just remove (self) here, because - this is a touch function called while looping - through area links... */ - ent->touch = NULL; - ent->nextthink = level.time + FRAMETIME; - ent->think = G_FreeEdict; - } -} - -void -Use_Multi(edict_t *ent, edict_t *other /* unused */, edict_t *activator) -{ - if (!ent || !activator) - { - return; - } - - if (ent->spawnflags & TRIGGER_TOGGLE) - { - if (ent->solid == SOLID_TRIGGER) - { - ent->solid = SOLID_NOT; - } - else - { - ent->solid = SOLID_TRIGGER; - } - - gi.linkentity(ent); - } - else - { - ent->activator = activator; - multi_trigger(ent); - } -} - -void -Touch_Multi(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - if (!self || !other) - { - return; - } - - if (other->client) - { - if (self->spawnflags & 2) - { - return; - } - } - else if (other->svflags & SVF_MONSTER) - { - if (!(self->spawnflags & 1)) - { - return; - } - } - else - { - return; - } - - if (!VectorCompare(self->movedir, vec3_origin)) - { - vec3_t forward; - - AngleVectors(other->s.angles, forward, NULL, NULL); - - if (_DotProduct(forward, self->movedir) < 0) - { - return; - } - } - - self->activator = other; - multi_trigger(self); -} - -/* - * QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE - * Variable sized repeatable trigger. Must be targeted at one or more entities. - * If "delay" is set, the trigger waits some time after activating before firing. - * "wait" : Seconds between triggerings. (.2 default) - * - * TOGGLE - using this trigger will activate/deactivate it. trigger will begin inactive. - * - * sounds - * 1) secret - * 2) beep beep - * 3) large switch - * 4) - * set "message" to text string - */ -void -trigger_enable(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - self->solid = SOLID_TRIGGER; - self->use = Use_Multi; - gi.linkentity(self); -} - -void -SP_trigger_multiple(edict_t *ent) -{ - if (!ent) - { - return; - } - - if (ent->sounds == 1) - { - ent->noise_index = gi.soundindex("misc/secret.wav"); - } - else if (ent->sounds == 2) - { - ent->noise_index = gi.soundindex("misc/talk.wav"); - } - else if (ent->sounds == 3) - { - ent->noise_index = gi.soundindex("misc/trigger1.wav"); - } - - if (!ent->wait) - { - ent->wait = 0.2; - } - - ent->touch = Touch_Multi; - ent->movetype = MOVETYPE_NONE; - ent->svflags |= SVF_NOCLIENT; - - if (ent->spawnflags & (TRIGGER_TRIGGERED | TRIGGER_TOGGLE)) - { - ent->solid = SOLID_NOT; - ent->use = trigger_enable; - } - else - { - ent->solid = SOLID_TRIGGER; - ent->use = Use_Multi; - } - - if (!VectorCompare(ent->s.angles, vec3_origin)) - { - G_SetMovedir(ent->s.angles, ent->movedir); - } - - gi.setmodel(ent, ent->model); - gi.linkentity(ent); -} - -/* - * QUAKED trigger_once (.5 .5 .5) ? x x TRIGGERED - * - * Triggers once, then removes itself. - * You must set the key "target" to the name of another object in the level that has a matching "targetname". - * - * If TRIGGERED, this trigger must be triggered before it is live. - * - * sounds - * 1) secret - * 2) beep beep - * 3) large switch - * 4) - * - * "message" string to be displayed when triggered - */ - -void -SP_trigger_once(edict_t *ent) -{ - if (!ent) - { - return; - } - - /* make old maps work because I messed up on flag assignments here */ - if (ent->spawnflags & 1) - { - vec3_t v; - - VectorMA(ent->mins, 0.5, ent->size, v); - ent->spawnflags &= ~1; - ent->spawnflags |= 4; - gi.dprintf("fixed TRIGGERED flag on %s at %s\n", ent->classname, vtos(v)); - } - - ent->wait = -1; - SP_trigger_multiple(ent); -} - -/* - * QUAKED trigger_relay (.5 .5 .5) (-8 -8 -8) (8 8 8) - * - * This fixed size trigger cannot be touched, it can only be fired by other events. - */ -void -trigger_relay_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - G_UseTargets(self, activator); -} - -void -SP_trigger_relay(edict_t *self) -{ - if (!self) - { - return; - } - - self->use = trigger_relay_use; -} - -/* - * QUAKED trigger_key (.5 .5 .5) (-8 -8 -8) (8 8 8) - * - * A relay trigger that only fires it's targets if player has the proper key. - * Use "item" to specify the required key, for example "key_data_cd" - */ -void -trigger_key_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - int index; - - if (!self|| !activator) - { - return; - } - - if (!self->item) - { - return; - } - - if (!activator->client) - { - return; - } - - index = ITEM_INDEX(self->item); - - if (!activator->client->pers.inventory[index]) - { - if (level.time < self->touch_debounce_time) - { - return; - } - - self->touch_debounce_time = level.time + 5.0; - gi.centerprintf(activator, "You need the %s", self->item->pickup_name); - gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keytry.wav"), 1, ATTN_NORM, 0); - return; - } - - gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/keyuse.wav"), 1, ATTN_NORM, 0); - - if (coop->value) - { - int player; - edict_t *ent; - - if (strcmp(self->item->classname, "key_power_cube") == 0) - { - int cube; - - for (cube = 0; cube < 8; cube++) - { - if (activator->client->pers.power_cubes & (1 << cube)) - { - break; - } - } - - for (player = 1; player <= game.maxclients; player++) - { - ent = &g_edicts[player]; - - if (!ent->inuse) - { - continue; - } - - if (!ent->client) - { - continue; - } - - if (ent->client->pers.power_cubes & (1 << cube)) - { - ent->client->pers.inventory[index]--; - ent->client->pers.power_cubes &= ~(1 << cube); - } - } - } - else - { - for (player = 1; player <= game.maxclients; player++) - { - ent = &g_edicts[player]; - - if (!ent->inuse) - { - continue; - } - - if (!ent->client) - { - continue; - } - - ent->client->pers.inventory[index] = 0; - } - } - } - else - { - activator->client->pers.inventory[index]--; - } - - G_UseTargets(self, activator); - - self->use = NULL; -} - -void -SP_trigger_key(edict_t *self) -{ - if (!self) - { - return; - } - - if (!st.item) - { - gi.dprintf("no key item for trigger_key at %s\n", vtos(self->s.origin)); - return; - } - - self->item = FindItemByClassname(st.item); - - if (!self->item) - { - gi.dprintf("item %s not found for trigger_key at %s\n", st.item, - vtos(self->s.origin)); - return; - } - - if (!self->target) - { - gi.dprintf("%s at %s has no target\n", self->classname, - vtos(self->s.origin)); - return; - } - - gi.soundindex("misc/keytry.wav"); - gi.soundindex("misc/keyuse.wav"); - - self->use = trigger_key_use; -} - -/* - * QUAKED trigger_counter (.5 .5 .5) ? nomessage - * - * Acts as an intermediary for an action that takes multiple inputs. - * - * If nomessage is not set, t will print "1 more.. " etc when triggered - * and "sequence complete" when finished. - * - * After the counter has been triggered "count" times (default 2), - * it will fire all of it's targets and remove itself. - */ - -void -trigger_counter_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - if (self->count == 0) - { - return; - } - - self->count--; - - if (self->count) - { - if (!(self->spawnflags & 1)) - { - gi.centerprintf(activator, "%i more to go...", self->count); - gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); - } - - return; - } - - if (!(self->spawnflags & 1)) - { - gi.centerprintf(activator, "Sequence completed!"); - gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0); - } - - self->activator = activator; - multi_trigger(self); -} - -void -SP_trigger_counter(edict_t *self) -{ - if (!self) - { - return; - } - - self->wait = -1; - - if (!self->count) - { - self->count = 2; - } - - self->use = trigger_counter_use; -} - -/* - * QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) - * - * This trigger will always fire. It is activated by the world. - */ -void -SP_trigger_always(edict_t *ent) -{ - if (!ent) - { - return; - } - - /* we must have some delay to make sure our use targets are present */ - if (ent->delay < 0.2) - { - ent->delay = 0.2; - } - - G_UseTargets(ent, ent); -} - -void -trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - if (!self || !other) - { - return; - } - - if (strcmp(other->classname, "grenade") == 0) - { - VectorScale(self->movedir, self->speed * 10, other->velocity); - } - else if (other->health > 0) - { - VectorScale(self->movedir, self->speed * 10, other->velocity); - - if (other->client) - { - /* don't take falling damage immediately from this */ - VectorCopy(other->velocity, other->client->oldvelocity); - - if (!(self->spawnflags & PUSH_SILENT) && - (other->fly_sound_debounce_time < level.time)) - { - other->fly_sound_debounce_time = level.time + 1.5; - gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0); - } - } - } - - if (self->spawnflags & PUSH_ONCE) - { - G_FreeEdict(self); - } -} - -void -trigger_push_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (self->solid == SOLID_NOT) - { - self->solid = SOLID_TRIGGER; - } - else - { - self->solid = SOLID_NOT; - } - - gi.linkentity(self); -} - -/* - * QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE START_OFF SILENT - * Pushes the player - * "speed" defaults to 1000 - * - * If targeted, it will toggle on and off when used. - * - * START_OFF - toggled trigger_push begins in off setting - * SILENT - doesn't make wind noise - */ -void -SP_trigger_push(edict_t *self) -{ - if (!self) - { - return; - } - - InitTrigger(self); - windsound = gi.soundindex("misc/windfly.wav"); - self->touch = trigger_push_touch; - - if (!self->speed) - { - self->speed = 1000; - } - - if (self->targetname) /* toggleable */ - { - self->use = trigger_push_use; - - if (self->spawnflags & PUSH_START_OFF) - { - self->solid = SOLID_NOT; - } - } - else if (self->spawnflags & PUSH_START_OFF) - { - gi.dprintf("trigger_push is START_OFF but not targeted.\n"); - self->svflags = 0; - self->touch = NULL; - self->solid = SOLID_BSP; - self->movetype = MOVETYPE_PUSH; - } - - gi.linkentity(self); -} - -/* - * QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW - * - * Any entity that touches this will be hurt. - * - * It does dmg points of damage each server frame - * - * SILENT supresses playing the sound - * SLOW changes the damage rate to once per second - * NO_PROTECTION *nothing* stops the damage - * - * "dmg" default 5 (whole numbers only) - * - */ -void -hurt_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (self->solid == SOLID_NOT) - { - self->solid = SOLID_TRIGGER; - } - else - { - self->solid = SOLID_NOT; - } - - gi.linkentity(self); - - if (!(self->spawnflags & 2)) - { - self->use = NULL; - } -} - -void -hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - int dflags; - - if (!self || !other) - { - return; - } - - if (!other->takedamage) - { - return; - } - - if (self->timestamp > level.time) - { - return; - } - - if (self->spawnflags & 16) - { - self->timestamp = level.time + 1; - } - else - { - self->timestamp = level.time + FRAMETIME; - } - - if (!(self->spawnflags & 4)) - { - if ((level.framenum % 10) == 0) - { - gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0); - } - } - - if (self->spawnflags & 8) - { - dflags = DAMAGE_NO_PROTECTION; - } - else - { - dflags = 0; - } - - T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, - self->dmg, self->dmg, dflags, MOD_TRIGGER_HURT); -} - -void -SP_trigger_hurt(edict_t *self) -{ - if (!self) - { - return; - } - - InitTrigger(self); - - self->noise_index = gi.soundindex("world/electro.wav"); - self->touch = hurt_touch; - - if (!self->dmg) - { - self->dmg = 5; - } - - if (self->spawnflags & 1) - { - self->solid = SOLID_NOT; - } - else - { - self->solid = SOLID_TRIGGER; - } - - if (self->spawnflags & 2) - { - self->use = hurt_use; - } - - gi.linkentity(self); -} - -void -trigger_gravity_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - if (self->solid == SOLID_NOT) - { - self->solid = SOLID_TRIGGER; - } - else - { - self->solid = SOLID_NOT; - } - - gi.linkentity(self); -} - -/* PGM */ - -void -trigger_gravity_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - if (!self || !other) - { - return; - } - - other->gravity = self->gravity; -} - -/* - * QUAKED trigger_gravity (.5 .5 .5) ? TOGGLE START_OFF - * Changes the touching entites gravity to - * the value of "gravity". 1.0 is standard - * gravity for the level. - * - * TOGGLE - trigger_gravity can be turned on and off - * START_OFF - trigger_gravity starts turned off (implies TOGGLE) - */ -void -SP_trigger_gravity(edict_t *self) -{ - if (!self) - { - return; - } - - if (st.gravity == 0) - { - gi.dprintf("trigger_gravity without gravity set at %s\n", vtos(self->s.origin)); - G_FreeEdict(self); - return; - } - - InitTrigger(self); - - self->gravity = strtof(st.gravity, (char **)NULL); - - if (self->spawnflags & 1) /* TOGGLE */ - { - self->use = trigger_gravity_use; - } - - if (self->spawnflags & 2) /* START_OFF */ - { - self->use = trigger_gravity_use; - self->solid = SOLID_NOT; - } - - self->touch = trigger_gravity_touch; - gi.linkentity(self); -} - -/* - * QUAKED trigger_monsterjump (.5 .5 .5) ? - * - * Walking monsters that touch this will jump in the direction of the trigger's angle - * "speed" default to 200, the speed thrown forward - * "height" default to 200, the speed thrown upwards - */ - -void -trigger_monsterjump_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, csurface_t *surf /* unused */) -{ - if (!self || !other) - { - return; - } - - if (other->flags & (FL_FLY | FL_SWIM)) - { - return; - } - - if (other->svflags & SVF_DEADMONSTER) - { - return; - } - - if (!(other->svflags & SVF_MONSTER)) - { - return; - } - - /* set XY even if not on ground, so the jump will clear lips */ - other->velocity[0] = self->movedir[0] * self->speed; - other->velocity[1] = self->movedir[1] * self->speed; - - if (!other->groundentity) - { - return; - } - - other->groundentity = NULL; - other->velocity[2] = self->movedir[2]; -} - -void -SP_trigger_monsterjump(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->speed) - { - self->speed = 200; - } - - if (!st.height) - { - st.height = 200; - } - - if (self->s.angles[YAW] == 0) - { - self->s.angles[YAW] = 360; - } - - InitTrigger(self); - self->touch = trigger_monsterjump_touch; - self->movedir[2] = st.height; -} diff --git a/src/rogue/g_turret.c b/src/rogue/g_turret.c deleted file mode 100644 index 89eb7017..00000000 --- a/src/rogue/g_turret.c +++ /dev/null @@ -1,834 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Turrets aka big cannons with a driver. - * - * ======================================================================= - */ - -#include "header/local.h" - -qboolean FindTarget(edict_t *self); -void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point); -void infantry_stand(edict_t *self); -void monster_use(edict_t *self, edict_t *other, edict_t *activator); -void SpawnTargetingSystem(edict_t *turret); - -void -AnglesNormalize(vec3_t vec) -{ - while (vec[0] > 360) - { - vec[0] -= 360; - } - - while (vec[0] < 0) - { - vec[0] += 360; - } - - while (vec[1] > 360) - { - vec[1] -= 360; - } - - while (vec[1] < 0) - { - vec[1] += 360; - } -} - -float -SnapToEights(float x) -{ - x *= 8.0; - - if (x > 0.0) - { - x += 0.5; - } - else - { - x -= 0.5; - } - - return 0.125 * (int)x; -} - -void -turret_blocked(edict_t *self, edict_t *other) -{ - if (!self || !other) - { - return; - } - - edict_t *attacker; - - if (other->takedamage) - { - if (self->teammaster->owner) - { - attacker = self->teammaster->owner; - } - else - { - attacker = self->teammaster; - } - - T_Damage(other, self, attacker, vec3_origin, other->s.origin, - vec3_origin, self->teammaster->dmg, 10, 0, MOD_CRUSH); - } -} - -/* - * QUAKED turret_breach (0 0 0) ? - * This portion of the turret can change both pitch and yaw. - * The model should be made with a flat pitch. - * It (and the associated base) need to be oriented towards 0. - * Use "angle" to set the starting angle. - * - * "speed" default 50 - * "dmg" default 10 - * "angle" point this forward - * "target" point this at an info_notnull at the muzzle tip - * "minpitch" min acceptable pitch angle : default -30 - * "maxpitch" max acceptable pitch angle : default 30 - * "minyaw" min acceptable yaw angle : default 0 - * "maxyaw" max acceptable yaw angle : default 360 - */ - -void -turret_breach_fire(edict_t *self) -{ - vec3_t f, r, u; - vec3_t start; - int damage; - int speed; - - if (!self) - { - return; - } - - AngleVectors(self->s.angles, f, r, u); - VectorMA(self->s.origin, self->move_origin[0], f, start); - VectorMA(start, self->move_origin[1], r, start); - VectorMA(start, self->move_origin[2], u, start); - - damage = 100 + random() * 50; - speed = 550 + 50 * skill->value; - fire_rocket(self->teammaster->owner, start, f, damage, speed, 150, damage); - gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0); -} - -void -turret_breach_think(edict_t *self) -{ - edict_t *ent; - vec3_t current_angles; - vec3_t delta; - - if (!self) - { - return; - } - - VectorCopy(self->s.angles, current_angles); - AnglesNormalize(current_angles); - - AnglesNormalize(self->move_angles); - - if (self->move_angles[PITCH] > 180) - { - self->move_angles[PITCH] -= 360; - } - - /* clamp angles to mins & maxs */ - if (self->move_angles[PITCH] > self->pos1[PITCH]) - { - self->move_angles[PITCH] = self->pos1[PITCH]; - } - else if (self->move_angles[PITCH] < self->pos2[PITCH]) - { - self->move_angles[PITCH] = self->pos2[PITCH]; - } - - if ((self->move_angles[YAW] < self->pos1[YAW]) || - (self->move_angles[YAW] > self->pos2[YAW])) - { - float dmin, dmax; - - dmin = fabs(self->pos1[YAW] - self->move_angles[YAW]); - - if (dmin < -180) - { - dmin += 360; - } - else if (dmin > 180) - { - dmin -= 360; - } - - dmax = fabs(self->pos2[YAW] - self->move_angles[YAW]); - - if (dmax < -180) - { - dmax += 360; - } - else if (dmax > 180) - { - dmax -= 360; - } - - if (fabs(dmin) < fabs(dmax)) - { - self->move_angles[YAW] = self->pos1[YAW]; - } - else - { - self->move_angles[YAW] = self->pos2[YAW]; - } - } - - VectorSubtract(self->move_angles, current_angles, delta); - - if (delta[0] < -180) - { - delta[0] += 360; - } - else if (delta[0] > 180) - { - delta[0] -= 360; - } - - if (delta[1] < -180) - { - delta[1] += 360; - } - else if (delta[1] > 180) - { - delta[1] -= 360; - } - - delta[2] = 0; - - if (delta[0] > self->speed * FRAMETIME) - { - delta[0] = self->speed * FRAMETIME; - } - - if (delta[0] < -1 * self->speed * FRAMETIME) - { - delta[0] = -1 * self->speed * FRAMETIME; - } - - if (delta[1] > self->speed * FRAMETIME) - { - delta[1] = self->speed * FRAMETIME; - } - - if (delta[1] < -1 * self->speed * FRAMETIME) - { - delta[1] = -1 * self->speed * FRAMETIME; - } - - VectorScale(delta, 1.0 / FRAMETIME, self->avelocity); - - self->nextthink = level.time + FRAMETIME; - - for (ent = self->teammaster; ent; ent = ent->teamchain) - { - ent->avelocity[1] = self->avelocity[1]; - } - - /* if we have adriver, adjust his velocities */ - if (self->owner) - { - float angle; - float target_z; - float diff; - vec3_t target; - vec3_t dir; - - /* angular is easy, just copy ours */ - self->owner->avelocity[0] = self->avelocity[0]; - self->owner->avelocity[1] = self->avelocity[1]; - - /* x & y */ - angle = self->s.angles[1] + self->owner->move_origin[1]; - angle *= (M_PI * 2 / 360); - target[0] = SnapToEights(self->s.origin[0] + cos(angle) * self->owner->move_origin[0]); - target[1] = SnapToEights(self->s.origin[1] + sin(angle) * self->owner->move_origin[0]); - target[2] = self->owner->s.origin[2]; - - VectorSubtract(target, self->owner->s.origin, dir); - self->owner->velocity[0] = dir[0] * 1.0 / FRAMETIME; - self->owner->velocity[1] = dir[1] * 1.0 / FRAMETIME; - - /* z */ - angle = self->s.angles[PITCH] * (M_PI * 2 / 360); - target_z = SnapToEights(self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2]); - - diff = target_z - self->owner->s.origin[2]; - self->owner->velocity[2] = diff * 1.0 / FRAMETIME; - - if (self->spawnflags & 65536) - { - turret_breach_fire(self); - self->spawnflags &= ~65536; - } - } -} - -void -turret_breach_finish_init(edict_t *self) -{ - if (!self) - { - return; - } - - /* get and save info for muzzle location */ - if (!self->target) - { - gi.dprintf("%s at %s needs a target\n", self->classname, vtos(self->s.origin)); - } - else - { - self->target_ent = G_PickTarget(self->target); - - if (self->target_ent) - { - VectorSubtract(self->target_ent->s.origin, self->s.origin, self->move_origin); - G_FreeEdict(self->target_ent); - } - else - { - gi.dprintf("could not find target entity for %s at %s\n", self->classname, vtos(self->s.origin)); - } - } - - self->teammaster->dmg = self->dmg; - self->think = turret_breach_think; - self->think(self); -} - -void -SP_turret_breach(edict_t *self) -{ - if (!self) - { - return; - } - - self->solid = SOLID_BSP; - self->movetype = MOVETYPE_PUSH; - gi.setmodel(self, self->model); - - if (!self->speed) - { - self->speed = 50; - } - - if (!self->dmg) - { - self->dmg = 10; - } - - if (!st.minpitch) - { - st.minpitch = -30; - } - - if (!st.maxpitch) - { - st.maxpitch = 30; - } - - if (!st.maxyaw) - { - st.maxyaw = 360; - } - - self->pos1[PITCH] = -1 * st.minpitch; - self->pos1[YAW] = st.minyaw; - self->pos2[PITCH] = -1 * st.maxpitch; - self->pos2[YAW] = st.maxyaw; - - self->ideal_yaw = self->s.angles[YAW]; - self->move_angles[YAW] = self->ideal_yaw; - - self->blocked = turret_blocked; - - self->think = turret_breach_finish_init; - self->nextthink = level.time + FRAMETIME; - gi.linkentity(self); -} - -/* - * QUAKED turret_base (0 0 0) ? - * This portion of the turret changes yaw only. - * MUST be teamed with a turret_breach. - */ - -void -SP_turret_base(edict_t *self) -{ - if (!self) - { - return; - } - - self->solid = SOLID_BSP; - self->movetype = MOVETYPE_PUSH; - gi.setmodel(self, self->model); - self->blocked = turret_blocked; - gi.linkentity(self); -} - -/* - * QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32) - * Must NOT be on the team with the rest of the turret parts. - * Instead it must target the turret_breach. - */ -void -turret_driver_die(edict_t *self, edict_t *inflictor, edict_t *attacker, - int damage, vec3_t point) -{ - edict_t *ent; - - if (!self || !inflictor || !attacker) - { - return; - } - - /* level the gun */ - self->target_ent->move_angles[0] = 0; - - /* remove the driver from the end of them team chain */ - for (ent = self->target_ent->teammaster; - ent->teamchain != self; - ent = ent->teamchain) - { - } - - ent->teamchain = NULL; - self->teammaster = NULL; - self->flags &= ~FL_TEAMSLAVE; - - self->target_ent->owner = NULL; - self->target_ent->teammaster->owner = NULL; - - infantry_die(self, inflictor, attacker, damage, point); -} - -void -turret_driver_think(edict_t *self) -{ - vec3_t target; - vec3_t dir; - float reaction_time; - - if (!self) - { - return; - } - - self->nextthink = level.time + FRAMETIME; - - if (self->enemy && (!self->enemy->inuse || (self->enemy->health <= 0))) - { - self->enemy = NULL; - } - - if (!self->enemy) - { - if (!FindTarget(self)) - { - return; - } - - self->monsterinfo.trail_time = level.time; - self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; - } - else - { - if (visible(self, self->enemy)) - { - if (self->monsterinfo.aiflags & AI_LOST_SIGHT) - { - self->monsterinfo.trail_time = level.time; - self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; - } - } - else - { - self->monsterinfo.aiflags |= AI_LOST_SIGHT; - return; - } - } - - /* let the turret know where we want it to aim */ - VectorCopy(self->enemy->s.origin, target); - target[2] += self->enemy->viewheight; - VectorSubtract(target, self->target_ent->s.origin, dir); - vectoangles(dir, self->target_ent->move_angles); - - /* decide if we should shoot */ - if (level.time < self->monsterinfo.attack_finished) - { - return; - } - - reaction_time = (3 - skill->value) * 1.0; - - if ((level.time - self->monsterinfo.trail_time) < reaction_time) - { - return; - } - - self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; - self->target_ent->spawnflags |= 65536; -} - -void -turret_driver_link(edict_t *self) -{ - vec3_t vec; - edict_t *ent; - - if (!self) - { - return; - } - - self->think = turret_driver_think; - self->nextthink = level.time + FRAMETIME; - - self->target_ent = G_PickTarget(self->target); - self->target_ent->owner = self; - self->target_ent->teammaster->owner = self; - VectorCopy(self->target_ent->s.angles, self->s.angles); - - vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; - vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; - vec[2] = 0; - self->move_origin[0] = VectorLength(vec); - - VectorSubtract(self->s.origin, self->target_ent->s.origin, vec); - vectoangles(vec, vec); - AnglesNormalize(vec); - self->move_origin[1] = vec[1]; - - self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; - - /* add the driver to the end of them team chain */ - for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) - { - } - - ent->teamchain = self; - self->teammaster = self->target_ent->teammaster; - self->flags |= FL_TEAMSLAVE; -} - -void -SP_turret_driver(edict_t *self) -{ - if (!self) - { - return; - } - - if (deathmatch->value) - { - G_FreeEdict(self); - return; - } - - self->movetype = MOVETYPE_PUSH; - self->solid = SOLID_BBOX; - self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2"); - VectorSet(self->mins, -16, -16, -24); - VectorSet(self->maxs, 16, 16, 32); - - self->health = 100; - self->gib_health = 0; - self->mass = 200; - self->viewheight = 24; - - self->die = turret_driver_die; - self->monsterinfo.stand = infantry_stand; - - self->flags |= FL_NO_KNOCKBACK; - - level.total_monsters++; - - self->svflags |= SVF_MONSTER; - self->s.renderfx |= RF_FRAMELERP; - self->takedamage = DAMAGE_AIM; - self->use = monster_use; - self->clipmask = MASK_MONSTERSOLID; - VectorCopy(self->s.origin, self->s.old_origin); - self->monsterinfo.aiflags |= AI_STAND_GROUND | AI_DUCKED; - - if (st.item) - { - self->item = FindItemByClassname(st.item); - - if (!self->item) - { - gi.dprintf("%s at %s has bad item: %s\n", self->classname, vtos(self->s.origin), st.item); - } - } - - self->think = turret_driver_link; - self->nextthink = level.time + FRAMETIME; - - gi.linkentity(self); -} - -/* - * invisible turret drivers so we can have unmanned turrets. - * originally designed to shoot at func_trains and such, so they - * fire at the center of the bounding box, rather than the entity's - * origin. */ - -void -turret_brain_think(edict_t *self) -{ - vec3_t dir; - vec3_t endpos; - float reaction_time; - trace_t trace; - - if (!self) - { - return; - } - - self->nextthink = level.time + FRAMETIME; - - if (self->enemy) - { - if (!self->enemy->inuse) - { - self->enemy = NULL; - } - else if (self->enemy->takedamage && (self->enemy->health <= 0)) - { - self->enemy = NULL; - } - } - - if (!self->enemy) - { - if (!FindTarget(self)) - { - return; - } - - self->monsterinfo.trail_time = level.time; - self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; - - VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos); - VectorScale(endpos, 0.5, endpos); - } - else - { - VectorAdd(self->enemy->absmax, self->enemy->absmin, endpos); - VectorScale(endpos, 0.5, endpos); - - trace = gi.trace(self->target_ent->s.origin, vec3_origin, vec3_origin, - endpos, self->target_ent, MASK_SHOT); - - if ((trace.fraction == 1) || (trace.ent == self->enemy)) - { - if (self->monsterinfo.aiflags & AI_LOST_SIGHT) - { - self->monsterinfo.trail_time = level.time; - self->monsterinfo.aiflags &= ~AI_LOST_SIGHT; - } - } - else - { - self->monsterinfo.aiflags |= AI_LOST_SIGHT; - return; - } - } - - /* let the turret know where we want it to aim */ - VectorSubtract(endpos, self->target_ent->s.origin, dir); - vectoangles(dir, self->target_ent->move_angles); - - /* decide if we should shoot */ - if (level.time < self->monsterinfo.attack_finished) - { - return; - } - - if (self->delay) - { - reaction_time = self->delay; - } - else - { - reaction_time = (3 - skill->value) * 1.0; - } - - if ((level.time - self->monsterinfo.trail_time) < reaction_time) - { - return; - } - - self->monsterinfo.attack_finished = level.time + reaction_time + 1.0; - self->target_ent->spawnflags |= 65536; -} - -void -turret_brain_link(edict_t *self) -{ - vec3_t vec; - edict_t *ent; - - if (!self) - { - return; - } - - if (self->killtarget) - { - self->enemy = G_PickTarget(self->killtarget); - } - - self->think = turret_brain_think; - self->nextthink = level.time + FRAMETIME; - - self->target_ent = G_PickTarget(self->target); - self->target_ent->owner = self; - self->target_ent->teammaster->owner = self; - VectorCopy(self->target_ent->s.angles, self->s.angles); - - vec[0] = self->target_ent->s.origin[0] - self->s.origin[0]; - vec[1] = self->target_ent->s.origin[1] - self->s.origin[1]; - vec[2] = 0; - self->move_origin[0] = VectorLength(vec); - - VectorSubtract(self->s.origin, self->target_ent->s.origin, vec); - vectoangles(vec, vec); - AnglesNormalize(vec); - self->move_origin[1] = vec[1]; - - self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2]; - - /* add the driver to the end of them team chain */ - for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain) - { - } - - ent->teamchain = self; - self->teammaster = self->target_ent->teammaster; - self->flags |= FL_TEAMSLAVE; -} - -void -turret_brain_deactivate(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) -{ - if (!self) - { - return; - } - - self->think = NULL; - self->nextthink = 0; -} - -void -turret_brain_activate(edict_t *self, edict_t *other /* unused */, edict_t *activator) -{ - if (!self || !activator) - { - return; - } - - if (!self->enemy) - { - self->enemy = activator; - } - - /* wait at least 3 seconds to fire. */ - self->monsterinfo.attack_finished = level.time + 3; - self->use = turret_brain_deactivate; - - self->think = turret_brain_link; - self->nextthink = level.time + FRAMETIME; -} - -/* - * QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16) - * Invisible brain to drive the turret. - * - * Does not search for targets. If targeted, can only be turned on once - * and then off once. After that they are completely disabled. - * - * "delay" the delay between firing (default ramps for skill level) - * "Target" the turret breach - * "Killtarget" the item you want it to attack. - * Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds - * before firing to acquire the target. - */ -void -SP_turret_invisible_brain(edict_t *self) -{ - if (!self) - { - return; - } - - if (!self->killtarget) - { - gi.dprintf("turret_invisible_brain with no killtarget!\n"); - G_FreeEdict(self); - return; - } - - if (!self->target) - { - gi.dprintf("turret_invisible_brain with no target!\n"); - G_FreeEdict(self); - return; - } - - if (self->targetname) - { - self->use = turret_brain_activate; - } - else - { - self->think = turret_brain_link; - self->nextthink = level.time + FRAMETIME; - } - - self->movetype = MOVETYPE_PUSH; - gi.linkentity(self); -} diff --git a/src/rogue/g_utils.c b/src/rogue/g_utils.c deleted file mode 100644 index 1f9c41d9..00000000 --- a/src/rogue/g_utils.c +++ /dev/null @@ -1,916 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Misc. utility functions for the game logic. - * - * ======================================================================= - */ - -#include "header/local.h" - -#define MAXCHOICES 8 - -static vec3_t VEC_UP = {0, -1, 0}; -static vec3_t MOVEDIR_UP = {0, 0, 1}; -static vec3_t VEC_DOWN = {0, -2, 0}; -static vec3_t MOVEDIR_DOWN = {0, 0, -1}; - -void -G_ProjectSource(vec3_t point, vec3_t distance, vec3_t forward, - vec3_t right, vec3_t result) -{ - result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1]; - result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1]; - result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + - distance[2]; -} - -void -G_ProjectSource2(vec3_t point, vec3_t distance, vec3_t forward, - vec3_t right, vec3_t up, vec3_t result) -{ - result[0] = point[0] + forward[0] * distance[0] + right[0] * distance[1] + - up[0] * distance[2]; - result[1] = point[1] + forward[1] * distance[0] + right[1] * distance[1] + - up[1] * distance[2]; - result[2] = point[2] + forward[2] * distance[0] + right[2] * distance[1] + - up[2] * distance[2]; -} - -/* - * Searches all active entities for the next - * one that holds the matching string at fieldofs - * (use the FOFS() macro) in the structure. - * - * Searches beginning at the edict after from, or - * the beginning. If NULL, NULL will be returned - * if the end of the list is reached. - */ -edict_t * -G_Find(edict_t *from, int fieldofs, char *match) -{ - char *s; - - if (!match) - { - return NULL; - } - - if (!from) - { - from = g_edicts; - } - else - { - from++; - } - - for ( ; from < &g_edicts[globals.num_edicts]; from++) - { - if (!from->inuse) - { - continue; - } - - s = *(char **)((byte *)from + fieldofs); - - if (!s) - { - continue; - } - - if (!Q_stricmp(s, match)) - { - return from; - } - } - - return NULL; -} - -/* - * Returns entities that have origins - * within a spherical area - */ -edict_t * -findradius(edict_t *from, vec3_t org, float rad) -{ - vec3_t eorg; - int j; - - if (!from) - { - from = g_edicts; - } - else - { - from++; - } - - for ( ; from < &g_edicts[globals.num_edicts]; from++) - { - if (!from->inuse) - { - continue; - } - - if (from->solid == SOLID_NOT) - { - continue; - } - - for (j = 0; j < 3; j++) - { - eorg[j] = org[j] - (from->s.origin[j] + - (from->mins[j] + from->maxs[j]) * 0.5); - } - - if (VectorLength(eorg) > rad) - { - continue; - } - - return from; - } - - return NULL; -} - -/* - * Returns entities that have origins within a spherical area - */ -edict_t * -findradius2(edict_t *from, vec3_t org, float rad) -{ - /* rad must be positive */ - vec3_t eorg; - int j; - - if (!from) - { - from = g_edicts; - } - else - { - from++; - } - - for ( ; from < &g_edicts[globals.num_edicts]; from++) - { - if (!from->inuse) - { - continue; - } - - if (from->solid == SOLID_NOT) - { - continue; - } - - if (!from->takedamage) - { - continue; - } - - if (!(from->svflags & SVF_DAMAGEABLE)) - { - continue; - } - - for (j = 0; j < 3; j++) - { - eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5); - } - - if (VectorLength(eorg) > rad) - { - continue; - } - - return from; - } - - return NULL; -} - -/* - * Searches all active entities for - * the next one that holds the matching - * string at fieldofs (use the FOFS() macro) - * in the structure. - * - * Searches beginning at the edict after from, - * or the beginning. If NULL, NULL will be - * returned if the end of the list is reached. - */ -edict_t * -G_PickTarget(char *targetname) -{ - edict_t *ent = NULL; - int num_choices = 0; - edict_t *choice[MAXCHOICES]; - - if (!targetname) - { - gi.dprintf("G_PickTarget called with NULL targetname\n"); - return NULL; - } - - while (1) - { - ent = G_Find(ent, FOFS(targetname), targetname); - - if (!ent) - { - break; - } - - choice[num_choices++] = ent; - - if (num_choices == MAXCHOICES) - { - break; - } - } - - if (!num_choices) - { - gi.dprintf("G_PickTarget: target %s not found\n", targetname); - return NULL; - } - - return choice[randk() % num_choices]; -} - -void -Think_Delay(edict_t *ent) -{ - if (!ent) - { - return; - } - - G_UseTargets(ent, ent->activator); - G_FreeEdict(ent); -} - -/* - * The global "activator" should be set to - * the entity that initiated the firing. - * - * If self.delay is set, a DelayedUse entity - * will be created that will actually do the - * SUB_UseTargets after that many seconds have passed. - * - * Centerprints any self.message to the activator. - * - * Search for (string)targetname in all entities that - * match (string)self.target and call their .use function - */ -void -G_UseTargets(edict_t *ent, edict_t *activator) -{ - edict_t *t; - edict_t *master; - - if (!ent) - { - return; - } - - /* check for a delay */ - if (ent->delay) - { - /* create a temp object to fire at a later time */ - t = G_Spawn(); - t->classname = "DelayedUse"; - t->nextthink = level.time + ent->delay; - t->think = Think_Delay; - t->activator = activator; - - if (!activator) - { - gi.dprintf("Think_Delay with no activator\n"); - } - - t->message = ent->message; - t->target = ent->target; - t->killtarget = ent->killtarget; - return; - } - - /* print the message */ - if (activator && (ent->message) && !(activator->svflags & SVF_MONSTER)) - { - gi.centerprintf(activator, "%s", ent->message); - - if (ent->noise_index) - { - gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0); - } - else - { - gi.sound(activator, CHAN_AUTO, gi.soundindex( - "misc/talk1.wav"), 1, ATTN_NORM, 0); - } - } - - /* kill killtargets */ - if (ent->killtarget) - { - t = NULL; - - while ((t = G_Find(t, FOFS(targetname), ent->killtarget))) - { - /* if this entity is part of a train, cleanly remove it */ - if (t->flags & FL_TEAMSLAVE) - { - master = t->teammaster; - - while (master) - { - if (master->teamchain == t) - { - master->teamchain = t->teamchain; - break; - } - - master = master->teamchain; - } - } - - /* correct killcounter if a living monster gets killtargeted */ - if ((t->monsterinfo.checkattack || strcmp (t->classname, "turret_driver") == 0) && - !(t->monsterinfo.aiflags & (AI_GOOD_GUY|AI_DO_NOT_COUNT)) && t->deadflag != DEAD_DEAD) - { - level.killed_monsters++; - } - - G_FreeEdict(t); - - if (!ent->inuse) - { - gi.dprintf("entity was removed while using killtargets\n"); - return; - } - } - } - - /* fire targets */ - if (ent->target) - { - t = NULL; - - while ((t = G_Find(t, FOFS(targetname), ent->target))) - { - /* doors fire area portals in a specific way */ - if (!Q_stricmp(t->classname, "func_areaportal") && - (!Q_stricmp(ent->classname, "func_door") || - !Q_stricmp(ent->classname, "func_door_rotating"))) - { - continue; - } - - if (t == ent) - { - gi.dprintf("WARNING: Entity used itself.\n"); - } - else - { - if (t->use) - { - t->use(t, ent, activator); - } - } - - if (!ent->inuse) - { - gi.dprintf("entity was removed while using targets\n"); - return; - } - } - } -} - -/* - * This is just a convenience function - * for making temporary vectors for function calls - */ -float * -tv(float x, float y, float z) -{ - static int index; - static vec3_t vecs[8]; - float *v; - - /* use an array so that multiple - tempvectors won't collide - for a while */ - v = vecs[index]; - index = (index + 1) & 7; - - v[0] = x; - v[1] = y; - v[2] = z; - - return v; -} - -/* - * This is just a convenience function - * for printing vectors - */ -char * -vtos(vec3_t v) -{ - static int index; - static char str[8][32]; - char *s; - - /* use an array so that multiple vtos won't collide */ - s = str[index]; - index = (index + 1) & 7; - - Com_sprintf(s, 32, "(%i %i %i)", (int)v[0], (int)v[1], (int)v[2]); - - return s; -} - -void -get_normal_vector(const cplane_t *p, vec3_t normal) -{ - if (p) - { - VectorCopy(p->normal, normal); - } - else - { - VectorCopy(vec3_origin, normal); - } -} - -void -G_SetMovedir(vec3_t angles, vec3_t movedir) -{ - if (VectorCompare(angles, VEC_UP)) - { - VectorCopy(MOVEDIR_UP, movedir); - } - else if (VectorCompare(angles, VEC_DOWN)) - { - VectorCopy(MOVEDIR_DOWN, movedir); - } - else - { - AngleVectors(angles, movedir, NULL, NULL); - } - - VectorClear(angles); -} - -float -vectoyaw(vec3_t vec) -{ - float yaw; - - if (vec[PITCH] == 0) - { - yaw = 0; - - if (vec[YAW] > 0) - { - yaw = 90; - } - else if (vec[YAW] < 0) - { - yaw = -90; - } - } - else - { - yaw = (int)(atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); - - if (yaw < 0) - { - yaw += 360; - } - } - - return yaw; -} - -float -vectoyaw2(vec3_t vec) -{ - float yaw; - - if (vec[PITCH] == 0) - { - if (vec[YAW] == 0) - { - yaw = 0; - } - else if (vec[YAW] > 0) - { - yaw = 90; - } - else - { - yaw = 270; - } - } - else - { - yaw = (atan2(vec[YAW], vec[PITCH]) * 180 / M_PI); - - if (yaw < 0) - { - yaw += 360; - } - } - - return yaw; -} - -void -vectoangles(vec3_t value1, vec3_t angles) -{ - float forward; - float yaw, pitch; - - if ((value1[1] == 0) && (value1[0] == 0)) - { - yaw = 0; - - if (value1[2] > 0) - { - pitch = 90; - } - else - { - pitch = 270; - } - } - else - { - if (value1[0]) - { - yaw = (int)(atan2(value1[1], value1[0]) * 180 / M_PI); - } - else if (value1[1] > 0) - { - yaw = 90; - } - else - { - yaw = -90; - } - - if (yaw < 0) - { - yaw += 360; - } - - forward = sqrt(value1[0] * value1[0] + value1[1] * value1[1]); - pitch = (int)(atan2(value1[2], forward) * 180 / M_PI); - - if (pitch < 0) - { - pitch += 360; - } - } - - angles[PITCH] = -pitch; - angles[YAW] = yaw; - angles[ROLL] = 0; -} - -void -vectoangles2(vec3_t value1, vec3_t angles) -{ - float forward; - float yaw, pitch; - - if ((value1[1] == 0) && (value1[0] == 0)) - { - yaw = 0; - - if (value1[2] > 0) - { - pitch = 90; - } - else - { - pitch = 270; - } - } - else - { - if (value1[0]) - { - yaw = (atan2(value1[1], value1[0]) * 180 / M_PI); - } - else if (value1[1] > 0) - { - yaw = 90; - } - else - { - yaw = 270; - } - - if (yaw < 0) - { - yaw += 360; - } - - forward = sqrt(value1[0] * value1[0] + value1[1] * value1[1]); - pitch = (atan2(value1[2], forward) * 180 / M_PI); - - if (pitch < 0) - { - pitch += 360; - } - } - - angles[PITCH] = -pitch; - angles[YAW] = yaw; - angles[ROLL] = 0; -} - -char * -G_CopyString(char *in) -{ - char *out; - - if (!in) - { - return NULL; - } - - out = gi.TagMalloc(strlen(in) + 1, TAG_LEVEL); - strcpy(out, in); - return out; -} - -void -G_InitEdict(edict_t *e) -{ - if (!e) - { - return; - } - - if (e->nextthink) - { - e->nextthink = 0; - } - - e->inuse = true; - e->classname = "noclass"; - e->gravity = 1.0; - e->s.number = e - g_edicts; - - e->gravityVector[0] = 0.0; - e->gravityVector[1] = 0.0; - e->gravityVector[2] = -1.0; -} - -/* - * Either finds a free edict, or allocates a - * new one. Try to avoid reusing an entity - * that was recently freed, because it can - * cause the client to think the entity - * morphed into something else instead of - * being removed and recreated, which can - * cause interpolated angles and bad trails. - */ -#define POLICY_DEFAULT 0 -#define POLICY_DESPERATE 1 - -static edict_t * -G_FindFreeEdict(int policy) -{ - edict_t *e; - - for (e = g_edicts + game.maxclients + 1 ; e < &g_edicts[globals.num_edicts] ; e++) - { - /* the first couple seconds of server time can involve a lot of - freeing and allocating, so relax the replacement policy - */ - if (!e->inuse && (policy == POLICY_DESPERATE || e->freetime < 2.0f || (level.time - e->freetime) > 0.5f)) - { - G_InitEdict (e); - return e; - } - } - - return NULL; -} - -edict_t * -G_SpawnOptional(void) -{ - edict_t *e = G_FindFreeEdict (POLICY_DEFAULT); - - if (e) - { - return e; - } - - if (globals.num_edicts >= game.maxentities) - { - return G_FindFreeEdict (POLICY_DESPERATE); - } - - e = &g_edicts[globals.num_edicts++]; - G_InitEdict (e); - - return e; -} - -edict_t * -G_Spawn(void) -{ - edict_t *e = G_SpawnOptional(); - - if (!e) - gi.error ("ED_Alloc: no free edicts"); - - return e; -} - -/* - * Marks the edict as free - */ -void -G_FreeEdict(edict_t *ed) -{ - if (!ed) - { - return; - } - - gi.unlinkentity(ed); /* unlink from world */ - - if (deathmatch->value || coop->value) - { - if ((ed - g_edicts) <= (maxclients->value + BODY_QUEUE_SIZE)) - { - return; - } - } - else - { - if ((ed - g_edicts) <= maxclients->value) - { - return; - } - } - - memset(ed, 0, sizeof(*ed)); - ed->classname = "freed"; - ed->freetime = level.time; - ed->inuse = false; -} - -void -G_TouchTriggers(edict_t *ent) -{ - int i, num; - edict_t *touch[MAX_EDICTS], *hit; - - if (!ent) - { - return; - } - - /* dead things don't activate triggers! */ - if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0)) - { - return; - } - - num = gi.BoxEdicts(ent->absmin, ent->absmax, touch, - MAX_EDICTS, AREA_TRIGGERS); - - /* be careful, it is possible to have an entity in this - list removed before we get to it (killtriggered) */ - for (i = 0; i < num; i++) - { - hit = touch[i]; - - if (!hit->inuse) - { - continue; - } - - if (!hit->touch) - { - continue; - } - - hit->touch(hit, ent, NULL, NULL); - } -} - -/* - * Call after linking a new trigger - * in during gameplay to force all - * entities it covers to immediately - * touch it - */ -void -G_TouchSolids(edict_t *ent) -{ - int i, num; - edict_t *touch[MAX_EDICTS], *hit; - - if (!ent) - { - return; - } - - num = gi.BoxEdicts(ent->absmin, ent->absmax, touch, - MAX_EDICTS, AREA_SOLID); - - /* be careful, it is possible to have an entity in this - list removed before we get to it (killtriggered) */ - for (i = 0; i < num; i++) - { - hit = touch[i]; - - if (!hit->inuse) - { - continue; - } - - if (ent->touch) - { - ent->touch(hit, ent, NULL, NULL); - } - - if (!ent->inuse) - { - break; - } - } -} - -/* - * Kills all entities that would touch the - * proposed new positioning of ent. Ent s - * hould be unlinked before calling this! - */ -qboolean -KillBox(edict_t *ent) -{ - trace_t tr; - - if (!ent) - { - return false; - } - - while (1) - { - tr = gi.trace(ent->s.origin, ent->mins, ent->maxs, ent->s.origin, - NULL, MASK_PLAYERSOLID); - - if (!tr.ent) - { - break; - } - - /* nail it */ - T_Damage(tr.ent, ent, ent, vec3_origin, ent->s.origin, vec3_origin, - 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG); - - /* if we didn't kill it, fail */ - if (tr.ent->solid) - { - return false; - } - } - - return true; /* all clear */ -} diff --git a/src/rogue/monster/chick/chick.c b/src/rogue/monster/chick/chick.c deleted file mode 100644 index a2a353fa..00000000 --- a/src/rogue/monster/chick/chick.c +++ /dev/null @@ -1,1103 +0,0 @@ -/* - * Copyright (C) 1997-2001 Id Software, Inc. - * Copyright (c) ZeniMax Media Inc. - * - * This program 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. - * - * This program 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 this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA - * 02111-1307, USA. - * - * ======================================================================= - * - * Iron Maiden. - * - * ======================================================================= - */ - -#include "../../header/local.h" -#include "chick.h" - -#define LEAD_TARGET 1 - -qboolean visible(edict_t *self, edict_t *other); - -void chick_stand(edict_t *self); -void chick_run(edict_t *self); -void chick_reslash(edict_t *self); -void chick_rerocket(edict_t *self); -void chick_attack1(edict_t *self); - -static int sound_missile_prelaunch; -static int sound_missile_launch; -static int sound_melee_swing; -static int sound_melee_hit; -static int sound_missile_reload; -static int sound_death1; -static int sound_death2; -static int sound_fall_down; -static int sound_idle1; -static int sound_idle2; -static int sound_pain1; -static int sound_pain2; -static int sound_pain3; -static int sound_sight; -static int sound_search; - -void -ChickMoan(edict_t *self) -{ - if (!self) - { - return; - } - - if (random() < 0.5) - { - gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0); - } - else - { - gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0); - } -} - -static mframe_t chick_frames_fidget[] = { - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, ChickMoan}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL} -}; - -mmove_t chick_move_fidget = { - FRAME_stand201, - FRAME_stand230, - chick_frames_fidget, - chick_stand -}; - -void -chick_fidget(edict_t *self) -{ - if (!self) - { - return; - } - - if (self->monsterinfo.aiflags & AI_STAND_GROUND) - { - return; - } - - if (random() <= 0.3) - { - self->monsterinfo.currentmove = &chick_move_fidget; - } -} - -static mframe_t chick_frames_stand[] = { - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, NULL}, - {ai_stand, 0, chick_fidget}, -}; - -mmove_t chick_move_stand = { - FRAME_stand101, - FRAME_stand130, - chick_frames_stand, - NULL -}; - -void -chick_stand(edict_t *self) -{ - if (!self) - { - return; - } - - self->monsterinfo.currentmove = &chick_move_stand; -} - -static mframe_t chick_frames_start_run[] = { - {ai_run, 1, NULL}, - {ai_run, 0, NULL}, - {ai_run, 0, NULL}, - {ai_run, -1, NULL}, - {ai_run, -1, NULL}, - {ai_run, 0, NULL}, - {ai_run, 1, NULL}, - {ai_run, 3, NULL}, - {ai_run, 6, NULL}, - {ai_run, 3, NULL} -}; - -mmove_t chick_move_start_run = { - FRAME_walk01, - FRAME_walk10, - chick_frames_start_run, - chick_run -}; - -static mframe_t chick_frames_run[] = { - {ai_run, 6, NULL}, - {ai_run, 8, NULL}, - {ai_run, 13, NULL}, - {ai_run, 5, NULL}, - {ai_run, 7, NULL}, - {ai_run, 4, NULL}, - {ai_run, 11, NULL}, - {ai_run, 5, NULL}, - {ai_run, 9, NULL}, - {ai_run, 7, NULL} -}; - -mmove_t chick_move_run = { - FRAME_walk11, - FRAME_walk20, - chick_frames_run, - NULL -}; - -static mframe_t chick_frames_walk[] = { - {ai_walk, 6, NULL}, - {ai_walk, 8, NULL}, - {ai_walk, 13, NULL}, - {ai_walk, 5, NULL}, - {ai_walk, 7, NULL}, - {ai_walk, 4, NULL}, - {ai_walk, 11, NULL}, - {ai_walk, 5, NULL}, - {ai_walk, 9, NULL}, - {ai_walk, 7, NULL} -}; - -mmove_t chick_move_walk = { - FRAME_walk11, - FRAME_walk20, - chick_frames_walk, - NULL -}; - -void -chick_walk(edict_t *self) -{ - if (!self) - { - return; - } - - self->monsterinfo.currentmove = &chick_move_walk; -} - -void -chick_run(edict_t *self) -{ - if (!self) - { - return; - } - - monster_done_dodge(self); - - if (self->monsterinfo.aiflags & AI_STAND_GROUND) - { - self->monsterinfo.currentmove = &chick_move_stand; - return; - } - - if ((self->monsterinfo.currentmove == &chick_move_walk) || - (self->monsterinfo.currentmove == &chick_move_start_run)) - { - self->monsterinfo.currentmove = &chick_move_run; - } - else - { - self->monsterinfo.currentmove = &chick_move_start_run; - } -} - -static mframe_t chick_frames_pain1[] = { - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL} -}; - -mmove_t chick_move_pain1 = { - FRAME_pain101, - FRAME_pain105, - chick_frames_pain1, chick_run -}; - -static mframe_t chick_frames_pain2[] = { - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL} -}; - -mmove_t chick_move_pain2 = { - FRAME_pain201, - FRAME_pain205, - chick_frames_pain2, - chick_run -}; - -static mframe_t chick_frames_pain3[] = { - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, -6, NULL}, - {ai_move, 3, NULL}, - {ai_move, 11, NULL}, - {ai_move, 3, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 4, NULL}, - {ai_move, 1, NULL}, - {ai_move, 0, NULL}, - {ai_move, -3, NULL}, - {ai_move, -4, NULL}, - {ai_move, 5, NULL}, - {ai_move, 7, NULL}, - {ai_move, -2, NULL}, - {ai_move, 3, NULL}, - {ai_move, -5, NULL}, - {ai_move, -2, NULL}, - {ai_move, -8, NULL}, - {ai_move, 2, NULL} -}; - -mmove_t chick_move_pain3 = { - FRAME_pain301, - FRAME_pain321, - chick_frames_pain3, - chick_run -}; - -void -chick_pain(edict_t *self, edict_t *other /* other */, float kick /* other */, int damage) -{ - float r; - - if (!self) - { - return; - } - - monster_done_dodge(self); - - if (self->health < (self->max_health / 2)) - { - self->s.skinnum = 1; - } - - if (level.time < self->pain_debounce_time) - { - return; - } - - self->pain_debounce_time = level.time + 3; - - r = random(); - - if (r < 0.33) - { - gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0); - } - else if (r < 0.66) - { - gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0); - } - else - { - gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0); - } - - if (skill->value == SKILL_HARDPLUS) - { - return; /* no pain anims in nightmare */ - } - - /* clear this from blindfire */ - self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; - - if (damage <= 10) - { - self->monsterinfo.currentmove = &chick_move_pain1; - } - else if (damage <= 25) - { - self->monsterinfo.currentmove = &chick_move_pain2; - } - else - { - self->monsterinfo.currentmove = &chick_move_pain3; - } - - /* clear duck flag */ - if (self->monsterinfo.aiflags & AI_DUCKED) - { - monster_duck_up(self); - } -} - -void -chick_dead(edict_t *self) -{ - if (!self) - { - return; - } - - VectorSet(self->mins, -16, -16, 0); - VectorSet(self->maxs, 16, 16, 16); - self->movetype = MOVETYPE_TOSS; - self->svflags |= SVF_DEADMONSTER; - self->nextthink = 0; - gi.linkentity(self); -} - -static mframe_t chick_frames_death2[] = { - {ai_move, -6, NULL}, - {ai_move, 0, NULL}, - {ai_move, -1, NULL}, - {ai_move, -5, NULL}, - {ai_move, 0, NULL}, - {ai_move, -1, NULL}, - {ai_move, -2, NULL}, - {ai_move, 1, NULL}, - {ai_move, 10, NULL}, - {ai_move, 2, NULL}, - {ai_move, 3, NULL}, - {ai_move, 1, NULL}, - {ai_move, 2, NULL}, - {ai_move, 0, NULL}, - {ai_move, 3, NULL}, - {ai_move, 3, NULL}, - {ai_move, 1, NULL}, - {ai_move, -3, NULL}, - {ai_move, -5, NULL}, - {ai_move, 4, NULL}, - {ai_move, 15, NULL}, - {ai_move, 14, NULL}, - {ai_move, 1, NULL} -}; - -mmove_t chick_move_death2 = { - FRAME_death201, - FRAME_death223, - chick_frames_death2, - chick_dead -}; - -static mframe_t chick_frames_death1[] = { - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, -7, NULL}, - {ai_move, 4, NULL}, - {ai_move, 11, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL}, - {ai_move, 0, NULL} -}; - -mmove_t chick_move_death1 = { - FRAME_death101, - FRAME_death112, - chick_frames_death1, - chick_dead -}; - -void -chick_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, - int damage, vec3_t point /* unused */) -{ - int n; - - if (!self) - { - return; - } - - /* check for gib */ - if (self->health <= self->gib_health) - { - gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); - - for (n = 0; n < 2; n++) - { - ThrowGib(self, "models/objects/gibs/bone/tris.md2", damage, - GIB_ORGANIC); - } - - for (n = 0; n < 4; n++) - { - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); - } - - ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); - self->deadflag = DEAD_DEAD; - return; - } - - if (self->deadflag == DEAD_DEAD) - { - return; - } - - /* regular death */ - self->deadflag = DEAD_DEAD; - self->takedamage = DAMAGE_YES; - - n = rand() % 2; - - if (n == 0) - { - self->monsterinfo.currentmove = &chick_move_death1; - gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0); - } - else - { - self->monsterinfo.currentmove = &chick_move_death2; - gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0); - } -} - -static mframe_t chick_frames_duck[] = { - {ai_move, 0, monster_duck_down}, - {ai_move, 1, NULL}, - {ai_move, 4, monster_duck_hold}, - {ai_move, -4, NULL}, - {ai_move, -5, monster_duck_up}, - {ai_move, 3, NULL}, - {ai_move, 1, NULL} -}; - -mmove_t chick_move_duck = { - FRAME_duck01, - FRAME_duck07, - chick_frames_duck, - chick_run -}; - -void -ChickSlash(edict_t *self) -{ - vec3_t aim; - - if (!self) - { - return; - } - - VectorSet(aim, MELEE_DISTANCE, self->mins[0], 10); - gi.sound(self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0); - fire_hit(self, aim, (10 + (rand() % 6)), 100); -} - -void -ChickRocket(edict_t *self) -{ - vec3_t forward, right; - vec3_t start; - vec3_t dir; - vec3_t vec; - trace_t trace; /* check target */ - int rocketSpeed; - float dist; - vec3_t target; - qboolean blindfire = false; - - if (!self) - { - return; - } - - if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) - { - blindfire = true; - } - else - { - blindfire = false; - } - - if (!self->enemy || !self->enemy->inuse) - { - return; - } - - AngleVectors(self->s.angles, forward, right, NULL); - G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_CHICK_ROCKET_1], - forward, right, start); - - rocketSpeed = 500 + (100 * skill->value); /* rock & roll.... :) */ - - if (blindfire) - { - VectorCopy(self->monsterinfo.blind_fire_target, target); - } - else - { - VectorCopy(self->enemy->s.origin, target); - } - - /* blindfire shooting */ - if (blindfire) - { - VectorCopy(target, vec); - VectorSubtract(vec, start, dir); - } - /* don't shoot at feet if they're above where i'm shooting from. */ - else if ((random() < 0.33) || (start[2] < self->enemy->absmin[2])) - { - VectorCopy(target, vec); - vec[2] += self->enemy->viewheight; - VectorSubtract(vec, start, dir); - } - else - { - VectorCopy(target, vec); - vec[2] = self->enemy->absmin[2]; - VectorSubtract(vec, start, dir); - } - - /* lead target (not when blindfiring) */ - if ((!blindfire) && ((random() < (0.2 + ((3 - skill->value) * 0.15))))) - { - float time; - - dist = VectorLength(dir); - time = dist / rocketSpeed; - VectorMA(vec, time, self->enemy->velocity, vec); - VectorSubtract(vec, start, dir); - } - - VectorNormalize(dir); - - if (blindfire) - { - /* blindfire has different fail criteria for the trace */ - if (!blind_rocket_ok(self, start, right, target, 10.0f, dir)) - { - return; - } - } - else - { - trace = gi.trace(start, vec3_origin, vec3_origin, vec, self, MASK_SHOT); - - if (((trace.ent != self->enemy) && (trace.ent != world)) || - ((trace.fraction <= 0.5f) && !trace.ent->client)) - { - return; - } - } - - monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1); -} - -void -Chick_PreAttack1(edict_t *self) -{ - if (!self) - { - return; - } - - gi.sound(self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0); -} - -void -ChickReload(edict_t *self) -{ - if (!self) - { - return; - } - - gi.sound(self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0); -} - -static mframe_t chick_frames_start_attack1[] = { - {ai_charge, 0, Chick_PreAttack1}, - {ai_charge, 0, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, 4, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, -3, NULL}, - {ai_charge, 3, NULL}, - {ai_charge, 5, NULL}, - {ai_charge, 7, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, 0, chick_attack1} -}; - -mmove_t chick_move_start_attack1 = { - FRAME_attak101, - FRAME_attak113, - chick_frames_start_attack1, - NULL -}; - -static mframe_t chick_frames_attack1[] = { - {ai_charge, 19, ChickRocket}, - {ai_charge, -6, NULL}, - {ai_charge, -5, NULL}, - {ai_charge, -2, NULL}, - {ai_charge, -7, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, 1, NULL}, - {ai_charge, 10, ChickReload}, - {ai_charge, 4, NULL}, - {ai_charge, 5, NULL}, - {ai_charge, 6, NULL}, - {ai_charge, 6, NULL}, - {ai_charge, 4, NULL}, - {ai_charge, 3, chick_rerocket} -}; - -mmove_t chick_move_attack1 = { - FRAME_attak114, - FRAME_attak127, - chick_frames_attack1, - NULL -}; - -static mframe_t chick_frames_end_attack1[] = { - {ai_charge, -3, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, -6, NULL}, - {ai_charge, -4, NULL}, - {ai_charge, -2, NULL} -}; - -mmove_t chick_move_end_attack1 = { - FRAME_attak128, - FRAME_attak132, - chick_frames_end_attack1, - chick_run -}; - -void -chick_rerocket(edict_t *self) -{ - if (!self) - { - return; - } - - if (self->monsterinfo.aiflags & AI_MANUAL_STEERING) - { - self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING; - self->monsterinfo.currentmove = &chick_move_end_attack1; - return; - } - - if (self->enemy->health > 0) - { - if (range(self, self->enemy) > RANGE_MELEE) - { - if (visible(self, self->enemy)) - { - if (random() <= (0.6 + (0.05 * ((float)skill->value)))) - { - self->monsterinfo.currentmove = &chick_move_attack1; - return; - } - } - } - } - - self->monsterinfo.currentmove = &chick_move_end_attack1; -} - -void -chick_attack1(edict_t *self) -{ - if (!self) - { - return; - } - - self->monsterinfo.currentmove = &chick_move_attack1; -} - -static mframe_t chick_frames_slash[] = { - {ai_charge, 1, NULL}, - {ai_charge, 7, ChickSlash}, - {ai_charge, -7, NULL}, - {ai_charge, 1, NULL}, - {ai_charge, -1, NULL}, - {ai_charge, 1, NULL}, - {ai_charge, 0, NULL}, - {ai_charge, 1, NULL}, - {ai_charge, -2, chick_reslash} -}; - -mmove_t chick_move_slash = { - FRAME_attak204, - FRAME_attak212, - chick_frames_slash, - NULL -}; - -static mframe_t chick_frames_end_slash[] = { - {ai_charge, -6, NULL}, - {ai_charge, -1, NULL}, - {ai_charge, -6, NULL}, - {ai_charge, 0, NULL} -}; - -mmove_t chick_move_end_slash = { - FRAME_attak213, - FRAME_attak216, - chick_frames_end_slash, - chick_run -}; - -void -chick_reslash(edict_t *self) -{ - if (!self) - { - return; - } - - if (self->enemy->health > 0) - { - if (range(self, self->enemy) == RANGE_MELEE) - { - if (random() <= 0.9) - { - self->monsterinfo.currentmove = &chick_move_slash; - return; - } - else - { - self->monsterinfo.currentmove = &chick_move_end_slash; - return; - } - } - } - - self->monsterinfo.currentmove = &chick_move_end_slash; -} - -void -chick_slash(edict_t *self) -{ - if (!self) - { - return; - } - - self->monsterinfo.currentmove = &chick_move_slash; -} - -static mframe_t chick_frames_start_slash[] = { - {ai_charge, 1, NULL}, - {ai_charge, 8, NULL}, - {ai_charge, 3, NULL} -}; - -mmove_t chick_move_start_slash = { - FRAME_attak201, - FRAME_attak203, - chick_frames_start_slash, - chick_slash -}; - -void -chick_melee(edict_t *self) -{ - if (!self) - { - return; - } - - self->monsterinfo.currentmove = &chick_move_start_slash; -} - -void -chick_attack(edict_t *self) -{ - float r, chance; - - if (!self) - { - return; - } - - monster_done_dodge(self); - - if (self->monsterinfo.attack_state == AS_BLIND) - { - /* setup shot probabilities */ - if (self->monsterinfo.blind_fire_delay < 1.0) - { - chance = 1.0; - } - else if (self->monsterinfo.blind_fire_delay < 7.5) - { - chance = 0.4; - } - else - { - chance = 0.1; - } - - r = random(); - - /* minimum of 2 seconds, plus 0-3, after the shots are done */ - self->monsterinfo.blind_fire_delay += 4.0 + 1.5 + random(); - - /* don't shoot at the origin */ - if (VectorCompare(self->monsterinfo.blind_fire_target, vec3_origin)) - { - return; - } - - /* don't shoot if the dice say not to */ - if (r > chance) - { - return; - } - - /* turn on manual steering to signal both manual steering and blindfire */ - self->monsterinfo.aiflags |= AI_MANUAL_STEERING; - self->monsterinfo.currentmove = &chick_move_start_attack1; - self->monsterinfo.attack_finished = level.time + 2 * random(); - return; - } - - self->monsterinfo.currentmove = &chick_move_start_attack1; -} - -void -chick_sight(edict_t *self, edict_t *other /* unused */) -{ - if (!self) - { - return; - } - - gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0); -} - -qboolean -chick_blocked(edict_t *self, float dist) -{ - if (!self) - { - return false; - } - - if (blocked_checkplat(self, dist)) - { - return true; - } - - return false; -} - -void -chick_duck(edict_t *self, float eta) -{ - if (!self) - { - return; - } - - if ((self->monsterinfo.currentmove == &chick_move_start_attack1) || - (self->monsterinfo.currentmove == &chick_move_attack1)) - { - /* if we're shooting, and not on easy, don't dodge */ - if (skill->value) - { - self->monsterinfo.aiflags &= ~AI_DUCKED; - return; - } - } - - if (skill->value == SKILL_EASY) - { - /* stupid dodge */ - self->monsterinfo.duck_wait_time = level.time + eta + 1; - } - else - { - self->monsterinfo.duck_wait_time = level.time + eta + (0.1 * (3 - skill->value)); - } - - /* has to be done immediately otherwise she can get stuck */ - monster_duck_down(self); - - self->monsterinfo.nextframe = FRAME_duck01; - self->monsterinfo.currentmove = &chick_move_duck; - return; -} - -void -chick_sidestep(edict_t *self) -{ - if (!self) - { - return; - } - - if ((self->monsterinfo.currentmove == &chick_move_start_attack1) || - (self->monsterinfo.currentmove == &chick_move_attack1)) - { - /* if we're shooting, and not on easy, don't dodge */ - if (skill->value > SKILL_EASY) - { - self->monsterinfo.aiflags &= ~AI_DODGING; - return; - } - } - - if (self->monsterinfo.currentmove != &chick_move_run) - { - self->monsterinfo.currentmove = &chick_move_run; - } -} - -/* - * QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight - */ -void -SP_monster_chick(edict_t *self) -{ - if (!self) - { - return; - } - - if (deathmatch->value) - { - G_FreeEdict(self); - return; - } - - sound_missile_prelaunch = gi.soundindex("chick/chkatck1.wav"); - sound_missile_launch = gi.soundindex("chick/chkatck2.wav"); - sound_melee_swing = gi.soundindex("chick/chkatck3.wav"); - sound_melee_hit = gi.soundindex("chick/chkatck4.wav"); - sound_missile_reload = gi.soundindex("chick/chkatck5.wav"); - sound_death1 = gi.soundindex("chick/chkdeth1.wav"); - sound_death2 = gi.soundindex("chick/chkdeth2.wav"); - sound_fall_down = gi.soundindex("chick/chkfall1.wav"); - sound_idle1 = gi.soundindex("chick/chkidle1.wav"); - sound_idle2 = gi.soundindex("chick/chkidle2.wav"); - sound_pain1 = gi.soundindex("chick/chkpain1.wav"); - sound_pain2 = gi.soundindex("chick/chkpain2.wav"); - sound_pain3 = gi.soundindex("chick/chkpain3.wav"); - sound_sight = gi.soundindex("chick/chksght1.wav"); - sound_search = gi.soundindex("chick/chksrch1.wav"); - - self->movetype = MOVETYPE_STEP; - self->solid = SOLID_BBOX; - self->s.modelindex = gi.modelindex("models/monsters/bitch2/tris.md2"); - VectorSet(self->mins, -16, -16, 0); - VectorSet(self->maxs, 16, 16, 56); - - self->health = 175; - self->gib_health = -70; - self->mass = 200; - - self->pain = chick_pain; - self->die = chick_die; - - self->monsterinfo.stand = chick_stand; - self->monsterinfo.walk = chick_walk; - self->monsterinfo.run = chick_run; - self->monsterinfo.dodge = M_MonsterDodge; - self->monsterinfo.duck = chick_duck; - self->monsterinfo.unduck = monster_duck_up; - self->monsterinfo.sidestep = chick_sidestep; - self->monsterinfo.attack = chick_attack; - self->monsterinfo.melee = chick_melee; - self->monsterinfo.sight = chick_sight; - self->monsterinfo.blocked = chick_blocked; - - gi.linkentity(self); - - self->monsterinfo.currentmove = &chick_move_stand; - self->monsterinfo.scale = MODEL_SCALE; - - self->monsterinfo.blindfire = true; - walkmonster_start(self); -} diff --git a/src/rogue/monster/soldier/soldier.c b/src/rogue/monster/soldier/soldier.c index a7b0e29f..df6d1694 100644 --- a/src/rogue/monster/soldier/soldier.c +++ b/src/rogue/monster/soldier/soldier.c @@ -22,14 +22,16 @@ * * Soldier aka "Guard". This is the most complex enemy in Quake 2, since * it uses all AI features (dodging, sight, crouching, etc) and comes - * in a myriad of variants. In Rogue it's even more complex due to - * the blindfire stuff. + * in a myriad of variants. + * In Rogue it's even more complex due to the blindfire stuff. + * In Xatrix it's even more complex due to another 4 variants added. * * ======================================================================= */ #include "../../header/local.h" #include "soldier.h" +#include "soldierh.h" static int sound_idle; static int sound_sight1; @@ -41,12 +43,55 @@ static int sound_death_light; static int sound_death; static int sound_death_ss; static int sound_cock; +static int sound_step; +static int sound_step2; +static int sound_step3; +static int sound_step4; void soldier_duck_up(edict_t *self); void soldier_stand(edict_t *self); void soldier_run(edict_t *self); void soldier_fire(edict_t *self, int); void soldier_blind(edict_t *self); +void soldierh_stand(edict_t *self); +void soldierh_run(edict_t *self); +extern void brain_dabeam(edict_t *self); + +void +soldier_footstep(edict_t *self) +{ + if (!g_monsterfootsteps->value) + return; + + // Lazy loading for savegame compatibility. + if (sound_step == 0 || sound_step2 == 0 || sound_step3 == 0 || sound_step4 == 0) + { + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + } + + int i; + i = randk() % 4; + + if (i == 0) + { + gi.sound(self, CHAN_BODY, sound_step, 1, ATTN_NORM, 0); + } + else if (i == 1) + { + gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0); + } + else if (i == 2) + { + gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0); + } + else if (i == 3) + { + gi.sound(self, CHAN_BODY, sound_step4, 1, ATTN_NORM, 0); + } +} void soldier_start_charge(edict_t *self) @@ -137,7 +182,8 @@ static mframe_t soldier_frames_stand1[] = { {ai_stand, 0, NULL} }; -mmove_t soldier_move_stand1 = { +mmove_t soldier_move_stand1 = +{ FRAME_stand101, FRAME_stand130, soldier_frames_stand1, @@ -189,7 +235,8 @@ static mframe_t soldier_frames_stand3[] = { {ai_stand, 0, NULL} }; -mmove_t soldier_move_stand3 = { +mmove_t soldier_move_stand3 = +{ FRAME_stand301, FRAME_stand339, soldier_frames_stand3, @@ -233,12 +280,12 @@ static mframe_t soldier_frames_walk1[] = { {ai_walk, 3, NULL}, {ai_walk, 6, NULL}, {ai_walk, 2, NULL}, - {ai_walk, 2, NULL}, + {ai_walk, 2, soldier_footstep}, {ai_walk, 2, NULL}, {ai_walk, 1, NULL}, {ai_walk, 6, NULL}, {ai_walk, 5, NULL}, - {ai_walk, 3, NULL}, + {ai_walk, 3, soldier_footstep}, {ai_walk, -1, soldier_walk1_random}, {ai_walk, 0, NULL}, {ai_walk, 0, NULL}, @@ -265,7 +312,8 @@ static mframe_t soldier_frames_walk1[] = { {ai_walk, 0, NULL} }; -mmove_t soldier_move_walk1 = { +mmove_t soldier_move_walk1 = +{ FRAME_walk101, FRAME_walk133, soldier_frames_walk1, @@ -273,11 +321,11 @@ mmove_t soldier_move_walk1 = { }; static mframe_t soldier_frames_walk2[] = { - {ai_walk, 4, NULL}, + {ai_walk, 4, soldier_footstep}, {ai_walk, 4, NULL}, {ai_walk, 9, NULL}, {ai_walk, 8, NULL}, - {ai_walk, 5, NULL}, + {ai_walk, 5, soldier_footstep}, {ai_walk, 1, NULL}, {ai_walk, 3, NULL}, {ai_walk, 7, NULL}, @@ -285,7 +333,8 @@ static mframe_t soldier_frames_walk2[] = { {ai_walk, 7, NULL} }; -mmove_t soldier_move_walk2 = { +mmove_t soldier_move_walk2 = +{ FRAME_walk209, FRAME_walk218, soldier_frames_walk2, @@ -315,7 +364,8 @@ static mframe_t soldier_frames_start_run[] = { {ai_run, 5, NULL} }; -mmove_t soldier_move_start_run = { +mmove_t soldier_move_start_run = +{ FRAME_run01, FRAME_run02, soldier_frames_start_run, @@ -338,14 +388,15 @@ soldier_fire_run(edict_t *self) static mframe_t soldier_frames_run[] = { {ai_run, 10, NULL}, - {ai_run, 11, NULL}, + {ai_run, 11, soldier_footstep}, {ai_run, 11, NULL}, {ai_run, 16, NULL}, - {ai_run, 10, NULL}, + {ai_run, 10, soldier_footstep}, {ai_run, 15, NULL} }; -mmove_t soldier_move_run = { +mmove_t soldier_move_run = +{ FRAME_run03, FRAME_run08, soldier_frames_run, @@ -388,7 +439,8 @@ static mframe_t soldier_frames_pain1[] = { {ai_move, 0, NULL} }; -mmove_t soldier_move_pain1 = { +mmove_t soldier_move_pain1 = +{ FRAME_pain101, FRAME_pain105, soldier_frames_pain1, @@ -405,7 +457,8 @@ static mframe_t soldier_frames_pain2[] = { {ai_move, 2, NULL} }; -mmove_t soldier_move_pain2 = { +mmove_t soldier_move_pain2 = +{ FRAME_pain201, FRAME_pain207, soldier_frames_pain2, @@ -415,7 +468,7 @@ mmove_t soldier_move_pain2 = { static mframe_t soldier_frames_pain3[] = { {ai_move, -8, NULL}, {ai_move, 10, NULL}, - {ai_move, -4, NULL}, + {ai_move, -4, soldier_footstep}, {ai_move, -1, NULL}, {ai_move, -3, NULL}, {ai_move, 0, NULL}, @@ -430,10 +483,11 @@ static mframe_t soldier_frames_pain3[] = { {ai_move, 2, NULL}, {ai_move, 4, NULL}, {ai_move, 3, NULL}, - {ai_move, 2, NULL} + {ai_move, 2, soldier_footstep} }; -mmove_t soldier_move_pain3 = { +mmove_t soldier_move_pain3 = +{ FRAME_pain301, FRAME_pain318, soldier_frames_pain3, @@ -460,7 +514,8 @@ static mframe_t soldier_frames_pain4[] = { {ai_move, 0, NULL} }; -mmove_t soldier_move_pain4 = { +mmove_t soldier_move_pain4 = +{ FRAME_pain401, FRAME_pain417, soldier_frames_pain4, @@ -468,7 +523,8 @@ mmove_t soldier_move_pain4 = { }; void -soldier_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage) +soldier_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) { float r; int n; @@ -493,8 +549,8 @@ soldier_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage) { if ((self->velocity[2] > 100) && ((self->monsterinfo.currentmove == &soldier_move_pain1) || - (self->monsterinfo.currentmove == &soldier_move_pain2) || - (self->monsterinfo.currentmove == &soldier_move_pain3))) + (self->monsterinfo.currentmove == &soldier_move_pain2) || + (self->monsterinfo.currentmove == &soldier_move_pain3))) { /* clear duck flag */ if (self->monsterinfo.aiflags & AI_DUCKED) @@ -565,20 +621,40 @@ soldier_pain(edict_t *self, edict_t *other /* unused */, float kick, int damage) } static int blaster_flash[] = -{MZ2_SOLDIER_BLASTER_1, MZ2_SOLDIER_BLASTER_2, MZ2_SOLDIER_BLASTER_3, - MZ2_SOLDIER_BLASTER_4, MZ2_SOLDIER_BLASTER_5, MZ2_SOLDIER_BLASTER_6, - MZ2_SOLDIER_BLASTER_7, MZ2_SOLDIER_BLASTER_8}; +{ + MZ2_SOLDIER_BLASTER_1, + MZ2_SOLDIER_BLASTER_2, + MZ2_SOLDIER_BLASTER_3, + MZ2_SOLDIER_BLASTER_4, + MZ2_SOLDIER_BLASTER_5, + MZ2_SOLDIER_BLASTER_6, + MZ2_SOLDIER_BLASTER_7, + MZ2_SOLDIER_BLASTER_8 +}; static int shotgun_flash[] = -{MZ2_SOLDIER_SHOTGUN_1, MZ2_SOLDIER_SHOTGUN_2, MZ2_SOLDIER_SHOTGUN_3, - MZ2_SOLDIER_SHOTGUN_4, MZ2_SOLDIER_SHOTGUN_5, MZ2_SOLDIER_SHOTGUN_6, - MZ2_SOLDIER_SHOTGUN_7, MZ2_SOLDIER_SHOTGUN_8}; +{ + MZ2_SOLDIER_SHOTGUN_1, + MZ2_SOLDIER_SHOTGUN_2, + MZ2_SOLDIER_SHOTGUN_3, + MZ2_SOLDIER_SHOTGUN_4, + MZ2_SOLDIER_SHOTGUN_5, + MZ2_SOLDIER_SHOTGUN_6, + MZ2_SOLDIER_SHOTGUN_7, + MZ2_SOLDIER_SHOTGUN_8 +}; static int machinegun_flash[] = -{MZ2_SOLDIER_MACHINEGUN_1, MZ2_SOLDIER_MACHINEGUN_2, MZ2_SOLDIER_MACHINEGUN_3, - MZ2_SOLDIER_MACHINEGUN_4, MZ2_SOLDIER_MACHINEGUN_5, - MZ2_SOLDIER_MACHINEGUN_6, MZ2_SOLDIER_MACHINEGUN_7, - MZ2_SOLDIER_MACHINEGUN_8}; +{ + MZ2_SOLDIER_MACHINEGUN_1, + MZ2_SOLDIER_MACHINEGUN_2, + MZ2_SOLDIER_MACHINEGUN_3, + MZ2_SOLDIER_MACHINEGUN_4, + MZ2_SOLDIER_MACHINEGUN_5, + MZ2_SOLDIER_MACHINEGUN_6, + MZ2_SOLDIER_MACHINEGUN_7, + MZ2_SOLDIER_MACHINEGUN_8 +}; void soldier_fire(edict_t *self, int in_flash_number) @@ -631,8 +707,8 @@ soldier_fire(edict_t *self, int in_flash_number) } AngleVectors(self->s.angles, forward, right, NULL); - G_ProjectSource(self->s.origin, monster_flash_offset[flash_index], forward, - right, start); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_index], + forward, right, start); if ((flash_number == 5) || (flash_number == 6)) /* he's dead */ { @@ -695,8 +771,9 @@ soldier_fire(edict_t *self, int in_flash_number) } else if (self->s.skinnum <= 3) { - monster_fire_shotgun(self, start, aim, 2, 1, DEFAULT_SHOTGUN_HSPREAD, - DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SHOTGUN_COUNT, flash_index); + monster_fire_shotgun(self, start, aim, 2, 1, + DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, + DEFAULT_SHOTGUN_COUNT, flash_index); } else { @@ -706,8 +783,9 @@ soldier_fire(edict_t *self, int in_flash_number) self->wait = level.time + (3 + rand() % 8) * FRAMETIME; } - monster_fire_bullet(self, start, aim, 2, 4, DEFAULT_BULLET_HSPREAD, - DEFAULT_BULLET_VSPREAD, flash_index); + monster_fire_bullet(self, start, aim, 2, 4, + DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, + flash_index); if (level.time >= self->wait) { @@ -720,6 +798,7 @@ soldier_fire(edict_t *self, int in_flash_number) } } +/* ATTACK1 (blaster/shotgun) */ void soldier_fire1(edict_t *self) { @@ -760,7 +839,8 @@ soldier_attack1_refire1(edict_t *self) return; } - if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) { self->monsterinfo.nextframe = FRAME_attak102; } @@ -793,7 +873,8 @@ soldier_attack1_refire2(edict_t *self) return; } - if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) { self->monsterinfo.nextframe = FRAME_attak102; } @@ -814,13 +895,15 @@ static mframe_t soldier_frames_attack1[] = { {ai_charge, 0, NULL} }; -mmove_t soldier_move_attack1 = { +mmove_t soldier_move_attack1 = +{ FRAME_attak101, FRAME_attak112, soldier_frames_attack1, soldier_run }; +/* ATTACK2 (blaster/shotgun) */ void soldier_fire2(edict_t *self) { @@ -855,7 +938,8 @@ soldier_attack2_refire1(edict_t *self) return; } - if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) { self->monsterinfo.nextframe = FRAME_attak204; } @@ -888,7 +972,8 @@ soldier_attack2_refire2(edict_t *self) return; } - if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) { self->monsterinfo.nextframe = FRAME_attak204; } @@ -915,13 +1000,49 @@ static mframe_t soldier_frames_attack2[] = { {ai_charge, 0, NULL} }; -mmove_t soldier_move_attack2 = { +mmove_t soldier_move_attack2 = +{ FRAME_attak201, FRAME_attak218, soldier_frames_attack2, soldier_run }; +/* ATTACK3 (duck and shoot) */ +void +soldier_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +soldier_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + void soldier_fire3(edict_t *self) { @@ -930,7 +1051,7 @@ soldier_fire3(edict_t *self) return; } - monster_duck_down(self); + soldier_duck_down(self); soldier_fire(self, 2); } @@ -960,13 +1081,15 @@ static mframe_t soldier_frames_attack3[] = { {ai_charge, 0, NULL} }; -mmove_t soldier_move_attack3 = { +mmove_t soldier_move_attack3 = +{ FRAME_attak301, FRAME_attak309, soldier_frames_attack3, soldier_run }; +/* ATTACK4 (machinegun) */ void soldier_fire4(edict_t *self) { @@ -980,20 +1103,22 @@ soldier_fire4(edict_t *self) static mframe_t soldier_frames_attack4[] = { {ai_charge, 0, NULL}, - {ai_charge, 0, NULL}, + {ai_charge, 0, soldier_footstep}, {ai_charge, 0, soldier_fire4}, {ai_charge, 0, NULL}, {ai_charge, 0, NULL}, - {ai_charge, 0, NULL} + {ai_charge, 0, soldier_footstep} }; -mmove_t soldier_move_attack4 = { +mmove_t soldier_move_attack4 = +{ FRAME_attak401, FRAME_attak406, soldier_frames_attack4, soldier_run }; +/* ATTACK6 (run & shoot) */ void soldier_fire8(edict_t *self) { @@ -1055,7 +1180,8 @@ static mframe_t soldier_frames_attack6[] = { {ai_run, 17, soldier_attack6_refire} }; -mmove_t soldier_move_attack6 = { +mmove_t soldier_move_attack6 = +{ FRAME_runs01, FRAME_runs14, soldier_frames_attack6, @@ -1172,15 +1298,34 @@ soldier_sight(edict_t *self, edict_t *other /* unused */) } } +void +soldier_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + static mframe_t soldier_frames_duck[] = { - {ai_move, 5, monster_duck_down}, - {ai_move, -1, monster_duck_hold}, + {ai_move, 5, soldier_duck_down}, + {ai_move, -1, soldier_duck_hold}, {ai_move, 1, NULL}, - {ai_move, 0, monster_duck_up}, + {ai_move, 0, soldier_duck_up}, {ai_move, 5, NULL} }; -mmove_t soldier_move_duck = { +mmove_t soldier_move_duck = +{ FRAME_duck01, FRAME_duck05, soldier_frames_duck, @@ -1210,6 +1355,70 @@ soldier_blocked(edict_t *self, float dist) return false; } +void +soldier_dodge(edict_t *self, edict_t *attacker, float eta, + trace_t *tr /* unused */) +{ + float r; + + if (!self || !attacker) + { + return; + } + + r = random(); + + if (r > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + FoundTarget(self); + } + + if (skill->value == SKILL_EASY) + { + self->monsterinfo.currentmove = &soldier_move_duck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == SKILL_MEDIUM) + { + if (r > 0.33) + { + self->monsterinfo.currentmove = &soldier_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack3; + } + + return; + } + + if (skill->value >= SKILL_HARD) + { + if (r > 0.66) + { + self->monsterinfo.currentmove = &soldier_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldier_move_attack3; + } + + return; + } + + self->monsterinfo.currentmove = &soldier_move_attack3; +} + void soldier_fire6(edict_t *self) { @@ -1330,7 +1539,8 @@ static mframe_t soldier_frames_death1[] = { {ai_move, 0, NULL} }; -mmove_t soldier_move_death1 = { +mmove_t soldier_move_death1 = +{ FRAME_death101, FRAME_death136, soldier_frames_death1, @@ -1378,7 +1588,8 @@ static mframe_t soldier_frames_death2[] = { {ai_move, 0, NULL} }; -mmove_t soldier_move_death2 = { +mmove_t soldier_move_death2 = +{ FRAME_death201, FRAME_death235, soldier_frames_death2, @@ -1437,7 +1648,8 @@ static mframe_t soldier_frames_death3[] = { {ai_move, 0, NULL}, }; -mmove_t soldier_move_death3 = { +mmove_t soldier_move_death3 = +{ FRAME_death301, FRAME_death345, soldier_frames_death3, @@ -1505,7 +1717,8 @@ static mframe_t soldier_frames_death4[] = { {ai_move, 0, NULL} }; -mmove_t soldier_move_death4 = { +mmove_t soldier_move_death4 = +{ FRAME_death401, FRAME_death453, soldier_frames_death4, @@ -1541,7 +1754,8 @@ static mframe_t soldier_frames_death5[] = { {ai_move, 0, NULL} }; -mmove_t soldier_move_death5 = { +mmove_t soldier_move_death5 = +{ FRAME_death501, FRAME_death524, soldier_frames_death5, @@ -1561,7 +1775,8 @@ static mframe_t soldier_frames_death6[] = { {ai_move, 0, NULL} }; -mmove_t soldier_move_death6 = { +mmove_t soldier_move_death6 = +{ FRAME_death601, FRAME_death610, soldier_frames_death6, @@ -1569,8 +1784,9 @@ mmove_t soldier_move_death6 = { }; void -soldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* unused */, - int damage, vec3_t point /* unused */) +soldier_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, + vec3_t point /* unused */) { int n; @@ -1586,11 +1802,14 @@ soldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* for (n = 0; n < 3; n++) { - ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", + damage, GIB_ORGANIC); } - ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); - ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + ThrowGib(self, "models/objects/gibs/chest/tris.md2", + damage, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/head2/tris.md2", + damage, GIB_ORGANIC); self->deadflag = DEAD_DEAD; return; } @@ -1625,7 +1844,7 @@ soldier_die(edict_t *self, edict_t *inflictor /* unused */, edict_t *attacker /* return; } - n = rand() % 5; + n = randk() % 5; if (n == 0) { @@ -1768,6 +1987,13 @@ SP_monster_soldier_x(edict_t *self) return; } + // Force recaching at next footstep to ensure + // that the sound indices are correct. + sound_step = 0; + sound_step2 = 0; + sound_step3 = 0; + sound_step4 = 0; + self->s.modelindex = gi.modelindex("models/monsters/soldier/tris.md2"); self->monsterinfo.scale = MODEL_SCALE; VectorSet(self->mins, -16, -16, -24); @@ -1788,7 +2014,7 @@ SP_monster_soldier_x(edict_t *self) self->monsterinfo.stand = soldier_stand; self->monsterinfo.walk = soldier_walk; self->monsterinfo.run = soldier_run; - self->monsterinfo.dodge = M_MonsterDodge; + self->monsterinfo.dodge = soldier_dodge; self->monsterinfo.attack = soldier_attack; self->monsterinfo.melee = NULL; self->monsterinfo.sight = soldier_sight; @@ -1836,6 +2062,10 @@ SP_monster_soldier_light(edict_t *self) sound_pain_light = gi.soundindex("soldier/solpain2.wav"); sound_death_light = gi.soundindex("soldier/soldeth2.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); gi.modelindex("models/objects/laser/tris.md2"); gi.soundindex("misc/lasfly.wav"); gi.soundindex("soldier/solatck2.wav"); @@ -1870,6 +2100,10 @@ SP_monster_soldier(edict_t *self) sound_pain = gi.soundindex("soldier/solpain1.wav"); sound_death = gi.soundindex("soldier/soldeth1.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); gi.soundindex("soldier/solatck1.wav"); self->s.skinnum = 2; @@ -1901,6 +2135,1756 @@ SP_monster_soldier_ss(edict_t *self) sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + gi.soundindex("soldier/solatck3.wav"); + + self->s.skinnum = 4; +} + +void +soldierh_idle(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() > 0.8) + { + gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0); + } +} + +void +soldierh_cock(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.frame == FRAME_stand322) + { + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_IDLE, 0); + } + else + { + gi.sound(self, CHAN_WEAPON, sound_cock, 1, ATTN_NORM, 0); + } +} + +static mframe_t soldierh_frames_stand1[] = { + {ai_stand, 0, soldierh_idle}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL} +}; + +mmove_t soldierh_move_stand1 = { + FRAME_stand101, + FRAME_stand130, + soldierh_frames_stand1, + soldierh_stand +}; + +static mframe_t soldierh_frames_stand3[] = { + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + + {ai_stand, 0, NULL}, + {ai_stand, 0, soldierh_cock}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL}, + {ai_stand, 0, NULL} +}; + +mmove_t soldierh_move_stand3 = { + FRAME_stand301, + FRAME_stand339, + soldierh_frames_stand3, + soldierh_stand +}; + +void +soldierh_stand(edict_t *self) +{ + if (!self) + { + return; + } + + if ((self->monsterinfo.currentmove == &soldierh_move_stand3) || (random() < 0.8)) + { + self->monsterinfo.currentmove = &soldierh_move_stand1; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_stand3; + } +} + +void +soldierh_walk1_random(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() > 0.1) + { + self->monsterinfo.nextframe = FRAME_walk101; + } +} + +static mframe_t soldierh_frames_walk1[] = { + {ai_walk, 3, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 2, NULL}, + {ai_walk, 1, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, -1, soldierh_walk1_random}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL}, + {ai_walk, 0, NULL} +}; + +mmove_t soldierh_move_walk1 = { + FRAME_walk101, + FRAME_walk133, + soldierh_frames_walk1, + NULL +}; + +static mframe_t soldierh_frames_walk2[] = { + {ai_walk, 4, NULL}, + {ai_walk, 4, NULL}, + {ai_walk, 9, NULL}, + {ai_walk, 8, NULL}, + {ai_walk, 5, NULL}, + {ai_walk, 1, NULL}, + {ai_walk, 3, NULL}, + {ai_walk, 7, NULL}, + {ai_walk, 6, NULL}, + {ai_walk, 7, NULL} +}; + +mmove_t soldierh_move_walk2 = { + FRAME_walk209, + FRAME_walk218, + soldierh_frames_walk2, + NULL +}; + +void +soldierh_walk(edict_t *self) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + self->monsterinfo.currentmove = &soldierh_move_walk1; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_walk2; + } +} + +static mframe_t soldierh_frames_start_run[] = { + {ai_run, 7, NULL}, + {ai_run, 5, NULL} +}; + +mmove_t soldierh_move_start_run = { + FRAME_run01, + FRAME_run02, + soldierh_frames_start_run, + soldierh_run +}; + +static mframe_t soldierh_frames_run[] = { + {ai_run, 10, NULL}, + {ai_run, 11, NULL}, + {ai_run, 11, NULL}, + {ai_run, 16, NULL}, + {ai_run, 10, NULL}, + {ai_run, 15, NULL} +}; + +mmove_t soldierh_move_run = { + FRAME_run03, + FRAME_run08, + soldierh_frames_run, + NULL +}; + +void +soldierh_run(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_STAND_GROUND) + { + self->monsterinfo.currentmove = &soldierh_move_stand1; + return; + } + + if ((self->monsterinfo.currentmove == &soldierh_move_walk1) || + (self->monsterinfo.currentmove == &soldierh_move_walk2) || + (self->monsterinfo.currentmove == &soldierh_move_start_run)) + { + self->monsterinfo.currentmove = &soldierh_move_run; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_start_run; + } +} + +static mframe_t soldierh_frames_pain1[] = { + {ai_move, -3, NULL}, + {ai_move, 4, NULL}, + {ai_move, 1, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_pain1 = { + FRAME_pain101, + FRAME_pain105, + soldierh_frames_pain1, + soldierh_run +}; + +static mframe_t soldierh_frames_pain2[] = { + {ai_move, -13, NULL}, + {ai_move, -1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 4, NULL}, + {ai_move, 2, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL} +}; + +mmove_t soldierh_move_pain2 = { + FRAME_pain201, + FRAME_pain207, + soldierh_frames_pain2, + soldierh_run +}; + +static mframe_t soldierh_frames_pain3[] = { + {ai_move, -8, NULL}, + {ai_move, 10, NULL}, + {ai_move, -4, NULL}, + {ai_move, -1, NULL}, + {ai_move, -3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 3, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 1, NULL}, + {ai_move, 2, NULL}, + {ai_move, 4, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL} +}; + +mmove_t soldierh_move_pain3 = { + FRAME_pain301, + FRAME_pain318, + soldierh_frames_pain3, + soldierh_run +}; + +static mframe_t soldierh_frames_pain4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, -6, NULL}, + {ai_move, 8, NULL}, + {ai_move, 4, NULL}, + {ai_move, 1, NULL}, + {ai_move, 0, NULL}, + {ai_move, 2, NULL}, + {ai_move, 5, NULL}, + {ai_move, 2, NULL}, + {ai_move, -1, NULL}, + {ai_move, -1, NULL}, + {ai_move, 3, NULL}, + {ai_move, 2, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_pain4 = { + FRAME_pain401, + FRAME_pain417, + soldierh_frames_pain4, + soldierh_run +}; + +void +soldierh_pain(edict_t *self, edict_t *other /* unused */, + float kick /* unused */, int damage /* unused */) +{ + float r; + int n; + + if (!self) + { + return; + } + + if (self->health < (self->max_health / 2)) + { + self->s.skinnum |= 1; + } + + if (level.time < self->pain_debounce_time) + { + if ((self->velocity[2] > 100) && + ((self->monsterinfo.currentmove == &soldierh_move_pain1) || + (self->monsterinfo.currentmove == &soldierh_move_pain2) || + (self->monsterinfo.currentmove == &soldierh_move_pain3))) + { + self->monsterinfo.currentmove = &soldierh_move_pain4; + } + + return; + } + + self->pain_debounce_time = level.time + 3; + + n = self->s.skinnum | 1; + + if (n == 1) + { + gi.sound(self, CHAN_VOICE, sound_pain_light, 1, ATTN_NORM, 0); + } + else if (n == 3) + { + gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_pain_ss, 1, ATTN_NORM, 0); + } + + if (self->velocity[2] > 100) + { + self->monsterinfo.currentmove = &soldierh_move_pain4; + return; + } + + if (skill->value == SKILL_HARDPLUS) + { + return; /* no pain anims in nightmare */ + } + + r = random(); + + if (r < 0.33) + { + self->monsterinfo.currentmove = &soldierh_move_pain1; + } + else if (r < 0.66) + { + self->monsterinfo.currentmove = &soldierh_move_pain2; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_pain3; + } +} + +void +soldierh_laserbeam(edict_t *self, int flash_index) +{ + vec3_t forward, right, up; + vec3_t tempang, start; + vec3_t dir, angles, end; + vec3_t tempvec; + edict_t *ent; + + if (!self) + { + return; + } + + if (random() > 0.8) + { + gi.sound(self, CHAN_AUTO, gi.soundindex("misc/lasfly.wav"), 1, ATTN_STATIC, 0); + } + + VectorCopy(self->s.origin, start); + VectorCopy(self->enemy->s.origin, end); + VectorSubtract(end, start, dir); + vectoangles(dir, angles); + VectorCopy(monster_flash_offset[flash_index], tempvec); + + ent = G_Spawn(); + VectorCopy(self->s.origin, ent->s.origin); + VectorCopy(angles, tempang); + AngleVectors(tempang, forward, right, up); + VectorCopy(tempang, ent->s.angles); + VectorCopy(ent->s.origin, start); + + if (flash_index == 85) + { + VectorMA(start, tempvec[0] - 14, right, start); + VectorMA(start, tempvec[2] + 8, up, start); + VectorMA(start, tempvec[1], forward, start); + } + else + { + VectorMA(start, tempvec[0] + 2, right, start); + VectorMA(start, tempvec[2] + 8, up, start); + VectorMA(start, tempvec[1], forward, start); + } + + VectorCopy(start, ent->s.origin); + ent->enemy = self->enemy; + ent->owner = self; + + ent->dmg = 1; + + monster_dabeam(ent); +} + +void +soldierh_fire(edict_t *self, int flash_number) +{ + vec3_t start; + vec3_t forward, right, up; + vec3_t aim; + vec3_t dir; + vec3_t end; + float r, u; + int flash_index; + + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + flash_index = blaster_flash[flash_number]; /* ripper */ + } + else if (self->s.skinnum < 4) + { + flash_index = blaster_flash[flash_number]; /* hyperblaster */ + } + else + { + flash_index = machinegun_flash[flash_number]; /* laserbeam */ + } + + AngleVectors(self->s.angles, forward, right, NULL); + G_ProjectSource(self->s.origin, monster_flash_offset[flash_index], + forward, right, start); + + if ((flash_number == 5) || (flash_number == 6)) + { + VectorCopy(forward, aim); + } + else + { + VectorCopy(self->enemy->s.origin, end); + end[2] += self->enemy->viewheight; + VectorSubtract(end, start, aim); + vectoangles(aim, dir); + AngleVectors(dir, forward, right, up); + + r = crandom() * 100; + u = crandom() * 50; + VectorMA(start, 8192, forward, end); + VectorMA(end, r, right, end); + VectorMA(end, u, up, end); + + VectorSubtract(end, start, aim); + VectorNormalize(aim); + } + + if (self->s.skinnum <= 1) + { + monster_fire_ionripper(self, start, aim, 5, 600, + flash_index, EF_IONRIPPER); + } + else if (self->s.skinnum <= 3) + { + monster_fire_blueblaster(self, start, aim, 1, 600, + MZ_BLUEHYPERBLASTER, EF_BLUEHYPERBLASTER); + } + else + { + if (!(self->monsterinfo.aiflags & AI_HOLD_FRAME)) + { + self->monsterinfo.pausetime = level.time + (3 + rand() % 8) * FRAMETIME; + } + + soldierh_laserbeam(self, flash_index); + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } + } +} + +void +soldierh_hyper_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + else if (self->s.skinnum < 4) + { + if (random() < 0.7) + { + self->s.frame = FRAME_attak103; + } + else + { + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + } + } +} + +void +soldierh_ripper1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + soldierh_fire(self, 0); + } + else if (self->s.skinnum < 4) + { + soldierh_fire(self, 0); + } +} + +void +soldierh_fire1(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 0); +} + +void +soldierh_attack1_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum > 1) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak102; + } + else + { + self->monsterinfo.nextframe = FRAME_attak110; + } +} + +void +soldierh_attack1_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak102; + } +} + +void +soldierh_hyper_sound(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + else if (self->s.skinnum < 4) + { + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbl1a.wav"), 1, ATTN_NORM, 0); + } + else + { + return; + } +} + +static mframe_t soldierh_frames_attack1[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_hyper_sound}, + {ai_charge, 0, soldierh_fire1}, + {ai_charge, 0, soldierh_ripper1}, + {ai_charge, 0, soldierh_ripper1}, + {ai_charge, 0, soldierh_attack1_refire1}, + {ai_charge, 0, soldierh_hyper_refire1}, + {ai_charge, 0, soldierh_cock}, + {ai_charge, 0, soldierh_attack1_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack1 = { + FRAME_attak101, + FRAME_attak112, + soldierh_frames_attack1, + soldierh_run +}; + +void +soldierh_hyper_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + else if (self->s.skinnum < 4) + { + if (random() < 0.7) + { + self->s.frame = FRAME_attak205; + } + else + { + gi.sound(self, CHAN_AUTO, gi.soundindex("weapons/hyprbd1a.wav"), 1, ATTN_NORM, 0); + } + } +} + +void +soldierh_ripper2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + soldierh_fire(self, 1); + } + else if (self->s.skinnum < 4) + { + soldierh_fire(self, 1); + } +} + +void +soldierh_fire2(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 1); +} + +void +soldierh_attack2_refire1(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum > 1) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && (random() < 0.5)) || (range(self, self->enemy) == RANGE_MELEE)) + { + self->monsterinfo.nextframe = FRAME_attak204; + } + else + { + self->monsterinfo.nextframe = FRAME_attak216; + } +} + +void +soldierh_attack2_refire2(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 2) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (((skill->value == SKILL_HARDPLUS) && + (random() < 0.5)) || + ((range(self, self->enemy) == RANGE_MELEE) && (self->s.skinnum < 4))) + { + self->monsterinfo.nextframe = FRAME_attak204; + } +} + +static mframe_t soldierh_frames_attack2[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_hyper_sound}, + {ai_charge, 0, soldierh_fire2}, + {ai_charge, 0, soldierh_ripper2}, + {ai_charge, 0, soldierh_ripper2}, + {ai_charge, 0, soldierh_attack2_refire1}, + {ai_charge, 0, soldierh_hyper_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_cock}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_attack2_refire2}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack2 = { + FRAME_attak201, + FRAME_attak218, + soldierh_frames_attack2, + soldierh_run +}; + +void +soldierh_duck_down(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->monsterinfo.aiflags & AI_DUCKED) + { + return; + } + + self->monsterinfo.aiflags |= AI_DUCKED; + self->maxs[2] -= 32; + self->takedamage = DAMAGE_YES; + self->monsterinfo.pausetime = level.time + 1; + gi.linkentity(self); +} + +void +soldierh_duck_up(edict_t *self) +{ + if (!self) + { + return; + } + + self->monsterinfo.aiflags &= ~AI_DUCKED; + self->maxs[2] += 32; + self->takedamage = DAMAGE_AIM; + gi.linkentity(self); +} + +void +soldierh_fire3(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_duck_down(self); + soldierh_fire(self, 2); +} + +void +soldierh_attack3_refire(edict_t *self) +{ + if (!self) + { + return; + } + + if ((level.time + 0.4) < self->monsterinfo.pausetime) + { + self->monsterinfo.nextframe = FRAME_attak303; + } +} + +static mframe_t soldierh_frames_attack3[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_fire3}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_attack3_refire}, + {ai_charge, 0, soldierh_duck_up}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack3 = { + FRAME_attak301, + FRAME_attak309, + soldierh_frames_attack3, + soldierh_run +}; + +void +soldierh_fire4(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 3); +} + +static mframe_t soldierh_frames_attack4[] = { + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, soldierh_fire4}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL}, + {ai_charge, 0, NULL} +}; + +mmove_t soldierh_move_attack4 = { + FRAME_attak401, + FRAME_attak406, + soldierh_frames_attack4, + soldierh_run +}; + +void +soldierh_fire8(edict_t *self) +{ + if (!self) + { + return; + } + + soldierh_fire(self, 7); +} + +void +soldierh_attack6_refire(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->enemy->health <= 0) + { + return; + } + + if (range(self, self->enemy) < RANGE_MID) + { + return; + } + + if (skill->value == SKILL_HARDPLUS) + { + self->monsterinfo.nextframe = FRAME_runs03; + } +} + +static mframe_t soldierh_frames_attack6[] = { + {ai_charge, 10, NULL}, + {ai_charge, 4, NULL}, + {ai_charge, 12, NULL}, + {ai_charge, 11, soldierh_fire8}, + {ai_charge, 13, NULL}, + {ai_charge, 18, NULL}, + {ai_charge, 15, NULL}, + {ai_charge, 14, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, 8, NULL}, + {ai_charge, 11, NULL}, + {ai_charge, 12, NULL}, + {ai_charge, 12, NULL}, + {ai_charge, 17, soldierh_attack6_refire} +}; + +mmove_t soldierh_move_attack6 = { + FRAME_runs01, + FRAME_runs14, + soldierh_frames_attack6, + soldierh_run +}; + +void +soldierh_attack(edict_t *self) +{ + if (!self) + { + return; + } + + if (self->s.skinnum < 4) + { + if (random() < 0.5) + { + self->monsterinfo.currentmove = &soldierh_move_attack1; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack2; + } + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack4; + } +} + +void +soldierh_sight(edict_t *self, edict_t *other /* unused */) +{ + if (!self) + { + return; + } + + if (random() < 0.5) + { + gi.sound(self, CHAN_VOICE, sound_sight1, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_sight2, 1, ATTN_NORM, 0); + } + + if ((skill->value > SKILL_EASY) && (range(self, self->enemy) >= RANGE_MID)) + { + if (random() > 0.5) + { + if (self->s.skinnum < 4) + { + self->monsterinfo.currentmove = &soldierh_move_attack6; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack4; + } + } + } +} + +void +soldierh_duck_hold(edict_t *self) +{ + if (!self) + { + return; + } + + if (level.time >= self->monsterinfo.pausetime) + { + self->monsterinfo.aiflags &= ~AI_HOLD_FRAME; + } + else + { + self->monsterinfo.aiflags |= AI_HOLD_FRAME; + } +} + +static mframe_t soldierh_frames_duck[] = { + {ai_move, 5, soldierh_duck_down}, + {ai_move, -1, soldierh_duck_hold}, + {ai_move, 1, NULL}, + {ai_move, 0, soldierh_duck_up}, + {ai_move, 5, NULL} +}; + +mmove_t soldierh_move_duck = { + FRAME_duck01, + FRAME_duck05, + soldierh_frames_duck, + soldierh_run +}; + +void +soldierh_dodge(edict_t *self, edict_t *attacker, float eta, + trace_t *tr /* unused */) +{ + float r; + + if (!self || !attacker) + { + return; + } + + r = random(); + + if (r > 0.25) + { + return; + } + + if (!self->enemy) + { + self->enemy = attacker; + } + + if (skill->value == SKILL_EASY) + { + self->monsterinfo.currentmove = &soldierh_move_duck; + return; + } + + self->monsterinfo.pausetime = level.time + eta + 0.3; + r = random(); + + if (skill->value == SKILL_MEDIUM) + { + if (r > 0.33) + { + self->monsterinfo.currentmove = &soldierh_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack3; + } + + return; + } + + if (skill->value >= SKILL_HARD) + { + if (r > 0.66) + { + self->monsterinfo.currentmove = &soldierh_move_duck; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_attack3; + } + + return; + } + + self->monsterinfo.currentmove = &soldierh_move_attack3; +} + +void +soldierh_fire6(edict_t *self) +{ + if (!self) + { + return; + } + + /* no fire laser */ + if (self->s.skinnum < 4) + { + soldierh_fire(self, 5); + } +} + +void +soldierh_fire7(edict_t *self) +{ + if (!self) + { + return; + } + + /* no fire laser */ + if (self->s.skinnum < 4) + { + soldierh_fire(self, 6); + } +} + +void +soldierh_dead(edict_t *self) +{ + if (!self) + { + return; + } + + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, -8); + self->movetype = MOVETYPE_TOSS; + self->svflags |= SVF_DEADMONSTER; + self->nextthink = 0; + gi.linkentity(self); +} + +static mframe_t soldierh_frames_death1[] = { + {ai_move, 0, NULL}, + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -10, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, soldierh_fire6}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, soldierh_fire7}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death1 = { + FRAME_death101, + FRAME_death136, + soldierh_frames_death1, + soldierh_dead +}; + +static mframe_t soldierh_frames_death2[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death2 = { + FRAME_death201, + FRAME_death235, + soldierh_frames_death2, + soldierh_dead +}; + +static mframe_t soldierh_frames_death3[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, +}; + +mmove_t soldierh_move_death3 = { + FRAME_death301, + FRAME_death345, + soldierh_frames_death3, + soldierh_dead +}; + +static mframe_t soldierh_frames_death4[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death4 = { + FRAME_death401, + FRAME_death453, + soldierh_frames_death4, + soldierh_dead +}; + +static mframe_t soldierh_frames_death5[] = { + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, -5, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death5 = { + FRAME_death501, + FRAME_death524, + soldierh_frames_death5, + soldierh_dead +}; + +static mframe_t soldierh_frames_death6[] = { + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL}, + {ai_move, 0, NULL} +}; + +mmove_t soldierh_move_death6 = { + FRAME_death601, + FRAME_death610, + soldierh_frames_death6, + soldierh_dead +}; + +void +soldierh_die(edict_t *self, edict_t *inflictor /* unused */, + edict_t *attacker /* unused */, int damage, vec3_t point) +{ + int n; + + if (!self) + { + return; + } + + /* check for gib */ + if (self->health <= self->gib_health) + { + gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0); + + for (n = 0; n < 3; n++) + { + ThrowGib(self, "models/objects/gibs/sm_meat/tris.md2", damage, GIB_ORGANIC); + } + + ThrowGib(self, "models/objects/gibs/chest/tris.md2", damage, GIB_ORGANIC); + ThrowHead(self, "models/objects/gibs/head2/tris.md2", damage, GIB_ORGANIC); + + self->deadflag = DEAD_DEAD; + return; + } + + if (self->deadflag == DEAD_DEAD) + { + return; + } + + /* regular death */ + self->deadflag = DEAD_DEAD; + self->takedamage = DAMAGE_YES; + self->s.skinnum |= 1; + + if (self->s.skinnum == 1) + { + gi.sound(self, CHAN_VOICE, sound_death_light, 1, ATTN_NORM, 0); + } + else if (self->s.skinnum == 3) + { + gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0); + } + else + { + gi.sound(self, CHAN_VOICE, sound_death_ss, 1, ATTN_NORM, 0); + } + + if (fabs((self->s.origin[2] + self->viewheight) - point[2]) <= 4) + { + /* head shot */ + self->monsterinfo.currentmove = &soldierh_move_death3; + return; + } + + n = (self->s.skinnum < 4) ? (rand() % 5) : (1 + (rand() % 4)); + + if (n == 0) + { + self->monsterinfo.currentmove = &soldierh_move_death1; + } + else if (n == 1) + { + self->monsterinfo.currentmove = &soldierh_move_death2; + } + else if (n == 2) + { + self->monsterinfo.currentmove = &soldierh_move_death4; + } + else if (n == 3) + { + self->monsterinfo.currentmove = &soldierh_move_death5; + } + else + { + self->monsterinfo.currentmove = &soldierh_move_death6; + } +} + +void +SP_monster_soldier_h(edict_t *self) +{ + if (!self) + { + return; + } + + self->s.modelindex = gi.modelindex("models/monsters/soldierh/tris.md2"); + self->monsterinfo.scale = MODEL_SCALE; + VectorSet(self->mins, -16, -16, -24); + VectorSet(self->maxs, 16, 16, 32); + self->movetype = MOVETYPE_STEP; + self->solid = SOLID_BBOX; + + sound_idle = gi.soundindex("soldier/solidle1.wav"); + sound_sight1 = gi.soundindex("soldier/solsght1.wav"); + sound_sight2 = gi.soundindex("soldier/solsrch1.wav"); + sound_cock = gi.soundindex("infantry/infatck3.wav"); + + self->mass = 100; + + self->pain = soldierh_pain; + self->die = soldierh_die; + + self->monsterinfo.stand = soldierh_stand; + self->monsterinfo.walk = soldierh_walk; + self->monsterinfo.run = soldierh_run; + self->monsterinfo.dodge = soldierh_dodge; + self->monsterinfo.attack = soldierh_attack; + self->monsterinfo.melee = NULL; + self->monsterinfo.sight = soldierh_sight; + + gi.linkentity(self); + + /* self->monsterinfo.stand (self); */ + self->monsterinfo.currentmove = &soldierh_move_stand3; + + walkmonster_start(self); +} + +/* + * QUAKED monster_soldier_ripper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_soldier_ripper(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 50; + self->gib_health = -30; + + SP_monster_soldier_h(self); + + sound_pain_light = gi.soundindex("soldier/solpain2.wav"); + sound_death_light = gi.soundindex("soldier/soldeth2.wav"); + + gi.modelindex("models/objects/boomrang/tris.md2"); + gi.soundindex("misc/lasfly.wav"); + gi.soundindex("soldier/solatck2.wav"); + + self->s.skinnum = 0; +} + +/* + * QUAKED monster_soldier_hypergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_soldier_hypergun(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 60; + self->gib_health = -30; + + SP_monster_soldier_h(self); + + gi.modelindex("models/objects/blaser/tris.md2"); + sound_pain = gi.soundindex("soldier/solpain1.wav"); + sound_death = gi.soundindex("soldier/soldeth1.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); + gi.soundindex("soldier/solatck1.wav"); + + self->s.skinnum = 2; +} + +/* + * QUAKED monster_soldier_lasergun (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight + */ +void +SP_monster_soldier_lasergun(edict_t *self) +{ + if (!self) + { + return; + } + + if (deathmatch->value) + { + G_FreeEdict(self); + return; + } + + self->health = 70; + self->gib_health = -30; + + SP_monster_soldier_h(self); + + sound_pain_ss = gi.soundindex("soldier/solpain3.wav"); + sound_death_ss = gi.soundindex("soldier/soldeth3.wav"); + sound_step = gi.soundindex("player/step1.wav"); + sound_step2 = gi.soundindex("player/step2.wav"); + sound_step3 = gi.soundindex("player/step3.wav"); + sound_step4 = gi.soundindex("player/step4.wav"); gi.soundindex("soldier/solatck3.wav"); self->s.skinnum = 4; diff --git a/src/rogue/monster/soldier/soldierh.h b/src/rogue/monster/soldier/soldierh.h new file mode 100644 index 00000000..acc54218 --- /dev/null +++ b/src/rogue/monster/soldier/soldierh.h @@ -0,0 +1,505 @@ +/* + * Copyright (C) 1997-2001 Id Software, Inc. + * Copyright (c) ZeniMax Media Inc. + * + * This program 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. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + * + * ======================================================================= + * + * Soldier aka "Guard" animations. This is the new model added in + * Xatrix, used for the new variants of the enemy. + * + * ======================================================================= + */ + +#define FRAME_attak101 0 +#define FRAME_attak102 1 +#define FRAME_attak103 2 +#define FRAME_attak104 3 +#define FRAME_attak105 4 +#define FRAME_attak106 5 +#define FRAME_attak107 6 +#define FRAME_attak108 7 +#define FRAME_attak109 8 +#define FRAME_attak110 9 +#define FRAME_attak111 10 +#define FRAME_attak112 11 +#define FRAME_attak201 12 +#define FRAME_attak202 13 +#define FRAME_attak203 14 +#define FRAME_attak204 15 +#define FRAME_attak205 16 +#define FRAME_attak206 17 +#define FRAME_attak207 18 +#define FRAME_attak208 19 +#define FRAME_attak209 20 +#define FRAME_attak210 21 +#define FRAME_attak211 22 +#define FRAME_attak212 23 +#define FRAME_attak213 24 +#define FRAME_attak214 25 +#define FRAME_attak215 26 +#define FRAME_attak216 27 +#define FRAME_attak217 28 +#define FRAME_attak218 29 +#define FRAME_attak301 30 +#define FRAME_attak302 31 +#define FRAME_attak303 32 +#define FRAME_attak304 33 +#define FRAME_attak305 34 +#define FRAME_attak306 35 +#define FRAME_attak307 36 +#define FRAME_attak308 37 +#define FRAME_attak309 38 +#define FRAME_attak401 39 +#define FRAME_attak402 40 +#define FRAME_attak403 41 +#define FRAME_attak404 42 +#define FRAME_attak405 43 +#define FRAME_attak406 44 +#define FRAME_duck01 45 +#define FRAME_duck02 46 +#define FRAME_duck03 47 +#define FRAME_duck04 48 +#define FRAME_duck05 49 +#define FRAME_pain101 50 +#define FRAME_pain102 51 +#define FRAME_pain103 52 +#define FRAME_pain104 53 +#define FRAME_pain105 54 +#define FRAME_pain201 55 +#define FRAME_pain202 56 +#define FRAME_pain203 57 +#define FRAME_pain204 58 +#define FRAME_pain205 59 +#define FRAME_pain206 60 +#define FRAME_pain207 61 +#define FRAME_pain301 62 +#define FRAME_pain302 63 +#define FRAME_pain303 64 +#define FRAME_pain304 65 +#define FRAME_pain305 66 +#define FRAME_pain306 67 +#define FRAME_pain307 68 +#define FRAME_pain308 69 +#define FRAME_pain309 70 +#define FRAME_pain310 71 +#define FRAME_pain311 72 +#define FRAME_pain312 73 +#define FRAME_pain313 74 +#define FRAME_pain314 75 +#define FRAME_pain315 76 +#define FRAME_pain316 77 +#define FRAME_pain317 78 +#define FRAME_pain318 79 +#define FRAME_pain401 80 +#define FRAME_pain402 81 +#define FRAME_pain403 82 +#define FRAME_pain404 83 +#define FRAME_pain405 84 +#define FRAME_pain406 85 +#define FRAME_pain407 86 +#define FRAME_pain408 87 +#define FRAME_pain409 88 +#define FRAME_pain410 89 +#define FRAME_pain411 90 +#define FRAME_pain412 91 +#define FRAME_pain413 92 +#define FRAME_pain414 93 +#define FRAME_pain415 94 +#define FRAME_pain416 95 +#define FRAME_pain417 96 +#define FRAME_run01 97 +#define FRAME_run02 98 +#define FRAME_run03 99 +#define FRAME_run04 100 +#define FRAME_run05 101 +#define FRAME_run06 102 +#define FRAME_run07 103 +#define FRAME_run08 104 +#define FRAME_run09 105 +#define FRAME_run10 106 +#define FRAME_run11 107 +#define FRAME_run12 108 +#define FRAME_runs01 109 +#define FRAME_runs02 110 +#define FRAME_runs03 111 +#define FRAME_runs04 112 +#define FRAME_runs05 113 +#define FRAME_runs06 114 +#define FRAME_runs07 115 +#define FRAME_runs08 116 +#define FRAME_runs09 117 +#define FRAME_runs10 118 +#define FRAME_runs11 119 +#define FRAME_runs12 120 +#define FRAME_runs13 121 +#define FRAME_runs14 122 +#define FRAME_runs15 123 +#define FRAME_runs16 124 +#define FRAME_runs17 125 +#define FRAME_runs18 126 +#define FRAME_runt01 127 +#define FRAME_runt02 128 +#define FRAME_runt03 129 +#define FRAME_runt04 130 +#define FRAME_runt05 131 +#define FRAME_runt06 132 +#define FRAME_runt07 133 +#define FRAME_runt08 134 +#define FRAME_runt09 135 +#define FRAME_runt10 136 +#define FRAME_runt11 137 +#define FRAME_runt12 138 +#define FRAME_runt13 139 +#define FRAME_runt14 140 +#define FRAME_runt15 141 +#define FRAME_runt16 142 +#define FRAME_runt17 143 +#define FRAME_runt18 144 +#define FRAME_runt19 145 +#define FRAME_stand101 146 +#define FRAME_stand102 147 +#define FRAME_stand103 148 +#define FRAME_stand104 149 +#define FRAME_stand105 150 +#define FRAME_stand106 151 +#define FRAME_stand107 152 +#define FRAME_stand108 153 +#define FRAME_stand109 154 +#define FRAME_stand110 155 +#define FRAME_stand111 156 +#define FRAME_stand112 157 +#define FRAME_stand113 158 +#define FRAME_stand114 159 +#define FRAME_stand115 160 +#define FRAME_stand116 161 +#define FRAME_stand117 162 +#define FRAME_stand118 163 +#define FRAME_stand119 164 +#define FRAME_stand120 165 +#define FRAME_stand121 166 +#define FRAME_stand122 167 +#define FRAME_stand123 168 +#define FRAME_stand124 169 +#define FRAME_stand125 170 +#define FRAME_stand126 171 +#define FRAME_stand127 172 +#define FRAME_stand128 173 +#define FRAME_stand129 174 +#define FRAME_stand130 175 +#define FRAME_stand301 176 +#define FRAME_stand302 177 +#define FRAME_stand303 178 +#define FRAME_stand304 179 +#define FRAME_stand305 180 +#define FRAME_stand306 181 +#define FRAME_stand307 182 +#define FRAME_stand308 183 +#define FRAME_stand309 184 +#define FRAME_stand310 185 +#define FRAME_stand311 186 +#define FRAME_stand312 187 +#define FRAME_stand313 188 +#define FRAME_stand314 189 +#define FRAME_stand315 190 +#define FRAME_stand316 191 +#define FRAME_stand317 192 +#define FRAME_stand318 193 +#define FRAME_stand319 194 +#define FRAME_stand320 195 +#define FRAME_stand321 196 +#define FRAME_stand322 197 +#define FRAME_stand323 198 +#define FRAME_stand324 199 +#define FRAME_stand325 200 +#define FRAME_stand326 201 +#define FRAME_stand327 202 +#define FRAME_stand328 203 +#define FRAME_stand329 204 +#define FRAME_stand330 205 +#define FRAME_stand331 206 +#define FRAME_stand332 207 +#define FRAME_stand333 208 +#define FRAME_stand334 209 +#define FRAME_stand335 210 +#define FRAME_stand336 211 +#define FRAME_stand337 212 +#define FRAME_stand338 213 +#define FRAME_stand339 214 +#define FRAME_walk101 215 +#define FRAME_walk102 216 +#define FRAME_walk103 217 +#define FRAME_walk104 218 +#define FRAME_walk105 219 +#define FRAME_walk106 220 +#define FRAME_walk107 221 +#define FRAME_walk108 222 +#define FRAME_walk109 223 +#define FRAME_walk110 224 +#define FRAME_walk111 225 +#define FRAME_walk112 226 +#define FRAME_walk113 227 +#define FRAME_walk114 228 +#define FRAME_walk115 229 +#define FRAME_walk116 230 +#define FRAME_walk117 231 +#define FRAME_walk118 232 +#define FRAME_walk119 233 +#define FRAME_walk120 234 +#define FRAME_walk121 235 +#define FRAME_walk122 236 +#define FRAME_walk123 237 +#define FRAME_walk124 238 +#define FRAME_walk125 239 +#define FRAME_walk126 240 +#define FRAME_walk127 241 +#define FRAME_walk128 242 +#define FRAME_walk129 243 +#define FRAME_walk130 244 +#define FRAME_walk131 245 +#define FRAME_walk132 246 +#define FRAME_walk133 247 +#define FRAME_walk201 248 +#define FRAME_walk202 249 +#define FRAME_walk203 250 +#define FRAME_walk204 251 +#define FRAME_walk205 252 +#define FRAME_walk206 253 +#define FRAME_walk207 254 +#define FRAME_walk208 255 +#define FRAME_walk209 256 +#define FRAME_walk210 257 +#define FRAME_walk211 258 +#define FRAME_walk212 259 +#define FRAME_walk213 260 +#define FRAME_walk214 261 +#define FRAME_walk215 262 +#define FRAME_walk216 263 +#define FRAME_walk217 264 +#define FRAME_walk218 265 +#define FRAME_walk219 266 +#define FRAME_walk220 267 +#define FRAME_walk221 268 +#define FRAME_walk222 269 +#define FRAME_walk223 270 +#define FRAME_walk224 271 +#define FRAME_death101 272 +#define FRAME_death102 273 +#define FRAME_death103 274 +#define FRAME_death104 275 +#define FRAME_death105 276 +#define FRAME_death106 277 +#define FRAME_death107 278 +#define FRAME_death108 279 +#define FRAME_death109 280 +#define FRAME_death110 281 +#define FRAME_death111 282 +#define FRAME_death112 283 +#define FRAME_death113 284 +#define FRAME_death114 285 +#define FRAME_death115 286 +#define FRAME_death116 287 +#define FRAME_death117 288 +#define FRAME_death118 289 +#define FRAME_death119 290 +#define FRAME_death120 291 +#define FRAME_death121 292 +#define FRAME_death122 293 +#define FRAME_death123 294 +#define FRAME_death124 295 +#define FRAME_death125 296 +#define FRAME_death126 297 +#define FRAME_death127 298 +#define FRAME_death128 299 +#define FRAME_death129 300 +#define FRAME_death130 301 +#define FRAME_death131 302 +#define FRAME_death132 303 +#define FRAME_death133 304 +#define FRAME_death134 305 +#define FRAME_death135 306 +#define FRAME_death136 307 +#define FRAME_death201 308 +#define FRAME_death202 309 +#define FRAME_death203 310 +#define FRAME_death204 311 +#define FRAME_death205 312 +#define FRAME_death206 313 +#define FRAME_death207 314 +#define FRAME_death208 315 +#define FRAME_death209 316 +#define FRAME_death210 317 +#define FRAME_death211 318 +#define FRAME_death212 319 +#define FRAME_death213 320 +#define FRAME_death214 321 +#define FRAME_death215 322 +#define FRAME_death216 323 +#define FRAME_death217 324 +#define FRAME_death218 325 +#define FRAME_death219 326 +#define FRAME_death220 327 +#define FRAME_death221 328 +#define FRAME_death222 329 +#define FRAME_death223 330 +#define FRAME_death224 331 +#define FRAME_death225 332 +#define FRAME_death226 333 +#define FRAME_death227 334 +#define FRAME_death228 335 +#define FRAME_death229 336 +#define FRAME_death230 337 +#define FRAME_death231 338 +#define FRAME_death232 339 +#define FRAME_death233 340 +#define FRAME_death234 341 +#define FRAME_death235 342 +#define FRAME_death301 343 +#define FRAME_death302 344 +#define FRAME_death303 345 +#define FRAME_death304 346 +#define FRAME_death305 347 +#define FRAME_death306 348 +#define FRAME_death307 349 +#define FRAME_death308 350 +#define FRAME_death309 351 +#define FRAME_death310 352 +#define FRAME_death311 353 +#define FRAME_death312 354 +#define FRAME_death313 355 +#define FRAME_death314 356 +#define FRAME_death315 357 +#define FRAME_death316 358 +#define FRAME_death317 359 +#define FRAME_death318 360 +#define FRAME_death319 361 +#define FRAME_death320 362 +#define FRAME_death321 363 +#define FRAME_death322 364 +#define FRAME_death323 365 +#define FRAME_death324 366 +#define FRAME_death325 367 +#define FRAME_death326 368 +#define FRAME_death327 369 +#define FRAME_death328 370 +#define FRAME_death329 371 +#define FRAME_death330 372 +#define FRAME_death331 373 +#define FRAME_death332 374 +#define FRAME_death333 375 +#define FRAME_death334 376 +#define FRAME_death335 377 +#define FRAME_death336 378 +#define FRAME_death337 379 +#define FRAME_death338 380 +#define FRAME_death339 381 +#define FRAME_death340 382 +#define FRAME_death341 383 +#define FRAME_death342 384 +#define FRAME_death343 385 +#define FRAME_death344 386 +#define FRAME_death345 387 +#define FRAME_death401 388 +#define FRAME_death402 389 +#define FRAME_death403 390 +#define FRAME_death404 391 +#define FRAME_death405 392 +#define FRAME_death406 393 +#define FRAME_death407 394 +#define FRAME_death408 395 +#define FRAME_death409 396 +#define FRAME_death410 397 +#define FRAME_death411 398 +#define FRAME_death412 399 +#define FRAME_death413 400 +#define FRAME_death414 401 +#define FRAME_death415 402 +#define FRAME_death416 403 +#define FRAME_death417 404 +#define FRAME_death418 405 +#define FRAME_death419 406 +#define FRAME_death420 407 +#define FRAME_death421 408 +#define FRAME_death422 409 +#define FRAME_death423 410 +#define FRAME_death424 411 +#define FRAME_death425 412 +#define FRAME_death426 413 +#define FRAME_death427 414 +#define FRAME_death428 415 +#define FRAME_death429 416 +#define FRAME_death430 417 +#define FRAME_death431 418 +#define FRAME_death432 419 +#define FRAME_death433 420 +#define FRAME_death434 421 +#define FRAME_death435 422 +#define FRAME_death436 423 +#define FRAME_death437 424 +#define FRAME_death438 425 +#define FRAME_death439 426 +#define FRAME_death440 427 +#define FRAME_death441 428 +#define FRAME_death442 429 +#define FRAME_death443 430 +#define FRAME_death444 431 +#define FRAME_death445 432 +#define FRAME_death446 433 +#define FRAME_death447 434 +#define FRAME_death448 435 +#define FRAME_death449 436 +#define FRAME_death450 437 +#define FRAME_death451 438 +#define FRAME_death452 439 +#define FRAME_death453 440 +#define FRAME_death501 441 +#define FRAME_death502 442 +#define FRAME_death503 443 +#define FRAME_death504 444 +#define FRAME_death505 445 +#define FRAME_death506 446 +#define FRAME_death507 447 +#define FRAME_death508 448 +#define FRAME_death509 449 +#define FRAME_death510 450 +#define FRAME_death511 451 +#define FRAME_death512 452 +#define FRAME_death513 453 +#define FRAME_death514 454 +#define FRAME_death515 455 +#define FRAME_death516 456 +#define FRAME_death517 457 +#define FRAME_death518 458 +#define FRAME_death519 459 +#define FRAME_death520 460 +#define FRAME_death521 461 +#define FRAME_death522 462 +#define FRAME_death523 463 +#define FRAME_death524 464 +#define FRAME_death601 465 +#define FRAME_death602 466 +#define FRAME_death603 467 +#define FRAME_death604 468 +#define FRAME_death605 469 +#define FRAME_death606 470 +#define FRAME_death607 471 +#define FRAME_death608 472 +#define FRAME_death609 473 +#define FRAME_death610 474 + +#define MODEL_SCALE 1.200000