Initial commit

This commit is contained in:
Mike Rubits
2023-08-07 14:47:28 -05:00
commit 60f29560f3
438 changed files with 317574 additions and 0 deletions

View File

@@ -0,0 +1,146 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_combat.c
#include "../g_local.h"
void M_SetEffects(edict_t *self);
/*
ROGUE
clean up heal targets for medic
*/
void cleanupHealTarget(edict_t *ent)
{
ent->monsterinfo.healer = nullptr;
ent->takedamage = true;
ent->monsterinfo.aiflags &= ~AI_RESURRECTING;
M_SetEffects(ent);
}
// **********************
// ROGUE
/*
============
T_RadiusNukeDamage
Like T_RadiusDamage, but ignores walls (skips CanDamage check, among others)
// up to KILLZONE radius, do 10,000 points
// after that, do damage linearly out to KILLZONE2 radius
============
*/
void T_RadiusNukeDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, mod_t mod)
{
float points;
edict_t *ent = nullptr;
vec3_t v;
vec3_t dir;
float len;
float killzone, killzone2;
trace_t tr;
float dist;
killzone = radius;
killzone2 = radius * 2.0f;
while ((ent = findradius(ent, inflictor->s.origin, killzone2)) != nullptr)
{
// ignore nobody
if (ent == ignore)
continue;
if (!ent->takedamage)
continue;
if (!ent->inuse)
continue;
if (!(ent->client || (ent->svflags & SVF_MONSTER) || (ent->flags & FL_DAMAGEABLE)))
continue;
v = ent->mins + ent->maxs;
v = ent->s.origin + (v * 0.5f);
v = inflictor->s.origin - v;
len = v.length();
if (len <= killzone)
{
if (ent->client)
ent->flags |= FL_NOGIB;
points = 10000;
}
else if (len <= killzone2)
points = (damage / killzone) * (killzone2 - len);
else
points = 0;
if (points > 0)
{
if (ent->client)
ent->client->nuke_time = level.time + 2_sec;
dir = ent->s.origin - inflictor->s.origin;
T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int) points, (int) points, DAMAGE_RADIUS, mod);
}
}
ent = g_edicts + 1; // skip the worldspawn
// cycle through players
while (ent)
{
if ((ent->client) && (ent->client->nuke_time != level.time + 2_sec) && (ent->inuse))
{
tr = gi.traceline(inflictor->s.origin, ent->s.origin, inflictor, MASK_SOLID);
if (tr.fraction == 1.0f)
ent->client->nuke_time = level.time + 2_sec;
else
{
dist = realrange(ent, inflictor);
if (dist < 2048)
ent->client->nuke_time = max(ent->client->nuke_time, level.time + 1.5_sec);
else
ent->client->nuke_time = max(ent->client->nuke_time, level.time + 1_sec);
}
ent++;
}
else
ent = nullptr;
}
}
/*
============
T_RadiusClassDamage
Like T_RadiusDamage, but ignores anything with classname=ignoreClass
============
*/
void T_RadiusClassDamage(edict_t *inflictor, edict_t *attacker, float damage, char *ignoreClass, float radius, mod_t mod)
{
float points;
edict_t *ent = nullptr;
vec3_t v;
vec3_t dir;
while ((ent = findradius(ent, inflictor->s.origin, radius)) != nullptr)
{
if (ent->classname && !strcmp(ent->classname, ignoreClass))
continue;
if (!ent->takedamage)
continue;
v = ent->mins + ent->maxs;
v = ent->s.origin + (v * 0.5f);
v = inflictor->s.origin - v;
points = damage - 0.5f * v.length();
if (ent == attacker)
points = points * 0.5f;
if (points > 0)
{
if (CanDamage(ent, inflictor))
{
dir = ent->s.origin - inflictor->s.origin;
T_Damage(ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int) points, (int) points, DAMAGE_RADIUS, mod);
}
}
}
}
// ROGUE
// ********************

View File

@@ -0,0 +1,430 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
//====
// PGM
constexpr spawnflags_t SPAWNFLAGS_PLAT2_TOGGLE = 2_spawnflag;
constexpr spawnflags_t SPAWNFLAGS_PLAT2_TOP = 4_spawnflag;
constexpr spawnflags_t SPAWNFLAGS_PLAT2_START_ACTIVE = 8_spawnflag;
constexpr spawnflags_t SPAWNFLAGS_PLAT2_BOX_LIFT = 32_spawnflag;
// PGM
//====
void plat2_go_down(edict_t *ent);
void plat2_go_up(edict_t *ent);
void plat2_spawn_danger_area(edict_t *ent)
{
vec3_t mins, maxs;
mins = ent->mins;
maxs = ent->maxs;
maxs[2] = ent->mins[2] + 64;
SpawnBadArea(mins, maxs, 0_ms, ent);
}
void plat2_kill_danger_area(edict_t *ent)
{
edict_t *t;
t = nullptr;
while ((t = G_FindByString<&edict_t::classname>(t, "bad_area")))
{
if (t->owner == ent)
G_FreeEdict(t);
}
}
MOVEINFO_ENDFUNC(plat2_hit_top) (edict_t *ent) -> void
{
if (!(ent->flags & FL_TEAMSLAVE))
{
if (ent->moveinfo.sound_end)
gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
}
ent->s.sound = 0;
ent->moveinfo.state = STATE_TOP;
if (ent->plat2flags & PLAT2_CALLED)
{
ent->plat2flags = PLAT2_WAITING;
if (!ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOGGLE))
{
ent->think = plat2_go_down;
ent->nextthink = level.time + 5_sec;
}
if (deathmatch->integer)
ent->last_move_time = level.time - 1_sec;
else
ent->last_move_time = level.time - 2_sec;
}
else if (!(ent->spawnflags & SPAWNFLAGS_PLAT2_TOP) && !ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOGGLE))
{
ent->plat2flags = PLAT2_NONE;
ent->think = plat2_go_down;
ent->nextthink = level.time + 2_sec;
ent->last_move_time = level.time;
}
else
{
ent->plat2flags = PLAT2_NONE;
ent->last_move_time = level.time;
}
G_UseTargets(ent, ent);
}
MOVEINFO_ENDFUNC(plat2_hit_bottom) (edict_t *ent) -> void
{
if (!(ent->flags & FL_TEAMSLAVE))
{
if (ent->moveinfo.sound_end)
gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_end, 1, ATTN_STATIC, 0);
}
ent->s.sound = 0;
ent->moveinfo.state = STATE_BOTTOM;
if (ent->plat2flags & PLAT2_CALLED)
{
ent->plat2flags = PLAT2_WAITING;
if (!(ent->spawnflags & SPAWNFLAGS_PLAT2_TOGGLE))
{
ent->think = plat2_go_up;
ent->nextthink = level.time + 5_sec;
}
if (deathmatch->integer)
ent->last_move_time = level.time - 1_sec;
else
ent->last_move_time = level.time - 2_sec;
}
else if (ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOP) && !ent->spawnflags.has(SPAWNFLAGS_PLAT2_TOGGLE))
{
ent->plat2flags = PLAT2_NONE;
ent->think = plat2_go_up;
ent->nextthink = level.time + 2_sec;
ent->last_move_time = level.time;
}
else
{
ent->plat2flags = PLAT2_NONE;
ent->last_move_time = level.time;
}
plat2_kill_danger_area(ent);
G_UseTargets(ent, ent);
}
THINK(plat2_go_down) (edict_t *ent) -> void
{
if (!(ent->flags & FL_TEAMSLAVE))
{
if (ent->moveinfo.sound_start)
gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
}
ent->s.sound = ent->moveinfo.sound_middle;
ent->moveinfo.state = STATE_DOWN;
ent->plat2flags |= PLAT2_MOVING;
Move_Calc(ent, ent->moveinfo.end_origin, plat2_hit_bottom);
}
THINK(plat2_go_up) (edict_t *ent) -> void
{
if (!(ent->flags & FL_TEAMSLAVE))
{
if (ent->moveinfo.sound_start)
gi.sound(ent, CHAN_NO_PHS_ADD | CHAN_VOICE, ent->moveinfo.sound_start, 1, ATTN_STATIC, 0);
}
ent->s.sound = ent->moveinfo.sound_middle;
ent->moveinfo.state = STATE_UP;
ent->plat2flags |= PLAT2_MOVING;
plat2_spawn_danger_area(ent);
Move_Calc(ent, ent->moveinfo.start_origin, plat2_hit_top);
}
void plat2_operate(edict_t *ent, edict_t *other)
{
int otherState;
gtime_t pauseTime;
float platCenter;
edict_t *trigger;
trigger = ent;
ent = ent->enemy; // now point at the plat, not the trigger
if (ent->plat2flags & PLAT2_MOVING)
return;
if ((ent->last_move_time + 2_sec) > level.time)
return;
platCenter = (trigger->absmin[2] + trigger->absmax[2]) / 2;
if (ent->moveinfo.state == STATE_TOP)
{
otherState = STATE_TOP;
if (ent->spawnflags.has(SPAWNFLAGS_PLAT2_BOX_LIFT))
{
if (platCenter > other->s.origin[2])
otherState = STATE_BOTTOM;
}
else
{
if (trigger->absmax[2] > other->s.origin[2])
otherState = STATE_BOTTOM;
}
}
else
{
otherState = STATE_BOTTOM;
if (other->s.origin[2] > platCenter)
otherState = STATE_TOP;
}
ent->plat2flags = PLAT2_MOVING;
if (deathmatch->integer)
pauseTime = 300_ms;
else
pauseTime = 500_ms;
if (ent->moveinfo.state != otherState)
{
ent->plat2flags |= PLAT2_CALLED;
pauseTime = 100_ms;
}
ent->last_move_time = level.time;
if (ent->moveinfo.state == STATE_BOTTOM)
{
ent->think = plat2_go_up;
ent->nextthink = level.time + pauseTime;
}
else
{
ent->think = plat2_go_down;
ent->nextthink = level.time + pauseTime;
}
}
TOUCH(Touch_Plat_Center2) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
// this requires monsters to actively trigger plats, not just step on them.
// FIXME - commented out for E3
// if (!other->client)
// return;
if (other->health <= 0)
return;
// PMM - don't let non-monsters activate plat2s
if ((!(other->svflags & SVF_MONSTER)) && (!other->client))
return;
plat2_operate(ent, other);
}
MOVEINFO_BLOCKED(plat2_blocked) (edict_t *self, edict_t *other) -> void
{
if (!(other->svflags & SVF_MONSTER) && (!other->client))
{
// give it a chance to go away on it's own terms (like gibs)
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100000, 1, DAMAGE_NONE, MOD_CRUSH);
// if it's still there, nuke it
if (other && other->inuse && other->solid)
BecomeExplosion1(other);
return;
}
// gib dead things
if (other->health < 1)
{
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, 100, 1, DAMAGE_NONE, MOD_CRUSH);
}
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 1, DAMAGE_NONE, MOD_CRUSH);
// [Paril-KEX] killed, so don't change direction
if (!other->inuse || !other->solid)
return;
if (self->moveinfo.state == STATE_UP)
plat2_go_down(self);
else if (self->moveinfo.state == STATE_DOWN)
plat2_go_up(self);
}
USE(Use_Plat2) (edict_t *ent, edict_t *other, edict_t *activator) -> void
{
edict_t *trigger;
if (ent->moveinfo.state > STATE_BOTTOM)
return;
// [Paril-KEX] disabled this; causes confusing situations
//if ((ent->last_move_time + 2_sec) > level.time)
// return;
uint32_t i;
for (i = 1, trigger = g_edicts + 1; i < globals.num_edicts; i++, trigger++)
{
if (!trigger->inuse)
continue;
if (trigger->touch == Touch_Plat_Center2)
{
if (trigger->enemy == ent)
{
// Touch_Plat_Center2 (trigger, activator, nullptr, nullptr);
plat2_operate(trigger, activator);
return;
}
}
}
}
USE(plat2_activate) (edict_t *ent, edict_t *other, edict_t *activator) -> void
{
edict_t *trigger;
// if(ent->targetname)
// ent->targetname[0] = 0;
ent->use = Use_Plat2;
trigger = plat_spawn_inside_trigger(ent); // the "start moving" trigger
trigger->maxs[0] += 10;
trigger->maxs[1] += 10;
trigger->mins[0] -= 10;
trigger->mins[1] -= 10;
gi.linkentity(trigger);
trigger->touch = Touch_Plat_Center2; // Override trigger touch function
plat2_go_down(ent);
}
/*QUAKED func_plat2 (0 .5 .8) ? PLAT_LOW_TRIGGER PLAT2_TOGGLE PLAT2_TOP PLAT2_START_ACTIVE UNUSED BOX_LIFT
speed default 150
PLAT_LOW_TRIGGER - creates a short trigger field at the bottom
PLAT2_TOGGLE - plat will not return to default position.
PLAT2_TOP - plat's default position will the the top.
PLAT2_START_ACTIVE - plat will trigger it's targets each time it hits top
UNUSED
BOX_LIFT - this indicates that the lift is a box, rather than just a platform
Plats are always drawn in the extended position, so they will light correctly.
If the plat is the target of another trigger or button, it will start out disabled in the extended position until it is trigger, when it will lower and become a normal plat.
"speed" overrides default 200.
"accel" overrides default 500
"lip" no default
If the "height" key is set, that will determine the amount the plat moves, instead of being implicitly determoveinfoned by the model's height.
*/
void SP_func_plat2(edict_t *ent)
{
edict_t *trigger;
ent->s.angles = {};
ent->solid = SOLID_BSP;
ent->movetype = MOVETYPE_PUSH;
gi.setmodel(ent, ent->model);
ent->moveinfo.blocked = plat2_blocked;
if (!ent->speed)
ent->speed = 20;
else
ent->speed *= 0.1f;
if (!ent->accel)
ent->accel = 5;
else
ent->accel *= 0.1f;
if (!ent->decel)
ent->decel = 5;
else
ent->decel *= 0.1f;
if (deathmatch->integer)
{
ent->speed *= 2;
ent->accel *= 2;
ent->decel *= 2;
}
// PMM Added to kill things it's being blocked by
if (!ent->dmg)
ent->dmg = 2;
// if (!st.lip)
// st.lip = 8;
// pos1 is the top position, pos2 is the bottom
ent->pos1 = ent->s.origin;
ent->pos2 = ent->s.origin;
if (st.height)
ent->pos2[2] -= (st.height - st.lip);
else
ent->pos2[2] -= (ent->maxs[2] - ent->mins[2]) - st.lip;
ent->moveinfo.state = STATE_TOP;
if (ent->targetname && !(ent->spawnflags & SPAWNFLAGS_PLAT2_START_ACTIVE))
{
ent->use = plat2_activate;
}
else
{
ent->use = Use_Plat2;
trigger = plat_spawn_inside_trigger(ent); // the "start moving" trigger
// PGM - debugging??
trigger->maxs[0] += 10;
trigger->maxs[1] += 10;
trigger->mins[0] -= 10;
trigger->mins[1] -= 10;
gi.linkentity(trigger);
trigger->touch = Touch_Plat_Center2; // Override trigger touch function
if (!(ent->spawnflags & SPAWNFLAGS_PLAT2_TOP))
{
ent->s.origin = ent->pos2;
ent->moveinfo.state = STATE_BOTTOM;
}
}
gi.linkentity(ent);
ent->moveinfo.speed = ent->speed;
ent->moveinfo.accel = ent->accel;
ent->moveinfo.decel = ent->decel;
ent->moveinfo.wait = ent->wait;
ent->moveinfo.start_origin = ent->pos1;
ent->moveinfo.start_angles = ent->s.angles;
ent->moveinfo.end_origin = ent->pos2;
ent->moveinfo.end_angles = ent->s.angles;
G_SetMoveinfoSounds(ent, "plats/pt1_strt.wav", "plats/pt1_mid.wav", "plats/pt1_end.wav");
}

View File

