mirror of
https://github.com/blendogames/thirtyflightsofloving.git
synced 2024-11-14 16:40:57 +00:00
3ce18b138c
Simplified loading and saving of cvars in slider menu control. Enlarged text in Game, Multiplayer, and Options menus. Fixed repeat of failed file causing HTTP downloads to restart. Added cl_zoommode cvar to simplify Lazarus zoom command. Changed zoom command to use new cl_zoommode cvar in default Lazarus and missionpack DLLs. Removed unused "crossh" cvar in default Lazarus and missionpack DLLs. Fixed Makron not having a classname when spawned from Jorg in default Lazarus and missionpack DLLs. Made Tactician Gunner ignore small amounts of damage in missionpack DLL.
4683 lines
No EOL
129 KiB
C
4683 lines
No EOL
129 KiB
C
/*
|
|
===========================================================================
|
|
Copyright (C) 1997-2001 Id Software, Inc.
|
|
Copyright (C) 2000-2002 Mr. Hyde and Mad Dog
|
|
|
|
This file is part of Lazarus Quake 2 Mod source code.
|
|
|
|
Lazarus Quake 2 Mod source code is free software; you can redistribute it
|
|
and/or modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of the License,
|
|
or (at your option) any later version.
|
|
|
|
Lazarus Quake 2 Mod source code is distributed in the hope that it will be
|
|
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Lazarus Quake 2 Mod source code; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "g_local.h"
|
|
|
|
#define IF_VISIBLE 8
|
|
#define SEEK_PLAYER 128
|
|
|
|
|
|
/*QUAKED target_temp_entity (1 0 0) (-8 -8 -8) (8 8 8)
|
|
Fire an origin based temp entity event to the clients.
|
|
"style" type byte
|
|
*/
|
|
void Use_Target_Tent (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (self->style);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
|
|
}
|
|
|
|
void SP_target_temp_entity (edict_t *ent)
|
|
{
|
|
ent->class_id = ENTITY_TARGET_TEMP_ENTITY;
|
|
|
|
ent->use = Use_Target_Tent;
|
|
}
|
|
|
|
|
|
//==========================================================
|
|
|
|
//==========================================================
|
|
|
|
/*QUAKED target_speaker (1 0 0) (-8 -8 -8) (8 8 8) looped-on looped-off reliable changelevel
|
|
"noise" wav file to play
|
|
"attenuation"
|
|
DWH
|
|
-2 = only played (full volume) for player who triggered the target_speaker
|
|
end DWH
|
|
|
|
-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.
|
|
|
|
Changelevel spawnflag added for Lazarus. This should ONLY be applied in the code,
|
|
and is an indication that the "message" key contains the noise.
|
|
*/
|
|
void Use_Target_Speaker (edict_t *ent, edict_t *other, edict_t *activator)
|
|
{
|
|
int chan;
|
|
|
|
if (ent->spawnflags & 3)
|
|
{ // looping sound toggles
|
|
if (ent->s.sound) {
|
|
ent->s.sound = 0; // turn it off
|
|
ent->nextthink = 0;
|
|
}
|
|
else {
|
|
ent->s.sound = ent->noise_index; // start it
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
ent->s.attenuation = ent->attenuation;
|
|
#endif
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ent->attenuation == -2)
|
|
{
|
|
if (ent->spawnflags & 4)
|
|
chan = CHAN_VOICE|CHAN_RELIABLE;
|
|
else
|
|
chan = CHAN_VOICE;
|
|
gi.sound (activator, chan, ent->noise_index, 1, ATTN_NORM, 0);
|
|
}
|
|
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);
|
|
}
|
|
|
|
ent->count--;
|
|
if (!ent->count) {
|
|
ent->think = G_FreeEdict;
|
|
ent->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
void SP_target_speaker (edict_t *ent)
|
|
{
|
|
size_t msgSize;
|
|
|
|
ent->class_id = ENTITY_TARGET_SPEAKER;
|
|
|
|
if (!(ent->spawnflags & 8))
|
|
{
|
|
if (!st.noise)
|
|
{
|
|
gi.dprintf("target_speaker with no noise set at %s\n", vtos(ent->s.origin));
|
|
G_FreeEdict(ent);
|
|
return;
|
|
}
|
|
// DWH: Use "message" key to store noise for speakers that change levels
|
|
// via trigger_transition
|
|
if (!strstr (st.noise, ".wav"))
|
|
{
|
|
msgSize = strlen(st.noise)+5;
|
|
ent->message = gi.TagMalloc(msgSize, TAG_LEVEL);
|
|
Com_sprintf (ent->message, msgSize, "%s.wav", st.noise);
|
|
}
|
|
else
|
|
{
|
|
msgSize = strlen(st.noise)+1;
|
|
ent->message = gi.TagMalloc(msgSize, TAG_LEVEL);
|
|
Q_strncpyz (ent->message, msgSize, st.noise);
|
|
}
|
|
}
|
|
|
|
ent->noise_index = gi.soundindex (ent->message);
|
|
ent->spawnflags &= ~8;
|
|
|
|
if (!ent->volume)
|
|
ent->volume = 1.0;
|
|
|
|
if (!ent->attenuation)
|
|
ent->attenuation = (ent->spawnflags & 1) ? 3.0 : 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;
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
ent->s.attenuation = ent->attenuation;
|
|
#endif
|
|
}
|
|
|
|
ent->use = Use_Target_Speaker;
|
|
|
|
// must link the entity so we get areas and clusters so
|
|
// the server can determine who to send updates to
|
|
gi.linkentity (ent);
|
|
}
|
|
|
|
|
|
//==========================================================
|
|
|
|
void Use_Target_Help (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->message)
|
|
{
|
|
if (self->spawnflags & 1)
|
|
// strncpy (game.helpmessage1, self->message, sizeof(game.helpmessage2)-1);
|
|
Q_strncpyz (game.helpmessage1, sizeof(game.helpmessage2), self->message);
|
|
else
|
|
// strncpy (game.helpmessage2, self->message, sizeof(game.helpmessage1)-1);
|
|
Q_strncpyz (game.helpmessage2, sizeof(game.helpmessage1), self->message);
|
|
}
|
|
|
|
game.helpchanged++;
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
/*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 (deathmatch->value)
|
|
{ // auto-remove for deathmatch
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
// Lazarus: we allow blank message if world->effects is "help=pic only"
|
|
if (!ent->message && !(world->effects & FX_WORLDSPAWN_NOHELP))
|
|
{
|
|
gi.dprintf ("%s with no message at %s\n", ent->classname, vtos(ent->s.origin));
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
ent->class_id = ENTITY_TARGET_HELP;
|
|
|
|
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.
|
|
|
|
Lazarus:
|
|
DISABLED SF=1
|
|
|
|
*/
|
|
void use_target_secret (edict_t *ent, edict_t *other, edict_t *activator)
|
|
{
|
|
if (ent->spawnflags & 1)
|
|
{
|
|
ent->spawnflags &= ~1;
|
|
level.total_secrets++;
|
|
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 (deathmatch->value)
|
|
{ // auto-remove for deathmatch
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
ent->class_id = ENTITY_TARGET_SECRET;
|
|
|
|
ent->use = use_target_secret;
|
|
if (!st.noise)
|
|
st.noise = "misc/secret.wav";
|
|
ent->noise_index = gi.soundindex (st.noise);
|
|
ent->svflags = SVF_NOCLIENT;
|
|
|
|
if (!(ent->spawnflags & 1))
|
|
level.total_secrets++;
|
|
|
|
// map bug hack
|
|
if (!Q_stricmp(level.mapname, "mine3") && ent->s.origin[0] == 280 && ent->s.origin[1] == -2048 && ent->s.origin[2] == -624)
|
|
ent->message = "You have found a secret area.";
|
|
}
|
|
|
|
//==========================================================
|
|
|
|
/*QUAKED target_goal (1 0 1) (-8 -8 -8) (8 8 8)
|
|
Counts a goal completed.
|
|
These are single use targets.
|
|
|
|
Lazarus:
|
|
DISABLED SF=1
|
|
|
|
*/
|
|
void use_target_goal (edict_t *ent, edict_t *other, edict_t *activator)
|
|
{
|
|
if (ent->spawnflags & 1)
|
|
{
|
|
ent->spawnflags &= ~1;
|
|
level.total_goals++;
|
|
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 (deathmatch->value)
|
|
{ // auto-remove for deathmatch
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
ent->class_id = ENTITY_TARGET_GOAL;
|
|
|
|
ent->use = use_target_goal;
|
|
if (!st.noise)
|
|
st.noise = "misc/secret.wav";
|
|
ent->noise_index = gi.soundindex (st.noise);
|
|
ent->svflags = SVF_NOCLIENT;
|
|
|
|
if (!(ent->spawnflags & 1))
|
|
level.total_goals++;
|
|
}
|
|
|
|
//==========================================================
|
|
|
|
|
|
/*QUAKED target_explosion (1 0 0) (-8 -8 -8) (8 8 8) BIG
|
|
Spawns an explosion temporary entity when used.
|
|
|
|
BIG Do you want a larger explosion model?
|
|
"delay" wait this long before going dff
|
|
"dmg" how much radius damage should be done, defaults to 0
|
|
*/
|
|
void target_explosion_explode (edict_t *self)
|
|
{
|
|
float save;
|
|
int eventNum;
|
|
|
|
if (self->spawnflags & 1) // Knightmare- big explosion
|
|
eventNum = TE_EXPLOSION1_BIG;
|
|
else
|
|
eventNum = TE_EXPLOSION1;
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
// if (self->spawnflags & 1) // Knightmare- big explosion
|
|
// gi.WriteByte (TE_EXPLOSION1_BIG);
|
|
// else
|
|
// gi.WriteByte (TE_EXPLOSION1);
|
|
gi.WriteByte (eventNum);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_PHS);
|
|
|
|
if (level.num_reflectors)
|
|
// ReflectExplosion (TE_EXPLOSION1, self->s.origin);
|
|
ReflectExplosion (eventNum, self->s.origin);
|
|
|
|
T_RadiusDamage (self, self->activator, self->dmg, NULL, self->dmg+40, MOD_EXPLOSIVE, -0.5);
|
|
|
|
save = self->delay;
|
|
self->delay = 0;
|
|
G_UseTargets (self, self->activator);
|
|
self->delay = save;
|
|
|
|
self->count--;
|
|
if (!self->count)
|
|
{
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
void use_target_explosion (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->activator = activator;
|
|
|
|
if (!self->delay)
|
|
{
|
|
target_explosion_explode (self);
|
|
return;
|
|
}
|
|
|
|
self->think = target_explosion_explode;
|
|
self->nextthink = level.time + self->delay;
|
|
}
|
|
|
|
void SP_target_explosion (edict_t *ent)
|
|
{
|
|
ent->class_id = ENTITY_TARGET_EXPLOSION;
|
|
|
|
ent->use = use_target_explosion;
|
|
ent->svflags = SVF_NOCLIENT;
|
|
}
|
|
|
|
|
|
//==========================================================
|
|
|
|
/*QUAKED target_changelevel (1 0 0) (-8 -8 -8) (8 8 8)
|
|
Changes level to "map" when fired
|
|
|
|
Lazarus spawnflags:
|
|
1 CLEAR_INVENTORY: Removes all pickups other than weapons, restore health to 100
|
|
2 LANDMARK: If set, player position when spawning in the next map will be at the
|
|
same offset from the info_player_start as his current position relative
|
|
to the target_changelevel. Velocity, angles, and crouch state will be
|
|
preserved across maps.
|
|
4 NO_GUN Sets cl_gun 0 and crosshair 0 for the next map/demo only
|
|
8 EASY Sets skill 0 for next map
|
|
16 NORMAL Sets skill 1 for next map
|
|
32 HARD Sets skill 2 for next map
|
|
64 NIGHTMARE Sets skill 3 for next map
|
|
*/
|
|
void use_target_changelevel (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *transition;
|
|
extern int nostatus;
|
|
|
|
if (level.intermissiontime)
|
|
return; // already activated
|
|
|
|
if (!deathmatch->value && !coop->value)
|
|
{
|
|
if (g_edicts[1].health <= 0)
|
|
return;
|
|
}
|
|
|
|
// if noexit, do a ton of damage to other
|
|
if (deathmatch->value && !( (int)dmflags->value & DF_ALLOW_EXIT) && other != world)
|
|
{
|
|
T_Damage (other, self, self, vec3_origin, other->s.origin, vec3_origin, 10 * other->max_health, 1000, 0, MOD_EXIT);
|
|
return;
|
|
}
|
|
|
|
// if multiplayer, let everyone know who hit the exit
|
|
if (deathmatch->value)
|
|
{
|
|
if (activator && activator->client)
|
|
safe_bprintf (PRINT_HIGH, "%s exited the level.\n", activator->client->pers.netname);
|
|
}
|
|
|
|
if (activator->client)
|
|
{
|
|
if (activator->client->chasetoggle)
|
|
{
|
|
ChasecamRemove (activator, OPTION_OFF);
|
|
activator->client->pers.chasetoggle = 1;
|
|
}
|
|
else
|
|
activator->client->pers.chasetoggle = 0;
|
|
|
|
if (!activator->vehicle)
|
|
activator->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
}
|
|
|
|
// if going to a new unit, clear cross triggers
|
|
if (strstr(self->map, "*"))
|
|
{
|
|
// Knightmare- game.serverflags is ONLY used for cross-level trigger bits, so we can just zero it.
|
|
// game.serverflags &= ~(SFL_CROSS_TRIGGER_MASK);
|
|
game.serverflags = 0;
|
|
game.lock_code[0] = 0;
|
|
game.lock_revealed = 0;
|
|
game.lock_hud = 0;
|
|
game.transition_ents = 0;
|
|
if (activator->client)
|
|
{
|
|
activator->client->pers.spawn_landmark = false;
|
|
activator->client->pers.spawn_levelchange = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (self->spawnflags & 2 && activator->client)
|
|
{
|
|
activator->client->pers.spawn_landmark = true;
|
|
VectorSubtract(activator->s.origin,self->s.origin,
|
|
activator->client->pers.spawn_offset);
|
|
VectorCopy(activator->velocity,activator->client->pers.spawn_velocity);
|
|
VectorCopy(activator->s.angles,activator->client->pers.spawn_angles);
|
|
activator->client->pers.spawn_angles[ROLL] = 0;
|
|
VectorCopy(activator->client->ps.viewangles,activator->client->pers.spawn_viewangles);
|
|
activator->client->pers.spawn_pm_flags = activator->client->ps.pmove.pm_flags;
|
|
if (self->s.angles[YAW])
|
|
{
|
|
vec3_t angles;
|
|
vec3_t forward, right, v;
|
|
|
|
angles[PITCH] = angles[ROLL] = 0.;
|
|
angles[YAW] = self->s.angles[YAW];
|
|
AngleVectors(angles,forward,right,NULL);
|
|
VectorNegate(right,right);
|
|
VectorCopy(activator->client->pers.spawn_offset,v);
|
|
G_ProjectSource (vec3_origin,
|
|
v, forward, right,
|
|
activator->client->pers.spawn_offset);
|
|
VectorCopy(activator->client->pers.spawn_velocity,v);
|
|
G_ProjectSource (vec3_origin,
|
|
v, forward, right,
|
|
activator->client->pers.spawn_velocity);
|
|
activator->client->pers.spawn_angles[YAW] += angles[YAW];
|
|
activator->client->pers.spawn_viewangles[YAW] += angles[YAW];
|
|
}
|
|
}
|
|
else if (activator && activator->client) //Knightmare- paranoia
|
|
{
|
|
activator->client->pers.spawn_landmark = false;
|
|
}
|
|
|
|
if ((self->spawnflags & 4) && activator->client && !deathmatch->value && !coop->value)
|
|
{
|
|
nostatus = 1;
|
|
#ifdef KMQUAKE2_ENGINE_MOD // engine has zoom mode and autosensitivity
|
|
stuffcmd (activator, "cl_zoommode 1\n");
|
|
#else
|
|
stuffcmd (activator, "cl_gun 0;crosshair 0\n");
|
|
#endif
|
|
activator->client->pers.hand = 2;
|
|
}
|
|
|
|
if (activator && activator->client) //Knightmare- paranoia
|
|
{
|
|
activator->client->pers.spawn_levelchange = true;
|
|
activator->client->pers.spawn_gunframe = activator->client->ps.gunframe;
|
|
activator->client->pers.spawn_modelframe = activator->s.frame;
|
|
activator->client->pers.spawn_anim_end = activator->client->anim_end;
|
|
}
|
|
}
|
|
|
|
if (level.next_skill > 0)
|
|
{
|
|
gi.cvar_forceset("skill", va("%d",level.next_skill-1));
|
|
level.next_skill = 0; // reset
|
|
}
|
|
else if (self->spawnflags & 8)
|
|
gi.cvar_forceset("skill", "0");
|
|
else if (self->spawnflags & 16)
|
|
gi.cvar_forceset("skill", "1");
|
|
else if (self->spawnflags & 32)
|
|
gi.cvar_forceset("skill", "2");
|
|
else if (self->spawnflags & 64)
|
|
gi.cvar_forceset("skill", "3");
|
|
|
|
// Knightmare- some of id's stock Q2 maps have this spawnflag
|
|
// set on their trigger_changelevels, so only allow this on custom maps
|
|
if ( (self->spawnflags & 1) && (level.maptype == MAPTYPE_CUSTOM) && (int)allow_clear_inventory->value )
|
|
{
|
|
int n;
|
|
if (activator && activator->client)
|
|
{
|
|
for (n = 0; n < MAX_ITEMS; n++)
|
|
{
|
|
// Keep blaster
|
|
if (!(itemlist[n].flags & IT_WEAPON) || itemlist[n].weapmodel != WEAP_BLASTER )
|
|
activator->client->pers.inventory[n] = 0;
|
|
}
|
|
//Knightmare- always have null weapon
|
|
if (!deathmatch->value)
|
|
activator->client->pers.inventory[ITEM_INDEX(FindItem("No Weapon"))] = 1;
|
|
// Switch to blaster
|
|
if ( activator->client->pers.inventory[ITEM_INDEX(FindItem("blaster"))] )
|
|
activator->client->newweapon = FindItem ("blaster");
|
|
else
|
|
activator->client->newweapon = FindItem ("No Weapon");
|
|
ChangeWeapon(activator);
|
|
activator->client->pers.health = activator->health = 100;
|
|
}
|
|
}
|
|
game.transition_ents = 0;
|
|
if (self->spawnflags & 2 && activator->client)
|
|
{
|
|
transition = G_Find(NULL,FOFS(classname), "trigger_transition");
|
|
while (transition)
|
|
{
|
|
if (!Q_stricmp(transition->targetname,self->targetname))
|
|
{
|
|
game.transition_ents = trigger_transition_ents(self,transition);
|
|
break;
|
|
}
|
|
transition = G_Find(transition,FOFS(classname), "trigger_transition");
|
|
}
|
|
}
|
|
BeginIntermission (self);
|
|
}
|
|
|
|
void SP_target_changelevel (edict_t *ent)
|
|
{
|
|
if (!ent->map)
|
|
{
|
|
gi.dprintf("target_changelevel with no map at %s\n", vtos(ent->s.origin));
|
|
G_FreeEdict (ent);
|
|
return;
|
|
}
|
|
|
|
ent->class_id = ENTITY_TARGET_CHANGELEVEL;
|
|
|
|
if ((deathmatch->value || coop->value) && (ent->spawnflags & 2))
|
|
{
|
|
gi.dprintf("target_changelevel at %s\nLANDMARK only valid in single-player\n",
|
|
vtos(ent->s.origin));
|
|
ent->spawnflags &= ~2;
|
|
}
|
|
// ugly hack because *SOMEBODY* screwed up their map
|
|
if ((Q_stricmp(level.mapname, "fact1") == 0) && (Q_stricmp(ent->map, "fact3") == 0))
|
|
ent->map = "fact3$secret1";
|
|
|
|
ent->use = use_target_changelevel;
|
|
ent->svflags = SVF_NOCLIENT;
|
|
}
|
|
|
|
|
|
//==========================================================
|
|
|
|
/*QUAKED target_splash (1 0 0) (-8 -8 -8) (8 8 8)
|
|
Creates a particle splash effect when used.
|
|
|
|
Set "sounds" to one of the following:
|
|
1) sparks
|
|
2) blue water
|
|
3) brown water
|
|
4) slime
|
|
5) lava
|
|
6) blood
|
|
|
|
"count" how many pixels in the splash
|
|
"dmg" if set, does a radius damage at this location when it splashes
|
|
useful for lava/sparks
|
|
*/
|
|
|
|
|
|
void use_target_splash (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_SPLASH);
|
|
gi.WriteByte (self->count);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WriteDir (self->movedir);
|
|
gi.WriteByte (self->sounds);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
if (self->dmg)
|
|
T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH, -0.5);
|
|
}
|
|
|
|
void SP_target_splash (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_SPLASH;
|
|
|
|
self->use = use_target_splash;
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
if (!self->count)
|
|
self->count = 32;
|
|
self->svflags = SVF_NOCLIENT;
|
|
}
|
|
|
|
//==========================================================
|
|
|
|
/*QUAKED target_spawner (1 0 0) (-8 -8 -8) (8 8 8)
|
|
Set target to the type of entity you want spawned.
|
|
Useful for spawning monsters and gibs in the factory levels.
|
|
|
|
For monsters:
|
|
Set direction to the facing you want it to have.
|
|
|
|
For gibs:
|
|
Set direction if you want it moving and
|
|
speed how fast it should be moving otherwise it
|
|
will just be dropped
|
|
*/
|
|
|
|
void ED_CallSpawn (edict_t *ent);
|
|
|
|
void use_target_spawner (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *ent;
|
|
|
|
ent = G_Spawn();
|
|
ent->classname = self->target;
|
|
ent->spawnflags = self->spawnflags;
|
|
ent->flags = self->flags;
|
|
VectorCopy (self->s.origin, ent->s.origin);
|
|
VectorCopy (self->s.angles, ent->s.angles);
|
|
ED_CallSpawn (ent);
|
|
|
|
if (ent && ent->inuse) // catch spawn failure
|
|
{
|
|
gi.unlinkentity (ent);
|
|
KillBox (ent);
|
|
gi.linkentity (ent);
|
|
if (self->speed)
|
|
VectorCopy (self->movedir, ent->velocity);
|
|
}
|
|
else
|
|
G_FreeEdict(ent);
|
|
|
|
self->count--;
|
|
if (!self->count)
|
|
{
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
|
|
void SP_target_spawner (edict_t *self)
|
|
{
|
|
vec3_t forward;
|
|
vec3_t fact2spawnpoint1 = {-1504, 512, 72};
|
|
|
|
self->class_id = ENTITY_TARGET_SPEAKER;
|
|
|
|
self->use = use_target_spawner;
|
|
self->svflags = SVF_NOCLIENT;
|
|
|
|
// Knightmare- a horrendously ugly hack for the insane spawner on fact2
|
|
if ( !Q_stricmp(level.mapname, "fact2")
|
|
&& VectorCompare(self->s.origin, fact2spawnpoint1) )
|
|
{
|
|
//gi.dprintf("Moving target_spawner origin downward 8 units\n");
|
|
VectorSet (forward, 0, 0, 1);
|
|
VectorMA (self->s.origin, -8, forward, self->s.origin);
|
|
}
|
|
|
|
if (self->speed)
|
|
{
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
VectorScale (self->movedir, self->speed, self->movedir);
|
|
}
|
|
}
|
|
|
|
//==========================================================
|
|
|
|
/*QUAKED target_blaster (1 0 0) (-8 -8 -8) (8 8 8) NOTRAIL NOEFFECTS
|
|
Fires a blaster bolt in the set direction when triggered.
|
|
|
|
dmg default is 15
|
|
speed default is 1000
|
|
*/
|
|
|
|
/* Lazarus:
|
|
sounds - weapon choice
|
|
0 = blaster
|
|
1 = railgun
|
|
2 = rocket
|
|
3 = bfg
|
|
4 = homing rocket
|
|
5 = machinegun
|
|
6 = grenade
|
|
*/
|
|
|
|
void use_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
vec3_t movedir, start, target;
|
|
int effect;
|
|
|
|
VectorCopy(self->s.origin,start);
|
|
if (self->enemy)
|
|
{
|
|
if (self->sounds == 6)
|
|
{
|
|
if (!AimGrenade (self, start, self->enemy->s.origin, self->speed, movedir))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
VectorMA(self->enemy->absmin,0.5,self->enemy->size,target);
|
|
VectorSubtract(target,start,movedir);
|
|
VectorNormalize(movedir);
|
|
}
|
|
}
|
|
else
|
|
VectorCopy(self->movedir,movedir);
|
|
|
|
if (self->spawnflags & 2)
|
|
effect = 0;
|
|
else if (self->spawnflags & 1)
|
|
effect = EF_HYPERBLASTER;
|
|
else
|
|
effect = EF_BLASTER;
|
|
|
|
// Lazarus: weapon choices
|
|
if (self->sounds == 1)
|
|
{
|
|
fire_rail (self, start, movedir, self->dmg, 0);
|
|
gi.WriteByte (svc_muzzleflash);
|
|
gi.WriteShort (self-g_edicts);
|
|
gi.WriteByte (MZ_RAILGUN);
|
|
gi.multicast (start, MULTICAST_PVS);
|
|
}
|
|
else if (self->sounds == 2)
|
|
{
|
|
fire_rocket(self, start, movedir, self->dmg, self->speed, self->dmg, self->dmg, NULL);
|
|
gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
else if (self->sounds == 3)
|
|
{
|
|
fire_bfg(self, start, movedir, self->dmg, self->speed, self->dmg);
|
|
gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/laser2.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
else if (self->sounds == 4)
|
|
{
|
|
fire_rocket(self, start, movedir, self->dmg, self->speed, self->dmg, self->dmg, self->enemy);
|
|
gi.positioned_sound (start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
|
|
}
|
|
else if (self->sounds == 5)
|
|
{
|
|
fire_bullet(self, start, movedir, self->dmg, 2, 0, 0, MOD_TARGET_BLASTER);
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_CHAINFIST_SMOKE);
|
|
gi.WritePosition(start);
|
|
gi.multicast(start, MULTICAST_PVS);
|
|
gi.positioned_sound(start,self,CHAN_WEAPON,gi.soundindex(va("weapons/machgf%db.wav",rand() % 5 + 1)),1,ATTN_NORM,0);
|
|
}
|
|
else if (self->sounds == 6)
|
|
{
|
|
fire_grenade(self, start, movedir, self->dmg, self->speed, 2.5, self->dmg+40, false);
|
|
gi.WriteByte (svc_muzzleflash2);
|
|
gi.WriteShort (self - g_edicts);
|
|
gi.WriteByte (MZ2_GUNNER_GRENADE_1);
|
|
gi.multicast (start, MULTICAST_PVS);
|
|
}
|
|
else {
|
|
fire_blaster (self, start, movedir, self->dmg, self->speed, effect, MOD_TARGET_BLASTER, BLASTER_ORANGE);
|
|
gi.sound (self, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
|
|
void target_blaster_think (edict_t *self)
|
|
{
|
|
edict_t *ent;
|
|
edict_t *player;
|
|
trace_t tr;
|
|
vec3_t target;
|
|
int i;
|
|
|
|
if (self->spawnflags & SEEK_PLAYER) {
|
|
// this takes precedence over everything else
|
|
|
|
// If we are currently targeting a non-player, reset and look for
|
|
// a player
|
|
if (self->enemy && !self->enemy->client)
|
|
self->enemy = NULL;
|
|
|
|
// Is currently targeted player alive and not using notarget?
|
|
if (self->enemy) {
|
|
if (self->enemy->flags & FL_NOTARGET)
|
|
self->enemy = NULL;
|
|
else if (!self->enemy->inuse || self->enemy->health < 0)
|
|
self->enemy = NULL;
|
|
}
|
|
|
|
// We have a live not-notarget player as target. If IF_VISIBLE is
|
|
// set, see if we can see him
|
|
if (self->enemy && (self->spawnflags & IF_VISIBLE) ) {
|
|
VectorMA(self->enemy->absmin,0.5,self->enemy->size,target);
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,target,self,MASK_OPAQUE);
|
|
if (tr.fraction != 1.0)
|
|
self->enemy = NULL;
|
|
}
|
|
|
|
// If we STILL have an enemy, then he must be a good player target. Frag him
|
|
if (self->enemy) {
|
|
use_target_blaster(self,self,self);
|
|
if (self->wait)
|
|
self->nextthink = level.time + self->wait;
|
|
return;
|
|
}
|
|
|
|
// Find a player - note that we search the entire entity list so we'll
|
|
// also hit on func_monitor-viewing fake players
|
|
for (i=1, player=g_edicts+1; i<globals.num_edicts && !self->enemy; i++, player++)
|
|
{
|
|
if (!player->inuse) continue;
|
|
if (!player->client) continue;
|
|
if (player->svflags & SVF_NOCLIENT) continue;
|
|
if (player->health >= 0 && !(player->flags & FL_NOTARGET) )
|
|
{
|
|
if (self->spawnflags & IF_VISIBLE)
|
|
{
|
|
// player must be seen to shoot
|
|
VectorMA(player->s.origin,0.5,player->size,target);
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,target,self,MASK_OPAQUE);
|
|
if (tr.fraction == 1.0)
|
|
self->enemy = player;
|
|
}
|
|
else {
|
|
// we don't care whether he can be seen
|
|
self->enemy = player;
|
|
}
|
|
}
|
|
}
|
|
// If we have an enemy, shoot
|
|
if (self->enemy) {
|
|
use_target_blaster(self,self,self);
|
|
if (self->wait)
|
|
self->nextthink = level.time + self->wait;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// If we get to this point, then either SEEK_PLAYER wasn't set or we couldn't find
|
|
// a live, notarget player.
|
|
|
|
if (self->target)
|
|
{
|
|
if (!(self->spawnflags & IF_VISIBLE))
|
|
{
|
|
// have a target, don't care whether it's visible; cannot be a gibbed monster
|
|
self->enemy = NULL;
|
|
ent = G_Find (NULL, FOFS(targetname), self->target);
|
|
while (ent && !self->enemy) {
|
|
// if target is not a monster, we're done
|
|
if ( !(ent->svflags & SVF_MONSTER)) {
|
|
self->enemy = ent;
|
|
break;
|
|
}
|
|
ent = G_Find(ent, FOFS(targetname), self->target);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// has a target, but must be visible and not a monster
|
|
self->enemy = NULL;
|
|
ent = G_Find (NULL, FOFS(targetname), self->target);
|
|
while (ent && !self->enemy)
|
|
{
|
|
// if the target isn't a monster, we don't care whether
|
|
// it can be seen or not.
|
|
if ( !(ent->svflags & SVF_MONSTER) ) {
|
|
self->enemy = ent;
|
|
break;
|
|
}
|
|
if ( ent->health > ent->gib_health)
|
|
{
|
|
// Not a gibbed monster
|
|
VectorMA(ent->absmin,0.5,ent->size,target);
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,target,self,MASK_OPAQUE);
|
|
if (tr.fraction == 1.0)
|
|
{
|
|
self->enemy = ent;
|
|
break;
|
|
}
|
|
}
|
|
ent = G_Find(ent, FOFS(targetname), self->target);
|
|
}
|
|
}
|
|
}
|
|
if (self->enemy || !(self->spawnflags & IF_VISIBLE) ) {
|
|
use_target_blaster(self,self,self);
|
|
if (self->wait)
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
else if (self->wait)
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void find_target_blaster_target(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
target_blaster_think(self);
|
|
}
|
|
|
|
void toggle_target_blaster (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
// used for target_blasters with a "wait" value
|
|
|
|
self->activator = activator;
|
|
if (self->spawnflags & 4)
|
|
{
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
else
|
|
{
|
|
self->spawnflags &= ~4;
|
|
self->nextthink = 0;
|
|
}
|
|
}
|
|
else {
|
|
self->spawnflags |= 4;
|
|
self->think (self);
|
|
}
|
|
}
|
|
|
|
void target_blaster_init (edict_t *self)
|
|
{
|
|
if (self->target) {
|
|
edict_t *ent;
|
|
ent = G_Find (NULL, FOFS(targetname), self->target);
|
|
if (!ent)
|
|
gi.dprintf("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
|
|
self->enemy = ent;
|
|
}
|
|
}
|
|
void SP_target_blaster (edict_t *self)
|
|
{
|
|
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;
|
|
|
|
// If SEEK_PLAYER is not set and there's no target, then
|
|
// IF_VISIBLE is meaningless
|
|
if (!(self->spawnflags & 128) && !self->target)
|
|
self->spawnflags &= ~16;
|
|
|
|
if (self->wait)
|
|
{
|
|
// toggled target_blaster
|
|
self->use = toggle_target_blaster;
|
|
self->enemy = NULL; // for now
|
|
self->think = target_blaster_think;
|
|
if (self->spawnflags & 4)
|
|
self->nextthink = level.time + 1;
|
|
else
|
|
self->nextthink = 0;
|
|
}
|
|
else if (self->target || (self->spawnflags & SEEK_PLAYER)) {
|
|
self->use = find_target_blaster_target;
|
|
if (self->target) {
|
|
self->think = target_blaster_init;
|
|
self->nextthink = level.time + 2*FRAMETIME;
|
|
}
|
|
}
|
|
else {
|
|
// normal targeted target_blaster
|
|
self->use = use_target_blaster;
|
|
}
|
|
self->class_id = ENTITY_TARGET_BLASTER;
|
|
|
|
gi.linkentity(self);
|
|
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)
|
|
{
|
|
// Knightmare- use moreflags to override spawnflags, allows use of 32 trigger bits
|
|
if (self->moreflags != 0) {
|
|
game.serverflags |= self->moreflags;
|
|
}
|
|
else {
|
|
game.serverflags |= self->spawnflags;
|
|
}
|
|
|
|
// DWH: By most editors, the trigger should be able to fire targets. Added
|
|
// the following line:
|
|
G_UseTargets (self, activator);
|
|
G_FreeEdict (self);
|
|
}
|
|
|
|
void SP_target_crosslevel_trigger (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_CROSSLEVEL_TRIGGER;
|
|
|
|
self->svflags = SVF_NOCLIENT;
|
|
self->use = trigger_crosslevel_trigger_use;
|
|
}
|
|
|
|
/*QUAKED target_crosslevel_target (.5 .5 .5) (-8 -8 -8) (8 8 8) trigger1 trigger2 trigger3 trigger4 trigger5 trigger6 trigger7 trigger8
|
|
Triggered by a trigger_crosslevel elsewhere within a unit. If multiple triggers are checked, all must be true. Delay, target and
|
|
killtarget also work.
|
|
|
|
"delay" delay before using targets if the trigger has been activated (default 1)
|
|
*/
|
|
void target_crosslevel_target_think (edict_t *self)
|
|
{
|
|
/* if (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags))
|
|
{
|
|
G_UseTargets (self, self);
|
|
G_FreeEdict (self);
|
|
}*/
|
|
// Knightmare- use moreflags to override spawnflags, allows use of 32 trigger bits
|
|
qboolean triggered = false;
|
|
|
|
if (self->moreflags != 0) {
|
|
triggered = (self->moreflags == (game.serverflags & self->moreflags));
|
|
}
|
|
else {
|
|
triggered = (self->spawnflags == (game.serverflags & SFL_CROSS_TRIGGER_MASK & self->spawnflags));
|
|
}
|
|
|
|
if (triggered) {
|
|
G_UseTargets (self, self);
|
|
G_FreeEdict (self);
|
|
}
|
|
}
|
|
|
|
void SP_target_crosslevel_target (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_CROSSLEVEL_TARGET;
|
|
|
|
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.
|
|
*/
|
|
|
|
// DWH - player-seeking laser stuff
|
|
void target_laser_ps_think (edict_t *self)
|
|
{
|
|
edict_t *ignore;
|
|
edict_t *player;
|
|
trace_t tr;
|
|
vec3_t start;
|
|
vec3_t end;
|
|
vec3_t point;
|
|
vec3_t last_movedir;
|
|
vec3_t target;
|
|
int count;
|
|
int i;
|
|
|
|
if ( self->wait > 0) {
|
|
if ( level.time >= self->starttime ) {
|
|
self->starttime = level.time + self->wait;
|
|
self->endtime = level.time + self->delay;
|
|
}
|
|
else if ( level.time >= self->endtime ) {
|
|
self->nextthink = level.time + FRAMETIME;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (self->spawnflags & 0x80000000)
|
|
count = 8; // spark count
|
|
else
|
|
count = 4;
|
|
|
|
if (self->enemy) {
|
|
if (self->enemy->flags & FL_NOTARGET || (self->enemy->health < self->enemy->gib_health) )
|
|
self->enemy = NULL;
|
|
else {
|
|
// first make sure laser can see the center of the enemy
|
|
VectorMA(self->enemy->absmin,0.5,self->enemy->size,target);
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,target,self,MASK_OPAQUE);
|
|
if (tr.fraction != 1.0)
|
|
self->enemy = NULL;
|
|
}
|
|
}
|
|
if (!self->enemy) {
|
|
// find a player - as with target_blaster, search entire entity list so
|
|
// we'll pick up fake players representing camera-viewers
|
|
for (i=1, player=g_edicts+1; i<globals.num_edicts && !self->enemy; i++, player++) {
|
|
if (!player->inuse) continue;
|
|
if (!player->client) continue;
|
|
if (player->svflags & SVF_NOCLIENT) continue;
|
|
if ((player->health >= player->gib_health) && !(player->flags & FL_NOTARGET) ) {
|
|
VectorMA(player->absmin,0.5,player->size,target);
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,target,self,MASK_OPAQUE);
|
|
if (tr.fraction == 1.0) {
|
|
self->enemy = player;
|
|
self->spawnflags |= 0x80000001;
|
|
count = 8;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!self->enemy) {
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
return;
|
|
}
|
|
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
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)
|
|
{
|
|
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 && !self->style)
|
|
{
|
|
if (!(tr.ent->flags & FL_IMMUNE_LASER) && (self->dmg > 0) )
|
|
T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
|
|
else if (self->dmg < 0)
|
|
{
|
|
tr.ent->health -= self->dmg;
|
|
if (tr.ent->health > tr.ent->max_health)
|
|
tr.ent->health = tr.ent->max_health;
|
|
}
|
|
}
|
|
|
|
// if we hit something that's not a monster or player or is immune to lasers, we're done
|
|
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client))
|
|
{
|
|
if ((self->spawnflags & 0x80000000) && (self->style != 3))
|
|
{
|
|
self->spawnflags &= ~0x80000000;
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_LASER_SPARKS);
|
|
gi.WriteByte (count);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.WriteDir (tr.plane.normal);
|
|
gi.WriteByte (self->s.skinnum);
|
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
|
}
|
|
break;
|
|
}
|
|
|
|
ignore = tr.ent;
|
|
VectorCopy (tr.endpos, start);
|
|
}
|
|
VectorCopy (tr.endpos, self->s.old_origin);
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void target_laser_ps_on (edict_t *self)
|
|
{
|
|
if (!self->activator)
|
|
self->activator = self;
|
|
self->spawnflags |= 0x80000001;
|
|
// self->svflags &= ~SVF_NOCLIENT;
|
|
if (self->wait > 0) {
|
|
self->starttime = level.time + self->wait;
|
|
self->endtime = level.time + self->delay;
|
|
}
|
|
target_laser_ps_think (self);
|
|
}
|
|
|
|
void target_laser_ps_off (edict_t *self)
|
|
{
|
|
self->spawnflags &= ~1;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->nextthink = 0;
|
|
}
|
|
|
|
void target_laser_ps_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->activator = activator;
|
|
if (self->spawnflags & 1) {
|
|
target_laser_ps_off (self);
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
else
|
|
target_laser_ps_on (self);
|
|
}
|
|
|
|
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;
|
|
|
|
// DWH
|
|
if ( self->wait > 0) {
|
|
// pulsed laser
|
|
if ( level.time >= self->starttime ) {
|
|
self->starttime = level.time + self->wait;
|
|
self->endtime = level.time + self->delay;
|
|
}
|
|
else if ( level.time >= self->endtime ) {
|
|
self->nextthink = level.time + FRAMETIME;
|
|
return;
|
|
}
|
|
}
|
|
// end DWH
|
|
|
|
if (self->spawnflags & 0x80000000)
|
|
count = 8;
|
|
else
|
|
count = 4;
|
|
|
|
if (self->enemy)
|
|
{
|
|
VectorCopy (self->movedir, last_movedir);
|
|
VectorMA (self->enemy->absmin, 0.5, self->enemy->size, point);
|
|
VectorSubtract (point, self->s.origin, self->movedir);
|
|
VectorNormalize (self->movedir);
|
|
if (!VectorCompare(self->movedir, last_movedir))
|
|
self->spawnflags |= 0x80000000;
|
|
}
|
|
|
|
ignore = self;
|
|
VectorCopy (self->s.origin, start);
|
|
VectorMA (start, 2048, self->movedir, end);
|
|
while (1)
|
|
{
|
|
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 && !self->style)
|
|
{
|
|
if (!(tr.ent->flags & FL_IMMUNE_LASER) && (self->dmg > 0))
|
|
T_Damage (tr.ent, self, self->activator, self->movedir, tr.endpos, vec3_origin, self->dmg, 1, DAMAGE_ENERGY, MOD_TARGET_LASER);
|
|
else if (self->dmg < 0)
|
|
{
|
|
tr.ent->health -= self->dmg;
|
|
if (tr.ent->health > tr.ent->max_health)
|
|
tr.ent->health = tr.ent->max_health;
|
|
}
|
|
}
|
|
|
|
// if we hit something that's not a monster or player or is immune to lasers, we're done
|
|
if (!(tr.ent->svflags & SVF_MONSTER) && (!tr.ent->client) )
|
|
{
|
|
if ((self->spawnflags & 0x80000000) && (self->style != 3))
|
|
{
|
|
self->spawnflags &= ~0x80000000;
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_LASER_SPARKS);
|
|
gi.WriteByte (count);
|
|
gi.WritePosition (tr.endpos);
|
|
gi.WriteDir (tr.plane.normal);
|
|
gi.WriteByte (self->s.skinnum);
|
|
gi.multicast (tr.endpos, MULTICAST_PVS);
|
|
}
|
|
break;
|
|
}
|
|
|
|
ignore = tr.ent;
|
|
VectorCopy (tr.endpos, start);
|
|
}
|
|
|
|
VectorCopy (tr.endpos, self->s.old_origin);
|
|
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
|
|
void target_laser_on (edict_t *self)
|
|
{
|
|
if (self->wait > 0) {
|
|
self->starttime = level.time + self->wait;
|
|
self->endtime = level.time + self->delay;
|
|
}
|
|
if (!self->activator)
|
|
self->activator = self;
|
|
self->spawnflags |= 0x80000001;
|
|
self->svflags &= ~SVF_NOCLIENT;
|
|
target_laser_think (self);
|
|
}
|
|
|
|
void target_laser_off (edict_t *self)
|
|
{
|
|
self->spawnflags &= ~1;
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->nextthink = 0;
|
|
}
|
|
|
|
void target_laser_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
self->activator = activator;
|
|
if (self->spawnflags & 1) {
|
|
target_laser_off (self);
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
else
|
|
target_laser_on (self);
|
|
}
|
|
|
|
void target_laser_start (edict_t *self)
|
|
{
|
|
edict_t *ent;
|
|
|
|
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->mass > 1)
|
|
self->s.frame = self->mass;
|
|
else if (self->spawnflags & 64)
|
|
self->s.frame = 16;
|
|
else
|
|
self->s.frame = 4;
|
|
|
|
// set the color
|
|
if (self->spawnflags & 2)
|
|
self->s.skinnum = 0xf2f2f0f0;
|
|
else if (self->spawnflags & 4)
|
|
self->s.skinnum = 0xd0d1d2d3;
|
|
else if (self->spawnflags & 8)
|
|
self->s.skinnum = 0xf3f3f1f1;
|
|
else if (self->spawnflags & 16)
|
|
self->s.skinnum = 0xdcdddedf;
|
|
else if (self->spawnflags & 32)
|
|
self->s.skinnum = 0xe0e1e2e3;
|
|
|
|
|
|
if (!self->dmg)
|
|
self->dmg = 1;
|
|
VectorSet (self->mins, -8, -8, -8);
|
|
VectorSet (self->maxs, 8, 8, 8);
|
|
|
|
// DWH
|
|
|
|
// pulsed laser
|
|
if (self->wait > 0) {
|
|
if (self->delay >= self->wait) {
|
|
gi.dprintf("target_laser at %s, delay must be < wait.\n",
|
|
vtos(self->s.origin));
|
|
self->wait = 0;
|
|
}
|
|
else if (self->delay == 0.) {
|
|
gi.dprintf("target_laser at %s, wait > 0 but delay = 0\n",
|
|
vtos(self->s.origin));
|
|
self->wait = 0;
|
|
}
|
|
}
|
|
|
|
if (self->spawnflags & 128) {
|
|
// player-seeking laser
|
|
self->enemy = NULL;
|
|
self->use = target_laser_ps_use;
|
|
self->think = target_laser_ps_think;
|
|
gi.linkentity(self);
|
|
if (self->spawnflags & 1)
|
|
target_laser_ps_on(self);
|
|
else
|
|
target_laser_ps_off(self);
|
|
return;
|
|
}
|
|
// end DWH
|
|
|
|
if (!self->enemy)
|
|
{
|
|
if (self->target)
|
|
{
|
|
ent = G_Find (NULL, FOFS(targetname), self->target);
|
|
if (!ent)
|
|
gi.dprintf ("%s at %s: %s is a bad target\n", self->classname, vtos(self->s.origin), self->target);
|
|
self->enemy = ent;
|
|
}
|
|
else
|
|
{
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
}
|
|
}
|
|
self->use = target_laser_use;
|
|
self->think = target_laser_think;
|
|
|
|
gi.linkentity (self);
|
|
|
|
if (self->spawnflags & 1)
|
|
target_laser_on (self);
|
|
else
|
|
target_laser_off (self);
|
|
}
|
|
|
|
void SP_target_laser (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_LASER;
|
|
|
|
// let everything else get spawned before we start firing
|
|
self->think = target_laser_start;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
|
|
//==========================================================
|
|
|
|
/*QUAKED target_lightramp (0 .5 .8) (-8 -8 -8) (8 8 8) TOGGLE
|
|
speed How many seconds the ramping will take
|
|
message two letters; starting lightlevel and ending lightlevel
|
|
*/
|
|
|
|
#define LIGHTRAMP_TOGGLE 1
|
|
#define LIGHTRAMP_CUSTOM 2
|
|
#define LIGHTRAMP_LOOP 4
|
|
#define LIGHTRAMP_ACTIVE 128
|
|
|
|
void target_lightramp_think (edict_t *self)
|
|
{
|
|
char style[2];
|
|
|
|
if (self->spawnflags & LIGHTRAMP_CUSTOM) {
|
|
if (self->movedir[2] > 0)
|
|
style[0] = self->message[(int)self->movedir[0]];
|
|
else
|
|
style[0] = self->message[(int)(self->movedir[1]-self->movedir[0])];
|
|
self->movedir[0]++;
|
|
}
|
|
else {
|
|
style[0] = 'a' + self->movedir[0] + (level.time - self->timestamp) / FRAMETIME * self->movedir[2];
|
|
}
|
|
style[1] = 0;
|
|
gi.configstring (CS_LIGHTS+self->enemy->style, style);
|
|
|
|
if (self->spawnflags & LIGHTRAMP_CUSTOM) {
|
|
if ((self->movedir[0] <= self->movedir[1]) ||
|
|
((self->spawnflags & LIGHTRAMP_LOOP) && (self->spawnflags & LIGHTRAMP_ACTIVE)) ) {
|
|
self->nextthink = level.time + FRAMETIME;
|
|
if (self->movedir[0] > self->movedir[1]) {
|
|
self->movedir[0] = 0;
|
|
if (self->spawnflags & LIGHTRAMP_TOGGLE)
|
|
self->movedir[2] *= -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
self->movedir[0] = 0;
|
|
if (self->spawnflags & LIGHTRAMP_TOGGLE)
|
|
self->movedir[2] *= -1;
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( (level.time - self->timestamp) < self->speed) {
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else if (self->spawnflags & LIGHTRAMP_TOGGLE)
|
|
{
|
|
char temp;
|
|
|
|
temp = self->movedir[0];
|
|
self->movedir[0] = self->movedir[1];
|
|
self->movedir[1] = temp;
|
|
self->movedir[2] *= -1;
|
|
if ( (self->spawnflags & LIGHTRAMP_LOOP) && (self->spawnflags & LIGHTRAMP_ACTIVE) ) {
|
|
self->timestamp = level.time;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
else if ((self->spawnflags & LIGHTRAMP_LOOP) && (self->spawnflags & LIGHTRAMP_ACTIVE)) {
|
|
// Not toggled, looping. Start sequence over
|
|
self->timestamp = level.time;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else {
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void target_lightramp_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->spawnflags & LIGHTRAMP_LOOP) {
|
|
if (self->spawnflags & LIGHTRAMP_ACTIVE) {
|
|
self->spawnflags &= ~LIGHTRAMP_ACTIVE; // already on, turn it off
|
|
target_lightramp_think(self);
|
|
return;
|
|
}
|
|
else {
|
|
self->spawnflags |= LIGHTRAMP_ACTIVE;
|
|
}
|
|
}
|
|
|
|
if (!self->enemy)
|
|
{
|
|
edict_t *e;
|
|
|
|
// check all the targets
|
|
e = NULL;
|
|
while (1)
|
|
{
|
|
e = G_Find (e, FOFS(targetname), self->target);
|
|
if (!e)
|
|
break;
|
|
if (strcmp(e->classname, "light") != 0)
|
|
{
|
|
gi.dprintf("%s at %s ", self->classname, vtos(self->s.origin));
|
|
gi.dprintf("target %s (%s at %s) is not a light\n", self->target, e->classname, vtos(e->s.origin));
|
|
}
|
|
else
|
|
{
|
|
self->enemy = e;
|
|
}
|
|
}
|
|
|
|
if (!self->enemy)
|
|
{
|
|
gi.dprintf("%s target %s not found at %s\n", self->classname, self->target, vtos(self->s.origin));
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
self->timestamp = level.time;
|
|
target_lightramp_think (self);
|
|
}
|
|
|
|
void SP_target_lightramp (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_LIGHTRAMP;
|
|
|
|
// DWH: CUSTOM spawnflag allows custom light switching, speed is ignored
|
|
if (self->spawnflags & LIGHTRAMP_CUSTOM)
|
|
{
|
|
if (!self->message || strlen(self->message) < 2) {
|
|
gi.dprintf("custom target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!self->message || strlen(self->message) != 2 || self->message[0] < 'a' || self->message[0] > 'z' || self->message[1] < 'a' || self->message[1] > 'z' || self->message[0] == self->message[1])
|
|
{
|
|
gi.dprintf("target_lightramp has bad ramp (%s) at %s\n", self->message, vtos(self->s.origin));
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (deathmatch->value)
|
|
{
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
if (!self->target)
|
|
{
|
|
gi.dprintf("%s with no target at %s\n", self->classname, vtos(self->s.origin));
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->use = target_lightramp_use;
|
|
self->think = target_lightramp_think;
|
|
|
|
if (self->spawnflags & LIGHTRAMP_CUSTOM) {
|
|
self->movedir[0] = 0; // index into message
|
|
self->movedir[1] = strlen(self->message)-1; // position of last character
|
|
self->movedir[2] = 1; // direction = start->end
|
|
}
|
|
else {
|
|
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->spawnflags &= ~LIGHTRAMP_ACTIVE; // not currently on
|
|
}
|
|
|
|
//==========================================================
|
|
|
|
/*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)
|
|
"accel" horizontal shaking speed for players
|
|
"count" duration of the quake (default:5)
|
|
*/
|
|
|
|
void target_earthquake_think (edict_t *self)
|
|
{
|
|
int i;
|
|
edict_t *e;
|
|
|
|
if (self->last_move_time < level.time)
|
|
{
|
|
gi.positioned_sound (self->s.origin, self, CHAN_AUTO, self->noise_index, 1.0, ATTN_NONE, 0);
|
|
self->last_move_time = level.time + 0.5;
|
|
}
|
|
|
|
for (i=1, e=g_edicts+i; i < globals.num_edicts; i++,e++)
|
|
{
|
|
if (!e->inuse)
|
|
continue;
|
|
if (!e->client)
|
|
continue;
|
|
if (!e->groundentity)
|
|
continue;
|
|
// Lazarus: special case for tracktrain riders -
|
|
// earthquakes hurt 'em too bad, so don't shake 'em
|
|
if ((e->groundentity->flags & FL_TRACKTRAIN) && (e->groundentity->moveinfo.state))
|
|
continue;
|
|
e->groundentity = NULL;
|
|
|
|
// Knightmare- added accel for horizontal shaking
|
|
// e->velocity[0] += crandom() * 150;
|
|
// e->velocity[1] += crandom() * 150;
|
|
e->velocity[0] += crandom( )* self->accel;
|
|
e->velocity[1] += crandom() * self->accel;
|
|
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)
|
|
{
|
|
self->timestamp = level.time + self->count;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
self->activator = activator;
|
|
self->last_move_time = 0;
|
|
}
|
|
|
|
void SP_target_earthquake (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_EARTHQUAKE;
|
|
|
|
if (!self->targetname)
|
|
gi.dprintf("untargeted %s at %s\n", self->classname, vtos(self->s.origin));
|
|
|
|
if (!self->count)
|
|
self->count = 5;
|
|
|
|
if (!self->speed)
|
|
self->speed = 200;
|
|
|
|
// Knightmare- added accel for horizontal shaking
|
|
if (!self->accel)
|
|
self->accel = 150;
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->think = target_earthquake_think;
|
|
self->use = target_earthquake_use;
|
|
|
|
self->noise_index = gi.soundindex ("world/quake.wav");
|
|
}
|
|
|
|
// DWH
|
|
//
|
|
// Tremor stuff follows
|
|
//
|
|
//
|
|
// target_locator can be used to move entities to a random selection
|
|
// from a series of path_corners. Move takes place at level start ONLY.
|
|
//
|
|
void target_locator_init (edict_t *self)
|
|
{
|
|
int num_points=0;
|
|
int i, N, nummoves;
|
|
qboolean looped;
|
|
edict_t *tgt0, *tgtlast, *target, *next;
|
|
edict_t *move;
|
|
|
|
move = NULL;
|
|
move = G_Find(move,FOFS(targetname),self->target);
|
|
|
|
if (!move)
|
|
{
|
|
gi.dprintf("Target of target_locator (%s) not found.\n",
|
|
self->target);
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
target = G_Find(NULL,FOFS(targetname),self->pathtarget);
|
|
if (!target)
|
|
{
|
|
gi.dprintf("Pathtarget of target_locator (%s) not found.\n",
|
|
self->pathtarget);
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
srand(time(NULL));
|
|
tgt0 = target;
|
|
next = NULL;
|
|
target->spawnflags &= 0x7FFE;
|
|
while (next != tgt0)
|
|
{
|
|
if (target->target)
|
|
{
|
|
next = G_Find(NULL,FOFS(targetname),target->target);
|
|
if ((!next) || (next==tgt0)) tgtlast = target;
|
|
if (!next)
|
|
{
|
|
gi.dprintf("Target %s of path_corner at %s not found.\n",
|
|
target->target,vtos(target->s.origin));
|
|
break;
|
|
}
|
|
target = next;
|
|
target->spawnflags &= 0x7FFE;
|
|
num_points++;
|
|
}
|
|
else
|
|
{
|
|
next = tgt0;
|
|
tgtlast = target;
|
|
}
|
|
}
|
|
if (!num_points) num_points=1;
|
|
|
|
nummoves = 1;
|
|
while (move)
|
|
{
|
|
if (nummoves > num_points) break; // more targets than path_corners
|
|
|
|
N = rand() % num_points;
|
|
i = 0;
|
|
next = tgt0;
|
|
looped = false;
|
|
while (i<=N)
|
|
{
|
|
target = next;
|
|
if (!(target->spawnflags & 1)) i++;
|
|
if (target==tgtlast)
|
|
{
|
|
// We've looped thru all path_corners, but not
|
|
// reached the target number yet. This can only
|
|
// happen in the case of multiple targets. Use the
|
|
// next available path_corner.
|
|
looped = true;
|
|
}
|
|
if (looped && !(target->spawnflags & 1)) i = N+1;
|
|
next = G_Find(NULL,FOFS(targetname),target->target);
|
|
}
|
|
target->spawnflags |= 1;
|
|
|
|
// Assumptions here: SOLID_BSP entities are assumed to be brush models,
|
|
// all others are point ents
|
|
if (move->solid == SOLID_BSP)
|
|
{
|
|
vec3_t origin;
|
|
VectorAdd(move->absmin,move->absmax,origin);
|
|
VectorScale(origin,0.5,origin);
|
|
VectorSubtract(target->s.origin,origin,move->s.origin);
|
|
}
|
|
else {
|
|
VectorCopy(target->s.origin,move->s.origin);
|
|
VectorCopy(target->s.angles,move->s.angles);
|
|
}
|
|
M_droptofloor(move);
|
|
gi.linkentity(move);
|
|
move = G_Find(move,FOFS(targetname),self->target);
|
|
nummoves++;
|
|
}
|
|
// All done, go away
|
|
G_FreeEdict(self);
|
|
}
|
|
void SP_target_locator (edict_t *self)
|
|
{
|
|
if (!self->target)
|
|
{
|
|
gi.dprintf("target_locator w/o target at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if (!self->pathtarget)
|
|
{
|
|
gi.dprintf("target_locator w/o pathtarget at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_LOCATOR;
|
|
|
|
self->think = target_locator_init;
|
|
self->nextthink = level.time + 2*FRAMETIME;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
//
|
|
// TARGET_ANGER
|
|
//
|
|
// target Monster(s) to make angry
|
|
// killtarget Entity to get angry at
|
|
// pathtarget Entity to run to
|
|
// Spawnflags:
|
|
// HOLD (16) Stand in place
|
|
// BRUTAL (32) Gib killtarget
|
|
|
|
void use_target_anger (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *kill_me, *movetarget;
|
|
edict_t *t;
|
|
vec3_t vec;
|
|
float dist, best_dist;
|
|
edict_t *best_target;
|
|
|
|
if (self->pathtarget)
|
|
movetarget = G_PickTarget(self->pathtarget);
|
|
else
|
|
movetarget = NULL;
|
|
|
|
if (self->target)
|
|
{
|
|
t = NULL;
|
|
while ((t = G_Find (t, FOFS(targetname), self->target)))
|
|
{
|
|
if (t == self)
|
|
{
|
|
gi.dprintf ("WARNING: entity used itself.\n");
|
|
}
|
|
else
|
|
{
|
|
if (t->use)
|
|
{
|
|
if (t->health < 0)
|
|
return;
|
|
if (self->movedir[2] > 0)
|
|
{
|
|
t->velocity[0] = self->movedir[0] * self->speed;
|
|
t->velocity[1] = self->movedir[1] * self->speed;
|
|
if (t->groundentity)
|
|
{
|
|
t->groundentity = NULL;
|
|
t->velocity[2] = self->movedir[2];
|
|
if (t->monsterinfo.aiflags & AI_ACTOR)
|
|
gi.sound (self, CHAN_VOICE, t->actor_sound_index[0], 1, ATTN_NORM, 0);
|
|
}
|
|
}
|
|
if (self->killtarget)
|
|
{
|
|
kill_me = G_Find(NULL, FOFS(targetname), self->killtarget);
|
|
if (kill_me)
|
|
{
|
|
best_dist = 9000.;
|
|
best_target = NULL;
|
|
if (kill_me->health > 0)
|
|
{
|
|
VectorSubtract(kill_me->s.origin,t->s.origin,vec);
|
|
best_dist = VectorLength(vec);
|
|
best_target = kill_me;
|
|
}
|
|
while (kill_me)
|
|
{
|
|
kill_me = G_Find(kill_me, FOFS(targetname), self->killtarget);
|
|
if (!kill_me)
|
|
break;
|
|
if (!kill_me->inuse)
|
|
continue;
|
|
if (kill_me->health <= 0)
|
|
continue;
|
|
VectorSubtract(kill_me->s.origin,t->s.origin,vec);
|
|
dist = VectorLength(vec);
|
|
if (dist < best_dist)
|
|
{
|
|
best_dist = dist;
|
|
best_target = kill_me;
|
|
}
|
|
}
|
|
kill_me = best_target;
|
|
}
|
|
}
|
|
if (kill_me)
|
|
{
|
|
// Make whatever a "good guy" so the monster will try to kill it!
|
|
kill_me->monsterinfo.aiflags |= AI_GOOD_GUY;
|
|
t->enemy = t->goalentity = kill_me;
|
|
t->monsterinfo.aiflags |= AI_TARGET_ANGER;
|
|
if (movetarget)
|
|
t->movetarget = movetarget;
|
|
FoundTarget (t);
|
|
}
|
|
else
|
|
{
|
|
t->monsterinfo.pausetime = 0;
|
|
t->goalentity = t->movetarget = movetarget;
|
|
}
|
|
|
|
if ((self->spawnflags & 16) && !(t->flags & (FL_SWIM|FL_FLY)))
|
|
{
|
|
t->monsterinfo.pausetime = level.time + 100000000;
|
|
t->monsterinfo.aiflags |= AI_STAND_GROUND;
|
|
t->monsterinfo.stand (t);
|
|
}
|
|
if (self->spawnflags & 32)
|
|
t->monsterinfo.aiflags |= AI_BRUTAL;
|
|
}
|
|
}
|
|
if (!self->inuse)
|
|
{
|
|
gi.dprintf("entity was removed while using targets\n");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
|
|
}
|
|
|
|
void SP_target_anger (edict_t *self)
|
|
{
|
|
if (deathmatch->value) {
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if (!self->target) {
|
|
gi.dprintf("target_anger with no target set at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if (!self->killtarget && !self->pathtarget) {
|
|
gi.dprintf("target_anger with no killtarget or\npathtarget set at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
// pathtarget is incompatible with HOLD SF
|
|
if (self->pathtarget && (self->spawnflags & 16))
|
|
{
|
|
gi.dprintf("target anger at %s,\npathtarget is incompatible with HOLD\n",
|
|
vtos(self->s.origin));
|
|
self->spawnflags &= ~16;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_ANGER;
|
|
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
self->movedir[2] = st.height;
|
|
self->use = use_target_anger;
|
|
}
|
|
|
|
// target_monsterbattle serves the same purpose as target_anger, but
|
|
// ends up turning a dmgteam group of monsters against another dmgteam
|
|
|
|
void use_target_monsterbattle(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *grouch, *grouchmate;
|
|
edict_t *target, *targetmate;
|
|
|
|
grouch = G_Find(NULL,FOFS(targetname),self->target);
|
|
if (!grouch) return;
|
|
if (!grouch->inuse) return;
|
|
target = G_Find(NULL,FOFS(targetname),self->killtarget);
|
|
if (!target) return;
|
|
if (!target->inuse) return;
|
|
if (grouch->dmgteam)
|
|
{
|
|
grouchmate = G_Find(NULL,FOFS(dmgteam),grouch->dmgteam);
|
|
while (grouchmate) {
|
|
grouchmate->monsterinfo.aiflags |= AI_FREEFORALL;
|
|
grouchmate = G_Find(grouchmate,FOFS(dmgteam),grouch->dmgteam);
|
|
}
|
|
}
|
|
if (target->dmgteam)
|
|
{
|
|
targetmate = G_Find(NULL,FOFS(dmgteam),target->dmgteam);
|
|
while (targetmate) {
|
|
targetmate->monsterinfo.aiflags |= AI_FREEFORALL;
|
|
targetmate = G_Find(targetmate,FOFS(dmgteam),target->dmgteam);
|
|
}
|
|
}
|
|
grouch->enemy = target;
|
|
grouch->monsterinfo.aiflags |= AI_TARGET_ANGER;
|
|
FoundTarget(grouch);
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
void SP_target_monsterbattle(edict_t *self)
|
|
{
|
|
if (deathmatch->value) {
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if (!self->target) {
|
|
gi.dprintf("target_monsterbattle with no target set at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if (!self->killtarget) {
|
|
gi.dprintf("target_monsterbattle with no killtarget set at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_MONSTERBATTLE;
|
|
|
|
self->use = use_target_monsterbattle;
|
|
}
|
|
|
|
/*====================================================================================
|
|
TARGET_ROCKS
|
|
======================================================================================*/
|
|
void directed_debris_die (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, vec3_t point)
|
|
{
|
|
G_FreeEdict (self);
|
|
}
|
|
//void FadeDieThink(edict_t *ent);
|
|
void gib_fade (edict_t *self);
|
|
void ThrowRock (edict_t *self, char *modelname, float speed, vec3_t origin, vec3_t size, int mass)
|
|
{
|
|
edict_t *chunk;
|
|
vec_t var = speed/5;
|
|
|
|
chunk = G_Spawn();
|
|
VectorCopy (origin, chunk->s.origin);
|
|
gi.setmodel (chunk, modelname);
|
|
VectorCopy(size,chunk->maxs);
|
|
VectorScale(chunk->maxs,0.5,chunk->maxs);
|
|
VectorNegate(chunk->maxs,chunk->mins);
|
|
chunk->velocity[0] = speed * self->movedir[0] + var * crandom();
|
|
chunk->velocity[1] = speed * self->movedir[1] + var * crandom();
|
|
chunk->velocity[2] = speed * self->movedir[2] + var * crandom();
|
|
chunk->movetype = MOVETYPE_DEBRIS;
|
|
chunk->attenuation = 0.5;
|
|
chunk->solid = SOLID_NOT;
|
|
chunk->avelocity[0] = random()*600;
|
|
chunk->avelocity[1] = random()*600;
|
|
chunk->avelocity[2] = random()*600;
|
|
chunk->think = gib_fade; // was FadeDieThink
|
|
chunk->nextthink = level.time + 15 + random()*5;
|
|
chunk->s.frame = 0;
|
|
chunk->flags = 0;
|
|
chunk->classname = "debris";
|
|
chunk->takedamage = DAMAGE_YES;
|
|
chunk->die = directed_debris_die;
|
|
chunk->mass = mass;
|
|
gi.linkentity (chunk);
|
|
}
|
|
|
|
void use_target_rocks (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
vec3_t chunkorigin;
|
|
vec3_t size, source;
|
|
vec_t mass;
|
|
int count;
|
|
char modelname[64];
|
|
|
|
VectorSet(source,8,8,8);
|
|
mass = self->mass;
|
|
|
|
// set movedir here- if we're
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
|
|
// big chunks
|
|
if (mass >= 100)
|
|
{
|
|
Com_sprintf (modelname, sizeof(modelname), "models/objects/rock%d/tris.md2",self->style*2+1);
|
|
count = mass / 100;
|
|
if (count > 16)
|
|
count = 16;
|
|
VectorSet(size,8,8,8);
|
|
while (count--)
|
|
{
|
|
chunkorigin[0] = self->s.origin[0] + crandom() * source[0];
|
|
chunkorigin[1] = self->s.origin[1] + crandom() * source[1];
|
|
chunkorigin[2] = self->s.origin[2] + crandom() * source[2];
|
|
ThrowRock (self, modelname, self->speed, chunkorigin, size, 100);
|
|
}
|
|
}
|
|
// small chunks
|
|
count = mass / 25;
|
|
Com_sprintf (modelname, sizeof(modelname), "models/objects/rock%d/tris.md2",self->style*2+2);
|
|
if (count > 16)
|
|
count = 16;
|
|
VectorSet(size,4,4,4);
|
|
while (count--)
|
|
{
|
|
chunkorigin[0] = self->s.origin[0] + crandom() * source[0];
|
|
chunkorigin[1] = self->s.origin[1] + crandom() * source[1];
|
|
chunkorigin[2] = self->s.origin[2] + crandom() * source[2];
|
|
ThrowRock (self, modelname, self->speed, chunkorigin, size, 25);
|
|
}
|
|
if (self->dmg)
|
|
T_RadiusDamage (self, activator, self->dmg, NULL, self->dmg+40, MOD_SPLASH, -0.5);
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
|
|
}
|
|
|
|
void SP_target_rocks (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_ROCKS;
|
|
|
|
// precache
|
|
gi.modelindex ("models/objects/rock1/tris.md2");
|
|
gi.modelindex ("models/objects/rock2/tris.md2");
|
|
|
|
if (!self->targetname)
|
|
gi.dprintf("target_rocks without a targetname at %s\n", vtos(self->s.origin));
|
|
if (!self->speed)
|
|
self->speed = 400;
|
|
if (!self->mass)
|
|
self->mass = 500;
|
|
|
|
// G_SetMovedir (self->s.angles, self->movedir);
|
|
self->use = use_target_rocks;
|
|
self->svflags = SVF_NOCLIENT;
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/*====================================================================================
|
|
TARGET_ROTATION
|
|
======================================================================================*/
|
|
|
|
void use_target_rotation (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *target;
|
|
int i, pick;
|
|
char *p1, *p2;
|
|
char targetname[256];
|
|
|
|
if (self->spawnflags & 2) {
|
|
// random pick
|
|
pick = self->sounds * random();
|
|
if (pick == self->sounds) pick--;
|
|
}
|
|
else
|
|
{
|
|
pick = self->mass;
|
|
if (pick == self->sounds) {
|
|
if (self->spawnflags & 1) // no loop
|
|
return;
|
|
else
|
|
pick = 0;
|
|
}
|
|
self->mass = pick+1;
|
|
}
|
|
p1 = self->target;
|
|
p2 = targetname;
|
|
memset(targetname,0,sizeof(targetname));
|
|
// skip over pick commas
|
|
for (i=0; i<pick; i++) {
|
|
p1 = strstr(p1,",");
|
|
if (p1)
|
|
p1++;
|
|
else
|
|
return; // should never happen
|
|
}
|
|
while (*p1 != 0 && *p1 != ',') {
|
|
*p2 = *p1;
|
|
p1++;
|
|
p2++;
|
|
}
|
|
target = G_Find(NULL,FOFS(targetname),targetname);
|
|
while (target) {
|
|
if (target->inuse && target->use)
|
|
target->use(target,other,activator);
|
|
target = G_Find(target,FOFS(targetname),targetname);
|
|
}
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
void SP_target_rotation (edict_t *self)
|
|
{
|
|
char *p;
|
|
|
|
if (!self->target) {
|
|
gi.dprintf("target_rotation without a target at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_ROTATION;
|
|
|
|
if ( (self->spawnflags & 3) == 3) {
|
|
gi.dprintf("target_rotation at %s: NO_LOOP and RANDOM are mutually exclusive.\n");
|
|
self->spawnflags = 2;
|
|
}
|
|
self->use = use_target_rotation;
|
|
self->svflags = SVF_NOCLIENT;
|
|
self->mass = 0; // index of currently selected target
|
|
self->sounds = 0; // number of comma-delimited targets in target string
|
|
p = self->target;
|
|
while ( (p = strstr(p,",")) != NULL)
|
|
{
|
|
self->sounds++;
|
|
p++;
|
|
}
|
|
self->sounds++;
|
|
}
|
|
|
|
/*====================================================================================
|
|
TARGET_EFFECT
|
|
======================================================================================*/
|
|
|
|
/* Unknowns or not supported
|
|
TE_FLAME, 32 Rogue flamethrower, never implemented
|
|
TE_FORCEWALL, 37 ??
|
|
*/
|
|
|
|
//=========================================================================
|
|
/* Spawns an effect at the entity origin
|
|
TE_FLASHLIGHT 36
|
|
*/
|
|
void target_effect_at (edict_t *self, edict_t *activator)
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (self->style);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WriteShort (self - g_edicts);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
}
|
|
/* Poor man's target_steam
|
|
TE_STEAM 40
|
|
*/
|
|
void target_effect_steam (edict_t *self, edict_t *activator)
|
|
{
|
|
static int nextid;
|
|
int wait;
|
|
|
|
if (self->wait)
|
|
wait = self->wait*1000;
|
|
else
|
|
wait = 0;
|
|
|
|
if (nextid > 20000)
|
|
nextid = nextid %20000;
|
|
nextid++;
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (self->style);
|
|
gi.WriteShort (nextid);
|
|
gi.WriteByte (self->count);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.WriteDir (self->movedir);
|
|
gi.WriteByte (self->sounds&0xff);
|
|
gi.WriteShort ( (int)(self->speed) );
|
|
gi.WriteLong ( (int)(wait) );
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
|
|
if (level.num_reflectors)
|
|
ReflectSteam (self->s.origin,self->movedir,self->count,self->sounds,(int)(self->speed),wait,nextid);
|
|
}
|
|
|
|
//=========================================================================
|
|
/*
|
|
Spawns (style) Splash with (count) particles of (sounds) color at (origin)
|
|
moving in (movedir) direction.
|
|
|
|
TE_SPLASH 10 Randomly shaded shower of particles
|
|
TE_LASER_SPARKS 15 Splash particles obey gravity
|
|
TE_WELDING_SPARKS 25 Splash particles with flash of light at {origin}
|
|
*/
|
|
//=========================================================================
|
|
void target_effect_splash (edict_t *self, edict_t *activator)
|
|
{
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(self->style);
|
|
gi.WriteByte(self->count);
|
|
gi.WritePosition(self->s.origin);
|
|
gi.WriteDir(self->movedir);
|
|
gi.WriteByte(self->sounds);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
|
}
|
|
|
|
//======================================================
|
|
/*
|
|
Spawns a trail of (type) from (start) to (end) and Broadcasts to all
|
|
in Potentially Visible Set from vector (origin)
|
|
|
|
TE_RAILTRAIL 3 Spawns a blue spiral trail filled with white smoke
|
|
TE_BUBBLETRAIL 11 Spawns a trail of bubbles
|
|
TE_PARASITE_ATTACK 16
|
|
TE_MEDIC_CABLE_ATTACK 19
|
|
TE_BFG_LASER 23 Spawns a green laser
|
|
TE_GRAPPLE_CABLE 24
|
|
TE_RAILTRAIL2 31 NOT IMPLEMENTED IN ENGINE
|
|
TE_DEBUGTRAIL 34
|
|
TE_HEATBEAM, 38 Requires Rogue model
|
|
TE_MONSTER_HEATBEAM, 39 Requires Rogue model
|
|
TE_BUBBLETRAIL2 41
|
|
*/
|
|
//======================================================
|
|
void target_effect_trail (edict_t *self, edict_t *activator)
|
|
{
|
|
edict_t *target;
|
|
|
|
if (!self->target) return;
|
|
target = G_Find(NULL,FOFS(targetname),self->target);
|
|
if (!target) return;
|
|
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(self->style);
|
|
if ((self->style == TE_PARASITE_ATTACK) || (self->style==TE_MEDIC_CABLE_ATTACK) ||
|
|
(self->style == TE_HEATBEAM) || (self->style==TE_MONSTER_HEATBEAM) ||
|
|
(self->style == TE_GRAPPLE_CABLE) )
|
|
gi.WriteShort(self-g_edicts);
|
|
gi.WritePosition(self->s.origin);
|
|
gi.WritePosition(target->s.origin);
|
|
if (self->style == TE_GRAPPLE_CABLE) {
|
|
gi.WritePosition(vec3_origin);
|
|
}
|
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
|
|
|
if (level.num_reflectors)
|
|
{
|
|
if ((self->style == TE_RAILTRAIL) || (self->style == TE_BUBBLETRAIL) ||
|
|
(self->style == TE_BFG_LASER) || (self->style == TE_DEBUGTRAIL) ||
|
|
(self->style == TE_BUBBLETRAIL2))
|
|
ReflectTrail(self->style,self->s.origin,target->s.origin);
|
|
}
|
|
}
|
|
//===========================================================================
|
|
/* TE_LIGHTNING 33 Lightning bolt
|
|
|
|
Similar but slightly different syntax to trail stuff */
|
|
void target_effect_lightning(edict_t *self, edict_t *activator)
|
|
{
|
|
edict_t *target;
|
|
|
|
if (!self->target) return;
|
|
target = G_Find(NULL,FOFS(targetname),self->target);
|
|
if (!target) return;
|
|
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (self->style);
|
|
gi.WriteShort (target - g_edicts); // destination entity
|
|
gi.WriteShort (self - g_edicts); // source entity
|
|
gi.WritePosition (target->s.origin);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
}
|
|
//===========================================================================
|
|
/*
|
|
Spawns sparks of (type) from (start) in direction of (movdir) and
|
|
Broadcasts to all in Potentially Visible Set from vector (origin)
|
|
|
|
TE_GUNSHOT 0 Spawns a grey splash of particles, with a bullet puff
|
|
TE_BLOOD 1 Spawns a spurt of red blood
|
|
TE_BLASTER 2 Spawns a blaster sparks
|
|
TE_SHOTGUN 4 Spawns a small grey splash of spark particles, with a bullet puff
|
|
TE_SPARKS 9 Spawns a red/gold splash of spark particles
|
|
TE_SCREEN_SPARKS 12 Spawns a large green/white splash of sparks
|
|
TE_SHIELD_SPARKS 13 Spawns a large blue/violet splash of sparks
|
|
TE_BULLET_SPARKS 14 Same as TE_SPARKS, with a bullet puff and richochet sound
|
|
TE_GREENBLOOD 26 Spurt of green (actually kinda yellow) blood
|
|
TE_BLUEHYPERBLASTER 27 NOT IMPLEMENTED
|
|
TE_BLASTER2 30 Green/white sparks with a yellow/white flash
|
|
TE_MOREBLOOD 42
|
|
TE_HEATBEAM_SPARKS 43
|
|
TE_HEATBEAM_STEAM 44
|
|
TE_CHAINFIST_SMOKE 45
|
|
TE_ELECTRIC_SPARKS 46
|
|
TE_FLECHETTE 55
|
|
*/
|
|
//======================================================
|
|
void target_effect_sparks (edict_t *self, edict_t *activator)
|
|
{
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(self->style);
|
|
gi.WritePosition(self->s.origin);
|
|
if (self->style != TE_CHAINFIST_SMOKE)
|
|
gi.WriteDir(self->movedir);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
|
|
|
if (level.num_reflectors)
|
|
ReflectSparks(self->style,self->s.origin,self->movedir);
|
|
}
|
|
|
|
//======================================================
|
|
/*
|
|
Spawns a (type) effect at (start} and Broadcasts to all in the
|
|
Potentially Hearable set from vector (origin)
|
|
|
|
TE_EXPLOSION1 5 airburst
|
|
TE_EXPLOSION2 6 ground burst
|
|
TE_ROCKET_EXPLOSION 7 rocket explosion
|
|
TE_GRENADE_EXPLOSION 8 grenade explosion
|
|
TE_ROCKET_EXPLOSION_WATER 17 underwater rocket explosion
|
|
TE_GRENADE_EXPLOSION_WATER 18 underwater grenade explosion
|
|
TE_BFG_EXPLOSION 20 BFG explosion sprite
|
|
TE_BFG_BIGEXPLOSION 21 BFG particle explosion
|
|
TE_BOSSTPORT 22
|
|
TE_PLASMA_EXPLOSION 28
|
|
TE_PLAIN_EXPLOSION 35
|
|
TE_TRACKER_EXPLOSION 47
|
|
TE_TELEPORT_EFFECT 48
|
|
TE_DBALL_GOAL 49 Identical to TE_TELEPORT_EFFECT?
|
|
TE_NUKEBLAST 51
|
|
TE_WIDOWSPLASH 52
|
|
TE_EXPLOSION1_BIG 53 Works, but requires Rogue models/objects/r_explode2
|
|
TE_EXPLOSION1_NP 54
|
|
*/
|
|
//==============================================================================
|
|
void target_effect_explosion (edict_t *self, edict_t *activator)
|
|
{
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(self->style);
|
|
gi.WritePosition(self->s.origin);
|
|
gi.multicast(self->s.origin, MULTICAST_PHS);
|
|
|
|
if (level.num_reflectors)
|
|
ReflectExplosion (self->style, self->s.origin);
|
|
|
|
}
|
|
//===============================================================================
|
|
/* TE_TUNNEL_SPARKS 29
|
|
Similar to other splash effects, but Xatrix does some funky things with
|
|
the origin so we'll do the same */
|
|
|
|
void target_effect_tunnel_sparks (edict_t *self, edict_t *activator)
|
|
{
|
|
vec3_t origin;
|
|
int i;
|
|
|
|
VectorCopy(self->s.origin,origin);
|
|
for (i=0; i<self->count; i++)
|
|
{
|
|
origin[2] += (self->speed * 0.01) * (i + random());
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (self->style);
|
|
gi.WriteByte (1);
|
|
gi.WritePosition (origin);
|
|
gi.WriteDir (vec3_origin);
|
|
gi.WriteByte (self->sounds + (rand()&7)); // color
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
}
|
|
}
|
|
//===============================================================================
|
|
/* TE_WIDOWBEAMOUT 50
|
|
*/
|
|
void target_effect_widowbeam(edict_t *self, edict_t *activator)
|
|
{
|
|
gi.WriteByte (svc_temp_entity);
|
|
gi.WriteByte (TE_WIDOWBEAMOUT);
|
|
gi.WriteShort (20001);
|
|
gi.WritePosition (self->s.origin);
|
|
gi.multicast (self->s.origin, MULTICAST_PVS);
|
|
}
|
|
//===============================================================================
|
|
|
|
void target_effect_use(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->spawnflags & 1) {
|
|
// currently looped on - turn it off
|
|
self->spawnflags &= ~1;
|
|
self->spawnflags |= 2;
|
|
self->nextthink = 0;
|
|
return;
|
|
}
|
|
if (self->spawnflags & 2) {
|
|
// currently looped off - turn it on
|
|
self->spawnflags &= ~2;
|
|
self->spawnflags |= 1;
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
if (self->spawnflags & 4) {
|
|
// "if_moving" set. If movewith target isn't moving,
|
|
// don't play
|
|
edict_t *mover;
|
|
if (!self->movewith) return;
|
|
mover = G_Find(NULL,FOFS(targetname),self->movewith);
|
|
if (!mover) return;
|
|
if (!VectorLength(mover->velocity)) return;
|
|
}
|
|
self->play(self,activator);
|
|
}
|
|
void target_effect_think(edict_t *self)
|
|
{
|
|
self->play(self,NULL);
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
//===============================================================================
|
|
void SP_target_effect (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_EFFECT;
|
|
|
|
if (self->movewith)
|
|
self->movetype = MOVETYPE_PUSH;
|
|
else
|
|
self->movetype = MOVETYPE_NONE;
|
|
|
|
switch (self->style ) {
|
|
case TE_FLASHLIGHT:
|
|
self->play = target_effect_at;
|
|
break;
|
|
case TE_STEAM:
|
|
self->play = target_effect_steam;
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
if (!self->count)
|
|
self->count = 32;
|
|
if (!self->sounds)
|
|
self->sounds = 8;
|
|
if (!self->speed)
|
|
self->speed = 75;
|
|
break;
|
|
case TE_SPLASH:
|
|
case TE_LASER_SPARKS:
|
|
case TE_WELDING_SPARKS:
|
|
self->play = target_effect_splash;
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
if (!self->count)
|
|
self->count = 32;
|
|
break;
|
|
case TE_RAILTRAIL:
|
|
case TE_BUBBLETRAIL:
|
|
case TE_PARASITE_ATTACK:
|
|
case TE_MEDIC_CABLE_ATTACK:
|
|
case TE_BFG_LASER:
|
|
case TE_GRAPPLE_CABLE:
|
|
case TE_DEBUGTRAIL:
|
|
case TE_HEATBEAM:
|
|
case TE_MONSTER_HEATBEAM:
|
|
case TE_BUBBLETRAIL2:
|
|
if (!self->target) {
|
|
gi.dprintf("%s at %s with style=%d needs a target\n",self->classname,vtos(self->s.origin),self->style);
|
|
G_FreeEdict(self);
|
|
}
|
|
else
|
|
self->play = target_effect_trail;
|
|
break;
|
|
case TE_LIGHTNING:
|
|
if (!self->target) {
|
|
gi.dprintf("%s at %s with style=%d needs a target\n",self->classname,vtos(self->s.origin),self->style);
|
|
G_FreeEdict(self);
|
|
}
|
|
else
|
|
self->play = target_effect_lightning;
|
|
break;
|
|
case TE_GUNSHOT:
|
|
case TE_BLOOD:
|
|
case TE_BLASTER:
|
|
case TE_SHOTGUN:
|
|
case TE_SPARKS:
|
|
case TE_SCREEN_SPARKS:
|
|
case TE_SHIELD_SPARKS:
|
|
case TE_BULLET_SPARKS:
|
|
case TE_GREENBLOOD:
|
|
case TE_BLASTER2:
|
|
case TE_MOREBLOOD:
|
|
case TE_HEATBEAM_SPARKS:
|
|
case TE_HEATBEAM_STEAM:
|
|
case TE_CHAINFIST_SMOKE:
|
|
case TE_ELECTRIC_SPARKS:
|
|
case TE_FLECHETTE:
|
|
self->play = target_effect_sparks;
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
break;
|
|
case TE_EXPLOSION1:
|
|
case TE_EXPLOSION2:
|
|
case TE_ROCKET_EXPLOSION:
|
|
case TE_GRENADE_EXPLOSION:
|
|
case TE_ROCKET_EXPLOSION_WATER:
|
|
case TE_GRENADE_EXPLOSION_WATER:
|
|
case TE_BFG_EXPLOSION:
|
|
case TE_BFG_BIGEXPLOSION:
|
|
case TE_BOSSTPORT:
|
|
case TE_PLASMA_EXPLOSION:
|
|
case TE_PLAIN_EXPLOSION:
|
|
case TE_TRACKER_EXPLOSION:
|
|
case TE_TELEPORT_EFFECT:
|
|
case TE_DBALL_GOAL:
|
|
case TE_NUKEBLAST:
|
|
case TE_WIDOWSPLASH:
|
|
case TE_EXPLOSION1_BIG:
|
|
case TE_EXPLOSION1_NP:
|
|
self->play = target_effect_explosion;
|
|
break;
|
|
case TE_TUNNEL_SPARKS:
|
|
if (!self->count)
|
|
self->count = 32;
|
|
if (!self->sounds)
|
|
self->sounds = 116; // Light blue, same color used by Xatrix
|
|
self->play = target_effect_tunnel_sparks;
|
|
break;
|
|
case TE_WIDOWBEAMOUT:
|
|
self->play = target_effect_widowbeam;
|
|
G_SetMovedir (self->s.angles, self->movedir);
|
|
break;
|
|
default:
|
|
gi.dprintf("%s at %s: bad style %d\n",self->classname,vtos(self->s.origin),self->style);
|
|
}
|
|
self->use = target_effect_use;
|
|
self->think = target_effect_think;
|
|
if (self->spawnflags & 1)
|
|
self->nextthink = level.time + 1;
|
|
|
|
}
|
|
|
|
/*=====================================================================================
|
|
TARGET_ATTRACTOR - pulls target entity towards its origin
|
|
target - Targetname of entity to attract. Ignored if PLAYER spawnflag is set
|
|
pathtarget - Entity or entities to "use" when distance criteria is met.
|
|
speed - Minimum speed to pull target with. Must use a value > sv_gravity/10
|
|
to overcome gravity when pulling up.
|
|
distance - When target is within "distance" units of target_attractor, attraction
|
|
is shut off. Use a value < 0 to hold target in place. 0 will be reset
|
|
to 1.
|
|
sounds - effect to use. ONLY VALID for PLAYER or MONSTER, not target. If sounds
|
|
is non-zero, SINGLE_TARGET and SIGHT are automatically set
|
|
0 = none
|
|
1 = medic cable
|
|
2 = green laser
|
|
|
|
Spawnflags: 1 - START_ON
|
|
2 - PLAYER (attract player, ignore "target"
|
|
4 - NO_GRAVITY - turns off gravity for target. W/O this flag you'll
|
|
get an annoying jitter when pulling players up.
|
|
8 - MONSTER - attract ALL monsters, ignore "target"
|
|
16 - SIGHT - must have LOS to target to attract it
|
|
32 - SINGLE_TARGET - will select best target
|
|
64 - PATHTARGET_FIRE - used internally only
|
|
=======================================================================================*/
|
|
#define ATTRACTOR_ON 1
|
|
#define ATTRACTOR_PLAYER 2
|
|
#define ATTRACTOR_NO_GRAVITY 4
|
|
#define ATTRACTOR_MONSTER 8
|
|
#define ATTRACTOR_SIGHT 16
|
|
#define ATTRACTOR_SINGLE 32
|
|
#define ATTRACTOR_PATHTARGET 64
|
|
|
|
void target_attractor_think_single (edict_t *self)
|
|
{
|
|
edict_t *ent, *target, *previous_target;
|
|
trace_t tr;
|
|
vec3_t dir, targ_org;
|
|
vec_t dist, speed;
|
|
vec_t best_dist;
|
|
vec3_t forward, right;
|
|
int i;
|
|
int num_targets = 0;
|
|
|
|
if ( !(self->spawnflags & ATTRACTOR_ON) ) return;
|
|
|
|
previous_target = self->target_ent;
|
|
target = NULL;
|
|
best_dist = WORLD_SIZE; // was 8192
|
|
|
|
if (self->spawnflags & ATTRACTOR_PLAYER)
|
|
{
|
|
for (i=1, ent=&g_edicts[i]; i<=game.maxclients; i++, ent++)
|
|
{
|
|
if (!ent->inuse) continue;
|
|
if (ent->health <= 0) continue;
|
|
num_targets++;
|
|
VectorSubtract(self->s.origin,ent->s.origin,dir);
|
|
dist = VectorLength(dir);
|
|
if (dist > self->moveinfo.distance) continue;
|
|
if (self->spawnflags & ATTRACTOR_SIGHT) {
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,ent->s.origin,NULL,MASK_OPAQUE | MASK_SHOT);
|
|
if (tr.ent != ent) continue;
|
|
}
|
|
if (dist < best_dist) {
|
|
best_dist = dist;
|
|
target = ent;
|
|
}
|
|
}
|
|
}
|
|
if (self->spawnflags & ATTRACTOR_MONSTER)
|
|
{
|
|
for (i=1, ent=&g_edicts[i]; i<=globals.num_edicts; i++, ent++)
|
|
{
|
|
if (!ent->inuse) continue;
|
|
if (ent->health <= 0) continue;
|
|
if (!(ent->svflags & SVF_MONSTER)) continue;
|
|
num_targets++;
|
|
VectorSubtract(self->s.origin,ent->s.origin,dir);
|
|
dist = VectorLength(dir);
|
|
if (dist > self->moveinfo.distance) continue;
|
|
if (self->spawnflags & ATTRACTOR_SIGHT) {
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,ent->s.origin,NULL,MASK_OPAQUE | MASK_SHOT);
|
|
if (tr.ent != ent) continue;
|
|
}
|
|
if (dist < best_dist) {
|
|
best_dist = dist;
|
|
target = ent;
|
|
}
|
|
}
|
|
}
|
|
if ( !(self->spawnflags & (ATTRACTOR_PLAYER | ATTRACTOR_MONSTER )))
|
|
{
|
|
ent = G_Find(NULL,FOFS(targetname),self->target);
|
|
while (ent)
|
|
{
|
|
if (!ent->inuse) continue;
|
|
num_targets++;
|
|
VectorAdd(ent->s.origin,ent->origin_offset,targ_org);
|
|
VectorSubtract(self->s.origin,targ_org,dir);
|
|
dist = VectorLength(dir);
|
|
if (dist > self->moveinfo.distance) continue;
|
|
if (self->spawnflags & ATTRACTOR_SIGHT) {
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,targ_org,NULL,MASK_OPAQUE | MASK_SHOT);
|
|
if (tr.ent != ent) continue;
|
|
}
|
|
if (dist < best_dist) {
|
|
best_dist = dist;
|
|
target = ent;
|
|
}
|
|
ent = G_Find(ent,FOFS(targetname),self->target);
|
|
}
|
|
}
|
|
self->target_ent = target;
|
|
if (!target) {
|
|
if (num_targets > 0) self->nextthink = level.time + FRAMETIME;
|
|
return;
|
|
}
|
|
|
|
if (target != previous_target)
|
|
self->moveinfo.speed = 0;
|
|
|
|
if (self->moveinfo.speed != self->speed) {
|
|
if (self->speed > 0)
|
|
self->moveinfo.speed = min(self->speed, self->moveinfo.speed + self->accel);
|
|
else
|
|
self->moveinfo.speed = max(self->speed, self->moveinfo.speed + self->accel);
|
|
}
|
|
|
|
VectorAdd(target->s.origin,target->origin_offset,targ_org);
|
|
VectorSubtract(self->s.origin,targ_org,dir);
|
|
dist = VectorLength(dir);
|
|
if (readout->value) gi.dprintf("distance=%g, pull speed=%g\n",dist,self->moveinfo.speed);
|
|
|
|
if ((self->pathtarget) && (self->spawnflags & ATTRACTOR_PATHTARGET))
|
|
{
|
|
if (dist == 0)
|
|
{
|
|
// fire pathtarget when close
|
|
ent = G_Find(NULL,FOFS(targetname),self->pathtarget);
|
|
while (ent) {
|
|
if (ent->use)
|
|
ent->use(ent,self,self);
|
|
ent = G_Find(ent,FOFS(targetname),self->pathtarget);
|
|
}
|
|
self->spawnflags &= ~ATTRACTOR_PATHTARGET;
|
|
}
|
|
}
|
|
VectorNormalize(dir);
|
|
speed = VectorNormalize(target->velocity);
|
|
speed = max(fabs(self->moveinfo.speed),speed);
|
|
if (self->moveinfo.speed < 0) speed = -speed;
|
|
if (speed > dist*10)
|
|
{
|
|
speed = dist*10;
|
|
VectorScale(dir,speed,target->velocity);
|
|
// if NO_GRAVITY is NOT set, and target would normally be affected by gravity,
|
|
// counteract gravity during the last move
|
|
if ( !(self->spawnflags & ATTRACTOR_NO_GRAVITY) ) {
|
|
if ( (target->movetype == MOVETYPE_BOUNCE ) ||
|
|
(target->movetype == MOVETYPE_PUSHABLE) ||
|
|
(target->movetype == MOVETYPE_STEP ) ||
|
|
(target->movetype == MOVETYPE_TOSS ) ||
|
|
(target->movetype == MOVETYPE_DEBRIS ) ) {
|
|
target->velocity[2] += target->gravity * sv_gravity->value * FRAMETIME;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
VectorScale(dir,speed,target->velocity);
|
|
// Add attractor velocity in case it's a movewith deal
|
|
VectorAdd(target->velocity,self->velocity,target->velocity);
|
|
if (target->client)
|
|
{
|
|
float scale;
|
|
if (target->groundentity || target->waterlevel > 1)
|
|
{
|
|
if (target->groundentity)
|
|
scale = 0.75;
|
|
else
|
|
scale = 0.375;
|
|
// Players - add movement stuff so he MAY be able to escape
|
|
AngleVectors (target->client->v_angle, forward, right, NULL);
|
|
for (i=0 ; i<3 ; i++)
|
|
target->velocity[i] += scale * forward[i] * target->client->ucmd.forwardmove +
|
|
scale * right[i] * target->client->ucmd.sidemove;
|
|
target->velocity[2] += scale * target->client->ucmd.upmove;
|
|
}
|
|
}
|
|
// If target is on the ground and attractor is overhead, give 'em a little nudge.
|
|
// This is only really necessary for players
|
|
if (target->groundentity && (self->s.origin[2] > target->absmax[2])) {
|
|
target->s.origin[2] += 1;
|
|
target->groundentity = NULL;
|
|
}
|
|
|
|
if (self->sounds)
|
|
{
|
|
vec3_t new_origin;
|
|
|
|
if (target->client)
|
|
VectorCopy(target->s.origin,new_origin);
|
|
else
|
|
VectorMA(targ_org,FRAMETIME,target->velocity,new_origin);
|
|
|
|
switch (self->sounds)
|
|
{
|
|
case 1:
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_MEDIC_CABLE_ATTACK);
|
|
gi.WriteShort(self-g_edicts);
|
|
gi.WritePosition(self->s.origin);
|
|
gi.WritePosition(new_origin);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
|
break;
|
|
case 2:
|
|
gi.WriteByte(svc_temp_entity);
|
|
gi.WriteByte(TE_BFG_LASER);
|
|
gi.WritePosition(self->s.origin);
|
|
gi.WritePosition(new_origin);
|
|
gi.multicast(self->s.origin, MULTICAST_PVS);
|
|
}
|
|
}
|
|
|
|
|
|
if (self->spawnflags & ATTRACTOR_NO_GRAVITY)
|
|
target->gravity_debounce_time = level.time + 2*FRAMETIME;
|
|
gi.linkentity(target);
|
|
|
|
if (!num_targets) {
|
|
// shut 'er down
|
|
self->spawnflags &= ~ATTRACTOR_ON;
|
|
}
|
|
else {
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
void target_attractor_think(edict_t *self)
|
|
{
|
|
edict_t *ent, *target;
|
|
trace_t tr;
|
|
vec3_t dir, targ_org;
|
|
vec_t dist, speed;
|
|
vec3_t forward, right;
|
|
int i;
|
|
int ent_start;
|
|
int num_targets = 0;
|
|
|
|
if ( !(self->spawnflags & ATTRACTOR_ON) ) return;
|
|
|
|
if (self->moveinfo.speed != self->speed) {
|
|
if (self->speed > 0)
|
|
self->moveinfo.speed = min(self->speed, self->moveinfo.speed + self->accel);
|
|
else
|
|
self->moveinfo.speed = max(self->speed, self->moveinfo.speed + self->accel);
|
|
}
|
|
|
|
target = NULL;
|
|
ent_start = 1;
|
|
while (true)
|
|
{
|
|
if (self->spawnflags & (ATTRACTOR_PLAYER | ATTRACTOR_MONSTER))
|
|
{
|
|
target = NULL;
|
|
for (i=ent_start, ent=&g_edicts[ent_start];i<globals.num_edicts && !target; i++, ent++)
|
|
{
|
|
if ((self->spawnflags & ATTRACTOR_PLAYER) && ent->client && ent->inuse)
|
|
{
|
|
target = ent;
|
|
ent_start = i+1;
|
|
continue;
|
|
}
|
|
if ((self->spawnflags & ATTRACTOR_MONSTER) && (ent->svflags & SVF_MONSTER) && (ent->inuse))
|
|
{
|
|
target = ent;
|
|
ent_start = i+1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
target = G_Find(target,FOFS(targetname),self->target);
|
|
if (!target) break;
|
|
if (!target->inuse) continue;
|
|
if ( ((target->client) || (target->svflags & SVF_MONSTER)) && (target->health <= 0)) continue;
|
|
num_targets++;
|
|
|
|
VectorAdd(target->s.origin,target->origin_offset,targ_org);
|
|
VectorSubtract(self->s.origin,targ_org,dir);
|
|
dist = VectorLength(dir);
|
|
|
|
if (self->spawnflags & ATTRACTOR_SIGHT) {
|
|
tr = gi.trace(self->s.origin,vec3_origin,vec3_origin,target->s.origin,NULL,MASK_OPAQUE | MASK_SHOT);
|
|
if (tr.ent != target) continue;
|
|
}
|
|
|
|
if (readout->value) gi.dprintf("distance=%g, pull speed=%g\n",dist,self->moveinfo.speed);
|
|
if (dist > self->moveinfo.distance)
|
|
continue;
|
|
|
|
if ((self->pathtarget) && (self->spawnflags & ATTRACTOR_PATHTARGET))
|
|
{
|
|
if (dist == 0) {
|
|
// fire pathtarget when close
|
|
ent = G_Find(NULL,FOFS(targetname),self->pathtarget);
|
|
while (ent) {
|
|
if (ent->use)
|
|
ent->use(ent,self,self);
|
|
ent = G_Find(ent,FOFS(targetname),self->pathtarget);
|
|
}
|
|
self->spawnflags &= ~ATTRACTOR_PATHTARGET;
|
|
}
|
|
}
|
|
VectorNormalize(dir);
|
|
speed = VectorNormalize(target->velocity);
|
|
speed = max(fabs(self->moveinfo.speed),speed);
|
|
if (self->moveinfo.speed < 0) speed = -speed;
|
|
if (speed > dist*10) {
|
|
speed = dist*10;
|
|
VectorScale(dir,speed,target->velocity);
|
|
// if NO_GRAVITY is NOT set, and target would normally be affected by gravity,
|
|
// counteract gravity during the last move
|
|
if ( !(self->spawnflags & ATTRACTOR_NO_GRAVITY) ) {
|
|
if ( (target->movetype == MOVETYPE_BOUNCE ) ||
|
|
(target->movetype == MOVETYPE_PUSHABLE) ||
|
|
(target->movetype == MOVETYPE_STEP ) ||
|
|
(target->movetype == MOVETYPE_TOSS ) ||
|
|
(target->movetype == MOVETYPE_DEBRIS ) ) {
|
|
target->velocity[2] += target->gravity * sv_gravity->value * FRAMETIME;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
VectorScale(dir,speed,target->velocity);
|
|
// Add attractor velocity in case it's a movewith deal
|
|
VectorAdd(target->velocity,self->velocity,target->velocity);
|
|
if (target->client) {
|
|
float scale;
|
|
if (target->groundentity || target->waterlevel > 1) {
|
|
if (target->groundentity)
|
|
scale = 0.75;
|
|
else
|
|
scale = 0.375;
|
|
// Players - add movement stuff so he MAY be able to escape
|
|
AngleVectors (target->client->v_angle, forward, right, NULL);
|
|
for (i=0 ; i<3 ; i++)
|
|
target->velocity[i] += scale * forward[i] * target->client->ucmd.forwardmove +
|
|
scale * right[i] * target->client->ucmd.sidemove;
|
|
target->velocity[2] += scale * target->client->ucmd.upmove;
|
|
}
|
|
}
|
|
// If target is on the ground and attractor is overhead, give 'em a little nudge.
|
|
// This is only really necessary for players
|
|
if (target->groundentity && (self->s.origin[2] > target->absmax[2])) {
|
|
target->s.origin[2] += 1;
|
|
target->groundentity = NULL;
|
|
}
|
|
if (self->spawnflags & ATTRACTOR_NO_GRAVITY)
|
|
target->gravity_debounce_time = level.time + 2*FRAMETIME;
|
|
gi.linkentity(target);
|
|
}
|
|
if (!num_targets) {
|
|
// shut 'er down
|
|
self->spawnflags &= ~ATTRACTOR_ON;
|
|
}
|
|
else {
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
}
|
|
|
|
void use_target_attractor(edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->spawnflags & ATTRACTOR_ON) {
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
else
|
|
{
|
|
self->spawnflags &= ~ATTRACTOR_ON;
|
|
self->s.sound = 0;
|
|
self->target_ent = NULL;
|
|
self->nextthink = 0;
|
|
}
|
|
}
|
|
else {
|
|
self->spawnflags |= (ATTRACTOR_ON + ATTRACTOR_PATHTARGET);
|
|
self->s.sound = self->noise_index;
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
self->s.attenuation = self->attenuation;
|
|
#endif
|
|
|
|
if (self->spawnflags & ATTRACTOR_SINGLE)
|
|
self->think = target_attractor_think_single;
|
|
else
|
|
self->think = target_attractor_think;
|
|
self->moveinfo.speed = 0;
|
|
gi.linkentity(self);
|
|
self->think(self);
|
|
}
|
|
}
|
|
|
|
void SP_target_attractor(edict_t *self)
|
|
{
|
|
if (!self->target && !(self->spawnflags & ATTRACTOR_PLAYER) &&
|
|
!(self->spawnflags & ATTRACTOR_MONSTER)) {
|
|
gi.dprintf("target_attractor without a target at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
self->class_id = ENTITY_TARGET_ATTRACTOR;
|
|
|
|
if (self->sounds) {
|
|
// if ((self->spawnflags & ATTRACTOR_PLAYER) || (self->spawnflags & ATTRACTOR_MONSTER)) {
|
|
self->spawnflags |= (ATTRACTOR_SIGHT | ATTRACTOR_SINGLE);
|
|
// }
|
|
// else {
|
|
// gi.dprintf("Target_attractor sounds key is only valid\n"
|
|
// "for PLAYER or MONSTER. Setting sounds=0\n");
|
|
// }
|
|
}
|
|
if (st.distance)
|
|
self->moveinfo.distance = st.distance;
|
|
else
|
|
self->moveinfo.distance = WORLD_SIZE; // was 8192
|
|
|
|
self->solid = SOLID_NOT;
|
|
if (self->movewith)
|
|
self->movetype = MOVETYPE_PUSH;
|
|
else
|
|
self->movetype = MOVETYPE_NONE;
|
|
self->use = use_target_attractor;
|
|
|
|
if (st.noise)
|
|
self->noise_index = gi.soundindex(st.noise);
|
|
else
|
|
self->noise_index = 0;
|
|
|
|
if (!self->speed)
|
|
self->speed = 100;
|
|
|
|
if (!self->accel)
|
|
self->accel = self->speed;
|
|
else {
|
|
self->accel *= 0.1;
|
|
if (self->accel > self->speed)
|
|
self->accel = self->speed;
|
|
}
|
|
|
|
if (self->spawnflags & ATTRACTOR_ON)
|
|
{
|
|
if (self->spawnflags & ATTRACTOR_SINGLE)
|
|
self->think = target_attractor_think_single;
|
|
else
|
|
self->think = target_attractor_think;
|
|
if (self->sounds)
|
|
self->nextthink = level.time + 2*FRAMETIME;
|
|
else
|
|
self->think(self);
|
|
}
|
|
}
|
|
|
|
/*===================================================================
|
|
TARGET_CD
|
|
Play the CD track specified by the "sounds" value, looping the
|
|
track "dmg" times. This does NOT override player's option to
|
|
disable CD music, and of course does nothing if a music CD is not
|
|
in place. If "dmg" is not specified, track is looped cd_loopcount
|
|
(default=4) times.
|
|
===================================================================*/
|
|
void use_target_CD (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (self->musictrack && strlen(self->musictrack))
|
|
gi.configstring (CS_CDTRACK, self->musictrack);
|
|
else
|
|
gi.configstring (CS_CDTRACK, va("%d",self->sounds));
|
|
if ((self->dmg > 0) && (!deathmatch->value) && (!coop->value))
|
|
stuffcmd(&g_edicts[1],va("cd_loopcount %d\n",self->dmg));
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
void SP_target_CD (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_CD;
|
|
|
|
self->use = use_target_CD;
|
|
if (!self->dmg)
|
|
self->dmg = lazarus_cd_loop->value;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
/*
|
|
===================================================================
|
|
TARGET_MONITOR
|
|
Move the player's viewpoint to the target_monitor origin,
|
|
gives the target_monitor angles to the player, and freezes him
|
|
for "wait" seconds (default=3). If wait < 0, target_monitor
|
|
must be targeted a second time to free the player.
|
|
===================================================================
|
|
*/
|
|
#define SF_MONITOR_CHASECAM 1
|
|
#define SF_MONITOR_EYEBALL 2 // Same as CHASECAM, but viewpoint is at the target
|
|
// entity's viewheight, and target entity is made
|
|
// invisible while in use
|
|
#define SF_MONITOR_CAMERAEFFECT 4 // Knightmare- camera effect
|
|
#define SF_MONITOR_LETTERBOX 8 // Knightmare- letterboxing
|
|
|
|
void target_monitor_off (edict_t *self)
|
|
{
|
|
int i;
|
|
edict_t *faker;
|
|
edict_t *player;
|
|
|
|
player = self->child;
|
|
if (!player) return;
|
|
|
|
if (self->spawnflags & SF_MONITOR_EYEBALL)
|
|
{
|
|
if (self->target_ent)
|
|
self->target_ent->svflags &= ~SVF_NOCLIENT;
|
|
}
|
|
faker = player->client->camplayer;
|
|
VectorCopy (faker->s.origin, player->s.origin);
|
|
gi.TagFree (faker->client);
|
|
G_FreeEdict (faker);
|
|
player->client->ps.pmove.origin[0] = player->s.origin[0]*8;
|
|
player->client->ps.pmove.origin[1] = player->s.origin[1]*8;
|
|
player->client->ps.pmove.origin[2] = player->s.origin[2]*8;
|
|
for (i=0 ; i<3 ; i++)
|
|
player->client->ps.pmove.delta_angles[i] =
|
|
ANGLE2SHORT(player->client->org_viewangles[i] - player->client->resp.cmd_angles[i]);
|
|
VectorCopy (player->client->org_viewangles, player->client->resp.cmd_angles);
|
|
VectorCopy (player->client->org_viewangles, player->s.angles);
|
|
VectorCopy (player->client->org_viewangles, player->client->ps.viewangles);
|
|
VectorCopy (player->client->org_viewangles, player->client->v_angle);
|
|
|
|
player->client->ps.gunindex = gi.modelindex(player->client->pers.weapon->view_model);
|
|
player->client->ps.pmove.pm_flags &= ~PMF_NO_PREDICTION;
|
|
player->client->ps.pmove.pm_type = PM_NORMAL;
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
player->client->ps.rdflags &= ~(RDF_CAMERAEFFECT|RDF_LETTERBOX); // Knightmare- letterboxing
|
|
#endif
|
|
player->svflags &= ~SVF_NOCLIENT;
|
|
player->clipmask = MASK_PLAYERSOLID;
|
|
player->solid = SOLID_BBOX;
|
|
player->viewheight = 22;
|
|
player->client->camplayer = NULL;
|
|
player->target_ent = NULL;
|
|
gi.unlinkentity(player);
|
|
KillBox(player);
|
|
gi.linkentity(player);
|
|
|
|
if (self->noise_index)
|
|
gi.sound (player, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
|
|
|
|
// if we were previously in third person view, restore it
|
|
if (tpp->value)
|
|
Cmd_Chasecam_Toggle (player);
|
|
|
|
self->child = NULL;
|
|
gi.linkentity(self);
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->use = NULL;
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
|
|
void target_monitor_move (edict_t *self)
|
|
{
|
|
// "chase cam"
|
|
trace_t trace;
|
|
vec3_t forward, o, goal;
|
|
|
|
if (!self->target_ent || !self->target_ent->inuse)
|
|
{
|
|
if (self->wait)
|
|
{
|
|
self->think = target_monitor_off;
|
|
self->nextthink = self->monsterinfo.attack_finished;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ( (self->monsterinfo.attack_finished > 0) &&
|
|
(level.time > self->monsterinfo.attack_finished))
|
|
{
|
|
target_monitor_off(self);
|
|
return;
|
|
}
|
|
|
|
AngleVectors (self->target_ent->s.angles, forward, NULL, NULL);
|
|
VectorMA (self->target_ent->s.origin, -self->moveinfo.distance, forward, o);
|
|
|
|
o[2] += self->viewheight;
|
|
|
|
VectorSubtract (o, self->s.origin, o);
|
|
VectorMA (self->s.origin, 0.2, o, o);
|
|
|
|
trace = gi.trace(self->target_ent->s.origin, NULL, NULL, o, self, MASK_SOLID);
|
|
VectorCopy (trace.endpos, goal);
|
|
VectorMA (goal, 2, forward, goal);
|
|
|
|
// pad for floors and ceilings
|
|
VectorCopy(goal, o);
|
|
o[2] += 6;
|
|
trace = gi.trace(goal, NULL, NULL, o, self, MASK_SOLID);
|
|
if (trace.fraction < 1) {
|
|
VectorCopy(trace.endpos, goal);
|
|
goal[2] -= 6;
|
|
}
|
|
|
|
VectorCopy(goal, o);
|
|
o[2] -= 6;
|
|
trace = gi.trace(goal, NULL, NULL, o, self, MASK_SOLID);
|
|
if (trace.fraction < 1) {
|
|
VectorCopy(trace.endpos, goal);
|
|
goal[2] += 6;
|
|
}
|
|
|
|
VectorCopy(goal, self->s.origin);
|
|
self->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
|
|
void use_target_monitor (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
int i;
|
|
edict_t *faker;
|
|
edict_t *monster;
|
|
gclient_t *cl;
|
|
|
|
if (!activator->client)
|
|
return;
|
|
|
|
if (self->child)
|
|
{
|
|
if (self->wait < 0)
|
|
target_monitor_off (self);
|
|
return;
|
|
}
|
|
|
|
if (self->target)
|
|
self->target_ent = G_Find(NULL, FOFS(targetname), self->target);
|
|
|
|
// if this is a CHASE_CAM target_monitor and the target no longer
|
|
// exists, remove this target_monitor and exit
|
|
if (self->spawnflags & SF_MONITOR_CHASECAM)
|
|
{
|
|
if (!self->target_ent || !self->target_ent->inuse)
|
|
{
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
}
|
|
// save current viewangles
|
|
VectorCopy (activator->client->v_angle, activator->client->org_viewangles);
|
|
|
|
// create a fake player to stand in real player's position
|
|
faker = activator->client->camplayer = G_Spawn();
|
|
faker->s.frame = activator->s.frame;
|
|
VectorCopy (activator->s.origin, faker->s.origin);
|
|
VectorCopy (activator->velocity, faker->velocity);
|
|
VectorCopy (activator->s.angles, faker->s.angles);
|
|
faker->s = activator->s;
|
|
faker->takedamage = DAMAGE_NO; // so monsters won't attack
|
|
faker->flags |= FL_NOTARGET; // ... just to make sure
|
|
// faker->movetype = MOVETYPE_WALK;
|
|
faker->movetype = MOVETYPE_TOSS;
|
|
faker->groundentity = activator->groundentity;
|
|
faker->viewheight = activator->viewheight;
|
|
faker->inuse = true;
|
|
faker->classname = "camplayer";
|
|
faker->mass = activator->mass;
|
|
faker->solid = SOLID_BBOX;
|
|
faker->deadflag = DEAD_NO;
|
|
faker->clipmask = MASK_PLAYERSOLID;
|
|
faker->health = 100000; // invulnerable
|
|
faker->light_level = activator->light_level;
|
|
faker->think = faker_animate;
|
|
faker->nextthink = level.time + FRAMETIME;
|
|
VectorCopy (activator->mins, faker->mins);
|
|
VectorCopy (activator->maxs, faker->maxs);
|
|
// create a client so you can pick up items/be shot/etc while in camera
|
|
cl = (gclient_t *)gi.TagMalloc(sizeof(gclient_t), TAG_LEVEL);
|
|
faker->client = cl;
|
|
faker->target_ent = activator;
|
|
gi.linkentity (faker);
|
|
|
|
if (self->target_ent && self->target_ent->inuse)
|
|
{
|
|
if (self->spawnflags & SF_MONITOR_EYEBALL)
|
|
VectorCopy (self->target_ent->s.angles, activator->client->ps.viewangles);
|
|
else
|
|
{
|
|
vec3_t dir;
|
|
VectorSubtract (self->target_ent->s.origin, self->s.origin, dir);
|
|
vectoangles (dir, activator->client->ps.viewangles);
|
|
}
|
|
}
|
|
else
|
|
VectorCopy (self->s.angles, activator->client->ps.viewangles);
|
|
|
|
VectorCopy (self->s.origin, activator->s.origin);
|
|
activator->client->ps.pmove.origin[0] = self->s.origin[0]*8;
|
|
activator->client->ps.pmove.origin[1] = self->s.origin[1]*8;
|
|
activator->client->ps.pmove.origin[2] = self->s.origin[2]*8;
|
|
activator->client->ps.pmove.pm_type = PM_FREEZE;
|
|
#ifdef KMQUAKE2_ENGINE_MOD
|
|
// Knightmare- camera effect and letterboxing
|
|
if (self->spawnflags & SF_MONITOR_CAMERAEFFECT)
|
|
activator->client->ps.rdflags |= RDF_CAMERAEFFECT;
|
|
if (self->spawnflags & SF_MONITOR_LETTERBOX)
|
|
activator->client->ps.rdflags |= RDF_LETTERBOX;
|
|
#endif
|
|
activator->svflags |= SVF_NOCLIENT;
|
|
activator->solid = SOLID_NOT;
|
|
activator->viewheight = 0;
|
|
if (activator->client->chasetoggle)
|
|
{
|
|
Cmd_Chasecam_Toggle (activator);
|
|
activator->client->pers.chasetoggle = 1;
|
|
}
|
|
else
|
|
activator->client->pers.chasetoggle = 0;
|
|
activator->clipmask = 0;
|
|
VectorClear (activator->velocity);
|
|
activator->client->ps.gunindex = 0;
|
|
activator->client->ps.pmove.pm_flags |= PMF_NO_PREDICTION;
|
|
gi.linkentity(activator);
|
|
|
|
gi.unlinkentity(faker);
|
|
KillBox (faker);
|
|
gi.linkentity(faker);
|
|
|
|
// check to see if player is the enemy of any monster.
|
|
for (i=maxclients->value+1, monster=g_edicts+i; i<globals.num_edicts; i++, monster++)
|
|
{
|
|
if (!monster->inuse) continue;
|
|
if (!(monster->svflags & SVF_MONSTER)) continue;
|
|
if (monster->enemy == activator)
|
|
{
|
|
monster->enemy = NULL;
|
|
monster->oldenemy = NULL;
|
|
if (monster->goalentity == activator)
|
|
monster->goalentity = NULL;
|
|
if (monster->movetarget == activator)
|
|
monster->movetarget = NULL;
|
|
monster->monsterinfo.attack_finished = level.time + 1;
|
|
FindTarget (monster);
|
|
}
|
|
}
|
|
|
|
activator->target_ent = self;
|
|
self->child = activator;
|
|
|
|
if (self->noise_index)
|
|
gi.sound (activator, CHAN_VOICE, self->noise_index, 1, ATTN_NORM, 0);
|
|
|
|
if (self->spawnflags & SF_MONITOR_CHASECAM)
|
|
{
|
|
if (self->wait > 0)
|
|
self->monsterinfo.attack_finished = level.time + self->wait;
|
|
else
|
|
self->monsterinfo.attack_finished = 0;
|
|
|
|
if (self->spawnflags & SF_MONITOR_EYEBALL)
|
|
{
|
|
self->viewheight = self->target_ent->viewheight;
|
|
self->target_ent->svflags |= SVF_NOCLIENT;
|
|
}
|
|
VectorCopy (self->target_ent->s.origin, self->s.origin);
|
|
self->think = target_monitor_move;
|
|
self->think(self);
|
|
}
|
|
else if (self->wait > 0)
|
|
{
|
|
self->think = target_monitor_off;
|
|
self->nextthink = level.time + self->wait;
|
|
}
|
|
}
|
|
|
|
|
|
void SP_target_monitor (edict_t *self)
|
|
{
|
|
char buffer[MAX_QPATH];
|
|
|
|
self->class_id = ENTITY_TARGET_MONITOR;
|
|
|
|
if (!self->wait)
|
|
self->wait = 3;
|
|
self->use = use_target_monitor;
|
|
self->movetype = MOVETYPE_NOCLIP;
|
|
if (st.noise)
|
|
{
|
|
if (!strstr (st.noise, ".wav"))
|
|
Com_sprintf (buffer, sizeof(buffer), "%s.wav", st.noise);
|
|
else
|
|
// strncpy (buffer, st.noise, sizeof(buffer));
|
|
Q_strncpyz (buffer, sizeof(buffer), st.noise);
|
|
self->noise_index = gi.soundindex (buffer);
|
|
}
|
|
|
|
if (self->spawnflags & SF_MONITOR_EYEBALL)
|
|
self->spawnflags |= SF_MONITOR_CHASECAM;
|
|
|
|
if (self->spawnflags & SF_MONITOR_CHASECAM)
|
|
{ // chase cam
|
|
if (self->spawnflags & SF_MONITOR_EYEBALL)
|
|
{
|
|
self->moveinfo.distance = 0;
|
|
self->viewheight = 0;
|
|
}
|
|
else
|
|
{
|
|
if (st.distance)
|
|
self->moveinfo.distance = st.distance;
|
|
else
|
|
self->moveinfo.distance = 128;
|
|
|
|
if (st.height)
|
|
self->viewheight = st.height;
|
|
else
|
|
self->viewheight = 16;
|
|
}
|
|
|
|
// MUST have target
|
|
if (!self->target)
|
|
{
|
|
gi.dprintf("CHASECAM target_monitor with no target at %s\n",vtos(self->s.origin));
|
|
self->spawnflags &= ~(SF_MONITOR_CHASECAM | SF_MONITOR_EYEBALL);
|
|
}
|
|
else if (self->movewith)
|
|
{
|
|
gi.dprintf("CHASECAM target_monitor cannot use 'movewith'\n");
|
|
self->spawnflags &= ~(SF_MONITOR_CHASECAM | SF_MONITOR_EYEBALL);
|
|
}
|
|
}
|
|
|
|
gi.linkentity(self);
|
|
}
|
|
|
|
/*====================================================================================
|
|
TARGET_ANIMATION causes the target entity to use the animation frames
|
|
"startframe" through "startframe" + "framenumbers" - 1.
|
|
|
|
Spawnflags:
|
|
ACTIVATOR = 1 - target_animation acts on it's activator rather than
|
|
it's target
|
|
|
|
"message" - specifies allowable classname to animate. This prevents
|
|
animating entities with inapplicable frame numbers
|
|
=====================================================================================*/
|
|
void target_animate (edict_t *ent)
|
|
{
|
|
if ( (ent->s.frame < ent->monsterinfo.currentmove->firstframe) ||
|
|
(ent->s.frame >= ent->monsterinfo.currentmove->lastframe ) )
|
|
{
|
|
if (ent->monsterinfo.currentmove->endfunc)
|
|
{
|
|
ent->think = ent->monsterinfo.currentmove->endfunc;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else if (ent->svflags & SVF_MONSTER)
|
|
{
|
|
// Hopefully we don't get here, but if we DO then we definitely
|
|
// need for monsters/actors to turn their brains back on.
|
|
ent->think = monster_think;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
ent->think = NULL;
|
|
ent->nextthink = 0;
|
|
}
|
|
ent->monsterinfo.currentmove = ent->monsterinfo.savemove;
|
|
return;
|
|
}
|
|
ent->s.frame++;
|
|
ent->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(ent);
|
|
}
|
|
|
|
void target_animation_use (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *target = NULL;
|
|
|
|
if (level.time < self->touch_debounce_time)
|
|
return;
|
|
if (self->spawnflags & 1)
|
|
{
|
|
if (activator && activator->client)
|
|
return;
|
|
if (self->message && Q_stricmp(self->message, activator->classname))
|
|
return;
|
|
if (!self->target)
|
|
target = activator;
|
|
}
|
|
if (!target)
|
|
{
|
|
if (!self->target)
|
|
return;
|
|
target = G_Find(NULL,FOFS(targetname),self->target);
|
|
if (!target)
|
|
return;
|
|
}
|
|
// Don't allow target to be animated if ALREADY under influence of
|
|
// another target_animation
|
|
if (target->think == target_animate)
|
|
return;
|
|
self->monsterinfo.currentmove->firstframe = self->startframe;
|
|
self->monsterinfo.currentmove->lastframe = self->startframe + self->framenumbers - 1;
|
|
self->monsterinfo.currentmove->frame = NULL;
|
|
self->monsterinfo.currentmove->endfunc = target->think;
|
|
target->s.frame = self->startframe;
|
|
target->think = target_animate;
|
|
target->monsterinfo.savemove = target->monsterinfo.currentmove;
|
|
target->monsterinfo.currentmove = self->monsterinfo.currentmove;
|
|
target->nextthink = level.time + FRAMETIME;
|
|
gi.linkentity(target);
|
|
|
|
self->count--;
|
|
if (!self->count)
|
|
G_FreeEdict(self);
|
|
else
|
|
self->touch_debounce_time = level.time + (self->framenumbers+1)*FRAMETIME;
|
|
}
|
|
|
|
void SP_target_animation (edict_t *self)
|
|
{
|
|
#if 1
|
|
gi.dprintf("Target_animation is currently not implemented.\n");
|
|
G_FreeEdict(self);
|
|
return;
|
|
#else
|
|
mmove_t *move;
|
|
|
|
self->class_id = ENTITY_TARGET_ANIMATION;
|
|
|
|
if (!self->target && !(self->spawnflags & 1))
|
|
{
|
|
gi.dprintf("target_animation w/o a target at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return; // Knightmare- exit function after this!
|
|
}
|
|
switch (self->sounds)
|
|
{
|
|
case 1:
|
|
// actor jump
|
|
self->startframe = 66;
|
|
self->framenumbers = 6;
|
|
break;
|
|
case 2:
|
|
// actor flip
|
|
self->startframe = 72;
|
|
self->framenumbers = 12;
|
|
break;
|
|
case 3:
|
|
// actor salute
|
|
self->startframe = 84;
|
|
self->framenumbers = 11;
|
|
break;
|
|
case 4:
|
|
// actor taunt
|
|
self->startframe = 95;
|
|
self->framenumbers = 17;
|
|
break;
|
|
case 5:
|
|
// actor wave
|
|
self->startframe = 112;
|
|
self->framenumbers = 11;
|
|
break;
|
|
case 6:
|
|
// actor point
|
|
self->startframe = 123;
|
|
self->framenumbers = 12;
|
|
break;
|
|
default:
|
|
if (!self->framenumbers)
|
|
self->framenumbers = 1;
|
|
}
|
|
self->use = target_animation_use;
|
|
move = gi.TagMalloc(sizeof(mmove_t), TAG_LEVEL);
|
|
self->monsterinfo.currentmove = move;
|
|
#endif
|
|
}
|
|
|
|
/*===================================================================================
|
|
TARGET_FAILURE - Halts the game, fades the screen to black and displays
|
|
a message explaining to the player how he screwed up.
|
|
Optionally plays a sound.
|
|
====================================================================================*/
|
|
void target_failure_wipe (edict_t *self)
|
|
{
|
|
edict_t *player;
|
|
|
|
player = &g_edicts[1]; // Gotta be, since this is SP only
|
|
if (player->client->textdisplay)
|
|
Text_Close(player);
|
|
}
|
|
|
|
void target_failure_player_die (edict_t *player)
|
|
{
|
|
int n;
|
|
|
|
// player_die w/o... umm... dying
|
|
|
|
if (player->client->chasetoggle)
|
|
{
|
|
ChasecamRemove (player, OPTION_OFF);
|
|
player->client->pers.chasetoggle = 1;
|
|
}
|
|
else
|
|
player->client->pers.chasetoggle = 0;
|
|
player->client->pers.spawn_landmark = false; // paranoia check
|
|
player->client->pers.spawn_levelchange = false;
|
|
player->client->zooming = 0;
|
|
player->client->zoomed = false;
|
|
SetSensitivities(player,true);
|
|
if (player->client->spycam)
|
|
camera_off(player);
|
|
VectorClear (player->avelocity);
|
|
player->takedamage = DAMAGE_NO;
|
|
player->movetype = MOVETYPE_NONE;
|
|
player->s.modelindex2 = 0; // remove linked weapon model
|
|
player->s.sound = 0;
|
|
player->client->weapon_sound = 0;
|
|
player->svflags |= SVF_DEADMONSTER;
|
|
player->client->respawn_time = level.time + 1.0;
|
|
player->client->ps.gunindex = 0;
|
|
// clear inventory
|
|
for (n = 0; n < game.num_items; n++)
|
|
{
|
|
player->client->pers.inventory[n] = 0;
|
|
}
|
|
// remove powerups
|
|
player->client->quad_framenum = 0;
|
|
player->client->invincible_framenum = 0;
|
|
player->client->breather_framenum = 0;
|
|
player->client->enviro_framenum = 0;
|
|
player->flags &= ~(FL_POWER_SHIELD|FL_POWER_SCREEN);
|
|
player->client->flashlight = false;
|
|
player->deadflag = DEAD_FROZEN;
|
|
gi.linkentity (player);
|
|
}
|
|
void target_failure_think (edict_t *self)
|
|
{
|
|
target_failure_player_die(self->target_ent);
|
|
self->target_ent = NULL;
|
|
self->think = target_failure_wipe;
|
|
self->nextthink = level.time + 10;
|
|
}
|
|
|
|
void target_failure_fade_lights (edict_t *self)
|
|
{
|
|
char lightvalue[2];
|
|
char values[] = "abcdefghijklm";
|
|
|
|
lightvalue[0] = values[self->flags];
|
|
lightvalue[1] = 0;
|
|
gi.configstring(CS_LIGHTS+0, lightvalue);
|
|
if (self->flags)
|
|
{
|
|
self->flags--;
|
|
self->nextthink = level.time + 0.2;
|
|
}
|
|
else
|
|
{
|
|
target_failure_player_die(self->target_ent);
|
|
self->target_ent = NULL;
|
|
self->think = target_failure_wipe;
|
|
self->nextthink = level.time + 10;
|
|
}
|
|
}
|
|
|
|
void Use_Target_Text(edict_t *self, edict_t *other, edict_t *activator);
|
|
void use_target_failure (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (!activator->client)
|
|
return;
|
|
|
|
if (self->target_ent)
|
|
return;
|
|
|
|
if (strlen(self->message))
|
|
Use_Target_Text(self,other,activator);
|
|
|
|
if (self->noise_index)
|
|
gi.sound (activator, CHAN_VOICE|CHAN_RELIABLE, self->noise_index, 1, ATTN_NORM, 0);
|
|
|
|
self->target_ent = activator;
|
|
if (Q_stricmp(vid_ref->string,"gl") && Q_stricmp(vid_ref->string,"kmgl"))
|
|
{
|
|
self->flags = 12;
|
|
self->think = target_failure_fade_lights;
|
|
self->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else
|
|
{
|
|
activator->client->fadestart= level.framenum;
|
|
activator->client->fadein = level.framenum + 40;
|
|
activator->client->fadehold = activator->client->fadein + 100000;
|
|
activator->client->fadeout = 0;
|
|
activator->client->fadecolor[0] = 0;
|
|
activator->client->fadecolor[1] = 0;
|
|
activator->client->fadecolor[2] = 0;
|
|
activator->client->fadealpha = 1.0;
|
|
self->think = target_failure_think;
|
|
self->nextthink = level.time + 4;
|
|
}
|
|
activator->deadflag = DEAD_FROZEN;
|
|
gi.linkentity(activator);
|
|
}
|
|
|
|
void SP_target_failure (edict_t *self)
|
|
{
|
|
if (deathmatch->value || coop->value)
|
|
{ // SP only
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_FAILURE;
|
|
|
|
self->use = use_target_failure;
|
|
if (st.noise)
|
|
self->noise_index = gi.soundindex(st.noise);
|
|
}
|
|
|
|
/*=====================================================================================
|
|
TARGET_CHANGE - copies selected fields to target.
|
|
Target value should be of the form "entitytotarget,new target value".
|
|
All other keys are replacements for the targeted entity, NOT the
|
|
target_change.
|
|
======================================================================================*/
|
|
void use_target_change (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
char *buffer;
|
|
char *target;
|
|
char *newtarget;
|
|
int L;
|
|
size_t bufSize;
|
|
int newteams=0;
|
|
edict_t *target_ent;
|
|
|
|
if (!self->target)
|
|
return;
|
|
|
|
L = (int)strlen(self->target);
|
|
bufSize = L+1;
|
|
buffer = (char *)gi.TagMalloc(bufSize, TAG_LEVEL);
|
|
// strncpy(buffer, self->target);
|
|
Q_strncpyz (buffer, bufSize, self->target);
|
|
newtarget = strstr(buffer,",");
|
|
if (newtarget)
|
|
{
|
|
*newtarget = 0;
|
|
newtarget++;
|
|
}
|
|
target = buffer;
|
|
target_ent = G_Find(NULL,FOFS(targetname),target);
|
|
while (target_ent)
|
|
{
|
|
if ( newtarget && (strlen(newtarget) > 0) )
|
|
target_ent->target = G_CopyString(newtarget);
|
|
if ( self->newtargetname && (strlen(self->newtargetname) > 0) )
|
|
target_ent->targetname = G_CopyString(self->newtargetname);
|
|
if ( self->team && (strlen(self->team) > 0) )
|
|
{
|
|
target_ent->team = G_CopyString(self->team);
|
|
newteams++;
|
|
}
|
|
if (VectorLength(self->s.angles))
|
|
{
|
|
VectorCopy (self->s.angles, target_ent->s.angles);
|
|
if (target_ent->solid == SOLID_BSP)
|
|
G_SetMovedir (target_ent->s.angles, target_ent->movedir);
|
|
}
|
|
if ( self->deathtarget && (strlen(self->deathtarget) > 0) )
|
|
target_ent->deathtarget = G_CopyString(self->deathtarget);
|
|
if ( self->pathtarget && (strlen(self->pathtarget) > 0) )
|
|
target_ent->pathtarget = G_CopyString(self->pathtarget);
|
|
if ( self->killtarget && (strlen(self->killtarget) > 0) )
|
|
target_ent->killtarget = G_CopyString(self->killtarget);
|
|
if ( self->message && (strlen(self->message) > 0) )
|
|
target_ent->message = G_CopyString(self->message);
|
|
if (self->delay > 0)
|
|
target_ent->delay = self->delay;
|
|
if (self->dmg > 0)
|
|
target_ent->dmg = self->dmg;
|
|
if (self->health > 0)
|
|
target_ent->health = self->health;
|
|
if (self->mass > 0)
|
|
target_ent->mass = self->mass;
|
|
if (self->pitch_speed > 0)
|
|
target_ent->pitch_speed = self->pitch_speed;
|
|
if (self->random > 0)
|
|
target_ent->random = self->random;
|
|
if (self->roll_speed > 0)
|
|
target_ent->roll_speed = self->roll_speed;
|
|
if (self->wait > 0)
|
|
target_ent->wait = self->wait;
|
|
if (self->yaw_speed > 0)
|
|
target_ent->yaw_speed = self->yaw_speed;
|
|
if (self->noise_index)
|
|
{
|
|
if (target_ent->s.sound == target_ent->noise_index)
|
|
target_ent->s.sound = target_ent->noise_index = self->noise_index;
|
|
else
|
|
target_ent->noise_index = self->noise_index;
|
|
}
|
|
#ifdef LOOP_SOUND_ATTENUATION
|
|
if (self->attenuation)
|
|
{
|
|
if (target_ent->s.attenuation == target_ent->attenuation)
|
|
target_ent->s.attenuation = target_ent->attenuation = self->attenuation;
|
|
else
|
|
target_ent->attenuation = self->attenuation;
|
|
}
|
|
#endif
|
|
if (self->spawnflags)
|
|
{
|
|
target_ent->spawnflags = self->spawnflags;
|
|
// special cases:
|
|
if ( !Q_stricmp(target_ent->classname, "model_train") )
|
|
{
|
|
if (target_ent->spawnflags & 32)
|
|
{
|
|
target_ent->spawnflags &= ~32;
|
|
target_ent->spawnflags |= 8;
|
|
}
|
|
if (target_ent->spawnflags & 64)
|
|
{
|
|
target_ent->spawnflags &= ~64;
|
|
target_ent->spawnflags |= 16;
|
|
}
|
|
}
|
|
}
|
|
// Knightmare- set usermodel and skinnum only for model_spawn/train/turret
|
|
if ( !Q_stricmp(target_ent->classname, "model_spawn") || !Q_stricmp(target_ent->classname, "model_train") || !Q_stricmp(target_ent->classname, "model_turret") )
|
|
{
|
|
char modelname[256]; // Knightmare added
|
|
|
|
if ( self->usermodel && (strlen(self->usermodel) > 0) )
|
|
{
|
|
if (strstr(self->usermodel,".sp2")) {
|
|
// check for "sprites/" already in path
|
|
if ( !strncmp(self->usermodel, "sprites/", 8) )
|
|
Com_sprintf (modelname, sizeof(modelname), "%s", self->usermodel);
|
|
else
|
|
Com_sprintf (modelname, sizeof(modelname), "sprites/%s", self->usermodel);
|
|
}
|
|
else {
|
|
// check for "models/" already in path
|
|
if ( !strncmp(self->usermodel, "models/", 7) )
|
|
Com_sprintf (modelname, sizeof(modelname), "%s", self->usermodel);
|
|
else
|
|
Com_sprintf (modelname, sizeof(modelname), "models/%s", self->usermodel);
|
|
}
|
|
target_ent->s.modelindex = gi.modelindex (modelname);
|
|
}
|
|
// Set skinnum, -1 = 0
|
|
if (self->skinnum != 0) {
|
|
target_ent->skinnum = self->skinnum;
|
|
target_ent->skinnum = max(0, target_ent->skinnum);
|
|
target_ent->s.skinnum = target_ent->skinnum;
|
|
}
|
|
// Don't bother checking if skinnum is non-zero, to allow setting to 0
|
|
// target_ent->s.skinnum = self->s.skinnum;
|
|
}
|
|
// Knightmare- set solidstate, style, effects, renderfx, startframe, and framenumbers for model_spawn/train
|
|
if ( !Q_stricmp(target_ent->classname, "model_spawn") || !Q_stricmp(target_ent->classname, "model_train") )
|
|
{
|
|
int effects = 0;
|
|
|
|
if (self->solidstate > 0)
|
|
{
|
|
switch (self->solidstate)
|
|
{
|
|
case 1 : target_ent->solid = SOLID_NOT; target_ent->movetype = MOVETYPE_NONE; break;
|
|
case 2 : target_ent->solid = SOLID_BBOX; target_ent->movetype = MOVETYPE_TOSS; break;
|
|
case 3 : target_ent->solid = SOLID_BBOX; target_ent->movetype = MOVETYPE_NONE; break;
|
|
case 4 : target_ent->solid = SOLID_NOT; target_ent->movetype = MOVETYPE_TOSS; break;
|
|
default: target_ent->solid = SOLID_NOT; target_ent->movetype = MOVETYPE_NONE; break;
|
|
}
|
|
if ( (target_ent->solid != SOLID_NOT) && (target_ent->health > 0) ) {
|
|
target_ent->die = model_die;
|
|
target_ent->takedamage = DAMAGE_YES;
|
|
}
|
|
else {
|
|
target_ent->die = NULL;
|
|
target_ent->takedamage = DAMAGE_NO;
|
|
}
|
|
}
|
|
|
|
if (self->style > 0)
|
|
{
|
|
switch (self->style)
|
|
{
|
|
case 1 : effects |= EF_ANIM01; break;
|
|
case 2 : effects |= EF_ANIM23; break;
|
|
case 3 : effects |= EF_ANIM_ALL; break;
|
|
case 4 : effects |= EF_ANIM_ALLFAST; break;
|
|
}
|
|
}
|
|
// Set effects, -1 = 0
|
|
if (self->effects != 0) {
|
|
target_ent->effects = self->effects;
|
|
target_ent->effects = max(0, target_ent->effects);
|
|
}
|
|
if ( (self->style > 0) || (self->effects != 0) ) {
|
|
target_ent->s.effects = (effects | target_ent->effects);
|
|
}
|
|
// Set renderfx, -1 = 0
|
|
if (self->renderfx != 0) {
|
|
target_ent->renderfx = self->renderfx;
|
|
target_ent->renderfx = max(0, target_ent->renderfx);
|
|
target_ent->s.renderfx = target_ent->renderfx;
|
|
}
|
|
|
|
// Set startframe, -1 = 0
|
|
if (self->startframe != 0) {
|
|
target_ent->startframe = self->startframe;
|
|
target_ent->startframe = max(0, target_ent->startframe);
|
|
}
|
|
// Set framenumbers, -1 = 0
|
|
if (self->framenumbers != 0) {
|
|
target_ent->framenumbers = self->framenumbers;
|
|
target_ent->framenumbers = max(1, target_ent->framenumbers);
|
|
}
|
|
if ( (self->startframe != 0) || (self->framenumbers != 0) ) {
|
|
// Change framenumbers to last frame to play
|
|
target_ent->framenumbers += target_ent->startframe;
|
|
target_ent->s.frame = target_ent->startframe;
|
|
}
|
|
}
|
|
// end Knightmare
|
|
|
|
gi.linkentity(target_ent);
|
|
target_ent = G_Find(target_ent,FOFS(targetname),target);
|
|
}
|
|
gi.TagFree(buffer);
|
|
if (newteams)
|
|
G_FindTeams();
|
|
}
|
|
|
|
void SP_target_change (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_CHANGE;
|
|
|
|
if (!self->targetname)
|
|
gi.dprintf("target_change without a targetname at %s\n", vtos(self->s.origin));
|
|
if (!self->target)
|
|
gi.dprintf("target_change without a target at %s\n", vtos(self->s.origin));
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->use = use_target_change;
|
|
if (st.noise)
|
|
self->noise_index = gi.soundindex(st.noise);
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/*====================================================================================
|
|
TARGET_MOVEWITH - sets an entity to "movewith" it's pathtarget (or remove
|
|
that setting if DETACH (=1) is set)
|
|
======================================================================================*/
|
|
|
|
void movewith_detach (edict_t *child)
|
|
{
|
|
edict_t *e;
|
|
edict_t *parent = NULL;
|
|
int i;
|
|
|
|
for (i=1; i<globals.num_edicts && !parent; i++) {
|
|
e = g_edicts + i;
|
|
if (e->movewith_next == child) parent = e;
|
|
}
|
|
if (parent) parent->movewith_next = child->movewith_next;
|
|
|
|
child->movewith_next = NULL;
|
|
child->movewith = NULL;
|
|
|
|
// Knightmare added
|
|
child->movewith_ent = NULL;
|
|
VectorClear (child->movewith_offset);
|
|
VectorClear (child->parent_attach_angles);
|
|
VectorClear (child->child_attach_angles);
|
|
VectorCopy (child->s.origin, child->s.old_origin);
|
|
// child->s.event = EV_OTHER_TELEPORT;
|
|
// end Knightmare
|
|
|
|
if (child->org_movetype)
|
|
child->movetype = child->org_movetype;
|
|
// if monster, give 'em a small vertical boost
|
|
if (child->svflags & SVF_MONSTER) {
|
|
child->s.origin[2] += 2;
|
|
// VectorClear (child->velocity); // Knightmare added
|
|
}
|
|
gi.linkentity (child);
|
|
}
|
|
|
|
void use_target_movewith (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *target;
|
|
|
|
if (!self->target)
|
|
return;
|
|
target = G_Find(NULL, FOFS(targetname), self->target);
|
|
|
|
if (self->spawnflags & 1)
|
|
{
|
|
// Detach
|
|
while (target)
|
|
{
|
|
if (target->movewith_ent)
|
|
movewith_detach (target);
|
|
target = G_Find(target, FOFS(targetname), self->target);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Attach
|
|
edict_t *parent;
|
|
edict_t *e;
|
|
edict_t *previous;
|
|
|
|
parent = G_Find(NULL, FOFS(targetname), self->pathtarget);
|
|
if (!parent || !parent->inuse)
|
|
return;
|
|
while (target)
|
|
{
|
|
if (!target->movewith_ent || (target->movewith_ent != parent) )
|
|
{
|
|
if (target->movewith_ent)
|
|
movewith_detach(target);
|
|
|
|
target->movewith_ent = parent;
|
|
VectorCopy (parent->s.angles, target->parent_attach_angles);
|
|
VectorCopy (target->s.angles, target->child_attach_angles);
|
|
if (target->org_movetype < 0)
|
|
target->org_movetype = target->movetype;
|
|
if (target->movetype != MOVETYPE_NONE)
|
|
target->movetype = MOVETYPE_PUSH;
|
|
VectorCopy (target->mins, target->org_mins);
|
|
VectorCopy (target->maxs, target->org_maxs);
|
|
VectorSubtract (target->s.origin, parent->s.origin, target->movewith_offset);
|
|
e = parent->movewith_next;
|
|
previous = parent;
|
|
while (e)
|
|
{
|
|
previous = e;
|
|
e = previous->movewith_next;
|
|
}
|
|
previous->movewith_next = target;
|
|
gi.linkentity(target);
|
|
}
|
|
target = G_Find(target, FOFS(targetname), self->target);
|
|
}
|
|
}
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
void SP_target_movewith (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_MOVEWITH;
|
|
|
|
if (!self->target)
|
|
{
|
|
gi.dprintf("target_movewith with no target\n");
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if (!(self->spawnflags & 1) && !self->pathtarget)
|
|
{
|
|
gi.dprintf("target_movewith w/o DETACH and no pathtarget\n");
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->svflags |= SVF_NOCLIENT;
|
|
self->use = use_target_movewith;
|
|
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/*QUAKED target_command (1 0 0) (-8 -8 -8) (8 8 8)
|
|
Sends a command to the console which gets executed imediately,
|
|
unless it requires a restart of the server.
|
|
|
|
message - command to send
|
|
*/
|
|
void target_command_use (edict_t *self, edict_t *activator, edict_t *other)
|
|
{
|
|
gi.WriteByte (11);
|
|
gi.WriteString (self->message);
|
|
gi.unicast (self, true);
|
|
}
|
|
|
|
void SP_target_command (edict_t *self)
|
|
{
|
|
if (!self->message)
|
|
{
|
|
gi.dprintf("target_command with no command, target name is %s at %s", self->targetname, vtos(self->s.origin));
|
|
G_FreeEdict (self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_COMMAND;
|
|
|
|
self->use = target_command_use;
|
|
self->svflags = SVF_NOCLIENT;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/*====================================================================================
|
|
TARGET_SET_EFFECT - sets s.effects and/or s.renderfx for targeted entity
|
|
Style
|
|
0 = Copy
|
|
1 = NOT
|
|
2 = XOR (toggle)
|
|
======================================================================================*/
|
|
|
|
void use_target_set_effect (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *target;
|
|
|
|
target = G_Find(NULL,FOFS(targetname),self->target);
|
|
while (target)
|
|
{
|
|
if (self->style == 1)
|
|
{
|
|
target->s.effects &= ~self->effects;
|
|
target->s.renderfx &= ~self->renderfx;
|
|
}
|
|
else if (self->style == 2)
|
|
{
|
|
target->s.effects ^= self->effects;
|
|
target->s.renderfx ^= self->renderfx;
|
|
}
|
|
else
|
|
{
|
|
target->s.effects = self->effects;
|
|
target->s.renderfx = self->renderfx;
|
|
}
|
|
#ifdef KMQUAKE2_ENGINE_MOD //Knightmare added
|
|
if ((self->alpha >= 0.0) && (self->alpha <= 1.0))
|
|
target->s.alpha = self->alpha;
|
|
#endif
|
|
gi.linkentity(target);
|
|
target = G_Find(target,FOFS(targetname),self->target);
|
|
}
|
|
}
|
|
|
|
void SP_target_set_effect (edict_t *self)
|
|
{
|
|
if (!self->target)
|
|
{
|
|
gi.dprintf("target_set_effect w/o a target at %s\n", vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_SET_EFFECT;
|
|
|
|
self->use = use_target_set_effect;
|
|
self->svflags = SVF_NOCLIENT;
|
|
gi.linkentity (self);
|
|
}
|
|
|
|
/*=============================================================================
|
|
TARGET_SKY - uses the "sky" string as parameter to "sky" console command.
|
|
Best used in areas where player cannot see the sky, of course.
|
|
|
|
NOTE: Tried using a START_ON spawnflag, but it ends up being meaningless.
|
|
If we don't delay use_target_sky a bit at level start, we get
|
|
"SZ_GetSpace: overflow without allowoverflow set", and if we use
|
|
a sufficient delay to get around the error then the original sky
|
|
is visible before the switch.
|
|
=============================================================================*/
|
|
void use_target_sky (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
gi.configstring(CS_SKY,self->pathtarget);
|
|
stuffcmd(&g_edicts[1],va("sky %s\n",self->pathtarget));
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
void SP_target_sky (edict_t *self)
|
|
{
|
|
size_t pathSize;
|
|
|
|
if (!st.sky || !*st.sky)
|
|
{
|
|
gi.dprintf("Target_sky with no sky string at %s\n",vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_SKY;
|
|
|
|
pathSize = strlen(st.sky)+1;
|
|
self->pathtarget = gi.TagMalloc(pathSize, TAG_LEVEL);
|
|
// strncpy(self->pathtarget, st.sky);
|
|
Q_strncpyz (self->pathtarget, pathSize, st.sky);
|
|
self->use = use_target_sky;
|
|
}
|
|
|
|
/*=============================================================================
|
|
TARGET_FADE fades the screen to a color
|
|
color = R,G,B components of fade color, 0-1
|
|
alpha = opacity of fade. 0=no effect, 1=solid color
|
|
fadein = time in seconds from trigger until full alpha
|
|
fadeout = time in seconds after fadein+holdtime from full alpha to clear screen
|
|
holdtime = time to hold the effect at full alpha value. -1 = permanent
|
|
=============================================================================*/
|
|
void use_target_fade (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
if (!activator->client)
|
|
return;
|
|
|
|
activator->client->fadestart= level.framenum;
|
|
activator->client->fadein = level.framenum + self->fadein*10;
|
|
activator->client->fadehold = activator->client->fadein + self->holdtime*10;
|
|
activator->client->fadeout = activator->client->fadehold + self->fadeout*10;
|
|
activator->client->fadecolor[0] = self->color[0];
|
|
activator->client->fadecolor[1] = self->color[1];
|
|
activator->client->fadecolor[2] = self->color[2];
|
|
activator->client->fadealpha = self->alpha;
|
|
|
|
self->count--;
|
|
if (!self->count) {
|
|
self->think = G_FreeEdict;
|
|
self->nextthink = level.time + 1;
|
|
}
|
|
}
|
|
|
|
void SP_target_fade (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_FADE;
|
|
|
|
self->use = use_target_fade;
|
|
if (self->fadein < 0)
|
|
self->fadein = 0;
|
|
if (self->holdtime < 0)
|
|
{
|
|
self->count = 1;
|
|
self->holdtime = 10000;
|
|
}
|
|
if (self->fadeout < 0)
|
|
self->fadeout = 0;
|
|
}
|
|
|
|
/*=============================================================================
|
|
TARGET_CLONE
|
|
Spawns a new entity, using model and other parameters of source entity.
|
|
|
|
source - targetname of source entity for model
|
|
newtargetname - targetname to assign to spawned entity
|
|
target - target to assign to spawned entity
|
|
count - number of spawner uses before being freed
|
|
Spawnflags
|
|
1 = START_ON
|
|
=============================================================================*/
|
|
void button_touch (edict_t *self, edict_t *other, cplane_t *plane, csurface_t *surf);
|
|
void Think_CalcMoveSpeed (edict_t *self);
|
|
void Think_SpawnDoorTrigger (edict_t *ent);
|
|
void func_train_find (edict_t *self);
|
|
void clone (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
edict_t *parent;
|
|
edict_t *child;
|
|
int newteams = 0;
|
|
size_t classSize;
|
|
|
|
parent = G_Find(NULL,FOFS(targetname),self->source);
|
|
if (!parent)
|
|
return;
|
|
|
|
child = G_Spawn();
|
|
classSize = strlen(parent->classname)+1;
|
|
child->classname = gi.TagMalloc(classSize, TAG_LEVEL);
|
|
// strncpy(child->classname, parent->classname);
|
|
Q_strncpyz (child->classname, classSize, parent->classname);
|
|
|
|
child->s.modelindex = parent->s.modelindex;
|
|
VectorCopy (self->s.origin, child->s.origin);
|
|
child->svflags = parent->svflags;
|
|
VectorCopy (parent->mins, child->mins);
|
|
VectorCopy (parent->maxs, child->maxs);
|
|
VectorCopy (parent->size, child->size);
|
|
|
|
if (self->newtargetname && strlen(self->newtargetname))
|
|
child->targetname = G_CopyString(self->newtargetname);
|
|
if (self->team && strlen(self->team))
|
|
{
|
|
child->team = G_CopyString(self->team);
|
|
newteams++;
|
|
}
|
|
if (self->target && strlen(self->target))
|
|
child->target = G_CopyString(self->target);
|
|
|
|
if (parent->deathtarget && strlen(parent->deathtarget))
|
|
child->deathtarget = G_CopyString(parent->deathtarget);
|
|
if (parent->destroytarget && strlen(parent->destroytarget))
|
|
child->destroytarget = G_CopyString(parent->destroytarget);
|
|
if (parent->killtarget && strlen(parent->killtarget))
|
|
child->killtarget = G_CopyString(parent->killtarget);
|
|
|
|
child->solid = parent->solid;
|
|
child->clipmask = parent->clipmask;
|
|
child->movetype = parent->movetype;
|
|
child->mass = parent->mass;
|
|
child->health = parent->health;
|
|
child->max_health = parent->max_health;
|
|
child->takedamage = parent->takedamage;
|
|
child->dmg = parent->dmg;
|
|
child->sounds = parent->sounds;
|
|
child->speed = parent->speed;
|
|
child->accel = parent->accel;
|
|
child->decel = parent->decel;
|
|
child->gib_type = parent->gib_type;
|
|
child->noise_index = parent->noise_index;
|
|
child->noise_index2 = parent->noise_index2;
|
|
child->wait = parent->wait;
|
|
child->delay = parent->delay;
|
|
child->random = parent->random;
|
|
child->style = parent->style;
|
|
child->flags = parent->flags;
|
|
child->blocked = parent->blocked;
|
|
child->touch = parent->touch;
|
|
child->use = parent->use;
|
|
child->pain = parent->pain;
|
|
child->die = parent->die;
|
|
child->s.effects = parent->s.effects;
|
|
#ifdef KMQUAKE2_ENGINE_MOD // Knightmare added
|
|
child->s.alpha = parent->s.alpha;
|
|
#endif
|
|
child->s.skinnum = parent->s.skinnum;
|
|
child->item = parent->item;
|
|
child->moveinfo.sound_start = parent->moveinfo.sound_start;
|
|
child->moveinfo.sound_middle = parent->moveinfo.sound_middle;
|
|
child->moveinfo.sound_end = parent->moveinfo.sound_end;
|
|
VectorCopy(parent->movedir,child->movedir);
|
|
VectorCopy(self->s.angles, child->s.angles);
|
|
if (VectorLength(child->s.angles) != 0)
|
|
{
|
|
if (child->s.angles[YAW] == 90 || child->s.angles[YAW] == 270)
|
|
{
|
|
// We're correct for these angles, not even gonna bother with others
|
|
vec_t temp;
|
|
temp = child->size[0];
|
|
child->size[0] = child->size[1];
|
|
child->size[1] = temp;
|
|
temp = child->mins[0];
|
|
if (child->s.angles[YAW] == 90)
|
|
{
|
|
child->mins[0] = -child->maxs[1];
|
|
child->maxs[1] = child->maxs[0];
|
|
child->maxs[0] = -child->mins[1];
|
|
child->mins[1] = temp;
|
|
}
|
|
else
|
|
{
|
|
child->mins[0] = child->mins[1];
|
|
child->mins[1] = -child->maxs[0];
|
|
child->maxs[0] = child->maxs[1];
|
|
child->maxs[1] = -temp;
|
|
}
|
|
}
|
|
vectoangles(child->movedir,child->movedir);
|
|
child->movedir[PITCH] += child->s.angles[PITCH];
|
|
child->movedir[YAW] += child->s.angles[YAW];
|
|
child->movedir[ROLL] += child->s.angles[ROLL];
|
|
if (child->movedir[PITCH] > 360) child->movedir[PITCH] -= 360;
|
|
if (child->movedir[YAW] > 360) child->movedir[YAW] -= 360;
|
|
if (child->movedir[ROLL] > 360) child->movedir[ROLL] -= 360;
|
|
AngleVectors(child->movedir,child->movedir,NULL,NULL);
|
|
}
|
|
VectorAdd(child->s.origin,child->mins,child->absmin);
|
|
VectorAdd(child->s.origin,child->maxs,child->absmax);
|
|
|
|
child->spawnflags = parent->spawnflags;
|
|
// classname-specific stuff
|
|
if (!Q_stricmp(child->classname, "func_button"))
|
|
{
|
|
VectorCopy(child->s.origin,child->pos1);
|
|
child->moveinfo.distance = parent->moveinfo.distance;
|
|
VectorMA(child->pos1, child->moveinfo.distance, child->movedir, child->pos2);
|
|
child->moveinfo.state = 1;
|
|
child->moveinfo.speed = child->speed;
|
|
child->moveinfo.accel = child->accel;
|
|
child->moveinfo.decel = child->decel;
|
|
child->moveinfo.wait = child->wait;
|
|
VectorCopy(child->pos1, child->moveinfo.start_origin);
|
|
VectorCopy(child->s.angles, child->moveinfo.start_angles);
|
|
VectorCopy(child->pos2, child->moveinfo.end_origin);
|
|
VectorCopy(child->s.angles, child->moveinfo.end_angles);
|
|
if (!child->targetname)
|
|
child->touch = button_touch;
|
|
}
|
|
else if (!Q_stricmp(child->classname, "func_door"))
|
|
{
|
|
VectorCopy(child->s.origin,child->pos1);
|
|
child->moveinfo.distance = parent->moveinfo.distance;
|
|
VectorMA(child->pos1, child->moveinfo.distance, child->movedir, child->pos2);
|
|
child->moveinfo.state = 1;
|
|
child->moveinfo.speed = child->speed;
|
|
child->moveinfo.accel = child->accel;
|
|
child->moveinfo.decel = child->decel;
|
|
child->moveinfo.wait = child->wait;
|
|
VectorCopy(child->pos1, child->moveinfo.start_origin);
|
|
VectorCopy(child->s.angles, child->moveinfo.start_angles);
|
|
VectorCopy(child->pos2, child->moveinfo.end_origin);
|
|
VectorCopy(child->s.angles, child->moveinfo.end_angles);
|
|
if (child->health || child->targetname)
|
|
child->think = Think_CalcMoveSpeed;
|
|
else
|
|
child->think = Think_SpawnDoorTrigger;
|
|
child->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else if (!Q_stricmp(child->classname, "func_door_rotating"))
|
|
{
|
|
VectorClear(child->s.angles);
|
|
VectorCopy(parent->s.angles,child->s.angles);
|
|
VectorCopy(parent->pos1, child->pos1);
|
|
VectorCopy(parent->pos2, child->pos2);
|
|
child->moveinfo.distance = parent->moveinfo.distance;
|
|
child->moveinfo.state = 1;
|
|
child->moveinfo.speed = child->speed;
|
|
child->moveinfo.accel = child->accel;
|
|
child->moveinfo.decel = child->decel;
|
|
child->moveinfo.wait = child->wait;
|
|
VectorCopy(child->s.origin, child->moveinfo.start_origin);
|
|
VectorCopy(child->pos1, child->moveinfo.start_angles);
|
|
VectorCopy(child->s.origin, child->moveinfo.end_origin);
|
|
VectorCopy(child->pos2, child->moveinfo.end_angles);
|
|
if (child->health || child->targetname)
|
|
child->think = Think_CalcMoveSpeed;
|
|
else
|
|
child->think = Think_SpawnDoorTrigger;
|
|
child->nextthink = level.time + FRAMETIME;
|
|
}
|
|
else if (!Q_stricmp(child->classname, "func_rotating"))
|
|
{
|
|
VectorClear(child->s.angles);
|
|
if (child->spawnflags & 1)
|
|
child->use (child, NULL, NULL);
|
|
}
|
|
else if (!Q_stricmp(child->classname, "func_train"))
|
|
{
|
|
VectorClear(self->s.angles);
|
|
child->smooth_movement = parent->smooth_movement;
|
|
child->pitch_speed = parent->pitch_speed;
|
|
child->yaw_speed = parent->yaw_speed;
|
|
child->roll_speed = parent->roll_speed;
|
|
child->moveinfo.speed = child->speed;
|
|
child->moveinfo.accel = child->moveinfo.decel = child->moveinfo.speed;
|
|
child->think = func_train_find;
|
|
child->nextthink = level.time + FRAMETIME;
|
|
if (child->moveinfo.sound_middle || parent->noise_index)
|
|
{
|
|
edict_t *speaker;
|
|
if (child->moveinfo.sound_middle)
|
|
child->noise_index = child->moveinfo.sound_middle;
|
|
else
|
|
child->noise_index = parent->noise_index;
|
|
child->moveinfo.sound_middle = 0;
|
|
speaker = G_Spawn();
|
|
speaker->classname = "moving_speaker";
|
|
speaker->s.sound = 0;
|
|
speaker->volume = 1;
|
|
speaker->attenuation = child->attenuation; // was 1
|
|
speaker->owner = child;
|
|
speaker->think = Moving_Speaker_Think;
|
|
speaker->nextthink = level.time + 2*FRAMETIME;
|
|
speaker->spawnflags = 7; // owner must be moving to play
|
|
child->speaker = speaker;
|
|
if (VectorLength(child->s.origin))
|
|
VectorCopy(child->s.origin,speaker->s.origin);
|
|
else {
|
|
VectorAdd(child->absmin,child->absmax,speaker->s.origin);
|
|
VectorScale(speaker->s.origin,0.5,speaker->s.origin);
|
|
}
|
|
VectorSubtract(speaker->s.origin,child->s.origin,speaker->offset);
|
|
}
|
|
}
|
|
|
|
#ifdef KMQUAKE2_ENGINE_MOD //Knightmare added
|
|
if ((self->alpha >= 0.0) && (self->alpha <= 1.0))
|
|
child->s.alpha = self->alpha;
|
|
#endif
|
|
|
|
gi.unlinkentity(child);
|
|
//Knightmare- only killbox if spawned entity is solid
|
|
if (child->solid == SOLID_BSP || child->solid == SOLID_BBOX)
|
|
KillBox(child);
|
|
gi.linkentity(child);
|
|
if (self->s.angles[YAW] != 0)
|
|
{
|
|
VectorAdd(child->s.origin,child->mins,child->absmin);
|
|
VectorAdd(child->s.origin,child->maxs,child->absmax);
|
|
}
|
|
self->count--;
|
|
if (!self->count)
|
|
G_FreeEdict(self);
|
|
if (newteams)
|
|
G_FindTeams();
|
|
}
|
|
|
|
void target_clone_starton (edict_t *self)
|
|
{
|
|
self->use(self,NULL,NULL);
|
|
}
|
|
|
|
void SP_target_clone (edict_t *self)
|
|
{
|
|
if (!self->targetname && !(self->spawnflags & 1))
|
|
{
|
|
gi.dprintf("%s without a targetname and without start_on at %s\n", self->classname, vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
if (!self->source)
|
|
{
|
|
gi.dprintf("%s with no source at %s\n",
|
|
self->classname,vtos(self->s.origin));
|
|
G_FreeEdict(self);
|
|
return;
|
|
}
|
|
|
|
self->class_id = ENTITY_TARGET_CLONE;
|
|
|
|
self->use = clone;
|
|
if (self->spawnflags & 1)
|
|
{
|
|
self->think = target_clone_starton;
|
|
self->nextthink = level.time + 2;
|
|
}
|
|
}
|
|
|
|
void use_target_skill (edict_t *self, edict_t *other, edict_t *activator)
|
|
{
|
|
level.next_skill = self->style + 1;
|
|
self->count--;
|
|
if (!self->count)
|
|
G_FreeEdict(self);
|
|
}
|
|
|
|
void SP_target_skill (edict_t *self)
|
|
{
|
|
self->class_id = ENTITY_TARGET_SKILL;
|
|
|
|
self->use = use_target_skill;
|
|
} |