Update 1 changes

This commit is contained in:
Mike Rubits 2023-10-03 14:43:06 -04:00
parent 60f29560f3
commit 8dc1fc9794
67 changed files with 1806 additions and 1479 deletions

View file

@ -965,6 +965,10 @@ Called by the bot subsystem to use an item.
Fetch an item ID by a classname; for the bot subsystem.
### Edict_ForceLookAtPoint
Force the player to look at the given point - used for the nav editor.
### Entity_IsVisibleToPlayer
This function is for item instancing; the rerelease of Quake II supports instanced items, which will display only for the players who haven't picked it up yet. For online players, this simply removes the item if you've gotten it, but for split screen players it will show a ghost where the entity was on players that have already picked it up.

View file

@ -254,7 +254,8 @@ enum player_stat_t
STAT_SELECTED_ITEM_NAME,
// [Paril-KEX]
STAT_HEALTH_BARS, // two health bar values; 7 bits for value, 1 bit for active
// if active,
// [Paril-KEX]
STAT_ACTIVE_WEAPON,
// don't use; just for verification
STAT_LAST

View file

@ -149,3 +149,40 @@ int32_t Bot_GetItemID( const char * classname ) {
return Item_Invalid;
}
/*
================
Edict_ForceLookAtPoint
================
*/
void Edict_ForceLookAtPoint( edict_t * edict, gvec3_cref_t point ) {
vec3_t viewOrigin = edict->s.origin;
if ( edict->client != nullptr ) {
viewOrigin += edict->client->ps.viewoffset;
}
const vec3_t ideal = ( point - viewOrigin ).normalized();
vec3_t viewAngles = vectoangles( ideal );
if ( viewAngles.x < -180.0f ) {
viewAngles.x = anglemod( viewAngles.x + 360.0f );
}
if ( edict->client != nullptr ) {
edict->client->ps.pmove.delta_angles = ( viewAngles - edict->client->resp.cmd_angles );
edict->client->ps.viewangles = {};
edict->client->v_angle = {};
edict->s.angles = {};
}
}
/*
================
Bot_PickedUpItem
Check if the given bot has picked up the given item or not.
================
*/
bool Bot_PickedUpItem( edict_t * bot, edict_t * item ) {
return item->item_picked_up_by[ ( bot->s.number - 1 ) ];
}

View file

@ -6,4 +6,6 @@
void Bot_SetWeapon( edict_t * bot, const int weaponIndex, const bool instantSwitch );
void Bot_TriggerEdict( edict_t * bot, edict_t * edict );
int32_t Bot_GetItemID( const char * classname );
void Bot_UseItem( edict_t * bot, const int32_t itemID );
void Bot_UseItem( edict_t * bot, const int32_t itemID );
void Edict_ForceLookAtPoint( edict_t * edict, gvec3_cref_t point );
bool Bot_PickedUpItem( edict_t * bot, edict_t * item );

View file

@ -248,9 +248,6 @@ void Item_UpdateState( edict_t * item ) {
}
}
// track who has picked us up so far...
item->sv.pickedup_list = item->item_picked_up_by;
const item_id_t itemID = item->item->id;
if ( itemID == IT_FLAG1 || itemID == IT_FLAG2 ) {
item->sv.ent_flags |= SVFL_IS_OBJECTIVE;
@ -529,4 +526,4 @@ const edict_t * FindActorUnderCrosshair( const edict_t * player ) {
}
return traceEnt;
}
}

View file