@@ -0,0 +1,228 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
// ================
// PMM
bool Pickup_Nuke(edict_t *ent, edict_t *other)
{
int quantity;
quantity = other->client->pers.inventory[ent->item->id];
if (quantity >= 1)
return false;
if (coop->integer && !P_UseCoopInstancedItems() && (ent->item->flags & IF_STAY_COOP) && (quantity > 0))
return false;
other->client->pers.inventory[ent->item->id]++;
if (deathmatch->integer)
{
if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED))
SetRespawn(ent, gtime_t::from_sec(ent->item->quantity));
}
return true;
}
// ================
// PGM
void Use_IR(edict_t *ent, gitem_t *item)
{
ent->client->pers.inventory[item->id]--;
ent->client->ir_time = max(level.time, ent->client->ir_time) + 60_sec;
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ir_start.wav"), 1, ATTN_NORM, 0);
}
void Use_Double(edict_t *ent, gitem_t *item)
{
ent->client->pers.inventory[item->id]--;
ent->client->double_time = max(level.time, ent->client->double_time) + 30_sec;
gi.sound(ent, CHAN_ITEM, gi.soundindex("misc/ddamage1.wav"), 1, ATTN_NORM, 0);
}
void Use_Nuke(edict_t *ent, gitem_t *item)
{
vec3_t forward, right, start;
int speed;
ent->client->pers.inventory[item->id]--;
AngleVectors(ent->client->v_angle, forward, right, nullptr);
start = ent->s.origin;
speed = 100;
fire_nuke(ent, start, forward, speed);
}
void Use_Doppleganger(edict_t *ent, gitem_t *item)
{
vec3_t forward, right;
vec3_t createPt, spawnPt;
vec3_t ang;
ang[PITCH] = 0;
ang[YAW] = ent->client->v_angle[YAW];
ang[ROLL] = 0;
AngleVectors(ang, forward, right, nullptr);
createPt = ent->s.origin + (forward * 48);
if (!FindSpawnPoint(createPt, ent->mins, ent->maxs, spawnPt, 32))
return;
if (!CheckGroundSpawnPoint(spawnPt, ent->mins, ent->maxs, 64, -1))
return;
ent->client->pers.inventory[item->id]--;
SpawnGrow_Spawn(spawnPt, 24.f, 48.f);
fire_doppleganger(ent, spawnPt, forward);
}
bool Pickup_Doppleganger(edict_t *ent, edict_t *other)
{
int quantity;
if (!deathmatch->integer) // item is DM only
return false;
quantity = other->client->pers.inventory[ent->item->id];
if (quantity >= 1) // FIXME - apply max to dopplegangers
return false;
other->client->pers.inventory[ent->item->id]++;
if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED))
SetRespawn(ent, gtime_t::from_sec(ent->item->quantity));
return true;
}
bool Pickup_Sphere(edict_t *ent, edict_t *other)
{
int quantity;
if (other->client && other->client->owned_sphere)
{
// gi.LocClient_Print(other, PRINT_HIGH, "$g_only_one_sphere_customer");
return false;
}
quantity = other->client->pers.inventory[ent->item->id];
if ((skill->integer == 1 && quantity >= 2) || (skill->integer >= 2 && quantity >= 1))
return false;
if ((coop->integer) && !P_UseCoopInstancedItems() && (ent->item->flags & IF_STAY_COOP) && (quantity > 0))
return false;
other->client->pers.inventory[ent->item->id]++;
if (deathmatch->integer)
{
if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED))
SetRespawn(ent, gtime_t::from_sec(ent->item->quantity));
if (g_dm_instant_items->integer)
{
// PGM
if (ent->item->use)
ent->item->use(other, ent->item);
else
gi.Com_Print("Powerup has no use function!\n");
// PGM
}
}
return true;
}
void Use_Defender(edict_t *ent, gitem_t *item)
{
if (ent->client && ent->client->owned_sphere)
{
gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time");
return;
}
ent->client->pers.inventory[item->id]--;
Defender_Launch(ent);
}
void Use_Hunter(edict_t *ent, gitem_t *item)
{
if (ent->client && ent->client->owned_sphere)
{
gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time");
return;
}
ent->client->pers.inventory[item->id]--;
Hunter_Launch(ent);
}
void Use_Vengeance(edict_t *ent, gitem_t *item)
{
if (ent->client && ent->client->owned_sphere)
{
gi.LocClient_Print(ent, PRINT_HIGH, "$g_only_one_sphere_time");
return;
}
ent->client->pers.inventory[item->id]--;
Vengeance_Launch(ent);
}
// PGM
// ================
//=================
// Item_TriggeredSpawn - create the item marked for spawn creation
//=================
USE(Item_TriggeredSpawn) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
self->svflags &= ~SVF_NOCLIENT;
self->use = nullptr;
if (self->spawnflags.has(SPAWNFLAG_ITEM_TOSS_SPAWN))
{
self->movetype = MOVETYPE_TOSS;
vec3_t forward, right;
AngleVectors(self->s.angles, forward, right, nullptr);
self->s.origin = self->s.origin;
self->s.origin[2] += 16;
self->velocity = forward * 100;
self->velocity[2] = 300;
}
if (self->item->id != IT_KEY_POWER_CUBE && self->item->id != IT_KEY_EXPLOSIVE_CHARGES) // leave them be on key_power_cube..
self->spawnflags &= SPAWNFLAG_ITEM_NO_TOUCH;
droptofloor(self);
}
//=================
// SetTriggeredSpawn - set up an item to spawn in later.
//=================
void SetTriggeredSpawn(edict_t *ent)
{
// don't do anything on key_power_cubes.
if (ent->item->id == IT_KEY_POWER_CUBE || ent->item->id == IT_KEY_EXPLOSIVE_CHARGES)
return;
ent->think = nullptr;
ent->nextthink = 0_ms;
ent->use = Item_TriggeredSpawn;
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
//======================
// ROGUE
USE(misc_nuke_core_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
if (self->svflags & SVF_NOCLIENT)
self->svflags &= ~SVF_NOCLIENT;
else
self->svflags |= SVF_NOCLIENT;
}
/*QUAKED misc_nuke_core (1 0 0) (-16 -16 -16) (16 16 16)
toggles visible/not visible. starts visible.
*/
void SP_misc_nuke_core(edict_t *ent)
{
gi.setmodel(ent, "models/objects/core/tris.md2");
gi.linkentity(ent);
ent->use = misc_nuke_core_use;
}
// ROGUE
//======================

View File

@@ -0,0 +1,109 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
// ROGUE
void monster_fire_blaster2(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, monster_muzzleflash_id_t flashtype, effects_t effect)
{
fire_blaster2(self, start, dir, damage, speed, effect, false);
monster_muzzleflash(self, start, flashtype);
}
void monster_fire_tracker(edict_t *self, const vec3_t &start, const vec3_t &dir, int damage, int speed, edict_t *enemy, monster_muzzleflash_id_t flashtype)
{
fire_tracker(self, start, dir, damage, speed, enemy);
monster_muzzleflash(self, start, flashtype);
}
void monster_fire_heatbeam(edict_t *self, const vec3_t &start, const vec3_t &dir, const vec3_t &offset, int damage, int kick, monster_muzzleflash_id_t flashtype)
{
fire_heatbeam(self, start, dir, offset, damage, kick, true);
monster_muzzleflash(self, start, flashtype);
}
// ROGUE
// ROGUE
void stationarymonster_start_go(edict_t *self);
THINK(stationarymonster_triggered_spawn) (edict_t *self) -> void
{
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_NONE;
self->svflags &= ~SVF_NOCLIENT;
self->air_finished = level.time + 12_sec;
gi.linkentity(self);
KillBox(self, false);
// FIXME - why doesn't this happen with real monsters?
self->spawnflags &= ~SPAWNFLAG_MONSTER_TRIGGER_SPAWN;
stationarymonster_start_go(self);
if (self->enemy && !(self->spawnflags & SPAWNFLAG_MONSTER_AMBUSH) && !(self->enemy->flags & FL_NOTARGET))
{
if (!(self->enemy->flags & FL_DISGUISED)) // PGM
FoundTarget(self);
else // PMM - just in case, make sure to clear the enemy so FindTarget doesn't get confused
self->enemy = nullptr;
}
else
{
self->enemy = nullptr;
}
}
USE(stationarymonster_triggered_spawn_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
// we have a one frame delay here so we don't telefrag the guy who activated us
self->think = stationarymonster_triggered_spawn;
self->nextthink = level.time + FRAME_TIME_S;
if (activator->client)
self->enemy = activator;
self->use = monster_use;
}
void stationarymonster_triggered_start(edict_t *self)
{
self->solid = SOLID_NOT;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
self->nextthink = 0_ms;
self->use = stationarymonster_triggered_spawn_use;
}
THINK(stationarymonster_start_go) (edict_t *self) -> void
{
if (!self->yaw_speed)
self->yaw_speed = 20;
monster_start_go(self);
if (self->spawnflags.has(SPAWNFLAG_MONSTER_TRIGGER_SPAWN))
stationarymonster_triggered_start(self);
}
void stationarymonster_start(edict_t *self)
{
self->flags |= FL_STATIONARY;
self->think = stationarymonster_start_go;
monster_start(self);
// fix viewheight
self->viewheight = 0;
}
void monster_done_dodge(edict_t *self)
{
self->monsterinfo.aiflags &= ~AI_DODGING;
if (self->monsterinfo.attack_state == AS_SLIDING)
self->monsterinfo.attack_state = AS_STRAIGHT;
}
int32_t M_SlotsLeft(edict_t *self)
{
return self->monsterinfo.monster_slots - self->monsterinfo.monster_used;
}
// ROGUE

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,360 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_newdm.c
// pmack
// june 1998
#include "../g_local.h"
#include "../m_player.h"
dm_game_rt DMGame;
//=================
//=================
constexpr item_flags_t IF_TYPE_MASK = (IF_WEAPON | IF_AMMO | IF_POWERUP | IF_ARMOR | IF_KEY);
void ED_CallSpawn(edict_t *ent);
bool Pickup_Health(edict_t *ent, edict_t *other);
bool Pickup_Armor(edict_t *ent, edict_t *other);
bool Pickup_PowerArmor(edict_t *ent, edict_t *other);
inline item_flags_t GetSubstituteItemFlags(item_id_t id)
{
const gitem_t *item = GetItemByIndex(id);
// we want to stay within the item class
item_flags_t flags = item->flags & IF_TYPE_MASK;
if ((flags & (IF_WEAPON | IF_AMMO)) == (IF_WEAPON | IF_AMMO))
flags = IF_AMMO;
// Adrenaline and Mega Health count as powerup
else if (id == IT_ITEM_ADRENALINE || id == IT_HEALTH_MEGA)
flags = IF_POWERUP;
return flags;
}
inline item_id_t FindSubstituteItem(edict_t *ent)
{
// never replace flags
if (ent->item->id == IT_FLAG1 || ent->item->id == IT_FLAG2 || ent->item->id == IT_ITEM_TAG_TOKEN)
return IT_NULL;
// stimpack/shard randomizes
if (ent->item->id == IT_HEALTH_SMALL ||
ent->item->id == IT_ARMOR_SHARD)
return brandom() ? IT_HEALTH_SMALL : IT_ARMOR_SHARD;
// health is special case
if (ent->item->id == IT_HEALTH_MEDIUM ||
ent->item->id == IT_HEALTH_LARGE)
{
float rnd = frandom();
if (rnd < 0.6f)
return IT_HEALTH_MEDIUM;
else
return IT_HEALTH_LARGE;
}
// armor is also special case
else if (ent->item->id == IT_ARMOR_JACKET ||
ent->item->id == IT_ARMOR_COMBAT ||
ent->item->id == IT_ARMOR_BODY ||
ent->item->id == IT_ITEM_POWER_SCREEN ||
ent->item->id == IT_ITEM_POWER_SHIELD)
{
float rnd = frandom();
if (rnd < 0.4f)
return IT_ARMOR_JACKET;
else if (rnd < 0.6f)
return IT_ARMOR_COMBAT;
else if (rnd < 0.8f)
return IT_ARMOR_BODY;
else if (rnd < 0.9f)
return IT_ITEM_POWER_SCREEN;
else
return IT_ITEM_POWER_SHIELD;
}
item_flags_t myflags = GetSubstituteItemFlags(ent->item->id);
std::array<item_id_t, MAX_ITEMS> possible_items;
size_t possible_item_count = 0;
// gather matching items
for (item_id_t i = static_cast<item_id_t>(IT_NULL + 1); i < IT_TOTAL; i = static_cast<item_id_t>(static_cast<int32_t>(i) + 1))
{
const gitem_t *it = GetItemByIndex(i);
item_flags_t itflags = it->flags;
if (!itflags || (itflags & (IF_NOT_GIVEABLE | IF_TECH | IF_NOT_RANDOM)) || !it->pickup || !it->world_model)
continue;
itflags = GetSubstituteItemFlags(i);
// don't respawn spheres if they're dmflag disabled.
if (g_no_spheres->integer)
{
if (ent->item->id == IT_ITEM_SPHERE_VENGEANCE ||
ent->item->id == IT_ITEM_SPHERE_HUNTER ||
ent->item->id == IT_ITEM_SPHERE_DEFENDER)
{
continue;
}
}
if (g_no_nukes->integer && ent->item->id == IT_AMMO_NUKE)
continue;
if (g_no_mines->integer &&
(ent->item->id == IT_AMMO_PROX || ent->item->id == IT_AMMO_TESLA || ent->item->id == IT_AMMO_TRAP))
continue;
if ((itflags & IF_TYPE_MASK) == (myflags & IF_TYPE_MASK))
possible_items[possible_item_count++] = i;
}
if (!possible_item_count)
return IT_NULL;
return possible_items[irandom(possible_item_count)];
}
//=================
//=================
item_id_t DoRandomRespawn(edict_t *ent)
{
if (!ent->item)
return IT_NULL; // why
item_id_t id = FindSubstituteItem(ent);
if (id == IT_NULL)
return IT_NULL;
return id;
}
//=================
//=================
void PrecacheForRandomRespawn()
{
gitem_t *it;
int i;
int itflags;
it = itemlist;
for (i = 0; i < IT_TOTAL; i++, it++)
{
itflags = it->flags;
if (!itflags || (itflags & (IF_NOT_GIVEABLE | IF_TECH | IF_NOT_RANDOM)) || !it->pickup || !it->world_model)
continue;
PrecacheItem(it);
}
}
// ***************************
// DOPPLEGANGER
// ***************************
edict_t *Sphere_Spawn(edict_t *owner, spawnflags_t spawnflags);
DIE(doppleganger_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
edict_t *sphere;
float dist;
vec3_t dir;
if ((self->enemy) && (self->enemy != self->teammaster))
{
dir = self->enemy->s.origin - self->s.origin;
dist = dir.length();
if (dist > 80.f)
{
if (dist > 768)
{
sphere = Sphere_Spawn(self, SPHERE_HUNTER | SPHERE_DOPPLEGANGER);
sphere->pain(sphere, attacker, 0, 0, mod);
}
else
{
sphere = Sphere_Spawn(self, SPHERE_VENGEANCE | SPHERE_DOPPLEGANGER);
sphere->pain(sphere, attacker, 0, 0, mod);
}
}
}
self->takedamage = DAMAGE_NONE;
// [Paril-KEX]
T_RadiusDamage(self, self->teammaster, 160.f, self, 140.f, DAMAGE_NONE, MOD_DOPPLE_EXPLODE);
if (self->teamchain)
BecomeExplosion1(self->teamchain);
BecomeExplosion1(self);
}
PAIN(doppleganger_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
self->enemy = other;
}
THINK(doppleganger_timeout) (edict_t *self) -> void
{
doppleganger_die(self, self, self, 9999, self->s.origin, MOD_UNKNOWN);
}
THINK(body_think) (edict_t *self) -> void
{
float r;
if (fabsf(self->ideal_yaw - anglemod(self->s.angles[YAW])) < 2)
{
if (self->timestamp < level.time)
{
r = frandom();
if (r < 0.10f)
{
self->ideal_yaw = frandom(350.0f);
self->timestamp = level.time + 1_sec;
}
}
}
else
M_ChangeYaw(self);
if (self->teleport_time <= level.time)
{
self->s.frame++;
if (self->s.frame > FRAME_stand40)
self->s.frame = FRAME_stand01;
self->teleport_time = level.time + 10_hz;
}
self->nextthink = level.time + FRAME_TIME_MS;
}
void fire_doppleganger(edict_t *ent, const vec3_t &start, const vec3_t &aimdir)
{
edict_t *base;
edict_t *body;
vec3_t dir;
vec3_t forward, right, up;
int number;
dir = vectoangles(aimdir);
AngleVectors(dir, forward, right, up);
base = G_Spawn();
base->s.origin = start;
base->s.angles = dir;
base->movetype = MOVETYPE_TOSS;
base->solid = SOLID_BBOX;
base->s.renderfx |= RF_IR_VISIBLE;
base->s.angles[PITCH] = 0;
base->mins = { -16, -16, -24 };
base->maxs = { 16, 16, 32 };
base->s.modelindex = gi.modelindex ("models/objects/dopplebase/tris.md2");
base->s.alpha = 0.1f;
base->teammaster = ent;
base->flags |= ( FL_DAMAGEABLE | FL_TRAP );
base->takedamage = true;
base->health = 30;
base->pain = doppleganger_pain;
base->die = doppleganger_die;
base->nextthink = level.time + 30_sec;
base->think = doppleganger_timeout;
base->classname = "doppleganger";
gi.linkentity(base);
body = G_Spawn();
number = body->s.number;
body->s = ent->s;
body->s.sound = 0;
body->s.event = EV_NONE;
body->s.number = number;
body->yaw_speed = 30;
body->ideal_yaw = 0;
body->s.origin = start;
body->s.origin[2] += 8;
body->teleport_time = level.time + 10_hz;
body->think = body_think;
body->nextthink = level.time + FRAME_TIME_MS;
gi.linkentity(body);
base->teamchain = body;
body->teammaster = base;
// [Paril-KEX]
body->owner = ent;
gi.sound(body, CHAN_AUTO, gi.soundindex("medic_commander/monsterspawn1.wav"), 1.f, ATTN_NORM, 0.f);
}
void Tag_GameInit();
void Tag_PostInitSetup();
void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker);
void Tag_Score(edict_t *attacker, edict_t *victim, int scoreChange, const mod_t &mod);
void Tag_PlayerEffects(edict_t *ent);
void Tag_DogTag(edict_t *ent, edict_t *killer, const char **pic);
void Tag_PlayerDisconnect(edict_t *ent);
int Tag_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod);
void DBall_GameInit();
void DBall_ClientBegin(edict_t *ent);
bool DBall_SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn);
int DBall_ChangeKnockback(edict_t *targ, edict_t *attacker, int knockback, mod_t mod);
int DBall_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod);
void DBall_PostInitSetup();
int DBall_CheckDMRules();
// ****************************
// General DM Stuff
// ****************************
void InitGameRules()
{
// clear out the game rule structure before we start
memset(&DMGame, 0, sizeof(dm_game_rt));
if (gamerules->integer)
{
switch (gamerules->integer)
{
case RDM_TAG:
DMGame.GameInit = Tag_GameInit;
DMGame.PostInitSetup = Tag_PostInitSetup;
DMGame.PlayerDeath = Tag_PlayerDeath;
DMGame.Score = Tag_Score;
DMGame.PlayerEffects = Tag_PlayerEffects;
DMGame.DogTag = Tag_DogTag;
DMGame.PlayerDisconnect = Tag_PlayerDisconnect;
DMGame.ChangeDamage = Tag_ChangeDamage;
break;
case RDM_DEATHBALL:
DMGame.GameInit = DBall_GameInit;
DMGame.ChangeKnockback = DBall_ChangeKnockback;
DMGame.ChangeDamage = DBall_ChangeDamage;
DMGame.ClientBegin = DBall_ClientBegin;
DMGame.SelectSpawnPoint = DBall_SelectSpawnPoint;
DMGame.PostInitSetup = DBall_PostInitSetup;
DMGame.CheckDMRules = DBall_CheckDMRules;
break;
// reset gamerules if it's not a valid number
default:
gi.cvar_forceset("gamerules", "0");
break;
}
}
// if we're set up to play, initialize the game as needed.
if (DMGame.GameInit)
DMGame.GameInit();
}

