From 34c6a65dadee77e04609fbb235720d2a274c72bd Mon Sep 17 00:00:00 2001 From: Denis Pauk Date: Wed, 6 Nov 2024 22:53:34 +0200 Subject: [PATCH] game: sync g_target.c --- Makefile | 3 +- src/game/g_field.c | 94 ---- src/game/g_target.c | 1090 ++++++++++++++++++++++++++++++++------- src/game/g_trigger.c | 277 +++++++++- src/game/g_utils.c | 13 +- src/game/header/local.h | 33 +- 6 files changed, 1211 insertions(+), 299 deletions(-) diff --git a/Makefile b/Makefile index 9df84aaee..31b60c20c 100644 --- a/Makefile +++ b/Makefile @@ -405,7 +405,7 @@ endif # ---------- -# Builds everything +# Builds everything but the GLES1 renderer all: config client server game ref_gl1 ref_gl3 ref_gles3 ref_soft ref_vk ref_gl4 player effects # ---------- @@ -1018,6 +1018,7 @@ GAME_OBJS_ = \ src/game/game_utilities.o \ src/game/g_breakable.o \ src/game/g_classstatics.o \ + src/game/g_chase.o \ src/game/g_cmds.o \ src/game/g_combat.o \ src/game/g_defaultmessagehandler.o \ diff --git a/src/game/g_field.c b/src/game/g_field.c index ac340f0e5..c33cafbeb 100644 --- a/src/game/g_field.c +++ b/src/game/g_field.c @@ -71,100 +71,6 @@ void SP_trigger_fogdensity(edict_t *self) } -//---------------------------------------------------------------------- -// Force Field -//---------------------------------------------------------------------- - -#define FIELD_FORCE_ONCE 1 - - -void push_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) -{ - vec3_t forward,up; - - if(other->health > 0) - { - if (other->client) // A player??? - { - // don't take falling damage immediately from this - VectorCopy(other->velocity, other->client->playerinfo.oldvelocity); - other->client->playerinfo.flags |= PLAYER_FLAG_USE_ENT_POS; - other->groundentity = NULL; - } - - AngleVectors(self->s.angles,forward,NULL,up); - - VectorMA(other->velocity,self->speed,forward,other->velocity); - VectorMA(other->velocity,self->speed,up,other->velocity); - - } - - G_UseTargets(self, self); - - if(self->spawnflags & FIELD_FORCE_ONCE) - { - G_FreeEdict (self); - } -} - -void push_touch_trigger(edict_t *self, edict_t *activator) -{ - push_touch(self,activator,NULL,NULL); -} - -void TrigPush_Deactivate(edict_t *self, G_Message_t *msg) -{ - self->solid = SOLID_NOT; - self->touch = NULL; -} - - -void TrigPush_Activate(edict_t *self, G_Message_t *msg) -{ - self->solid = SOLID_TRIGGER; - self->touch = push_touch; - gi.linkentity (self); -} - - -void TrigPushStaticsInit() -{ - classStatics[CID_TRIG_PUSH].msgReceivers[G_MSG_SUSPEND] = TrigPush_Deactivate; - classStatics[CID_TRIG_PUSH].msgReceivers[G_MSG_UNSUSPEND] = TrigPush_Activate; -} - -/*QUAKED trigger_push (.5 .5 .5) ? FORCE_ONCE -Pushes the player ----------KEYS---------- -speed - how fast the player is pushed (default 500) -angle - the angle to push the player along the X,Y -zangle - the up direction to push the player (0 is straight up, 180 is straight down) ------------------------------------- --------FLAGS--------------- -FORCE_ONCE - pushes once and then goes away ------------------------------------- -*/ -void SP_trigger_push(edict_t *self) -{ - InitTrigger(self); - self->solid = SOLID_TRIGGER; - self->msgHandler = DefaultMsgHandler; - self->classID = CID_TRIG_PUSH; - - if (!self->speed) - { - self->speed = 500; - } - - self->s.angles[2] = st.zangle; - - // Can't really use the normal trigger setup cause it doesn't update velocity often enough - self->touch = push_touch; - self->TriggerActivated = push_touch_trigger; -} - - - //---------------------------------------------------------------------- // Damage Field //---------------------------------------------------------------------- diff --git a/src/game/g_target.c b/src/game/g_target.c index af9f0c12e..f49ff0985 100644 --- a/src/game/g_target.c +++ b/src/game/g_target.c @@ -67,6 +67,334 @@ SP_target_temp_entity(edict_t *ent) /* ========================================================== */ +/* + * 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); +} + +/* ========================================================== */ + +static void +Target_Help_Apply(const char *msg, int is_primary) +{ + char *curr; + size_t sz; + + if (!msg) + { + return; + } + + if (is_primary) + { + curr = game.helpmessage1; + sz = sizeof (game.helpmessage1); + } + else + { + curr = game.helpmessage2; + sz = sizeof (game.helpmessage2); + } + + if (strcmp(curr, msg) == 0) + { + return; + } + + Q_strlcpy(curr, msg, sz - 1); + + game.helpchanged++; +} + +void +Target_Help_Think (edict_t *ent) +{ + if (!ent) + { + return; + } + + Target_Help_Apply(ent->message, ent->spawnflags & TARGET_HELP_PRIMARY); + ent->think = NULL; +} + +void +Use_Target_Help(edict_t *ent, edict_t *other /* unused */, edict_t *activator /* unused */) +{ + if (!ent) + { + return; + } + + if (level.time > TARGET_HELP_THINK_DELAY) + { + Target_Help_Apply(ent->message, ent->spawnflags & TARGET_HELP_PRIMARY); + } + else + { + /* The game is still pre-loading so delay the help message a bit, + otherwise its changes to game structure will leak past save loads + */ + ent->think = Target_Help_Think; + ent->nextthink = TARGET_HELP_THINK_DELAY; + } +} + +/* + * 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 /* acticator */) +{ + 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 quirk for mine3 */ + 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. @@ -135,7 +463,9 @@ SP_target_explosion(edict_t *ent) /* * QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8) -Changes map player is on. + * + * Changes level to "map" when fired + * Changes map player is on. map - the map to change to 'newmap'$'target' @@ -147,7 +477,7 @@ target is the targetname of the info_player_start to go to. If an info_player_start is not given a random one on the level is chosen -*/ + */ void use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator) { @@ -173,7 +503,8 @@ use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator) if (deathmatch->value && !((int)dmflags->value & DF_ALLOW_EXIT) && (other != world)) { - T_Damage(activator, self, self, vec3_origin, other->s.origin, vec3_origin, 10000, 10000, DAMAGE_AVOID_ARMOR,MOD_EXIT); + T_Damage(activator, self, self, vec3_origin, other->s.origin, + vec3_origin, 10000, 10000, DAMAGE_AVOID_ARMOR,MOD_EXIT); return; } @@ -182,7 +513,8 @@ use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator) { if (activator && activator->client) { - G_BroadcastObituary (PRINT_HIGH, GM_EXIT, activator->s.number, 0); + gi.bprintf(PRINT_HIGH, "%s exited the level.\n", + activator->client->pers.netname); } } @@ -200,12 +532,25 @@ use_target_changelevel(edict_t *self, edict_t *other, edict_t *activator) 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; } + + /* Mapquirk for secret exists in fact1 and fact3 */ + 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; } @@ -214,214 +559,296 @@ SP_target_changelevel(edict_t *ent) /* * 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, edict_t *activator) + * 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.CreateEffect(NULL, FX_SPLASH, 0, self->s.origin, "b", self->count); if (self->dmg) - T_DamageRadius(self, activator, NULL, self->dmg+40, - self->dmg, self->dmg/4, DAMAGE_NORMAL,MOD_DIED); + { + T_DamageRadius(self, activator, NULL, + self->dmg + 40, self->dmg, self->dmg / 4, DAMAGE_NORMAL,MOD_DIED); + } } -void SP_target_splash (edict_t *self) +void +SP_target_splash(edict_t *self) { + if (!self) + { + return; + } + self->use = use_target_splash; - G_SetMovedir (self->s.angles, self->movedir); + 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 ED_CallSpawn (edict_t *ent); + * 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, edict_t *activator) +void +use_target_spawner(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { - edict_t *ent; + 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); + 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); + { + VectorCopy(self->movedir, ent->velocity); + } } -void SP_target_spawner (edict_t *self) +void +SP_target_spawner(edict_t *self) { 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); + 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. + * Fires a blaster bolt in the set direction when triggered. + * + * dmg default is 15 + * speed default is 1000 + */ -dmg default is 15 -speed default is 1000 -*/ void -use_target_blaster(edict_t *self, edict_t *other, edict_t *activator) +use_target_blaster(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { - int effect; - - if (self->spawnflags & 2) - effect = 0; - else if (self->spawnflags & 1) - effect = EF_HYPERBLASTER; - else - effect = EF_BLASTER; + if (!self) + { + return; + } - // fire_blaster (self, self->s.origin, self->movedir, self->dmg, self->speed, EF_BLASTER); - gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0); + // fire_blaster(self, self->s.origin, self->movedir, self->dmg, self->speed, EF_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); + 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, edict_t *activator) + * + * 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) { + if (!self || !activator) + { + return; + } + game.serverflags |= self->spawnflags; - G_FreeEdict (self); + G_FreeEdict(self); } -void SP_target_crosslevel_trigger (edict_t *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) + * + * 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->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + if (!self) { - G_UseTargets (self, self); - G_FreeEdict (self); + return; + } + + if (self->spawnflags == + (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags)) + { + G_UseTargets(self, self); + G_FreeEdict(self); } self->nextthink = level.time + FRAMETIME; } -void SP_target_crosslevel_target (edict_t *self) +void +SP_target_crosslevel_target(edict_t *self) { - if (! self->delay) + 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 -When triggered, fires a laser. You can either set a target -or a direction. -*/ -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; - static vec3_t lmins = {-4, -4, -4}; - static vec3_t lmaxs = {4, 4, 4}; + * 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); + 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) + VectorCopy(self->s.origin, start); + VectorMA(start, 2048, self->movedir, end); + + while (1) { - tr = gi.trace (start, NULL, NULL, end, ignore, CONTENTS_SOLID|CONTENTS_MONSTER|CONTENTS_DEADMONSTER); + 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) && (tr.ent != self->owner)) @@ -433,130 +860,356 @@ void target_laser_think (edict_t *self) 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); + 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, start); } - VectorCopy (tr.endpos, self->s.old_origin); + VectorCopy(tr.endpos, self->s.old_origin); self->nextthink = level.time + FRAMETIME; } -void target_laser_on (edict_t *self) +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); + target_laser_think(self); } -void target_laser_off (edict_t *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, edict_t *activator) +void +target_laser_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { + if (!self || !activator) + { + return; + } + if (self->spawnflags & 1) - target_laser_off (self); + { + target_laser_off(self); + } else - target_laser_on (self); + { + target_laser_on(self); + } } -void target_laser_start (edict_t *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 + self->s.renderfx |= RF_BEAM | RF_TRANSLUCENT; + self->s.modelindex = 1; /* must be non-zero */ - // set the beam diameter + /* set the beam diameter */ if (self->spawnflags & 64) + { self->s.frame = 16; + } else + { self->s.frame = 4; + } - // set the color + /* 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->owner) + { self->owner = self; + } if (!self->enemy) { if (self->target) { - ent = G_Find (NULL, FOFS(targetname), 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); + { + 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); + 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); + VectorSet(self->mins, -8, -8, -8); + VectorSet(self->maxs, 8, 8, 8); + gi.linkentity(self); if (self->spawnflags & 1) - target_laser_on (self); + { + target_laser_on(self); + } else - target_laser_off (self); + { + target_laser_off(self); + } } -void SP_target_laser (edict_t *self) +void +SP_target_laser(edict_t *self) { - // let everything else get spawned before we start firing + if (!self) + { + return; + } + + /* let everything else get spawned before we start firing */ self->think = target_laser_start; self->nextthink = level.time + 1; } -//========================================================== +/* QUAKED target_mal_laser (1 0 0) (-4 -4 -4) (4 4 4) START_ON RED GREEN BLUE YELLOW ORANGE FAT + * Mal's laser + */ +void +target_mal_laser_on(edict_t *self) +{ + if (!self) + { + return; + } + + if (!self->activator) + { + self->activator = self; + } + + self->spawnflags |= 0x80000001; + self->svflags &= ~SVF_NOCLIENT; + self->nextthink = level.time + self->wait + self->delay; +} + +void +target_mal_laser_off(edict_t *self) +{ + if (!self) + { + return; + } + + self->spawnflags &= ~1; + self->svflags |= SVF_NOCLIENT; + self->nextthink = 0; +} + +void +target_mal_laser_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) +{ + if (!self || !activator) + { + return; + } + + self->activator = activator; + + if (self->spawnflags & 1) + { + target_mal_laser_off(self); + } + else + { + target_mal_laser_on(self); + } +} + +void +mal_laser_think(edict_t *self) +{ + if (!self) + { + return; + } + + target_laser_think(self); + self->nextthink = level.time + self->wait + 0.1; + self->spawnflags |= 0x80000000; +} + +void +SP_target_mal_laser(edict_t *self) +{ + 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; + } + + G_SetMovedir(self->s.angles, self->movedir); + + if (!self->delay) + { + self->delay = 0.1; + } + + if (!self->wait) + { + self->wait = 0.1; + } + + if (!self->dmg) + { + self->dmg = 5; + } + + VectorSet(self->mins, -8, -8, -8); + VectorSet(self->maxs, 8, 8, 8); + + self->nextthink = level.time + self->delay; + self->think = mal_laser_think; + + self->use = target_mal_laser_use; + + gi.linkentity(self); + + if (self->spawnflags & 1) + { + target_mal_laser_on(self); + } + else + { + target_mal_laser_off(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) +void +target_lightramp_think(edict_t *self) { - char style[2]; + char style[2]; + + if (!self) + { + return; + } - style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2]; + 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); + gi.configstring(CS_LIGHTS + self->enemy->style, style); if ((level.time - self->timestamp) < self->speed) { @@ -564,7 +1217,7 @@ void target_lightramp_think (edict_t *self) } else if (self->spawnflags & 1) { - char temp; + char temp; temp = self->movedir[0]; self->movedir[0] = self->movedir[1]; @@ -573,24 +1226,35 @@ void target_lightramp_think (edict_t *self) } } -void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) +void +target_lightramp_use(edict_t *self, edict_t *other /* unused */, edict_t *activator /* unused */) { + if (!self) + { + return; + } + if (!self->enemy) { - edict_t *e; + edict_t *e; - // check all the targets + /* check all the targets */ e = NULL; + while (1) { - e = G_Find (e, FOFS(targetname), self->target); + 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)); - + gi.dprintf("target %s (%s at %s) is not a light\n", + self->target, e->classname, vtos(e->s.origin)); } else { @@ -600,38 +1264,48 @@ void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator) if (!self->enemy) { - gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin)); - - G_FreeEdict (self); + 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); + target_lightramp_think(self); } -void SP_target_lightramp (edict_t *self) +void +SP_target_lightramp(edict_t *self) { - 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]) + if (!self) { - gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin)); + return; + } - G_FreeEdict (self); + 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); + 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); + gi.dprintf("%s with no target at %s\n", self->classname, + vtos(self->s.origin)); + G_FreeEdict(self); return; } @@ -641,58 +1315,86 @@ void SP_target_lightramp (edict_t *self) 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); + self->movedir[2] = + (self->movedir[1] - self->movedir[0]) / (self->speed / FRAMETIME); } -//========================================================== +/* ========================================================== */ /* - * QUAKED target_earthquake (1 0 0) (-8 -8 -8) (8 8 8) -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) + * 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; + int i; + edict_t *e; + + if (!self) + { + return; + } if (sv_jumpcinematic->value) // Don't do this if jumping a cinematic return; if (self->last_move_time < level.time) { - gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0); + 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++) + 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] += flrand(-150.0F, 150.0F); - e->velocity[1] += flrand(-150.0F, 150.0F); + 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, edict_t *activator) +target_earthquake_use(edict_t *self, edict_t *other /* unused */, edict_t *activator) { + if (!self || !activator) + { + return; + } if (sv_jumpcinematic->value) // Don't do this if jumping a cinematic + { return; + } self->timestamp = level.time + self->count; self->nextthink = level.time + FRAMETIME; @@ -700,22 +1402,38 @@ target_earthquake_use(edict_t *self, edict_t *other, edict_t *activator) self->last_move_time = 0; } -void SP_target_earthquake (edict_t *self) +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)); + { + 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; - 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 8210ef719..2853c051a 100644 --- a/src/game/g_trigger.c +++ b/src/game/g_trigger.c @@ -30,11 +30,22 @@ #include "common/fx.h" #include "header/g_playstats.h" #include "common/cl_strings.h" +#include "player/library/p_main.h" -#define TRIGGER_MONSTER 1 -#define TRIGGER_NOT_PLAYER 2 -#define TRIGGER_TRIGGERED 4 -#define TRIGGER_ANY 8 +#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 trigger_push_active(edict_t *self); +void hurt_touch(edict_t *self, edict_t *other, cplane_t *plane /* unused */, + csurface_t *surf /* unused */); #define PUZZLE_SHOWNO_INVENTORY 16 #define PUZZLE_DONT_REMOVE 32 @@ -52,8 +63,12 @@ void TriggerStaticsInit() classStatics[CID_TRIGGER].msgReceivers[G_MSG_UNSUSPEND] = Trigger_Activate; } -// the wait time has passed, so set back up for another activation -void multi_wait(edict_t *self) +/* + * The wait time has passed, so + * set back up for another activation + */ +void +multi_wait(edict_t *self) { self->think = NULL; if(self->activator) @@ -92,7 +107,7 @@ void TriggerActivated(edict_t *self) void Touch_Multi(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) { // Monsters or players can trigger it - if ((self->spawnflags & TRIGGER_ANY) && ((strcmp(other->classname, "player") == 0) || + if ((self->spawnflags & TRIGGER_TOGGLE) && ((strcmp(other->classname, "player") == 0) || (other->svflags & SVF_MONSTER))) ; // Player cannot trigger it @@ -1142,6 +1157,254 @@ void SP_trigger_PlayerPushLever(edict_t *self) } +//---------------------------------------------------------------------- +// Force Field +//---------------------------------------------------------------------- + +#define FIELD_FORCE_ONCE 1 + + +void +trigger_push_touch(edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf) +{ + vec3_t forward,up; + + if(other->health > 0) + { + if (other->client) // A player??? + { + // don't take falling damage immediately from this + VectorCopy(other->velocity, other->client->playerinfo.oldvelocity); + other->client->playerinfo.flags |= PLAYER_FLAG_USE_ENT_POS; + other->groundentity = NULL; + } + + AngleVectors(self->s.angles,forward,NULL,up); + + VectorMA(other->velocity,self->speed,forward,other->velocity); + VectorMA(other->velocity,self->speed,up,other->velocity); + + } + + G_UseTargets(self, self); + + if(self->spawnflags & FIELD_FORCE_ONCE) + { + G_FreeEdict (self); + } +} + +void +push_touch_trigger(edict_t *self, edict_t *activator) +{ + trigger_push_touch(self, activator, NULL, NULL); +} + +void +TrigPush_Deactivate(edict_t *self, G_Message_t *msg) +{ + self->solid = SOLID_NOT; + self->touch = NULL; +} + +void +TrigPush_Activate(edict_t *self, G_Message_t *msg) +{ + self->solid = SOLID_TRIGGER; + self->touch = trigger_push_touch; + gi.linkentity(self); +} + +void +TrigPushStaticsInit() +{ + classStatics[CID_TRIG_PUSH].msgReceivers[G_MSG_SUSPEND] = TrigPush_Deactivate; + classStatics[CID_TRIG_PUSH].msgReceivers[G_MSG_UNSUSPEND] = TrigPush_Activate; +} + +/* + * QUAKED trigger_push (.5 .5 .5) ? FORCE_ONCE + * Pushes the player + * "speed" defaults to 1000 + * angle - the angle to push the player along the X,Y + * zangle - the up direction to push the player (0 is straight up, 180 is straight down) + * + * If targeted, it will toggle on and off when used. + * + * FORCE_ONCE - pushes once and then goes away + */ +void +SP_trigger_push(edict_t *self) +{ + if (!self) + { + return; + } + + InitTrigger(self); + self->solid = SOLID_TRIGGER; + self->msgHandler = DefaultMsgHandler; + self->classID = CID_TRIG_PUSH; + + if (!self->speed) + { + self->speed = 500; + } + + self->s.angles[2] = st.zangle; + + // Can't really use the normal trigger setup cause it doesn't update velocity often enough + self->touch = trigger_push_touch; + self->TriggerActivated = push_touch_trigger; +} + +/* + * ============================================================================== + * + * trigger_hurt + * + * ============================================================================== + */ + +/* + * 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) + { + 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 */, + 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); +} + /* * ============================================================================== * diff --git a/src/game/g_utils.c b/src/game/g_utils.c index 2d0db54b9..11b3c7a59 100644 --- a/src/game/g_utils.c +++ b/src/game/g_utils.c @@ -659,23 +659,22 @@ G_InitEdict(edict_t *e) e->nextthink = 0; } + e->inuse = true; + e->classname = "noclass"; + e->gravity = 1.0; + e->s.number = e - g_edicts; + e->s.clientEffects.buf = NULL; e->s.clientEffects.bufSize = 0; e->s.clientEffects.freeBlock = 0; e->s.clientEffects.numEffects = 0; - - e->inuse = true; e->movetype = MOVETYPE_NONE; - e->classname = "noclass"; - e->gravity = 1.0; e->friction = 1.0; e->elasticity = ELASTICITY_SLIDE; - e->s.number = e - g_edicts; e->s.scale = 1.0; e->msgHandler = NULL; e->svflags = 0; e->reflected_time = level.time; - } /* @@ -690,7 +689,7 @@ angles and bad trails. ================= */ edict_t * -G_Spawn (void) +G_Spawn(void) { int i; edict_t *e; diff --git a/src/game/header/local.h b/src/game/header/local.h index f67e7febd..bb466da42 100644 --- a/src/game/header/local.h +++ b/src/game/header/local.h @@ -1593,14 +1593,39 @@ struct gclient_s client_respawn_t resp; pmove_state_t old_pmove; /* for detecting out-of-pmove changes */ + qboolean showscores; /* set layout stat */ + qboolean inmenu; /* in menu */ + pmenuhnd_t *menu; /* current menu */ + qboolean showinventory; /* set layout stat */ + qboolean showhelp; + qboolean showhelpicon; + + int ammo_index; + + int buttons; + int oldbuttons; + int latched_buttons; + + qboolean weapon_thunk; + + gitem_t *newweapon; + + /* sum up damage over an entire frame, so + shotgun blasts give a single big kick */ + int damage_armor; /* damage absorbed by armor */ + int damage_parmor; /* damage absorbed by power armor */ + int damage_blood; /* damage taken out of health */ + int damage_knockback; /* impact damage */ + vec3_t damage_from; /* origin for vector calculation */ + qboolean damage_gas; /* Did damage come from plague mist? */ + + float killer_yaw; /* when dead, look at killer */ + float invisible_framenum; // Damage stuff. Sum up damage over an entire frame. - qboolean damage_gas; // Did damage come from plague mist? - int damage_blood; // Damage taken out of health. - int damage_knockback; // Impact damage. - vec3_t damage_from; // Origin for vector calculation. + //