mirror of
https://github.com/id-Software/quake2-rerelease-dll.git
synced 2026-03-19 16:39:46 +01:00
Initial commit
This commit is contained in:
146
rerelease/rogue/g_rogue_combat.cpp
Normal file
146
rerelease/rogue/g_rogue_combat.cpp
Normal 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
|
||||
// ********************
|
||||
430
rerelease/rogue/g_rogue_func.cpp
Normal file
430
rerelease/rogue/g_rogue_func.cpp
Normal 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");
|
||||
}
|
||||
228
rerelease/rogue/g_rogue_items.cpp
Normal file
228
rerelease/rogue/g_rogue_items.cpp
Normal 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;
|
||||
}
|
||||
27
rerelease/rogue/g_rogue_misc.cpp
Normal file
27
rerelease/rogue/g_rogue_misc.cpp
Normal 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
|
||||
//======================
|
||||
109
rerelease/rogue/g_rogue_monster.cpp
Normal file
109
rerelease/rogue/g_rogue_monster.cpp
Normal 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
|
||||
1613
rerelease/rogue/g_rogue_newai.cpp
Normal file
1613
rerelease/rogue/g_rogue_newai.cpp
Normal file
File diff suppressed because it is too large
Load Diff
360
rerelease/rogue/g_rogue_newdm.cpp
Normal file
360
rerelease/rogue/g_rogue_newdm.cpp
Normal 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();
|
||||
}
|
||||
325
rerelease/rogue/g_rogue_newfnc.cpp
Normal file
325
rerelease/rogue/g_rogue_newfnc.cpp
Normal 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);
|
||||
}
|
||||
324
rerelease/rogue/g_rogue_newtarg.cpp
Normal file
324
rerelease/rogue/g_rogue_newtarg.cpp
Normal 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);
|
||||
}
|
||||
189
rerelease/rogue/g_rogue_newtrig.cpp
Normal file
189
rerelease/rogue/g_rogue_newtrig.cpp
Normal 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);
|
||||
}
|
||||
1644
rerelease/rogue/g_rogue_newweap.cpp
Normal file
1644
rerelease/rogue/g_rogue_newweap.cpp
Normal file
File diff suppressed because it is too large
Load Diff
121
rerelease/rogue/g_rogue_phys.cpp
Normal file
121
rerelease/rogue/g_rogue_phys.cpp
Normal 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
|
||||
//============
|
||||
367
rerelease/rogue/g_rogue_spawn.cpp
Normal file
367
rerelease/rogue/g_rogue_spawn.cpp
Normal 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);
|
||||
}
|
||||
710
rerelease/rogue/g_rogue_sphere.cpp
Normal file
710
rerelease/rogue/g_rogue_sphere.cpp
Normal 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);
|
||||
}
|
||||
47
rerelease/rogue/g_rogue_utils.cpp
Normal file
47
rerelease/rogue/g_rogue_utils.cpp
Normal 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;
|
||||
}
|
||||
1238
rerelease/rogue/m_rogue_carrier.cpp
Normal file
1238
rerelease/rogue/m_rogue_carrier.cpp
Normal file
File diff suppressed because it is too large
Load Diff
88
rerelease/rogue/m_rogue_carrier.h
Normal file
88
rerelease/rogue/m_rogue_carrier.h
Normal 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;
|
||||
1041
rerelease/rogue/m_rogue_stalker.cpp
Normal file
1041
rerelease/rogue/m_rogue_stalker.cpp
Normal file
File diff suppressed because it is too large
Load Diff
104
rerelease/rogue/m_rogue_stalker.h
Normal file
104
rerelease/rogue/m_rogue_stalker.h
Normal 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;
|
||||
1083
rerelease/rogue/m_rogue_turret.cpp
Normal file
1083
rerelease/rogue/m_rogue_turret.cpp
Normal file
File diff suppressed because it is too large
Load Diff
27
rerelease/rogue/m_rogue_turret.h
Normal file
27
rerelease/rogue/m_rogue_turret.h
Normal 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;
|
||||
1390
rerelease/rogue/m_rogue_widow.cpp
Normal file
1390
rerelease/rogue/m_rogue_widow.cpp
Normal file
File diff suppressed because it is too large
Load Diff
180
rerelease/rogue/m_rogue_widow.h
Normal file
180
rerelease/rogue/m_rogue_widow.h
Normal 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;
|
||||
1635
rerelease/rogue/m_rogue_widow2.cpp
Normal file
1635
rerelease/rogue/m_rogue_widow2.cpp
Normal file
File diff suppressed because it is too large
Load Diff
137
rerelease/rogue/m_rogue_widow2.h
Normal file
137
rerelease/rogue/m_rogue_widow2.h
Normal 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;
|
||||
446
rerelease/rogue/p_rogue_weapon.cpp
Normal file
446
rerelease/rogue/p_rogue_weapon.cpp
Normal 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);
|
||||
}
|
||||
687
rerelease/rogue/rogue_dm_ball.cpp
Normal file
687
rerelease/rogue/rogue_dm_ball.cpp
Normal 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);
|
||||
}
|
||||
297
rerelease/rogue/rogue_dm_tag.cpp
Normal file
297
rerelease/rogue/rogue_dm_tag.cpp
Normal 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));
|
||||
}
|
||||
Reference in New Issue
Block a user