View File

@@ -0,0 +1,325 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
void fd_secret_move1(edict_t *self);
void fd_secret_move2(edict_t *self);
void fd_secret_move3(edict_t *self);
void fd_secret_move4(edict_t *self);
void fd_secret_move5(edict_t *self);
void fd_secret_move6(edict_t *self);
void fd_secret_done(edict_t *self);
/*
=============================================================================
SECRET DOORS
=============================================================================
*/
constexpr spawnflags_t SPAWNFLAG_SEC_OPEN_ONCE = 1_spawnflag; // stays open
// unused
// constexpr uint32_t SPAWNFLAG_SEC_1ST_LEFT = 2; // 1st move is left of arrow
constexpr spawnflags_t SPAWNFLAG_SEC_1ST_DOWN = 4_spawnflag; // 1st move is down from arrow
// unused
// constexpr uint32_t SPAWNFLAG_SEC_NO_SHOOT = 8; // only opened by trigger
constexpr spawnflags_t SPAWNFLAG_SEC_YES_SHOOT = 16_spawnflag; // shootable even if targeted
constexpr spawnflags_t SPAWNFLAG_SEC_MOVE_RIGHT = 32_spawnflag;
constexpr spawnflags_t SPAWNFLAG_SEC_MOVE_FORWARD = 64_spawnflag;
USE(fd_secret_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
edict_t *ent;
if (self->flags & FL_TEAMSLAVE)
return;
// trigger all paired doors
for (ent = self; ent; ent = ent->teamchain)
Move_Calc(ent, ent->moveinfo.start_origin, fd_secret_move1);
}
DIE(fd_secret_killed) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
self->health = self->max_health;
self->takedamage = false;
if (self->flags & FL_TEAMSLAVE && self->teammaster && self->teammaster->takedamage != false)
fd_secret_killed(self->teammaster, inflictor, attacker, damage, point, mod);
else
fd_secret_use(self, inflictor, attacker);
}
// Wait after first movement...
MOVEINFO_ENDFUNC(fd_secret_move1) (edict_t *self) -> void
{
self->nextthink = level.time + 1_sec;
self->think = fd_secret_move2;
}
// Start moving sideways w/sound...
THINK(fd_secret_move2) (edict_t *self) -> void
{
Move_Calc(self, self->moveinfo.end_origin, fd_secret_move3);
}
// Wait here until time to go back...
MOVEINFO_ENDFUNC(fd_secret_move3) (edict_t *self) -> void
{
if (!self->spawnflags.has(SPAWNFLAG_SEC_OPEN_ONCE))
{
self->nextthink = level.time + gtime_t::from_sec(self->wait);
self->think = fd_secret_move4;
}
}
// Move backward...
THINK(fd_secret_move4) (edict_t *self) -> void
{
Move_Calc(self, self->moveinfo.start_origin, fd_secret_move5);
}
// Wait 1 second...
MOVEINFO_ENDFUNC(fd_secret_move5) (edict_t *self) -> void
{
self->nextthink = level.time + 1_sec;
self->think = fd_secret_move6;
}
THINK(fd_secret_move6) (edict_t *self) -> void
{
Move_Calc(self, self->move_origin, fd_secret_done);
}
MOVEINFO_ENDFUNC(fd_secret_done) (edict_t *self) -> void
{
if (!self->targetname || self->spawnflags.has(SPAWNFLAG_SEC_YES_SHOOT))
{
self->health = 1;
self->takedamage = true;
self->die = fd_secret_killed;
}
}
MOVEINFO_BLOCKED(secret_blocked) (edict_t *self, edict_t *other) -> void
{
if (!(self->flags & FL_TEAMSLAVE))
T_Damage(other, self, self, vec3_origin, other->s.origin, vec3_origin, self->dmg, 0, DAMAGE_NONE, MOD_CRUSH);
}
/*
================
secret_touch
Prints messages
================
*/
TOUCH(secret_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
if (other->health <= 0)
return;
if (!(other->client))
return;
if (self->monsterinfo.attack_finished > level.time)
return;
self->monsterinfo.attack_finished = level.time + 2_sec;
if (self->message)
gi.LocCenter_Print(other, self->message);
}
/*QUAKED func_door_secret2 (0 .5 .8) ? open_once 1st_left 1st_down no_shoot always_shoot slide_right slide_forward
Basic secret door. Slides back, then to the left. Angle determines direction.
FLAGS:
open_once = not implemented yet
1st_left = 1st move is left/right of arrow
1st_down = 1st move is forwards/backwards
no_shoot = not implemented yet
always_shoot = even if targeted, keep shootable
reverse_left = the sideways move will be to right of arrow
reverse_back = the to/fro move will be forward
VALUES:
wait = # of seconds before coming back (5 default)
dmg = damage to inflict when blocked (2 default)
*/
void SP_func_door_secret2(edict_t *ent)
{
vec3_t forward, right, up;
float lrSize, fbSize;
G_SetMoveinfoSounds(ent, "doors/dr1_strt.wav", "doors/dr1_mid.wav", "doors/dr1_end.wav");
if (!ent->dmg)
ent->dmg = 2;
AngleVectors(ent->s.angles, forward, right, up);
ent->move_origin = ent->s.origin;
ent->move_angles = ent->s.angles;
G_SetMovedir(ent->s.angles, ent->movedir);
ent->movetype = MOVETYPE_PUSH;
ent->solid = SOLID_BSP;
gi.setmodel(ent, ent->model);
if (ent->move_angles[1] == 0 || ent->move_angles[1] == 180)
{
lrSize = ent->size[1];
fbSize = ent->size[0];
}
else if (ent->move_angles[1] == 90 || ent->move_angles[1] == 270)
{
lrSize = ent->size[0];
fbSize = ent->size[1];
}
else
{
gi.Com_Print("Secret door not at 0,90,180,270!\n");
G_FreeEdict(ent);
return;
}
if (ent->spawnflags.has(SPAWNFLAG_SEC_MOVE_FORWARD))
forward *= fbSize;
else
forward *= fbSize * -1;
if (ent->spawnflags.has(SPAWNFLAG_SEC_MOVE_RIGHT))
right *= lrSize;
else
right *= lrSize * -1;
if (ent->spawnflags.has(SPAWNFLAG_SEC_1ST_DOWN))
{
ent->moveinfo.start_origin = ent->s.origin + forward;
ent->moveinfo.end_origin = ent->moveinfo.start_origin + right;
}
else
{
ent->moveinfo.start_origin = ent->s.origin + right;
ent->moveinfo.end_origin = ent->moveinfo.start_origin + forward;
}
ent->touch = secret_touch;
ent->moveinfo.blocked = secret_blocked;
ent->use = fd_secret_use;
ent->moveinfo.speed = 50;
ent->moveinfo.accel = 50;
ent->moveinfo.decel = 50;
if (!ent->targetname || ent->spawnflags.has(SPAWNFLAG_SEC_YES_SHOOT))
{
ent->health = 1;
ent->max_health = ent->health;
ent->takedamage = true;
ent->die = fd_secret_killed;
}
if (!ent->wait)
ent->wait = 5; // 5 seconds before closing
gi.linkentity(ent);
}
// ==================================================
constexpr spawnflags_t SPAWNFLAG_FORCEWALL_START_ON = 1_spawnflag;
THINK(force_wall_think) (edict_t *self) -> void
{
if (!self->wait)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_FORCEWALL);
gi.WritePosition(self->pos1);
gi.WritePosition(self->pos2);
gi.WriteByte(self->style);
gi.multicast(self->offset, MULTICAST_PVS, false);
}
self->think = force_wall_think;
self->nextthink = level.time + 10_hz;
}
USE(force_wall_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
if (!self->wait)
{
self->wait = 1;
self->think = nullptr;
self->nextthink = 0_ms;
self->solid = SOLID_NOT;
gi.linkentity(self);
}
else
{
self->wait = 0;
self->think = force_wall_think;
self->nextthink = level.time + 10_hz;
self->solid = SOLID_BSP;
gi.linkentity(self);
KillBox(self, false); // Is this appropriate?
}
}
/*QUAKED func_force_wall (1 0 1) ? start_on
A vertical particle force wall. Turns on and solid when triggered.
If someone is in the force wall when it turns on, they're telefragged.
start_on - forcewall begins activated. triggering will turn it off.
style - color of particles to use.
208: green, 240: red, 241: blue, 224: orange
*/
void SP_func_force_wall(edict_t *ent)
{
gi.setmodel(ent, ent->model);
ent->offset[0] = (ent->absmax[0] + ent->absmin[0]) / 2;
ent->offset[1] = (ent->absmax[1] + ent->absmin[1]) / 2;
ent->offset[2] = (ent->absmax[2] + ent->absmin[2]) / 2;
ent->pos1[2] = ent->absmax[2];
ent->pos2[2] = ent->absmax[2];
if (ent->size[0] > ent->size[1])
{
ent->pos1[0] = ent->absmin[0];
ent->pos2[0] = ent->absmax[0];
ent->pos1[1] = ent->offset[1];
ent->pos2[1] = ent->offset[1];
}
else
{
ent->pos1[0] = ent->offset[0];
ent->pos2[0] = ent->offset[0];
ent->pos1[1] = ent->absmin[1];
ent->pos2[1] = ent->absmax[1];
}
if (!ent->style)
ent->style = 208;
ent->movetype = MOVETYPE_NONE;
ent->wait = 1;
if (ent->spawnflags.has(SPAWNFLAG_FORCEWALL_START_ON))
{
ent->solid = SOLID_BSP;
ent->think = force_wall_think;
ent->nextthink = level.time + 10_hz;
}
else
ent->solid = SOLID_NOT;
ent->use = force_wall_use;
ent->svflags = SVF_NOCLIENT;
gi.linkentity(ent);
}

View File