@ -1050,7 +1050,7 @@ static void CG_ExecuteLayoutString (const char *s, vrect_t hud_vrect, vrect_t hu
width = 3;
value = ps->stats[STAT_AMMO];
int32_t min_ammo = cgi.CL_GetWarnAmmoCount(ps->stats[STAT_ACTIVE_WHEEL_WEAPON]);
int32_t min_ammo = cgi.CL_GetWarnAmmoCount(ps->stats[STAT_ACTIVE_WEAPON]);
if (!min_ammo)
min_ammo = 5; // back compat
@ -1702,7 +1702,6 @@ static void CG_DrawInventory(const player_state_t *ps, const std::array<int16_t,
else
{
const char *string = G_Fmt("{}", inventory[item]).data();
vec2_t strSz = cgi.SCR_MeasureFontString(string, scale);
cgi.SCR_DrawFontString(string, x + (216 * scale) - (16 * scale), y - (font_y_offset * scale), scale, (item == selected) ? alt_color : rgba_white, true, text_align_t::RIGHT);
string = cgi.Localize(cgi.get_configstring(CS_ITEMS + item), nullptr, 0);

View file

@ -783,6 +783,8 @@ THINK(CTFDropFlagThink) (edict_t *ent) -> void
gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned",
CTFTeamName(CTF_TEAM2));
}
gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
}
// Called from PlayerDie, to drop the flag from a dying player
@ -1087,6 +1089,20 @@ void SetCTFStats(edict_t *ent)
p1 = imageindex_i_ctf1t;
break;
}
// [Paril-KEX] make sure there is a dropped version on the map somewhere
if (p1 == imageindex_i_ctf1d)
{
e = G_FindByString<&edict_t::classname>(e, "item_flag_team1");
if (e == nullptr)
{
CTFResetFlag(CTF_TEAM1);
gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned",
CTFTeamName(CTF_TEAM1));
gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
}
}
}
else if (e->spawnflags.has(SPAWNFLAG_ITEM_DROPPED))
p1 = imageindex_i_ctf1d; // must be dropped
@ -1108,6 +1124,20 @@ void SetCTFStats(edict_t *ent)
p2 = imageindex_i_ctf2t;
break;
}
// [Paril-KEX] make sure there is a dropped version on the map somewhere
if (p2 == imageindex_i_ctf2d)
{
e = G_FindByString<&edict_t::classname>(e, "item_flag_team2");
if (e == nullptr)
{
CTFResetFlag(CTF_TEAM2);
gi.LocBroadcast_Print(PRINT_HIGH, "$g_flag_returned",
CTFTeamName(CTF_TEAM2));
gi.sound(ent, CHAN_RELIABLE | CHAN_NO_PHS_ADD | CHAN_AUX, gi.soundindex("ctf/flagret.wav"), 1, ATTN_NONE, 0);
}
}
}
else if (e->spawnflags.has(SPAWNFLAG_ITEM_DROPPED))
p2 = imageindex_i_ctf2d; // must be dropped
@ -2960,7 +2990,7 @@ bool CTFStartClient(edict_t *ent)
void CTFObserver(edict_t *ent)
{
if (!G_TeamplayEnabled())
if (!G_TeamplayEnabled() || g_teamplay_force_join->integer)
return;
// start as 'observer'

View file

@ -93,7 +93,8 @@ void ai_stand(edict_t *self, float dist)
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
// [Paril-KEX] check if we've been pushed out of our point_combat
if (self->movetarget)
if (!(self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND) &&
self->movetarget && self->movetarget->classname && !strcmp(self->movetarget->classname, "point_combat"))
{
if (!boxes_intersect(self->absmin, self->absmax, self->movetarget->absmin, self->movetarget->absmax))
{
@ -105,7 +106,7 @@ void ai_stand(edict_t *self, float dist)
}
}
if (self->enemy)
if (self->enemy && !(self->enemy->classname && !strcmp(self->enemy->classname, "player_noise")))
{
v = self->enemy->s.origin - self->s.origin;
self->ideal_yaw = vectoyaw(v);
@ -453,6 +454,12 @@ bool infront(edict_t *self, edict_t *other)
vec = other->s.origin - self->s.origin;
vec.normalize();
dot = vec.dot(forward);
// [Paril-KEX] if we're an ambush monster, reduce our cone of
// vision to not ruin surprises, unless we already had an enemy.
if (self->spawnflags.has(SPAWNFLAG_MONSTER_AMBUSH) && !self->monsterinfo.trail_time && !self->enemy)
return dot > 0.15f;
return dot > -0.30f;
}
@ -730,7 +737,31 @@ bool FindTarget(edict_t *self)
return false;
if (client == self->enemy)
return true; // JDC false;
{
bool skip_found = true;
// [Paril-KEX] slight special behavior if we are currently going to a sound
// and we hear a new one; because player noises are re-used, this can leave
// us with the "same" enemy even though it's a different noise.
if (heardit && (self->monsterinfo.aiflags & AI_SOUND_TARGET))
{
vec3_t temp = client->s.origin - self->s.origin;
self->ideal_yaw = vectoyaw(temp);
if (!FacingIdeal(self))
skip_found = false;
else if (!SV_CloseEnough(self, client, 8.f))
skip_found = false;
if (!skip_found && (self->monsterinfo.aiflags & AI_TEMP_STAND_GROUND))
{
self->monsterinfo.aiflags &= ~(AI_STAND_GROUND | AI_TEMP_STAND_GROUND);
}
}
if (skip_found)
return true; // JDC false;
}
// ROGUE - hintpath coop fix
if ((self->monsterinfo.aiflags & AI_HINT_PATH) && coop->integer)
@ -873,7 +904,8 @@ bool FacingIdeal(edict_t *self)
//=============================================================================
MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
// [Paril-KEX] split this out so we can use it for the other bosses
bool M_CheckAttack_Base(edict_t *self, float stand_ground_chance, float melee_chance, float near_chance, float mid_chance, float far_chance, float strafe_scalar)
{
vec3_t spot1, spot2;
float chance;
@ -903,7 +935,7 @@ MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
spot2[2] += self->enemy->viewheight;
tr = gi.traceline(spot1, spot2, self,
MASK_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA);
MASK_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA);
}
else
{
@ -938,7 +970,7 @@ MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
{
// make sure we're not going to shoot a monster
tr = gi.traceline(spot1, self->monsterinfo.blind_fire_target, self,
CONTENTS_MONSTER);
CONTENTS_MONSTER);
if (tr.allsolid || tr.startsolid || ((tr.fraction < 1.0f) && (tr.ent != self->enemy)))
return false;
@ -982,28 +1014,29 @@ MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
if (level.time < self->monsterinfo.attack_finished)
return false;
if (enemy_range > RANGE_MID)
return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.7f;
chance = stand_ground_chance;
}
else if (enemy_range <= RANGE_MELEE)
{
chance = 0.4f;
chance = melee_chance;
}
else if (enemy_range <= RANGE_NEAR)
{
chance = 0.25f;
chance = near_chance;
}
else if (enemy_range <= RANGE_MID)
{
chance = mid_chance;
}
else
{
chance = 0.06f;
chance = far_chance;
}
// PGM - go ahead and shoot every time if it's a info_notnull
if ((frandom() < chance) || (!self->enemy->client && self->enemy->solid == SOLID_NOT))
if ((!self->enemy->client && self->enemy->solid == SOLID_NOT) || (frandom() < chance))
{
self->monsterinfo.attack_state = AS_MISSILE;
self->monsterinfo.attack_finished = level.time;
@ -1018,6 +1051,7 @@ MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
{
// originally, just 0.3
float strafe_chance;
if (!(strcmp(self->classname, "monster_daedalus")))
strafe_chance = 0.8f;
else
@ -1026,16 +1060,21 @@ MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
// if enemy is tesla, never strafe
if ((self->enemy) && (self->enemy->classname) && (!strcmp(self->enemy->classname, "tesla_mine")))
strafe_chance = 0;
else
strafe_chance *= strafe_scalar;
monster_attack_state_t new_state = AS_STRAIGHT;
if (frandom() < strafe_chance)
new_state = AS_SLIDING;
if (new_state != self->monsterinfo.attack_state)
if (strafe_chance)
{
self->monsterinfo.strafe_check_time = level.time + random_time(1_sec, 3_sec);
self->monsterinfo.attack_state = new_state;
monster_attack_state_t new_state = AS_STRAIGHT;
if (frandom() < strafe_chance)
new_state = AS_SLIDING;
if (new_state != self->monsterinfo.attack_state)
{
self->monsterinfo.strafe_check_time = level.time + random_time(1_sec, 3_sec);
self->monsterinfo.attack_state = new_state;
}
}
}
}
@ -1051,6 +1090,11 @@ MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
return false;
}
MONSTERINFO_CHECKATTACK(M_CheckAttack) (edict_t *self) -> bool
{
return M_CheckAttack_Base(self, 0.7f, 0.4f, 0.25f, 0.06f, 0.f, 1.0f);
}
/*
=============
ai_run_melee
@ -1222,12 +1266,7 @@ bool ai_checkattack(edict_t *self, float dist)
}
else
{
if (self->monsterinfo.aiflags & AI_BRUTAL)
{
if (self->enemy->health <= self->enemy->gib_health)
hesDeadJim = true;
}
else
if (!(self->monsterinfo.aiflags & AI_BRUTAL))
{
if (self->enemy->health <= 0)
hesDeadJim = true;

View file

@ -3,7 +3,7 @@
#include "g_local.h"
#include "m_player.h"
void SelectNextItem(edict_t *ent, item_flags_t itflags)
void SelectNextItem(edict_t *ent, item_flags_t itflags, bool menu = true)
{
gclient_t *cl;
item_id_t i, index;
@ -12,12 +12,12 @@ void SelectNextItem(edict_t *ent, item_flags_t itflags)
cl = ent->client;
// ZOID
if (cl->menu)
if (menu && cl->menu)
{
PMenu_Next(ent);
return;
}
else if (cl->chase_target)
else if (menu && cl->chase_target)
{
ChaseNext(ent);
return;
@ -96,7 +96,7 @@ void ValidateSelectedItem(edict_t *ent)
if (cl->pers.inventory[cl->pers.selected_item])
return; // valid
SelectNextItem(ent, IF_ANY);
SelectNextItem(ent, IF_ANY, false);
}
//=================================================================================
@ -456,7 +456,7 @@ void Cmd_Spawn_f(edict_t *ent)
if (other->inuse)
gi.linkentity(other);
if (other->svflags & SVF_MONSTER)
if ((other->svflags & SVF_MONSTER) && other->think)
other->think(other);
}
@ -490,6 +490,19 @@ void Cmd_Teleport_f(edict_t *ent)
ent->s.origin[0] = (float) atof(gi.argv(1));
ent->s.origin[1] = (float) atof(gi.argv(2));
ent->s.origin[2] = (float) atof(gi.argv(3));
if (gi.argc() >= 4)
{
float pitch = (float) atof(gi.argv(4));
float yaw = (float) atof(gi.argv(5));
float roll = (float) atof(gi.argv(6));
vec3_t ang { pitch, yaw, roll };
ent->client->ps.pmove.delta_angles = ( ang - ent->client->resp.cmd_angles );
ent->client->ps.viewangles = {};
ent->client->v_angle = {};
}
gi.linkentity(ent);
}
@ -660,10 +673,16 @@ void Cmd_Drop_f(edict_t *ent)
return;
// ZOID--special case for tech powerups
if (Q_strcasecmp(gi.args(), "tech") == 0 && (it = CTFWhat_Tech(ent)) != nullptr)
if (Q_strcasecmp(gi.args(), "tech") == 0)
{
it->drop(ent, it);
ValidateSelectedItem(ent);
it = CTFWhat_Tech(ent);
if (it)
{
it->drop(ent, it);
ValidateSelectedItem(ent);
}
return;
}
// ZOID
@ -1033,7 +1052,7 @@ void Cmd_Where_f( edict_t * ent ) {
const vec3_t & origin = ent->s.origin;
std::string location;
fmt::format_to( std::back_inserter( location ), FMT_STRING( "{:.1f} {:.1f} {:.1f}\n" ), origin[ 0 ], origin[ 1 ], origin[ 2 ] );
fmt::format_to( std::back_inserter( location ), FMT_STRING( "{:.1f} {:.1f} {:.1f} {:.1f} {:.1f} {:.1f}\n" ), origin[ 0 ], origin[ 1 ], origin[ 2 ], ent->client->ps.viewangles[0], ent->client->ps.viewangles[1], ent->client->ps.viewangles[2] );
gi.LocClient_Print( ent, PRINT_HIGH, "Location: {}\n", location.c_str() );
gi.SendToClipBoard( location.c_str() );
}
@ -1212,9 +1231,7 @@ void Cmd_Wave_f(edict_t *ent)
if (do_animate)
ent->client->anim_priority = ANIM_WAVE;
constexpr float NOTIFY_DISTANCE = 256.f;
bool notified_anybody = false;
const char *self_notify_msg = nullptr, *other_notify_msg = nullptr, *other_notify_none_msg = nullptr;
const char *other_notify_msg = nullptr, *other_notify_none_msg = nullptr;
vec3_t start, dir;
P_ProjectSource(ent, ent->client->v_angle, { 0, 0, 0 }, start, dir);
@ -1245,7 +1262,6 @@ void Cmd_Wave_f(edict_t *ent)
switch (i)
{
case GESTURE_FLIP_OFF:
self_notify_msg = "$g_flipoff";
other_notify_msg = "$g_flipoff_other";
other_notify_none_msg = "$g_flipoff_none";
if (do_animate)
@ -1255,7 +1271,6 @@ void Cmd_Wave_f(edict_t *ent)
}
break;
case GESTURE_SALUTE:
self_notify_msg = "$g_salute";
other_notify_msg = "$g_salute_other";
other_notify_none_msg = "$g_salute_none";
if (do_animate)
@ -1265,7 +1280,6 @@ void Cmd_Wave_f(edict_t *ent)
}
break;
case GESTURE_TAUNT:
self_notify_msg = "$g_taunt";
other_notify_msg = "$g_taunt_other";
other_notify_none_msg = "$g_taunt_none";
if (do_animate)
@ -1275,7 +1289,6 @@ void Cmd_Wave_f(edict_t *ent)
}
break;
case GESTURE_WAVE:
self_notify_msg = "$g_wave";
other_notify_msg = "$g_wave_other";
other_notify_none_msg = "$g_wave_none";
if (do_animate)
@ -1286,7 +1299,6 @@ void Cmd_Wave_f(edict_t *ent)
break;
case GESTURE_POINT:
default:
self_notify_msg = "$g_point";
other_notify_msg = "$g_point_other";
other_notify_none_msg = "$g_point_none";
if (do_animate)
@ -1336,7 +1348,7 @@ void Cmd_Wave_f(edict_t *ent)
gi.WriteShort(POI_PING + (ent->s.number - 1));
gi.WriteShort(5000);
gi.WritePosition(tr.endpos);
gi.WriteShort(gi.imageindex("loc_ping"));
gi.WriteShort(level.pic_ping);
gi.WriteByte(208);
gi.WriteByte(POI_FLAG_NONE);
gi.unicast(player, false);

View file

@ -112,7 +112,27 @@ THINK(Move_Begin) (edict_t *ent) -> void
ent->think = Move_Final;
}
void Think_AccelMove_New(edict_t *ent);
void Think_AccelMove(edict_t *ent);
bool Think_AccelMove_MoveInfo(moveinfo_t *moveinfo);
constexpr float AccelerationDistance(float target, float rate)
{
return (target * ((target / rate) + 1) / 2);
}
inline void Move_Regular(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self))
{
if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
{
Move_Begin(ent);
}
else
{
ent->nextthink = level.time + FRAME_TIME_S;
ent->think = Move_Begin;
}
}
void Move_Calc(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self))
{
@ -124,25 +144,101 @@ void Move_Calc(edict_t *ent, const vec3_t &dest, void(*endfunc)(edict_t *self))
if (ent->moveinfo.speed == ent->moveinfo.accel && ent->moveinfo.speed == ent->moveinfo.decel)
{
if (level.current_entity == ((ent->flags & FL_TEAMSLAVE) ? ent->teammaster : ent))
{
Move_Begin(ent);
}
else
{
ent->nextthink = level.time + FRAME_TIME_S;
ent->think = Move_Begin;
}
Move_Regular(ent, dest, endfunc);
}
else
{
// accelerative
ent->moveinfo.current_speed = 0;
ent->think = Think_AccelMove;
if (gi.tick_rate == 10)
ent->think = Think_AccelMove;
else
{
// [Paril-KEX] rewritten to work better at higher tickrates
ent->moveinfo.curve_frame = 0;
ent->moveinfo.num_subframes = (0.1f / gi.frame_time_s) - 1;
float total_dist = ent->moveinfo.remaining_distance;
std::vector<float> distances;
if (ent->moveinfo.num_subframes)
{
distances.push_back(0);
ent->moveinfo.curve_frame = 1;
}
else
ent->moveinfo.curve_frame = 0;
// simulate 10hz movement
while (ent->moveinfo.remaining_distance)
{
if (!Think_AccelMove_MoveInfo(&ent->moveinfo))
break;
ent->moveinfo.remaining_distance -= ent->moveinfo.current_speed;
distances.push_back(total_dist - ent->moveinfo.remaining_distance);
}
if (ent->moveinfo.num_subframes)
distances.push_back(total_dist);
ent->moveinfo.subframe = 0;
ent->moveinfo.curve_ref = ent->s.origin;
ent->moveinfo.curve_positions = make_savable_memory<float, TAG_LEVEL>(distances.size());
std::copy(distances.begin(), distances.end(), ent->moveinfo.curve_positions.ptr);
ent->moveinfo.num_frames_done = 0;
ent->think = Think_AccelMove_New;
}
ent->nextthink = level.time + FRAME_TIME_S;
}
}
THINK(Think_AccelMove_New) (edict_t *ent) -> void
{
float t = 0.f;
float target_dist;
if (ent->moveinfo.num_subframes)
{
if (ent->moveinfo.subframe == ent->moveinfo.num_subframes + 1)
{
ent->moveinfo.subframe = 0;
ent->moveinfo.curve_frame++;
if (ent->moveinfo.curve_frame == ent->moveinfo.curve_positions.count)
{
Move_Final(ent);
return;
}
}
t = (ent->moveinfo.subframe + 1) / ((float) ent->moveinfo.num_subframes + 1);
target_dist = lerp(ent->moveinfo.curve_positions[ent->moveinfo.curve_frame - 1], ent->moveinfo.curve_positions[ent->moveinfo.curve_frame], t);
ent->moveinfo.subframe++;
}
else
{
if (ent->moveinfo.curve_frame == ent->moveinfo.curve_positions.count)
{
Move_Final(ent);
return;
}
target_dist = ent->moveinfo.curve_positions[ent->moveinfo.curve_frame++];
}
ent->moveinfo.num_frames_done++;
vec3_t target_pos = ent->moveinfo.curve_ref + (ent->moveinfo.dir * target_dist);
ent->velocity = (target_pos - ent->s.origin) * (1.f / gi.frame_time_s);
ent->nextthink = level.time + FRAME_TIME_S;
}
//
// Support routines for angular movement (changes in angle using avelocity)
//
@ -269,11 +365,6 @@ The team has completed a frame of movement, so
change the speed for the next frame
==============
*/
constexpr float AccelerationDistance(float target, float rate)
{
return (target * ((target / rate) + 1) / 2);
}
void plat_CalcAcceleratedMove(moveinfo_t *moveinfo)
{
float accel_dist;
@ -382,6 +473,18 @@ void plat_Accelerate(moveinfo_t *moveinfo)
return;
}
bool Think_AccelMove_MoveInfo (moveinfo_t *moveinfo)
{
if (moveinfo->current_speed == 0) // starting or blocked
plat_CalcAcceleratedMove(moveinfo);
plat_Accelerate(moveinfo);
// will the entire move complete on next frame?
return moveinfo->remaining_distance > moveinfo->current_speed;
}
// Paril: old acceleration code; this is here only to support old save games.
THINK(Think_AccelMove) (edict_t *ent) -> void
{
// [Paril-KEX] calculate distance dynamically
@ -390,12 +493,13 @@ THINK(Think_AccelMove) (edict_t *ent) -> void
else
ent->moveinfo.remaining_distance = (ent->moveinfo.end_origin - ent->s.origin).length();
if (ent->moveinfo.current_speed == 0) // starting or blocked
plat_CalcAcceleratedMove(&ent->moveinfo);
plat_Accelerate(&ent->moveinfo);
// will the entire move complete on next frame?
if (!Think_AccelMove_MoveInfo(&ent->moveinfo))
{
Move_Final(ent);
return;
}
if (ent->moveinfo.remaining_distance <= ent->moveinfo.current_speed)
{
Move_Final(ent);
@ -2233,6 +2337,8 @@ void train_resume(edict_t *self)
dest -= vec3_t{1.f, 1.f, 1.f};
}
self->s.sound = self->moveinfo.sound_middle;
self->moveinfo.state = STATE_TOP;
self->moveinfo.start_origin = self->s.origin;
self->moveinfo.end_origin = dest;

View file

@ -137,6 +137,9 @@ THINK(DoRespawn) (edict_t *ent) -> void
else
{
// ZOID
ent->svflags |= SVF_NOCLIENT;
ent->solid = SOLID_NOT;
gi.linkentity(ent);
for (count = 0, ent = master; ent; ent = ent->chain, count++)
;
@ -243,7 +246,7 @@ bool Pickup_Powerup(edict_t *ent, edict_t *other)
if (deathmatch->integer)
{
if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED))
if (!(ent->spawnflags & SPAWNFLAG_ITEM_DROPPED) && !is_dropped_from_death)
SetRespawn(ent, gtime_t::from_sec(ent->item->quantity));
}
@ -307,7 +310,7 @@ void G_CheckPowerArmor(edict_t *ent)
if (!ent->client->pers.inventory[IT_AMMO_CELLS])
has_enough_cells = false;
else if (ent->client->pers.autoshield >= AUTO_SHIELD_AUTO)
has_enough_cells = !(ent->flags & FL_WANTS_POWER_ARMOR) || ent->client->pers.inventory[IT_AMMO_CELLS] > ent->client->pers.autoshield;
has_enough_cells = (ent->flags & FL_WANTS_POWER_ARMOR) && ent->client->pers.inventory[IT_AMMO_CELLS] > ent->client->pers.autoshield;
else
has_enough_cells = true;
@ -612,6 +615,10 @@ bool Pickup_Ammo(edict_t *ent, edict_t *other)
void Drop_Ammo(edict_t *ent, gitem_t *item)
{
// [Paril-KEX]
if (G_CheckInfiniteAmmo(item))
return;
item_id_t index = item->id;
edict_t *dropped = Drop_Item(ent, item);
dropped->spawnflags |= SPAWNFLAG_ITEM_DROPPED_PLAYER;
@ -638,7 +645,11 @@ void Drop_Ammo(edict_t *ent, gitem_t *item)
THINK(MegaHealth_think) (edict_t *self) -> void
{
if (self->owner->health > self->owner->max_health)
if (self->owner->health > self->owner->max_health
//ZOID
&& !CTFHasRegeneration(self->owner)
//ZOID
)
{
self->nextthink = level.time + 1_sec;
self->owner->health -= 1;
@ -668,13 +679,22 @@ bool Pickup_Health(edict_t *ent, edict_t *other)
other->health += count;
//ZOID
if (ctf->integer && other->health > 250 && count > 25)
other->health = 250;
//ZOID
if (!(health_flags & HEALTH_IGNORE_MAX))
{
if (other->health > other->max_health)
other->health = other->max_health;
}
if (ent->item->tag & HEALTH_TIMED)
if ((ent->item->tag & HEALTH_TIMED)
//ZOID
&& !CTFHasRegeneration(other)
//ZOID
)
{
if (!deathmatch->integer)
{
@ -735,6 +755,9 @@ bool Pickup_Armor(edict_t *ent, edict_t *other)
old_armor_index = ArmorIndex(other);
// [Paril-KEX] for g_start_items
int32_t base_count = ent->count ? ent->count : newinfo ? newinfo->base_count : 0;
// handle armor shards specially
if (ent->item->id == IT_ARMOR_SHARD)
{
@ -743,11 +766,10 @@ bool Pickup_Armor(edict_t *ent, edict_t *other)
else
other->client->pers.inventory[old_armor_index] += 2;
}
// if player has no armor, just use it
else if (!old_armor_index)
{
other->client->pers.inventory[ent->item->id] = newinfo->base_count;
other->client->pers.inventory[ent->item->id] = base_count;
}
// use the better armor
@ -766,7 +788,7 @@ bool Pickup_Armor(edict_t *ent, edict_t *other)
// calc new armor values
salvage = oldinfo->normal_protection / newinfo->normal_protection;
salvagecount = (int) (salvage * other->client->pers.inventory[old_armor_index]);
newcount = newinfo->base_count + salvagecount;
newcount = base_count + salvagecount;
if (newcount > newinfo->max_count)
newcount = newinfo->max_count;
@ -780,7 +802,7 @@ bool Pickup_Armor(edict_t *ent, edict_t *other)
{
// calc new armor values
salvage = newinfo->normal_protection / oldinfo->normal_protection;
salvagecount = (int) (salvage * newinfo->base_count);
salvagecount = (int) (salvage * base_count);
newcount = other->client->pers.inventory[old_armor_index] + salvagecount;
if (newcount > oldinfo->max_count)
newcount = oldinfo->max_count;
@ -1291,7 +1313,9 @@ void SpawnItem(edict_t *ent, gitem_t *item)
{
if (item->pickup == Pickup_Armor || item->pickup == Pickup_PowerArmor ||
item->pickup == Pickup_Powerup || item->pickup == Pickup_Sphere || item->pickup == Pickup_Doppleganger ||
(item->flags & IF_HEALTH) || (item->flags & IF_AMMO) || item->pickup == Pickup_Weapon || item->pickup == Pickup_Pack)
(item->flags & IF_HEALTH) || (item->flags & IF_AMMO) || item->pickup == Pickup_Weapon || item->pickup == Pickup_Pack ||
item->id == IT_ITEM_BANDOLIER || item->id == IT_ITEM_PACK ||
item->id == IT_AMMO_NUKE)
{
G_FreeEdict(ent);
return;
@ -2970,7 +2994,10 @@ gives +1 to maximum health
/* use_name */ "Bandolier",
/* pickup_name */ "$item_bandolier",
/* pickup_name_definite */ "$item_bandolier_def",
/* quantity */ 60
/* quantity */ 60,
/* ammo */ IT_NULL,
/* chain */ IT_NULL,
/* flags */ IF_POWERUP
},
/*QUAKED item_pack (.3 .3 1) (-16 -16 -16) (16 16 16)
@ -2991,6 +3018,9 @@ gives +1 to maximum health
/* pickup_name */ "$item_ammo_pack",
/* pickup_name_definite */ "$item_ammo_pack_def",
/* quantity */ 180,
/* ammo */ IT_NULL,
/* chain */ IT_NULL,
/* flags */ IF_POWERUP
},
@ -3931,14 +3961,20 @@ void InitItems()
if (!P_UseCoopInstancedItems() && (it.flags & IF_STAY_COOP))
it.drop = nullptr;
}
else if (deathmatch->integer)
{
if (g_dm_weapons_stay->integer && it.drop == Drop_Weapon)
it.drop = nullptr;
}
}
}
// [Paril-KEX]
inline bool G_CanDropItem(const gitem_t &item)
{
if (!item.drop)
return false;
else if ((item.flags & IF_WEAPON) && !(item.flags & IF_AMMO) && deathmatch->integer && g_dm_weapons_stay->integer)
return false;
return true;
}
/*
===============
SetItemNames
@ -3988,7 +4024,7 @@ void SetItemNames()
(itemlist[i].flags & IF_POWERUP_WHEEL) ? 1 : 0,
itemlist[i].sort_id,
itemlist[i].quantity_warn,
itemlist[i].drop != nullptr ? 1 : 0
G_CanDropItem(itemlist[i]) ? 1 : 0
).data());
itemlist[i].weapon_wheel_index = cs_index;
cs_index++;
@ -4010,7 +4046,7 @@ void SetItemNames()
gi.imageindex(itemlist[i].icon),
(itemlist[i].flags & IF_POWERUP_ONOFF) ? 1 : 0,
itemlist[i].sort_id,
itemlist[i].drop != nullptr ? 1 : 0,
G_CanDropItem(itemlist[i]) ? 1 : 0,
itemlist[i].ammo ? GetItemByIndex(itemlist[i].ammo)->ammo_wheel_index : -1
).data());
itemlist[i].powerup_wheel_index = cs_index;

View file

@ -1177,6 +1177,7 @@ struct level_locals_t
bool respawn_intermission; // only set once for respawning players
int32_t pic_health;
int32_t pic_ping;
int32_t total_secrets;
int32_t found_secrets;
@ -1257,6 +1258,8 @@ struct shadow_light_temp_t
const char *lightstyletarget = nullptr;
};
void G_LoadShadowLights();
#include <unordered_set>
// spawn_temp_t is only used to hold entity field values that
@ -1341,6 +1344,82 @@ DEFINE_DATA_FUNC(moveinfo_blocked, MOVEINFO_BLOCKED, void, edict_t *self, edict_
#define MOVEINFO_BLOCKED(n) \
SAVE_DATA_FUNC(n, MOVEINFO_BLOCKED, void, edict_t *self, edict_t *other)
// a struct that can store type-safe allocations
// of a fixed amount of data. it self-destructs when
// re-assigned. TODO: because edicts are still kind of
// managed like C memory, the destructor may not be
// called for a freed entity if this is stored as a member.
template<typename T, int32_t tag>
struct savable_allocated_memory_t
{
T *ptr;
size_t count;
constexpr savable_allocated_memory_t(T *ptr, size_t count) :
ptr(ptr),
count(count)
{
}
inline ~savable_allocated_memory_t()
{
release();
}
// no copy
constexpr savable_allocated_memory_t(const savable_allocated_memory_t &) = delete;
constexpr savable_allocated_memory_t &operator=(const savable_allocated_memory_t &) = delete;
// free move
constexpr savable_allocated_memory_t(savable_allocated_memory_t &&move)
{
ptr = move.ptr;
count = move.count;
move.ptr = nullptr;
move.count = 0;
}
constexpr savable_allocated_memory_t &operator=(savable_allocated_memory_t &&move)
{
ptr = move.ptr;
count = move.count;
move.ptr = nullptr;
move.count = 0;
return *this;
}
inline void release()
{
if (ptr)
{
gi.TagFree(ptr);
count = 0;
ptr = nullptr;
}
}
constexpr explicit operator T *() { return ptr; }
constexpr explicit operator const T *() const { return ptr; }
constexpr std::add_lvalue_reference_t<T> operator[](const size_t index) { return ptr[index]; }
constexpr const std::add_lvalue_reference_t<T> operator[](const size_t index) const { return ptr[index]; }
constexpr size_t size() const { return count * sizeof(T); }
constexpr operator bool() const { return !!ptr; }
};
template<typename T, int32_t tag>
inline savable_allocated_memory_t<T, tag> make_savable_memory(size_t count)
{
if (!count)
return { nullptr, 0 };
return { reinterpret_cast<T *>(gi.TagMalloc(sizeof(T) * count, tag)), count };
}
struct moveinfo_t
{
// fixed data
@ -1372,6 +1451,13 @@ struct moveinfo_t
float decel_distance;
save_moveinfo_endfunc_t endfunc;
save_moveinfo_blocked_t blocked;
// [Paril-KEX] new accel state
vec3_t curve_ref;
savable_allocated_memory_t<float, TAG_LEVEL> curve_positions;
size_t curve_frame;
uint8_t subframe, num_subframes;
size_t num_frames_done;
};
struct mframe_t
@ -1693,9 +1779,6 @@ extern level_locals_t level;
extern game_export_t globals;
extern spawn_temp_t st;
extern int sm_meat_index;
extern int snd_fry;
extern edict_t *g_edicts;
#include <random>
@ -2223,7 +2306,8 @@ void HuntTarget(edict_t *self, bool animate_state = true);
bool infront(edict_t *self, edict_t *other);
bool visible(edict_t *self, edict_t *other, bool through_glass = true);
bool FacingIdeal(edict_t *self);
// [Paril-KEX] generic function
bool M_CheckAttack_Base(edict_t *self, float stand_ground_chance, float melee_chance, float near_chance, float mid_chance, float far_chance, float strafe_scalar);
//
// g_weapon.c
@ -2731,6 +2815,9 @@ constexpr gtime_t LADDER_SOUND_TIME = 300_ms;
// time after damage that we can't respawn on a player for
constexpr gtime_t COOP_DAMAGE_RESPAWN_TIME = 2000_ms;
// time after firing that we can't respawn on a player for
constexpr gtime_t COOP_DAMAGE_FIRING_TIME = 2500_ms;
// this structure is cleared on each PutClientInServer(),
// except for 'client->pers'
struct gclient_t
@ -2917,6 +3004,8 @@ struct gclient_t
height_fog_t heightfog;
gtime_t last_attacker_time;
// saved - for coop; last time we were in a firing state
gtime_t last_firing_time;
};
// ==========================================
@ -3497,4 +3586,64 @@ inline void pierce_args_t::restore()
}
num_pierced = 0;
}
}
// [Paril-KEX] these are to fix a legacy bug with cached indices
// in save games. these can *only* be static/globals!
template<auto T>
struct cached_assetindex
{
static cached_assetindex<T> *head;
const char *name;
int32_t index = 0;
cached_assetindex *next = nullptr;
inline cached_assetindex()
{
next = head;
cached_assetindex<T>::head = this;
}
constexpr operator int32_t() const { return index; }
// assigned from spawn functions
inline void assign(const char *name) { this->name = name; index = (gi.*T)(name); }
// cleared before SpawnEntities
constexpr void clear() { index = 0; }
// re-find the index for the given cached entry, if we were cached
// by the regular map load
inline void reset() { if (index) index = (gi.*T)(this->name); }
static void reset_all()
{
auto asset = head;
while (asset)
{
asset->reset();
asset = asset->next;
}
}
static void clear_all()
{
auto asset = head;
while (asset)
{
asset->clear();
asset = asset->next;
}
}
};
using cached_soundindex = cached_assetindex<&local_game_import_t::soundindex>;
using cached_modelindex = cached_assetindex<&local_game_import_t::modelindex>;
using cached_imageindex = cached_assetindex<&local_game_import_t::imageindex>;
template<> cached_soundindex *cached_soundindex::head;
template<> cached_modelindex *cached_modelindex::head;
template<> cached_imageindex *cached_imageindex::head;
extern cached_modelindex sm_meat_index;
extern cached_soundindex snd_fry;

View file

@ -22,8 +22,8 @@ local_game_import_t gi;
game_export_t globals;
spawn_temp_t st;
int sm_meat_index;
int snd_fry;
cached_modelindex sm_meat_index;
cached_soundindex snd_fry;
edict_t *g_edicts;
@ -333,7 +333,7 @@ void InitGame()
g_friendly_fire = gi.cvar("g_friendly_fire", "0", CVAR_NOFLAGS);
g_dm_force_respawn = gi.cvar("g_dm_force_respawn", "0", CVAR_NOFLAGS);
g_dm_force_respawn_time = gi.cvar("g_dm_force_respawn_time", "0", CVAR_NOFLAGS);
g_dm_spawn_farthest = gi.cvar("g_dm_spawn_farthest", "0", CVAR_NOFLAGS);
g_dm_spawn_farthest = gi.cvar("g_dm_spawn_farthest", "1", CVAR_NOFLAGS);
g_no_armor = gi.cvar("g_no_armor", "0", CVAR_NOFLAGS);
g_dm_allow_exit = gi.cvar("g_dm_allow_exit", "0", CVAR_NOFLAGS);
g_infinite_ammo = gi.cvar("g_infinite_ammo", "0", CVAR_LATCH);
@ -446,6 +446,9 @@ Q2GAME_API game_export_t *GetGameAPI(game_import_t *import)
globals.Bot_TriggerEdict = Bot_TriggerEdict;
globals.Bot_GetItemID = Bot_GetItemID;
globals.Bot_UseItem = Bot_UseItem;
globals.Edict_ForceLookAtPoint = Edict_ForceLookAtPoint;
globals.Bot_PickedUpItem = Bot_PickedUpItem;
globals.Entity_IsVisibleToPlayer = Entity_IsVisibleToPlayer;
globals.GetShadowLightData = GetShadowLightData;

View file

@ -508,6 +508,7 @@ USE(light_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
// [Sam-KEX] For keeping track of shadow light parameters and setting them up on
// the server side.
// TODO move to level_locals_t
struct shadow_light_info_t
{
int entity_number;
@ -568,6 +569,57 @@ void setup_shadow_lights()
shadowlightinfo[i].shadowlight.conedirection[2]).data());
}
}
// fix an oversight in shadow light code that causes
// lights to be ordered wrong on return levels
// if the spawn functions are changed.
// this will work without changing the save/load code.
void G_LoadShadowLights()
{
for (size_t i = 0; i < level.shadow_light_count; i++)
{
const char *cstr = gi.get_configstring(CS_SHADOWLIGHTS + i);
const char *token = COM_ParseEx(&cstr, ";");
if (token && *token)
{
shadowlightinfo[i].entity_number = atoi(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.lighttype = (shadow_light_type_t) atoi(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.radius = atof(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.resolution = atoi(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.intensity = atof(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.fade_start = atof(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.fade_end = atof(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.lightstyle = atoi(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.coneangle = atof(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.conedirection[0] = atof(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.conedirection[1] = atof(token);
token = COM_ParseEx(&cstr, ";");
shadowlightinfo[i].shadowlight.conedirection[2] = atof(token);
}
}
}
// ---------------------------------------------------------------------------------
static void setup_dynamic_light(edict_t* self)

View file

@ -791,6 +791,26 @@ void monster_dead(edict_t *self)
gi.linkentity(self);
}
/*
=============
infront
returns 1 if the entity is in front (in sight) of self
=============
*/
static bool projectile_infront(edict_t *self, edict_t *other)
{
vec3_t vec;
float dot;
vec3_t forward;
AngleVectors(self->s.angles, forward, nullptr, nullptr);
vec = other->s.origin - self->s.origin;
vec.normalize();
dot = vec.dot(forward);
return dot > 0.35f;
}
static BoxEdictsResult_t M_CheckDodge_BoxEdictsFilter(edict_t *ent, void *data)
{
edict_t *self = (edict_t *) data;
@ -804,7 +824,7 @@ static BoxEdictsResult_t M_CheckDodge_BoxEdictsFilter(edict_t *ent, void *data)
return BoxEdictsResult_t::Skip;
// projectile is behind us, we can't see it
if (!infront(self, ent))
if (!projectile_infront(self, ent))
return BoxEdictsResult_t::Skip;
// will it hit us within 1 second? gives us enough time to dodge
@ -1065,7 +1085,7 @@ USE(monster_triggered_spawn_use) (edict_t *self, edict_t *other, edict_t *activa
// we have a one frame delay here so we don't telefrag the guy who activated us
self->think = monster_triggered_spawn;
self->nextthink = level.time + FRAME_TIME_S;
if (activator->client && !(self->hackflags & HACKFLAG_END_CUTSCENE))
if (activator && activator->client && !(self->hackflags & HACKFLAG_END_CUTSCENE))
self->enemy = activator;
self->use = monster_use;

View file

@ -450,13 +450,6 @@ retry:
if (!obstacle->inuse)
goto retry;
// the move failed, bump all nextthink times and back out moves
for (mv = ent; mv; mv = mv->teamchain)
{
if (mv->nextthink > 0_ms)
mv->nextthink += FRAME_TIME_S;
}
}
else
{
@ -849,7 +842,11 @@ void SV_Physics_Step(edict_t *ent)
ent->gravity = 1.0;
// ========
G_TouchTriggers(ent);
// [Paril-KEX] this is something N64 does to avoid doors opening
// at the start of a level, which triggers some monsters to spawn.
if (!level.is_n64 || level.time > FRAME_TIME_S)
G_TouchTriggers(ent);
if (!ent->inuse)
return;

View file

@ -205,7 +205,8 @@ enum save_type_id_t
ST_TIME, // serialized as milliseconds
ST_DATA, // serialized as name of data ptr from global list; `tag` = list tag
ST_INVENTORY, // serialized as classname => number key/value pairs
ST_REINFORCEMENTS // serialized as array of data
ST_REINFORCEMENTS, // serialized as array of data
ST_SAVABLE_DYNAMIC // serialized similar to ST_FIXED_ARRAY but includes count
};
struct save_struct_t;
@ -495,6 +496,21 @@ struct save_type_deducer<std::array<T, N>>
}
};
// savable_allocated_memory_t
template<typename T, int32_t Tag>
struct save_type_deducer<savable_allocated_memory_t<T, Tag>>
{
static constexpr save_field_t get_save_type(const char *name, size_t offset)
{
auto type = save_type_deducer<std::remove_extent_t<T>>::get_save_type(nullptr, 0).type;
if (type.id <= ST_BOOL || type.id >= ST_DOUBLE)
return { name, offset, { ST_SAVABLE_DYNAMIC, ST_INVALID, Tag, []() { return save_type_deducer<std::remove_extent_t<T>>::get_save_type(nullptr, 0).type; } } };
return { name, offset, { ST_SAVABLE_DYNAMIC, type.id, Tag } };
}
};
// save_data_ref<T>
template<typename T, size_t Tag>
struct save_type_deducer<save_data_t<T, Tag>>
@ -681,6 +697,7 @@ SAVE_STRUCT_START
FIELD_AUTO(intermission_angle),
// pic_health is set by worldspawn
// pic_ping is set by worldspawn
FIELD_AUTO(total_secrets),
FIELD_AUTO(found_secrets),
@ -910,6 +927,8 @@ SAVE_STRUCT_START
FIELD_AUTO(sound_entity_time),
FIELD_AUTO(sound2_entity),
FIELD_AUTO(sound2_entity_time),
FIELD_AUTO(last_firing_time),
SAVE_STRUCT_END
#undef DECLARE_SAVE_STRUCT
// clang-format on
@ -1107,6 +1126,13 @@ SAVE_STRUCT_START
FIELD_AUTO(moveinfo.endfunc),
FIELD_AUTO(moveinfo.blocked),
FIELD_AUTO(moveinfo.curve_ref),
FIELD_AUTO(moveinfo.curve_positions),
FIELD_AUTO(moveinfo.curve_frame),
FIELD_AUTO(moveinfo.subframe),
FIELD_AUTO(moveinfo.num_subframes),
FIELD_AUTO(moveinfo.num_frames_done),
// monsterinfo_t
FIELD_AUTO(monsterinfo.active_move),
FIELD_AUTO(monsterinfo.next_move),
@ -1281,7 +1307,7 @@ SAVE_STRUCT_END
#undef DECLARE_SAVE_STRUCT
// clang-format on
size_t get_simple_type_size(save_type_id_t id)
inline size_t get_simple_type_size(save_type_id_t id, bool fatal = true)
{
switch (id)
{
@ -1309,8 +1335,12 @@ size_t get_simple_type_size(save_type_id_t id)
return sizeof(size_t);
case ST_ITEM_INDEX:
return sizeof(uint32_t);
case ST_SAVABLE_DYNAMIC:
return sizeof(savable_allocated_memory_t<void *, 0>);
default:
gi.Com_ErrorFmt("Can't calculate static size for type ID {}", (int32_t) id);
if (fatal)
gi.Com_ErrorFmt("Can't calculate static size for type ID {}", (int32_t) id);
break;
}
return 0;
@ -1319,8 +1349,8 @@ size_t get_simple_type_size(save_type_id_t id)
size_t get_complex_type_size(const save_type_t &type)
{
// these are simple types
if ((type.id >= ST_BOOL && type.id <= ST_DOUBLE) || (type.id >= ST_ENTITY && type.id <= ST_TIME))
return get_simple_type_size(type.id);
if (auto simple = get_simple_type_size(type.id, false))
return simple;
switch (type.id)
{
@ -1448,7 +1478,7 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t
else if (json.asInt() < INT8_MIN || json.asInt() > INT8_MAX)
json_print_error(field, "int8 out of range", false);
else
*((int8_t *) data) = json.isInt();
*((int8_t *) data) = json.asInt();
return;
case ST_INT16:
if (!json.isInt())
@ -1622,6 +1652,40 @@ void read_save_type_json(const Json::Value &json, void *data, const save_type_t
}
}
return;
case ST_SAVABLE_DYNAMIC:
if (!json.isArray())
json_print_error(field, "expected array", false);
else
{
savable_allocated_memory_t<void, 0> *savptr = (savable_allocated_memory_t<void, 0> *) data;
size_t element_size;
save_type_t element_type;
if (type->type_resolver)
{
element_type = type->type_resolver();
element_size = get_complex_type_size(element_type);
}
else
{
element_size = get_simple_type_size((save_type_id_t) type->tag);
element_type = { (save_type_id_t) type->tag };
}
savptr->count = json.size();
savptr->ptr = gi.TagMalloc(element_size * savptr->count, type->count);
byte *out_element = (byte *) savptr->ptr;
for (Json::Value::ArrayIndex i = 0; i < savptr->count; i++, out_element += element_size)
{
const Json::Value &v = json[i];
read_save_type_json(v, out_element, &element_type,
fmt::format("[{}]", i).c_str());
}
}
return;
case ST_BITSET:
type->read(data, json, field);
@ -2005,7 +2069,62 @@ bool write_save_type_json(const void *data, const save_type_t *type, bool null_f
v.append(std::move(value));
}
output = v;
output = std::move(v);
return true;
}
case ST_SAVABLE_DYNAMIC: {
const savable_allocated_memory_t<void, 0> *savptr = (const savable_allocated_memory_t<void, 0> *) data;
size_t i;
size_t element_size;
save_type_t element_type;
if (type->type_resolver)
{
element_type = type->type_resolver();
element_size = get_complex_type_size(element_type);
}
else
{
element_size = get_simple_type_size((save_type_id_t) type->tag);
element_type = { (save_type_id_t) type->tag };
}
const uint8_t *element = (const uint8_t *) savptr->ptr;
if (null_for_empty)
{
if (type->is_empty)
{
if (type->is_empty(data))
return false;
}
else
{
for (i = 0; i < savptr->count; i++, element += element_size)
{
Json::Value value;
bool valid_value = write_save_type_json(element, &element_type, !element_type.never_empty, value);
if (valid_value)
break;
}
if (i == savptr->count)
return false;
}
}
element = (const uint8_t *) savptr->ptr;
Json::Value v(Json::arrayValue);
for (i = 0; i < savptr->count; i++, element += element_size)
{
Json::Value value;
write_save_type_json(element, &element_type, false, value);
v.append(std::move(value));
}
output = std::move(v);
return true;
}
case ST_BITSET:
@ -2020,7 +2139,7 @@ bool write_save_type_json(const void *data, const save_type_t *type, bool null_f
if (null_for_empty && (!valid_value || !obj.size()))
return false;
output = obj;
output = std::move(obj);
return true;
}
case ST_ENTITY: {
@ -2134,7 +2253,7 @@ bool write_save_type_json(const void *data, const save_type_t *type, bool null_f
if (null_for_empty && inventory.empty())
return false;
output = inventory;
output = std::move(inventory);
return true;
}
case ST_REINFORCEMENTS: {
@ -2165,7 +2284,7 @@ bool write_save_type_json(const void *data, const save_type_t *type, bool null_f
reinforcements[i] = obj;
}
output = reinforcements;
output = std::move(reinforcements);
return true;
}
default:
@ -2194,7 +2313,7 @@ bool write_save_struct_json(const void *data, const save_struct_t *structure, bo
if (null_for_empty && obj.empty())
return false;
output = obj;
output = std::move(obj);
return true;
}
@ -2458,6 +2577,13 @@ void ReadLevelJson(const char *jsonString)
}
G_PrecacheInventoryItems();
// clear cached indices
cached_soundindex::reset_all();
cached_modelindex::reset_all();
cached_imageindex::reset_all();
G_LoadShadowLights();
}
// [Paril-KEX]
@ -2468,6 +2594,16 @@ bool G_CanSave()
gi.LocClient_Print(&g_edicts[1], PRINT_CENTER, "$g_no_save_dead");
return false;
}
// don't allow saving during cameras/intermissions as this
// causes the game to act weird when these are loaded
else if (level.intermissiontime)
{
return false;
}
return true;
}
}
/*static*/ template<> cached_soundindex *cached_soundindex::head = nullptr;
/*static*/ template<> cached_modelindex *cached_modelindex::head = nullptr;
/*static*/ template<> cached_imageindex *cached_imageindex::head = nullptr;

