1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333 |
- // Copyright (c) ZeniMax Media Inc.
- // Licensed under the GNU General Public License 2.0.
- #include "g_local.h"
- // PGM - some of these are mine, some id's. I added the define's.
- constexpr spawnflags_t SPAWNFLAG_TRIGGER_MONSTER = 0x01_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_TRIGGER_NOT_PLAYER = 0x02_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_TRIGGER_TRIGGERED = 0x04_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_TRIGGER_TOGGLE = 0x08_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_TRIGGER_LATCHED = 0x10_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_TRIGGER_CLIP = 0x20_spawnflag;
- // PGM
- void InitTrigger(edict_t *self)
- {
- if (st.was_key_specified("angle") || st.was_key_specified("angles") || self->s.angles)
- G_SetMovedir(self->s.angles, self->movedir);
- self->solid = SOLID_TRIGGER;
- self->movetype = MOVETYPE_NONE;
- // [Paril-KEX] adjusted to allow mins/maxs to be defined
- // by hand instead
- if (self->model)
- gi.setmodel(self, self->model);
- self->svflags = SVF_NOCLIENT;
- }
- // the wait time has passed, so set back up for another activation
- THINK(multi_wait) (edict_t *ent) -> void
- {
- ent->nextthink = 0_ms;
- }
- // the trigger was just activated
- // ent->activator should be set to the activator so it can be held through a delay
- // so wait for the delay time before firing
- void multi_trigger(edict_t *ent)
- {
- if (ent->nextthink)
- return; // already been triggered
- G_UseTargets(ent, ent->activator);
- if (ent->wait > 0)
- {
- ent->think = multi_wait;
- ent->nextthink = level.time + gtime_t::from_sec(ent->wait);
- }
- else
- { // we can't just remove (self) here, because this is a touch function
- // called while looping through area links...
- ent->touch = nullptr;
- ent->nextthink = level.time + FRAME_TIME_S;
- ent->think = G_FreeEdict;
- }
- }
- USE(Use_Multi) (edict_t *ent, edict_t *other, edict_t *activator) -> void
- {
- // PGM
- if (ent->spawnflags.has(SPAWNFLAG_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);
- }
- // PGM
- }
- TOUCH(Touch_Multi) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- if (other->client)
- {
- if (self->spawnflags.has(SPAWNFLAG_TRIGGER_NOT_PLAYER))
- return;
- }
- else if (other->svflags & SVF_MONSTER)
- {
- if (!self->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER))
- return;
- }
- else
- return;
- if (self->spawnflags.has(SPAWNFLAG_TRIGGER_CLIP))
- {
- trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
- if (clip.fraction == 1.0f)
- return;
- }
- if (self->movedir)
- {
- vec3_t forward;
- AngleVectors(other->s.angles, forward, nullptr, nullptr);
- if (forward.dot(self->movedir) < 0)
- return;
- }
- self->activator = other;
- multi_trigger(self);
- }
- /*QUAKED trigger_multiple (.5 .5 .5) ? MONSTER NOT_PLAYER TRIGGERED TOGGLE LATCHED
- 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
- */
- USE(trigger_enable) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- self->solid = SOLID_TRIGGER;
- self->use = Use_Multi;
- gi.linkentity(self);
- }
- static BoxEdictsResult_t latched_trigger_filter(edict_t *other, void *data)
- {
- edict_t *self = (edict_t *) data;
- if (other->client)
- {
- if (self->spawnflags.has(SPAWNFLAG_TRIGGER_NOT_PLAYER))
- return BoxEdictsResult_t::Skip;
- }
- else if (other->svflags & SVF_MONSTER)
- {
- if (!self->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER))
- return BoxEdictsResult_t::Skip;
- }
- else
- return BoxEdictsResult_t::Skip;
- if (self->movedir)
- {
- vec3_t forward;
- AngleVectors(other->s.angles, forward, nullptr, nullptr);
- if (forward.dot(self->movedir) < 0)
- return BoxEdictsResult_t::Skip;
- }
- self->activator = other;
- return BoxEdictsResult_t::Keep | BoxEdictsResult_t::End;
- }
- THINK(latched_trigger_think) (edict_t *self) -> void
- {
- self->nextthink = level.time + 1_ms;
- bool any_inside = !!gi.BoxEdicts(self->absmin, self->absmax, nullptr, 0, AREA_SOLID, latched_trigger_filter, self);
- if (!!self->count != any_inside)
- {
- G_UseTargets(self, self->activator);
- self->count = any_inside ? 1 : 0;
- }
- }
- void SP_trigger_multiple(edict_t *ent)
- {
- 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.2f;
- InitTrigger(ent);
- if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_LATCHED))
- {
- if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TRIGGERED | SPAWNFLAG_TRIGGER_TOGGLE))
- gi.Com_PrintFmt("{}: latched and triggered/toggle are not supported\n", *ent);
- ent->think = latched_trigger_think;
- ent->nextthink = level.time + 1_ms;
- ent->use = Use_Multi;
- return;
- }
- else
- ent->touch = Touch_Multi;
- // PGM
- if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_TRIGGERED | SPAWNFLAG_TRIGGER_TOGGLE))
- // PGM
- {
- ent->solid = SOLID_NOT;
- ent->use = trigger_enable;
- }
- else
- {
- ent->solid = SOLID_TRIGGER;
- ent->use = Use_Multi;
- }
- gi.linkentity(ent);
- if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_CLIP))
- ent->svflags |= SVF_HULL;
- }
- /*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)
- {
- // make old maps work because I messed up on flag assignments here
- // triggered was on bit 1 when it should have been on bit 4
- if (ent->spawnflags.has(SPAWNFLAG_TRIGGER_MONSTER))
- {
- ent->spawnflags &= ~SPAWNFLAG_TRIGGER_MONSTER;
- ent->spawnflags |= SPAWNFLAG_TRIGGER_TRIGGERED;
- gi.Com_PrintFmt("{}: fixed TRIGGERED flag\n", *ent);
- }
- 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.
- */
- constexpr spawnflags_t SPAWNFLAGS_TRIGGER_RELAY_NO_SOUND = 1_spawnflag;
- USE(trigger_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- if (self->crosslevel_flags && !(self->crosslevel_flags == (game.cross_level_flags & SFL_CROSS_TRIGGER_MASK & self->crosslevel_flags)))
- return;
- G_UseTargets(self, activator);
- }
- void SP_trigger_relay(edict_t *self)
- {
- self->use = trigger_relay_use;
- if (self->spawnflags.has(SPAWNFLAGS_TRIGGER_RELAY_NO_SOUND))
- self->noise_index = -1;
- }
- /*
- ==============================================================================
- trigger_key
- ==============================================================================
- */
- /*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"
- */
- USE(trigger_key_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- item_id_t index;
- if (!self->item)
- return;
- if (!activator->client)
- return;
- index = self->item->id;
- if (!activator->client->pers.inventory[index])
- {
- if (level.time < self->touch_debounce_time)
- return;
- self->touch_debounce_time = level.time + 5_sec;
- gi.LocCenter_Print(activator, "$g_you_need", self->item->pickup_name_definite);
- 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->integer)
- {
- edict_t *ent;
- if (self->item->id == IT_KEY_POWER_CUBE || self->item->id == IT_KEY_EXPLOSIVE_CHARGES)
- {
- int cube;
- for (cube = 0; cube < 8; cube++)
- if (activator->client->pers.power_cubes & (1 << cube))
- break;
- for (uint32_t 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);
- // [Paril-KEX] don't allow respawning players to keep
- // used keys
- if (!P_UseCoopInstancedItems())
- {
- ent->client->resp.coop_respawn.inventory[index] = 0;
- ent->client->resp.coop_respawn.power_cubes &= ~(1 << cube);
- }
- }
- }
- }
- else
- {
- for (uint32_t player = 1; player <= game.maxclients; player++)
- {
- ent = &g_edicts[player];
- if (!ent->inuse)
- continue;
- if (!ent->client)
- continue;
- ent->client->pers.inventory[index] = 0;
- // [Paril-KEX] don't allow respawning players to keep
- // used keys
- if (!P_UseCoopInstancedItems())
- ent->client->resp.coop_respawn.inventory[index] = 0;
- }
- }
- }
- else
- {
- activator->client->pers.inventory[index]--;
- }
- G_UseTargets(self, activator);
- self->use = nullptr;
- }
- void SP_trigger_key(edict_t *self)
- {
- if (!st.item)
- {
- gi.Com_PrintFmt("{}: no key item\n", *self);
- return;
- }
- self->item = FindItemByClassname(st.item);
- if (!self->item)
- {
- gi.Com_PrintFmt("{}: item {} not found\n", *self, st.item);
- return;
- }
- if (!self->target)
- {
- gi.Com_PrintFmt("{}: no target\n", *self);
- return;
- }
- gi.soundindex("misc/keytry.wav");
- gi.soundindex("misc/keyuse.wav");
- self->use = trigger_key_use;
- }
- /*
- ==============================================================================
- trigger_counter
- ==============================================================================
- */
- /*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.
- */
- constexpr spawnflags_t SPAWNFLAG_COUNTER_NOMESSAGE = 1_spawnflag;
- USE(trigger_counter_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- if (self->count == 0)
- return;
- self->count--;
- if (self->count)
- {
- if (!(self->spawnflags & SPAWNFLAG_COUNTER_NOMESSAGE))
- {
- gi.LocCenter_Print(activator, "$g_more_to_go", self->count);
- gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
- }
- return;
- }
- if (!(self->spawnflags & SPAWNFLAG_COUNTER_NOMESSAGE))
- {
- gi.LocCenter_Print(activator, "$g_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)
- {
- self->wait = -1;
- if (!self->count)
- self->count = 2;
- self->use = trigger_counter_use;
- }
- /*
- ==============================================================================
- trigger_always
- ==============================================================================
- */
- /*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)
- {
- // we must have some delay to make sure our use targets are present
- if (!ent->delay)
- ent->delay = 0.2f;
- G_UseTargets(ent, ent);
- }
- /*
- ==============================================================================
- trigger_push
- ==============================================================================
- */
- // PGM
- constexpr spawnflags_t SPAWNFLAG_PUSH_ONCE = 0x01_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_PUSH_PLUS = 0x02_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_PUSH_SILENT = 0x04_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_PUSH_START_OFF = 0x08_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_PUSH_CLIP = 0x10_spawnflag;
- // PGM
- static cached_soundindex windsound;
- TOUCH(trigger_push_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- if (self->spawnflags.has(SPAWNFLAG_PUSH_CLIP))
- {
- trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
- if (clip.fraction == 1.0f)
- return;
- }
- if (strcmp(other->classname, "grenade") == 0)
- {
- other->velocity = self->movedir * (self->speed * 10);
- }
- else if (other->health > 0)
- {
- other->velocity = self->movedir * (self->speed * 10);
- if (other->client)
- {
- // don't take falling damage immediately from this
- other->client->oldvelocity = other->velocity;
- other->client->oldgroundentity = other->groundentity;
- if (
- !(self->spawnflags & SPAWNFLAG_PUSH_SILENT) &&
- (other->fly_sound_debounce_time < level.time))
- {
- other->fly_sound_debounce_time = level.time + 1.5_sec;
- gi.sound(other, CHAN_AUTO, windsound, 1, ATTN_NORM, 0);
- }
- }
- }
- if (self->spawnflags.has(SPAWNFLAG_PUSH_ONCE))
- G_FreeEdict(self);
- }
- //======
- // PGM
- USE(trigger_push_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- if (self->solid == SOLID_NOT)
- self->solid = SOLID_TRIGGER;
- else
- self->solid = SOLID_NOT;
- gi.linkentity(self);
- }
- // PGM
- //======
- // RAFAEL
- void trigger_push_active(edict_t *self);
- void trigger_effect(edict_t *self)
- {
- vec3_t origin;
- int i;
- origin = (self->absmin + self->absmax) * 0.5f;
- for (i = 0; i < 10; i++)
- {
- origin[2] += (self->speed * 0.01f) * (i + frandom());
- gi.WriteByte(svc_temp_entity);
- gi.WriteByte(TE_TUNNEL_SPARKS);
- gi.WriteByte(1);
- gi.WritePosition(origin);
- gi.WriteDir(vec3_origin);
- gi.WriteByte(irandom(0x74, 0x7C));
- gi.multicast(self->s.origin, MULTICAST_PVS, false);
- }
- }
- THINK(trigger_push_inactive) (edict_t *self) -> void
- {
- if (self->delay > level.time.seconds())
- {
- self->nextthink = level.time + 100_ms;
- }
- else
- {
- self->touch = trigger_push_touch;
- self->think = trigger_push_active;
- self->nextthink = level.time + 100_ms;
- self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds();
- }
- }
- THINK(trigger_push_active) (edict_t *self) -> void
- {
- if (self->delay > level.time.seconds())
- {
- self->nextthink = level.time + 100_ms;
- trigger_effect(self);
- }
- else
- {
- self->touch = nullptr;
- self->think = trigger_push_inactive;
- self->nextthink = level.time + 100_ms;
- self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds();
- }
- }
- // RAFAEL
- /*QUAKED trigger_push (.5 .5 .5) ? PUSH_ONCE PUSH_PLUS PUSH_SILENT START_OFF CLIP
- Pushes the player
- "speed" defaults to 1000
- "wait" defaults to 10, must use PUSH_PLUS
- 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)
- {
- InitTrigger(self);
- if (!(self->spawnflags & SPAWNFLAG_PUSH_SILENT))
- windsound.assign("misc/windfly.wav");
- self->touch = trigger_push_touch;
- // RAFAEL
- if (self->spawnflags.has(SPAWNFLAG_PUSH_PLUS))
- {
- if (!self->wait)
- self->wait = 10;
- self->think = trigger_push_active;
- self->nextthink = level.time + 100_ms;
- self->delay = (self->nextthink + gtime_t::from_sec(self->wait)).seconds();
- }
- // RAFAEL
- if (!self->speed)
- self->speed = 1000;
- // PGM
- if (self->targetname) // toggleable
- {
- self->use = trigger_push_use;
- if (self->spawnflags.has(SPAWNFLAG_PUSH_START_OFF))
- self->solid = SOLID_NOT;
- }
- else if (self->spawnflags.has(SPAWNFLAG_PUSH_START_OFF))
- {
- gi.Com_Print("trigger_push is START_OFF but not targeted.\n");
- self->svflags = SVF_NONE;
- self->touch = nullptr;
- self->solid = SOLID_BSP;
- self->movetype = MOVETYPE_PUSH;
- }
- // PGM
- gi.linkentity(self);
- if (self->spawnflags.has(SPAWNFLAG_PUSH_CLIP))
- self->svflags |= SVF_HULL;
- }
- /*
- ==============================================================================
- trigger_hurt
- ==============================================================================
- */
- /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW NO_PLAYERS NO_MONSTERS
- 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)
- */
- constexpr spawnflags_t SPAWNFLAG_HURT_START_OFF = 1_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_HURT_TOGGLE = 2_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_HURT_SILENT = 4_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_HURT_NO_PROTECTION = 8_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_HURT_SLOW = 16_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_HURT_NO_PLAYERS = 32_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_HURT_NO_MONSTERS = 64_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_HURT_CLIPPED = 128_spawnflag;
- USE(hurt_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- if (self->solid == SOLID_NOT)
- self->solid = SOLID_TRIGGER;
- else
- self->solid = SOLID_NOT;
- gi.linkentity(self);
- if (!(self->spawnflags & SPAWNFLAG_HURT_TOGGLE))
- self->use = nullptr;
- }
- TOUCH(hurt_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- damageflags_t dflags;
- if (!other->takedamage)
- return;
- else if (!(other->svflags & SVF_MONSTER) && !(other->flags & FL_DAMAGEABLE) && (!other->client) && (strcmp(other->classname, "misc_explobox") != 0))
- return;
- else if (self->spawnflags.has(SPAWNFLAG_HURT_NO_MONSTERS) && (other->svflags & SVF_MONSTER))
- return;
- else if (self->spawnflags.has(SPAWNFLAG_HURT_NO_PLAYERS) && (other->client))
- return;
- if (self->timestamp > level.time)
- return;
- if (self->spawnflags.has(SPAWNFLAG_HURT_CLIPPED))
- {
- trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
- if (clip.fraction == 1.0f)
- return;
- }
- if (self->spawnflags.has(SPAWNFLAG_HURT_SLOW))
- self->timestamp = level.time + 1_sec;
- else
- self->timestamp = level.time + 10_hz;
- if (!(self->spawnflags & SPAWNFLAG_HURT_SILENT))
- {
- if (self->fly_sound_debounce_time < level.time)
- {
- gi.sound(other, CHAN_AUTO, self->noise_index, 1, ATTN_NORM, 0);
- self->fly_sound_debounce_time = level.time + 1_sec;
- }
- }
- if (self->spawnflags.has(SPAWNFLAG_HURT_NO_PROTECTION))
- dflags = DAMAGE_NO_PROTECTION;
- else
- dflags = DAMAGE_NONE;
- 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)
- {
- InitTrigger(self);
- self->noise_index = gi.soundindex("world/electro.wav");
- self->touch = hurt_touch;
- if (!self->dmg)
- self->dmg = 5;
- if (self->spawnflags.has(SPAWNFLAG_HURT_START_OFF))
- self->solid = SOLID_NOT;
- else
- self->solid = SOLID_TRIGGER;
- if (self->spawnflags.has(SPAWNFLAG_HURT_TOGGLE))
- self->use = hurt_use;
- gi.linkentity(self);
- if (self->spawnflags.has(SPAWNFLAG_HURT_CLIPPED))
- self->svflags |= SVF_HULL;
- }
- /*
- ==============================================================================
- trigger_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)
- */
- constexpr spawnflags_t SPAWNFLAG_GRAVITY_TOGGLE = 1_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_GRAVITY_START_OFF = 2_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_GRAVITY_CLIPPED = 4_spawnflag;
- // PGM
- USE(trigger_gravity_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- if (self->solid == SOLID_NOT)
- self->solid = SOLID_TRIGGER;
- else
- self->solid = SOLID_NOT;
- gi.linkentity(self);
- }
- // PGM
- TOUCH(trigger_gravity_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- if (self->spawnflags.has(SPAWNFLAG_GRAVITY_CLIPPED))
- {
- trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
- if (clip.fraction == 1.0f)
- return;
- }
- other->gravity = self->gravity;
- }
- void SP_trigger_gravity(edict_t *self)
- {
- if (!st.gravity || !*st.gravity)
- {
- gi.Com_PrintFmt("{}: no gravity set\n", *self);
- G_FreeEdict(self);
- return;
- }
- InitTrigger(self);
- // PGM
- self->gravity = (float) atof(st.gravity);
- if (self->spawnflags.has(SPAWNFLAG_GRAVITY_TOGGLE))
- self->use = trigger_gravity_use;
- if (self->spawnflags.has(SPAWNFLAG_GRAVITY_START_OFF))
- {
- self->use = trigger_gravity_use;
- self->solid = SOLID_NOT;
- }
- self->touch = trigger_gravity_touch;
- // PGM
- gi.linkentity(self);
- if (self->spawnflags.has(SPAWNFLAG_GRAVITY_CLIPPED))
- self->svflags |= SVF_HULL;
- }
- /*
- ==============================================================================
- trigger_monsterjump
- ==============================================================================
- */
- /*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
- TOGGLE - trigger_monsterjump can be turned on and off
- START_OFF - trigger_monsterjump starts turned off (implies TOGGLE)
- */
- constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_TOGGLE = 1_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_START_OFF = 2_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_MONSTERJUMP_CLIPPED = 4_spawnflag;
- USE(trigger_monsterjump_use) (edict_t* self, edict_t* other, edict_t* activator) -> void
- {
- if (self->solid == SOLID_NOT)
- self->solid = SOLID_TRIGGER;
- else
- self->solid = SOLID_NOT;
- gi.linkentity(self);
- }
- TOUCH(trigger_monsterjump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- if (other->flags & (FL_FLY | FL_SWIM))
- return;
- if (other->svflags & SVF_DEADMONSTER)
- return;
- if (!(other->svflags & SVF_MONSTER))
- return;
- if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_CLIPPED))
- {
- trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
- if (clip.fraction == 1.0f)
- 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 = nullptr;
- other->velocity[2] = self->movedir[2];
- }
- void SP_trigger_monsterjump(edict_t *self)
- {
- 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] = (float) st.height;
- if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_TOGGLE))
- self->use = trigger_monsterjump_use;
- if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_START_OFF))
- {
- self->use = trigger_monsterjump_use;
- self->solid = SOLID_NOT;
- }
- gi.linkentity(self);
- if (self->spawnflags.has(SPAWNFLAG_MONSTERJUMP_CLIPPED))
- self->svflags |= SVF_HULL;
- }
- /*
- ==============================================================================
- trigger_flashlight
- ==============================================================================
- */
- /*QUAKED trigger_flashlight (.5 .5 .5) ?
- Players moving against this trigger will have their flashlight turned on or off.
- "style" default to 0, set to 1 to always turn flashlight on, 2 to always turn off,
- otherwise "angles" are used to control on/off state
- */
- constexpr spawnflags_t SPAWNFLAG_FLASHLIGHT_CLIPPED = 1_spawnflag;
- TOUCH(trigger_flashlight_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- if (!other->client)
- return;
- if (self->spawnflags.has(SPAWNFLAG_FLASHLIGHT_CLIPPED))
- {
- trace_t clip = gi.clip(self, other->s.origin, other->mins, other->maxs, other->s.origin, G_GetClipMask(other));
- if (clip.fraction == 1.0f)
- return;
- }
- if (self->style == 1)
- {
- P_ToggleFlashlight(other, true);
- }
- else if (self->style == 2)
- {
- P_ToggleFlashlight(other, false);
- }
- else if (other->velocity.lengthSquared() > 32.f)
- {
- vec3_t forward = other->velocity.normalized();
- P_ToggleFlashlight(other, forward.dot(self->movedir) > 0);
- }
- }
- void SP_trigger_flashlight(edict_t *self)
- {
- if (self->s.angles[YAW] == 0)
- self->s.angles[YAW] = 360;
- InitTrigger(self);
- self->touch = trigger_flashlight_touch;
- self->movedir[2] = (float) st.height;
- if (self->spawnflags.has(SPAWNFLAG_FLASHLIGHT_CLIPPED))
- self->svflags |= SVF_HULL;
- gi.linkentity(self);
- }
- /*
- ==============================================================================
- trigger_fog
- ==============================================================================
- */
- /*QUAKED trigger_fog (.5 .5 .5) ? AFFECT_FOG AFFECT_HEIGHTFOG INSTANTANEOUS FORCE BLEND
- Players moving against this trigger will have their fog settings changed.
- Fog/heightfog will be adjusted if the spawnflags are set. Instantaneous
- ignores any delays. Force causes it to ignore movement dir and always use
- the "on" values. Blend causes it to change towards how far you are into the trigger
- with respect to angles.
- "target" can target an info_notnull to pull the keys below from.
- "delay" default to 0.5; time in seconds a change in fog will occur over
- "wait" default to 0.0; time in seconds before a re-trigger can be executed
- "fog_density"; density value of fog, 0-1
- "fog_color"; color value of fog, 3d vector with values between 0-1 (r g b)
- "fog_density_off"; transition density value of fog, 0-1
- "fog_color_off"; transition color value of fog, 3d vector with values between 0-1 (r g b)
- "fog_sky_factor"; sky factor value of fog, 0-1
- "fog_sky_factor_off"; transition sky factor value of fog, 0-1
- "heightfog_falloff"; falloff value of heightfog, 0-1
- "heightfog_density"; density value of heightfog, 0-1
- "heightfog_start_color"; the start color for the fog (r g b, 0-1)
- "heightfog_start_dist"; the start distance for the fog (units)
- "heightfog_end_color"; the start color for the fog (r g b, 0-1)
- "heightfog_end_dist"; the end distance for the fog (units)
- "heightfog_falloff_off"; transition falloff value of heightfog, 0-1
- "heightfog_density_off"; transition density value of heightfog, 0-1
- "heightfog_start_color_off"; transition the start color for the fog (r g b, 0-1)
- "heightfog_start_dist_off"; transition the start distance for the fog (units)
- "heightfog_end_color_off"; transition the start color for the fog (r g b, 0-1)
- "heightfog_end_dist_off"; transition the end distance for the fog (units)
- */
- constexpr spawnflags_t SPAWNFLAG_FOG_AFFECT_FOG = 1_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_FOG_AFFECT_HEIGHTFOG = 2_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_FOG_INSTANTANEOUS = 4_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_FOG_FORCE = 8_spawnflag;
- constexpr spawnflags_t SPAWNFLAG_FOG_BLEND = 16_spawnflag;
- TOUCH(trigger_fog_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
- {
- if (!other->client)
- return;
- if (self->timestamp > level.time)
- return;
- self->timestamp = level.time + gtime_t::from_sec(self->wait);
- edict_t *fog_value_storage = self;
- if (self->movetarget)
- fog_value_storage = self->movetarget;
- if (self->spawnflags.has(SPAWNFLAG_FOG_INSTANTANEOUS))
- other->client->pers.fog_transition_time = 0_ms;
- else
- other->client->pers.fog_transition_time = gtime_t::from_sec(fog_value_storage->delay);
- if (self->spawnflags.has(SPAWNFLAG_FOG_BLEND))
- {
- vec3_t center = (self->absmin + self->absmax) * 0.5f;
- vec3_t half_size = (self->size * 0.5f) + (other->size * 0.5f);
- vec3_t start = (-self->movedir).scaled(half_size);
- vec3_t end = (self->movedir).scaled(half_size);
- vec3_t player_dist = (other->s.origin - center).scaled(vec3_t{fabs(self->movedir[0]),fabs(self->movedir[1]),fabs(self->movedir[2])});
- float dist = (player_dist - start).length();
- dist /= (start - end).length();
- dist = clamp(dist, 0.f, 1.f);
- if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_FOG))
- {
- other->client->pers.wanted_fog = {
- lerp(fog_value_storage->fog.density_off, fog_value_storage->fog.density, dist),
- lerp(fog_value_storage->fog.color_off[0], fog_value_storage->fog.color[0], dist),
- lerp(fog_value_storage->fog.color_off[1], fog_value_storage->fog.color[1], dist),
- lerp(fog_value_storage->fog.color_off[2], fog_value_storage->fog.color[2], dist),
- lerp(fog_value_storage->fog.sky_factor_off, fog_value_storage->fog.sky_factor, dist)
- };
- }
- if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_HEIGHTFOG))
- {
- other->client->pers.wanted_heightfog = {
- {
- lerp(fog_value_storage->heightfog.start_color_off[0], fog_value_storage->heightfog.start_color[0], dist),
- lerp(fog_value_storage->heightfog.start_color_off[1], fog_value_storage->heightfog.start_color[1], dist),
- lerp(fog_value_storage->heightfog.start_color_off[2], fog_value_storage->heightfog.start_color[2], dist),
- lerp(fog_value_storage->heightfog.start_dist_off, fog_value_storage->heightfog.start_dist, dist)
- },
- {
- lerp(fog_value_storage->heightfog.end_color_off[0], fog_value_storage->heightfog.end_color[0], dist),
- lerp(fog_value_storage->heightfog.end_color_off[1], fog_value_storage->heightfog.end_color[1], dist),
- lerp(fog_value_storage->heightfog.end_color_off[2], fog_value_storage->heightfog.end_color[2], dist),
- lerp(fog_value_storage->heightfog.end_dist_off, fog_value_storage->heightfog.end_dist, dist)
- },
- lerp(fog_value_storage->heightfog.falloff_off, fog_value_storage->heightfog.falloff, dist),
- lerp(fog_value_storage->heightfog.density_off, fog_value_storage->heightfog.density, dist)
- };
- }
- return;
- }
- bool use_on = true;
- if (!self->spawnflags.has(SPAWNFLAG_FOG_FORCE))
- {
- float len;
- vec3_t forward = other->velocity.normalized(len);
- // not moving enough to trip; this is so we don't trip
- // the wrong direction when on an elevator, etc.
- if (len <= 0.0001f)
- return;
- use_on = forward.dot(self->movedir) > 0;
- }
- if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_FOG))
- {
- if (use_on)
- {
- other->client->pers.wanted_fog = {
- fog_value_storage->fog.density,
- fog_value_storage->fog.color[0],
- fog_value_storage->fog.color[1],
- fog_value_storage->fog.color[2],
- fog_value_storage->fog.sky_factor
- };
- }
- else
- {
- other->client->pers.wanted_fog = {
- fog_value_storage->fog.density_off,
- fog_value_storage->fog.color_off[0],
- fog_value_storage->fog.color_off[1],
- fog_value_storage->fog.color_off[2],
- fog_value_storage->fog.sky_factor_off
- };
- }
- }
- if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_HEIGHTFOG))
- {
- if (use_on)
- {
- other->client->pers.wanted_heightfog = {
- {
- fog_value_storage->heightfog.start_color[0],
- fog_value_storage->heightfog.start_color[1],
- fog_value_storage->heightfog.start_color[2],
- fog_value_storage->heightfog.start_dist
- },
- {
- fog_value_storage->heightfog.end_color[0],
- fog_value_storage->heightfog.end_color[1],
- fog_value_storage->heightfog.end_color[2],
- fog_value_storage->heightfog.end_dist
- },
- fog_value_storage->heightfog.falloff,
- fog_value_storage->heightfog.density
- };
- }
- else
- {
- other->client->pers.wanted_heightfog = {
- {
- fog_value_storage->heightfog.start_color_off[0],
- fog_value_storage->heightfog.start_color_off[1],
- fog_value_storage->heightfog.start_color_off[2],
- fog_value_storage->heightfog.start_dist_off
- },
- {
- fog_value_storage->heightfog.end_color_off[0],
- fog_value_storage->heightfog.end_color_off[1],
- fog_value_storage->heightfog.end_color_off[2],
- fog_value_storage->heightfog.end_dist_off
- },
- fog_value_storage->heightfog.falloff_off,
- fog_value_storage->heightfog.density_off
- };
- }
- }
- }
- void SP_trigger_fog(edict_t *self)
- {
- if (self->s.angles[YAW] == 0)
- self->s.angles[YAW] = 360;
- InitTrigger(self);
- if (!(self->spawnflags & (SPAWNFLAG_FOG_AFFECT_FOG | SPAWNFLAG_FOG_AFFECT_HEIGHTFOG)))
- gi.Com_PrintFmt("WARNING: {} with no fog spawnflags set\n", *self);
- if (self->target)
- {
- self->movetarget = G_PickTarget(self->target);
- if (self->movetarget)
- {
- if (!self->movetarget->delay)
- self->movetarget->delay = 0.5f;
- }
- }
- if (!self->delay)
- self->delay = 0.5f;
- self->touch = trigger_fog_touch;
- }
- /*QUAKED trigger_coop_relay (.5 .5 .5) ? AUTO_FIRE
- Like a trigger_relay, but all players must be touching its
- mins/maxs in order to fire, otherwise a message will be printed.
- AUTO_FIRE: check every `wait` seconds for containment instead of
- requiring to be fired by something else. Frees itself after firing.
- "message"; message to print to the one activating the relay if
- not all players are inside the bounds
- "message2"; message to print to players not inside the trigger
- if they aren't in the bounds
- */
- constexpr spawnflags_t SPAWNFLAG_COOP_RELAY_AUTO_FIRE = 1_spawnflag;
- inline bool trigger_coop_relay_filter(edict_t *player)
- {
- return (player->health <= 0 || player->deadflag || player->movetype == MOVETYPE_NOCLIP ||
- player->client->resp.spectator || player->s.modelindex != MODELINDEX_PLAYER);
- }
- static bool trigger_coop_relay_can_use(edict_t *self, edict_t *activator)
- {
- // not coop, so act like a standard trigger_relay minus the message
- if (!coop->integer)
- return true;
- // coop; scan for all alive players, print appropriate message
- // to those in/out of range
- bool can_use = true;
- for (auto player : active_players())
- {
- // dead or spectator, don't count them
- if (trigger_coop_relay_filter(player))
- continue;
- if (boxes_intersect(player->absmin, player->absmax, self->absmin, self->absmax))
- continue;
- if (self->timestamp < level.time)
- gi.LocCenter_Print(player, self->map);
- can_use = false;
- }
- return can_use;
- }
- USE(trigger_coop_relay_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
- {
- if (!trigger_coop_relay_can_use(self, activator))
- {
- if (self->timestamp < level.time)
- gi.LocCenter_Print(activator, self->message);
- self->timestamp = level.time + 5_sec;
- return;
- }
- const char *msg = self->message;
- self->message = nullptr;
- G_UseTargets(self, activator);
- self->message = msg;
- }
- static BoxEdictsResult_t trigger_coop_relay_player_filter(edict_t *ent, void *data)
- {
- if (!ent->client)
- return BoxEdictsResult_t::Skip;
- else if (trigger_coop_relay_filter(ent))
- return BoxEdictsResult_t::Skip;
- return BoxEdictsResult_t::Keep;
- }
- THINK(trigger_coop_relay_think) (edict_t *self) -> void
- {
- std::array<edict_t *, MAX_SPLIT_PLAYERS> players;
- size_t num_active = 0;
- for (auto player : active_players())
- if (!trigger_coop_relay_filter(player))
- num_active++;
- size_t n = gi.BoxEdicts(self->absmin, self->absmax, players.data(), num_active, AREA_SOLID, trigger_coop_relay_player_filter, nullptr);
- if (n == num_active)
- {
- const char *msg = self->message;
- self->message = nullptr;
- G_UseTargets(self, &globals.edicts[1]);
- self->message = msg;
- G_FreeEdict(self);
- return;
- }
- else if (n && self->timestamp < level.time)
- {
- for (size_t i = 0; i < n; i++)
- gi.LocCenter_Print(players[i], self->message);
- for (auto player : active_players())
- if (std::find(players.begin(), players.end(), player) == players.end())
- gi.LocCenter_Print(player, self->map);
- self->timestamp = level.time + 5_sec;
- }
- self->nextthink = level.time + gtime_t::from_sec(self->wait);
- }
- void SP_trigger_coop_relay(edict_t *self)
- {
- if (self->targetname && self->spawnflags.has(SPAWNFLAG_COOP_RELAY_AUTO_FIRE))
- gi.Com_PrintFmt("{}: targetname and auto-fire are mutually exclusive\n", *self);
- InitTrigger(self);
-
- if (!self->message)
- self->message = "$g_coop_wait_for_players";
- if (!self->map)
- self->map = "$g_coop_players_waiting_for_you";
- if (!self->wait)
- self->wait = 1;
- if (self->spawnflags.has(SPAWNFLAG_COOP_RELAY_AUTO_FIRE))
- {
- self->think = trigger_coop_relay_think;
- self->nextthink = level.time + gtime_t::from_sec(self->wait);
- }
- else
- self->use = trigger_coop_relay_use;
- self->svflags |= SVF_NOCLIENT;
- gi.linkentity(self);
- }
|