@@ -0,0 +1,324 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
//==========================================================
/*QUAKED target_steam (1 0 0) (-8 -8 -8) (8 8 8)
Creates a steam effect (particles w/ velocity in a line).
speed = velocity of particles (default 50)
count = number of particles (default 32)
sounds = color of particles (default 8 for steam)
the color range is from this color to this color + 6
wait = seconds to run before stopping (overrides default
value derived from func_timer)
best way to use this is to tie it to a func_timer that "pokes"
it every second (or however long you set the wait time, above)
note that the width of the base is proportional to the speed
good colors to use:
6-9 - varying whites (darker to brighter)
224 - sparks
176 - blue water
80 - brown water
208 - slime
232 - blood
*/
USE(use_target_steam) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
// FIXME - this needs to be a global
static int nextid;
vec3_t point;
if (nextid > 20000)
nextid = nextid % 20000;
nextid++;
// automagically set wait from func_timer unless they set it already, or
// default to 1000 if not called by a func_timer (eek!)
if (!self->wait)
{
if (other)
self->wait = other->wait * 1000;
else
self->wait = 1000;
}
if (self->enemy)
{
point = (self->enemy->absmin + self->enemy->absmax) * 0.5f;
self->movedir = point - self->s.origin;
self->movedir.normalize();
}
point = self->s.origin + (self->movedir * (self->style * 0.5f));
if (self->wait > 100)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_STEAM);
gi.WriteShort(nextid);
gi.WriteByte(self->count);
gi.WritePosition(self->s.origin);
gi.WriteDir(self->movedir);
gi.WriteByte(self->sounds & 0xff);
gi.WriteShort((short int) (self->style));
gi.WriteLong((int) (self->wait));
gi.multicast(self->s.origin, MULTICAST_PVS, false);
}
else
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_STEAM);
gi.WriteShort((short int) -1);
gi.WriteByte(self->count);
gi.WritePosition(self->s.origin);
gi.WriteDir(self->movedir);
gi.WriteByte(self->sounds & 0xff);
gi.WriteShort((short int) (self->style));
gi.multicast(self->s.origin, MULTICAST_PVS, false);
}
}
THINK(target_steam_start) (edict_t *self) -> void
{
edict_t *ent;
self->use = use_target_steam;
if (self->target)
{
ent = G_FindByString<&edict_t::targetname>(nullptr, self->target);
if (!ent)
gi.Com_PrintFmt("{}: target {} not found\n", *self, self->target);
self->enemy = ent;
}
else
{
G_SetMovedir(self->s.angles, self->movedir);
}
if (!self->count)
self->count = 32;
if (!self->style)
self->style = 75;
if (!self->sounds)
self->sounds = 8;
if (self->wait)
self->wait *= 1000; // we want it in milliseconds, not seconds
// paranoia is good
self->sounds &= 0xff;
self->count &= 0xff;
self->svflags = SVF_NOCLIENT;
gi.linkentity(self);
}
void SP_target_steam(edict_t *self)
{
self->style = (int) self->speed;
if (self->target)
{
self->think = target_steam_start;
self->nextthink = level.time + 1_sec;
}
else
target_steam_start(self);
}
//==========================================================
// target_anger
//==========================================================
USE(target_anger_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
edict_t *target;
edict_t *t;
t = nullptr;
target = G_FindByString<&edict_t::targetname>(t, self->killtarget);
if (target && self->target)
{
// Make whatever a "good guy" so the monster will try to kill it!
if (!(target->svflags & SVF_MONSTER))
{
target->monsterinfo.aiflags |= AI_GOOD_GUY | AI_DO_NOT_COUNT;
target->svflags |= SVF_MONSTER;
target->health = 300;
}
t = nullptr;
while ((t = G_FindByString<&edict_t::targetname>(t, self->target)))
{
if (t == self)
{
gi.Com_Print("WARNING: entity used itself.\n");
}
else
{
if (t->use)
{
if (t->health <= 0)
return;
t->enemy = target;
t->monsterinfo.aiflags |= AI_TARGET_ANGER;
FoundTarget(t);
}
}
if (!self->inuse)
{
gi.Com_Print("entity was removed while using targets\n");
return;
}
}
}
}
/*QUAKED target_anger (1 0 0) (-8 -8 -8) (8 8 8)
This trigger will cause an entity to be angry at another entity when a player touches it. Target the
entity you want to anger, and killtarget the entity you want it to be angry at.
target - entity to piss off
killtarget - entity to be pissed off at
*/
void SP_target_anger(edict_t *self)
{
if (!self->target)
{
gi.Com_Print("target_anger without target!\n");
G_FreeEdict(self);
return;
}
if (!self->killtarget)
{
gi.Com_Print("target_anger without killtarget!\n");
G_FreeEdict(self);
return;
}
self->use = target_anger_use;
self->svflags = SVF_NOCLIENT;
}
// ***********************************
// target_killplayers
// ***********************************
USE(target_killplayers_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
level.deadly_kill_box = true;
edict_t *ent, *player;
// kill any visible monsters
for (ent = g_edicts; ent < &g_edicts[globals.num_edicts]; ent++)
{
if (!ent->inuse)
continue;
if (ent->health < 1)
continue;
if (!ent->takedamage)
continue;
for (uint32_t i = 0; i < game.maxclients; i++)
{
player = &g_edicts[1 + i];
if (!player->inuse)
continue;
if (gi.inPVS(player->s.origin, ent->s.origin, false))
{
T_Damage(ent, self, self, vec3_origin, ent->s.origin, vec3_origin,
ent->health, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
break;
}
}
}
// kill the players
for (uint32_t i = 0; i < game.maxclients; i++)
{
player = &g_edicts[1 + i];
if (!player->inuse)
continue;
// nail it
T_Damage(player, self, self, vec3_origin, self->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, MOD_TELEFRAG);
}
level.deadly_kill_box = false;
}
/*QUAKED target_killplayers (1 0 0) (-8 -8 -8) (8 8 8)
When triggered, this will kill all the players on the map.
*/
void SP_target_killplayers(edict_t *self)
{
self->use = target_killplayers_use;
self->svflags = SVF_NOCLIENT;
}
/*QUAKED target_blacklight (1 0 1) (-16 -16 -24) (16 16 24)
Pulsing black light with sphere in the center
*/
THINK(blacklight_think) (edict_t *self) -> void
{
self->s.angles[0] += frandom(10);
self->s.angles[1] += frandom(10);
self->s.angles[2] += frandom(10);
self->nextthink = level.time + FRAME_TIME_MS;
}
void SP_target_blacklight(edict_t *ent)
{
if (deathmatch->integer)
{ // auto-remove for deathmatch
G_FreeEdict(ent);
return;
}
ent->mins = {};
ent->maxs = {};
ent->s.effects |= (EF_TRACKERTRAIL | EF_TRACKER);
ent->think = blacklight_think;
ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2");
ent->s.scale = 6.f;
ent->s.skinnum = 0;
ent->nextthink = level.time + FRAME_TIME_MS;
gi.linkentity(ent);
}
/*QUAKED target_orb (1 0 1) (-16 -16 -24) (16 16 24)
Translucent pulsing orb with speckles
*/
void SP_target_orb(edict_t *ent)
{
if (deathmatch->integer)
{ // auto-remove for deathmatch
G_FreeEdict(ent);
return;
}
ent->mins = {};
ent->maxs = {};
// ent->s.effects |= EF_TRACKERTRAIL;
ent->think = blacklight_think;
ent->nextthink = level.time + 10_hz;
ent->s.skinnum = 1;
ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2");
ent->s.frame = 2;
ent->s.scale = 8.f;
ent->s.effects |= EF_SPHERETRANS;
gi.linkentity(ent);
}

View File

@@ -0,0 +1,189 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_newtrig.c
// pmack
// october 1997
#include "../g_local.h"
/*QUAKED info_teleport_destination (.5 .5 .5) (-16 -16 -24) (16 16 32)
Destination marker for a teleporter.
*/
void SP_info_teleport_destination(edict_t *self)
{
}
// unused; broken?
// constexpr uint32_t SPAWNFLAG_TELEPORT_PLAYER_ONLY = 1;
// unused
// constexpr uint32_t SPAWNFLAG_TELEPORT_SILENT = 2;
// unused
// constexpr uint32_t SPAWNFLAG_TELEPORT_CTF_ONLY = 4;
constexpr spawnflags_t SPAWNFLAG_TELEPORT_START_ON = 8_spawnflag;
/*QUAKED trigger_teleport (.5 .5 .5) ? player_only silent ctf_only start_on
Any object touching this will be transported to the corresponding
info_teleport_destination entity. You must set the "target" field,
and create an object with a "targetname" field that matches.
If the trigger_teleport has a targetname, it will only teleport
entities when it has been fired.
player_only: only players are teleported
silent: <not used right now>
ctf_only: <not used right now>
start_on: when trigger has targetname, start active, deactivate when used.
*/
TOUCH(trigger_teleport_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
edict_t *dest;
if (/*(self->spawnflags & SPAWNFLAG_TELEPORT_PLAYER_ONLY) &&*/ !(other->client))
return;
if (self->delay)
return;
dest = G_PickTarget(self->target);
if (!dest)
{
gi.Com_Print("Teleport Destination not found!\n");
return;
}
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_TELEPORT_EFFECT);
gi.WritePosition(other->s.origin);
gi.multicast(other->s.origin, MULTICAST_PVS, false);
other->s.origin = dest->s.origin;
other->s.old_origin = dest->s.origin;
other->s.origin[2] += 10;
// clear the velocity and hold them in place briefly
other->velocity = {};
if (other->client)
{
other->client->ps.pmove.pm_time = 160; // hold time
other->client->ps.pmove.pm_flags |= PMF_TIME_TELEPORT;
// draw the teleport splash at source and on the player
other->s.event = EV_PLAYER_TELEPORT;
// set angles
other->client->ps.pmove.delta_angles = dest->s.angles - other->client->resp.cmd_angles;
other->client->ps.viewangles = {};
other->client->v_angle = {};
}
other->s.angles = {};
gi.linkentity(other);
// kill anything at the destination
KillBox(other, !!other->client);
// [Paril-KEX] move sphere, if we own it
if (other->client && other->client->owned_sphere)
{
edict_t *sphere = other->client->owned_sphere;
sphere->s.origin = other->s.origin;
sphere->s.origin[2] = other->absmax[2];
sphere->s.angles[YAW] = other->s.angles[YAW];
gi.linkentity(sphere);
}
}
USE(trigger_teleport_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
if (self->delay)
self->delay = 0;
else
self->delay = 1;
}
void SP_trigger_teleport(edict_t *self)
{
if (!self->wait)
self->wait = 0.2f;
self->delay = 0;
if (self->targetname)
{
self->use = trigger_teleport_use;
if (!self->spawnflags.has(SPAWNFLAG_TELEPORT_START_ON))
self->delay = 1;
}
self->touch = trigger_teleport_touch;
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
if (self->s.angles)
G_SetMovedir(self->s.angles, self->movedir);
gi.setmodel(self, self->model);
gi.linkentity(self);
}
// ***************************
// TRIGGER_DISGUISE
// ***************************
/*QUAKED trigger_disguise (.5 .5 .5) ? TOGGLE START_ON REMOVE
Anything passing through this trigger when it is active will
be marked as disguised.
TOGGLE - field is turned off and on when used. (Paril N.B.: always the case)
START_ON - field is active when spawned.
REMOVE - field removes the disguise
*/
// unused
// constexpr uint32_t SPAWNFLAG_DISGUISE_TOGGLE = 1;
constexpr spawnflags_t SPAWNFLAG_DISGUISE_START_ON = 2_spawnflag;
constexpr spawnflags_t SPAWNFLAG_DISGUISE_REMOVE = 4_spawnflag;
TOUCH(trigger_disguise_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
if (other->client)
{
if (self->spawnflags.has(SPAWNFLAG_DISGUISE_REMOVE))
other->flags &= ~FL_DISGUISED;
else
other->flags |= FL_DISGUISED;
}
}
USE(trigger_disguise_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
if (self->solid == SOLID_NOT)
self->solid = SOLID_TRIGGER;
else
self->solid = SOLID_NOT;
gi.linkentity(self);
}
void SP_trigger_disguise(edict_t *self)
{
if (!level.disguise_icon)
level.disguise_icon = gi.imageindex("i_disguise");
if (self->spawnflags.has(SPAWNFLAG_DISGUISE_START_ON))
self->solid = SOLID_TRIGGER;
else
self->solid = SOLID_NOT;
self->touch = trigger_disguise_touch;
self->use = trigger_disguise_use;
self->movetype = MOVETYPE_NONE;
self->svflags = SVF_NOCLIENT;
gi.setmodel(self, self->model);
gi.linkentity(self);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,121 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
//============
// ROGUE
/*
=============
SV_Physics_NewToss
Toss, bounce, and fly movement. When on ground and no velocity, do nothing. With velocity,
slide.
=============
*/
void SV_Physics_NewToss(edict_t *ent)
{
trace_t trace;
vec3_t move;
// float backoff;
edict_t *slave;
bool wasinwater;
bool isinwater;
float speed, newspeed;
vec3_t old_origin;
// float firstmove;
// int mask;
// regular thinking
SV_RunThink(ent);
// if not a team captain, so movement will be handled elsewhere
if (ent->flags & FL_TEAMSLAVE)
return;
wasinwater = ent->waterlevel;
// find out what we're sitting on.
move = ent->s.origin;
move[2] -= 0.25f;
trace = gi.trace(ent->s.origin, ent->mins, ent->maxs, move, ent, ent->clipmask);
if (ent->groundentity && ent->groundentity->inuse)
ent->groundentity = trace.ent;
else
ent->groundentity = nullptr;
// if we're sitting on something flat and have no velocity of our own, return.
if (ent->groundentity && (trace.plane.normal[2] == 1.0f) &&
!ent->velocity[0] && !ent->velocity[1] && !ent->velocity[2])
{
return;
}
// store the old origin
old_origin = ent->s.origin;
SV_CheckVelocity(ent);
// add gravity
SV_AddGravity(ent);
if (ent->avelocity[0] || ent->avelocity[1] || ent->avelocity[2])
SV_AddRotationalFriction(ent);
// add friction
speed = ent->velocity.length();
if (ent->waterlevel) // friction for water movement
{
newspeed = speed - (sv_waterfriction * 6 * (float) ent->waterlevel);
if (newspeed < 0)
newspeed = 0;
newspeed /= speed;
ent->velocity *= newspeed;
}
else if (!ent->groundentity) // friction for air movement
{
newspeed = speed - ((sv_friction));
if (newspeed < 0)
newspeed = 0;
newspeed /= speed;
ent->velocity *= newspeed;
}
else // use ground friction
{
newspeed = speed - (sv_friction * 6);
if (newspeed < 0)
newspeed = 0;
newspeed /= speed;
ent->velocity *= newspeed;
}
SV_FlyMove(ent, gi.frame_time_s, ent->clipmask);
gi.linkentity(ent);
G_TouchTriggers(ent);
// check for water transition
wasinwater = (ent->watertype & MASK_WATER);
ent->watertype = gi.pointcontents(ent->s.origin);
isinwater = ent->watertype & MASK_WATER;
if (isinwater)
ent->waterlevel = WATER_FEET;
else
ent->waterlevel = WATER_NONE;
if (!wasinwater && isinwater)
gi.positioned_sound(old_origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
else if (wasinwater && !isinwater)
gi.positioned_sound(ent->s.origin, g_edicts, CHAN_AUTO, gi.soundindex("misc/h2ohit1.wav"), 1, 1, 0);
// move teamslaves
for (slave = ent->teamchain; slave; slave = slave->teamchain)
{
slave->s.origin = ent->s.origin;
gi.linkentity(slave);
}
}
// ROGUE
//============

View File

@@ -0,0 +1,367 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
//
// ROGUE
//
//
// Monster spawning code
//
// Used by the carrier, the medic_commander, and the black widow
//
// The sequence to create a flying monster is:
//
// FindSpawnPoint - tries to find suitable spot to spawn the monster in
// CreateFlyMonster - this verifies the point as good and creates the monster
// To create a ground walking monster:
//
// FindSpawnPoint - same thing
// CreateGroundMonster - this checks the volume and makes sure the floor under the volume is suitable
//
// FIXME - for the black widow, if we want the stalkers coming in on the roof, we'll have to tweak some things
//
// CreateMonster
//
edict_t *CreateMonster(const vec3_t &origin, const vec3_t &angles, const char *classname)
{
edict_t *newEnt;
newEnt = G_Spawn();
newEnt->s.origin = origin;
newEnt->s.angles = angles;
newEnt->classname = classname;
newEnt->monsterinfo.aiflags |= AI_DO_NOT_COUNT;
newEnt->gravityVector = { 0, 0, -1 };
ED_CallSpawn(newEnt);
newEnt->s.renderfx |= RF_IR_VISIBLE;
return newEnt;
}
edict_t *CreateFlyMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &mins, const vec3_t &maxs, const char *classname)
{
if (!CheckSpawnPoint(origin, mins, maxs))
return nullptr;
return (CreateMonster(origin, angles, classname));
}
// This is just a wrapper for CreateMonster that looks down height # of CMUs and sees if there
// are bad things down there or not
edict_t *CreateGroundMonster(const vec3_t &origin, const vec3_t &angles, const vec3_t &entMins, const vec3_t &entMaxs, const char *classname, float height)
{
edict_t *newEnt;
// check the ground to make sure it's there, it's relatively flat, and it's not toxic
if (!CheckGroundSpawnPoint(origin, entMins, entMaxs, height, -1.f))
return nullptr;
newEnt = CreateMonster(origin, angles, classname);
if (!newEnt)
return nullptr;
return newEnt;
}
// FindSpawnPoint
// PMM - this is used by the medic commander (possibly by the carrier) to find a good spawn point
// if the startpoint is bad, try above the startpoint for a bit
bool FindSpawnPoint(const vec3_t &startpoint, const vec3_t &mins, const vec3_t &maxs, vec3_t &spawnpoint, float maxMoveUp, bool drop)
{
spawnpoint = startpoint;
// drop first
if (!drop || !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false))
{
spawnpoint = startpoint;
// fix stuck if we couldn't drop initially
if (G_FixStuckObject_Generic(spawnpoint, mins, maxs, [] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) {
return gi.trace(start, mins, maxs, end, nullptr, MASK_MONSTERSOLID);
}) == stuck_result_t::NO_GOOD_POSITION)
return false;
// fixed, so drop again
if (drop && !M_droptofloor_generic(spawnpoint, mins, maxs, false, nullptr, MASK_MONSTERSOLID, false))
return false; // ???
}
return true;
}
// FIXME - all of this needs to be tweaked to handle the new gravity rules
// if we ever want to spawn stuff on the roof
//
// CheckSpawnPoint
//
// PMM - checks volume to make sure we can spawn a monster there (is it solid?)
//
// This is all fliers should need
bool CheckSpawnPoint(const vec3_t &origin, const vec3_t &mins, const vec3_t &maxs)
{
trace_t tr;
if (!mins || !maxs)
return false;
tr = gi.trace(origin, mins, maxs, origin, nullptr, MASK_MONSTERSOLID);
if (tr.startsolid || tr.allsolid)
return false;
if (tr.ent != world)
return false;
return true;
}
//
// CheckGroundSpawnPoint
//
// PMM - used for walking monsters
// checks:
// 1) is there a ground within the specified height of the origin?
// 2) is the ground non-water?
// 3) is the ground flat enough to walk on?
//
bool CheckGroundSpawnPoint(const vec3_t &origin, const vec3_t &entMins, const vec3_t &entMaxs, float height, float gravity)
{
if (!CheckSpawnPoint(origin, entMins, entMaxs))
return false;
if (M_CheckBottom_Fast_Generic(origin + entMins, origin + entMaxs, false))
return true;
if (M_CheckBottom_Slow_Generic(origin, entMins, entMaxs, nullptr, MASK_MONSTERSOLID, false, false))
return true;
return false;
}
// ****************************
// SPAWNGROW stuff
// ****************************
constexpr gtime_t SPAWNGROW_LIFESPAN = 1000_ms;
THINK(spawngrow_think) (edict_t *self) -> void
{
if (level.time >= self->timestamp)
{
G_FreeEdict(self->target_ent);
G_FreeEdict(self);
return;
}
self->s.angles += self->avelocity * gi.frame_time_s;
float t = 1.f - ((level.time - self->teleport_time).seconds() / self->wait);
self->s.scale = clamp(lerp(self->accel, self->decel, t) / 16.f, 0.001f, 16.f);
self->s.alpha = t * t;
self->nextthink += FRAME_TIME_MS;
}
static vec3_t SpawnGro_laser_pos(edict_t *ent)
{
// pick random direction
float theta = frandom(2 * PIf);
float phi = acos(crandom());
vec3_t d {
sin(phi) * cos(theta),
sin(phi) * sin(theta),
cos(phi)
};
return ent->s.origin + (d * ent->owner->s.scale * 9.f);
}
THINK(SpawnGro_laser_think) (edict_t *self) -> void
{
self->s.old_origin = SpawnGro_laser_pos(self);
gi.linkentity(self);
self->nextthink = level.time + 1_ms;
}
void SpawnGrow_Spawn(const vec3_t &startpos, float start_size, float end_size)
{
edict_t *ent;
ent = G_Spawn();
ent->s.origin = startpos;
ent->s.angles[0] = (float) irandom(360);
ent->s.angles[1] = (float) irandom(360);
ent->s.angles[2] = (float) irandom(360);
ent->avelocity[0] = frandom(280.f, 360.f) * 2.f;
ent->avelocity[1] = frandom(280.f, 360.f) * 2.f;
ent->avelocity[2] = frandom(280.f, 360.f) * 2.f;
ent->solid = SOLID_NOT;
ent->s.renderfx |= RF_IR_VISIBLE;
ent->movetype = MOVETYPE_NONE;
ent->classname = "spawngro";
ent->s.modelindex = gi.modelindex("models/items/spawngro3/tris.md2");
ent->s.skinnum = 1;
ent->accel = start_size;
ent->decel = end_size;
ent->think = spawngrow_think;
ent->s.scale = clamp(start_size / 16.f, 0.001f, 8.f);
ent->teleport_time = level.time;
ent->wait = SPAWNGROW_LIFESPAN.seconds();
ent->timestamp = level.time + SPAWNGROW_LIFESPAN;
ent->nextthink = level.time + FRAME_TIME_MS;
gi.linkentity(ent);
// [Paril-KEX]
edict_t *beam = ent->target_ent = G_Spawn();
beam->s.modelindex = MODELINDEX_WORLD;
beam->s.renderfx = RF_BEAM_LIGHTNING | RF_NO_ORIGIN_LERP;
beam->s.frame = 1;
beam->s.skinnum = 0x30303030;
beam->classname = "spawngro_beam";
beam->angle = end_size;
beam->owner = ent;
beam->s.origin = ent->s.origin;
beam->think = SpawnGro_laser_think;
beam->nextthink = level.time + 1_ms;
beam->s.old_origin = SpawnGro_laser_pos(beam);
gi.linkentity(beam);
}
// ****************************
// WidowLeg stuff
// ****************************
constexpr int32_t MAX_LEGSFRAME = 23;
constexpr gtime_t LEG_WAIT_TIME = 1_sec;
void ThrowMoreStuff(edict_t *self, const vec3_t &point);
void ThrowSmallStuff(edict_t *self, const vec3_t &point);
void ThrowWidowGibLoc(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, bool fade);
void ThrowWidowGibSized(edict_t *self, const char *gibname, int damage, gib_type_t type, const vec3_t *startpos, int hitsound, bool fade);
THINK(widowlegs_think) (edict_t *self) -> void
{
vec3_t offset;
vec3_t point;
vec3_t f, r, u;
if (self->s.frame == 17)
{
offset = { 11.77f, -7.24f, 23.31f };
AngleVectors(self->s.angles, f, r, u);
point = G_ProjectSource2(self->s.origin, offset, f, r, u);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL, false);
ThrowSmallStuff(self, point);
}
if (self->s.frame < MAX_LEGSFRAME)
{
self->s.frame++;
self->nextthink = level.time + 10_hz;
return;
}
else if (self->wait == 0)
{
self->wait = (level.time + LEG_WAIT_TIME).seconds();
}
if (level.time > gtime_t::from_sec(self->wait))
{
AngleVectors(self->s.angles, f, r, u);
offset = { -65.6f, -8.44f, 28.59f };
point = G_ProjectSource2(self->s.origin, offset, f, r, u);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL, false);
ThrowSmallStuff(self, point);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
offset = { -1.04f, -51.18f, 7.04f };
point = G_ProjectSource2(self->s.origin, offset, f, r, u);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL, false);
ThrowSmallStuff(self, point);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib1/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib2/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
ThrowWidowGibSized(self, "models/monsters/blackwidow/gib3/tris.md2", 80 + (int) frandom(20.0f), GIB_METALLIC, &point, 0, true);
G_FreeEdict(self);
return;
}
if ((level.time > gtime_t::from_sec(self->wait - 0.5f)) && (self->count == 0))
{
self->count = 1;
AngleVectors(self->s.angles, f, r, u);
offset = { 31, -88.7f, 10.96f };
point = G_ProjectSource2(self->s.origin, offset, f, r, u);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL, false);
// ThrowSmallStuff (self, point);
offset = { -12.67f, -4.39f, 15.68f };
point = G_ProjectSource2(self->s.origin, offset, f, r, u);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(point);
gi.multicast(point, MULTICAST_ALL, false);
// ThrowSmallStuff (self, point);
self->nextthink = level.time + 10_hz;
return;
}
self->nextthink = level.time + 10_hz;
}
void Widowlegs_Spawn(const vec3_t &startpos, const vec3_t &angles)
{
edict_t *ent;
ent = G_Spawn();
ent->s.origin = startpos;
ent->s.angles = angles;
ent->solid = SOLID_NOT;
ent->s.renderfx = RF_IR_VISIBLE;
ent->movetype = MOVETYPE_NONE;
ent->classname = "widowlegs";
ent->s.modelindex = gi.modelindex("models/monsters/legs/tris.md2");
ent->think = widowlegs_think;
ent->nextthink = level.time + 10_hz;
gi.linkentity(ent);
}