View file

@ -1144,6 +1144,11 @@ parsing textual entity definitions out of an ent file.
*/
void SpawnEntities(const char *mapname, const char *entities, const char *spawnpoint)
{
// clear cached indices
cached_soundindex::clear_all();
cached_modelindex::clear_all();
cached_imageindex::clear_all();
edict_t *ent;
int inhibit;
const char *com_token;
@ -1509,7 +1514,7 @@ void SP_worldspawn(edict_t *ent)
gi.configstring(CS_MAXCLIENTS, G_Fmt("{}", game.maxclients).data());
if (level.is_n64)
if (level.is_n64 && !deathmatch->integer)
{
gi.configstring(CONFIG_N64_PHYSICS, "1");
pm_config.n64_physics = true;
@ -1544,7 +1549,7 @@ void SP_worldspawn(edict_t *ent)
gi.cvar_set("sv_gravity", st.gravity);
}
snd_fry = gi.soundindex("player/fry.wav"); // standing in lava / slime
snd_fry.assign("player/fry.wav"); // standing in lava / slime
PrecacheItem(GetItemByIndex(IT_ITEM_COMPASS));
PrecacheItem(GetItemByIndex(IT_WEAPON_BLASTER));
@ -1583,6 +1588,7 @@ void SP_worldspawn(edict_t *ent)
gi.soundindex("*pain75_2.wav");
gi.soundindex("*pain100_1.wav");
gi.soundindex("*pain100_2.wav");
gi.soundindex("*drown1.wav"); // [Paril-KEX]
// sexed models
for (auto &item : itemlist)
@ -1624,6 +1630,10 @@ void SP_worldspawn(edict_t *ent)
gi.soundindex("player/u_breath1.wav");
gi.soundindex("player/u_breath2.wav");
gi.soundindex("player/wade1.wav");
gi.soundindex("player/wade2.wav");
gi.soundindex("player/wade3.wav");
gi.soundindex("items/pkup.wav"); // bonus item pickup
gi.soundindex("world/land.wav"); // landing thud
gi.soundindex("misc/h2ohit1.wav"); // landing splash
@ -1637,7 +1647,7 @@ void SP_worldspawn(edict_t *ent)
gi.soundindex("infantry/inflies1.wav");
sm_meat_index = gi.modelindex("models/objects/gibs/sm_meat/tris.md2");
sm_meat_index.assign("models/objects/gibs/sm_meat/tris.md2");
gi.modelindex("models/objects/gibs/arm/tris.md2");
gi.modelindex("models/objects/gibs/bone/tris.md2");
gi.modelindex("models/objects/gibs/bone2/tris.md2");
@ -1646,7 +1656,7 @@ void SP_worldspawn(edict_t *ent)
gi.modelindex("models/objects/gibs/head2/tris.md2");
gi.modelindex("models/objects/gibs/sm_metal/tris.md2");
gi.imageindex("loc_ping");
level.pic_ping = gi.imageindex("loc_ping");
//
// Setup light animation tables. 'a' is total darkness, 'z' is doublebright.

View file

@ -1327,7 +1327,15 @@ USE(use_target_camera) (edict_t *self, edict_t *other, edict_t *activator) -> vo
// respawn any dead clients
if (client->health <= 0)
{
// give us our max health back since it will reset
// to pers.health; in instanced items we'd lose the items
// we touched so we always want to respawn with our max.
if (P_UseCoopInstancedItems())
client->client->pers.health = client->client->pers.max_health = client->max_health;
respawn(client);
}
MoveClientToIntermission(client);
}
@ -1606,6 +1614,9 @@ static float distance_to_poi(vec3_t start, vec3_t end)
if (gi.GetPathToGoal(request, info))
return info.pathDistSqr;
if (info.returnCode == PathReturnCode::NoNavAvailable)
return (end - start).lengthSquared();
return std::numeric_limits<float>::infinity();
}
@ -1699,6 +1710,13 @@ USE(target_poi_use) (edict_t *ent, edict_t *other, edict_t *activator) -> void
else
return;
}
// copy over POI stage value
if (ent->count)
{
if (level.current_poi_stage <= ent->count)
level.current_poi_stage = ent->count;
}
}
else
{

View file

@ -480,7 +480,7 @@ constexpr spawnflags_t SPAWNFLAG_PUSH_START_OFF = 0x08_spawnflag;
constexpr spawnflags_t SPAWNFLAG_PUSH_CLIP = 0x10_spawnflag;
// PGM
static int windsound;
static cached_soundindex windsound;
TOUCH(trigger_push_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
@ -601,7 +601,7 @@ void SP_trigger_push(edict_t *self)
{
InitTrigger(self);
if (!(self->spawnflags & SPAWNFLAG_PUSH_SILENT))
windsound = gi.soundindex("misc/windfly.wav");
windsound.assign("misc/windfly.wav");
self->touch = trigger_push_touch;
// RAFAEL
@ -1054,11 +1054,11 @@ TOUCH(trigger_fog_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool
if (self->spawnflags.has(SPAWNFLAG_FOG_AFFECT_FOG))
{
other->client->pers.wanted_fog = {
lerp(fog_value_storage->fog.density, fog_value_storage->fog.density_off, dist),
lerp(fog_value_storage->fog.color[0], fog_value_storage->fog.color_off[0], dist),
lerp(fog_value_storage->fog.color[1], fog_value_storage->fog.color_off[1], dist),
lerp(fog_value_storage->fog.color[2], fog_value_storage->fog.color_off[2], dist),
lerp(fog_value_storage->fog.sky_factor, fog_value_storage->fog.sky_factor_off, dist)
lerp(fog_value_storage->fog.density_off, fog_value_storage->fog.density, dist),
lerp(fog_value_storage->fog.color_off[0], fog_value_storage->fog.color[0], dist),
lerp(fog_value_storage->fog.color_off[1], fog_value_storage->fog.color[1], dist),
lerp(fog_value_storage->fog.color_off[2], fog_value_storage->fog.color[2], dist),
lerp(fog_value_storage->fog.sky_factor_off, fog_value_storage->fog.sky_factor, dist)
};
}
@ -1066,19 +1066,19 @@ TOUCH(trigger_fog_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool
{
other->client->pers.wanted_heightfog = {
{
lerp(fog_value_storage->heightfog.start_color[0], fog_value_storage->heightfog.start_color_off[0], dist),
lerp(fog_value_storage->heightfog.start_color[1], fog_value_storage->heightfog.start_color_off[1], dist),
lerp(fog_value_storage->heightfog.start_color[2], fog_value_storage->heightfog.start_color_off[2], dist),
lerp(fog_value_storage->heightfog.start_dist, fog_value_storage->heightfog.start_dist_off, dist)
lerp(fog_value_storage->heightfog.start_color_off[0], fog_value_storage->heightfog.start_color[0], dist),
lerp(fog_value_storage->heightfog.start_color_off[1], fog_value_storage->heightfog.start_color[1], dist),
lerp(fog_value_storage->heightfog.start_color_off[2], fog_value_storage->heightfog.start_color[2], dist),
lerp(fog_value_storage->heightfog.start_dist_off, fog_value_storage->heightfog.start_dist, dist)
},
{
lerp(fog_value_storage->heightfog.end_color[0], fog_value_storage->heightfog.end_color_off[0], dist),
lerp(fog_value_storage->heightfog.end_color[1], fog_value_storage->heightfog.end_color_off[1], dist),
lerp(fog_value_storage->heightfog.end_color[2], fog_value_storage->heightfog.end_color_off[2], dist),
lerp(fog_value_storage->heightfog.end_dist, fog_value_storage->heightfog.end_dist_off, dist)
lerp(fog_value_storage->heightfog.end_color_off[0], fog_value_storage->heightfog.end_color[0], dist),
lerp(fog_value_storage->heightfog.end_color_off[1], fog_value_storage->heightfog.end_color[1], dist),
lerp(fog_value_storage->heightfog.end_color_off[2], fog_value_storage->heightfog.end_color[2], dist),
lerp(fog_value_storage->heightfog.end_dist_off, fog_value_storage->heightfog.end_dist, dist)
},
lerp(fog_value_storage->heightfog.falloff, fog_value_storage->heightfog.falloff_off, dist),
lerp(fog_value_storage->heightfog.density, fog_value_storage->heightfog.density_off, dist)
lerp(fog_value_storage->heightfog.falloff_off, fog_value_storage->heightfog.falloff, dist),
lerp(fog_value_storage->heightfog.density_off, fog_value_storage->heightfog.density, dist)
};
}

View file

@ -447,8 +447,13 @@ void G_TouchTriggers(edict_t *ent)
// to see if we need to collide against them
void G_TouchProjectiles(edict_t *ent, vec3_t previous_origin)
{
struct skipped_projectile
{
edict_t *projectile;
int32_t spawn_count;
};
// a bit ugly, but we'll store projectiles we are ignoring here.
static std::vector<edict_t *> skipped;
static std::vector<skipped_projectile> skipped;
while (true)
{
@ -462,7 +467,7 @@ void G_TouchProjectiles(edict_t *ent, vec3_t previous_origin)
// always skip this projectile since certain conditions may cause the projectile
// to not disappear immediately
tr.ent->svflags &= ~SVF_PROJECTILE;
skipped.push_back(tr.ent);
skipped.push_back({ tr.ent, tr.ent->spawn_count });
// if we're both players and it's coop, allow the projectile to "pass" through
if (ent->client && tr.ent->owner && tr.ent->owner->client && !G_ShouldPlayersCollide(true))
@ -472,7 +477,8 @@ void G_TouchProjectiles(edict_t *ent, vec3_t previous_origin)
}
for (auto &skip : skipped)
skip->svflags |= SVF_PROJECTILE;
if (skip.projectile->inuse && skip.projectile->spawn_count == skip.spawn_count)
skip.projectile->svflags |= SVF_PROJECTILE;
skipped.clear();
}
@ -526,7 +532,7 @@ bool KillBox(edict_t *ent, bool from_spawning, mod_id_t mod, bool bsp_clipping)
if (hit == ent)
continue;
else if (!hit->inuse || !hit->takedamage || !hit->solid || hit->solid == SOLID_TRIGGER)
else if (!hit->inuse || !hit->takedamage || !hit->solid || hit->solid == SOLID_TRIGGER || hit->solid == SOLID_BSP)
continue;
else if (hit->client && !(mask & CONTENTS_PLAYER))
continue;
@ -539,6 +545,16 @@ bool KillBox(edict_t *ent, bool from_spawning, mod_id_t mod, bool bsp_clipping)
continue;
}
// [Paril-KEX] don't allow telefragging of friends in coop.
// the player that is about to be telefragged will have collision
// disabled until another time.
if (ent->client && hit->client && coop->integer)
{
hit->clipmask &= ~CONTENTS_PLAYER;
ent->clipmask &= ~CONTENTS_PLAYER;
continue;
}
T_Damage(hit, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, mod);
}

View file

@ -863,7 +863,7 @@ void fire_rail(edict_t *self, const vec3_t &start, const vec3_t &aimdir, int dam
if (binary_positional_search(org, start, args.tr.endpos, gi.inPHS, 3))
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(g_instagib->integer ? TE_RAILTRAIL2 : TE_RAILTRAIL);
gi.WriteByte((deathmatch->integer && g_instagib->integer) ? TE_RAILTRAIL2 : TE_RAILTRAIL);
gi.WritePosition(start);
gi.WritePosition(args.tr.endpos);
gi.unicast(player, false, unicast_key);

View file

@ -110,7 +110,7 @@ constexpr bit_t<n> bit_v = 1ull << n;
// game.h -- game dll information visible to server
// PARIL_NEW_API - value likely not used by any other Q2-esque engine in the wild
constexpr int32_t GAME_API_VERSION = 2022;
constexpr int32_t GAME_API_VERSION = 2023;
constexpr int32_t CGAME_API_VERSION = 2022;
// forward declarations
@ -238,6 +238,7 @@ enum contents_t : uint32_t
// remaining contents are non-visible, and don't eat brushes
CONTENTS_NO_WATERJUMP = bit_v<13>, // [Paril-KEX] this brush cannot be waterjumped out of
CONTENTS_PROJECTILECLIP = bit_v<14>, // [Paril-KEX] projectiles will collide with this
CONTENTS_AREAPORTAL = bit_v<15>,
@ -504,6 +505,7 @@ struct pmove_t
gvec4_t screen_blend;
refdef_flags_t rdflags; // merged with rdflags from server
bool jump_sound; // play jump sound
bool step_clip; // we clipped on top of an object from below
float impact_delta; // impact delta, for falling damage
};
@ -1740,8 +1742,6 @@ enum sv_ent_flags_t : uint64_t {
};
MAKE_ENUM_BITFLAGS( sv_ent_flags_t );
#include <bitset>
static constexpr int Max_Armor_Types = 3;
struct armorInfo_t {
@ -1780,7 +1780,6 @@ struct sv_entity_t {
char netname[ MAX_NETNAME ];
int32_t inventory[ MAX_ITEMS ] = { 0 };
armorInfo_t armor_info[ Max_Armor_Types ];
std::bitset<MAX_CLIENTS> pickedup_list;
};
#ifndef GAME_INCLUDE
@ -2005,9 +2004,10 @@ struct game_import_t
void (*Draw_Bounds)(gvec3_cref_t mins, gvec3_cref_t maxs, const rgba_t &color, const float lifeTime, const bool depthTest);
void (*Draw_Sphere)(gvec3_cref_t origin, const float radius, const rgba_t &color, const float lifeTime, const bool depthTest);
void (*Draw_OrientedWorldText)(gvec3_cref_t origin, const char * text, const rgba_t &color, const float size, const float lifeTime, const bool depthTest);
void (*Draw_StaticWorldText )(gvec3_cref_t origin, gvec3_cref_t angles, const char * text, const rgba_t & color, const float size, const float lifeTime, const bool depthTest);
void (*Draw_StaticWorldText)(gvec3_cref_t origin, gvec3_cref_t angles, const char * text, const rgba_t & color, const float size, const float lifeTime, const bool depthTest);
void (*Draw_Cylinder)(gvec3_cref_t origin, const float halfHeight, const float radius, const rgba_t &color, const float lifeTime, const bool depthTest);
void (*Draw_Ray)(gvec3_cref_t origin, gvec3_cref_t direction, const float length, const float size, const rgba_t &color, const float lifeTime, const bool depthTest);
void (*Draw_Arrow)(gvec3_cref_t start, gvec3_cref_t end, const float size, const rgba_t & lineColor, const rgba_t & arrowColor, const float lifeTime, const bool depthTest);
// scoreboard
void (*ReportMatchDetails_Multicast)(bool is_end);
@ -2139,6 +2139,8 @@ struct game_export_t
void (*Bot_TriggerEdict)(edict_t * botEdict, edict_t * edict);
void (*Bot_UseItem)(edict_t * botEdict, const int32_t itemID);
int32_t (*Bot_GetItemID)(const char * classname);
void (*Edict_ForceLookAtPoint)(edict_t * edict, gvec3_cref_t point);
bool (*Bot_PickedUpItem )(edict_t * botEdict, edict_t * itemEdict);
// [KEX]: Checks entity visibility instancing
bool (*Entity_IsVisibleToPlayer)(edict_t* ent, edict_t* player);
@ -2167,7 +2169,7 @@ struct cg_server_data_t
std::array<int16_t, MAX_ITEMS> inventory;
};
constexpr int32_t PROTOCOL_VERSION_3XX = 34;
constexpr int32_t PROTOCOL_VERSION_3XX = 34;
constexpr int32_t PROTOCOL_VERSION_DEMOS = 2022;
constexpr int32_t PROTOCOL_VERSION = 2023;
@ -2260,7 +2262,7 @@ struct cgame_export_t
int apiversion;
// the init/shutdown functions will be called between levels/connections
// (cgame does not run in menus)
// and when the client initially loads.
void (*Init)();
void (*Shutdown)();

View file

@ -12,9 +12,9 @@ TANK
#include "m_arachnid.h"
#include "m_flash.h"
static int sound_pain;
static int sound_death;
static int sound_sight;
static cached_soundindex sound_pain;
static cached_soundindex sound_death;
static cached_soundindex sound_sight;
MONSTERINFO_SIGHT(arachnid_sight) (edict_t *self, edict_t *other) -> void
{
@ -51,7 +51,7 @@ MONSTERINFO_STAND(arachnid_stand) (edict_t *self) -> void
// walk
//
static int sound_step;
static cached_soundindex sound_step;
void arachnid_footstep(edict_t *self)
{
@ -148,7 +148,7 @@ PAIN(arachnid_pain) (edict_t *self, edict_t *other, float kick, int damage, cons
M_SetAnimation(self, &arachnid_move_pain2);
}
static int sound_charge;
static cached_soundindex sound_charge;
void arachnid_charge_rail(edict_t *self)
{
@ -229,7 +229,7 @@ mframe_t arachnid_frames_attack_up1[] = {
};
MMOVE_T(arachnid_attack_up1) = { FRAME_rails_up1, FRAME_rails_up16, arachnid_frames_attack_up1, arachnid_run };
static int sound_melee, sound_melee_hit;
static cached_soundindex sound_melee, sound_melee_hit;
void arachnid_melee_charge(edict_t *self)
{
@ -348,13 +348,13 @@ void SP_monster_arachnid(edict_t *self)
return;
}
sound_step = gi.soundindex("insane/insane11.wav");
sound_charge = gi.soundindex("gladiator/railgun.wav");
sound_melee = gi.soundindex("gladiator/melee3.wav");
sound_melee_hit = gi.soundindex("gladiator/melee2.wav");
sound_pain = gi.soundindex("arachnid/pain.wav");
sound_death = gi.soundindex("arachnid/death.wav");
sound_sight = gi.soundindex("arachnid/sight.wav");
sound_step.assign("insane/insane11.wav");
sound_charge.assign("gladiator/railgun.wav");
sound_melee.assign("gladiator/melee3.wav");
sound_melee_hit.assign("gladiator/melee2.wav");
sound_pain.assign("arachnid/pain.wav");
sound_death.assign("arachnid/death.wav");
sound_sight.assign("arachnid/sight.wav");
self->s.modelindex = gi.modelindex("models/monsters/arachnid/tris.md2");
self->mins = { -48, -48, -20 };

View file

@ -13,15 +13,16 @@ BERSERK
constexpr spawnflags_t SPAWNFLAG_BERSERK_NOJUMPING = 8_spawnflag;
static int sound_pain;
static int sound_die;
static int sound_idle;
static int sound_idle2;
static int sound_punch;
static int sound_sight;
static int sound_search;
static int sound_thud;
static int sound_jump;
static cached_soundindex sound_pain;
static cached_soundindex sound_die;
static cached_soundindex sound_idle;
static cached_soundindex sound_idle2;
static cached_soundindex sound_punch;
static cached_soundindex sound_sight;
static cached_soundindex sound_search;
static cached_soundindex sound_thud;
static cached_soundindex sound_explod;
static cached_soundindex sound_jump;
MONSTERINFO_SIGHT(berserk_sight) (edict_t *self, edict_t *other) -> void
{
@ -215,7 +216,7 @@ void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, flo
vec3_t v;
vec3_t dir;
while ((ent = findradius(ent, inflictor->s.origin, radius)) != nullptr)
while ((ent = findradius(ent, inflictor->s.origin, radius * 2.f)) != nullptr)
{
if (ent == ignore)
continue;
@ -223,18 +224,29 @@ void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, flo
continue;
if (!CanDamage(ent, inflictor))
continue;
// don't hit players in mid air
if (ent->client && !ent->groundentity)
continue;
v = closest_point_to_box(point, ent->s.origin + ent->mins, ent->s.origin + ent->maxs) - point;
points = damage - 0.5f * v.length();
if (ent == attacker)
points = points * 0.5f;
points = max(1.f, points);
// calculate contribution amount
float amount = min(1.f, 1.f - (v.length() / radius));
// too far away
if (amount <= 0.f)
continue;
amount *= amount;
// damage & kick are exponentially scaled
points = max(1.f, damage * amount);
dir = (ent->s.origin - point).normalized();
// keep the point at their feet so they always get knocked up
point[2] = ent->absmin[2];
T_Damage(ent, inflictor, attacker, dir, point, dir, (int) points, (int) kick,
T_Damage(ent, inflictor, attacker, dir, point, dir, (int) points, (int) (kick * amount),
DAMAGE_RADIUS, mod);
if (ent->client)
@ -245,6 +257,7 @@ void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, flo
static void berserk_attack_slam(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
gi.sound(self, CHAN_AUTO, sound_explod, 0.75f, ATTN_NORM, 0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_BERSERK_SLAM);
vec3_t f, r, start;
@ -258,7 +271,7 @@ static void berserk_attack_slam(edict_t *self)
self->velocity = {};
self->flags |= FL_KILL_VELOCITY;
T_SlamRadiusDamage(tr.endpos, self, self, 35, 150.f, self, 275, MOD_UNKNOWN);
T_SlamRadiusDamage(tr.endpos, self, self, 8, 300.f, self, 165, MOD_UNKNOWN);
}
TOUCH(berserk_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
@ -309,7 +322,6 @@ void berserk_jump_takeoff(edict_t *self)
self->monsterinfo.aiflags |= AI_DUCKED;
self->monsterinfo.attack_finished = level.time + 3_sec;
self->touch = berserk_jump_touch;
gi.sound(self, CHAN_WEAPON, sound_jump, 1, ATTN_NORM, 0);
berserk_high_gravity(self);
}
@ -332,15 +344,15 @@ void berserk_check_landing(edict_t *self)
}
if (level.time > self->monsterinfo.attack_finished)
self->monsterinfo.nextframe = FRAME_slam2;
self->monsterinfo.nextframe = FRAME_slam3;
else
self->monsterinfo.nextframe = FRAME_slam5;
}
mframe_t berserk_frames_attack_strike[] = {
{ ai_charge },
{ ai_charge, 0, berserk_jump_takeoff },
{ ai_move, 0, berserk_high_gravity },
{ ai_charge },
{ ai_move, 0, berserk_jump_takeoff },
{ ai_move, 0, berserk_high_gravity },
{ ai_move, 0, berserk_check_landing },
{ ai_move, 0, monster_footstep },
@ -440,6 +452,7 @@ MONSTERINFO_ATTACK(berserk_attack) (edict_t *self) -> void
{
M_SetAnimation(self, &berserk_move_attack_strike);
// don't do this for a while, otherwise we just keep doing it
gi.sound(self, CHAN_WEAPON, sound_jump, 1, ATTN_NORM, 0);
self->timestamp = level.time + 5_sec;
}
else if (self->monsterinfo.active_move == &berserk_move_run1 && (range_to(self, self->enemy) <= RANGE_NEAR))
@ -763,15 +776,16 @@ void SP_monster_berserk(edict_t *self)
}
// pre-caches
sound_pain = gi.soundindex("berserk/berpain2.wav");
sound_die = gi.soundindex("berserk/berdeth2.wav");
sound_idle = gi.soundindex("berserk/beridle1.wav");
sound_idle2 = gi.soundindex("berserk/idle.wav");
sound_punch = gi.soundindex("berserk/attack.wav");
sound_search = gi.soundindex("berserk/bersrch1.wav");
sound_sight = gi.soundindex("berserk/sight.wav");
sound_thud = gi.soundindex("mutant/thud1.wav");
sound_jump = gi.soundindex("berserk/jump.wav");
sound_pain.assign("berserk/berpain2.wav");
sound_die.assign("berserk/berdeth2.wav");
sound_idle.assign("berserk/beridle1.wav");
sound_idle2.assign("berserk/idle.wav");
sound_punch.assign("berserk/attack.wav");
sound_search.assign("berserk/bersrch1.wav");
sound_sight.assign("berserk/sight.wav");
sound_thud.assign("mutant/thud1.wav");
sound_explod.assign("world/explod2.wav");
sound_jump.assign("berserk/jump.wav");
self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2");

View file

@ -17,11 +17,11 @@ constexpr spawnflags_t SPAWNFLAG_BOSS2_N64 = 8_spawnflag;
bool infront(edict_t *self, edict_t *other);
static int sound_pain1;
static int sound_pain2;
static int sound_pain3;
static int sound_death;
static int sound_search1;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_pain3;
static cached_soundindex sound_death;
static cached_soundindex sound_search1;
MONSTERINFO_SEARCH(boss2_search) (edict_t *self) -> void
{
@ -607,93 +607,10 @@ DIE(boss2_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage
M_SetAnimation(self, &boss2_move_death);
}
// [Paril-KEX] use generic function
MONSTERINFO_CHECKATTACK(Boss2_CheckAttack) (edict_t *self) -> bool
{
vec3_t spot1, spot2;
vec3_t temp;
float chance;
trace_t tr;
float enemy_yaw;
if (self->enemy->health > 0)
{
// see if any entities are in the way of the shot
spot1 = self->s.origin;
spot1[2] += self->viewheight;
spot2 = self->enemy->s.origin;
spot2[2] += self->enemy->viewheight;
tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_PLAYER | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA);
// do we have a clear shot?
if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER))
{
// PGM - we want them to go ahead and shoot at info_notnulls if they can.
if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM
return false;
}
}
float enemy_range = range_to(self, self->enemy);
temp = self->enemy->s.origin - self->s.origin;
enemy_yaw = vectoyaw(temp);
self->ideal_yaw = enemy_yaw;
// melee attack
if (enemy_range <= RANGE_MELEE)
{
if (self->monsterinfo.melee)
self->monsterinfo.attack_state = AS_MELEE;
else
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
// missile attack
if (!self->monsterinfo.attack)
return false;
if (level.time < self->monsterinfo.attack_finished)
return false;
if (enemy_range > RANGE_MID)
return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.4f;
}
else if (enemy_range <= RANGE_MELEE)
{
chance = 0.8f;
}
else if (enemy_range <= RANGE_NEAR)
{
chance = 0.8f;
}
else
{
chance = 0.8f;
}
// PGM - go ahead and shoot every time if it's a info_notnull
if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT))
{
self->monsterinfo.attack_state = AS_MISSILE;
self->monsterinfo.attack_finished = level.time + random_time(2_sec);
return true;
}
if (self->flags & FL_FLY)
{
if (frandom() < 0.3f)
self->monsterinfo.attack_state = AS_SLIDING;
else
self->monsterinfo.attack_state = AS_STRAIGHT;
}
return false;
return M_CheckAttack_Base(self, 0.4f, 0.8f, 0.8f, 0.8f, 0.f, 0.f);
}
/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight Hyperblaster
@ -705,11 +622,11 @@ void SP_monster_boss2(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("bosshovr/bhvpain1.wav");
sound_pain2 = gi.soundindex("bosshovr/bhvpain2.wav");
sound_pain3 = gi.soundindex("bosshovr/bhvpain3.wav");
sound_death = gi.soundindex("bosshovr/bhvdeth1.wav");
sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav");
sound_pain1.assign("bosshovr/bhvpain1.wav");
sound_pain2.assign("bosshovr/bhvpain2.wav");
sound_pain3.assign("bosshovr/bhvpain3.wav");
sound_death.assign("bosshovr/bhvdeth1.wav");
sound_search1.assign("bosshovr/bhvunqv1.wav");
gi.soundindex("tank/rocket.wav");

View file

@ -14,20 +14,20 @@ jorg
void SP_monster_makron(edict_t *self);
static int sound_pain1;
static int sound_pain2;
static int sound_pain3;
static int sound_idle;
static int sound_death;
static int sound_search1;
static int sound_search2;
static int sound_search3;
static int sound_attack1, sound_attack1_loop, sound_attack1_end;
static int sound_attack2, sound_bfg_fire;
static int sound_firegun;
static int sound_step_left;
static int sound_step_right;
static int sound_death_hit;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_pain3;
static cached_soundindex sound_idle;
static cached_soundindex sound_death;
static cached_soundindex sound_search1;
static cached_soundindex sound_search2;
static cached_soundindex sound_search3;
static cached_soundindex sound_attack1, sound_attack1_loop, sound_attack1_end;
static cached_soundindex sound_attack2, sound_bfg_fire;
static cached_soundindex sound_firegun;
static cached_soundindex sound_step_left;
static cached_soundindex sound_step_right;
static cached_soundindex sound_death_hit;
void MakronToss(edict_t *self);
@ -559,88 +559,10 @@ DIE(jorg_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage,
M_SetAnimation(self, &jorg_move_death);
}
// [Paril-KEX] use generic function
MONSTERINFO_CHECKATTACK(Jorg_CheckAttack) (edict_t *self) -> bool
{
vec3_t spot1, spot2;
vec3_t temp;
float chance;
trace_t tr;
float enemy_yaw;
if (self->enemy->health > 0)
{
// see if any entities are in the way of the shot
spot1 = self->s.origin;
spot1[2] += self->viewheight;
spot2 = self->enemy->s.origin;
spot2[2] += self->enemy->viewheight;
tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA);
// do we have a clear shot?
if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER))
return false;
}
float enemy_range = range_to(self, self->enemy);
temp = self->enemy->s.origin - self->s.origin;
enemy_yaw = vectoyaw(temp);
self->ideal_yaw = enemy_yaw;
// melee attack
if (enemy_range <= RANGE_MELEE)
{
if (self->monsterinfo.melee)
self->monsterinfo.attack_state = AS_MELEE;
else
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
// missile attack
if (!self->monsterinfo.attack)
return false;
if (level.time < self->monsterinfo.attack_finished)
return false;
if (enemy_range > RANGE_MID)
return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.4f;
}
else if (enemy_range <= RANGE_MELEE)
{
chance = 0.8f;
}
else if (enemy_range <= RANGE_NEAR)
{
chance = 0.4f;
}
else
{
chance = 0.2f;
}
if (frandom() < chance)
{
self->monsterinfo.attack_state = AS_MISSILE;
self->monsterinfo.attack_finished = level.time + random_time(2_sec);
return true;
}
if (self->flags & FL_FLY)
{
if (frandom() < 0.3f)
self->monsterinfo.attack_state = AS_SLIDING;
else
self->monsterinfo.attack_state = AS_STRAIGHT;
}
return false;
return M_CheckAttack_Base(self, 0.4f, 0.8f, 0.4f, 0.2f, 0.0f, 0.f);
}
void MakronPrecache();
@ -654,23 +576,23 @@ void SP_monster_jorg(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("boss3/bs3pain1.wav");
sound_pain2 = gi.soundindex("boss3/bs3pain2.wav");
sound_pain3 = gi.soundindex("boss3/bs3pain3.wav");
sound_death = gi.soundindex("boss3/bs3deth1.wav");
sound_attack1 = gi.soundindex("boss3/bs3atck1.wav");
sound_attack1_loop = gi.soundindex("boss3/bs3atck1_loop.wav");
sound_attack1_end = gi.soundindex("boss3/bs3atck1_end.wav");
sound_attack2 = gi.soundindex("boss3/bs3atck2.wav");
sound_search1 = gi.soundindex("boss3/bs3srch1.wav");
sound_search2 = gi.soundindex("boss3/bs3srch2.wav");
sound_search3 = gi.soundindex("boss3/bs3srch3.wav");
sound_idle = gi.soundindex("boss3/bs3idle1.wav");
sound_step_left = gi.soundindex("boss3/step1.wav");
sound_step_right = gi.soundindex("boss3/step2.wav");
sound_firegun = gi.soundindex("boss3/xfire.wav");
sound_death_hit = gi.soundindex("boss3/d_hit.wav");
sound_bfg_fire = gi.soundindex("makron/bfg_fire.wav");
sound_pain1.assign("boss3/bs3pain1.wav");
sound_pain2.assign("boss3/bs3pain2.wav");
sound_pain3.assign("boss3/bs3pain3.wav");
sound_death.assign("boss3/bs3deth1.wav");
sound_attack1.assign("boss3/bs3atck1.wav");
sound_attack1_loop.assign("boss3/bs3atck1_loop.wav");
sound_attack1_end.assign("boss3/bs3atck1_end.wav");
sound_attack2.assign("boss3/bs3atck2.wav");
sound_search1.assign("boss3/bs3srch1.wav");
sound_search2.assign("boss3/bs3srch2.wav");
sound_search3.assign("boss3/bs3srch3.wav");
sound_idle.assign("boss3/bs3idle1.wav");
sound_step_left.assign("boss3/step1.wav");
sound_step_right.assign("boss3/step2.wav");
sound_firegun.assign("boss3/xfire.wav");
sound_death_hit.assign("boss3/d_hit.wav");
sound_bfg_fire.assign("makron/bfg_fire.wav");
MakronPrecache();

View file

@ -20,20 +20,20 @@ void makron_step_right(edict_t *self);
void makronBFG(edict_t *self);
void makron_dead(edict_t *self);
static int sound_pain4;
static int sound_pain5;
static int sound_pain6;
static int sound_death;
static int sound_step_left;
static int sound_step_right;
static int sound_attack_bfg;
static int sound_brainsplorch;
static int sound_prerailgun;
static int sound_popup;
static int sound_taunt1;
static int sound_taunt2;
static int sound_taunt3;
static int sound_hit;
static cached_soundindex sound_pain4;
static cached_soundindex sound_pain5;
static cached_soundindex sound_pain6;
static cached_soundindex sound_death;
static cached_soundindex sound_step_left;
static cached_soundindex sound_step_right;
static cached_soundindex sound_attack_bfg;
static cached_soundindex sound_brainsplorch;
static cached_soundindex sound_prerailgun;
static cached_soundindex sound_popup;
static cached_soundindex sound_taunt1;
static cached_soundindex sound_taunt2;
static cached_soundindex sound_taunt3;
static cached_soundindex sound_hit;
void makron_taunt(edict_t *self)
{
@ -692,88 +692,10 @@ DIE(makron_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag
self->maxs = { 60, 60, 48 };
}
// [Paril-KEX] use generic function
MONSTERINFO_CHECKATTACK(Makron_CheckAttack) (edict_t *self) -> bool
{
vec3_t spot1, spot2;
vec3_t temp;
float chance;
trace_t tr;
float enemy_yaw;
if (self->enemy->health > 0)
{
// see if any entities are in the way of the shot
spot1 = self->s.origin;
spot1[2] += self->viewheight;
spot2 = self->enemy->s.origin;
spot2[2] += self->enemy->viewheight;
tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_PLAYER | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA);
// do we have a clear shot?
if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER))
return false;
}
float enemy_range = range_to(self, self->enemy);
temp = self->enemy->s.origin - self->s.origin;
enemy_yaw = vectoyaw(temp);
self->ideal_yaw = enemy_yaw;
// melee attack
if (enemy_range <= RANGE_MELEE)
{
if (self->monsterinfo.melee)
self->monsterinfo.attack_state = AS_MELEE;
else
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
// missile attack
if (!self->monsterinfo.attack)
return false;
if (level.time < self->monsterinfo.attack_finished)
return false;
if (enemy_range > RANGE_MID)
return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.4f;
}
else if (enemy_range <= RANGE_MELEE)
{
chance = 0.8f;
}
else if (enemy_range <= RANGE_NEAR)
{
chance = 0.4f;
}
else
{
chance = 0.2f;
}
if (frandom() < chance)
{
self->monsterinfo.attack_state = AS_MISSILE;
self->monsterinfo.attack_finished = level.time + random_time(2_sec);
return true;
}
if (self->flags & FL_FLY)
{
if (frandom() < 0.3f)
self->monsterinfo.attack_state = AS_SLIDING;
else
self->monsterinfo.attack_state = AS_STRAIGHT;
}
return false;
return M_CheckAttack_Base(self, 0.4f, 0.8f, 0.4f, 0.2f, 0.0f, 0.f);
}
//
@ -782,20 +704,20 @@ MONSTERINFO_CHECKATTACK(Makron_CheckAttack) (edict_t *self) -> bool
void MakronPrecache()
{
sound_pain4 = gi.soundindex("makron/pain3.wav");
sound_pain5 = gi.soundindex("makron/pain2.wav");
sound_pain6 = gi.soundindex("makron/pain1.wav");
sound_death = gi.soundindex("makron/death.wav");
sound_step_left = gi.soundindex("makron/step1.wav");
sound_step_right = gi.soundindex("makron/step2.wav");
sound_attack_bfg = gi.soundindex("makron/bfg_fire.wav");
sound_brainsplorch = gi.soundindex("makron/brain1.wav");
sound_prerailgun = gi.soundindex("makron/rail_up.wav");
sound_popup = gi.soundindex("makron/popup.wav");
sound_taunt1 = gi.soundindex("makron/voice4.wav");
sound_taunt2 = gi.soundindex("makron/voice3.wav");
sound_taunt3 = gi.soundindex("makron/voice.wav");
sound_hit = gi.soundindex("makron/bhit.wav");
sound_pain4.assign("makron/pain3.wav");
sound_pain5.assign("makron/pain2.wav");
sound_pain6.assign("makron/pain1.wav");
sound_death.assign("makron/death.wav");
sound_step_left.assign("makron/step1.wav");
sound_step_right.assign("makron/step2.wav");
sound_attack_bfg.assign("makron/bfg_fire.wav");
sound_brainsplorch.assign("makron/brain1.wav");
sound_prerailgun.assign("makron/rail_up.wav");
sound_popup.assign("makron/popup.wav");
sound_taunt1.assign("makron/voice4.wav");
sound_taunt2.assign("makron/voice3.wav");
sound_taunt3.assign("makron/voice.wav");
sound_hit.assign("makron/bhit.wav");
gi.modelindex("models/monsters/boss3/rider/tris.md2");
}

