/* * Copyright (C) 1999-2000 Id Software, Inc. */ #include "g_trigger.h" #include "g_local.h" #include "g_spawn.h" #include "g_client.h" #include "g_lua.h" #include "g_logger.h" #include "g_syscalls.h" enum g_triggerLimits_e { MAX_TRANSPORTER_POINTS = 16 }; void G_Trigger_Init(gentity_t* self) { G_LogFuncBegin(); if (VectorCompare(self->s.angles, vec3_origin) == 0) { G_SetMovedir(self->s.angles, self->movedir); } if (!self->tmpEntity) { /* for spawnTent command */ trap_SetBrushModel(self, self->model); } self->r.contents = CONTENTS_TRIGGER; /* replaces the -1 from trap_SetBrushModel */ self->r.svFlags = SVF_NOCLIENT; G_LogFuncEnd(); } /** * @brief Reactivate a trigger_multiple after the wait time has passed. * * Reactivates the trigger_multiple after the wait time has passed. * * @param ent the trigger */ static void multi_wait(gentity_t* ent) { G_LogFuncBegin(); ent->nextthink = 0; G_LogFuncEnd(); } /** * @brief Function that gets called when a trigger_multiple has been ... umm well triggered ;) * * The trigger_multiple was just activated. ent->activator should be set to the activator * so it can be held through a delay. May wait for the delay time until firing. * * @param ent the trigger * @param activator the activator */ static void multi_trigger(gentity_t* ent, gentity_t* activator) { ent->activator = activator; G_LogFuncBegin(); if (ent->nextthink != 0) { G_LocLogger(LL_DEBUG, "multi_trigger called before wait was over\n"); G_LogFuncEnd(); return; /* can't retrigger until the wait is over */ } if ((activator->client != NULL) && (((ent->spawnflags & 4) != 0) || ((ent->spawnflags & 2) != 0) || ((ent->spawnflags & 1) != 0))) { /* see if it's usable by this team */ switch (activator->client->sess.sessionTeam) { case TEAM_RED: if ((ent->spawnflags & 1) == 0) { return; /* red is not allowed */ } break; case TEAM_BLUE: if ((ent->spawnflags & 2) == 0) { return; /* blue is not allowed */ } break; default: if ((ent->spawnflags & 4) != 0) { return; /* must be on a team */ } break; } } G_UseTargets(ent, ent->activator); #ifdef G_LUA if ((ent->luaTrigger != NULL) && (ent->nextthink != 0)) { if (activator != NULL) { LuaHook_G_EntityTrigger(ent->luaTrigger, ent->s.number, activator->s.number); } else { LuaHook_G_EntityTrigger(ent->luaTrigger, ent->s.number, ENTITYNUM_WORLD); } } #endif if (ent->wait > 0) { ent->think = multi_wait; ent->nextthink = level.time + (ent->wait + ent->random * crandom()) * 1000; } else { /* * we can't just remove (self) here, because this is a touch function * called while looping through area links... */ ent->touch = 0; ent->nextthink = level.time + FRAMETIME; ent->think = G_FreeEntity; } G_LogFuncEnd(); } /** * @brief Use function for trigger_multiple. * * @param ent the trigger * @param other another entity * @param activator the activator */ static void Use_Multi(gentity_t* ent, gentity_t* other, gentity_t* activator) { G_LogFuncBegin(); multi_trigger(ent, activator); G_LogFuncEnd(); } /** * @brief Touch function for trigger_multiple. * * @param self the trigger * @param other touching entity * @param trace a trace */ void Touch_Multi(gentity_t* self, gentity_t* other, trace_t* trace) { G_LogFuncBegin(); if (other->client == NULL) { G_LocLogger(LL_DEBUG, "other->client is NULL\n"); G_LogFuncEnd(); return; } multi_trigger(self, other); G_LogFuncEnd(); } /*QUAKED trigger_multiple (.5 .5 .5) ? RED_OK BLUE_OK TEAM_ONLY -----DESCRIPTION----- Variable sized repeatable trigger. Must be targeted at one or more entities. -----SPAWNFLAGS----- 1: RED_OK - People on the red team can fire this trigger 2: BLUE_OK - People on the blue team can fire this trigger 4: TEAM_ONLY - Only people on red or blue can fire this trigger (not TEAM_FREE like in straight holomatch or spectators) -----KEYS----- "wait" - Seconds between triggerings, 0.5 default, -1 = one time only. "random" - wait variance, default is 0 so, the basic time between firing is a random time between (wait - random) and (wait + random) */ void SP_trigger_multiple(gentity_t *ent) { G_LogFuncBegin(); ent->type = EntityType::ENT_TRIGGER_MULTIPLE; G_SpawnFloat("wait", "0.5", &ent->wait); G_SpawnFloat("random", "0", &ent->random); G_SetOrigin(ent, ent->s.origin); if (ent->random >= ent->wait && ent->wait >= 0) { ent->random = ent->wait - FRAMETIME; G_LocLogger(LL_WARN, "trigger_multiple has random >= wait\n"); } ent->touch = Touch_Multi; ent->use = Use_Multi; G_Trigger_Init(ent); VectorCopy(ent->r.maxs, ent->s.origin2); VectorCopy(ent->r.mins, ent->s.angles2); trap_LinkEntity(ent); level.numBrushEnts++; G_LogFuncEnd(); } /* ============================================================================== trigger_always ============================================================================== */ /** * @brief Think function of trigger_always. * * Think function of trigger_always. * * @param ent the trigger */ static void trigger_always_think(gentity_t* ent) { G_LogFuncBegin(); G_UseTargets(ent, ent); #ifdef G_LUA if (ent->luaTrigger != NULL) { LuaHook_G_EntityTrigger(ent->luaTrigger, ent->s.number, ent->s.number); } #endif G_FreeEntity(ent); G_LogFuncEnd(); } /*QUAKED trigger_always (.5 .5 .5) (-8 -8 -8) (8 8 8) -----DESCRIPTION----- This trigger will always fire. It is activated by the world. Actually this is going to fire once 0.3 secs after spawn, so it's more a trigger_init. -----SPAWNFLAGS----- none -----KEYS----- "target" - targets to fire */ void SP_trigger_always(gentity_t* ent) { G_LogFuncBegin(); ent->type = EntityType::ENT_TRIGGER_ALWAYS; /* we must have some delay to make sure our use targets are present */ ent->nextthink = level.time + 300; ent->think = trigger_always_think; G_LogFuncEnd(); } /* ============================================================================== trigger_push ============================================================================== */ /** * @brief Touch function of trigger_push. * * Touch function of trigger_push. * * @param self the trigger * @param other the touching entity * @param trace a trace */ static void trigger_push_touch(gentity_t* self, gentity_t* other, trace_t* trace) { G_LogFuncBegin(); if (other->client == NULL) { G_LocLogger(LL_DEBUG, "other->client is NULL\n"); G_LogFuncEnd(); return; } if (other->client->ps.velocity[2] < 100) { /* don't play the event sound again if we are in a fat trigger */ G_AddPredictableEvent(other, EV_JUMP_PAD, 0); } VectorCopy(self->s.origin2, other->client->ps.velocity); G_LogFuncEnd(); } /** * @brief Calculate origin2 so the target apogee will be hit. * * Calculate origin2 so the target apogee will be hit. * * @param the trigger */ static void AimAtTarget(gentity_t* self) { gentity_t *ent = NULL; vec3_t origin = { 0, 0, 0 }; double height = 0; double gravity = 0; double time = 0; double forward = 0; double dist = 0; G_LogFuncBegin(); VectorAdd(self->r.absmin, self->r.absmax, origin); VectorScale(origin, 0.5, origin); ent = G_PickTarget(self->target); if (ent == NULL) { G_LocLogger(LL_ERROR, "Could not spawn new entity!\n"); G_FreeEntity(self); G_LogFuncEnd(); return; } height = ent->s.origin[2] - origin[2]; gravity = g_gravity.value; time = sqrt(height / (.5 * gravity)); if (time <= 0) { G_LocLogger(LL_ERROR, "time <= 0\n"); G_FreeEntity(self); G_LogFuncEnd(); return; } /* set s.origin2 to the push velocity */ VectorSubtract(ent->s.origin, origin, self->s.origin2); self->s.origin2[2] = 0; dist = VectorNormalize(self->s.origin2); forward = dist / time; VectorScale(self->s.origin2, forward, self->s.origin2); self->s.origin2[2] = time * gravity; G_LogFuncEnd(); } /*QUAKED trigger_push (.5 .5 .5) ? -----DESCRIPTION----- Jumpfield/Booster Effect predicted on client side. This is activated by touch function. -----SPAWNFLAGS----- None -----KEYS----- "target" - apex of the leap. Must be a target_position or info_notnull. */ void SP_trigger_push(gentity_t* self) { G_LogFuncBegin(); self->type = EntityType::ENT_TRIGGER_PUSH; G_Trigger_Init(self); /* unlike other triggers, we need to send this one to the client */ self->r.svFlags &= ~SVF_NOCLIENT; self->s.eType = ET_PUSH_TRIGGER; self->touch = trigger_push_touch; self->think = AimAtTarget; self->nextthink = level.time + FRAMETIME; trap_LinkEntity(self); VectorCopy(self->r.maxs, self->s.apos.trBase); VectorCopy(self->r.mins, self->s.pos.trBase); level.numBrushEnts++; G_LogFuncEnd(); } /** * @brief Use function of target_push. * * @param self the entity * @param other another entity * @param activator the activator */ static void Use_target_push(gentity_t* self, gentity_t* other, gentity_t* activator) { G_LogFuncBegin(); if (activator->client == NULL) { G_LocLogger(LL_DEBUG, "activator->client is NULL\n"); G_LogFuncEnd(); return; } /* RPG-X: J2J noclip use */ if ((activator->client->ps.pm_type != PM_NORMAL) || (activator->client->ps.pm_type != PM_NOCLIP)) { G_LocLogger(LL_DEBUG, "noclip in use\n"); G_LogFuncEnd(); return; } if (activator->client->ps.powerups[PW_FLIGHT]) { G_LocLogger(LL_DEBUG, "flight in use\n"); G_LogFuncEnd(); return; } VectorCopy(self->s.origin2, activator->client->ps.velocity); /* play fly sound every 1.5 seconds */ if (activator->fly_sound_debounce_time < level.time) { activator->fly_sound_debounce_time = level.time + 1500; G_Sound(activator, self->noise_index); } G_LogFuncEnd(); } /*QUAKED target_push (.5 .5 .5) (-8 -8 -8) (8 8 8) ENERGYNOISE -----DESCRIPTION----- Pushes the activator in the direction of angle, or towards a target apex. This is predicted on the serverside and is triggered by use-function. -----SPAWNFLAGS----- 1: ENERGYNOISE - play energy noise instead of windfly -----KEYS----- "speed" - defaults to 1000 "target" - apex of the leap. Must be a target_position or info_notnull. */ void SP_target_push(gentity_t* self) { G_LogFuncBegin(); self->type = EntityType::ENT_TARGET_PUSH; if (self->speed <= 0) { self->speed = 1000; } G_SetMovedir(self->s.angles, self->s.origin2); VectorScale(self->s.origin2, self->speed, self->s.origin2); if (self->spawnflags & 1) { self->noise_index = G_SoundIndex("sound/ambience/forge/antigrav.wav"); } else { self->noise_index = G_SoundIndex("sound/misc/windfly.wav"); /* fixme need sound! */ } if (self->target) { VectorCopy(self->s.origin, self->r.absmin); VectorCopy(self->s.origin, self->r.absmax); self->think = AimAtTarget; self->nextthink = level.time + FRAMETIME; } self->use = Use_target_push; G_LogFuncEnd(); } /* ============================================================================== trigger_teleport ============================================================================== */ gentity_t* SelectRandomSpawnPoint(void) { gentity_t* spot = NULL; int count = 0; int selection = 0; gentity_t* spots[MAX_TRANSPORTER_POINTS]; char* classname; G_LogFuncBegin(); classname = "info_player_deathmatch"; while ((spot = G_Find(spot, FOFS(classname), classname)) != NULL) { spots[count++] = spot; if (count == MAX_TRANSPORTER_POINTS) { break; } } if (count == 0) { /* no spawn points !!??! */ G_LocLogger(LL_ERROR, "no spawn points found!\n"); G_LogFuncEnd(); return NULL; } selection = rand() % count; G_LogFuncEnd(); return spots[selection]; } /** * @brief Touch function of trigger_teleport. * * @param self the trigger * @param other the touching entity * @param trace a trace */ static void trigger_teleporter_touch(gentity_t* self, gentity_t* other, trace_t* trace) { gentity_t* dest = NULL; vec3_t destPoint = { 0, 0, 0 }; trace_t tr; vec3_t tracePoint = { 0, 0, 0 }; int clientNum = 0; G_LogFuncBegin(); if ((self->flags & FL_LOCKED) != 0) { G_LocLogger(LL_DEBUG, "locked"); G_LogFuncEnd(); return; } if (other->client == NULL) { G_LocLogger(LL_DEBUG, "other->client is NULL\n"); G_LogFuncEnd(); return; } if (other->client->ps.pm_type == PM_DEAD) { G_LocLogger(LL_DEBUG, "other->client is dead\n"); G_LogFuncEnd(); return; } /* Spectators only? */ if (((self->spawnflags & 1) != 0) && other->client->sess.sessionTeam != TEAM_SPECTATOR) { G_LocLogger(LL_DEBUG, "other->client is spectator\n"); G_LogFuncEnd(); return; } clientNum = other->client->ps.clientNum; /* BOOKMARK J2J */ if ((self->spawnflags & 2) != 0) { /* find a random spawn point */ dest = SelectRandomSpawnPoint(); } else { dest = G_PickTarget(self->target); } if (dest == NULL) { G_LocLogger(LL_ERROR, "Couldn't find teleporter destination\n"); G_LogFuncEnd(); return; } /* suspended */ if ((self->spawnflags & 8) != 0) { /* put the bottom of the player's bbox at the bottom of the target's bbox */ VectorCopy(dest->s.origin, destPoint); destPoint[2] += dest->r.mins[2]; destPoint[2] -= other->r.mins[2]; /* fudge it upwards just a bit */ destPoint[2] += 1; } else { VectorCopy(dest->s.origin, tracePoint); tracePoint[2] -= 4096.0f; trap_Trace(&tr, dest->s.origin, dest->r.mins, dest->r.maxs, tracePoint, dest->s.number, MASK_PLAYERSOLID); VectorCopy(tr.endpos, destPoint); /* offset the player's bounding box. */ destPoint[2] += dest->r.mins[2]; destPoint[2] -= other->r.mins[2]; /* add 1 to ensure non-direct collision */ destPoint[2] += 1; } if (self->health != 0) { /* * TiM - Transporter VFX * Only if no spitting though. Having spitting transporter with these VFX would look freaky weird */ if (self->health == -1 && (self->spawnflags & 4)) { if (TransDat[clientNum].beamTime == 0) { G_InitTransport(clientNum, destPoint, dest->s.angles); } } else { TransportPlayer(other, destPoint, dest->s.angles, (self->health == -1) ? 0 : self->health); } } else { /* Transporter VFX */ if (self->spawnflags & 4) { if (TransDat[clientNum].beamTime == 0) { G_InitTransport(clientNum, destPoint, dest->s.angles); } } else { TeleportPlayer(other, destPoint, dest->s.angles, TP_NORMAL); } } if (self->sound1to2 && self->wait && !(self->flags & FL_CLAMPED)) { G_AddEvent(self, EV_GENERAL_SOUND, self->sound1to2); self->flags ^= FL_CLAMPED; } G_LogFuncEnd(); } /** * @brief Think function of trigger_teleport. * * @param ent the trigger */ static void trigger_teleport_think(gentity_t* ent) { G_LogFuncBegin(); ent->nextthink = -1; if ((ent->flags & FL_LOCKED) == 0) { ent->flags ^= FL_LOCKED; } if (ent->wait > 0 && ((ent->flags & FL_CLAMPED) != 0)) { ent->flags ^= FL_CLAMPED; } G_LogFuncEnd(); } /** * @brief Use function of trigger_teleport. * * @param ent the trigger * @param other another entity * @param activator the activator */ static void trigger_teleport_use(gentity_t* ent, gentity_t* other, gentity_t* activator) { G_LogFuncBegin(); if (Q_stricmp(ent->swapname, activator->target) == 0) { if (ent->flags & FL_LOCKED) { ent->nextthink = level.time + (ent->wait * 1000); } ent->flags ^= FL_LOCKED; } G_LogFuncEnd(); } /*QUAKED trigger_teleport (.5 .5 .5) ? SPECTATOR RANDOM VISUAL_FX SUSPENDED DEACTIVATED -----DESCRIPTION----- Allows client side prediction of teleportation events. Must point at a target_position or info_notnull, which will be the teleport destination. -----SPAWNFLAGS----- 1: SPECTATOR: If set, only spectators can use this teleport. Spectator teleporters are not normally placed in the editor, but are created automatically near doors to allow spectators to move through them. 2: RANDOM: send player to random info_player_deathmatch spawn point 4: VISUAL_FX: plays the Star Trek transporter FX and beams the player out slowly 8: SUSPENDED: player appears with the bounding box aligned to the bottom of the target If this isn't set, the player materializes at the first solid surface under it 16: DEACTIVATED: Spawns deactivated -----KEYS----- "swapname" - ACTIVATE/DEACTIVATE (Using entity needs SELF/NOACTIVATOR) "wait" - time before trigger deactivates itself automatically "soundstart" - sound to play if triggered "health" - default is original behavior (speed of 400), any other value will be the speed at which the player is spewed forth from the tranpsorter destination. -1 if you want no speed. The transporter VISUAL_FX flag will only work if the health is set to 0 or -1 as it cannot support 'spewing'. */ void SP_trigger_teleport(gentity_t* self) { char* temp = NULL; G_LogFuncBegin(); self->type = EntityType::ENT_TRIGGER_TELEPORT; G_Trigger_Init(self); /* * unlike other triggers, we need to send this one to the client * unless is a spectator trigger */ if ((self->spawnflags & 1) != 0) { self->r.svFlags |= SVF_NOCLIENT; } else { self->r.svFlags &= ~SVF_NOCLIENT; } self->s.eType = ET_TELEPORT_TRIGGER; self->touch = trigger_teleporter_touch; if ((self->spawnflags & 16) != 0) self->flags ^= FL_LOCKED; self->use = trigger_teleport_use; if (self->wait > 0) { self->nextthink = -1; self->think = trigger_teleport_think; } if (G_SpawnString("soundstart", "", &temp)) { self->sound1to2 = G_SoundIndex(temp); } VectorCopy(self->r.maxs, self->s.origin2); VectorCopy(self->r.mins, self->s.angles2); trap_LinkEntity(self); level.numBrushEnts++; G_LogFuncEnd(); } /* ============================================================================== trigger_hurt ============================================================================== */ /*QUAKED trigger_hurt (.5 .5 .5) ? START_OFF TOGGLE SILENT NO_PROTECTION SLOW EVO_PROTECT NO_ADMIN -----DESCRIPTION----- Any entity that touches this will be hurt. It does dmg points of damage each server frame Targeting the trigger will toggle its on / off state. -----SPAWNFLAGS----- 1: START_OFF - trigger will not be doing damage until toggled on 2: TOGGLE - can be toggled 4: SILENT - supresses playing the sound 8: NO_PROTECTION - *nothing* stops the damage 16: SLOW - changes the damage rate to once per second 32: EVO_PROTECT - Evosuit protects the client, even if NO_PROTECTION is set 64: NO_ADMIN - admins don't get hurt, even if NO_PROTECTION is set -----KEYS----- "dmg" - default 5 (whole numbers only) */ /** * @brief Use function of trigger_hurt. * @param self the trigger * @param other another entity * @param activator the activator */ static void hurt_use(gentity_t* self, gentity_t* other, gentity_t* activator) { G_LogFuncBegin(); if (self->count != 0) { self->count = 0; } else { self->count = 1; } G_LogFuncEnd(); } /** * @brief Touch function of trigger_hurt. * @param self the trigger * @param other the touching entity * @param trace a trace */ static void hurt_touch(gentity_t* self, gentity_t* other, trace_t* trace) { int dflags; G_LogFuncBegin(); if (self->count == 0) { G_LocLogger(LL_DEBUG, "self->count == 0\n"); G_LogFuncEnd(); return; } /* * RPG-X | Phenix | 8/8/204 * (If the guy is wearning an evosuit) */ if (((self->spawnflags & 32) != 0) && ((other->flags & FL_EVOSUIT) != 0)) { G_LocLogger(LL_DEBUG, "evosuit protection\n"); G_LogFuncEnd(); return; } if ((self->spawnflags & 64) != 0) { if (G_Client_IsAdmin(other)) { G_LocLogger(LL_DEBUG, "admins are protected\n"); G_LogFuncEnd(); return; } } if (!other->takedamage) { G_LocLogger(LL_DEBUG, "other->takedamage is false\n"); G_LogFuncEnd(); return; } if (self->timestamp > level.time) { G_LocLogger(LL_DEBUG, "self->timestamp > level.time\n"); G_LogFuncEnd(); return; } /* RPG-X | Phenix | 9/8/2004 */ if (other->health <= 1) { G_LocLogger(LL_DEBUG, "other->health <= 1\n"); G_LogFuncEnd(); return; } if ((self->spawnflags & 16) != 0) { self->timestamp = level.time + 1000; } else { self->timestamp = level.time + FRAMETIME; } /* play sound */ if ((self->spawnflags & 4) == 0) { G_Sound(other, self->noise_index); } if ((self->spawnflags & 8) != 0) { dflags = DAMAGE_NO_PROTECTION; } else { dflags = 0; } G_Combat_Damage(other, self, self, NULL, NULL, self->damage, dflags, MOD_TRIGGER_HURT); G_LogFuncEnd(); } void SP_trigger_hurt(gentity_t* self) { G_LogFuncBegin(); self->type = EntityType::ENT_TRIGGER_HURT; G_Trigger_Init(self); /* TiM - gets very annoying after a while */ self->noise_index = G_SoundIndex("sound/world/electro.wav"); self->touch = hurt_touch; if (self->damage == 0) { self->damage = 5; } self->r.contents = CONTENTS_TRIGGER; if ((self->spawnflags & 2) != 0) { self->use = hurt_use; } if ((self->spawnflags & 1) == 0) { self->count = 1; } else { self->count = 0; } VectorCopy(self->r.maxs, self->s.origin2); VectorCopy(self->r.mins, self->s.angles2); trap_LinkEntity(self); level.numBrushEnts++; G_LogFuncEnd(); } /* ============================================================================== timer This should be renamed trigger_timer... ============================================================================== */ /*QUAKED func_timer (0.3 0.1 0.6) (-8 -8 -8) (8 8 8) START_ON -----DESCRIPTION----- Fires its targets every "wait" seconds. Can be turned on or off by using. -----SPAWNFLAGS----- 1: START_ON - will be on at spawn and setting up for it's first intervall -----KEYS----- "wait" - base time between triggering all targets, default is 1 "random" - wait variance, default is 0 so, the basic time between firing is a random time between (wait - random) and (wait + random) */ /** * @brief Think function of func_timer. * @param self the entity */ static void func_timer_think(gentity_t* self) { G_LogFuncBegin(); G_UseTargets(self, self->activator); /* set time before next firing */ self->nextthink = level.time + 1000 * (self->wait + crandom() * self->random); G_LogFuncEnd(); } /** * @brief Use function of func_timer. * @param self the entity * @param other another entity * @param activator the activator */ static void func_timer_use(gentity_t* self, gentity_t* other, gentity_t* activator) { G_LogFuncBegin(); self->activator = activator; /* if on, turn it off */ if (self->nextthink != 0) { self->nextthink = 0; G_LogFuncEnd(); return; } /* turn it on */ func_timer_think(self); G_LogFuncEnd(); } void SP_func_timer(gentity_t* self) { G_LogFuncBegin(); self->type = EntityType::ENT_FUNC_TIMER; G_SpawnFloat("random", "1", &self->random); G_SpawnFloat("wait", "1", &self->wait); self->use = func_timer_use; self->think = func_timer_think; if (self->random >= self->wait) { self->random = self->wait - FRAMETIME; DEVELOPER(G_Printf(S_COLOR_YELLOW "[Entity-Warning] func_timer at %s has random >= wait\n", vtos(self->s.origin));); } if ((self->spawnflags & 1) != 0) { self->nextthink = level.time + FRAMETIME; self->activator = self; } self->r.svFlags = SVF_NOCLIENT; G_LogFuncEnd(); } /*QUAKED trigger_transporter (0.5 0.5 0.5) ? -----DESCRIPTION----- This is used in combination with ui_transporter. Have this be targeted by ui_transporter. -----SPAWNFLAGS----- none -----KEYS----- "wait" time to wait before trigger gets deactivated again(in seconds, default 5) "soundstart" transport sound; */ /** * @brief Think function of trigger_transporter. * @param ent the trigger */ void trigger_transporter_think(gentity_t *ent) { G_LogFuncBegin(); if (Q_stricmp(ent->classname, "tent") != 0) { ent->nextthink = -1; ent->flags ^= FL_LOCKED; ent->flags ^= FL_CLAMPED; } else { ent->target_ent->flags ^= FL_LOCKED; ent->target_ent->flags ^= FL_CLAMPED; G_FreeEntity(ent); } G_LogFuncEnd(); } /** * @brief Think function of trigger_transporter for server change. * @param ent the trigger */ static void trigger_transporter_serverchange(gentity_t* ent) { G_LogFuncBegin(); trap_SendServerCommand(ent->touched - g_entities, va("cg_connect \"%s\"\n", ent->targetname2)); G_FreeEntity(ent); G_LogFuncEnd(); } /** * @brief Touch function of trigger_transporter. * @param ent the trigger * @param other the touching entity * @param trace a trace */ static void trigger_transporter_touch(gentity_t* ent, gentity_t* other, trace_t* trace) { char* srv = NULL; vec3_t offset = { 0, 0, 12 }; vec3_t target = { 0, 0, 0 }; gentity_t* targetEnt = NULL; gentity_t* tent = NULL; gentity_t* tent2 = NULL; int clientNum = 0; G_LogFuncBegin(); if ((ent->flags & FL_LOCKED) != 0) { G_LocLogger(LL_DEBUG, "locked\n"); G_LogFuncEnd(); return; } if (other == NULL) { G_LocLogger(LL_DEBUG, "other entity is NULL\n"); G_LogFuncEnd(); return; } if ((other->flags & FL_CLAMPED) != 0) { G_LocLogger(LL_DEBUG, "other entity is clamped\n"); G_LogFuncEnd(); return; } if (other->client == NULL) { G_LocLogger(LL_DEBUG, "other entity is not a client\n"); G_LogFuncEnd(); return; } clientNum = other->client->ps.clientNum; if ((ent->sound1to2 != 0) && ((ent->flags & FL_CLAMPED) == 0)) { G_AddEvent(other, EV_GENERAL_SOUND, ent->sound1to2); ent->flags ^= FL_CLAMPED; } other->flags ^= FL_CLAMPED; if (ent->count == 0) if (ent->target != NULL && ent->target_ent->target != NULL) { targetEnt = G_PickTarget(ent->target_ent->target); VectorAdd(targetEnt->s.origin, offset, target); G_InitTransport(clientNum, target, targetEnt->s.angles); } else { G_InitTransport(clientNum, ent->target_ent->s.origin, ent->target_ent->s.angles); } else { srv = level.srvChangeData[ent->health].m_ip.data(); tent = G_Spawn(); if (tent == NULL) { G_LocLogger(LL_ERROR, "Could not spawn new entity!\n"); G_LogFuncEnd(); return; } tent->think = trigger_transporter_serverchange; tent->nextthink = level.time + 3000; TransDat[clientNum].beamTime = level.time + 8000; other->client->ps.powerups[PW_BEAM_OUT] = level.time + 8000; tent->touched = other; tent->targetname2 = G_NewString(srv); tent2 = G_Spawn(); if (tent2 == NULL) { G_FreeEntity(tent); G_LocLogger(LL_ERROR, "Could not spawn new entity!\n"); G_LogFuncEnd(); return; } tent2->classname = G_NewString("tent"); tent2->target_ent = ent; tent2->think = trigger_transporter_think; tent2->nextthink = level.time + ent->wait; } G_LogFuncEnd(); } /** * @brief Delay think function of trigger_transporter. * @param ent the trigger */ void trigger_transporter_delay(gentity_t* ent) { G_LogFuncBegin(); ent->think = trigger_teleport_think; ent->nextthink = level.time + ent->wait; ent->flags ^= FL_LOCKED; G_LogFuncEnd(); } void SP_trigger_transporter(gentity_t* ent) { char* temp = NULL; G_LogFuncBegin(); ent->type = EntityType::ENT_TRIGGER_TRANSPORTER; G_Trigger_Init(ent); if (G_SpawnString("soundstart", "", &temp)) ent->sound1to2 = G_SoundIndex(temp); if (ent->wait <= 0) { ent->wait = 5; } ent->wait *= 1000; ent->touch = trigger_transporter_touch; ent->flags ^= FL_LOCKED; VectorCopy(ent->r.maxs, ent->s.apos.trBase); VectorCopy(ent->r.mins, ent->s.pos.trBase); trap_LinkEntity(ent); level.numBrushEnts++; G_LogFuncEnd(); } /*QUAKED trigger_radiation (0.5 0.5 0.5) ? START_OFF MAP_WIDE -----DESCRIPTION----- This can be used in three ways: - as radiation volume trigger - as mapwide radiation -----SPAWNFLAGS----- 1: START_OFF - ent is off at spawn 2: MAP_WIDE - mapwide radiation -----KEYS----- The damage the radiation does is calculated from these two values: "damage" damage(default 1) "wait" wait(seconds, default 10) Forumla is: dps = dmg / wait */ /** * @brief Touch function of trigger_radiation. * @param ent the trigger * @param other the touching entity * @param trace a trace */ static void trigger_radiation_touch(gentity_t* ent, gentity_t* other, trace_t* trace) { G_LogFuncBegin(); if (other == NULL) { G_LocLogger(LL_DEBUG, "other entity is NULL\n"); G_LogFuncEnd(); return; } if (other->client == NULL) { G_LocLogger(LL_DEBUG, "other entity is not a client\n"); G_LogFuncEnd(); return; } if (ent->count == 0) { G_LocLogger(LL_DEBUG, "ent->count is 0\n"); G_LogFuncEnd(); return; } if ((ent->flags & FL_LOCKED) == 0) { if (other->health - ent->damage < 0) { other->health = 0; } else { other->health -= ent->damage; } other->client->ps.stats[STAT_HEALTH] = other->health; ent->flags ^= FL_LOCKED; } /* TODO: display radiation symbol? */ G_LogFuncEnd(); } /** * @brief Think function of trigger_radiation. * @param ent the trigger */ static void trigger_radiation_think(gentity_t* ent) { G_LogFuncBegin(); if ((ent->flags & FL_LOCKED) != 0) { ent->flags ^= FL_LOCKED; } G_LogFuncEnd(); } /** * @brief Use function of trigger_radiation. * @param ent the trigger * @param other another entity * @param activator the activator */ static void trigger_radiation_use(gentity_t* ent, gentity_t* other, gentity_t* activator) { G_LogFuncBegin(); if (ent->count == 0) { ent->count = 1; } else { ent->count = 0; } G_LogFuncEnd(); } void SP_trigger_radiation(gentity_t* ent) { G_LogFuncBegin(); ent->type = EntityType::ENT_TRIGGGER_RADIATION; if (ent->damage == 0) { ent->damage = 1; } if (ent->wait <= 0) { ent->wait = 10000; } else { ent->wait *= 1000; } G_Trigger_Init(ent); if ((ent->spawnflags & 1) == 0) { ent->count = 1; } else { ent->count = 0; } if ((ent->spawnflags & 2) == 0) { ent->touch = trigger_radiation_touch; } ent->think = trigger_radiation_think; ent->nextthink = level.time + ent->wait; ent->flags ^= FL_LOCKED; ent->use = trigger_radiation_use; VectorCopy(ent->r.maxs, ent->s.apos.trBase); VectorCopy(ent->r.mins, ent->s.pos.trBase); trap_LinkEntity(ent); if (!ent->tmpEntity) { level.numBrushEnts++; } G_LogFuncEnd(); } /*QUAKED trigger_airlock (0.5 0.5 0.5) ? -----DESCRIPTION----- This is an entity that manages airlocks. It can be used for Maintenance-Locks (Space Walks) internal Airlocks (for example to a quarantene zone) or... as a way to abandon ship the hard way ^^ In that last case even an EVA-Suit won't protect you. It is controlled by User Interface. The Entity automatically features door management in a cycle, a check for an EVA-Suit (trigger_hurt) and a push to get overboard (trigger_push). It is hardcoded to expect func_doors for entrance on either side and a func_forcefiled for ejecting (the forcefield is best placed within the outer door). For Setup please set both sets of doors to wait = -1 and have the inner (if you select spawnflag no. 1 the outer) door spawn in it's open state. -----SPAWNFLAGS----- 1: START_OUTSIDE - assumes that the outside door is open and set's itself up approopriately to cycle in at first use 2: NO_VENT - Will not check for push-target and forcefield that are required to vent the airlock. 4: QUARTANTENE_LOCK - airlock will be considered as an internal airlock to a quarantene zone or similar. this will not kill and will not check for ejection-stuff, so NO_VENT is included in this spawnflag. -----KEYS----- "wait" time to wait before trigger gets deactivated again(in seconds, default 5) "soundstart" transport sound; */