View File

@@ -0,0 +1,710 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_sphere.c
// pmack
// april 1998
// defender - actively finds and shoots at enemies
// hunter - waits until < 25% health and vore ball tracks person who hurt you
// vengeance - kills person who killed you.
#include "../g_local.h"
constexpr gtime_t DEFENDER_LIFESPAN = 30_sec;
constexpr gtime_t HUNTER_LIFESPAN = 30_sec;
constexpr gtime_t VENGEANCE_LIFESPAN = 30_sec;
constexpr gtime_t MINIMUM_FLY_TIME = 15_sec;
void LookAtKiller(edict_t *self, edict_t *inflictor, edict_t *attacker);
void vengeance_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self);
void hunter_touch(edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self);
// *************************
// General Sphere Code
// *************************
// =================
// =================
THINK(sphere_think_explode) (edict_t *self) -> void
{
if (self->owner && self->owner->client && !(self->spawnflags & SPHERE_DOPPLEGANGER))
{
self->owner->client->owned_sphere = nullptr;
}
BecomeExplosion1(self);
}
// =================
// sphere_explode
// =================
DIE(sphere_explode) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
sphere_think_explode(self);
}
// =================
// sphere_if_idle_die - if the sphere is not currently attacking, blow up.
// =================
DIE(sphere_if_idle_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (!self->enemy)
sphere_think_explode(self);
}
// *************************
// Sphere Movement
// *************************
// =================
// =================
void sphere_fly(edict_t *self)
{
vec3_t dest;
vec3_t dir;
if (level.time >= gtime_t::from_sec(self->wait))
{
sphere_think_explode(self);
return;
}
dest = self->owner->s.origin;
dest[2] = self->owner->absmax[2] + 4;
if (level.time.seconds() == level.time.seconds<int>())
{
if (!visible(self, self->owner))
{
self->s.origin = dest;
gi.linkentity(self);
return;
}
}
dir = dest - self->s.origin;
self->velocity = dir * 5;
}
// =================
// =================
void sphere_chase(edict_t *self, int stupidChase)
{
vec3_t dest;
vec3_t dir;
float dist;
if (level.time >= gtime_t::from_sec(self->wait) || (self->enemy && self->enemy->health < 1))
{
sphere_think_explode(self);
return;
}
dest = self->enemy->s.origin;
if (self->enemy->client)
dest[2] += self->enemy->viewheight;
if (visible(self, self->enemy) || stupidChase)
{
// if moving, hunter sphere uses active sound
if (!stupidChase)
self->s.sound = gi.soundindex("spheres/h_active.wav");
dir = dest - self->s.origin;
dir.normalize();
self->s.angles = vectoangles(dir);
self->velocity = dir * 500;
self->monsterinfo.saved_goal = dest;
}
else if (!self->monsterinfo.saved_goal)
{
dir = self->enemy->s.origin - self->s.origin;
dist = dir.normalize();
self->s.angles = vectoangles(dir);
// if lurking, hunter sphere uses lurking sound
self->s.sound = gi.soundindex("spheres/h_lurk.wav");
self->velocity = {};
}
else
{
dir = self->monsterinfo.saved_goal - self->s.origin;
dist = dir.normalize();
if (dist > 1)
{
self->s.angles = vectoangles(dir);
if (dist > 500)
self->velocity = dir * 500;
else if (dist < 20)
self->velocity = dir * (dist / gi.frame_time_s);
else
self->velocity = dir * dist;
// if moving, hunter sphere uses active sound
if (!stupidChase)
self->s.sound = gi.soundindex("spheres/h_active.wav");
}
else
{
dir = self->enemy->s.origin - self->s.origin;
dist = dir.normalize();
self->s.angles = vectoangles(dir);
// if not moving, hunter sphere uses lurk sound
if (!stupidChase)
self->s.sound = gi.soundindex("spheres/h_lurk.wav");
self->velocity = {};
}
}
}
// *************************
// Attack related stuff
// *************************
// =================
// =================
void sphere_fire(edict_t *self, edict_t *enemy)
{
vec3_t dest;
vec3_t dir;
if (!enemy || level.time >= gtime_t::from_sec(self->wait))
{
sphere_think_explode(self);
return;
}
dest = enemy->s.origin;
self->s.effects |= EF_ROCKET;
dir = dest - self->s.origin;
dir.normalize();
self->s.angles = vectoangles(dir);
self->velocity = dir * 1000;
self->touch = vengeance_touch;
self->think = sphere_think_explode;
self->nextthink = gtime_t::from_sec(self->wait);
}
// =================
// =================
void sphere_touch(edict_t *self, edict_t *other, const trace_t &tr, mod_t mod)
{
if (self->spawnflags.has(SPHERE_DOPPLEGANGER))
{
if (other == self->teammaster)
return;
self->takedamage = false;
self->owner = self->teammaster;
self->teammaster = nullptr;
}
else
{
if (other == self->owner)
return;
// PMM - don't blow up on bodies
if (!strcmp(other->classname, "bodyque"))
return;
}
if (tr.surface && (tr.surface->flags & SURF_SKY))
{
G_FreeEdict(self);
return;
}
if (self->owner)
{
if (other->takedamage)
{
T_Damage(other, self, self->owner, self->velocity, self->s.origin, tr.plane.normal,
10000, 1, DAMAGE_DESTROY_ARMOR, mod);
}
else
{
T_RadiusDamage(self, self->owner, 512, self->owner, 256, DAMAGE_NONE, mod);
}
}
sphere_think_explode(self);
}
// =================
// =================
TOUCH(vengeance_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
if (self->spawnflags.has(SPHERE_DOPPLEGANGER))
sphere_touch(self, other, tr, MOD_DOPPLE_VENGEANCE);
else
sphere_touch(self, other, tr, MOD_VENGEANCE_SPHERE);
}
// =================
// =================
TOUCH(hunter_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
edict_t *owner;
// don't blow up if you hit the world.... sheesh.
if (other == world)
return;
if (self->owner)
{
// if owner is flying with us, make sure they stop too.
owner = self->owner;
if (owner->flags & FL_SAM_RAIMI)
{
owner->velocity = {};
owner->movetype = MOVETYPE_NONE;
gi.linkentity(owner);
}
}
if (self->spawnflags.has(SPHERE_DOPPLEGANGER))
sphere_touch(self, other, tr, MOD_DOPPLE_HUNTER);
else
sphere_touch(self, other, tr, MOD_HUNTER_SPHERE);
}
// =================
// =================
void defender_shoot(edict_t *self, edict_t *enemy)
{
vec3_t dir;
vec3_t start;
if (!(enemy->inuse) || enemy->health <= 0)
return;
if (enemy == self->owner)
return;
dir = enemy->s.origin - self->s.origin;
dir.normalize();
if (self->monsterinfo.attack_finished > level.time)
return;
if (!visible(self, self->enemy))
return;
start = self->s.origin;
start[2] += 2;
fire_blaster2(self->owner, start, dir, 10, 1000, EF_BLASTER, 0);
self->monsterinfo.attack_finished = level.time + 400_ms;
}
// *************************
// Activation Related Stuff
// *************************
// =================
// =================
void body_gib(edict_t *self)
{
gi.sound(self, CHAN_BODY, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
ThrowGibs(self, 50, {
{ 4, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/objects/gibs/skull/tris.md2" }
});
}
// =================
// =================
PAIN(hunter_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
edict_t *owner;
float dist;
vec3_t dir;
if (self->enemy)
return;
owner = self->owner;
if (!(self->spawnflags & SPHERE_DOPPLEGANGER))
{
if (owner && (owner->health > 0))
return;
// PMM
if (other == owner)
return;
// pmm
}
else
{
// if fired by a doppleganger, set it to 10 second timeout
self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
}
if ((gtime_t::from_sec(self->wait) - level.time) < MINIMUM_FLY_TIME)
self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
self->s.effects |= EF_BLASTER | EF_TRACKER;
self->touch = hunter_touch;
self->enemy = other;
// if we're not owned by a player, no sam raimi
// if we're spawned by a doppleganger, no sam raimi
if (self->spawnflags.has(SPHERE_DOPPLEGANGER) || !(owner && owner->client))
return;
// sam raimi cam is disabled if FORCE_RESPAWN is set.
// sam raimi cam is also disabled if huntercam->value is 0.
if (!g_dm_force_respawn->integer && huntercam->integer)
{
dir = other->s.origin - self->s.origin;
dist = dir.length();
if (owner && (dist >= 192))
{
// detach owner from body and send him flying
owner->movetype = MOVETYPE_FLYMISSILE;
// gib like we just died, even though we didn't, really.
body_gib(owner);
// move the sphere to the owner's current viewpoint.
// we know it's a valid spot (or will be momentarily)
self->s.origin = owner->s.origin;
self->s.origin[2] += owner->viewheight;
// move the player's origin to the sphere's new origin
owner->s.origin = self->s.origin;
owner->s.angles = self->s.angles;
owner->client->v_angle = self->s.angles;
owner->mins = { -5, -5, -5 };
owner->maxs = { 5, 5, 5 };
owner->client->ps.fov = 140;
owner->s.modelindex = 0;
owner->s.modelindex2 = 0;
owner->viewheight = 8;
owner->solid = SOLID_NOT;
owner->flags |= FL_SAM_RAIMI;
gi.linkentity(owner);
self->solid = SOLID_BBOX;
gi.linkentity(self);
}
}
}
// =================
// =================
PAIN(defender_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
// PMM
if (other == self->owner)
return;
// pmm
self->enemy = other;
}
// =================
// =================
PAIN(vengeance_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (self->enemy)
return;
if (!(self->spawnflags & SPHERE_DOPPLEGANGER))
{
if (self->owner && self->owner->health >= 25)
return;
// PMM
if (other == self->owner)
return;
// pmm
}
else
{
self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
}
if ((gtime_t::from_sec(self->wait) - level.time) < MINIMUM_FLY_TIME)
self->wait = (level.time + MINIMUM_FLY_TIME).seconds();
self->s.effects |= EF_ROCKET;
self->touch = vengeance_touch;
self->enemy = other;
}
// *************************
// Think Functions
// *************************
// ===================
// ===================
THINK(defender_think) (edict_t *self) -> void
{
if (!self->owner)
{
G_FreeEdict(self);
return;
}
// if we've exited the level, just remove ourselves.
if (level.intermissiontime)
{
sphere_think_explode(self);
return;
}
if (self->owner->health <= 0)
{
sphere_think_explode(self);
return;
}
self->s.frame++;
if (self->s.frame > 19)
self->s.frame = 0;
if (self->enemy)
{
if (self->enemy->health > 0)
defender_shoot(self, self->enemy);
else
self->enemy = nullptr;
}
sphere_fly(self);
if (self->inuse)
self->nextthink = level.time + 10_hz;
}
// =================
// =================
THINK(hunter_think) (edict_t *self) -> void
{
// if we've exited the level, just remove ourselves.
if (level.intermissiontime)
{
sphere_think_explode(self);
return;
}
edict_t *owner = self->owner;
if (!owner && !(self->spawnflags & SPHERE_DOPPLEGANGER))
{
G_FreeEdict(self);
return;
}
if (owner)
self->ideal_yaw = owner->s.angles[YAW];
else if (self->enemy) // fired by doppleganger
{
vec3_t dir = self->enemy->s.origin - self->s.origin;
self->ideal_yaw = vectoyaw(dir);
}
M_ChangeYaw(self);
if (self->enemy)
{
sphere_chase(self, 0);
// deal with sam raimi cam
if (owner && (owner->flags & FL_SAM_RAIMI))
{
if (self->inuse)
{
owner->movetype = MOVETYPE_FLYMISSILE;
LookAtKiller(owner, self, self->enemy);
// owner is flying with us, move him too
owner->movetype = MOVETYPE_FLYMISSILE;
owner->viewheight = (int) (self->s.origin[2] - owner->s.origin[2]);
owner->s.origin = self->s.origin;
owner->velocity = self->velocity;
owner->mins = {};
owner->maxs = {};
gi.linkentity(owner);
}
else // sphere timed out
{
owner->velocity = {};
owner->movetype = MOVETYPE_NONE;
gi.linkentity(owner);
}
}
}
else
sphere_fly(self);
if (self->inuse)
self->nextthink = level.time + 10_hz;
}
// =================
// =================
THINK(vengeance_think) (edict_t *self) -> void
{
// if we've exited the level, just remove ourselves.
if (level.intermissiontime)
{
sphere_think_explode(self);
return;
}
if (!(self->owner) && !(self->spawnflags & SPHERE_DOPPLEGANGER))
{
G_FreeEdict(self);
return;
}
if (self->enemy)
sphere_chase(self, 1);
else
sphere_fly(self);
if (self->inuse)
self->nextthink = level.time + 10_hz;
}
// *************************
// Spawning / Creation
// *************************
// monsterinfo_t
// =================
// =================
edict_t *Sphere_Spawn(edict_t *owner, spawnflags_t spawnflags)
{
edict_t *sphere;
sphere = G_Spawn();
sphere->s.origin = owner->s.origin;
sphere->s.origin[2] = owner->absmax[2];
sphere->s.angles[YAW] = owner->s.angles[YAW];
sphere->solid = SOLID_BBOX;
sphere->clipmask = MASK_PROJECTILE;
sphere->s.renderfx = RF_FULLBRIGHT | RF_IR_VISIBLE;
sphere->movetype = MOVETYPE_FLYMISSILE;
if (spawnflags.has(SPHERE_DOPPLEGANGER))
sphere->teammaster = owner->teammaster;
else
sphere->owner = owner;
sphere->classname = "sphere";
sphere->yaw_speed = 40;
sphere->monsterinfo.attack_finished = 0_ms;
sphere->spawnflags = spawnflags; // need this for the HUD to recognize sphere
// PMM
sphere->takedamage = false;
switch ((spawnflags & SPHERE_TYPE).value)
{
case SPHERE_DEFENDER.value:
sphere->s.modelindex = gi.modelindex("models/items/defender/tris.md2");
sphere->s.modelindex2 = gi.modelindex("models/items/shell/tris.md2");
sphere->s.sound = gi.soundindex("spheres/d_idle.wav");
sphere->pain = defender_pain;
sphere->wait = (level.time + DEFENDER_LIFESPAN).seconds();
sphere->die = sphere_explode;
sphere->think = defender_think;
break;
case SPHERE_HUNTER.value:
sphere->s.modelindex = gi.modelindex("models/items/hunter/tris.md2");
sphere->s.sound = gi.soundindex("spheres/h_idle.wav");
sphere->wait = (level.time + HUNTER_LIFESPAN).seconds();
sphere->pain = hunter_pain;
sphere->die = sphere_if_idle_die;
sphere->think = hunter_think;
break;
case SPHERE_VENGEANCE.value:
sphere->s.modelindex = gi.modelindex("models/items/vengnce/tris.md2");
sphere->s.sound = gi.soundindex("spheres/v_idle.wav");
sphere->wait = (level.time + VENGEANCE_LIFESPAN).seconds();
sphere->pain = vengeance_pain;
sphere->die = sphere_if_idle_die;
sphere->think = vengeance_think;
sphere->avelocity = { 30, 30, 0 };
break;
default:
gi.Com_Print("Tried to create an invalid sphere\n");
G_FreeEdict(sphere);
return nullptr;
}
sphere->nextthink = level.time + 10_hz;
gi.linkentity(sphere);
return sphere;
}
// =================
// Own_Sphere - attach the sphere to the client so we can
// directly access it later
// =================
void Own_Sphere(edict_t *self, edict_t *sphere)
{
if (!sphere)
return;
// ownership only for players
if (self->client)
{
// if they don't have one
if (!(self->client->owned_sphere))
{
self->client->owned_sphere = sphere;
}
// they already have one, take care of the old one
else
{
if (self->client->owned_sphere->inuse)
{
G_FreeEdict(self->client->owned_sphere);
self->client->owned_sphere = sphere;
}
else
{
self->client->owned_sphere = sphere;
}
}
}
}
// =================
// =================
void Defender_Launch(edict_t *self)
{
edict_t *sphere;
sphere = Sphere_Spawn(self, SPHERE_DEFENDER);
Own_Sphere(self, sphere);
}
// =================
// =================
void Hunter_Launch(edict_t *self)
{
edict_t *sphere;
sphere = Sphere_Spawn(self, SPHERE_HUNTER);
Own_Sphere(self, sphere);
}
// =================
// =================
void Vengeance_Launch(edict_t *self)
{
edict_t *sphere;
sphere = Sphere_Spawn(self, SPHERE_VENGEANCE);
Own_Sphere(self, sphere);
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
/*
=================
findradius2
Returns entities that have origins within a spherical area
ROGUE - tweaks for performance for tesla specific code
only returns entities that can be damaged
only returns entities that are FL_DAMAGEABLE
findradius2 (origin, radius)
=================
*/
edict_t *findradius2(edict_t *from, const vec3_t &org, float rad)
{
// rad must be positive
vec3_t eorg;
int j;
if (!from)
from = g_edicts;
else
from++;
for (; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
continue;
if (from->solid == SOLID_NOT)
continue;
if (!from->takedamage)
continue;
if (!(from->flags & FL_DAMAGEABLE))
continue;
for (j = 0; j < 3; j++)
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5f);
if (eorg.length() > rad)
continue;
return from;
}
return nullptr;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,88 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\xpack\models/monsters/carrier
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_search01,
FRAME_search02,
FRAME_search03,
FRAME_search04,
FRAME_search05,
FRAME_search06,
FRAME_search07,
FRAME_search08,
FRAME_search09,
FRAME_search10,
FRAME_search11,
FRAME_search12,
FRAME_search13,
FRAME_firea01,
FRAME_firea02,
FRAME_firea03,
FRAME_firea04,
FRAME_firea05,
FRAME_firea06,
FRAME_firea07,
FRAME_firea08,
FRAME_firea09,
FRAME_firea10,
FRAME_firea11,
FRAME_firea12,
FRAME_firea13,
FRAME_firea14,
FRAME_firea15,
FRAME_fireb01,
FRAME_fireb02,
FRAME_fireb03,
FRAME_fireb04,
FRAME_fireb05,
FRAME_fireb06,
FRAME_fireb07,
FRAME_fireb08,
FRAME_fireb09,
FRAME_fireb10,
FRAME_fireb11,
FRAME_fireb12,
FRAME_fireb13,
FRAME_fireb14,
FRAME_fireb15,
FRAME_fireb16,
FRAME_spawn01,
FRAME_spawn02,
FRAME_spawn03,
FRAME_spawn04,
FRAME_spawn05,
FRAME_spawn06,
FRAME_spawn07,
FRAME_spawn08,
FRAME_spawn09,
FRAME_spawn10,
FRAME_spawn11,
FRAME_spawn12,
FRAME_spawn13,
FRAME_spawn14,
FRAME_spawn15,
FRAME_spawn16,
FRAME_spawn17,
FRAME_spawn18,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16
};
constexpr float MODEL_SCALE = 1.000000f;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,104 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// /expanse/quake2/xpack/models/monsters/stalker
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_idle01,
FRAME_idle02,
FRAME_idle03,
FRAME_idle04,
FRAME_idle05,
FRAME_idle06,
FRAME_idle07,
FRAME_idle08,
FRAME_idle09,
FRAME_idle10,
FRAME_idle11,
FRAME_idle12,
FRAME_idle13,
FRAME_idle14,
FRAME_idle15,
FRAME_idle16,
FRAME_idle17,
FRAME_idle18,
FRAME_idle19,
FRAME_idle20,
FRAME_idle21,
FRAME_idle201,
FRAME_idle202,
FRAME_idle203,
FRAME_idle204,
FRAME_idle205,
FRAME_idle206,
FRAME_idle207,
FRAME_idle208,
FRAME_idle209,
FRAME_idle210,
FRAME_idle211,
FRAME_idle212,
FRAME_idle213,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_jump01,
FRAME_jump02,
FRAME_jump03,
FRAME_jump04,
FRAME_jump05,
FRAME_jump06,
FRAME_jump07,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_attack01,
FRAME_attack02,
FRAME_attack03,
FRAME_attack04,
FRAME_attack05,
FRAME_attack06,
FRAME_attack07,
FRAME_attack08,
FRAME_attack11,
FRAME_attack12,
FRAME_attack13,
FRAME_attack14,
FRAME_attack15,
FRAME_pain01,
FRAME_pain02,
FRAME_pain03,
FRAME_pain04,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_twitch01,
FRAME_twitch02,
FRAME_twitch03,
FRAME_twitch04,
FRAME_twitch05,
FRAME_twitch06,
FRAME_twitch07,
FRAME_twitch08,
FRAME_twitch09,
FRAME_twitch10,
FRAME_reactive01,
FRAME_reactive02,
FRAME_reactive03,
FRAME_reactive04
};
constexpr float MODEL_SCALE = 1.000000f;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,27 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\xpack\models/monsters/turret
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_stand01,
FRAME_stand02,
FRAME_active01,
FRAME_active02,
FRAME_active03,
FRAME_active04,
FRAME_active05,
FRAME_active06,
FRAME_run01,
FRAME_run02,
FRAME_pow01,
FRAME_pow02,
FRAME_pow03,
FRAME_pow04,
FRAME_death01,
FRAME_death02
};
constexpr float MODEL_SCALE = 3.500000f;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,180 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\xpack\models/monsters/blackwidow
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_idle01,
FRAME_idle02,
FRAME_idle03,
FRAME_idle04,
FRAME_idle05,
FRAME_idle06,
FRAME_idle07,
FRAME_idle08,
FRAME_idle09,
FRAME_idle10,
FRAME_idle11,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_run07,
FRAME_run08,
FRAME_firea01,
FRAME_firea02,
FRAME_firea03,
FRAME_firea04,
FRAME_firea05,
FRAME_firea06,
FRAME_firea07,
FRAME_firea08,
FRAME_firea09,
FRAME_fireb01,
FRAME_fireb02,
FRAME_fireb03,
FRAME_fireb04,
FRAME_fireb05,
FRAME_fireb06,
FRAME_fireb07,
FRAME_fireb08,
FRAME_fireb09,
FRAME_firec01,
FRAME_firec02,
FRAME_firec03,
FRAME_firec04,
FRAME_firec05,
FRAME_firec06,
FRAME_firec07,
FRAME_firec08,
FRAME_firec09,
FRAME_fired01,
FRAME_fired02,
FRAME_fired02a,
FRAME_fired03,
FRAME_fired04,
FRAME_fired05,
FRAME_fired06,
FRAME_fired07,
FRAME_fired08,
FRAME_fired09,
FRAME_fired10,
FRAME_fired11,
FRAME_fired12,
FRAME_fired13,
FRAME_fired14,
FRAME_fired15,
FRAME_fired16,
FRAME_fired17,
FRAME_fired18,
FRAME_fired19,
FRAME_fired20,
FRAME_fired21,
FRAME_fired22,
FRAME_spawn01,
FRAME_spawn02,
FRAME_spawn03,
FRAME_spawn04,
FRAME_spawn05,
FRAME_spawn06,
FRAME_spawn07,
FRAME_spawn08,
FRAME_spawn09,
FRAME_spawn10,
FRAME_spawn11,
FRAME_spawn12,
FRAME_spawn13,
FRAME_spawn14,
FRAME_spawn15,
FRAME_spawn16,
FRAME_spawn17,
FRAME_spawn18,
FRAME_pain01,
FRAME_pain02,
FRAME_pain03,
FRAME_pain04,
FRAME_pain05,
FRAME_pain06,
FRAME_pain07,
FRAME_pain08,
FRAME_pain09,
FRAME_pain10,
FRAME_pain11,
FRAME_pain12,
FRAME_pain13,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_transa01,
FRAME_transa02,
FRAME_transa03,
FRAME_transa04,
FRAME_transa05,
FRAME_transb01,
FRAME_transb02,
FRAME_transb03,
FRAME_transb04,
FRAME_transb05,
FRAME_transc01,
FRAME_transc02,
FRAME_transc03,
FRAME_transc04,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_death23,
FRAME_death24,
FRAME_death25,
FRAME_death26,
FRAME_death27,
FRAME_death28,
FRAME_death29,
FRAME_death30,
FRAME_death31,
FRAME_kick01,
FRAME_kick02,
FRAME_kick03,
FRAME_kick04,
FRAME_kick05,
FRAME_kick06,
FRAME_kick07,
FRAME_kick08
};
constexpr float MODEL_SCALE = 2.000000f;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,137 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\xpack\models/monsters/blackwidow2
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_blackwidow3,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_spawn01,
FRAME_spawn02,
FRAME_spawn03,
FRAME_spawn04,
FRAME_spawn05,
FRAME_spawn06,
FRAME_spawn07,
FRAME_spawn08,
FRAME_spawn09,
FRAME_spawn10,
FRAME_spawn11,
FRAME_spawn12,
FRAME_spawn13,
FRAME_spawn14,
FRAME_spawn15,
FRAME_spawn16,
FRAME_spawn17,
FRAME_spawn18,
FRAME_firea01,
FRAME_firea02,
FRAME_firea03,
FRAME_firea04,
FRAME_firea05,
FRAME_firea06,
FRAME_firea07,
FRAME_fireb01,
FRAME_fireb02,
FRAME_fireb03,
FRAME_fireb04,
FRAME_fireb05,
FRAME_fireb06,
FRAME_fireb07,
FRAME_fireb08,
FRAME_fireb09,
FRAME_fireb10,
FRAME_fireb11,
FRAME_fireb12,
FRAME_tongs01,
FRAME_tongs02,
FRAME_tongs03,
FRAME_tongs04,
FRAME_tongs05,
FRAME_tongs06,
FRAME_tongs07,
FRAME_tongs08,
FRAME_pain01,
FRAME_pain02,
FRAME_pain03,
FRAME_pain04,
FRAME_pain05,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_death23,
FRAME_death24,
FRAME_death25,
FRAME_death26,
FRAME_death27,
FRAME_death28,
FRAME_death29,
FRAME_death30,
FRAME_death31,
FRAME_death32,
FRAME_death33,
FRAME_death34,
FRAME_death35,
FRAME_death36,
FRAME_death37,
FRAME_death38,
FRAME_death39,
FRAME_death40,
FRAME_death41,
FRAME_death42,
FRAME_death43,
FRAME_death44,
FRAME_dthsrh01,
FRAME_dthsrh02,
FRAME_dthsrh03,
FRAME_dthsrh04,
FRAME_dthsrh05,
FRAME_dthsrh06,
FRAME_dthsrh07,
FRAME_dthsrh08,
FRAME_dthsrh09,
FRAME_dthsrh10,
FRAME_dthsrh11,
FRAME_dthsrh12,
FRAME_dthsrh13,
FRAME_dthsrh14,
FRAME_dthsrh15,
FRAME_dthsrh16,
FRAME_dthsrh17,
FRAME_dthsrh18,
FRAME_dthsrh19,
FRAME_dthsrh20,
FRAME_dthsrh21,
FRAME_dthsrh22
};
constexpr float MODEL_SCALE = 2.000000f;