View file

@ -11,20 +11,20 @@ brain
#include "g_local.h"
#include "m_brain.h"
static int sound_chest_open;
static int sound_tentacles_extend;
static int sound_tentacles_retract;
static int sound_death;
static int sound_idle1;
static int sound_idle2;
static int sound_idle3;
static int sound_pain1;
static int sound_pain2;
static int sound_sight;
static int sound_search;
static int sound_melee1;
static int sound_melee2;
static int sound_melee3;
static cached_soundindex sound_chest_open;
static cached_soundindex sound_tentacles_extend;
static cached_soundindex sound_tentacles_retract;
static cached_soundindex sound_death;
static cached_soundindex sound_idle1;
static cached_soundindex sound_idle2;
static cached_soundindex sound_idle3;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_sight;
static cached_soundindex sound_search;
static cached_soundindex sound_melee1;
static cached_soundindex sound_melee2;
static cached_soundindex sound_melee3;
MONSTERINFO_SIGHT(brain_sight) (edict_t *self, edict_t *other) -> void
{
@ -321,11 +321,9 @@ mframe_t brain_frames_attack1[] = {
};
MMOVE_T(brain_move_attack1) = { FRAME_attak101, FRAME_attak118, brain_frames_attack1, brain_run };
constexpr spawnflags_t SPAWNFLAG_BRAIN_TENTACLES_HIT = 65536_spawnflag;
void brain_chest_open(edict_t *self)
{
self->spawnflags &= ~SPAWNFLAG_BRAIN_TENTACLES_HIT;
self->count = 0;
self->monsterinfo.power_armor_type = IT_NULL;
gi.sound(self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0);
}
@ -334,7 +332,7 @@ void brain_tentacle_attack(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, 0, 8 };
if (fire_hit(self, aim, irandom(10, 15), -600))
self->spawnflags |= SPAWNFLAG_BRAIN_TENTACLES_HIT;
self->count = 1;
else
self->monsterinfo.melee_debounce_time = level.time + 3_sec;
gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
@ -343,9 +341,9 @@ void brain_tentacle_attack(edict_t *self)
void brain_chest_closed(edict_t *self)
{
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN;
if (self->spawnflags.has(SPAWNFLAG_BRAIN_TENTACLES_HIT))
if (self->count)
{
self->spawnflags &= ~SPAWNFLAG_BRAIN_TENTACLES_HIT;
self->count = 0;
M_SetAnimation(self, &brain_move_attack1);
}
}
@ -731,20 +729,20 @@ void SP_monster_brain(edict_t *self)
return;
}
sound_chest_open = gi.soundindex("brain/brnatck1.wav");
sound_tentacles_extend = gi.soundindex("brain/brnatck2.wav");
sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav");
sound_death = gi.soundindex("brain/brndeth1.wav");
sound_idle1 = gi.soundindex("brain/brnidle1.wav");
sound_idle2 = gi.soundindex("brain/brnidle2.wav");
sound_idle3 = gi.soundindex("brain/brnlens1.wav");
sound_pain1 = gi.soundindex("brain/brnpain1.wav");
sound_pain2 = gi.soundindex("brain/brnpain2.wav");
sound_sight = gi.soundindex("brain/brnsght1.wav");
sound_search = gi.soundindex("brain/brnsrch1.wav");
sound_melee1 = gi.soundindex("brain/melee1.wav");
sound_melee2 = gi.soundindex("brain/melee2.wav");
sound_melee3 = gi.soundindex("brain/melee3.wav");
sound_chest_open.assign("brain/brnatck1.wav");
sound_tentacles_extend.assign("brain/brnatck2.wav");
sound_tentacles_retract.assign("brain/brnatck3.wav");
sound_death.assign("brain/brndeth1.wav");
sound_idle1.assign("brain/brnidle1.wav");
sound_idle2.assign("brain/brnidle2.wav");
sound_idle3.assign("brain/brnlens1.wav");
sound_pain1.assign("brain/brnpain1.wav");
sound_pain2.assign("brain/brnpain2.wav");
sound_sight.assign("brain/brnsght1.wav");
sound_search.assign("brain/brnsrch1.wav");
sound_melee1.assign("brain/melee1.wav");
sound_melee2.assign("brain/melee2.wav");
sound_melee3.assign("brain/melee3.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;

View file

@ -18,21 +18,21 @@ void chick_reslash(edict_t *self);
void chick_rerocket(edict_t *self);
void chick_attack1(edict_t *self);
static int sound_missile_prelaunch;
static int sound_missile_launch;
static int sound_melee_swing;
static int sound_melee_hit;
static int sound_missile_reload;
static int sound_death1;
static int sound_death2;
static int sound_fall_down;
static int sound_idle1;
static int sound_idle2;
static int sound_pain1;
static int sound_pain2;
static int sound_pain3;
static int sound_sight;
static int sound_search;
static cached_soundindex sound_missile_prelaunch;
static cached_soundindex sound_missile_launch;
static cached_soundindex sound_melee_swing;
static cached_soundindex sound_melee_hit;
static cached_soundindex sound_missile_reload;
static cached_soundindex sound_death1;
static cached_soundindex sound_death2;
static cached_soundindex sound_fall_down;
static cached_soundindex sound_idle1;
static cached_soundindex sound_idle2;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_pain3;
static cached_soundindex sound_sight;
static cached_soundindex sound_search;
void ChickMoan(edict_t *self)
{
@ -795,21 +795,21 @@ void SP_monster_chick(edict_t *self)
return;
}
sound_missile_prelaunch = gi.soundindex("chick/chkatck1.wav");
sound_missile_launch = gi.soundindex("chick/chkatck2.wav");
sound_melee_swing = gi.soundindex("chick/chkatck3.wav");
sound_melee_hit = gi.soundindex("chick/chkatck4.wav");
sound_missile_reload = gi.soundindex("chick/chkatck5.wav");
sound_death1 = gi.soundindex("chick/chkdeth1.wav");
sound_death2 = gi.soundindex("chick/chkdeth2.wav");
sound_fall_down = gi.soundindex("chick/chkfall1.wav");
sound_idle1 = gi.soundindex("chick/chkidle1.wav");
sound_idle2 = gi.soundindex("chick/chkidle2.wav");
sound_pain1 = gi.soundindex("chick/chkpain1.wav");
sound_pain2 = gi.soundindex("chick/chkpain2.wav");
sound_pain3 = gi.soundindex("chick/chkpain3.wav");
sound_sight = gi.soundindex("chick/chksght1.wav");
sound_search = gi.soundindex("chick/chksrch1.wav");
sound_missile_prelaunch.assign("chick/chkatck1.wav");
sound_missile_launch.assign("chick/chkatck2.wav");
sound_melee_swing.assign("chick/chkatck3.wav");
sound_melee_hit.assign("chick/chkatck4.wav");
sound_missile_reload.assign("chick/chkatck5.wav");
sound_death1.assign("chick/chkdeth1.wav");
sound_death2.assign("chick/chkdeth2.wav");
sound_fall_down.assign("chick/chkfall1.wav");
sound_idle1.assign("chick/chkidle1.wav");
sound_idle2.assign("chick/chkidle2.wav");
sound_pain1.assign("chick/chkpain1.wav");
sound_pain2.assign("chick/chkpain2.wav");
sound_pain3.assign("chick/chkpain3.wav");
sound_sight.assign("chick/chksght1.wav");
sound_search.assign("chick/chksrch1.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;

View file

@ -11,14 +11,14 @@ FLIPPER
#include "g_local.h"
#include "m_flipper.h"
static int sound_chomp;
static int sound_attack;
static int sound_pain1;
static int sound_pain2;
static int sound_death;
static int sound_idle;
static int sound_search;
static int sound_sight;
static cached_soundindex sound_chomp;
static cached_soundindex sound_attack;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_death;
static cached_soundindex sound_idle;
static cached_soundindex sound_search;
static cached_soundindex sound_sight;
mframe_t flipper_frames_stand[] = {
{ ai_stand }
@ -343,14 +343,14 @@ void SP_monster_flipper(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("flipper/flppain1.wav");
sound_pain2 = gi.soundindex("flipper/flppain2.wav");
sound_death = gi.soundindex("flipper/flpdeth1.wav");
sound_chomp = gi.soundindex("flipper/flpatck1.wav");
sound_attack = gi.soundindex("flipper/flpatck2.wav");
sound_idle = gi.soundindex("flipper/flpidle1.wav");
sound_search = gi.soundindex("flipper/flpsrch1.wav");
sound_sight = gi.soundindex("flipper/flpsght1.wav");
sound_pain1.assign("flipper/flppain1.wav");
sound_pain2.assign("flipper/flppain2.wav");
sound_death.assign("flipper/flpdeth1.wav");
sound_chomp.assign("flipper/flpatck1.wav");
sound_attack.assign("flipper/flpatck2.wav");
sound_idle.assign("flipper/flpidle1.wav");
sound_search.assign("flipper/flpsrch1.wav");
sound_sight.assign("flipper/flpsght1.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;

View file

@ -12,13 +12,13 @@ floater
#include "m_float.h"
#include "m_flash.h"
static int sound_attack2;
static int sound_attack3;
static int sound_death1;
static int sound_idle;
static int sound_pain1;
static int sound_pain2;
static int sound_sight;
static cached_soundindex sound_attack2;
static cached_soundindex sound_attack3;
static cached_soundindex sound_death1;
static cached_soundindex sound_idle;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_sight;
MONSTERINFO_SIGHT(floater_sight) (edict_t *self, edict_t *other) -> void
{
@ -658,13 +658,13 @@ void SP_monster_floater(edict_t *self)
return;
}
sound_attack2 = gi.soundindex("floater/fltatck2.wav");
sound_attack3 = gi.soundindex("floater/fltatck3.wav");
sound_death1 = gi.soundindex("floater/fltdeth1.wav");
sound_idle = gi.soundindex("floater/fltidle1.wav");
sound_pain1 = gi.soundindex("floater/fltpain1.wav");
sound_pain2 = gi.soundindex("floater/fltpain2.wav");
sound_sight = gi.soundindex("floater/fltsght1.wav");
sound_attack2.assign("floater/fltatck2.wav");
sound_attack3.assign("floater/fltatck3.wav");
sound_death1.assign("floater/fltdeth1.wav");
sound_idle.assign("floater/fltidle1.wav");
sound_pain1.assign("floater/fltpain1.wav");
sound_pain2.assign("floater/fltpain2.wav");
sound_sight.assign("floater/fltsght1.wav");
gi.soundindex("floater/fltatck1.wav");

View file

@ -12,13 +12,13 @@ flyer
#include "m_flyer.h"
#include "m_flash.h"
static int sound_sight;
static int sound_idle;
static int sound_pain1;
static int sound_pain2;
static int sound_slash;
static int sound_sproing;
static int sound_die;
static cached_soundindex sound_sight;
static cached_soundindex sound_idle;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_slash;
static cached_soundindex sound_sproing;
static cached_soundindex sound_die;
void flyer_check_melee(edict_t *self);
void flyer_loop_melee(edict_t *self);
@ -704,13 +704,13 @@ void SP_monster_flyer(edict_t *self)
return;
}
sound_sight = gi.soundindex("flyer/flysght1.wav");
sound_idle = gi.soundindex("flyer/flysrch1.wav");
sound_pain1 = gi.soundindex("flyer/flypain1.wav");
sound_pain2 = gi.soundindex("flyer/flypain2.wav");
sound_slash = gi.soundindex("flyer/flyatck2.wav");
sound_sproing = gi.soundindex("flyer/flyatck1.wav");
sound_die = gi.soundindex("flyer/flydeth1.wav");
sound_sight.assign("flyer/flysght1.wav");
sound_idle.assign("flyer/flysrch1.wav");
sound_pain1.assign("flyer/flypain1.wav");
sound_pain2.assign("flyer/flypain2.wav");
sound_slash.assign("flyer/flyatck2.wav");
sound_sproing.assign("flyer/flyatck1.wav");
sound_die.assign("flyer/flydeth1.wav");
gi.soundindex("flyer/flyatck3.wav");

View file

@ -12,18 +12,18 @@ GLADIATOR
#include "m_gladiator.h"
#include "m_flash.h"
static int sound_pain1;
static int sound_pain2;
static int sound_die;
static int sound_die2;
static int sound_gun;
static int sound_gunb;
static int sound_cleaver_swing;
static int sound_cleaver_hit;
static int sound_cleaver_miss;
static int sound_idle;
static int sound_search;
static int sound_sight;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_die;
static cached_soundindex sound_die2;
static cached_soundindex sound_gun;
static cached_soundindex sound_gunb;
static cached_soundindex sound_cleaver_swing;
static cached_soundindex sound_cleaver_hit;
static cached_soundindex sound_cleaver_miss;
static cached_soundindex sound_idle;
static cached_soundindex sound_search;
static cached_soundindex sound_sight;
MONSTERINFO_IDLE(gladiator_idle) (edict_t *self) -> void
{
@ -397,16 +397,16 @@ void SP_monster_gladiator(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("gladiator/pain.wav");
sound_pain2 = gi.soundindex("gladiator/gldpain2.wav");
sound_die = gi.soundindex("gladiator/glddeth2.wav");
sound_die2 = gi.soundindex("gladiator/death.wav");
sound_cleaver_swing = gi.soundindex("gladiator/melee1.wav");
sound_cleaver_hit = gi.soundindex("gladiator/melee2.wav");
sound_cleaver_miss = gi.soundindex("gladiator/melee3.wav");
sound_idle = gi.soundindex("gladiator/gldidle1.wav");
sound_search = gi.soundindex("gladiator/gldsrch1.wav");
sound_sight = gi.soundindex("gladiator/sight.wav");
sound_pain1.assign("gladiator/pain.wav");
sound_pain2.assign("gladiator/gldpain2.wav");
sound_die.assign("gladiator/glddeth2.wav");
sound_die2.assign("gladiator/death.wav");
sound_cleaver_swing.assign("gladiator/melee1.wav");
sound_cleaver_hit.assign("gladiator/melee2.wav");
sound_cleaver_miss.assign("gladiator/melee3.wav");
sound_idle.assign("gladiator/gldidle1.wav");
sound_search.assign("gladiator/gldsrch1.wav");
sound_sight.assign("gladiator/sight.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
@ -421,7 +421,7 @@ void SP_monster_gladiator(edict_t *self)
// RAFAEL
if (strcmp(self->classname, "monster_gladb") == 0)
{
sound_gunb = gi.soundindex("weapons/plasshot.wav");
sound_gunb.assign("weapons/plasshot.wav");
self->health = 250 * st.health_multiplier;
self->mass = 350;
@ -440,7 +440,7 @@ void SP_monster_gladiator(edict_t *self)
else
{
// RAFAEL
sound_gun = gi.soundindex("gladiator/railgun.wav");
sound_gun.assign("gladiator/railgun.wav");
self->health = 400 * st.health_multiplier;
self->mass = 400;

View file

@ -81,7 +81,7 @@ MONSTERINFO_STAND(guardian_stand) (edict_t *self) -> void
// walk
//
static int sound_step;
static cached_soundindex sound_step;
void guardian_footstep(edict_t *self)
{
@ -213,8 +213,8 @@ void guardian_atk1_finish(edict_t *self)
self->monsterinfo.weapon_sound = 0;
}
static int sound_charge;
static int sound_spin_loop;
static cached_soundindex sound_charge;
static cached_soundindex sound_spin_loop;
void guardian_atk1_charge(edict_t *self)
{
@ -292,7 +292,7 @@ void guardian_atk2_out(edict_t *self)
M_SetAnimation(self, &guardian_move_atk2_out);
}
static int sound_laser;
static cached_soundindex sound_laser;
constexpr vec3_t laser_positions[] = {
{ 125.0f, -70.f, 60.f },
@ -487,10 +487,10 @@ void SP_monster_guardian(edict_t *self)
return;
}
sound_step = gi.soundindex("zortemp/step.wav");
sound_charge = gi.soundindex("weapons/hyprbu1a.wav");
sound_spin_loop = gi.soundindex("weapons/hyprbl1a.wav");
sound_laser = gi.soundindex("weapons/laser2.wav");
sound_step.assign("zortemp/step.wav");
sound_charge.assign("weapons/hyprbu1a.wav");
sound_spin_loop.assign("weapons/hyprbl1a.wav");
sound_laser.assign("weapons/laser2.wav");
for (auto &gib : gibs)
gi.modelindex(gib);

View file

@ -14,13 +14,13 @@ GUNNER
constexpr spawnflags_t SPAWNFLAG_GUNCMDR_NOJUMPING = 8_spawnflag;
static int sound_pain;
static int sound_pain2;
static int sound_death;
static int sound_idle;
static int sound_open;
static int sound_search;
static int sound_sight;
static cached_soundindex sound_pain;
static cached_soundindex sound_pain2;
static cached_soundindex sound_death;
static cached_soundindex sound_idle;
static cached_soundindex sound_open;
static cached_soundindex sound_search;
static cached_soundindex sound_sight;
void guncmdr_idlesound(edict_t *self)
{
@ -1326,7 +1326,7 @@ MONSTERINFO_DUCK(guncmdr_duck) (edict_t *self, gtime_t eta) -> bool
if ((self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_left) ||
(self->monsterinfo.active_move == &guncmdr_move_fire_chain_dodge_right) ||
(self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_right) ||
(self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_left) ||
(self->monsterinfo.active_move == &guncmdr_move_attack_grenade_back_dodge_right) ||
(self->monsterinfo.active_move == &guncmdr_move_attack_mortar_dodge))
{
@ -1404,13 +1404,13 @@ void SP_monster_guncmdr(edict_t *self)
return;
}
sound_death = gi.soundindex("guncmdr/gcdrdeath1.wav");
sound_pain = gi.soundindex("guncmdr/gcdrpain2.wav");
sound_pain2 = gi.soundindex("guncmdr/gcdrpain1.wav");
sound_idle = gi.soundindex("guncmdr/gcdridle1.wav");
sound_open = gi.soundindex("guncmdr/gcdratck1.wav");
sound_search = gi.soundindex("guncmdr/gcdrsrch1.wav");
sound_sight = gi.soundindex("guncmdr/sight1.wav");
sound_death.assign("guncmdr/gcdrdeath1.wav");
sound_pain.assign("guncmdr/gcdrpain2.wav");
sound_pain2.assign("guncmdr/gcdrpain1.wav");
sound_idle.assign("guncmdr/gcdridle1.wav");
sound_open.assign("guncmdr/gcdratck1.wav");
sound_search.assign("guncmdr/gcdrsrch1.wav");
sound_sight.assign("guncmdr/sight1.wav");
gi.soundindex("guncmdr/gcdratck2.wav");
gi.soundindex("guncmdr/gcdratck3.wav");

View file

@ -12,13 +12,13 @@ GUNNER
#include "m_gunner.h"
#include "m_flash.h"
static int sound_pain;
static int sound_pain2;
static int sound_death;
static int sound_idle;
static int sound_open;
static int sound_search;
static int sound_sight;
static cached_soundindex sound_pain;
static cached_soundindex sound_pain2;
static cached_soundindex sound_death;
static cached_soundindex sound_idle;
static cached_soundindex sound_open;
static cached_soundindex sound_search;
static cached_soundindex sound_sight;
void gunner_idlesound(edict_t *self)
{
@ -858,13 +858,13 @@ void SP_monster_gunner(edict_t *self)
return;
}
sound_death = gi.soundindex("gunner/death1.wav");
sound_pain = gi.soundindex("gunner/gunpain2.wav");
sound_pain2 = gi.soundindex("gunner/gunpain1.wav");
sound_idle = gi.soundindex("gunner/gunidle1.wav");
sound_open = gi.soundindex("gunner/gunatck1.wav");
sound_search = gi.soundindex("gunner/gunsrch1.wav");
sound_sight = gi.soundindex("gunner/sight1.wav");
sound_death.assign("gunner/death1.wav");
sound_pain.assign("gunner/gunpain2.wav");
sound_pain2.assign("gunner/gunpain1.wav");
sound_idle.assign("gunner/gunidle1.wav");
sound_open.assign("gunner/gunatck1.wav");
sound_search.assign("gunner/gunsrch1.wav");
sound_sight.assign("gunner/sight1.wav");
gi.soundindex("gunner/gunatck2.wav");
gi.soundindex("gunner/gunatck3.wav");

View file

@ -12,23 +12,23 @@ hover
#include "m_hover.h"
#include "m_flash.h"
static int sound_pain1;
static int sound_pain2;
static int sound_death1;
static int sound_death2;
static int sound_sight;
static int sound_search1;
static int sound_search2;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_death1;
static cached_soundindex sound_death2;
static cached_soundindex sound_sight;
static cached_soundindex sound_search1;
static cached_soundindex sound_search2;
// ROGUE
// daedalus sounds
static int daed_sound_pain1;
static int daed_sound_pain2;
static int daed_sound_death1;
static int daed_sound_death2;
static int daed_sound_sight;
static int daed_sound_search1;
static int daed_sound_search2;
static cached_soundindex daed_sound_pain1;
static cached_soundindex daed_sound_pain2;
static cached_soundindex daed_sound_death1;
static cached_soundindex daed_sound_death2;
static cached_soundindex daed_sound_sight;
static cached_soundindex daed_sound_search1;
static cached_soundindex daed_sound_search2;
// ROGUE
MONSTERINFO_SIGHT(hover_sight) (edict_t *self, edict_t *other) -> void
@ -619,26 +619,26 @@ void SP_monster_hover(edict_t *self)
self->monsterinfo.power_armor_power = 100;
// PMM - daedalus sounds
self->monsterinfo.engine_sound = gi.soundindex("daedalus/daedidle1.wav");
daed_sound_pain1 = gi.soundindex("daedalus/daedpain1.wav");
daed_sound_pain2 = gi.soundindex("daedalus/daedpain2.wav");
daed_sound_death1 = gi.soundindex("daedalus/daeddeth1.wav");
daed_sound_death2 = gi.soundindex("daedalus/daeddeth2.wav");
daed_sound_sight = gi.soundindex("daedalus/daedsght1.wav");
daed_sound_search1 = gi.soundindex("daedalus/daedsrch1.wav");
daed_sound_search2 = gi.soundindex("daedalus/daedsrch2.wav");
daed_sound_pain1.assign("daedalus/daedpain1.wav");
daed_sound_pain2.assign("daedalus/daedpain2.wav");
daed_sound_death1.assign("daedalus/daeddeth1.wav");
daed_sound_death2.assign("daedalus/daeddeth2.wav");
daed_sound_sight.assign("daedalus/daedsght1.wav");
daed_sound_search1.assign("daedalus/daedsrch1.wav");
daed_sound_search2.assign("daedalus/daedsrch2.wav");
gi.soundindex("tank/tnkatck3.wav");
// pmm
}
else
{
self->yaw_speed = 18;
sound_pain1 = gi.soundindex("hover/hovpain1.wav");
sound_pain2 = gi.soundindex("hover/hovpain2.wav");
sound_death1 = gi.soundindex("hover/hovdeth1.wav");
sound_death2 = gi.soundindex("hover/hovdeth2.wav");
sound_sight = gi.soundindex("hover/hovsght1.wav");
sound_search1 = gi.soundindex("hover/hovsrch1.wav");
sound_search2 = gi.soundindex("hover/hovsrch2.wav");
sound_pain1.assign("hover/hovpain1.wav");
sound_pain2.assign("hover/hovpain2.wav");
sound_death1.assign("hover/hovdeth1.wav");
sound_death2.assign("hover/hovdeth2.wav");
sound_sight.assign("hover/hovsght1.wav");
sound_search1.assign("hover/hovsrch1.wav");
sound_search2.assign("hover/hovsrch2.wav");
gi.soundindex("hover/hovatck1.wav");
self->monsterinfo.engine_sound = gi.soundindex("hover/hovidle1.wav");

View file

@ -14,18 +14,18 @@ INFANTRY
void InfantryMachineGun(edict_t *self);
static int sound_pain1;
static int sound_pain2;
static int sound_die1;
static int sound_die2;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_die1;
static cached_soundindex sound_die2;
static int sound_gunshot;
static int sound_weapon_cock;
static int sound_punch_swing;
static int sound_punch_hit;
static int sound_sight;
static int sound_search;
static int sound_idle;
static cached_soundindex sound_gunshot;
static cached_soundindex sound_weapon_cock;
static cached_soundindex sound_punch_swing;
static cached_soundindex sound_punch_hit;
static cached_soundindex sound_sight;
static cached_soundindex sound_search;
static cached_soundindex sound_idle;
// range at which we'll try to initiate a run-attack to close distance
constexpr float RANGE_RUN_ATTACK = RANGE_NEAR * 0.75f;
@ -848,19 +848,19 @@ MONSTERINFO_SIDESTEP(infantry_sidestep) (edict_t *self) -> bool
void InfantryPrecache()
{
sound_pain1 = gi.soundindex("infantry/infpain1.wav");
sound_pain2 = gi.soundindex("infantry/infpain2.wav");
sound_die1 = gi.soundindex("infantry/infdeth1.wav");
sound_die2 = gi.soundindex("infantry/infdeth2.wav");
sound_pain1.assign("infantry/infpain1.wav");
sound_pain2.assign("infantry/infpain2.wav");
sound_die1.assign("infantry/infdeth1.wav");
sound_die2.assign("infantry/infdeth2.wav");
sound_gunshot = gi.soundindex("infantry/infatck1.wav");
sound_weapon_cock = gi.soundindex("infantry/infatck3.wav");
sound_punch_swing = gi.soundindex("infantry/infatck2.wav");
sound_punch_hit = gi.soundindex("infantry/melee2.wav");
sound_gunshot.assign("infantry/infatck1.wav");
sound_weapon_cock.assign("infantry/infatck3.wav");
sound_punch_swing.assign("infantry/infatck2.wav");
sound_punch_hit.assign("infantry/melee2.wav");
sound_sight = gi.soundindex("infantry/infsght1.wav");
sound_search = gi.soundindex("infantry/infsrch1.wav");
sound_idle = gi.soundindex("infantry/infidle1.wav");
sound_sight.assign("infantry/infsght1.wav");
sound_search.assign("infantry/infsrch1.wav");
sound_idle.assign("infantry/infidle1.wav");
}
constexpr spawnflags_t SPAWNFLAG_INFANTRY_NOJUMPING = 8_spawnflag;

View file

@ -17,10 +17,10 @@ constexpr spawnflags_t SPAWNFLAG_INSANE_STAND_GROUND = 16_spawnflag;
constexpr spawnflags_t SPAWNFLAG_INSANE_ALWAYS_STAND = 32_spawnflag;
constexpr spawnflags_t SPAWNFLAG_INSANE_QUIET = 64_spawnflag;
static int sound_fist;
static int sound_shake;
static int sound_moan;
static int sound_scream[8];
static cached_soundindex sound_fist;
static cached_soundindex sound_shake;
static cached_soundindex sound_moan;
static cached_soundindex sound_scream[8];
void insane_fist(edict_t *self)
{
@ -627,19 +627,19 @@ void SP_misc_insane(edict_t *self)
return;
}
sound_fist = gi.soundindex("insane/insane11.wav");
sound_fist.assign("insane/insane11.wav");
if (!self->spawnflags.has(SPAWNFLAG_INSANE_QUIET))
{
sound_shake = gi.soundindex("insane/insane5.wav");
sound_moan = gi.soundindex("insane/insane7.wav");
sound_scream[0] = gi.soundindex("insane/insane1.wav");
sound_scream[1] = gi.soundindex("insane/insane2.wav");
sound_scream[2] = gi.soundindex("insane/insane3.wav");
sound_scream[3] = gi.soundindex("insane/insane4.wav");
sound_scream[4] = gi.soundindex("insane/insane6.wav");
sound_scream[5] = gi.soundindex("insane/insane8.wav");
sound_scream[6] = gi.soundindex("insane/insane9.wav");
sound_scream[7] = gi.soundindex("insane/insane10.wav");
sound_shake.assign("insane/insane5.wav");
sound_moan.assign("insane/insane7.wav");
sound_scream[0].assign("insane/insane1.wav");
sound_scream[1].assign("insane/insane2.wav");
sound_scream[2].assign("insane/insane3.wav");
sound_scream[3].assign("insane/insane4.wav");
sound_scream[4].assign("insane/insane6.wav");
sound_scream[5].assign("insane/insane8.wav");
sound_scream[6].assign("insane/insane9.wav");
sound_scream[7].assign("insane/insane10.wav");
}
self->movetype = MOVETYPE_STEP;

View file

@ -29,29 +29,29 @@ bool FindTarget(edict_t *self);
void FoundTarget(edict_t *self);
void ED_CallSpawn(edict_t *ent);
static int sound_idle1;
static int sound_pain1;
static int sound_pain2;
static int sound_die;
static int sound_sight;
static int sound_search;
static int sound_hook_launch;
static int sound_hook_hit;
static int sound_hook_heal;
static int sound_hook_retract;
static cached_soundindex sound_idle1;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_die;
static cached_soundindex sound_sight;
static cached_soundindex sound_search;
static cached_soundindex sound_hook_launch;
static cached_soundindex sound_hook_hit;
static cached_soundindex sound_hook_heal;
static cached_soundindex sound_hook_retract;
// PMM - commander sounds
static int commander_sound_idle1;
static int commander_sound_pain1;
static int commander_sound_pain2;
static int commander_sound_die;
static int commander_sound_sight;
static int commander_sound_search;
static int commander_sound_hook_launch;
static int commander_sound_hook_hit;
static int commander_sound_hook_heal;
static int commander_sound_hook_retract;
static int commander_sound_spawn;
static cached_soundindex commander_sound_idle1;
static cached_soundindex commander_sound_pain1;
static cached_soundindex commander_sound_pain2;
static cached_soundindex commander_sound_die;
static cached_soundindex commander_sound_sight;
static cached_soundindex commander_sound_search;
static cached_soundindex commander_sound_hook_launch;
static cached_soundindex commander_sound_hook_hit;
static cached_soundindex commander_sound_hook_heal;
static cached_soundindex commander_sound_hook_retract;
static cached_soundindex commander_sound_spawn;
constexpr const char *default_reinforcements = "monster_soldier_light 1;monster_soldier 2;monster_soldier_ss 2;monster_infantry 3;monster_gunner 4;monster_medic 5;monster_gladiator 6";
constexpr int32_t default_monster_slots_base = 3;
@ -199,30 +199,34 @@ void abortHeal(edict_t *self, bool change_frame, bool gib, bool mark)
int hurt;
constexpr vec3_t pain_normal = { 0, 0, 1 };
cleanupHealTarget(self->enemy);
// gib em!
if ((mark) && (self->enemy) && (self->enemy->inuse))
if (self->enemy && self->enemy->inuse)
{
// if the first badMedic slot is filled by a medic, skip it and use the second one
if ((self->enemy->monsterinfo.badMedic1) && (self->enemy->monsterinfo.badMedic1->inuse) && (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13)))
{
self->enemy->monsterinfo.badMedic2 = self;
}
else
{
self->enemy->monsterinfo.badMedic1 = self;
}
}
if ((gib) && (self->enemy) && (self->enemy->inuse))
{
if (self->enemy->gib_health)
hurt = -self->enemy->gib_health;
else
hurt = 500;
cleanupHealTarget(self->enemy);
T_Damage(self->enemy, self, self, vec3_origin, self->enemy->s.origin,
pain_normal, hurt, 0, DAMAGE_NONE, MOD_UNKNOWN);
// gib em!
if (mark)
{
// if the first badMedic slot is filled by a medic, skip it and use the second one
if ((self->enemy->monsterinfo.badMedic1) && (self->enemy->monsterinfo.badMedic1->inuse) && (!strncmp(self->enemy->monsterinfo.badMedic1->classname, "monster_medic", 13)))
{
self->enemy->monsterinfo.badMedic2 = self;
}
else
{
self->enemy->monsterinfo.badMedic1 = self;
}
}
if (gib)
{
if (self->enemy->gib_health)
hurt = -self->enemy->gib_health;
else
hurt = 500;
T_Damage(self->enemy, self, self, vec3_origin, self->enemy->s.origin,
pain_normal, hurt, 0, DAMAGE_NONE, MOD_UNKNOWN);
}
}
// clean up self
@ -632,21 +636,6 @@ void medic_fire_blaster(edict_t *self)
}
else
{
static constexpr vec3_t hb_offsets[] = {
{ 33.0f, 12.5f, 15.0f },
{ 32.4f, 11.2f, 15.0f },
{ 35.6f, 7.4f, 15.0f },
{ 34.0f, 4.1f, 15.0f },
{ 36.6f, 1.0f, 15.0f },
{ 34.7f, -1.9f, 15.0f },
{ 36.6f, -0.5f, 15.0f },
{ 34.2f, 2.8f, 15.0f },
{ 36.5f, 3.8f, 15.0f },
{ 33.5f, 6.9f, 15.0f },
{ 32.7f, 9.9f, 15.0f },
{ 34.5f, 11.0f, 15.0f }
};
effect = (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER;
mz = static_cast<monster_muzzleflash_id_t>(((self->mass > 400) ? MZ2_MEDIC_HYPERBLASTER2_1 : MZ2_MEDIC_HYPERBLASTER1_1) + (self->s.frame - FRAME_attack19));
}
@ -1232,10 +1221,8 @@ void medic_spawngrows(edict_t *self)
{
offset = reinforcement_position[count];
if (self->s.scale)
offset *= self->s.scale;
startpoint = M_ProjectFlashSource(self, offset, f, r);
// a little off the ground
startpoint[2] += 10 * (self->s.scale ? self->s.scale : 1.0f);
@ -1277,9 +1264,6 @@ void medic_finish_spawn(edict_t *self)
auto &reinforcement = self->monsterinfo.reinforcements.reinforcements[self->monsterinfo.chosen_reinforcements[count]];
offset = reinforcement_position[count];
if (self->s.scale)
offset *= self->s.scale;
startpoint = M_ProjectFlashSource(self, offset, f, r);
// a little off the ground
@ -1595,17 +1579,17 @@ void SP_monster_medic(edict_t *self)
self->s.skinnum = 2;
// commander sounds
commander_sound_idle1 = gi.soundindex("medic_commander/medidle.wav");
commander_sound_pain1 = gi.soundindex("medic_commander/medpain1.wav");
commander_sound_pain2 = gi.soundindex("medic_commander/medpain2.wav");
commander_sound_die = gi.soundindex("medic_commander/meddeth.wav");
commander_sound_sight = gi.soundindex("medic_commander/medsght.wav");
commander_sound_search = gi.soundindex("medic_commander/medsrch.wav");
commander_sound_hook_launch = gi.soundindex("medic_commander/medatck2c.wav");
commander_sound_hook_hit = gi.soundindex("medic_commander/medatck3a.wav");
commander_sound_hook_heal = gi.soundindex("medic_commander/medatck4a.wav");
commander_sound_hook_retract = gi.soundindex("medic_commander/medatck5a.wav");
commander_sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav");
commander_sound_idle1.assign("medic_commander/medidle.wav");
commander_sound_pain1.assign("medic_commander/medpain1.wav");
commander_sound_pain2.assign("medic_commander/medpain2.wav");
commander_sound_die.assign("medic_commander/meddeth.wav");
commander_sound_sight.assign("medic_commander/medsght.wav");
commander_sound_search.assign("medic_commander/medsrch.wav");
commander_sound_hook_launch.assign("medic_commander/medatck2c.wav");
commander_sound_hook_hit.assign("medic_commander/medatck3a.wav");
commander_sound_hook_heal.assign("medic_commander/medatck4a.wav");
commander_sound_hook_retract.assign("medic_commander/medatck5a.wav");
commander_sound_spawn.assign("medic_commander/monsterspawn1.wav");
gi.soundindex("tank/tnkatck3.wav");
const char *reinforcements = default_reinforcements;
@ -1625,16 +1609,16 @@ void SP_monster_medic(edict_t *self)
}
else
{
sound_idle1 = gi.soundindex("medic/idle.wav");
sound_pain1 = gi.soundindex("medic/medpain1.wav");
sound_pain2 = gi.soundindex("medic/medpain2.wav");
sound_die = gi.soundindex("medic/meddeth1.wav");
sound_sight = gi.soundindex("medic/medsght1.wav");
sound_search = gi.soundindex("medic/medsrch1.wav");
sound_hook_launch = gi.soundindex("medic/medatck2.wav");
sound_hook_hit = gi.soundindex("medic/medatck3.wav");
sound_hook_heal = gi.soundindex("medic/medatck4.wav");
sound_hook_retract = gi.soundindex("medic/medatck5.wav");
sound_idle1.assign("medic/idle.wav");
sound_pain1.assign("medic/medpain1.wav");
sound_pain2.assign("medic/medpain2.wav");
sound_die.assign("medic/meddeth1.wav");
sound_sight.assign("medic/medsght1.wav");
sound_search.assign("medic/medsrch1.wav");
sound_hook_launch.assign("medic/medatck2.wav");
sound_hook_hit.assign("medic/medatck3.wav");
sound_hook_heal.assign("medic/medatck4.wav");
sound_hook_retract.assign("medic/medatck5.wav");
gi.soundindex("medic/medatck1.wav");
self->s.skinnum = 0;

View file

@ -656,7 +656,7 @@ bool SV_movestep(edict_t *ent, vec3_t move, bool relink)
float stepsize;
// push down from a step height above the wished position
if (ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP))
if (ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP) && ent->health > 0)
stepsize = 64.f;
else if (!(ent->monsterinfo.aiflags & AI_NOSTEP))
stepsize = STEPSIZE;
@ -741,7 +741,8 @@ bool SV_movestep(edict_t *ent, vec3_t move, bool relink)
ent->groundentity = nullptr;
return true;
}
else if (!ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP))
// [Paril-KEX] allow dead monsters to "fall" off of edges in their death animation
else if (!ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP) && ent->health > 0)
return false; // walked off an edge
}
@ -831,7 +832,7 @@ bool SV_movestep(edict_t *ent, vec3_t move, bool relink)
return false;
}
if (ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP))
if (ent->spawnflags.has(SPAWNFLAG_MONSTER_SUPER_STEP) && ent->health > 0)
{
if (!ent->groundentity || ent->groundentity->solid == SOLID_BSP)
{
@ -867,7 +868,11 @@ bool SV_movestep(edict_t *ent, vec3_t move, bool relink)
if (relink)
{
gi.linkentity(ent);
G_TouchTriggers(ent);
// [Paril-KEX] this is something N64 does to avoid doors opening
// at the start of a level, which triggers some monsters to spawn.
if (!level.is_n64 || level.time > FRAME_TIME_S)
G_TouchTriggers(ent);
}
if (stepped)

View file

@ -13,19 +13,19 @@ mutant
constexpr spawnflags_t SPAWNFLAG_MUTANT_NOJUMPING = 8_spawnflag;
static int sound_swing;
static int sound_hit;
static int sound_hit2;
static int sound_death;
static int sound_idle;
static int sound_pain1;
static int sound_pain2;
static int sound_sight;
static int sound_search;
static int sound_step1;
static int sound_step2;
static int sound_step3;
static int sound_thud;
static cached_soundindex sound_swing;
static cached_soundindex sound_hit;
static cached_soundindex sound_hit2;
static cached_soundindex sound_death;
static cached_soundindex sound_idle;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_sight;
static cached_soundindex sound_search;
static cached_soundindex sound_step1;
static cached_soundindex sound_step2;
static cached_soundindex sound_step3;
static cached_soundindex sound_thud;
//
// SOUNDS
@ -286,7 +286,8 @@ TOUCH(mutant_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool
if (self->style == 1 && other->takedamage)
{
if (self->velocity.length() > 400)
// [Paril-KEX] only if we're actually moving fast enough to hurt
if (self->velocity.length() > 30)
{
vec3_t point;
vec3_t normal;
@ -321,8 +322,8 @@ void mutant_jump_takeoff(edict_t *self)
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
AngleVectors(self->s.angles, forward, nullptr, nullptr);
self->s.origin[2] += 1;
self->velocity = forward * 400;
self->velocity[2] = 150;
self->velocity = forward * 425;
self->velocity[2] = 160;
self->groundentity = nullptr;
self->monsterinfo.aiflags |= AI_DUCKED;
self->monsterinfo.attack_finished = level.time + 3_sec;
@ -506,15 +507,6 @@ MONSTERINFO_SETSKIN(mutant_setskin) (edict_t *self) -> void
// DEATH
//
// FIXME: expanded dead box a bit, but rotation of dead body
// means it'll never always fit unless you move the whole box based on angle
void mutant_dead(edict_t *self)
{
self->mins = { 0, -48, -24 };
self->maxs = { 64, 16, -8 };
monster_dead(self);
}
void mutant_shrink(edict_t *self)
{
self->maxs[2] = 0;
@ -522,32 +514,43 @@ void mutant_shrink(edict_t *self)
gi.linkentity(self);
}
// [Paril-KEX]
static void ai_move_slide_right(edict_t *self, float dist)
{
M_walkmove(self, self->s.angles[YAW] + 90, dist);
}
static void ai_move_slide_left(edict_t *self, float dist)
{
M_walkmove(self, self->s.angles[YAW] - 90, dist);
}
mframe_t mutant_frames_death1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, mutant_shrink },
{ ai_move },
{ ai_move },
{ ai_move }
{ ai_move_slide_right },
{ ai_move_slide_right },
{ ai_move_slide_right },
{ ai_move_slide_right, 2 },
{ ai_move_slide_right, 5 },
{ ai_move_slide_right, 7, mutant_shrink },
{ ai_move_slide_right, 6 },
{ ai_move_slide_right, 2 },
{ ai_move_slide_right }
};
MMOVE_T(mutant_move_death1) = { FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_dead };
MMOVE_T(mutant_move_death1) = { FRAME_death101, FRAME_death109, mutant_frames_death1, monster_dead };
mframe_t mutant_frames_death2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, mutant_shrink },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
{ ai_move_slide_left },
{ ai_move_slide_left },
{ ai_move_slide_left },
{ ai_move_slide_left, 1 },
{ ai_move_slide_left, 3, mutant_shrink },
{ ai_move_slide_left, 6 },
{ ai_move_slide_left, 8 },
{ ai_move_slide_left, 5 },
{ ai_move_slide_left, 2 },
{ ai_move_slide_left }
};
MMOVE_T(mutant_move_death2) = { FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead };
MMOVE_T(mutant_move_death2) = { FRAME_death201, FRAME_death210, mutant_frames_death2, monster_dead };
DIE(mutant_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
@ -676,19 +679,19 @@ void SP_monster_mutant(edict_t *self)
return;
}
sound_swing = gi.soundindex("mutant/mutatck1.wav");
sound_hit = gi.soundindex("mutant/mutatck2.wav");
sound_hit2 = gi.soundindex("mutant/mutatck3.wav");
sound_death = gi.soundindex("mutant/mutdeth1.wav");
sound_idle = gi.soundindex("mutant/mutidle1.wav");
sound_pain1 = gi.soundindex("mutant/mutpain1.wav");
sound_pain2 = gi.soundindex("mutant/mutpain2.wav");
sound_sight = gi.soundindex("mutant/mutsght1.wav");
sound_search = gi.soundindex("mutant/mutsrch1.wav");
sound_step1 = gi.soundindex("mutant/step1.wav");
sound_step2 = gi.soundindex("mutant/step2.wav");
sound_step3 = gi.soundindex("mutant/step3.wav");
sound_thud = gi.soundindex("mutant/thud1.wav");
sound_swing.assign("mutant/mutatck1.wav");
sound_hit.assign("mutant/mutatck2.wav");
sound_hit2.assign("mutant/mutatck3.wav");
sound_death.assign("mutant/mutdeth1.wav");
sound_idle.assign("mutant/mutidle1.wav");
sound_pain1.assign("mutant/mutpain1.wav");
sound_pain2.assign("mutant/mutpain2.wav");
sound_sight.assign("mutant/mutsght1.wav");
sound_search.assign("mutant/mutsrch1.wav");
sound_step1.assign("mutant/step1.wav");
sound_step2.assign("mutant/step2.wav");
sound_step3.assign("mutant/step3.wav");
sound_thud.assign("mutant/thud1.wav");
self->monsterinfo.aiflags |= AI_STINKY;

View file

@ -15,17 +15,17 @@ constexpr float g_athena_parasite_miss_chance = 0.1f;
constexpr float g_athena_parasite_proboscis_speed = 1250;
constexpr float g_athena_parasite_proboscis_retract_modifier = 2.0f;
static int sound_pain1;
static int sound_pain2;
static int sound_die;
static int sound_launch;
static int sound_impact;
static int sound_suck;
static int sound_reelin;
static int sound_sight;
static int sound_tap;
static int sound_scratch;
static int sound_search;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_die;
static cached_soundindex sound_launch;
static cached_soundindex sound_impact;
static cached_soundindex sound_suck;
static cached_soundindex sound_reelin;
static cached_soundindex sound_sight;
static cached_soundindex sound_tap;
static cached_soundindex sound_scratch;
static cached_soundindex sound_search;
void parasite_stand(edict_t *self);
void parasite_start_run(edict_t *self);
@ -909,17 +909,17 @@ void SP_monster_parasite(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("parasite/parpain1.wav");
sound_pain2 = gi.soundindex("parasite/parpain2.wav");
sound_die = gi.soundindex("parasite/pardeth1.wav");
sound_launch = gi.soundindex("parasite/paratck1.wav");
sound_impact = gi.soundindex("parasite/paratck2.wav");
sound_suck = gi.soundindex("parasite/paratck3.wav");
sound_reelin = gi.soundindex("parasite/paratck4.wav");
sound_sight = gi.soundindex("parasite/parsght1.wav");
sound_tap = gi.soundindex("parasite/paridle1.wav");
sound_scratch = gi.soundindex("parasite/paridle2.wav");
sound_search = gi.soundindex("parasite/parsrch1.wav");
sound_pain1.assign("parasite/parpain1.wav");
sound_pain2.assign("parasite/parpain2.wav");
sound_die.assign("parasite/pardeth1.wav");
sound_launch.assign("parasite/paratck1.wav");
sound_impact.assign("parasite/paratck2.wav");
sound_suck.assign("parasite/paratck3.wav");
sound_reelin.assign("parasite/paratck4.wav");
sound_sight.assign("parasite/parsght1.wav");
sound_tap.assign("parasite/paridle1.wav");
sound_scratch.assign("parasite/paridle2.wav");
sound_search.assign("parasite/parsrch1.wav");
gi.modelindex("models/monsters/parasite/tip/tris.md2");
gi.modelindex("models/monsters/parasite/segment/tris.md2");

View file

@ -12,15 +12,15 @@ SHAMBLER
#include "m_shambler.h"
#include "m_flash.h"
static int sound_pain;
static int sound_idle;
static int sound_die;
static int sound_sight;
static int sound_windup;
static int sound_melee1;
static int sound_melee2;
static int sound_smack;
static int sound_boom;
static cached_soundindex sound_pain;
static cached_soundindex sound_idle;
static cached_soundindex sound_die;
static cached_soundindex sound_sight;
static cached_soundindex sound_windup;
static cached_soundindex sound_melee1;
static cached_soundindex sound_melee2;
static cached_soundindex sound_smack;
static cached_soundindex sound_boom;
//
// misc
@ -558,15 +558,15 @@ void SP_monster_shambler(edict_t* self)
self->solid = SOLID_BBOX;
gi.modelindex("models/proj/lightning/tris.md2");
sound_pain = gi.soundindex("shambler/shurt2.wav");
sound_idle = gi.soundindex("shambler/sidle.wav");
sound_die = gi.soundindex("shambler/sdeath.wav");
sound_windup = gi.soundindex("shambler/sattck1.wav");
sound_melee1 = gi.soundindex("shambler/melee1.wav");
sound_melee2 = gi.soundindex("shambler/melee2.wav");
sound_sight = gi.soundindex("shambler/ssight.wav");
sound_smack = gi.soundindex("shambler/smack.wav");
sound_boom = gi.soundindex("shambler/sboom.wav");
sound_pain.assign("shambler/shurt2.wav");
sound_idle.assign("shambler/sidle.wav");
sound_die.assign("shambler/sdeath.wav");
sound_windup.assign("shambler/sattck1.wav");
sound_melee1.assign("shambler/melee1.wav");
sound_melee2.assign("shambler/melee2.wav");
sound_sight.assign("shambler/ssight.wav");
sound_smack.assign("shambler/smack.wav");
sound_boom.assign("shambler/sboom.wav");
self->health = 600 * st.health_multiplier;
self->gib_health = -60;

View file

@ -12,16 +12,16 @@ SOLDIER
#include "m_soldier.h"
#include "m_flash.h"
static int sound_idle;
static int sound_sight1;
static int sound_sight2;
static int sound_pain_light;
static int sound_pain;
static int sound_pain_ss;
static int sound_death_light;
static int sound_death;
static int sound_death_ss;
static int sound_cock;
static cached_soundindex sound_idle;
static cached_soundindex sound_sight1;
static cached_soundindex sound_sight2;
static cached_soundindex sound_pain_light;
static cached_soundindex sound_pain;
static cached_soundindex sound_pain_ss;
static cached_soundindex sound_death_light;
static cached_soundindex sound_death;
static cached_soundindex sound_death_ss;
static cached_soundindex sound_cock;
void soldier_start_charge(edict_t *self)
{
@ -648,6 +648,13 @@ void soldier_fire_xatrix(edict_t *self, int flash_number, bool angle_limited)
}
else
{
// [Paril-KEX] no enemy = no fire
if ((!self->enemy) || (!self->enemy->inuse))
{
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
return;
}
// PMM
if (self->monsterinfo.attack_state == AS_BLIND)
end = self->monsterinfo.blind_fire_target;
@ -1838,10 +1845,10 @@ void SP_monster_soldier_x(edict_t *self)
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
sound_idle = gi.soundindex("soldier/solidle1.wav");
sound_sight1 = gi.soundindex("soldier/solsght1.wav");
sound_sight2 = gi.soundindex("soldier/solsrch1.wav");
sound_cock = gi.soundindex("infantry/infatck3.wav");
sound_idle.assign("soldier/solidle1.wav");
sound_sight1.assign("soldier/solsght1.wav");
sound_sight2.assign("soldier/solsrch1.wav");
sound_cock.assign("infantry/infatck3.wav");
gi.modelindex("models/monsters/soldier/gibs/head.md2");
gi.modelindex("models/monsters/soldier/gibs/gun.md2");
@ -1897,8 +1904,8 @@ void SP_monster_soldier_light(edict_t *self)
SP_monster_soldier_x(self);
sound_pain_light = gi.soundindex("soldier/solpain2.wav");
sound_death_light = gi.soundindex("soldier/soldeth2.wav");
sound_pain_light.assign("soldier/solpain2.wav");
sound_death_light.assign("soldier/soldeth2.wav");
gi.modelindex("models/objects/laser/tris.md2");
gi.soundindex("misc/lasfly.wav");
gi.soundindex("soldier/solatck2.wav");
@ -1923,8 +1930,8 @@ void SP_monster_soldier(edict_t *self)
SP_monster_soldier_x(self);
sound_pain = gi.soundindex("soldier/solpain1.wav");
sound_death = gi.soundindex("soldier/soldeth1.wav");
sound_pain.assign("soldier/solpain1.wav");
sound_death.assign("soldier/soldeth1.wav");
gi.soundindex("soldier/solatck1.wav");
self->s.skinnum = 2;
@ -1944,8 +1951,8 @@ void SP_monster_soldier_ss(edict_t *self)
SP_monster_soldier_x(self);
sound_pain_ss = gi.soundindex("soldier/solpain3.wav");
sound_death_ss = gi.soundindex("soldier/soldeth3.wav");
sound_pain_ss.assign("soldier/solpain3.wav");
sound_death_ss.assign("soldier/soldeth3.wav");
gi.soundindex("soldier/solatck3.wav");
self->s.skinnum = 4;
@ -1975,8 +1982,8 @@ void SP_monster_soldier_ripper(edict_t *self)
SP_monster_soldier_h(self);
sound_pain_light = gi.soundindex("soldier/solpain2.wav");
sound_death_light = gi.soundindex("soldier/soldeth2.wav");
sound_pain_light.assign("soldier/solpain2.wav");
sound_death_light.assign("soldier/soldeth2.wav");
gi.modelindex("models/objects/boomrang/tris.md2");
gi.soundindex("misc/lasfly.wav");
@ -2003,8 +2010,8 @@ void SP_monster_soldier_hypergun(edict_t *self)
SP_monster_soldier_h(self);
gi.modelindex("models/objects/laser/tris.md2");
sound_pain = gi.soundindex("soldier/solpain1.wav");
sound_death = gi.soundindex("soldier/soldeth1.wav");
sound_pain.assign("soldier/solpain1.wav");
sound_death.assign("soldier/soldeth1.wav");
gi.soundindex("soldier/solatck1.wav");
gi.soundindex("weapons/hyprbd1a.wav");
gi.soundindex("weapons/hyprbl1a.wav");
@ -2029,8 +2036,8 @@ void SP_monster_soldier_lasergun(edict_t *self)
SP_monster_soldier_h(self);
sound_pain_ss = gi.soundindex("soldier/solpain3.wav");
sound_death_ss = gi.soundindex("soldier/soldeth3.wav");
sound_pain_ss.assign("soldier/solpain3.wav");
sound_death_ss.assign("soldier/soldeth3.wav");
gi.soundindex("soldier/solatck3.wav");
self->s.skinnum = 10;

View file

@ -16,14 +16,14 @@ constexpr spawnflags_t SPAWNFLAG_SUPERTANK_POWERSHIELD = 8_spawnflag;
// n64
constexpr spawnflags_t SPAWNFLAG_SUPERTANK_LONG_DEATH = 16_spawnflag;
static int sound_pain1;
static int sound_pain2;
static int sound_pain3;
static int sound_death;
static int sound_search1;
static int sound_search2;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_pain3;
static cached_soundindex sound_death;
static cached_soundindex sound_search1;
static cached_soundindex sound_search2;
static int tread_sound;
static cached_soundindex tread_sound;
void TreadSound(edict_t *self)
{
@ -640,14 +640,14 @@ void SP_monster_supertank(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("bosstank/btkpain1.wav");
sound_pain2 = gi.soundindex("bosstank/btkpain2.wav");
sound_pain3 = gi.soundindex("bosstank/btkpain3.wav");
sound_death = gi.soundindex("bosstank/btkdeth1.wav");
sound_search1 = gi.soundindex("bosstank/btkunqv1.wav");
sound_search2 = gi.soundindex("bosstank/btkunqv2.wav");
sound_pain1.assign("bosstank/btkpain1.wav");
sound_pain2.assign("bosstank/btkpain2.wav");
sound_pain3.assign("bosstank/btkpain3.wav");
sound_death.assign("bosstank/btkdeth1.wav");
sound_search1.assign("bosstank/btkunqv1.wav");
sound_search2.assign("bosstank/btkunqv2.wav");
tread_sound = gi.soundindex("bosstank/btkengn1.wav");
tread_sound.assign("bosstank/btkengn1.wav");
gi.soundindex("gunner/gunatck3.wav");
gi.soundindex("infantry/infatck1.wav");

View file

@ -16,14 +16,14 @@ void tank_refire_rocket(edict_t *self);
void tank_doattack_rocket(edict_t *self);
void tank_reattack_blaster(edict_t *self);
static int sound_thud;
static int sound_pain, sound_pain2;
static int sound_idle;
static int sound_die;
static int sound_step;
static int sound_sight;
static int sound_windup;
static int sound_strike;
static cached_soundindex sound_thud;
static cached_soundindex sound_pain, sound_pain2;
static cached_soundindex sound_idle;
static cached_soundindex sound_die;
static cached_soundindex sound_step;
static cached_soundindex sound_sight;
static cached_soundindex sound_windup;
static cached_soundindex sound_strike;
constexpr spawnflags_t SPAWNFLAG_TANK_COMMANDER_GUARDIAN = 8_spawnflag;
constexpr spawnflags_t SPAWNFLAG_TANK_COMMANDER_HEAT_SEEKING = 16_spawnflag;
@ -612,7 +612,9 @@ void tank_reattack_blaster(edict_t *self)
void tank_poststrike(edict_t *self)
{
self->enemy = nullptr;
tank_run(self);
// [Paril-KEX]
self->monsterinfo.pausetime = HOLD_FOREVER;
self->monsterinfo.stand(self);
}
mframe_t tank_frames_attack_strike[] = {
@ -1041,13 +1043,13 @@ void SP_monster_tank(edict_t *self)
gi.modelindex("models/monsters/tank/gibs/foot.md2");
gi.modelindex("models/monsters/tank/gibs/thigh.md2");
sound_thud = gi.soundindex("tank/tnkdeth2.wav");
sound_idle = gi.soundindex("tank/tnkidle1.wav");
sound_die = gi.soundindex("tank/death.wav");
sound_step = gi.soundindex("tank/step.wav");
sound_windup = gi.soundindex("tank/tnkatck4.wav");
sound_strike = gi.soundindex("tank/tnkatck5.wav");
sound_sight = gi.soundindex("tank/sight1.wav");
sound_thud.assign("tank/tnkdeth2.wav");
sound_idle.assign("tank/tnkidle1.wav");
sound_die.assign("tank/death.wav");
sound_step.assign("tank/step.wav");
sound_windup.assign("tank/tnkatck4.wav");
sound_strike.assign("tank/tnkatck5.wav");
sound_sight.assign("tank/sight1.wav");
gi.soundindex("tank/tnkatck1.wav");
gi.soundindex("tank/tnkatk2a.wav");
@ -1062,13 +1064,13 @@ void SP_monster_tank(edict_t *self)
self->health = 1000 * st.health_multiplier;
self->gib_health = -225;
self->count = 1;
sound_pain2 = gi.soundindex("tank/pain.wav");
sound_pain2.assign("tank/pain.wav");
}
else
{
self->health = 750 * st.health_multiplier;
self->gib_health = -200;
sound_pain = gi.soundindex("tank/tnkpain2.wav");
sound_pain.assign("tank/tnkpain2.wav");
}
self->monsterinfo.scale = MODEL_SCALE;

View file

@ -858,14 +858,14 @@ void InitClientPersistant(edict_t *ent, gclient_t *client)
client->pers.max_ammo[AMMO_TESLA] = 5;
// ROGUE
if (!g_instagib->integer)
if (!deathmatch->integer || !g_instagib->integer)
client->pers.inventory[IT_WEAPON_BLASTER] = 1;
// [Kex]
// start items!
if (*g_start_items->string)
Player_GiveStartItems(ent, g_start_items->string);
else if (g_instagib->integer)
else if (deathmatch->integer && g_instagib->integer)
{
client->pers.inventory[IT_WEAPON_RAILGUN] = 1;
client->pers.inventory[IT_AMMO_SLUGS] = 99;
@ -924,11 +924,6 @@ void InitClientResp(gclient_t *client)
client->resp.entertime = level.time;
client->resp.coop_respawn = client->pers;
// ZOID
if (G_TeamplayEnabled() && client->pers.connected && client->resp.ctf_team < CTF_TEAM1)
CTFAssignTeam(client);
// ZOID
}
/*
@ -1051,7 +1046,9 @@ select_spawn_result_t SelectDeathmatchSpawnPoint(bool farthest, bool force_spawn
if (spawn_points.size() == 0)
{
spot = G_FindByString<&edict_t::classname>(nullptr, "info_player_start");
spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) });
if (spot)
spawn_points.push_back({ spot, PlayersRangeFromSpot(spot) });
// map is malformed
if (spawn_points.size() == 0)
@ -1231,30 +1228,46 @@ static edict_t *SelectSingleSpawnPoint(edict_t *ent)
}
// [Paril-KEX]
static edict_t *G_UnsafeSpawnPosition(vec3_t spot)
static edict_t *G_UnsafeSpawnPosition(vec3_t spot, bool check_players)
{
trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, MASK_PLAYERSOLID);
contents_t mask = MASK_PLAYERSOLID;
if (!check_players)
mask &= ~CONTENTS_PLAYER;
trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask);
// sometimes the spot is too close to the ground, give it a bit of slack
if (tr.startsolid && !tr.ent->client)
{
spot[2] += 1;
tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, MASK_PLAYERSOLID);
tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask);
}
// no idea why this happens in some maps..
if (tr.startsolid && !tr.ent->client)
return tr.ent;
{
// try a nudge
if (G_FixStuckObject_Generic(spot, PLAYER_MINS, PLAYER_MAXS, [mask] (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);
}) == stuck_result_t::NO_GOOD_POSITION)
return tr.ent; // what do we do here...?
trace_t tr = gi.trace(spot, PLAYER_MINS, PLAYER_MAXS, spot, nullptr, mask);
if (tr.startsolid && !tr.ent->client)
return tr.ent; // what do we do here...?
}
if (tr.fraction == 1.f)
return nullptr;
else if (tr.ent->client)
else if (check_players && tr.ent && tr.ent->client)
return tr.ent;
return nullptr;
}
edict_t *SelectCoopSpawnPoint(edict_t *ent, bool force_spawn)
edict_t *SelectCoopSpawnPoint(edict_t *ent, bool force_spawn, bool check_players)
{
edict_t *spot = nullptr;
const char *target;
@ -1268,7 +1281,7 @@ edict_t *SelectCoopSpawnPoint(edict_t *ent, bool force_spawn)
// try the main spawn point first
spot = SelectSingleSpawnPoint(ent);
if (spot && !G_UnsafeSpawnPosition(spot->s.origin))
if (spot && !G_UnsafeSpawnPosition(spot->s.origin, check_players))
return spot;
spot = nullptr;
@ -1289,7 +1302,7 @@ edict_t *SelectCoopSpawnPoint(edict_t *ent, bool force_spawn)
{ // this is a coop spawn point for one of the clients here
num_valid_spots++;
if (!G_UnsafeSpawnPosition(spot->s.origin))
if (!G_UnsafeSpawnPosition(spot->s.origin, check_players))
return spot; // this is it
}
}
@ -1314,7 +1327,7 @@ edict_t *SelectCoopSpawnPoint(edict_t *ent, bool force_spawn)
// this is a coop spawn point for one of the clients here
num_valid_spots++;
if (!G_UnsafeSpawnPosition(spot->s.origin))
if (!G_UnsafeSpawnPosition(spot->s.origin, check_players))
return spot; // this is it
}
}
@ -1392,7 +1405,7 @@ bool TryLandmarkSpawn(edict_t* ent, vec3_t& origin, vec3_t& angles)
// sometimes, landmark spawns can cause slight inconsistencies in collision;
// we'll do a bit of tracing to make sure the bbox is clear
if (G_FixStuckObject_Generic(origin, PLAYER_MINS, PLAYER_MAXS, [ent] (const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end) {
return gi.trace(start, mins, maxs, end, ent, MASK_PLAYERSOLID);
return gi.trace(start, mins, maxs, end, ent, MASK_PLAYERSOLID & ~CONTENTS_PLAYER);
}) == stuck_result_t::NO_GOOD_POSITION)
{
origin = old_origin;
@ -1451,11 +1464,26 @@ bool SelectSpawnPoint(edict_t *ent, vec3_t &origin, vec3_t &angles, bool force_s
if (coop->integer)
{
spot = SelectCoopSpawnPoint(ent, force_spawn);
spot = SelectCoopSpawnPoint(ent, force_spawn, true);
if (!spot)
spot = SelectCoopSpawnPoint(ent, force_spawn, false);
// no open spot yet
if (!spot)
{
// in worst case scenario in coop during intermission, just spawn us at intermission
// spot. this only happens for a single frame, and won't break
// anything if they come back.
if (level.intermissiontime)
{
origin = level.intermission_origin;
angles = level.intermission_angle;
return true;
}
return false;
}
}
else
{
@ -2227,15 +2255,20 @@ void PutClientInServer(edict_t *ent)
{
if (coop->integer)
{
if (edict_t *collision = G_UnsafeSpawnPosition(ent->s.origin); collision && collision->client)
edict_t *collision = G_UnsafeSpawnPosition(ent->s.origin, true);
if (collision)
{
// link us early so that the other player sees us there
gi.linkentity(ent);
// we spawned in somebody else, so we're going to change their spawn position
bool lm = false;
SelectSpawnPoint(collision, spawn_origin, spawn_angles, true, lm);
PutClientOnSpawnPoint(collision, spawn_origin, spawn_angles);
if (collision->client)
{
// we spawned in somebody else, so we're going to change their spawn position
bool lm = false;
SelectSpawnPoint(collision, spawn_origin, spawn_angles, true, lm);
PutClientOnSpawnPoint(collision, spawn_origin, spawn_angles);
}
// else, no choice but to accept where ever we spawned :(
}
}
@ -2285,6 +2318,11 @@ void ClientBeginDeathmatch(edict_t *ent)
InitClientResp(ent->client);
// ZOID
if (G_TeamplayEnabled() && ent->client->resp.ctf_team < CTF_TEAM1)
CTFAssignTeam(ent->client);
// ZOID
// PGM
if (gamerules->integer && DMGame.ClientBegin)
{
@ -2433,6 +2471,9 @@ void ClientBegin(edict_t *ent)
ent->client->awaiting_respawn = false;
ent->client->respawn_timeout = 0_ms;
// [Paril-KEX] we're always connected by this point...
ent->client->pers.connected = true;
if (deathmatch->integer)
{
ClientBeginDeathmatch(ent);
@ -2732,7 +2773,7 @@ inline edict_t *ClientChooseSlot_Coop(const char *userinfo, const char *social_i
// make sure we have a known default
matches[i].slot->svflags |= SVF_PLAYER;
matches[i].slot->sv.init = true;
matches[i].slot->sv.init = false;
matches[i].slot->classname = "player";
matches[i].slot->client->pers.connected = true;
matches[i].slot->client->pers.spawned = true;
@ -2872,6 +2913,9 @@ bool ClientConnect(edict_t *ent, char *userinfo, const char *social_id, bool isB
}
ent->client->pers.connected = true;
// [Paril-KEX] force a state update
ent->sv.init = false;
return true;
}
@ -3190,7 +3234,9 @@ void ClientThink(edict_t *ent, usercmd_t *ucmd)
client->ps.pmove.pm_type = PM_NORMAL;
// [Paril-KEX]
if (!G_ShouldPlayersCollide(false))
if (!G_ShouldPlayersCollide(false) ||
(coop->integer && !(ent->clipmask & CONTENTS_PLAYER)) // if player collision is on and we're temporarily ghostly...
)
client->ps.pmove.pm_flags |= PMF_IGNORE_PLAYER_COLLISION;
else
client->ps.pmove.pm_flags &= ~PMF_IGNORE_PLAYER_COLLISION;
@ -3248,7 +3294,8 @@ void ClientThink(edict_t *ent, usercmd_t *ucmd)
if (pm.s.pm_flags & PMF_ON_LADDER)
{
if (client->last_ladder_sound < level.time)
if (!deathmatch->integer &&
client->last_ladder_sound < level.time)
{
ent->s.event = EV_LADDER_STEP;
client->last_ladder_sound = level.time + LADDER_SOUND_TIME;
@ -3407,12 +3454,15 @@ inline bool G_MonstersSearchingFor(edict_t *player)
{
for (auto ent : active_monsters())
{
// check for *any* player target
if (player == nullptr && ent->enemy && !ent->enemy->client)
continue;
// they're not targeting us, so who cares
if (ent->enemy != player)
else if (player != nullptr && ent->enemy != player)
continue;
// they lost sight of us
if (ent->monsterinfo.aiflags & AI_LOST_SIGHT && level.time > ent->monsterinfo.trail_time + 5_sec)
if ((ent->monsterinfo.aiflags & AI_LOST_SIGHT) && level.time > ent->monsterinfo.trail_time + 5_sec)
continue;
// no sir
@ -3524,6 +3574,8 @@ inline bool G_FindRespawnSpot(edict_t *player, vec3_t &spot)
// respawn target & position
inline std::tuple<edict_t *, vec3_t> G_FindSquadRespawnTarget()
{
bool monsters_searching_for_anybody = G_MonstersSearchingFor(nullptr);
for (auto player : active_players())
{
// no dead players
@ -3545,6 +3597,14 @@ inline std::tuple<edict_t *, vec3_t> G_FindSquadRespawnTarget()
continue;
}
// check firing state; if any enemies are mad at any players,
// don't respawn until everybody has cooled down
if (monsters_searching_for_anybody && player->client->last_firing_time >= level.time)
{
player->client->coop_respawn_state = COOP_RESPAWN_IN_COMBAT;
continue;
}
// check positioning; we must be on world ground
if (player->groundentity != world)
{

View file

@ -279,6 +279,8 @@ void BeginIntermission(edict_t *targ)
game.autosaved = false;
level.intermissiontime = level.time;
// respawn any dead clients
for (uint32_t i = 0; i < game.maxclients; i++)
{
@ -286,10 +288,17 @@ void BeginIntermission(edict_t *targ)
if (!client->inuse)
continue;
if (client->health <= 0)
{
// give us our max health back since it will reset
// to pers.health; in instanced items we'd lose the items
// we touched so we always want to respawn with our max.
if (P_UseCoopInstancedItems())
client->client->pers.health = client->client->pers.max_health = client->max_health;
respawn(client);
}
}
level.intermissiontime = level.time;
level.intermission_server_frame = gi.ServerFrame();
level.changemap = targ->map;
level.intermission_clear = targ->spawnflags.has(SPAWNFLAG_CHANGELEVEL_CLEAR_INVENTORY);
@ -754,6 +763,7 @@ void G_SetStats(edict_t *ent)
ent->client->ps.stats[STAT_ACTIVE_WHEEL_WEAPON] = (ent->client->newweapon ? ent->client->newweapon->weapon_wheel_index :
ent->client->pers.weapon ? ent->client->pers.weapon->weapon_wheel_index :
-1);
ent->client->ps.stats[STAT_ACTIVE_WEAPON] = ent->client->pers.weapon ? ent->client->pers.weapon->weapon_wheel_index : -1;
//
// ammo

View file

@ -479,10 +479,27 @@ void PM_StepSlideMove()
// push down the final amount
down = pml.origin;
down[2] -= stepSize;
// [Paril-KEX] jitspoe suggestion for stair clip fix; store
// the old down position, and pick a better spot for downwards
// trace if the start origin's Z position is lower than the down end pt.
vec3_t original_down = down;
if (start_o[2] < down[2])
down[2] = start_o[2] - 1.f;
trace = PM_Trace(pml.origin, pm->mins, pm->maxs, down);
if (!trace.allsolid)
{
pml.origin = trace.endpos;
// [Paril-KEX] from above, do the proper trace now
trace_t real_trace = PM_Trace(pml.origin, pm->mins, pm->maxs, original_down);
pml.origin = real_trace.endpos;
// only an upwards jump is a stair clip
if (pml.velocity.z > 0.f)
{
pm->step_clip = true;
}
}
up = pml.origin;
@ -496,7 +513,9 @@ void PM_StepSlideMove()
pml.origin = down_o;
pml.velocity = down_v;
}
else if (pm->s.pm_flags & PMF_ON_GROUND)
// [Paril-KEX] NB: this line being commented is crucial for ramp-jumps to work.
// thanks to Jitspoe for pointing this one out.
else// if (pm->s.pm_flags & PMF_ON_GROUND)
//!! Special case
// if we were walking along a plane, then we need to copy the Z over
pml.velocity[2] = down_v[2];
@ -1012,7 +1031,12 @@ void PM_CatagorizePosition()
pm->s.pm_time = 64;
}
PM_ClipVelocity(pml.velocity, pm->groundplane.normal, pml.velocity, 1.01f);
// [Paril-KEX] calculate impact delta; this also fixes triple jumping
vec3_t clipped_velocity;
PM_ClipVelocity(pml.velocity, pm->groundplane.normal, clipped_velocity, 1.01f);
pm->impact_delta = pml.start_velocity[2] - clipped_velocity[2];
pm->s.pm_flags |= PMF_ON_GROUND;
if (pm_config.n64_physics || (pm->s.pm_flags & PMF_DUCKED))
@ -1073,15 +1097,6 @@ void PM_CheckJump()
float jump_height = 270.f;
// [Paril-KEX]
if (pm->s.pm_flags & PMF_TIME_TRICK)
{
pm->s.pm_flags &= ~PMF_TIME_TRICK;
pm->s.pm_time = 0;
jump_height *= 1.40f;
}
pml.velocity[2] += jump_height;
if (pml.velocity[2] < jump_height)
pml.velocity[2] = jump_height;
@ -1125,6 +1140,9 @@ void PM_CheckSpecialMovement()
if (pm->waterlevel != WATER_WAIST)
return;
// [Paril-KEX]
else if (pm->watertype & CONTENTS_NO_WATERJUMP)
return;
// quick check that something is even blocking us forward
trace = PM_Trace(pml.origin, pm->mins, pm->maxs, pml.origin + (flatforward * 40), MASK_SOLID);
@ -1561,6 +1579,7 @@ void Pmove(pmove_t *pmove)
pm->screen_blend = {};
pm->rdflags = RDF_NONE;
pm->jump_sound = false;
pm->step_clip = false;
pm->impact_delta = 0;
// clear all pmove local vars
@ -1621,8 +1640,6 @@ void Pmove(pmove_t *pmove)
if (PM_CheckDuck())
PM_CatagorizePosition();
bool was_on_ground = !!pm->groundentity;
if (pm->s.pm_type == PM_DEAD)
PM_DeadMove();
@ -1680,10 +1697,6 @@ void Pmove(pmove_t *pmove)
// set groundentity, watertype, and waterlevel for final spot
PM_CatagorizePosition();
// impact
if (pm->groundentity && !was_on_ground)
pm->impact_delta = pml.start_velocity[2] - pml.velocity[2];
// trick jump
if (pm->s.pm_flags & PMF_TIME_TRICK)
PM_CheckJump();

View file

@ -323,7 +323,7 @@ void SV_CalcViewOffset(edict_t *ent)
}
ent->client->ps.viewangles[YAW] = ent->client->killer_yaw;
}
else
else if (!ent->client->pers.bob_skip && !SkipViewModifiers())
{
// add angles based on weapon kick
angles = P_CurrentKickAngles(ent);
@ -416,29 +416,29 @@ void SV_CalcViewOffset(edict_t *ent)
v = {};
// add fall height
if (ent->client->fall_time > level.time)
{
// [Paril-KEX] 100ms of slack is added to account for
// visual difference in higher tickrates
gtime_t diff = ent->client->fall_time - level.time;
// slack time remaining
if (DAMAGE_TIME_SLACK())
{
if (diff > FALL_TIME() - DAMAGE_TIME_SLACK())
ratio = (FALL_TIME() - diff).seconds() / DAMAGE_TIME_SLACK().seconds();
else
ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds();
}
else
ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds();
v[2] -= ratio * ent->client->fall_value * 0.4f;
}
// add bob height
if (!ent->client->pers.bob_skip && !SkipViewModifiers())
{
if (ent->client->fall_time > level.time)
{
// [Paril-KEX] 100ms of slack is added to account for
// visual difference in higher tickrates
gtime_t diff = ent->client->fall_time - level.time;
// slack time remaining
if (DAMAGE_TIME_SLACK())
{
if (diff > FALL_TIME() - DAMAGE_TIME_SLACK())
ratio = (FALL_TIME() - diff).seconds() / DAMAGE_TIME_SLACK().seconds();
else
ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds();
}
else
ratio = diff.seconds() / (FALL_TIME() - DAMAGE_TIME_SLACK()).seconds();
v[2] -= ratio * ent->client->fall_value * 0.4f;
}
// add bob height
bob = bobfracsin * xyspeed * bob_up->value;
if (bob > 6)
bob = 6;
@ -447,9 +447,9 @@ void SV_CalcViewOffset(edict_t *ent)
}
// add kick offset
v += P_CurrentKickOrigin(ent);
if (!ent->client->pers.bob_skip && !SkipViewModifiers())
v += P_CurrentKickOrigin(ent);
// absolutely bound offsets
// so the view can never be outside the player box
@ -532,6 +532,9 @@ void SV_CalcGunOffset(edict_t *ent)
else if (d < 0)
d = min(0.f, d + gi.frame_time_ms * reduction_factor);
}
// [Paril-KEX] cl_rollhack
ent->client->ps.gunangles[ROLL] = -ent->client->ps.gunangles[ROLL];
}
// ROGUE
else
@ -790,7 +793,7 @@ void P_WorldEffects()
// play a gurp sound instead of a normal pain sound
if (current_player->health <= current_player->dmg)
gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/drown1.wav"), 1, ATTN_NORM, 0);
gi.sound(current_player, CHAN_VOICE, gi.soundindex("*drown1.wav"), 1, ATTN_NORM, 0); // [Paril-KEX]
else if (brandom())
gi.sound(current_player, CHAN_VOICE, gi.soundindex("*gurp1.wav"), 1, ATTN_NORM, 0);
else
@ -802,12 +805,11 @@ void P_WorldEffects()
}
}
// Paril: almost-drowning sounds
// FIXME use better sound + precache in worldspawn
else if (current_player->air_finished <= level.time + 3_sec)
{
if (current_player->client->next_drown_time < level.time)
{
gi.sound(current_player, CHAN_VOICE, gi.soundindex("player/wade1.wav"), 1, ATTN_NORM, 0);
gi.sound(current_player, CHAN_VOICE, gi.soundindex(fmt::format("player/wade{}.wav", 1 + ((int32_t) level.time.seconds() % 3)).c_str()), 1, ATTN_NORM, 0);
current_player->client->next_drown_time = level.time + 1_sec;
}
}
@ -998,7 +1000,8 @@ void G_SetClientEvent(edict_t *ent)
if (ent->client->ps.pmove.pm_flags & PMF_ON_LADDER)
{
if (current_client->last_ladder_sound < level.time &&
if (!deathmatch->integer &&
current_client->last_ladder_sound < level.time &&
(current_client->last_ladder_pos - ent->s.origin).length() > 48.f)
{
ent->s.event = EV_LADDER_STEP;
@ -1416,7 +1419,8 @@ void ClientEndServerFrame(edict_t *ent)
ent->s.angles[YAW] = ent->client->v_angle[YAW];
ent->s.angles[ROLL] = 0;
ent->s.angles[ROLL] = SV_CalcRoll(ent->s.angles, ent->velocity) * 4;
// [Paril-KEX] cl_rollhack
ent->s.angles[ROLL] = -SV_CalcRoll(ent->s.angles, ent->velocity) * 4;
//
// calculate speed and cycle to be used for
@ -1524,4 +1528,30 @@ void ClientEndServerFrame(edict_t *ent)
G_SaveLagCompensation(ent);
Compass_Update(ent, false);
// [Paril-KEX] in coop, if player collision is enabled and
// we are currently in no-player-collision mode, check if
// it's safe.
if (coop->integer && G_ShouldPlayersCollide(false) && !(ent->clipmask & CONTENTS_PLAYER) && ent->takedamage)
{
bool clipped_player = false;
for (auto player : active_players())
{
if (player == ent)
continue;
trace_t clip = gi.clip(player, ent->s.origin, ent->mins, ent->maxs, ent->s.origin, CONTENTS_MONSTER | CONTENTS_PLAYER);
if (clip.startsolid || clip.allsolid)
{
clipped_player = true;
break;
}
}
// safe!
if (!clipped_player)
ent->clipmask |= CONTENTS_PLAYER;
}
}

View file

@ -27,7 +27,7 @@ bool G_CheckInfiniteAmmo(gitem_t *item)
if (item->flags & IF_NO_INFINITE_AMMO)
return false;
return g_infinite_ammo->integer || g_instagib->integer;
return g_infinite_ammo->integer || (deathmatch->integer && g_instagib->integer);
}
//========
@ -456,7 +456,7 @@ void G_RemoveAmmo(edict_t *ent)
// [Paril-KEX] get time per animation frame
inline gtime_t Weapon_AnimationTime(edict_t *ent)
{
if (g_quick_weapon_switch->integer && (gi.tick_rate == 20 || gi.tick_rate == 40) &&
if (g_quick_weapon_switch->integer && (gi.tick_rate >= 20) &&
(ent->client->weaponstate == WEAPON_ACTIVATING || ent->client->weaponstate == WEAPON_DROPPING))
ent->client->ps.gunrate = 20;
else
@ -635,6 +635,10 @@ Drop_Weapon
*/
void Drop_Weapon(edict_t *ent, gitem_t *item)
{
// [Paril-KEX]
if (deathmatch->integer && g_dm_weapons_stay->integer)
return;
item_id_t index = item->id;
// see if we're already using it
if (((item == ent->client->pers.weapon) || (item == ent->client->newweapon)) && (ent->client->pers.inventory[index] == 1))
@ -817,6 +821,7 @@ inline weapon_ready_state_t Weapon_HandleReady(edict_t *ent, int FRAME_FIRE_FIRS
(ent->client->pers.inventory[ent->client->pers.weapon->ammo] >= ent->client->pers.weapon->quantity))
{
ent->client->weaponstate = WEAPON_FIRING;
ent->client->last_firing_time = level.time + COOP_DAMAGE_FIRING_TIME;
return READY_FIRING;
}
else
@ -928,6 +933,7 @@ void Weapon_Generic(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST,
if (ent->client->weaponstate == WEAPON_FIRING && ent->client->weapon_think_time <= level.time)
{
ent->client->last_firing_time = level.time + COOP_DAMAGE_FIRING_TIME;
ent->client->ps.gunframe++;
Weapon_HandleFiring(ent, FRAME_IDLE_FIRST, [&]() {
for (int n = 0; fire_frames[n]; n++)
@ -963,6 +969,7 @@ void Weapon_Repeating(edict_t *ent, int FRAME_ACTIVATE_LAST, int FRAME_FIRE_LAST
if (ent->client->weaponstate == WEAPON_FIRING && ent->client->weapon_think_time <= level.time)
{
ent->client->last_firing_time = level.time + COOP_DAMAGE_FIRING_TIME;
Weapon_HandleFiring(ent, FRAME_IDLE_FIRST, [&]() { fire(ent); });
if (ent->client->weapon_thunk)
@ -1092,6 +1099,8 @@ void Throw_Generic(edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int F
if (ent->client->weaponstate == WEAPON_FIRING)
{
ent->client->last_firing_time = level.time + COOP_DAMAGE_FIRING_TIME;
if (ent->client->weapon_think_time <= level.time)
{
if (prime_sound && ent->client->ps.gunframe == FRAME_PRIME_SOUND)
@ -1108,12 +1117,10 @@ void Throw_Generic(edict_t *ent, int FRAME_FIRE_LAST, int FRAME_IDLE_LAST, int F
if (ent->client->ps.gunframe == FRAME_THROW_HOLD)
{
if (!ent->client->grenade_time && !ent->client->grenade_finished_time)
{
ent->client->grenade_time = level.time + GRENADE_TIMER + 200_ms;
if (primed_sound)
ent->client->weapon_sound = gi.soundindex(primed_sound);
}
if (primed_sound && !ent->client->grenade_blew_up)
ent->client->weapon_sound = gi.soundindex(primed_sound);
// they waited too long, detonate it in their hand
if (EXPLODE && !ent->client->grenade_blew_up && level.time >= ent->client->grenade_time)
@ -1775,8 +1782,19 @@ RAILGUN
void weapon_railgun_fire(edict_t *ent)
{
int damage = 100;
int kick = 200;
int damage, kick;
// normal damage too extreme for DM
if (deathmatch->integer)
{
damage = 100;
kick = 200;
}
else
{
damage = 125;
kick = 225;
}
if (is_quad)
{

View file

@ -114,7 +114,7 @@ using std::clamp;
template<typename T>
constexpr T lerp(T from, T to, float t)
{
return t * from + (1.f - t) * to;
return (to * t) + (from * (1.f - t));
}
// angle indexes

View file

@ -60,7 +60,7 @@ USE(stationarymonster_triggered_spawn_use) (edict_t *self, edict_t *other, edict
// 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)
if (activator && activator->client)
self->enemy = activator;
self->use = monster_use;
}

View file

@ -91,26 +91,26 @@ inline item_id_t FindSubstituteItem(edict_t *ent)
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)
if (i == IT_ITEM_SPHERE_VENGEANCE ||
i == IT_ITEM_SPHERE_HUNTER ||
i == IT_ITEM_SPHERE_DEFENDER)
{
continue;
}
}
if (g_no_nukes->integer && ent->item->id == IT_AMMO_NUKE)
if (g_no_nukes->integer && i == 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))
(i == IT_AMMO_PROX || i == IT_AMMO_TESLA || i == IT_AMMO_TRAP || i == IT_WEAPON_PROXLAUNCHER))
continue;
itflags = GetSubstituteItemFlags(i);
if ((itflags & IF_TYPE_MASK) == (myflags & IF_TYPE_MASK))
possible_items[possible_item_count++] = i;
}

View file

@ -567,6 +567,8 @@ bool fire_player_melee(edict_t *self, const vec3_t &start, const vec3_t &aim, in
if (!hit->inuse || !hit->takedamage)
continue;
else if (!CanDamage(self, hit))
continue;
// do the damage
vec3_t closest_point_to_check = closest_point_to_box(start, hit->s.origin + hit->mins, hit->s.origin + hit->maxs);

View file

@ -169,7 +169,7 @@ THINK(spawngrow_think) (edict_t *self) -> void
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.scale = clamp(lerp(self->decel, self->accel, t) / 16.f, 0.001f, 16.f);
self->s.alpha = t * t;
self->nextthink += FRAME_TIME_MS;

View file

@ -31,15 +31,15 @@ void drawbbox(edict_t *self);
void ED_CallSpawn(edict_t *ent);
static int sound_pain1;
static int sound_pain2;
static int sound_pain3;
static int sound_death;
static int sound_sight;
static int sound_rail;
static int sound_spawn;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_pain3;
static cached_soundindex sound_death;
static cached_soundindex sound_sight;
static cached_soundindex sound_rail;
static cached_soundindex sound_spawn;
static int sound_cg_down, sound_cg_loop, sound_cg_up;
static cached_soundindex sound_cg_down, sound_cg_loop, sound_cg_up;
float orig_yaw_speed;
@ -1002,48 +1002,9 @@ DIE(carrier_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int dama
MONSTERINFO_CHECKATTACK(Carrier_CheckAttack) (edict_t *self) -> bool
{
vec3_t spot1, spot2;
vec3_t temp;
float chance;
trace_t tr;
bool enemy_infront, enemy_inback, enemy_below;
float enemy_yaw;
if (self->enemy->health > 0)
{
// see if any entities are in the way of the shot
spot1 = self->s.origin;
spot1[2] += self->viewheight;
spot2 = self->enemy->s.origin;
spot2[2] += self->enemy->viewheight;
tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_PLAYER | CONTENTS_MONSTER | CONTENTS_SLIME | CONTENTS_LAVA);
// do we have a clear shot?
if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER))
{
// go ahead and spawn stuff if we're mad a a client
if (self->enemy->client && M_SlotsLeft(self) > 2)
{
self->monsterinfo.attack_state = AS_BLIND;
return true;
}
// PGM - we want them to go ahead and shoot at info_notnulls if they can.
if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM
return false;
}
}
enemy_infront = infront(self, self->enemy);
enemy_inback = inback(self, self->enemy);
enemy_below = below(self, self->enemy);
float enemy_range = range_to(self, self->enemy);
temp = self->enemy->s.origin - self->s.origin;
enemy_yaw = vectoyaw(temp);
self->ideal_yaw = enemy_yaw;
bool enemy_infront = infront(self, self->enemy);
bool enemy_inback = inback(self, self->enemy);
bool enemy_below = below(self, self->enemy);
// PMM - shoot out the back if appropriate
if ((enemy_inback) || (!enemy_infront && enemy_below))
@ -1061,53 +1022,7 @@ MONSTERINFO_CHECKATTACK(Carrier_CheckAttack) (edict_t *self) -> bool
}
}
// melee attack
if (enemy_range <= RANGE_MELEE)
{
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
// if (level.time < self->monsterinfo.attack_finished)
// return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.4f;
}
else if (enemy_range <= RANGE_MELEE)
{
chance = 0.8f;
}
else if (enemy_range <= RANGE_NEAR)
{
chance = 0.8f;
}
else if (enemy_range <= RANGE_MID)
{
chance = 0.8f;
}
else
{
chance = 0.5f;
}
// PGM - go ahead and shoot every time if it's a info_notnull
if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT))
{
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
if (self->flags & FL_FLY)
{
if (frandom() < 0.6f)
self->monsterinfo.attack_state = AS_SLIDING;
else
self->monsterinfo.attack_state = AS_STRAIGHT;
}
return false;
return M_CheckAttack_Base(self, 0.4f, 0.8f, 0.8f, 0.8f, 0.5f, 0.f);
}
void CarrierPrecache()
@ -1145,17 +1060,17 @@ void SP_monster_carrier(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("carrier/pain_md.wav");
sound_pain2 = gi.soundindex("carrier/pain_lg.wav");
sound_pain3 = gi.soundindex("carrier/pain_sm.wav");
sound_death = gi.soundindex("carrier/death.wav");
sound_rail = gi.soundindex("gladiator/railgun.wav");
sound_sight = gi.soundindex("carrier/sight.wav");
sound_spawn = gi.soundindex("medic_commander/monsterspawn1.wav");
sound_pain1.assign("carrier/pain_md.wav");
sound_pain2.assign("carrier/pain_lg.wav");
sound_pain3.assign("carrier/pain_sm.wav");
sound_death.assign("carrier/death.wav");
sound_rail.assign("gladiator/railgun.wav");
sound_sight.assign("carrier/sight.wav");
sound_spawn.assign("medic_commander/monsterspawn1.wav");
sound_cg_down = gi.soundindex("weapons/chngnd1a.wav");
sound_cg_loop = gi.soundindex("weapons/chngnl1a.wav");
sound_cg_up = gi.soundindex("weapons/chngnu1a.wav");
sound_cg_down.assign("weapons/chngnd1a.wav");
sound_cg_loop.assign("weapons/chngnl1a.wav");
sound_cg_up.assign("weapons/chngnu1a.wav");
self->monsterinfo.engine_sound = gi.soundindex("bosshovr/bhvengn1.wav");

View file

@ -12,12 +12,12 @@ stalker
#include "m_rogue_stalker.h"
#include <float.h>
static int sound_pain;
static int sound_die;
static int sound_sight;
static int sound_punch_hit1;
static int sound_punch_hit2;
static int sound_idle;
static cached_soundindex sound_pain;
static cached_soundindex sound_die;
static cached_soundindex sound_sight;
static cached_soundindex sound_punch_hit1;
static cached_soundindex sound_punch_hit2;
static cached_soundindex sound_idle;
bool stalker_do_pounce(edict_t *self, const vec3_t &dest);
void stalker_walk(edict_t *self);
@ -979,12 +979,12 @@ void SP_monster_stalker(edict_t *self)
return;
}
sound_pain = gi.soundindex("stalker/pain.wav");
sound_die = gi.soundindex("stalker/death.wav");
sound_sight = gi.soundindex("stalker/sight.wav");
sound_punch_hit1 = gi.soundindex("stalker/melee1.wav");
sound_punch_hit2 = gi.soundindex("stalker/melee2.wav");
sound_idle = gi.soundindex("stalker/idle.wav");
sound_pain.assign("stalker/pain.wav");
sound_die.assign("stalker/death.wav");
sound_sight.assign("stalker/sight.wav");
sound_punch_hit1.assign("stalker/melee1.wav");
sound_punch_hit2.assign("stalker/melee2.wav");
sound_idle.assign("stalker/idle.wav");
// PMM - precache bolt2
gi.modelindex("models/objects/laser/tris.md2");

View file

@ -28,7 +28,7 @@ void turret_run(edict_t *self);
extern const mmove_t turret_move_fire;
extern const mmove_t turret_move_fire_blind;
static int sound_moved, sound_moving;
static cached_soundindex sound_moved, sound_moving;
void TurretAim(edict_t *self)
{
@ -399,18 +399,10 @@ void TurretFire(edict_t *self)
chance = frandom();
// rockets fire less often than the others do.
if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET))
{
chance = chance * 3;
rocketSpeed = 650;
}
else if (self->spawnflags.has(SPAWNFLAG_TURRET_BLASTER))
{
rocketSpeed = 800;
chance = chance * 2;
}
else
rocketSpeed = 0;
@ -500,16 +492,11 @@ void TurretFireBlind(edict_t *self)
return;
if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET))
{
if (skill->integer == 2)
{
rocketSpeed += (int) frandom(200);
}
else if (skill->integer == 3)
{
rocketSpeed += (int) frandom(100, 300);
}
}
rocketSpeed = 650;
else if (self->spawnflags.has(SPAWNFLAG_TURRET_BLASTER))
rocketSpeed = 800;
else
rocketSpeed = 0;
start = self->s.origin;
end = self->monsterinfo.blind_fire_target;
@ -524,9 +511,9 @@ void TurretFireBlind(edict_t *self)
dir.normalize();
if (self->spawnflags.has(SPAWNFLAG_TURRET_BLASTER))
monster_fire_blaster(self, start, dir, 20, 1000, MZ2_TURRET_BLASTER, EF_BLASTER);
monster_fire_blaster(self, start, dir, TURRET_BLASTER_DAMAGE, rocketSpeed, MZ2_TURRET_BLASTER, EF_BLASTER);
else if (self->spawnflags.has(SPAWNFLAG_TURRET_ROCKET))
monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_TURRET_ROCKET);
monster_fire_rocket(self, start, dir, 40, rocketSpeed, MZ2_TURRET_ROCKET);
}
// pmm
@ -623,7 +610,7 @@ DIE(turret_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag
if (self->teamchain)
{
base = self->teamchain;
base->solid = SOLID_BBOX;
base->solid = SOLID_NOT;
base->takedamage = false;
base->movetype = MOVETYPE_NONE;
base->teammaster = base;
@ -937,8 +924,8 @@ void SP_monster_turret(edict_t *self)
}
// pre-caches
sound_moved = gi.soundindex("turret/moved.wav");
sound_moving = gi.soundindex("turret/moving.wav");
sound_moved.assign("turret/moved.wav");
sound_moving.assign("turret/moving.wav");
gi.modelindex("models/objects/debris1/tris.md2");
self->s.modelindex = gi.modelindex("models/monsters/turret/tris.md2");

View file

@ -22,10 +22,10 @@ constexpr int WIDOW_RAIL_DAMAGE = 50;
bool infront(edict_t *self, edict_t *other);
static int sound_pain1;
static int sound_pain2;
static int sound_pain3;
static int sound_rail;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_pain3;
static cached_soundindex sound_rail;
static uint32_t shotsfired;
@ -1126,13 +1126,6 @@ void WidowPowerups(edict_t *self)
MONSTERINFO_CHECKATTACK(Widow_CheckAttack) (edict_t *self) -> bool
{
vec3_t spot1, spot2;
vec3_t temp;
float chance;
trace_t tr;
float enemy_yaw;
float real_enemy_range;
if (!self->enemy)
return false;
@ -1165,85 +1158,7 @@ MONSTERINFO_CHECKATTACK(Widow_CheckAttack) (edict_t *self) -> bool
return true;
}
if (self->enemy->health > 0)
{
// see if any entities are in the way of the shot
spot1 = self->s.origin;
spot1[2] += self->viewheight;
spot2 = self->enemy->s.origin;
spot2[2] += self->enemy->viewheight;
tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA);
// do we have a clear shot?
if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER))
{
// go ahead and spawn stuff if we're mad a a client
if (self->enemy->client && M_SlotsLeft(self) >= 2)
{
self->monsterinfo.attack_state = AS_BLIND;
return true;
}
// PGM - we want them to go ahead and shoot at info_notnulls if they can.
if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM
return false;
}
}
float enemy_range = range_to(self, self->enemy);
temp = self->enemy->s.origin - self->s.origin;
enemy_yaw = vectoyaw(temp);
self->ideal_yaw = enemy_yaw;
real_enemy_range = realrange(self, self->enemy);
// melee attack
if (real_enemy_range <= (MELEE_DISTANCE + 20))
{
// don't always melee in easy mode
if (skill->integer == 0 && irandom(4))
return false;
if (self->monsterinfo.melee)
self->monsterinfo.attack_state = AS_MELEE;
else
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
if (level.time < self->monsterinfo.attack_finished)
return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.4f;
}
else if (enemy_range <= RANGE_MELEE)
{
chance = 0.8f;
}
else if (enemy_range <= RANGE_NEAR)
{
chance = 0.7f;
}
else if (enemy_range <= RANGE_MID)
{
chance = 0.6f;
}
else
{
chance = 0.5f;
}
// PGM - go ahead and shoot every time if it's a info_notnull
if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT))
{
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
return false;
return M_CheckAttack_Base(self, 0.4f, 0.8f, 0.7f, 0.6f, 0.5f, 0.f);
}
MONSTERINFO_BLOCKED(widow_blocked) (edict_t *self, float dist) -> bool
@ -1333,10 +1248,10 @@ void SP_monster_widow(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("widow/bw1pain1.wav");
sound_pain2 = gi.soundindex("widow/bw1pain2.wav");
sound_pain3 = gi.soundindex("widow/bw1pain3.wav");
sound_rail = gi.soundindex("gladiator/railgun.wav");
sound_pain1.assign("widow/bw1pain1.wav");
sound_pain2.assign("widow/bw1pain2.wav");
sound_pain3.assign("widow/bw1pain3.wav");
sound_rail.assign("gladiator/railgun.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;

View file

@ -14,12 +14,12 @@ black widow, part 2
#include "m_rogue_widow2.h"
#include "../m_flash.h"
static int sound_pain1;
static int sound_pain2;
static int sound_pain3;
static int sound_death;
static int sound_search1;
static int sound_tentacles_retract;
static cached_soundindex sound_pain1;
static cached_soundindex sound_pain2;
static cached_soundindex sound_pain3;
static cached_soundindex sound_death;
static cached_soundindex sound_search1;
static cached_soundindex sound_tentacles_retract;
// sqrt(64*64*2) + sqrt(28*28*2) => 130.1
constexpr vec3_t spawnpoints[] = {
@ -685,14 +685,19 @@ MONSTERINFO_WALK(widow2_walk) (edict_t *self) -> void
M_SetAnimation(self, &widow2_move_walk);
}
void widow2_attack(edict_t *self);
MONSTERINFO_MELEE(widow2_melee) (edict_t *self) -> void
{
M_SetAnimation(self, &widow2_move_tongs);
if (self->timestamp >= level.time)
widow2_attack(self);
else
M_SetAnimation(self, &widow2_move_tongs);
}
MONSTERINFO_ATTACK(widow2_attack) (edict_t *self) -> void
{
float range, luck;
float luck;
bool blocked = false;
if (self->monsterinfo.aiflags & AI_BLOCKED)
@ -704,6 +709,31 @@ MONSTERINFO_ATTACK(widow2_attack) (edict_t *self) -> void
if (!self->enemy)
return;
float real_enemy_range = realrange(self, self->enemy);
// melee attack
if (self->timestamp < level.time)
{
if (real_enemy_range < 300)
{
vec3_t f, r, u;
AngleVectors(self->s.angles, f, r, u);
vec3_t spot1 = G_ProjectSource2(self->s.origin, offsets[0], f, r, u);
vec3_t spot2 = self->enemy->s.origin;
if (widow2_tongue_attack_ok(spot1, spot2, 256))
{
// melee attack ok
// be nice in easy mode
if (skill->integer != 0 || irandom(4))
{
M_SetAnimation(self, &widow2_move_tongs);
return;
}
}
}
}
if (self->bad_area)
{
if ((frandom() < 0.75f) || (level.time < self->monsterinfo.attack_finished))
@ -731,9 +761,7 @@ MONSTERINFO_ATTACK(widow2_attack) (edict_t *self) -> void
return;
}
range = realrange(self, self->enemy);
if (range < 600)
if (real_enemy_range < 600)
{
luck = frandom();
if (M_SlotsLeft(self) >= 2)
@ -924,14 +952,6 @@ DIE(widow2_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damag
MONSTERINFO_CHECKATTACK(Widow2_CheckAttack) (edict_t *self) -> bool
{
vec3_t spot1, spot2;
vec3_t temp;
float chance;
trace_t tr;
float enemy_yaw;
float real_enemy_range;
vec3_t f, r, u;
if (!self->enemy)
return false;
@ -944,92 +964,7 @@ MONSTERINFO_CHECKATTACK(Widow2_CheckAttack) (edict_t *self) -> bool
return true;
}
if (self->enemy->health > 0)
{
// see if any entities are in the way of the shot
spot1 = self->s.origin;
spot1[2] += self->viewheight;
spot2 = self->enemy->s.origin;
spot2[2] += self->enemy->viewheight;
tr = gi.traceline(spot1, spot2, self, CONTENTS_SOLID | CONTENTS_MONSTER | CONTENTS_PLAYER | CONTENTS_SLIME | CONTENTS_LAVA);
// do we have a clear shot?
if (tr.ent != self->enemy && !(tr.ent->svflags & SVF_PLAYER))
{
// go ahead and spawn stuff if we're mad a a client
if (self->enemy->client && M_SlotsLeft(self) >= 2)
{
self->monsterinfo.attack_state = AS_BLIND;
return true;
}
// PGM - we want them to go ahead and shoot at info_notnulls if they can.
if (self->enemy->solid != SOLID_NOT || tr.fraction < 1.0f) // PGM
return false;
}
}
float enemy_range = range_to(self, self->enemy);
temp = self->enemy->s.origin - self->s.origin;
enemy_yaw = vectoyaw(temp);
self->ideal_yaw = enemy_yaw;
// melee attack
if (self->timestamp < level.time)
{
real_enemy_range = realrange(self, self->enemy);
if (real_enemy_range < 300)
{
AngleVectors(self->s.angles, f, r, u);
spot1 = G_ProjectSource2(self->s.origin, offsets[0], f, r, u);
spot2 = self->enemy->s.origin;
if (widow2_tongue_attack_ok(spot1, spot2, 256))
{
// melee attack ok
// be nice in easy mode
if (skill->integer == 0 && irandom(4))
return false;
if (self->monsterinfo.melee)
self->monsterinfo.attack_state = AS_MELEE;
else
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
}
}
if (level.time < self->monsterinfo.attack_finished)
return false;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
chance = 0.4f;
}
else if (enemy_range <= RANGE_NEAR)
{
chance = 0.8f;
}
else if (enemy_range <= RANGE_MID)
{
chance = 0.8f;
}
else
{
chance = 0.5f;
}
// PGM - go ahead and shoot every time if it's a info_notnull
if ((frandom() < chance) || (self->enemy->solid == SOLID_NOT))
{
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
return false;
return M_CheckAttack_Base(self, 0.4f, 0.8f, 0.8f, 0.5f, 0.f, 0.f);
}
void Widow2Precache()
@ -1072,12 +1007,12 @@ void SP_monster_widow2(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("widow/bw2pain1.wav");
sound_pain2 = gi.soundindex("widow/bw2pain2.wav");
sound_pain3 = gi.soundindex("widow/bw2pain3.wav");
sound_death = gi.soundindex("widow/death.wav");
sound_search1 = gi.soundindex("bosshovr/bhvunqv1.wav");
sound_tentacles_retract = gi.soundindex("brain/brnatck3.wav");
sound_pain1.assign("widow/bw2pain1.wav");
sound_pain2.assign("widow/bw2pain2.wav");
sound_pain3.assign("widow/bw2pain3.wav");
sound_death.assign("widow/death.wav");
sound_search1.assign("bosshovr/bhvunqv1.wav");
sound_tentacles_retract.assign("brain/brnatck3.wav");
// self->s.sound = gi.soundindex ("bosshovr/bhvengn1.wav");

View file

@ -8,7 +8,7 @@
// defines
constexpr spawnflags_t SPAWNFLAG_DBALL_GOAL_TEAM1 = 0x0001_spawnflag;
[[maybe_unused]] constexpr spawnflags_t SPAWNFLAG_DBALL_GOAL_TEAM1 = 0x0001_spawnflag;
// unused; assumed by not being team1
// constexpr uint32_t SPAWNFLAG_DBALL_GOAL_TEAM2 = 0x0002;

View file

@ -117,7 +117,7 @@ void SP_misc_transport(edict_t *ent)
/*QUAKED misc_amb4 (1 0 0) (-16 -16 -16) (16 16 16)
Mal's amb4 loop entity
*/
static int amb4sound;
static cached_soundindex amb4sound;
THINK(amb4_think) (edict_t *ent) -> void
{
@ -129,7 +129,7 @@ void SP_misc_amb4(edict_t *ent)
{
ent->think = amb4_think;
ent->nextthink = level.time + 1_sec;
amb4sound = gi.soundindex("world/amb4.wav");
amb4sound.assign("world/amb4.wav");
gi.linkentity(ent);
}

View file

@ -11,11 +11,11 @@
bool infront(edict_t *self, edict_t *other);
bool FindTarget(edict_t *self);
static int sound_pain1;
static int sound_die;
static int sound_weld1;
static int sound_weld2;
static int sound_weld3;
static cached_soundindex sound_pain1;
static cached_soundindex sound_die;
static cached_soundindex sound_weld1;
static cached_soundindex sound_weld2;
static cached_soundindex sound_weld3;
void fixbot_run(edict_t *self);
void fixbot_attack(edict_t *self);
@ -1335,6 +1335,8 @@ PAIN(fixbot_pain) (edict_t *self, edict_t *other, float kick, int damage, const
M_SetAnimation(self, &fixbot_move_painb);
else
M_SetAnimation(self, &fixbot_move_paina);
abortHeal(self, false, false, false);
}
void fixbot_dead(edict_t *self)
@ -1364,12 +1366,12 @@ void SP_monster_fixbot(edict_t *self)
return;
}
sound_pain1 = gi.soundindex("flyer/flypain1.wav");
sound_die = gi.soundindex("flyer/flydeth1.wav");
sound_pain1.assign("flyer/flypain1.wav");
sound_die.assign("flyer/flydeth1.wav");
sound_weld1 = gi.soundindex("misc/welder1.wav");
sound_weld2 = gi.soundindex("misc/welder2.wav");
sound_weld3 = gi.soundindex("misc/welder3.wav");
sound_weld1.assign("misc/welder1.wav");
sound_weld2.assign("misc/welder2.wav");
sound_weld3.assign("misc/welder3.wav");
self->s.modelindex = gi.modelindex("models/monsters/fixbot/tris.md2");

View file

@ -12,22 +12,22 @@ constexpr spawnflags_t SPAWNFLAG_GEKK_CHANT = 8_spawnflag;
constexpr spawnflags_t SPAWNFLAG_GEKK_NOJUMPING = 16_spawnflag;
constexpr spawnflags_t SPAWNFLAG_GEKK_NOSWIM = 32_spawnflag;
static int sound_swing;
static int sound_hit;
static int sound_hit2;
static int sound_speet;
static int loogie_hit;
static int sound_death;
static int sound_pain1;
static int sound_sight;
static int sound_search;
static int sound_step1;
static int sound_step2;
static int sound_step3;
static int sound_thud;
static int sound_chantlow;
static int sound_chantmid;
static int sound_chanthigh;
static cached_soundindex sound_swing;
static cached_soundindex sound_hit;
static cached_soundindex sound_hit2;
static cached_soundindex sound_speet;
static cached_soundindex loogie_hit;
static cached_soundindex sound_death;
static cached_soundindex sound_pain1;
static cached_soundindex sound_sight;
static cached_soundindex sound_search;
static cached_soundindex sound_step1;
static cached_soundindex sound_step2;
static cached_soundindex sound_step3;
static cached_soundindex sound_thud;
static cached_soundindex sound_chantlow;
static cached_soundindex sound_chantmid;
static cached_soundindex sound_chanthigh;
void gekk_swim(edict_t *self);
@ -1589,23 +1589,23 @@ void SP_monster_gekk(edict_t *self)
return;
}
sound_swing = gi.soundindex("gek/gk_atck1.wav");
sound_hit = gi.soundindex("gek/gk_atck2.wav");
sound_hit2 = gi.soundindex("gek/gk_atck3.wav");
sound_speet = gi.soundindex("gek/gk_atck4.wav");
loogie_hit = gi.soundindex("gek/loogie_hit.wav");
sound_death = gi.soundindex("gek/gk_deth1.wav");
sound_pain1 = gi.soundindex("gek/gk_pain1.wav");
sound_sight = gi.soundindex("gek/gk_sght1.wav");
sound_search = gi.soundindex("gek/gk_idle1.wav");
sound_step1 = gi.soundindex("gek/gk_step1.wav");
sound_step2 = gi.soundindex("gek/gk_step2.wav");
sound_step3 = gi.soundindex("gek/gk_step3.wav");
sound_thud = gi.soundindex("mutant/thud1.wav");
sound_swing.assign("gek/gk_atck1.wav");
sound_hit.assign("gek/gk_atck2.wav");
sound_hit2.assign("gek/gk_atck3.wav");
sound_speet.assign("gek/gk_atck4.wav");
loogie_hit.assign("gek/loogie_hit.wav");
sound_death.assign("gek/gk_deth1.wav");
sound_pain1.assign("gek/gk_pain1.wav");
sound_sight.assign("gek/gk_sght1.wav");
sound_search.assign("gek/gk_idle1.wav");
sound_step1.assign("gek/gk_step1.wav");
sound_step2.assign("gek/gk_step2.wav");
sound_step3.assign("gek/gk_step3.wav");
sound_thud.assign("mutant/thud1.wav");
sound_chantlow = gi.soundindex("gek/gek_low.wav");
sound_chantmid = gi.soundindex("gek/gek_mid.wav");
sound_chanthigh = gi.soundindex("gek/gek_high.wav");
sound_chantlow.assign("gek/gek_low.wav");
sound_chantmid.assign("gek/gek_mid.wav");
sound_chanthigh.assign("gek/gek_high.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;