View File

@@ -0,0 +1,446 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
#include "../m_player.h"
void weapon_prox_fire(edict_t *ent)
{
vec3_t start, dir;
// Paril: kill sideways angle on grenades
// limit upwards angle so you don't fire behind you
P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 8, 0, -8 }, start, dir);
P_AddWeaponKick(ent, ent->client->v_forward * -2, { -1.f, 0.f, 0.f });
fire_prox(ent, start, dir, damage_multiplier, 600);
gi.WriteByte(svc_muzzleflash);
gi.WriteEntity(ent);
gi.WriteByte(MZ_PROX | is_silenced);
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
PlayerNoise(ent, start, PNOISE_WEAPON);
G_RemoveAmmo(ent);
}
void Weapon_ProxLauncher(edict_t *ent)
{
constexpr int pause_frames[] = { 34, 51, 59, 0 };
constexpr int fire_frames[] = { 6, 0 };
Weapon_Generic(ent, 5, 16, 59, 64, pause_frames, fire_frames, weapon_prox_fire);
}
void weapon_tesla_fire(edict_t *ent, bool held)
{
vec3_t start, dir;
// Paril: kill sideways angle on grenades
// limit upwards angle so you don't throw behind you
P_ProjectSource(ent, { max(-62.5f, ent->client->v_angle[0]), ent->client->v_angle[1], ent->client->v_angle[2] }, { 0, 0, -22 }, start, dir);
gtime_t timer = ent->client->grenade_time - level.time;
int speed = (int) (ent->health <= 0 ? GRENADE_MINSPEED : min(GRENADE_MINSPEED + (GRENADE_TIMER - timer).seconds() * ((GRENADE_MAXSPEED - GRENADE_MINSPEED) / GRENADE_TIMER.seconds()), GRENADE_MAXSPEED));
ent->client->grenade_time = 0_ms;
fire_tesla(ent, start, dir, damage_multiplier, speed);
G_RemoveAmmo(ent, 1);
}
void Weapon_Tesla(edict_t *ent)
{
constexpr int pause_frames[] = { 21, 0 };
Throw_Generic(ent, 8, 32, -1, nullptr, 1, 2, pause_frames, false, nullptr, weapon_tesla_fire, false);
}
//======================================================================
// ROGUE MODS BELOW
//======================================================================
//
// CHAINFIST
//
constexpr int32_t CHAINFIST_REACH = 24;
void weapon_chainfist_fire(edict_t *ent)
{
if (!(ent->client->buttons & BUTTON_ATTACK))
{
if (ent->client->ps.gunframe == 13 ||
ent->client->ps.gunframe == 23 ||
ent->client->ps.gunframe >= 32)
{
ent->client->ps.gunframe = 33;
return;
}
}
int damage = 7;
if (deathmatch->integer)
damage = 15;
if (is_quad)
damage *= damage_multiplier;
// set start point
vec3_t start, dir;
P_ProjectSource(ent, ent->client->v_angle, { 0, 0, -4 }, start, dir);
if (fire_player_melee(ent, start, dir, CHAINFIST_REACH, damage, 100, MOD_CHAINFIST))
{
if (ent->client->empty_click_sound < level.time)
{
ent->client->empty_click_sound = level.time + 500_ms;
gi.sound(ent, CHAN_WEAPON, gi.soundindex("weapons/sawslice.wav"), 1.f, ATTN_NORM, 0.f);
}
}
PlayerNoise(ent, start, PNOISE_WEAPON);
ent->client->ps.gunframe++;
if (ent->client->buttons & BUTTON_ATTACK)
{
if (ent->client->ps.gunframe == 12)
ent->client->ps.gunframe = 14;
else if (ent->client->ps.gunframe == 22)
ent->client->ps.gunframe = 24;
else if (ent->client->ps.gunframe >= 32)
ent->client->ps.gunframe = 7;
}
// start the animation
if (ent->client->anim_priority != ANIM_ATTACK || frandom() < 0.25f)
{
ent->client->anim_priority = ANIM_ATTACK;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crattak1 - 1;
ent->client->anim_end = FRAME_crattak9;
}
else
{
ent->s.frame = FRAME_attack1 - 1;
ent->client->anim_end = FRAME_attack8;
}
ent->client->anim_time = 0_ms;
}
}
// this spits out some smoke from the motor. it's a two-stroke, you know.
void chainfist_smoke(edict_t *ent)
{
vec3_t tempVec, dir;
P_ProjectSource(ent, ent->client->v_angle, { 8, 8, -4 }, tempVec, dir);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_CHAINFIST_SMOKE);
gi.WritePosition(tempVec);
gi.unicast(ent, 0);
}
void Weapon_ChainFist(edict_t *ent)
{
constexpr int pause_frames[] = { 0 };
Weapon_Repeating(ent, 4, 32, 57, 60, pause_frames, weapon_chainfist_fire);
// smoke on idle sequence
if (ent->client->ps.gunframe == 42 && irandom(8))
{
if ((ent->client->pers.hand != CENTER_HANDED) && frandom() < 0.4f)
chainfist_smoke(ent);
}
else if (ent->client->ps.gunframe == 51 && irandom(8))
{
if ((ent->client->pers.hand != CENTER_HANDED) && frandom() < 0.4f)
chainfist_smoke(ent);
}
// set the appropriate weapon sound.
if (ent->client->weaponstate == WEAPON_FIRING)
ent->client->weapon_sound = gi.soundindex("weapons/sawhit.wav");
else if (ent->client->weaponstate == WEAPON_DROPPING)
ent->client->weapon_sound = 0;
else if (ent->client->pers.weapon->id == IT_WEAPON_CHAINFIST)
ent->client->weapon_sound = gi.soundindex("weapons/sawidle.wav");
}
//
// Disintegrator
//
void weapon_tracker_fire(edict_t *self)
{
vec3_t end;
edict_t *enemy;
trace_t tr;
int damage;
vec3_t mins, maxs;
// PMM - felt a little high at 25
if (deathmatch->integer)
damage = 45;
else
damage = 135;
if (is_quad)
damage *= damage_multiplier; // pgm
mins = { -16, -16, -16 };
maxs = { 16, 16, 16 };
vec3_t start, dir;
P_ProjectSource(self, self->client->v_angle, { 24, 8, -8 }, start, dir);
end = start + (dir * 8192);
enemy = nullptr;
// PMM - doing two traces .. one point and one box.
contents_t mask = MASK_PROJECTILE;
// [Paril-KEX]
if (!G_ShouldPlayersCollide(true))
mask &= ~CONTENTS_PLAYER;
G_LagCompensate(self, start, dir);
tr = gi.traceline(start, end, self, mask);
G_UnLagCompensate();
if (tr.ent != world)
{
if ((tr.ent->svflags & SVF_MONSTER) || tr.ent->client || (tr.ent->flags & FL_DAMAGEABLE))
{
if (tr.ent->health > 0)
enemy = tr.ent;
}
}
else
{
tr = gi.trace(start, mins, maxs, end, self, mask);
if (tr.ent != world)
{
if ((tr.ent->svflags & SVF_MONSTER) || tr.ent->client || (tr.ent->flags & FL_DAMAGEABLE))
{
if (tr.ent->health > 0)
enemy = tr.ent;
}
}
}
P_AddWeaponKick(self, self->client->v_forward * -2, { -1.f, 0.f, 0.f });
fire_tracker(self, start, dir, damage, 1000, enemy);
// send muzzle flash
gi.WriteByte(svc_muzzleflash);
gi.WriteEntity(self);
gi.WriteByte(MZ_TRACKER | is_silenced);
gi.multicast(self->s.origin, MULTICAST_PVS, false);
PlayerNoise(self, start, PNOISE_WEAPON);
G_RemoveAmmo(self);
}
void Weapon_Disintegrator(edict_t *ent)
{
constexpr int pause_frames[] = { 14, 19, 23, 0 };
constexpr int fire_frames[] = { 5, 0 };
Weapon_Generic(ent, 4, 9, 29, 34, pause_frames, fire_frames, weapon_tracker_fire);
}
/*
======================================================================
ETF RIFLE
======================================================================
*/
void weapon_etf_rifle_fire(edict_t *ent)
{
int damage;
int kick = 3;
int i;
vec3_t offset;
if (deathmatch->integer)
damage = 10;
else
damage = 10;
if (!(ent->client->buttons & BUTTON_ATTACK))
{
ent->client->ps.gunframe = 8;
return;
}
if (ent->client->ps.gunframe == 6)
ent->client->ps.gunframe = 7;
else
ent->client->ps.gunframe = 6;
// PGM - adjusted to use the quantity entry in the weapon structure.
if (ent->client->pers.inventory[ent->client->pers.weapon->ammo] < ent->client->pers.weapon->quantity)
{
ent->client->ps.gunframe = 8;
NoAmmoWeaponChange(ent, true);
return;
}
if (is_quad)
{
damage *= damage_multiplier;
kick *= damage_multiplier;
}
vec3_t kick_origin {}, kick_angles {};
for (i = 0; i < 3; i++)
{
kick_origin[i] = crandom() * 0.85f;
kick_angles[i] = crandom() * 0.85f;
}
P_AddWeaponKick(ent, kick_origin, kick_angles);
// get start / end positions
if (ent->client->ps.gunframe == 6)
offset = { 15, 8, -8 };
else
offset = { 15, 6, -8 };
vec3_t start, dir;
P_ProjectSource(ent, ent->client->v_angle + kick_angles, offset, start, dir);
fire_flechette(ent, start, dir, damage, 1150, kick);
Weapon_PowerupSound(ent);
// send muzzle flash
gi.WriteByte(svc_muzzleflash);
gi.WriteEntity(ent);
gi.WriteByte((ent->client->ps.gunframe == 6 ? MZ_ETF_RIFLE : MZ_ETF_RIFLE_2) | is_silenced);
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
PlayerNoise(ent, start, PNOISE_WEAPON);
G_RemoveAmmo(ent);
ent->client->anim_priority = ANIM_ATTACK;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f);
ent->client->anim_end = FRAME_crattak9;
}
else
{
ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f);
ent->client->anim_end = FRAME_attack8;
}
ent->client->anim_time = 0_ms;
}
void Weapon_ETF_Rifle(edict_t *ent)
{
constexpr int pause_frames[] = { 18, 28, 0 };
Weapon_Repeating(ent, 4, 7, 37, 41, pause_frames, weapon_etf_rifle_fire);
}
constexpr int32_t HEATBEAM_DM_DMG = 15;
constexpr int32_t HEATBEAM_SP_DMG = 15;
void Heatbeam_Fire(edict_t *ent)
{
bool firing = (ent->client->buttons & BUTTON_ATTACK);
bool has_ammo = ent->client->pers.inventory[ent->client->pers.weapon->ammo] >= ent->client->pers.weapon->quantity;
if (!firing || !has_ammo)
{
ent->client->ps.gunframe = 13;
ent->client->weapon_sound = 0;
ent->client->ps.gunskin = 0;
if (firing && !has_ammo)
NoAmmoWeaponChange(ent, true);
return;
}
// start on frame 8
if (ent->client->ps.gunframe > 12)
ent->client->ps.gunframe = 8;
else
ent->client->ps.gunframe++;
if (ent->client->ps.gunframe == 12)
ent->client->ps.gunframe = 8;
// play weapon sound for firing
ent->client->weapon_sound = gi.soundindex("weapons/bfg__l1a.wav");
ent->client->ps.gunskin = 1;
int damage;
int kick;
// for comparison, the hyperblaster is 15/20
// jim requested more damage, so try 15/15 --- PGM 07/23/98
if (deathmatch->integer)
damage = HEATBEAM_DM_DMG;
else
damage = HEATBEAM_SP_DMG;
if (deathmatch->integer) // really knock 'em around in deathmatch
kick = 75;
else
kick = 30;
if (is_quad)
{
damage *= damage_multiplier;
kick *= damage_multiplier;
}
ent->client->kick.time = 0_ms;
// This offset is the "view" offset for the beam start (used by trace)
vec3_t start, dir;
P_ProjectSource(ent, ent->client->v_angle, { 7, 2, -3 }, start, dir);
// This offset is the entity offset
G_LagCompensate(ent, start, dir);
fire_heatbeam(ent, start, dir, { 2, 7, -3 }, damage, kick, false);
G_UnLagCompensate();
Weapon_PowerupSound(ent);
// send muzzle flash
gi.WriteByte(svc_muzzleflash);
gi.WriteEntity(ent);
gi.WriteByte(MZ_HEATBEAM | is_silenced);
gi.multicast(ent->s.origin, MULTICAST_PVS, false);
PlayerNoise(ent, start, PNOISE_WEAPON);
G_RemoveAmmo(ent);
ent->client->anim_priority = ANIM_ATTACK;
if (ent->client->ps.pmove.pm_flags & PMF_DUCKED)
{
ent->s.frame = FRAME_crattak1 - (int) (frandom() + 0.25f);
ent->client->anim_end = FRAME_crattak9;
}
else
{
ent->s.frame = FRAME_attack1 - (int) (frandom() + 0.25f);
ent->client->anim_end = FRAME_attack8;
}
ent->client->anim_time = 0_ms;
}
void Weapon_Heatbeam(edict_t *ent)
{
constexpr int pause_frames[] = { 35, 0 };
Weapon_Repeating(ent, 8, 12, 42, 47, pause_frames, Heatbeam_Fire);
}

View File

@@ -0,0 +1,687 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// dm_ball.c
// pmack
// june 98
#include "../g_local.h"
// defines
constexpr spawnflags_t SPAWNFLAG_DBALL_GOAL_TEAM1 = 0x0001_spawnflag;
// unused; assumed by not being team1
// constexpr uint32_t SPAWNFLAG_DBALL_GOAL_TEAM2 = 0x0002;
// globals
edict_t *dball_ball_entity = nullptr;
int dball_ball_startpt_count;
int dball_team1_goalscore;
int dball_team2_goalscore;
cvar_t *dball_team1_skin;
cvar_t *dball_team2_skin;
cvar_t *goallimit;
// prototypes
void EndDMLevel();
float PlayersRangeFromSpot(edict_t *spot);
void DBall_BallDie(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod);
void DBall_BallRespawn(edict_t *self);
// **************************
// Game rules
// **************************
int DBall_CheckDMRules()
{
if (goallimit->integer)
{
if (dball_team1_goalscore >= goallimit->integer)
gi.LocBroadcast_Print(PRINT_HIGH, "Team 1 Wins.\n");
else if (dball_team2_goalscore >= goallimit->integer)
gi.LocBroadcast_Print(PRINT_HIGH, "Team 2 Wins.\n");
else
return 0;
EndDMLevel();
return 1;
}
return 0;
}
//==================
//==================
void DBall_ClientBegin(edict_t *ent)
{
#if 0
uint32_t team1, team2, unassigned;
edict_t *other;
char *p;
static char value[512];
team1 = 0;
team2 = 0;
unassigned = 0;
for (uint32_t j = 1; j <= game.maxclients; j++)
{
other = &g_edicts[j];
if (!other->inuse)
continue;
if (!other->client)
continue;
if (other == ent) // don't count the new player
continue;
Q_strlcpy(value, Info_ValueForKey(other->client->pers.userinfo, "skin"), sizeof(value));
p = strchr(value, '/');
if (p)
{
if (!strcmp(dball_team1_skin->string, value))
team1++;
else if (!strcmp(dball_team2_skin->string, value))
team2++;
else
unassigned++;
}
else
unassigned++;
}
if (team1 > team2)
{
gi.Com_Print("assigned to team 2\n");
Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team2_skin->string);
}
else
{
gi.Com_Print("assigned to team 1\n");
Info_SetValueForKey(ent->client->pers.userinfo, "skin", dball_team1_skin->string);
}
ClientUserinfoChanged(ent, ent->client->pers.userinfo);
if (unassigned)
gi.Com_PrintFmt("{} unassigned players present!\n", unassigned);
#endif
}
//==================
//==================
bool DBall_SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_spawn)
{
#if 0
edict_t *bestspot;
float bestdistance, bestplayerdistance;
edict_t *spot;
const char *spottype;
static char skin[512];
Q_strlcpy(skin, Info_ValueForKey(ent->client->pers.userinfo, "skin"), sizeof(skin));
if (!strcmp(dball_team1_skin->string, skin))
spottype = "dm_dball_team1_start";
else if (!strcmp(dball_team2_skin->string, skin))
spottype = "dm_dball_team2_start";
else
spottype = "info_player_deathmatch";
spot = nullptr;
bestspot = nullptr;
bestdistance = 0;
while ((spot = G_FindByString<&edict_t::classname>(spot, spottype)) != nullptr)
{
bestplayerdistance = PlayersRangeFromSpot(spot);
if (bestplayerdistance > bestdistance)
{
bestspot = spot;
bestdistance = bestplayerdistance;
}
}
if (bestspot)
{
origin = bestspot->s.origin;
origin[2] += 9;
angles = bestspot->s.angles;
return true;
}
// if we didn't find an appropriate spawnpoint, just
// call the standard one.
#endif
bool lm = false;
return SelectSpawnPoint(ent, origin, angles, force_spawn, lm);
}
//==================
//==================
void DBall_GameInit()
{
// we don't want a minimum speed for friction to take effect.
// this will allow any knockback to move stuff.
gi.cvar_forceset("sv_stopspeed", "0");
dball_team1_goalscore = 0;
dball_team2_goalscore = 0;
gi.cvar_forceset(g_no_mines->name, "1");
gi.cvar_forceset(g_no_nukes->name, "1");
gi.cvar_forceset(g_dm_no_stack_double->name, "1");
gi.cvar_forceset(g_friendly_fire->name, "0");
//gi.cvar_forceset(g_no_mines->name, "1"); note: skin teams gone...
dball_team1_skin = gi.cvar("dball_team1_skin", "male/ctf_r", CVAR_NOFLAGS);
dball_team2_skin = gi.cvar("dball_team2_skin", "male/ctf_b", CVAR_NOFLAGS);
goallimit = gi.cvar("goallimit", "0", CVAR_NOFLAGS);
}
//==================
//==================
void DBall_PostInitSetup()
{
edict_t *e;
e = nullptr;
// turn teleporter destinations nonsolid.
while ((e = G_FindByString<&edict_t::classname>(e, "misc_teleporter_dest")))
{
e->solid = SOLID_NOT;
gi.linkentity(e);
}
// count the ball start points
dball_ball_startpt_count = 0;
e = nullptr;
while ((e = G_FindByString<&edict_t::classname>(e, "dm_dball_ball_start")))
{
dball_ball_startpt_count++;
}
if (dball_ball_startpt_count == 0)
gi.Com_Print("No Deathball start points!\n");
}
//==================
// DBall_ChangeDamage - half damage between players. full if it involves
// the ball entity
//==================
int DBall_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod)
{
// cut player -> ball damage to 1
if (targ == dball_ball_entity)
return 1;
// damage player -> player is halved
if (attacker != dball_ball_entity)
return damage / 2;
return damage;
}
//==================
//==================
int DBall_ChangeKnockback(edict_t *targ, edict_t *attacker, int knockback, mod_t mod)
{
if (targ != dball_ball_entity)
return knockback;
if (knockback < 1)
{
// FIXME - these don't account for quad/double
if (mod.id == MOD_ROCKET) // rocket
knockback = 70;
else if (mod.id == MOD_BFG_EFFECT) // bfg
knockback = 90;
else
gi.Com_PrintFmt("zero knockback, mod {}\n", (int32_t) mod.id);
}
else
{
// FIXME - change this to an array?
switch (mod.id)
{
case MOD_BLASTER:
knockback *= 3;
break;
case MOD_SHOTGUN:
knockback = (knockback * 3) / 8;
break;
case MOD_SSHOTGUN:
knockback = knockback / 3;
break;
case MOD_MACHINEGUN:
knockback = (knockback * 3) / 2;
break;
case MOD_HYPERBLASTER:
knockback *= 4;
break;
case MOD_GRENADE:
case MOD_HANDGRENADE:
case MOD_PROX:
case MOD_G_SPLASH:
case MOD_HG_SPLASH:
case MOD_HELD_GRENADE:
case MOD_TRACKER:
case MOD_DISINTEGRATOR:
knockback /= 2;
break;
case MOD_R_SPLASH:
knockback = (knockback * 3) / 2;
break;
case MOD_RAILGUN:
case MOD_HEATBEAM:
knockback /= 3;
break;
default:
break;
}
}
// gi.dprintf("mod: %d knockback: %d\n", mod, knockback);
return knockback;
}
// **************************
// Goals
// **************************
TOUCH(DBall_GoalTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
#if 0
static char value[512];
int team_score;
int scorechange;
char *p;
edict_t *ent;
if (other != dball_ball_entity)
return;
self->health = self->max_health;
// determine which team scored, and bump the team score
if (self->spawnflags.has(SPAWNFLAG_DBALL_GOAL_TEAM1))
{
dball_team1_goalscore += (int) self->wait;
team_score = 1;
}
else
{
dball_team2_goalscore += (int) self->wait;
team_score = 2;
}
// bump the score for everyone on the correct team.
for (uint32_t j = 1; j <= game.maxclients; j++)
{
ent = &g_edicts[j];
if (!ent->inuse)
continue;
if (!ent->client)
continue;
if (ent == other->enemy)
scorechange = (int) self->wait + 5;
else
scorechange = (int) self->wait;
Q_strlcpy(value, Info_ValueForKey(ent->client->pers.userinfo, "skin"), sizeof(value));
p = strchr(value, '/');
if (p)
{
if (!strcmp(dball_team1_skin->string, value))
{
if (team_score == 1)
ent->client->resp.score += scorechange;
else if (other->enemy == ent)
ent->client->resp.score -= scorechange;
}
else if (!strcmp(dball_team2_skin->string, value))
{
if (team_score == 2)
ent->client->resp.score += scorechange;
else if (other->enemy == ent)
ent->client->resp.score -= scorechange;
}
else
gi.Com_Print("unassigned player!!!!\n");
}
}
if (other->enemy)
gi.Com_PrintFmt("score for team {} by {}\n", team_score, other->enemy->client->pers.netname);
else
gi.Com_PrintFmt("score for team {} by someone\n", team_score);
DBall_BallDie(other, other->enemy, other->enemy, 0, vec3_origin, MOD_SUICIDE);
G_UseTargets(self, other);
#endif
}
// **************************
// Ball
// **************************
edict_t *PickBallStart(edict_t *ent)
{
int which, current;
edict_t *e;
which = irandom(dball_ball_startpt_count);
e = nullptr;
current = 0;
while ((e = G_FindByString<&edict_t::classname>(e, "dm_dball_ball_start")))
{
current++;
if (current == which)
return e;
}
if (current == 0)
gi.Com_Print("No ball start points found!\n");
return G_FindByString<&edict_t::classname>(nullptr, "dm_dball_ball_start");
}
//==================
// DBall_BallTouch - if the ball hit another player, hurt them
//==================
TOUCH(DBall_BallTouch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
vec3_t dir;
float dot;
float speed;
if (other->takedamage == false)
return;
// hit a player
if (other->client)
{
if (ent->velocity[0] || ent->velocity[1] || ent->velocity[2])
{
speed = ent->velocity.length();
dir = ent->s.origin - other->s.origin;
dot = dir.dot(ent->velocity);
if (dot > 0.7f)
{
T_Damage(other, ent, ent, vec3_origin, ent->s.origin, vec3_origin,
(int) (speed / 10), (int) (speed / 10), DAMAGE_NONE, MOD_DBALL_CRUSH);
}
}
}
}
//==================
// DBall_BallPain
//==================
PAIN(DBall_BallPain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
self->enemy = other;
self->health = self->max_health;
// if(other->classname)
// gi.Com_PrintFmt("hurt by {} -- {}\n", other->classname, self->health);
}
DIE(DBall_BallDie) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
// do the splash effect
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_DBALL_GOAL);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PVS, false);
self->s.angles = {};
self->velocity = {};
self->avelocity = {};
// make it invisible and desolid until respawn time
self->solid = SOLID_NOT;
// self->s.modelindex = 0;
self->think = DBall_BallRespawn;
self->nextthink = level.time + 2_sec;
gi.linkentity(self);
}
THINK(DBall_BallRespawn) (edict_t *self) -> void
{
edict_t *start;
// do the splash effect
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_DBALL_GOAL);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PVS, false);
// move the ball and stop it
start = PickBallStart(self);
if (start)
{
self->s.origin = start->s.origin;
self->s.old_origin = start->s.origin;
}
self->s.angles = {};
self->velocity = {};
self->avelocity = {};
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2");
self->s.event = EV_PLAYER_TELEPORT;
self->groundentity = nullptr;
gi.linkentity(self);
// kill anything at the destination
KillBox(self, false);
}
// ************************
// SPEED CHANGES
// ************************
constexpr spawnflags_t SPAWNFLAG_DBALL_SPEED_ONEWAY = 1_spawnflag;
TOUCH(DBall_SpeedTouch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
float dot;
vec3_t vel;
if (other != dball_ball_entity)
return;
if (self->timestamp >= level.time)
return;
if (other->velocity.length() < 1)
return;
if (self->spawnflags.has(SPAWNFLAG_DBALL_SPEED_ONEWAY))
{
vel = other->velocity;
vel.normalize();
dot = vel.dot(self->movedir);
if (dot < 0.8f)
return;
}
self->timestamp = level.time + gtime_t::from_sec(self->delay);
other->velocity *= self->speed;
}
// ************************
// SPAWN FUNCTIONS
// ************************
/*QUAKED dm_dball_ball (1 .5 .5) (-48 -48 -48) (48 48 48) ONEWAY
Deathball Ball
*/
void SP_dm_dball_ball(edict_t *self)
{
if (!deathmatch->integer)
{
G_FreeEdict(self);
return;
}
if (gamerules->integer != RDM_DEATHBALL)
{
G_FreeEdict(self);
return;
}
dball_ball_entity = self;
// dball_ball_startpt = self->s.origin;
self->s.modelindex = gi.modelindex("models/objects/dball/tris.md2");
self->mins = { -32, -32, -32 };
self->maxs = { 32, 32, 32 };
self->solid = SOLID_BBOX;
self->movetype = MOVETYPE_NEWTOSS;
self->clipmask = MASK_MONSTERSOLID;
self->takedamage = true;
self->mass = 50;
self->health = 50000;
self->max_health = 50000;
self->pain = DBall_BallPain;
self->die = DBall_BallDie;
self->touch = DBall_BallTouch;
gi.linkentity(self);
}
/*QUAKED dm_dball_team1_start (1 .5 .5) (-16 -16 -24) (16 16 32)
Deathball team 1 start point
*/
void SP_dm_dball_team1_start(edict_t *self)
{
if (!deathmatch->integer)
{
G_FreeEdict(self);
return;
}
if (gamerules->integer != RDM_DEATHBALL)
{
G_FreeEdict(self);
return;
}
}
/*QUAKED dm_dball_team2_start (1 .5 .5) (-16 -16 -24) (16 16 32)
Deathball team 2 start point
*/
void SP_dm_dball_team2_start(edict_t *self)
{
if (!deathmatch->integer)
{
G_FreeEdict(self);
return;
}
if (gamerules->integer != RDM_DEATHBALL)
{
G_FreeEdict(self);
return;
}
}
/*QUAKED dm_dball_ball_start (1 .5 .5) (-48 -48 -48) (48 48 48)
Deathball ball start point
*/
void SP_dm_dball_ball_start(edict_t *self)
{
if (!deathmatch->integer)
{
G_FreeEdict(self);
return;
}
if (gamerules->integer != RDM_DEATHBALL)
{
G_FreeEdict(self);
return;
}
}
/*QUAKED dm_dball_speed_change (1 .5 .5) ? ONEWAY
Deathball ball speed changing field.
speed: multiplier for speed (.5 = half, 2 = double, etc) (default = double)
angle: used with ONEWAY so speed change is only one way.
delay: time between speed changes (default: 0.2 sec)
*/
void SP_dm_dball_speed_change(edict_t *self)
{
if (!deathmatch->integer)
{
G_FreeEdict(self);
return;
}
if (gamerules->integer != RDM_DEATHBALL)
{
G_FreeEdict(self);
return;
}
if (!self->speed)
self->speed = 2;
if (!self->delay)
self->delay = 0.2f;
self->touch = DBall_SpeedTouch;
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
if (self->s.angles)
G_SetMovedir(self->s.angles, self->movedir);
else
self->movedir = { 1, 0, 0 };
gi.setmodel(self, self->model);
gi.linkentity(self);
}
/*QUAKED dm_dball_goal (1 .5 .5) ? TEAM1 TEAM2
Deathball goal
Team1/Team2 - beneficiary of this goal. when the ball enters this goal, the beneficiary team will score.
"wait": score to be given for this goal (default 10) player gets score+5.
*/
void SP_dm_dball_goal(edict_t *self)
{
if (!deathmatch->integer)
{
G_FreeEdict(self);
return;
}
if (gamerules->integer != RDM_DEATHBALL)
{
G_FreeEdict(self);
return;
}
if (!self->wait)
self->wait = 10;
self->touch = DBall_GoalTouch;
self->solid = SOLID_TRIGGER;
self->movetype = MOVETYPE_NONE;
self->svflags |= SVF_NOCLIENT;
if (self->s.angles)
G_SetMovedir(self->s.angles, self->movedir);
gi.setmodel(self, self->model);
gi.linkentity(self);
}

View File

@@ -0,0 +1,297 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// dm_tag
// pmack
// june 1998
#include "../g_local.h"
void SP_dm_tag_token(edict_t *self);
// ***********************
// Tag Specific Stuff
// ***********************
edict_t *tag_token;
edict_t *tag_owner;
int tag_count;
//=================
//=================
void Tag_PlayerDeath(edict_t *targ, edict_t *inflictor, edict_t *attacker)
{
if (tag_token && targ && (targ == tag_owner))
{
Tag_DropToken(targ, GetItemByIndex(IT_ITEM_TAG_TOKEN));
tag_owner = nullptr;
tag_count = 0;
}
}
//=================
//=================
void Tag_KillItBonus(edict_t *self)
{
edict_t *armor;
// if the player is hurt, boost them up to max.
if (self->health < self->max_health)
{
self->health += 200;
if (self->health > self->max_health)
self->health = self->max_health;
}
// give the player a body armor
armor = G_Spawn();
armor->spawnflags |= SPAWNFLAG_ITEM_DROPPED;
armor->item = GetItemByIndex(IT_ARMOR_BODY);
Touch_Item(armor, self, null_trace, true);
if (armor->inuse)
G_FreeEdict(armor);
}
//=================
//=================
void Tag_PlayerDisconnect(edict_t *self)
{
if (tag_token && self && (self == tag_owner))
{
Tag_DropToken(self, GetItemByIndex(IT_ITEM_TAG_TOKEN));
tag_owner = nullptr;
tag_count = 0;
}
}
//=================
//=================
void Tag_Score(edict_t *attacker, edict_t *victim, int scoreChange, const mod_t &mod)
{
gitem_t *quad;
if (tag_token && tag_owner)
{
// owner killed somone else
if ((scoreChange > 0) && tag_owner == attacker)
{
scoreChange = 3;
tag_count++;
if (tag_count == 5)
{
quad = GetItemByIndex(IT_ITEM_QUAD);
attacker->client->pers.inventory[IT_ITEM_QUAD]++;
quad->use(attacker, quad);
tag_count = 0;
}
}
// owner got killed. 5 points and switch owners
else if (tag_owner == victim && tag_owner != attacker)
{
scoreChange = 5;
if ((mod.id == MOD_HUNTER_SPHERE) || (mod.id == MOD_DOPPLE_EXPLODE) ||
(mod.id == MOD_DOPPLE_VENGEANCE) || (mod.id == MOD_DOPPLE_HUNTER) ||
(attacker->health <= 0))
{
Tag_DropToken(tag_owner, GetItemByIndex(IT_ITEM_TAG_TOKEN));
tag_owner = nullptr;
tag_count = 0;
}
else
{
Tag_KillItBonus(attacker);
tag_owner = attacker;
tag_count = 0;
}
}
}
attacker->client->resp.score += scoreChange;
}
//=================
//=================
bool Tag_PickupToken(edict_t *ent, edict_t *other)
{
if (gamerules->integer != RDM_TAG)
{
return false;
}
// sanity checking is good.
if (tag_token != ent)
tag_token = ent;
other->client->pers.inventory[ent->item->id]++;
tag_owner = other;
tag_count = 0;
Tag_KillItBonus(other);
return true;
}
//=================
//=================
THINK(Tag_Respawn) (edict_t *ent) -> void
{
edict_t *spot;
spot = SelectDeathmatchSpawnPoint(true, false, true).spot;
if (spot == nullptr)
{
ent->nextthink = level.time + 1_sec;
return;
}
ent->s.origin = spot->s.origin;
gi.linkentity(ent);
}
//=================
//=================
THINK(Tag_MakeTouchable) (edict_t *ent) -> void
{
ent->touch = Touch_Item;
tag_token->think = Tag_Respawn;
// check here to see if it's in lava or slime. if so, do a respawn sooner
if (gi.pointcontents(ent->s.origin) & (CONTENTS_LAVA | CONTENTS_SLIME))
tag_token->nextthink = level.time + 3_sec;
else
tag_token->nextthink = level.time + 30_sec;
}
//=================
//=================
void Tag_DropToken(edict_t *ent, gitem_t *item)
{
trace_t trace;
vec3_t forward, right;
vec3_t offset;
// reset the score count for next player
tag_count = 0;
tag_owner = nullptr;
tag_token = G_Spawn();
tag_token->classname = item->classname;
tag_token->item = item;
tag_token->spawnflags = SPAWNFLAG_ITEM_DROPPED;
tag_token->s.effects = EF_ROTATE | EF_TAGTRAIL;
tag_token->s.renderfx = RF_GLOW | RF_NO_LOD;
tag_token->mins = { -15, -15, -15 };
tag_token->maxs = { 15, 15, 15 };
gi.setmodel(tag_token, tag_token->item->world_model);
tag_token->solid = SOLID_TRIGGER;
tag_token->movetype = MOVETYPE_TOSS;
tag_token->touch = nullptr;
tag_token->owner = ent;
AngleVectors(ent->client->v_angle, forward, right, nullptr);
offset = { 24, 0, -16 };
tag_token->s.origin = G_ProjectSource(ent->s.origin, offset, forward, right);
trace = gi.trace(ent->s.origin, tag_token->mins, tag_token->maxs,
tag_token->s.origin, ent, CONTENTS_SOLID);
tag_token->s.origin = trace.endpos;
tag_token->velocity = forward * 100;
tag_token->velocity[2] = 300;
tag_token->think = Tag_MakeTouchable;
tag_token->nextthink = level.time + 1_sec;
gi.linkentity(tag_token);
// tag_token = Drop_Item (ent, item);
ent->client->pers.inventory[item->id]--;
}
//=================
//=================
void Tag_PlayerEffects(edict_t *ent)
{
if (ent == tag_owner)
ent->s.effects |= EF_TAGTRAIL;
}
//=================
//=================
void Tag_DogTag(edict_t *ent, edict_t *killer, const char **pic)
{
if (ent == tag_owner)
(*pic) = "tag3";
}
//=================
// Tag_ChangeDamage - damage done that does not involve the tag owner
// is at 75% original to encourage folks to go after the tag owner.
//=================
int Tag_ChangeDamage(edict_t *targ, edict_t *attacker, int damage, mod_t mod)
{
if ((targ != tag_owner) && (attacker != tag_owner))
return (damage * 3 / 4);
return damage;
}
//=================
//=================
void Tag_GameInit()
{
tag_token = nullptr;
tag_owner = nullptr;
tag_count = 0;
}
//=================
//=================
void Tag_PostInitSetup()
{
edict_t *e;
vec3_t origin, angles;
// automatic spawning of tag token if one is not present on map.
e = G_FindByString<&edict_t::classname>(nullptr, "dm_tag_token");
if (e == nullptr)
{
e = G_Spawn();
e->classname = "dm_tag_token";
bool lm = false;
SelectSpawnPoint(e, origin, angles, true, lm);
e->s.origin = origin;
e->s.old_origin = origin;
e->s.angles = angles;
SP_dm_tag_token(e);
}
}
/*QUAKED dm_tag_token (.3 .3 1) (-16 -16 -16) (16 16 16)
The tag token for deathmatch tag games.
*/
void SP_dm_tag_token(edict_t *self)
{
if (!deathmatch->integer)
{
G_FreeEdict(self);
return;
}
if (gamerules->integer != RDM_TAG)
{
G_FreeEdict(self);
return;
}
// store the tag token edict pointer for later use.
tag_token = self;
tag_count = 0;
self->classname = "dm_tag_token";
self->model = "models/items/tagtoken/tris.md2";
self->count = 1;
SpawnItem(self, GetItemByIndex(IT_ITEM_TAG_TOKEN));
}