Initial commit

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

263
rerelease/bg_local.h Normal file
View File

@@ -0,0 +1,263 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_local.h -- local definitions for game module
#pragma once
#include "q_std.h"
// define GAME_INCLUDE so that game.h does not define the
// short, server-visible gclient_t and edict_t structures,
// because we define the full size ones in this file
#define GAME_INCLUDE
#include "game.h"
//
// p_move.c
//
struct pm_config_t
{
int32_t airaccel = 0;
bool n64_physics = false;
};
extern pm_config_t pm_config;
void Pmove(pmove_t *pmove);
using pm_trace_func_t = trace_t(const vec3_t &start, const vec3_t &mins, const vec3_t &maxs, const vec3_t &end);
using pm_trace_t = std::function<pm_trace_func_t>;
void PM_StepSlideMove_Generic(vec3_t &origin, vec3_t &velocity, float frametime, const vec3_t &mins, const vec3_t &maxs, touch_list_t &touch, bool has_time, pm_trace_t trace);
enum class stuck_result_t
{
GOOD_POSITION,
FIXED,
NO_GOOD_POSITION
};
using stuck_object_trace_fn_t = trace_t(const vec3_t &, const vec3_t &, const vec3_t &, const vec3_t &);
stuck_result_t G_FixStuckObject_Generic(vec3_t &origin, const vec3_t &own_mins, const vec3_t &own_maxs, std::function<stuck_object_trace_fn_t> trace);
// state for coop respawning; used to select which
// message to print for the player this is set on.
enum coop_respawn_t
{
COOP_RESPAWN_NONE, // no messagee
COOP_RESPAWN_IN_COMBAT, // player is in combat
COOP_RESPAWN_BAD_AREA, // player not in a good spot
COOP_RESPAWN_BLOCKED, // spawning was blocked by something
COOP_RESPAWN_WAITING, // for players that are waiting to respawn
COOP_RESPAWN_NO_LIVES, // out of lives, so need to wait until level switch
COOP_RESPAWN_TOTAL
};
// reserved general CS ranges
enum
{
CONFIG_CTF_MATCH = CS_GENERAL,
CONFIG_CTF_TEAMINFO,
CONFIG_CTF_PLAYER_NAME,
CONFIG_CTF_PLAYER_NAME_END = CONFIG_CTF_PLAYER_NAME + MAX_CLIENTS,
// nb: offset by 1 since NONE is zero
CONFIG_COOP_RESPAWN_STRING,
CONFIG_COOP_RESPAWN_STRING_END = CONFIG_COOP_RESPAWN_STRING + (COOP_RESPAWN_TOTAL - 1),
// [Paril-KEX] if 1, n64 player physics apply
CONFIG_N64_PHYSICS,
CONFIG_HEALTH_BAR_NAME, // active health bar name
CONFIG_STORY,
CONFIG_LAST
};
static_assert(CONFIG_LAST <= CS_GENERAL + MAX_GENERAL);
// ammo IDs
enum ammo_t : uint8_t
{
AMMO_BULLETS,
AMMO_SHELLS,
AMMO_ROCKETS,
AMMO_GRENADES,
AMMO_CELLS,
AMMO_SLUGS,
// RAFAEL
AMMO_MAGSLUG,
AMMO_TRAP,
// RAFAEL
// ROGUE
AMMO_FLECHETTES,
AMMO_TESLA,
AMMO_DISRUPTOR,
AMMO_PROX,
// ROGUE
AMMO_MAX
};
// powerup IDs
enum powerup_t : uint8_t
{
POWERUP_SCREEN,
POWERUP_SHIELD,
POWERUP_AM_BOMB,
POWERUP_QUAD,
POWERUP_QUADFIRE,
POWERUP_INVULNERABILITY,
POWERUP_INVISIBILITY,
POWERUP_SILENCER,
POWERUP_REBREATHER,
POWERUP_ENVIROSUIT,
POWERUP_ADRENALINE,
POWERUP_IR_GOGGLES,
POWERUP_DOUBLE,
POWERUP_SPHERE_VENGEANCE,
POWERUP_SPHERE_HUNTER,
POWERUP_SPHERE_DEFENDER,
POWERUP_DOPPELGANGER,
POWERUP_FLASHLIGHT,
POWERUP_COMPASS,
POWERUP_TECH1,
POWERUP_TECH2,
POWERUP_TECH3,
POWERUP_TECH4,
POWERUP_MAX
};
// ammo stats compressed in 9 bits per entry
// since the range is 0-300
constexpr size_t BITS_PER_AMMO = 9;
template<typename TI>
constexpr size_t num_of_type_for_bits(size_t num_bits)
{
return (num_bits + (sizeof(TI) * 8) - 1) / ((sizeof(TI) * 8) + 1);
}
template<size_t bits_per_value>
constexpr void set_compressed_integer(uint16_t *start, uint8_t id, uint16_t count)
{
uint16_t bit_offset = bits_per_value * id;
uint16_t byte = bit_offset / 8;
uint16_t bit_shift = bit_offset % 8;
uint16_t mask = (bit_v<bits_per_value> - 1) << bit_shift;
uint16_t *base = (uint16_t *) ((uint8_t *) start + byte);
*base = (*base & ~mask) | ((count << bit_shift) & mask);
}
template<size_t bits_per_value>
constexpr uint16_t get_compressed_integer(uint16_t *start, uint8_t id)
{
uint16_t bit_offset = bits_per_value * id;
uint16_t byte = bit_offset / 8;
uint16_t bit_shift = bit_offset % 8;
uint16_t mask = (bit_v<bits_per_value> - 1) << bit_shift;
uint16_t *base = (uint16_t *) ((uint8_t *) start + byte);
return (*base & mask) >> bit_shift;
}
constexpr size_t NUM_BITS_FOR_AMMO = 9;
constexpr size_t NUM_AMMO_STATS = num_of_type_for_bits<uint16_t>(NUM_BITS_FOR_AMMO * AMMO_MAX);
// if this value is set on an STAT_AMMO_INFO_xxx, don't render ammo
constexpr uint16_t AMMO_VALUE_INFINITE = bit_v<NUM_BITS_FOR_AMMO> - 1;
constexpr void G_SetAmmoStat(uint16_t *start, uint8_t ammo_id, uint16_t count)
{
set_compressed_integer<NUM_BITS_FOR_AMMO>(start, ammo_id, count);
}
constexpr uint16_t G_GetAmmoStat(uint16_t *start, uint8_t ammo_id)
{
return get_compressed_integer<NUM_BITS_FOR_AMMO>(start, ammo_id);
}
// powerup stats compressed in 2 bits per entry;
// 3 is the max you'll ever hold, and for some
// (flashlight) it's to indicate on/off state
constexpr size_t NUM_BITS_PER_POWERUP = 2;
constexpr size_t NUM_POWERUP_STATS = num_of_type_for_bits<uint16_t>(NUM_BITS_PER_POWERUP * POWERUP_MAX);
constexpr void G_SetPowerupStat(uint16_t *start, uint8_t powerup_id, uint16_t count)
{
set_compressed_integer<NUM_BITS_PER_POWERUP>(start, powerup_id, count);
}
constexpr uint16_t G_GetPowerupStat(uint16_t *start, uint8_t powerup_id)
{
return get_compressed_integer<NUM_BITS_PER_POWERUP>(start, powerup_id);
}
// player_state->stats[] indexes
enum player_stat_t
{
STAT_HEALTH_ICON = 0,
STAT_HEALTH = 1,
STAT_AMMO_ICON = 2,
STAT_AMMO = 3,
STAT_ARMOR_ICON = 4,
STAT_ARMOR = 5,
STAT_SELECTED_ICON = 6,
STAT_PICKUP_ICON = 7,
STAT_PICKUP_STRING = 8,
STAT_TIMER_ICON = 9,
STAT_TIMER = 10,
STAT_HELPICON = 11,
STAT_SELECTED_ITEM = 12,
STAT_LAYOUTS = 13,
STAT_FRAGS = 14,
STAT_FLASHES = 15, // cleared each frame, 1 = health, 2 = armor
STAT_CHASE = 16,
STAT_SPECTATOR = 17,
STAT_CTF_TEAM1_PIC = 18,
STAT_CTF_TEAM1_CAPS = 19,
STAT_CTF_TEAM2_PIC = 20,
STAT_CTF_TEAM2_CAPS = 21,
STAT_CTF_FLAG_PIC = 22,
STAT_CTF_JOINED_TEAM1_PIC = 23,
STAT_CTF_JOINED_TEAM2_PIC = 24,
STAT_CTF_TEAM1_HEADER = 25,
STAT_CTF_TEAM2_HEADER = 26,
STAT_CTF_TECH = 27,
STAT_CTF_ID_VIEW = 28,
STAT_CTF_MATCH = 29,
STAT_CTF_ID_VIEW_COLOR = 30,
STAT_CTF_TEAMINFO = 31,
// [Kex] More stats for weapon wheel
STAT_WEAPONS_OWNED_1 = 32,
STAT_WEAPONS_OWNED_2 = 33,
STAT_AMMO_INFO_START = 34,
STAT_AMMO_INFO_END = STAT_AMMO_INFO_START + NUM_AMMO_STATS - 1,
STAT_POWERUP_INFO_START,
STAT_POWERUP_INFO_END = STAT_POWERUP_INFO_START + NUM_POWERUP_STATS - 1,
// [Paril-KEX] Key display
STAT_KEY_A,
STAT_KEY_B,
STAT_KEY_C,
// [Paril-KEX] currently active wheel weapon (or one we're switching to)
STAT_ACTIVE_WHEEL_WEAPON,
// [Paril-KEX] top of screen coop respawn state
STAT_COOP_RESPAWN,
// [Paril-KEX] respawns remaining
STAT_LIVES,
// [Paril-KEX] hit marker; # of damage we successfully landed
STAT_HIT_MARKER,
// [Paril-KEX]
STAT_SELECTED_ITEM_NAME,
// [Paril-KEX]
STAT_HEALTH_BARS, // two health bar values; 7 bits for value, 1 bit for active
// if active,
// don't use; just for verification
STAT_LAST
};
static_assert(STAT_LAST <= MAX_STATS + 1, "stats list overflow");

View File

@@ -0,0 +1,178 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
#include "bot_utils.h"
#include "bot_debug.h"
static const edict_t * escortBot = nullptr;
static const edict_t * escortActor = nullptr;
static const edict_t * moveToPointBot = nullptr;
static vec3_t moveToPointPos = vec3_origin;
// how close the bot will try to get to the move to point goal
constexpr float moveToPointTolerance = 16.0f;
/*
================
ShowMonsterPathToPlayer
================
*/
void ShowMonsterPathToPlayer( const edict_t * player ) {
const edict_t * monster = FindFirstMonster();
if ( monster == nullptr ) {
return;
}
const float moveDist = 8.0f;
std::array<vec3_t, 512> pathPoints;
PathRequest request;
request.start = monster->s.origin;
request.goal = player->s.origin;
request.moveDist = moveDist;
request.pathFlags = PathFlags::All;
request.debugging.drawTime = 0.10f;
request.nodeSearch.minHeight = 64.0f;
request.nodeSearch.maxHeight = 64.0f;
request.nodeSearch.radius = 512.0f;
request.pathPoints.array = &pathPoints.front();
request.pathPoints.count = pathPoints.size();
PathInfo info;
if ( gi.GetPathToGoal( request, info ) ) {
// Do movement stuff....
for ( int i = 0; i < info.numPathPoints; ++i ) {
const gvec3_t & point = pathPoints[ i ];
gi.Draw_Point( point, 8.0f, rgba_yellow, 0.10f, false );
}
}
}
/*
================
UpdateFollowActorDebug
Set cvar "bot_debug_follow_actor" to 1
and then run your cursor over any player/monster to pick
that "actor" for the bot to follow.
When successful, you will see the player/monster highlighted
with a yellow box, and the bot will follow them around the map until
the actor they're following dies, or the bot is told to do something
else by you.
Check the console for debugging feedback...
================
*/
void UpdateFollowActorDebug( const edict_t * localPlayer ) {
if ( bot_debug_follow_actor->integer ) {
if ( bot_debug_follow_actor->integer == 1 ) {
escortBot = FindFirstBot();
escortActor = FindActorUnderCrosshair( localPlayer );
if ( gi.Bot_FollowActor( escortBot, escortActor ) != GoalReturnCode::Error ) {
gi.cvar_set( "bot_debug_follow_actor", "2" );
gi.Com_Print( "Follow_Actor: Bot Found Actor To Follow!\n" );
} else {
gi.Com_Print( "Follow_Actor: Hover Over Monster/Player To Follow...\n" );
}
} else {
if ( gi.Bot_FollowActor( escortBot, escortActor ) != GoalReturnCode::Error ) {
gi.Draw_Bounds( escortActor->absmin, escortActor->absmax, rgba_yellow, gi.frame_time_s, false );
gi.Draw_Bounds( escortBot->absmin, escortBot->absmax, rgba_cyan, gi.frame_time_s, false );
} else {
gi.Com_Print( "Follow_Actor: Bot Or Actor Removed...\n" );
gi.cvar_set( "bot_debug_follow_actor", "0" );
}
}
} else {
escortBot = nullptr;
escortActor = nullptr;
}
}
/*
================
UpdateMoveToPointDebug
Set cvar "bot_debug_move_to_point" to 1,
look anywhere in world you'd like the bot to move to,
and then fire your weapon. The point at the end of your crosshair
will be the point in the world the bot will move toward.
When successful, a point marker will be drawn where the bot will move
toward, and the bot itself will have a box drawn around it.
Once bot reaches the point, it will clear the goal and go about it's
business until you give it something else to do.
Check the console for debugging feedback...
================
*/
void UpdateMoveToPointDebug( const edict_t * localPlayer ) {
if ( bot_debug_move_to_point->integer ) {
if ( bot_debug_move_to_point->integer == 1 ) {
if ( localPlayer->client->buttons & BUTTON_ATTACK ) {
vec3_t localPlayerForward, right, up;
AngleVectors( localPlayer->client->v_angle, localPlayerForward, right, up );
const vec3_t localPlayerViewPos = ( localPlayer->s.origin + vec3_t{ 0.0f, 0.0f, (float)localPlayer->viewheight } );
const vec3_t end = ( localPlayerViewPos + ( localPlayerForward * 8192.0f ) );
const contents_t mask = ( MASK_PROJECTILE & ~CONTENTS_DEADMONSTER );
trace_t tr = gi.traceline( localPlayerViewPos, end, localPlayer, mask );
moveToPointPos = tr.endpos;
moveToPointBot = FindFirstBot();
if ( gi.Bot_MoveToPoint( moveToPointBot, moveToPointPos, moveToPointTolerance ) != GoalReturnCode::Error ) {
gi.cvar_set( "bot_debug_move_to_point", "2" );
gi.Com_Print( "Move_To_Point: Bot Has Position To Move Toward!\n" );
}
} else {
gi.Com_Print( "Move_To_Point: Fire Weapon To Select Move Point...\n" );
}
} else {
const GoalReturnCode result = gi.Bot_MoveToPoint( moveToPointBot, moveToPointPos, moveToPointTolerance );
if ( result == GoalReturnCode::Error ) {
gi.cvar_set( "bot_debug_move_to_point", "0" );
gi.Com_Print( "Move_To_Point: Bot Can't Reach Goal Position!\n" );
} else if ( result == GoalReturnCode::Finished ) {
gi.cvar_set( "bot_debug_move_to_point", "0" );
gi.Com_Print( "Move_To_Point: Bot Reached Goal Position!\n" );
} else {
gi.Draw_Point( moveToPointPos, 8.0f, rgba_yellow, gi.frame_time_s, false );
gi.Draw_Bounds( moveToPointBot->absmin, moveToPointBot->absmax, rgba_cyan, gi.frame_time_s, false );
}
}
} else {
moveToPointBot = nullptr;
moveToPointPos = vec3_origin;
}
}
/*
================
Bot_UpdateDebug
================
*/
void Bot_UpdateDebug() {
if ( !sv_cheats->integer ) {
return;
}
const edict_t * localPlayer = FindLocalPlayer();
if ( localPlayer == nullptr ) {
return;
}
if ( g_debug_monster_paths->integer == 2 ) {
ShowMonsterPathToPlayer( localPlayer );
}
UpdateFollowActorDebug( localPlayer );
UpdateMoveToPointDebug( localPlayer );
}

View File

@@ -0,0 +1,6 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#pragma once
void Bot_UpdateDebug();

View File

@@ -0,0 +1,151 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
#include "bot_exports.h"
/*
================
Bot_SetWeapon
================
*/
void Bot_SetWeapon( edict_t * bot, const int weaponIndex, const bool instantSwitch ) {
if ( weaponIndex <= IT_NULL || weaponIndex > IT_TOTAL ) {
return;
}
if ( ( bot->svflags & SVF_BOT ) == 0 ) {
return;
}
gclient_t * client = bot->client;
if ( client == nullptr || !client->pers.inventory[ weaponIndex ] ) {
return;
}
const item_id_t weaponItemID = static_cast<item_id_t>( weaponIndex );
const gitem_t * currentGun = client->pers.weapon;
if ( currentGun != nullptr ) {
if ( currentGun->id == weaponItemID ) {
return;
} // already have the gun in hand.
}
const gitem_t * pendingGun = client->newweapon;
if ( pendingGun != nullptr ) {
if ( pendingGun->id == weaponItemID ) {
return;
} // already in the process of switching to that gun, just be patient!
}
gitem_t * item = &itemlist[ weaponIndex ];
if ( ( item->flags & IF_WEAPON ) == 0 ) {
return;
}
if ( item->use == nullptr ) {
return;
}
bot->client->no_weapon_chains = true;
item->use( bot, item );
if ( instantSwitch ) {
// FIXME: ugly, maybe store in client later
const int temp_instant_weapon = g_instant_weapon_switch->integer;
g_instant_weapon_switch->integer = 1;
ChangeWeapon( bot );
g_instant_weapon_switch->integer = temp_instant_weapon;
}
}
/*
================
Bot_TriggerEdict
================
*/
void Bot_TriggerEdict( edict_t * bot, edict_t * edict ) {
if ( !bot->inuse || !edict->inuse ) {
return;
}
if ( ( bot->svflags & SVF_BOT ) == 0 ) {
return;
}
if ( edict->use ) {
edict->use( edict, bot, bot );
}
trace_t unUsed;
if ( edict->touch ) {
edict->touch( edict, bot, unUsed, true );
}
}
/*
================
Bot_UseItem
================
*/
void Bot_UseItem( edict_t * bot, const int32_t itemID ) {
if ( !bot->inuse ) {
return;
}
if ( ( bot->svflags & SVF_BOT ) == 0 ) {
return;
}
const item_id_t desiredItemID = item_id_t( itemID );
bot->client->pers.selected_item = desiredItemID;
ValidateSelectedItem( bot );
if ( bot->client->pers.selected_item == IT_NULL ) {
return;
}
if ( bot->client->pers.selected_item != desiredItemID ) {
return;
} // the itemID changed on us - don't use it!
gitem_t * item = &itemlist[ bot->client->pers.selected_item ];
bot->client->pers.selected_item = IT_NULL;
if ( item->use == nullptr ) {
return;
}
bot->client->no_weapon_chains = true;
item->use( bot, item );
}
/*
================
Bot_GetItemID
================
*/
int32_t Bot_GetItemID( const char * classname ) {
if ( classname == nullptr || classname[ 0 ] == '\0' ) {
return Item_Invalid;
}
if ( Q_strcasecmp( classname, "none" ) == 0 ) {
return Item_Null;
}
for ( int i = 0; i < IT_TOTAL; ++i ) {
const gitem_t * item = itemlist + i;
if ( item->classname == nullptr || item->classname[ 0 ] == '\0' ) {
continue;
}
if ( Q_strcasecmp( item->classname, classname ) == 0 ) {
return item->id;
}
}
return Item_Invalid;
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#pragma once
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 );

View File

@@ -0,0 +1,9 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#pragma once
#include "bot_utils.h"
#include "bot_think.h"
#include "bot_debug.h"
#include "bot_exports.h"

View File

@@ -0,0 +1,23 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
#include "bot_think.h"
/*
================
Bot_BeginFrame
================
*/
void Bot_BeginFrame( edict_t * bot ) {
}
/*
================
Bot_EndFrame
================
*/
void Bot_EndFrame( edict_t * bot ) {
}

View File

@@ -0,0 +1,7 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#pragma once
void Bot_BeginFrame( edict_t * bot );
void Bot_EndFrame( edict_t * bot );

View File

@@ -0,0 +1,532 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
#include "../m_player.h"
#include "bot_utils.h"
constexpr int Team_Coop_Monster = 0;
/*
================
Player_UpdateState
================
*/
void Player_UpdateState( edict_t * player ) {
const client_persistant_t & persistant = player->client->pers;
player->sv.ent_flags = SVFL_NONE;
if ( player->groundentity != nullptr || ( player->flags & FL_PARTIALGROUND ) != 0 ) {
player->sv.ent_flags |= SVFL_ONGROUND;
} else {
if ( player->client->ps.pmove.pm_flags & PMF_JUMP_HELD ) {
player->sv.ent_flags |= SVFL_IS_JUMPING;
}
}
if ( player->client->ps.pmove.pm_flags & PMF_ON_LADDER ) {
player->sv.ent_flags |= SVFL_ON_LADDER;
}
if ( ( player->client->ps.pmove.pm_flags & PMF_DUCKED ) != 0 ) {
player->sv.ent_flags |= SVFL_IS_CROUCHING;
}
if ( player->client->quad_time > level.time ) {
player->sv.ent_flags |= SVFL_HAS_DMG_BOOST;
} else if ( player->client->quadfire_time > level.time ) {
player->sv.ent_flags |= SVFL_HAS_DMG_BOOST;
} else if ( player->client->double_time > level.time ) {
player->sv.ent_flags |= SVFL_HAS_DMG_BOOST;
}
if ( player->client->invincible_time > level.time ) {
player->sv.ent_flags |= SVFL_HAS_PROTECTION;
}
if ( player->client->invisible_time > level.time ) {
player->sv.ent_flags |= SVFL_HAS_INVISIBILITY;
}
if ( ( player->client->ps.pmove.pm_flags & PMF_TIME_TELEPORT ) != 0 ) {
player->sv.ent_flags |= SVFL_HAS_TELEPORTED;
}
if ( player->takedamage ) {
player->sv.ent_flags |= SVFL_TAKES_DAMAGE;
}
if ( player->solid == SOLID_NOT ) {
player->sv.ent_flags |= SVFL_IS_HIDDEN;
}
if ( ( player->flags & FL_INWATER ) != 0 ) {
if ( player->waterlevel >= WATER_WAIST ) {
player->sv.ent_flags |= SVFL_IN_WATER;
}
}
if ( ( player->flags & FL_NOTARGET ) != 0 ) {
player->sv.ent_flags |= SVFL_NO_TARGET;
}
if ( ( player->flags & FL_GODMODE ) != 0 ) {
player->sv.ent_flags |= SVFL_GOD_MODE;
}
if ( player->movetype == MOVETYPE_NOCLIP ) {
player->sv.ent_flags |= SVFL_IS_NOCLIP;
}
if ( player->client->anim_end == FRAME_flip12 ) {
player->sv.ent_flags |= SVFL_IS_FLIPPING_OFF;
}
if ( player->client->anim_end == FRAME_salute11 ) {
player->sv.ent_flags |= SVFL_IS_SALUTING;
}
if ( player->client->anim_end == FRAME_taunt17 ) {
player->sv.ent_flags |= SVFL_IS_TAUNTING;
}
if ( player->client->anim_end == FRAME_wave11 ) {
player->sv.ent_flags |= SVFL_IS_WAVING;
}
if ( player->client->anim_end == FRAME_point12 ) {
player->sv.ent_flags |= SVFL_IS_POINTING;
}
if ( ( player->client->ps.pmove.pm_flags & PMF_DUCKED ) == 0 && player->client->anim_priority <= ANIM_WAVE ) {
player->sv.ent_flags |= SVFL_CAN_GESTURE;
}
if ( player->lastMOD.id == MOD_TELEFRAG || player->lastMOD.id == MOD_TELEFRAG_SPAWN ) {
player->sv.ent_flags |= SVFL_WAS_TELEFRAGGED;
}
if ( player->client->resp.spectator ) {
player->sv.ent_flags |= SVFL_IS_SPECTATOR;
}
player_skinnum_t pl_skinnum;
pl_skinnum.skinnum = player->s.skinnum;
player->sv.team = pl_skinnum.team_index;
player->sv.buttons = player->client->buttons;
const item_id_t armorType = ArmorIndex( player );
player->sv.armor_type = armorType;
player->sv.armor_value = persistant.inventory[ armorType ];
player->sv.health = ( player->deadflag != true ) ? player->health : -1;
player->sv.weapon = ( persistant.weapon != nullptr ) ? persistant.weapon->id : IT_NULL;
player->sv.last_attackertime = static_cast<int32_t>( player->client->last_attacker_time.milliseconds() );
player->sv.respawntime = static_cast<int32_t>( player->client->respawn_time.milliseconds() );
player->sv.waterlevel = player->waterlevel;
player->sv.viewheight = player->viewheight;
player->sv.viewangles = player->client->v_angle;
player->sv.viewforward = player->client->v_forward;
player->sv.velocity = player->velocity;
player->sv.ground_entity = player->groundentity;
player->sv.enemy = player->enemy;
static_assert( sizeof( persistant.inventory ) <= sizeof( player->sv.inventory ) );
memcpy( &player->sv.inventory, &persistant.inventory, sizeof( persistant.inventory ) );
if ( !player->sv.init ) {
player->sv.init = true;
player->sv.classname = player->classname;
player->sv.targetname = player->targetname;
player->sv.lobby_usernum = P_GetLobbyUserNum( player );
player->sv.starting_health = player->health;
player->sv.max_health = player->max_health;
// NOTE: entries are assumed to be ranked with the first armor assumed
// NOTE: to be the "best", and last the "worst". You don't need to add
// NOTE: entries for things like armor shards, only actual armors.
// NOTE: Check "Max_Armor_Types" to raise/lower the armor count.
armorInfo_t * armorInfo = player->sv.armor_info;
armorInfo[ 0 ].item_id = IT_ARMOR_BODY;
armorInfo[ 0 ].max_count = bodyarmor_info.max_count;
armorInfo[ 1 ].item_id = IT_ARMOR_COMBAT;
armorInfo[ 1 ].max_count = combatarmor_info.max_count;
armorInfo[ 2 ].item_id = IT_ARMOR_JACKET;
armorInfo[ 2 ].max_count = jacketarmor_info.max_count;
gi.Info_ValueForKey( player->client->pers.userinfo, "name", player->sv.netname, sizeof( player->sv.netname ) );
gi.Bot_RegisterEdict( player );
}
}
/*
================
Monster_UpdateState
================
*/
void Monster_UpdateState( edict_t * monster ) {
monster->sv.ent_flags = SVFL_NONE;
if ( monster->groundentity != nullptr ) {
monster->sv.ent_flags |= SVFL_ONGROUND;
}
if ( monster->takedamage ) {
monster->sv.ent_flags |= SVFL_TAKES_DAMAGE;
}
if ( monster->solid == SOLID_NOT || monster->movetype == MOVETYPE_NONE ) {
monster->sv.ent_flags |= SVFL_IS_HIDDEN;
}
if ( ( monster->flags & FL_INWATER ) != 0 ) {
monster->sv.ent_flags |= SVFL_IN_WATER;
}
if ( coop->integer ) {
monster->sv.team = Team_Coop_Monster;
} else {
monster->sv.team = Team_None; // TODO: CTF/TDM/etc...
}
monster->sv.health = ( monster->deadflag != true ) ? monster->health : -1;
monster->sv.waterlevel = monster->waterlevel;
monster->sv.enemy = monster->enemy;
monster->sv.ground_entity = monster->groundentity;
int32_t viewHeight = monster->viewheight;
if ( ( monster->monsterinfo.aiflags & AI_DUCKED ) != 0 ) {
viewHeight = int32_t( monster->maxs[ 2 ] - 4.0f );
}
monster->sv.viewheight = viewHeight;
monster->sv.viewangles = monster->s.angles;
AngleVectors( monster->s.angles, monster->sv.viewforward, nullptr, nullptr );
monster->sv.velocity = monster->velocity;
if ( !monster->sv.init ) {
monster->sv.init = true;
monster->sv.classname = monster->classname;
monster->sv.targetname = monster->targetname;
monster->sv.starting_health = monster->health;
monster->sv.max_health = monster->max_health;
gi.Bot_RegisterEdict( monster );
}
}
/*
================
Item_UpdateState
================
*/
void Item_UpdateState( edict_t * item ) {
item->sv.ent_flags = SVFL_IS_ITEM;
item->sv.respawntime = 0;
if ( item->team != nullptr ) {
item->sv.ent_flags |= SVFL_IN_TEAM;
} // some DM maps have items chained together in teams...
if ( item->solid == SOLID_NOT ) {
item->sv.ent_flags |= SVFL_IS_HIDDEN;
if ( item->nextthink.milliseconds() > 0 ) {
if ( ( item->svflags & SVF_RESPAWNING ) != 0 ) {
const gtime_t pendingRespawnTime = ( item->nextthink - level.time );
item->sv.respawntime = static_cast<int32_t>( pendingRespawnTime.milliseconds() );
} else {
// item will respawn at some unknown time in the future...
item->sv.respawntime = Item_UnknownRespawnTime;
}
}
}
// 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;
// TODO: figure out if the objective is dropped/carried/home...
}
// always need to update these for items, since random item spawning
// could change them at any time...
item->sv.classname = item->classname;
item->sv.item_id = item->item->id;
if ( !item->sv.init ) {
item->sv.init = true;
item->sv.targetname = item->targetname;
gi.Bot_RegisterEdict( item );
}
}
/*
================
Trap_UpdateState
================
*/
void Trap_UpdateState( edict_t * danger ) {
danger->sv.ent_flags = SVFL_TRAP_DANGER;
danger->sv.velocity = danger->velocity;
if ( danger->owner != nullptr && danger->owner->client != nullptr ) {
player_skinnum_t pl_skinnum;
pl_skinnum.skinnum = danger->owner->s.skinnum;
danger->sv.team = pl_skinnum.team_index;
}
if ( danger->groundentity != nullptr ) {
danger->sv.ent_flags |= SVFL_ONGROUND;
}
if ( ( danger->flags & FL_TRAP_LASER_FIELD ) == 0 ) {
danger->sv.ent_flags |= SVFL_ACTIVE; // non-lasers are always active
} else {
danger->sv.start_origin = danger->s.origin;
danger->sv.end_origin = danger->s.old_origin;
if ( ( danger->svflags & SVF_NOCLIENT ) == 0 ) {
if ( ( danger->s.renderfx & RF_BEAM ) ) {
danger->sv.ent_flags |= SVFL_ACTIVE; // lasers are active!!
}
}
}
if ( !danger->sv.init ) {
danger->sv.init = true;
danger->sv.classname = danger->classname;
gi.Bot_RegisterEdict( danger );
}
}
/*
================
Edict_UpdateState
================
*/
void Edict_UpdateState( edict_t * edict ) {
edict->sv.ent_flags = SVFL_NONE;
edict->sv.health = edict->health;
if ( edict->takedamage ) {
edict->sv.ent_flags |= SVFL_TAKES_DAMAGE;
}
// plats, movers, and doors use this to determine move state.
const bool isDoor = ( ( edict->svflags & SVF_DOOR ) != 0 );
const bool isReversedDoor = ( isDoor && edict->spawnflags.has( SPAWNFLAG_DOOR_REVERSE ) );
// doors have their top/bottom states reversed from plats
// ( unless "reverse" spawnflag is set! )
if ( isDoor && !isReversedDoor ) {
if ( edict->moveinfo.state == STATE_TOP ) {
edict->sv.ent_flags |= SVFL_MOVESTATE_BOTTOM;
} else if ( edict->moveinfo.state == STATE_BOTTOM ) {
edict->sv.ent_flags |= SVFL_MOVESTATE_TOP;
}
} else {
if ( edict->moveinfo.state == STATE_TOP ) {
edict->sv.ent_flags |= SVFL_MOVESTATE_TOP;
} else if ( edict->moveinfo.state == STATE_BOTTOM ) {
edict->sv.ent_flags |= SVFL_MOVESTATE_BOTTOM;
}
}
if ( edict->moveinfo.state == STATE_UP || edict->moveinfo.state == STATE_DOWN ) {
edict->sv.ent_flags |= SVFL_MOVESTATE_MOVING;
}
edict->sv.start_origin = edict->moveinfo.start_origin;
edict->sv.end_origin = edict->moveinfo.end_origin;
if ( edict->svflags & SVF_DOOR ) {
if ( edict->flags & FL_LOCKED ) {
edict->sv.ent_flags |= SVFL_IS_LOCKED_DOOR;
}
}
if ( !edict->sv.init ) {
edict->sv.init = true;
edict->sv.classname = edict->classname;
edict->sv.targetname = edict->targetname;
edict->sv.spawnflags = edict->spawnflags.value;
}
}
/*
================
Entity_UpdateState
================
*/
void Entity_UpdateState( edict_t * edict ) {
if ( edict->svflags & SVF_MONSTER ) {
Monster_UpdateState( edict );
} else if ( edict->flags & FL_TRAP || edict->flags & FL_TRAP_LASER_FIELD ) {
Trap_UpdateState( edict );
} else if ( edict->item != nullptr ) {
Item_UpdateState( edict );
} else if ( edict->client != nullptr ) {
Player_UpdateState( edict );
} else {
Edict_UpdateState( edict );
}
}
USE( info_nav_lock_use ) ( edict_t * self, edict_t * other, edict_t * activator ) -> void {
edict_t * n = nullptr;
while ( ( n = G_FindByString<&edict_t::targetname>( n, self->target ) ) ) {
if ( !( n->svflags & SVF_DOOR ) ) {
gi.Com_PrintFmt( "{} tried targeting {}, a non-SVF_DOOR\n", *self, *n );
continue;
}
n->flags ^= FL_LOCKED;
}
}
/*QUAKED info_nav_lock (1.0 1.0 0.0) (-16 -16 0) (16 16 32)
toggle locked state on linked entity
*/
void SP_info_nav_lock( edict_t * self ) {
if ( !self->targetname ) {
gi.Com_PrintFmt( "{} missing targetname\n", *self );
G_FreeEdict( self );
return;
}
if ( !self->target ) {
gi.Com_PrintFmt( "{} missing target\n", *self );
G_FreeEdict( self );
return;
}
self->svflags |= SVF_NOCLIENT;
self->use = info_nav_lock_use;
}
/*
================
FindLocalPlayer
================
*/
const edict_t * FindLocalPlayer() {
const edict_t * localPlayer = nullptr;
const edict_t * ent = &g_edicts[ 0 ];
for ( uint32_t i = 0; i < globals.num_edicts; i++, ent++ ) {
if ( !ent->inuse || !( ent->svflags & SVF_PLAYER ) ) {
continue;
}
if ( ent->health <= 0 ) {
continue;
}
localPlayer = ent;
break;
}
return localPlayer;
}
/*
================
FindFirstBot
================
*/
const edict_t * FindFirstBot() {
const edict_t * firstBot = nullptr;
const edict_t * ent = &g_edicts[ 0 ];
for ( uint32_t i = 0; i < globals.num_edicts; i++, ent++ ) {
if ( !ent->inuse || !( ent->svflags & SVF_PLAYER ) ) {
continue;
}
if ( ent->health <= 0 ) {
continue;
}
if ( !( ent->svflags & SVF_BOT ) ) {
continue;
}
firstBot = ent;
break;
}
return firstBot;
}
/*
================
FindFirstMonster
================
*/
const edict_t * FindFirstMonster() {
const edict_t * firstMonster = nullptr;
const edict_t * ent = &g_edicts[ 0 ];
for ( uint32_t i = 0; i < globals.num_edicts; i++, ent++ ) {
if ( !ent->inuse || !( ent->svflags & SVF_MONSTER ) ) {
continue;
}
if ( ent->health <= 0 ) {
continue;
}
firstMonster = ent;
break;
}
return firstMonster;
}
/*
================
FindFirstMonster
"Actors" are either players or monsters - i.e. something alive and thinking.
================
*/
const edict_t * FindActorUnderCrosshair( const edict_t * player ) {
if ( player == nullptr || !player->inuse ) {
return nullptr;
}
vec3_t forward, right, up;
AngleVectors( player->client->v_angle, forward, right, up );
const vec3_t eye_position = ( player->s.origin + vec3_t{ 0.0f, 0.0f, (float)player->viewheight } );
const vec3_t end = ( eye_position + ( forward * 8192.0f ) );
const contents_t mask = ( MASK_PROJECTILE & ~CONTENTS_DEADMONSTER );
trace_t tr = gi.traceline( eye_position, end, player, mask );
const edict_t * traceEnt = tr.ent;
if ( traceEnt == nullptr || !tr.ent->inuse ) {
return nullptr;
}
if ( !( traceEnt->svflags & SVF_PLAYER ) && !( traceEnt->svflags & SVF_MONSTER ) ) {
return nullptr;
}
if ( traceEnt->health <= 0 ) {
return nullptr;
}
return traceEnt;
}

View File

@@ -0,0 +1,10 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#pragma once
void Entity_UpdateState( edict_t * edict );
const edict_t * FindLocalPlayer();
const edict_t * FindFirstBot();
const edict_t * FindFirstMonster();
const edict_t * FindActorUnderCrosshair( const edict_t * player );

14
rerelease/cg_local.h Normal file
View File

@@ -0,0 +1,14 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_local.h -- local definitions for game module
#pragma once
#include "bg_local.h"
extern cgame_import_t cgi;
extern cgame_export_t cglobals;
#define SERVER_TICK_RATE cgi.tick_rate // in hz
#define FRAME_TIME_S cgi.frame_time_s
#define FRAME_TIME_MS cgi.frame_time_ms

124
rerelease/cg_main.cpp Normal file
View File

@@ -0,0 +1,124 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "cg_local.h"
#include "m_flash.h"
cgame_import_t cgi;
cgame_export_t cglobals;
static void *CG_GetExtension(const char *name)
{
return nullptr;
}
void CG_InitScreen();
uint64_t cgame_init_time = 0;
static void InitCGame()
{
CG_InitScreen();
cgame_init_time = cgi.CL_ClientRealTime();
pm_config.n64_physics = !!atoi(cgi.get_configstring(CONFIG_N64_PHYSICS));
pm_config.airaccel = atoi(cgi.get_configstring(CS_AIRACCEL));
}
static void ShutdownCGame()
{
}
void CG_DrawHUD (int32_t isplit, const cg_server_data_t *data, vrect_t hud_vrect, vrect_t hud_safe, int32_t scale, int32_t playernum, const player_state_t *ps);
void CG_TouchPics();
layout_flags_t CG_LayoutFlags(const player_state_t *ps);
int32_t CG_GetActiveWeaponWheelWeapon(const player_state_t *ps)
{
return ps->stats[STAT_ACTIVE_WHEEL_WEAPON];
}
uint32_t CG_GetOwnedWeaponWheelWeapons(const player_state_t *ps)
{
return ((uint32_t) (uint16_t) ps->stats[STAT_WEAPONS_OWNED_1]) | ((uint32_t) (uint16_t) (ps->stats[STAT_WEAPONS_OWNED_2]) << 16);
}
int16_t CG_GetWeaponWheelAmmoCount(const player_state_t *ps, int32_t ammo_id)
{
uint16_t ammo = G_GetAmmoStat((uint16_t *) &ps->stats[STAT_AMMO_INFO_START], ammo_id);
if (ammo == AMMO_VALUE_INFINITE)
return -1;
return ammo;
}
int16_t CG_GetPowerupWheelCount(const player_state_t *ps, int32_t powerup_id)
{
return G_GetPowerupStat((uint16_t *) &ps->stats[STAT_POWERUP_INFO_START], powerup_id);
}
int16_t CG_GetHitMarkerDamage(const player_state_t *ps)
{
return ps->stats[STAT_HIT_MARKER];
}
static void CG_ParseConfigString(int32_t i, const char *s)
{
if (i == CONFIG_N64_PHYSICS)
pm_config.n64_physics = !!atoi(s);
else if (i == CS_AIRACCEL)
pm_config.airaccel = atoi(s);
}
void CG_ParseCenterPrint (const char *str, int isplit, bool instant);
void CG_ClearNotify(int32_t isplit);
void CG_ClearCenterprint(int32_t isplit);
void CG_NotifyMessage(int32_t isplit, const char *msg, bool is_chat);
void CG_GetMonsterFlashOffset(monster_muzzleflash_id_t id, gvec3_ref_t offset)
{
if (id >= q_countof(monster_flash_offset))
cgi.Com_Error("Bad muzzle flash offset");
offset = monster_flash_offset[id];
}
/*
=================
GetCGameAPI
Returns a pointer to the structure with all entry points
and global variables
=================
*/
Q2GAME_API cgame_export_t *GetCGameAPI(cgame_import_t *import)
{
cgi = *import;
cglobals.apiversion = CGAME_API_VERSION;
cglobals.Init = InitCGame;
cglobals.Shutdown = ShutdownCGame;
cglobals.Pmove = Pmove;
cglobals.DrawHUD = CG_DrawHUD;
cglobals.LayoutFlags = CG_LayoutFlags;
cglobals.TouchPics = CG_TouchPics;
cglobals.GetActiveWeaponWheelWeapon = CG_GetActiveWeaponWheelWeapon;
cglobals.GetOwnedWeaponWheelWeapons = CG_GetOwnedWeaponWheelWeapons;
cglobals.GetWeaponWheelAmmoCount = CG_GetWeaponWheelAmmoCount;
cglobals.GetPowerupWheelCount = CG_GetPowerupWheelCount;
cglobals.GetHitMarkerDamage = CG_GetHitMarkerDamage;
cglobals.ParseConfigString = CG_ParseConfigString;
cglobals.ParseCenterPrint = CG_ParseCenterPrint;
cglobals.ClearNotify = CG_ClearNotify;
cglobals.ClearCenterprint = CG_ClearCenterprint;
cglobals.NotifyMessage = CG_NotifyMessage;
cglobals.GetMonsterFlashOffset = CG_GetMonsterFlashOffset;
cglobals.GetExtension = CG_GetExtension;
return &cglobals;
}

1781
rerelease/cg_screen.cpp Normal file

File diff suppressed because it is too large Load Diff

3832
rerelease/ctf/g_ctf.cpp Normal file

File diff suppressed because it is too large Load Diff

162
rerelease/ctf/g_ctf.h Normal file
View File

@@ -0,0 +1,162 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#define CTF_VERSION 1.52
#define CTF_VSTRING2(x) #x
#define CTF_VSTRING(x) CTF_VSTRING2(x)
#define CTF_STRING_VERSION CTF_VSTRING(CTF_VERSION)
enum ctfteam_t
{
CTF_NOTEAM,
CTF_TEAM1,
CTF_TEAM2
};
enum ctfgrapplestate_t
{
CTF_GRAPPLE_STATE_FLY,
CTF_GRAPPLE_STATE_PULL,
CTF_GRAPPLE_STATE_HANG
};
struct ghost_t
{
char netname[MAX_NETNAME];
int number;
// stats
int deaths;
int kills;
int caps;
int basedef;
int carrierdef;
int code; // ghost code
ctfteam_t team; // team
int score; // frags at time of disconnect
edict_t *ent;
};
extern cvar_t *ctf;
extern cvar_t *g_teamplay_force_join;
extern cvar_t *teamplay;
constexpr const char *CTF_TEAM1_SKIN = "ctf_r";
constexpr const char *CTF_TEAM2_SKIN = "ctf_b";
constexpr int32_t CTF_CAPTURE_BONUS = 15; // what you get for capture
constexpr int32_t CTF_TEAM_BONUS = 10; // what your team gets for capture
constexpr int32_t CTF_RECOVERY_BONUS = 1; // what you get for recovery
constexpr int32_t CTF_FLAG_BONUS = 0; // what you get for picking up enemy flag
constexpr int32_t CTF_FRAG_CARRIER_BONUS = 2; // what you get for fragging enemy flag carrier
constexpr gtime_t CTF_FLAG_RETURN_TIME = 40_sec; // seconds until auto return
constexpr int32_t CTF_CARRIER_DANGER_PROTECT_BONUS = 2; // bonus for fraggin someone who has recently hurt your flag carrier
constexpr int32_t CTF_CARRIER_PROTECT_BONUS = 1; // bonus for fraggin someone while either you or your target are near your flag carrier
constexpr int32_t CTF_FLAG_DEFENSE_BONUS = 1; // bonus for fraggin someone while either you or your target are near your flag
constexpr int32_t CTF_RETURN_FLAG_ASSIST_BONUS = 1; // awarded for returning a flag that causes a capture to happen almost immediately
constexpr int32_t CTF_FRAG_CARRIER_ASSIST_BONUS = 2; // award for fragging a flag carrier if a capture happens almost immediately
constexpr float CTF_TARGET_PROTECT_RADIUS = 400; // the radius around an object being defended where a target will be worth extra frags
constexpr float CTF_ATTACKER_PROTECT_RADIUS = 400; // the radius around an object being defended where an attacker will get extra frags when making kills
constexpr gtime_t CTF_CARRIER_DANGER_PROTECT_TIMEOUT = 8_sec;
constexpr gtime_t CTF_FRAG_CARRIER_ASSIST_TIMEOUT = 10_sec;
constexpr gtime_t CTF_RETURN_FLAG_ASSIST_TIMEOUT = 10_sec;
constexpr gtime_t CTF_AUTO_FLAG_RETURN_TIMEOUT = 30_sec; // number of seconds before dropped flag auto-returns
constexpr gtime_t CTF_TECH_TIMEOUT = 60_sec; // seconds before techs spawn again
constexpr int32_t CTF_DEFAULT_GRAPPLE_SPEED = 650; // speed of grapple in flight
constexpr float CTF_DEFAULT_GRAPPLE_PULL_SPEED = 650; // speed player is pulled at
void CTFInit();
void CTFSpawn();
void CTFPrecache();
bool G_TeamplayEnabled();
void G_AdjustTeamScore(ctfteam_t team, int32_t offset);
void SP_info_player_team1(edict_t *self);
void SP_info_player_team2(edict_t *self);
const char *CTFTeamName(int team);
const char *CTFOtherTeamName(int team);
void CTFAssignSkin(edict_t *ent, const char *s);
void CTFAssignTeam(gclient_t *who);
edict_t *SelectCTFSpawnPoint(edict_t *ent, bool force_spawn);
bool CTFPickup_Flag(edict_t *ent, edict_t *other);
void CTFDrop_Flag(edict_t *ent, gitem_t *item);
void CTFEffects(edict_t *player);
void CTFCalcScores();
void CTFCalcRankings(std::array<uint32_t, MAX_CLIENTS> &player_ranks); // [Paril-KEX]
void CheckEndTDMLevel(); // [Paril-KEX]
void SetCTFStats(edict_t *ent);
void CTFDeadDropFlag(edict_t *self);
void CTFScoreboardMessage(edict_t *ent, edict_t *killer);
void CTFTeam_f(edict_t *ent);
void CTFID_f(edict_t *ent);
#ifndef KEX_Q2_GAME
void CTFSay_Team(edict_t *who, const char *msg);
#endif
void CTFFlagSetup(edict_t *ent);
void CTFResetFlag(int ctf_team);
void CTFFragBonuses(edict_t *targ, edict_t *inflictor, edict_t *attacker);
void CTFCheckHurtCarrier(edict_t *targ, edict_t *attacker);
void CTFDirtyTeamMenu();
// GRAPPLE
void CTFWeapon_Grapple(edict_t *ent);
void CTFPlayerResetGrapple(edict_t *ent);
void CTFGrapplePull(edict_t *self);
void CTFResetGrapple(edict_t *self);
// TECH
gitem_t *CTFWhat_Tech(edict_t *ent);
bool CTFPickup_Tech(edict_t *ent, edict_t *other);
void CTFDrop_Tech(edict_t *ent, gitem_t *item);
void CTFDeadDropTech(edict_t *ent);
void CTFSetupTechSpawn();
int CTFApplyResistance(edict_t *ent, int dmg);
int CTFApplyStrength(edict_t *ent, int dmg);
bool CTFApplyStrengthSound(edict_t *ent);
bool CTFApplyHaste(edict_t *ent);
void CTFApplyHasteSound(edict_t *ent);
void CTFApplyRegeneration(edict_t *ent);
bool CTFHasRegeneration(edict_t *ent);
void CTFRespawnTech(edict_t *ent);
void CTFResetTech();
void CTFOpenJoinMenu(edict_t *ent);
bool CTFStartClient(edict_t *ent);
void CTFVoteYes(edict_t *ent);
void CTFVoteNo(edict_t *ent);
void CTFReady(edict_t *ent);
void CTFNotReady(edict_t *ent);
bool CTFNextMap();
bool CTFMatchSetup();
bool CTFMatchOn();
void CTFGhost(edict_t *ent);
void CTFAdmin(edict_t *ent);
bool CTFInMatch();
void CTFStats(edict_t *ent);
void CTFWarp(edict_t *ent);
void CTFBoot(edict_t *ent);
void CTFPlayerList(edict_t *ent);
bool CTFCheckRules();
void SP_misc_ctf_banner(edict_t *ent);
void SP_misc_ctf_small_banner(edict_t *ent);
void UpdateChaseCam(edict_t *ent);
void ChaseNext(edict_t *ent);
void ChasePrev(edict_t *ent);
void CTFObserver(edict_t *ent);
void SP_trigger_teleport(edict_t *ent);
void SP_info_teleport_destination(edict_t *ent);
void CTFSetPowerUpEffect(edict_t *ent, effects_t def);

View File

@@ -0,0 +1,282 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "../g_local.h"
// Note that the pmenu entries are duplicated
// this is so that a static set of pmenu entries can be used
// for multiple clients and changed without interference
// note that arg will be freed when the menu is closed, it must be allocated memory
pmenuhnd_t *PMenu_Open(edict_t *ent, const pmenu_t *entries, int cur, int num, void *arg, UpdateFunc_t UpdateFunc)
{
pmenuhnd_t *hnd;
const pmenu_t *p;
int i;
if (!ent->client)
return nullptr;
if (ent->client->menu)
{
gi.Com_Print("warning, ent already has a menu\n");
PMenu_Close(ent);
}
hnd = (pmenuhnd_t *) gi.TagMalloc(sizeof(*hnd), TAG_LEVEL);
hnd->UpdateFunc = UpdateFunc;
hnd->arg = arg;
hnd->entries = (pmenu_t *) gi.TagMalloc(sizeof(pmenu_t) * num, TAG_LEVEL);
memcpy(hnd->entries, entries, sizeof(pmenu_t) * num);
// duplicate the strings since they may be from static memory
for (i = 0; i < num; i++)
Q_strlcpy(hnd->entries[i].text, entries[i].text, sizeof(entries[i].text));
hnd->num = num;
if (cur < 0 || !entries[cur].SelectFunc)
{
for (i = 0, p = entries; i < num; i++, p++)
if (p->SelectFunc)
break;
}
else
i = cur;
if (i >= num)
hnd->cur = -1;
else
hnd->cur = i;
ent->client->showscores = true;
ent->client->inmenu = true;
ent->client->menu = hnd;
if (UpdateFunc)
UpdateFunc(ent);
PMenu_Do_Update(ent);
gi.unicast(ent, true);
return hnd;
}
void PMenu_Close(edict_t *ent)
{
pmenuhnd_t *hnd;
if (!ent->client->menu)
return;
hnd = ent->client->menu;
gi.TagFree(hnd->entries);
if (hnd->arg)
gi.TagFree(hnd->arg);
gi.TagFree(hnd);
ent->client->menu = nullptr;
ent->client->showscores = false;
}
// only use on pmenu's that have been called with PMenu_Open
void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc)
{
Q_strlcpy(entry->text, text, sizeof(entry->text));
entry->align = align;
entry->SelectFunc = SelectFunc;
}
#include "../g_statusbar.h"
void PMenu_Do_Update(edict_t *ent)
{
int i;
pmenu_t *p;
int x;
pmenuhnd_t *hnd;
const char *t;
bool alt = false;
if (!ent->client->menu)
{
gi.Com_Print("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
if (hnd->UpdateFunc)
hnd->UpdateFunc(ent);
statusbar_t sb;
sb.xv(32).yv(8).picn("inventory");
for (i = 0, p = hnd->entries; i < hnd->num; i++, p++)
{
if (!*(p->text))
continue; // blank line
t = p->text;
if (*t == '*')
{
alt = true;
t++;
}
sb.yv(32 + i * 8);
const char *loc_func = "loc_string";
if (p->align == PMENU_ALIGN_CENTER)
{
x = 0;
loc_func = "loc_cstring";
}
else if (p->align == PMENU_ALIGN_RIGHT)
{
x = 260;
loc_func = "loc_rstring";
}
else
x = 64;
sb.xv(x);
sb.sb << loc_func;
if (hnd->cur == i || alt)
sb.sb << '2';
sb.sb << " 1 \"" << t << "\" \"" << p->text_arg1 << "\" ";
if (hnd->cur == i)
{
sb.xv(56);
sb.string2("\">\"");
}
alt = false;
}
gi.WriteByte(svc_layout);
gi.WriteString(sb.sb.str().c_str());
}
void PMenu_Update(edict_t *ent)
{
if (!ent->client->menu)
{
gi.Com_Print("warning: ent has no menu\n");
return;
}
if (level.time - ent->client->menutime >= 1_sec)
{
// been a second or more since last update, update now
PMenu_Do_Update(ent);
gi.unicast(ent, true);
ent->client->menutime = level.time + 1_sec;
ent->client->menudirty = false;
}
ent->client->menutime = level.time;
ent->client->menudirty = true;
}
void PMenu_Next(edict_t *ent)
{
pmenuhnd_t *hnd;
int i;
pmenu_t *p;
if (!ent->client->menu)
{
gi.Com_Print("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
if (hnd->cur < 0)
return; // no selectable entries
i = hnd->cur;
p = hnd->entries + hnd->cur;
do
{
i++;
p++;
if (i == hnd->num)
{
i = 0;
p = hnd->entries;
}
if (p->SelectFunc)
break;
} while (i != hnd->cur);
hnd->cur = i;
PMenu_Update(ent);
}
void PMenu_Prev(edict_t *ent)
{
pmenuhnd_t *hnd;
int i;
pmenu_t *p;
if (!ent->client->menu)
{
gi.Com_Print("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
if (hnd->cur < 0)
return; // no selectable entries
i = hnd->cur;
p = hnd->entries + hnd->cur;
do
{
if (i == 0)
{
i = hnd->num - 1;
p = hnd->entries + i;
}
else
{
i--;
p--;
}
if (p->SelectFunc)
break;
} while (i != hnd->cur);
hnd->cur = i;
PMenu_Update(ent);
}
void PMenu_Select(edict_t *ent)
{
pmenuhnd_t *hnd;
pmenu_t *p;
if (!ent->client->menu)
{
gi.Com_Print("warning: ent has no menu\n");
return;
}
hnd = ent->client->menu;
if (hnd->cur < 0)
return; // no selectable entries
p = hnd->entries + hnd->cur;
if (p->SelectFunc)
p->SelectFunc(ent, hnd);
}

View File

@@ -0,0 +1,41 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
enum
{
PMENU_ALIGN_LEFT,
PMENU_ALIGN_CENTER,
PMENU_ALIGN_RIGHT
};
struct pmenu_t;
using UpdateFunc_t = void (*)(edict_t *ent);
struct pmenuhnd_t
{
pmenu_t *entries;
int cur;
int num;
void *arg;
UpdateFunc_t UpdateFunc;
};
using SelectFunc_t = void (*)(edict_t *ent, pmenuhnd_t *hnd);
struct pmenu_t
{
char text[64];
int align;
SelectFunc_t SelectFunc;
char text_arg1[64];
};
pmenuhnd_t *PMenu_Open(edict_t *ent, const pmenu_t *entries, int cur, int num, void *arg, UpdateFunc_t UpdateFunc);
void PMenu_Close(edict_t *ent);
void PMenu_UpdateEntry(pmenu_t *entry, const char *text, int align, SelectFunc_t SelectFunc);
void PMenu_Do_Update(edict_t *ent);
void PMenu_Update(edict_t *ent);
void PMenu_Next(edict_t *ent);
void PMenu_Prev(edict_t *ent);
void PMenu_Select(edict_t *ent);

1769
rerelease/g_ai.cpp Normal file

File diff suppressed because it is too large Load Diff

171
rerelease/g_chase.cpp Normal file
View File

@@ -0,0 +1,171 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "g_local.h"
void UpdateChaseCam(edict_t *ent)
{
vec3_t o, ownerv, goal;
edict_t *targ;
vec3_t forward, right;
trace_t trace;
vec3_t oldgoal;
vec3_t angles;
// is our chase target gone?
if (!ent->client->chase_target->inuse || ent->client->chase_target->client->resp.spectator)
{
edict_t *old = ent->client->chase_target;
ChaseNext(ent);
if (ent->client->chase_target == old)
{
ent->client->chase_target = nullptr;
ent->client->ps.pmove.pm_flags &= ~(PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION);
return;
}
}
targ = ent->client->chase_target;
ownerv = targ->s.origin;
oldgoal = ent->s.origin;
ownerv[2] += targ->viewheight;
angles = targ->client->v_angle;
if (angles[PITCH] > 56)
angles[PITCH] = 56;
AngleVectors(angles, forward, right, nullptr);
forward.normalize();
o = ownerv + (forward * -30);
if (o[2] < targ->s.origin[2] + 20)
o[2] = targ->s.origin[2] + 20;
// jump animation lifts
if (!targ->groundentity)
o[2] += 16;
trace = gi.traceline(ownerv, o, targ, MASK_SOLID);
goal = trace.endpos;
goal += (forward * 2);
// pad for floors and ceilings
o = goal;
o[2] += 6;
trace = gi.traceline(goal, o, targ, MASK_SOLID);
if (trace.fraction < 1)
{
goal = trace.endpos;
goal[2] -= 6;
}
o = goal;
o[2] -= 6;
trace = gi.traceline(goal, o, targ, MASK_SOLID);
if (trace.fraction < 1)
{
goal = trace.endpos;
goal[2] += 6;
}
if (targ->deadflag)
ent->client->ps.pmove.pm_type = PM_DEAD;
else
ent->client->ps.pmove.pm_type = PM_FREEZE;
ent->s.origin = goal;
ent->client->ps.pmove.delta_angles = targ->client->v_angle - ent->client->resp.cmd_angles;
if (targ->deadflag)
{
ent->client->ps.viewangles[ROLL] = 40;
ent->client->ps.viewangles[PITCH] = -15;
ent->client->ps.viewangles[YAW] = targ->client->killer_yaw;
}
else
{
ent->client->ps.viewangles = targ->client->v_angle;
ent->client->v_angle = targ->client->v_angle;
AngleVectors(ent->client->v_angle, ent->client->v_forward, nullptr, nullptr);
}
ent->viewheight = 0;
ent->client->ps.pmove.pm_flags |= PMF_NO_POSITIONAL_PREDICTION | PMF_NO_ANGULAR_PREDICTION;
gi.linkentity(ent);
}
void ChaseNext(edict_t *ent)
{
ptrdiff_t i;
edict_t *e;
if (!ent->client->chase_target)
return;
i = ent->client->chase_target - g_edicts;
do
{
i++;
if (i > game.maxclients)
i = 1;
e = g_edicts + i;
if (!e->inuse)
continue;
if (!e->client->resp.spectator)
break;
} while (e != ent->client->chase_target);
ent->client->chase_target = e;
ent->client->update_chase = true;
}
void ChasePrev(edict_t *ent)
{
int i;
edict_t *e;
if (!ent->client->chase_target)
return;
i = ent->client->chase_target - g_edicts;
do
{
i--;
if (i < 1)
i = game.maxclients;
e = g_edicts + i;
if (!e->inuse)
continue;
if (!e->client->resp.spectator)
break;
} while (e != ent->client->chase_target);
ent->client->chase_target = e;
ent->client->update_chase = true;
}
void GetChaseTarget(edict_t *ent)
{
uint32_t i;
edict_t *other;
for (i = 1; i <= game.maxclients; i++)
{
other = g_edicts + i;
if (other->inuse && !other->client->resp.spectator)
{
ent->client->chase_target = other;
ent->client->update_chase = true;
UpdateChaseCam(ent);
return;
}
}
if (ent->client->chase_msg_time <= level.time)
{
gi.LocCenter_Print(ent, "$g_no_players_chase");
ent->client->chase_msg_time = level.time + 5_sec;
}
}

1741
rerelease/g_cmds.cpp Normal file

File diff suppressed because it is too large Load Diff

911
rerelease/g_combat.cpp Normal file
View File

@@ -0,0 +1,911 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_combat.c
#include "g_local.h"
/*
============
CanDamage
Returns true if the inflictor can directly damage the target. Used for
explosions and melee attacks.
============
*/
bool CanDamage(edict_t *targ, edict_t *inflictor)
{
vec3_t dest;
trace_t trace;
// bmodels need special checking because their origin is 0,0,0
vec3_t inflictor_center;
if (inflictor->linked)
inflictor_center = (inflictor->absmin + inflictor->absmax) * 0.5f;
else
inflictor_center = inflictor->s.origin;
if (targ->solid == SOLID_BSP)
{
dest = closest_point_to_box(inflictor_center, targ->absmin, targ->absmax);
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0f)
return true;
}
vec3_t targ_center;
if (targ->linked)
targ_center = (targ->absmin + targ->absmax) * 0.5f;
else
targ_center = targ->s.origin;
trace = gi.traceline(inflictor_center, targ_center, inflictor, MASK_SOLID);
if (trace.fraction == 1.0f)
return true;
dest = targ_center;
dest[0] += 15.0f;
dest[1] += 15.0f;
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0f)
return true;
dest = targ_center;
dest[0] += 15.0f;
dest[1] -= 15.0f;
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0f)
return true;
dest = targ_center;
dest[0] -= 15.0f;
dest[1] += 15.0f;
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0f)
return true;
dest = targ_center;
dest[0] -= 15.0f;
dest[1] -= 15.0f;
trace = gi.traceline(inflictor_center, dest, inflictor, MASK_SOLID);
if (trace.fraction == 1.0f)
return true;
return false;
}
/*
============
Killed
============
*/
void Killed(edict_t *targ, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, mod_t mod)
{
if (targ->health < -999)
targ->health = -999;
// [Paril-KEX]
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
{
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
{
cleanupHealTarget(targ->enemy);
}
// clean up self
targ->monsterinfo.aiflags &= ~AI_MEDIC;
}
targ->enemy = attacker;
targ->lastMOD = mod;
// [Paril-KEX] monsters call die in their damage handler
if (targ->svflags & SVF_MONSTER)
return;
targ->die(targ, inflictor, attacker, damage, point, mod);
if (targ->monsterinfo.setskin)
targ->monsterinfo.setskin(targ);
}
/*
================
SpawnDamage
================
*/
void SpawnDamage(int type, const vec3_t &origin, const vec3_t &normal, int damage)
{
if (damage > 255)
damage = 255;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(type);
// gi.WriteByte (damage);
gi.WritePosition(origin);
gi.WriteDir(normal);
gi.multicast(origin, MULTICAST_PVS, false);
}
/*
============
T_Damage
targ entity that is being damaged
inflictor entity that is causing the damage
attacker entity that caused the inflictor to damage targ
example: targ=monster, inflictor=rocket, attacker=player
dir direction of the attack
point point at which the damage is being inflicted
normal normal vector from that point
damage amount of damage being inflicted
knockback force to be applied against targ as a result of the damage
dflags these flags are used to control how T_Damage works
DAMAGE_RADIUS damage was indirect (from a nearby explosion)
DAMAGE_NO_ARMOR armor does not protect from this damage
DAMAGE_ENERGY damage is from an energy based weapon
DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles
DAMAGE_BULLET damage is from a bullet (used for ricochets)
DAMAGE_NO_PROTECTION kills godmode, armor, everything
============
*/
static int CheckPowerArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, damageflags_t dflags)
{
gclient_t *client;
int save;
item_id_t power_armor_type;
int damagePerCell;
int pa_te_type;
int *power;
int power_used;
if (ent->health <= 0)
return 0;
if (!damage)
return 0;
client = ent->client;
if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_POWER_ARMOR)) // PGM
return 0;
if (client)
{
power_armor_type = PowerArmorType(ent);
power = &client->pers.inventory[IT_AMMO_CELLS];
}
else if (ent->svflags & SVF_MONSTER)
{
power_armor_type = ent->monsterinfo.power_armor_type;
power = &ent->monsterinfo.power_armor_power;
}
else
return 0;
if (power_armor_type == IT_NULL)
return 0;
if (!*power)
return 0;
if (power_armor_type == IT_ITEM_POWER_SCREEN)
{
vec3_t vec;
float dot;
vec3_t forward;
// only works if damage point is in front
AngleVectors(ent->s.angles, forward, nullptr, nullptr);
vec = point - ent->s.origin;
vec.normalize();
dot = vec.dot(forward);
if (dot <= 0.3f)
return 0;
damagePerCell = 1;
pa_te_type = TE_SCREEN_SPARKS;
damage = damage / 3;
}
else
{
if (ctf->integer)
damagePerCell = 1; // power armor is weaker in CTF
else
damagePerCell = 2;
pa_te_type = TE_SCREEN_SPARKS;
damage = (2 * damage) / 3;
}
// Paril: fix small amounts of damage not
// being absorbed
damage = max(1, damage);
save = *power * damagePerCell;
if (!save)
return 0;
// [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots.
if (dflags & DAMAGE_ENERGY)
save = max(1, save / 2);
if (save > damage)
save = damage;
// [Paril-KEX] energy damage should do more to power armor, not ETF Rifle shots.
if (dflags & DAMAGE_ENERGY)
power_used = (save / damagePerCell) * 2;
else
power_used = save / damagePerCell;
power_used = max(1, power_used);
SpawnDamage(pa_te_type, point, normal, save);
ent->powerarmor_time = level.time + 200_ms;
// Paril: adjustment so that power armor
// always uses damagePerCell even if it does
// only a single point of damage
*power = max(0, *power - max(damagePerCell, power_used));
// check power armor turn-off states
if (ent->client)
G_CheckPowerArmor(ent);
else if (!*power)
{
gi.sound(ent, CHAN_AUTO, gi.soundindex("misc/mon_power2.wav"), 1.f, ATTN_NORM, 0.f);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_POWER_SPLASH);
gi.WriteEntity(ent);
gi.WriteByte((power_armor_type == IT_ITEM_POWER_SCREEN) ? 1 : 0);
gi.multicast(ent->s.origin, MULTICAST_PHS, false);
}
return save;
}
static int CheckArmor(edict_t *ent, const vec3_t &point, const vec3_t &normal, int damage, int te_sparks,
damageflags_t dflags)
{
gclient_t *client;
int save;
item_id_t index;
gitem_t *armor;
int *power;
if (!damage)
return 0;
// ROGUE
if (dflags & (DAMAGE_NO_ARMOR | DAMAGE_NO_REG_ARMOR))
// ROGUE
return 0;
client = ent->client;
index = ArmorIndex(ent);
if (!index)
return 0;
armor = GetItemByIndex(index);
if (dflags & DAMAGE_ENERGY)
save = (int) ceilf(armor->armor_info->energy_protection * damage);
else
save = (int) ceilf(armor->armor_info->normal_protection * damage);
if (client)
power = &client->pers.inventory[index];
else
power = &ent->monsterinfo.armor_power;
if (save >= *power)
save = *power;
if (!save)
return 0;
*power -= save;
if (!client && !ent->monsterinfo.armor_power)
ent->monsterinfo.armor_type = IT_NULL;
SpawnDamage(te_sparks, point, normal, save);
return save;
}
void M_ReactToDamage(edict_t *targ, edict_t *attacker, edict_t *inflictor)
{
// pmm
bool new_tesla;
if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER))
return;
//=======
// ROGUE
// logic for tesla - if you are hit by a tesla, and can't see who you should be mad at (attacker)
// attack the tesla
// also, target the tesla if it's a "new" tesla
if ((inflictor) && (!strcmp(inflictor->classname, "tesla_mine")))
{
new_tesla = MarkTeslaArea(targ, inflictor);
if ((new_tesla || brandom()) && (!targ->enemy || !targ->enemy->classname || strcmp(targ->enemy->classname, "tesla_mine")))
TargetTesla(targ, inflictor);
return;
}
// ROGUE
//=======
if (attacker == targ || attacker == targ->enemy)
return;
// if we are a good guy monster and our attacker is a player
// or another good guy, do not get mad at them
if (targ->monsterinfo.aiflags & AI_GOOD_GUY)
{
if (attacker->client || (attacker->monsterinfo.aiflags & AI_GOOD_GUY))
return;
}
// PGM
// if we're currently mad at something a target_anger made us mad at, ignore
// damage
if (targ->enemy && targ->monsterinfo.aiflags & AI_TARGET_ANGER)
{
float percentHealth;
// make sure whatever we were pissed at is still around.
if (targ->enemy->inuse)
{
percentHealth = (float) (targ->health) / (float) (targ->max_health);
if (targ->enemy->inuse && percentHealth > 0.33f)
return;
}
// remove the target anger flag
targ->monsterinfo.aiflags &= ~AI_TARGET_ANGER;
}
// PGM
// we recently switched from reacting to damage, don't do it
if (targ->monsterinfo.react_to_damage_time > level.time)
return;
// PMM
// if we're healing someone, do like above and try to stay with them
if ((targ->enemy) && (targ->monsterinfo.aiflags & AI_MEDIC))
{
float percentHealth;
percentHealth = (float) (targ->health) / (float) (targ->max_health);
// ignore it some of the time
if (targ->enemy->inuse && percentHealth > 0.25f)
return;
// remove the medic flag
cleanupHealTarget(targ->enemy);
targ->monsterinfo.aiflags &= ~AI_MEDIC;
}
// PMM
// we now know that we are not both good guys
targ->monsterinfo.react_to_damage_time = level.time + random_time(3_sec, 5_sec);
// if attacker is a client, get mad at them because he's good and we're not
if (attacker->client)
{
targ->monsterinfo.aiflags &= ~AI_SOUND_TARGET;
// this can only happen in coop (both new and old enemies are clients)
// only switch if can't see the current enemy
if (targ->enemy != attacker)
{
if (targ->enemy && targ->enemy->client)
{
if (visible(targ, targ->enemy))
{
targ->oldenemy = attacker;
return;
}
targ->oldenemy = targ->enemy;
}
// [Paril-KEX]
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
{
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
{
cleanupHealTarget(targ->enemy);
}
// clean up self
targ->monsterinfo.aiflags &= ~AI_MEDIC;
}
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget(targ);
}
return;
}
if (attacker->enemy == targ // if they *meant* to shoot us, then shoot back
// it's the same base (walk/swim/fly) type and both don't ignore shots,
// get mad at them
|| (((targ->flags & (FL_FLY | FL_SWIM)) == (attacker->flags & (FL_FLY | FL_SWIM))) &&
(strcmp(targ->classname, attacker->classname) != 0) && !(attacker->monsterinfo.aiflags & AI_IGNORE_SHOTS) &&
!(targ->monsterinfo.aiflags & AI_IGNORE_SHOTS)))
{
if (targ->enemy != attacker)
{
// [Paril-KEX]
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
{
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
{
cleanupHealTarget(targ->enemy);
}
// clean up self
targ->monsterinfo.aiflags &= ~AI_MEDIC;
}
if (targ->enemy && targ->enemy->client)
targ->oldenemy = targ->enemy;
targ->enemy = attacker;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget(targ);
}
}
// otherwise get mad at whoever they are mad at (help our buddy) unless it is us!
else if (attacker->enemy && attacker->enemy != targ && targ->enemy != attacker->enemy)
{
if (targ->enemy != attacker->enemy)
{
// [Paril-KEX]
if ((targ->svflags & SVF_MONSTER) && targ->monsterinfo.aiflags & AI_MEDIC)
{
if (targ->enemy && targ->enemy->inuse && (targ->enemy->svflags & SVF_MONSTER)) // god, I hope so
{
cleanupHealTarget(targ->enemy);
}
// clean up self
targ->monsterinfo.aiflags &= ~AI_MEDIC;
}
if (targ->enemy && targ->enemy->client)
targ->oldenemy = targ->enemy;
targ->enemy = attacker->enemy;
if (!(targ->monsterinfo.aiflags & AI_DUCKED))
FoundTarget(targ);
}
}
}
// check if the two given entities are on the same team
bool OnSameTeam(edict_t *ent1, edict_t *ent2)
{
// monsters are never on our team atm
if (!ent1->client || !ent2->client)
return false;
// we're never on our own team
else if (ent1 == ent2)
return false;
// [Paril-KEX] coop 'team' support
if (coop->integer)
return ent1->client && ent2->client;
// ZOID
else if (G_TeamplayEnabled() && ent1->client && ent2->client)
{
if (ent1->client->resp.ctf_team == ent2->client->resp.ctf_team)
return true;
}
// ZOID
return false;
}
// check if the two entities are on a team and that
// they wouldn't damage each other
bool CheckTeamDamage(edict_t *targ, edict_t *attacker)
{
// always damage teammates if friendly fire is enabled
if (g_friendly_fire->integer)
return false;
return OnSameTeam(targ, attacker);
}
void T_Damage(edict_t *targ, edict_t *inflictor, edict_t *attacker, const vec3_t &dir, const vec3_t &point,
const vec3_t &normal, int damage, int knockback, damageflags_t dflags, mod_t mod)
{
gclient_t *client;
int take;
int save;
int asave;
int psave;
int te_sparks;
bool sphere_notified; // PGM
if (!targ->takedamage)
return;
if (g_instagib->integer && attacker->client && targ->client)
{
// [Kex] always kill no matter what on instagib
damage = 9999;
}
sphere_notified = false; // PGM
// friendly fire avoidance
// if enabled you can't hurt teammates (but you can hurt yourself)
// knockback still occurs
if ((targ != attacker) && !(dflags & DAMAGE_NO_PROTECTION))
{
// mark as friendly fire
if (OnSameTeam(targ, attacker))
{
mod.friendly_fire = true;
// if we're not a nuke & friendly fire is disabled, just kill the damage
if (!g_friendly_fire->integer && (mod.id != MOD_NUKE))
damage = 0;
}
}
// ROGUE
// allow the deathmatch game to change values
if (deathmatch->integer && gamerules->integer)
{
if (DMGame.ChangeDamage)
damage = DMGame.ChangeDamage(targ, attacker, damage, mod);
if (DMGame.ChangeKnockback)
knockback = DMGame.ChangeKnockback(targ, attacker, knockback, mod);
if (!damage)
return;
}
// ROGUE
// easy mode takes half damage
if (skill->integer == 0 && deathmatch->integer == 0 && targ->client && damage)
{
damage /= 2;
if (!damage)
damage = 1;
}
if ( ( targ->svflags & SVF_MONSTER ) != 0 ) {
damage *= ai_damage_scale->integer;
} else {
damage *= g_damage_scale->integer;
} // mal: just for debugging...
client = targ->client;
// PMM - defender sphere takes half damage
if (damage && (client) && (client->owned_sphere) && (client->owned_sphere->spawnflags == SPHERE_DEFENDER))
{
damage /= 2;
if (!damage)
damage = 1;
}
if (dflags & DAMAGE_BULLET)
te_sparks = TE_BULLET_SPARKS;
else
te_sparks = TE_SPARKS;
// bonus damage for surprising a monster
if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) &&
(!targ->enemy || targ->monsterinfo.surprise_time == level.time) && (targ->health > 0))
{
damage *= 2;
targ->monsterinfo.surprise_time = level.time;
}
// ZOID
// strength tech
damage = CTFApplyStrength(attacker, damage);
// ZOID
if ((targ->flags & FL_NO_KNOCKBACK) ||
((targ->flags & FL_ALIVE_KNOCKBACK_ONLY) && (!targ->deadflag || targ->dead_time != level.time)))
knockback = 0;
// figure momentum add
if (!(dflags & DAMAGE_NO_KNOCKBACK))
{
if ((knockback) && (targ->movetype != MOVETYPE_NONE) && (targ->movetype != MOVETYPE_BOUNCE) &&
(targ->movetype != MOVETYPE_PUSH) && (targ->movetype != MOVETYPE_STOP))
{
vec3_t normalized = dir.normalized();
vec3_t kvel;
float mass;
if (targ->mass < 50)
mass = 50;
else
mass = (float) targ->mass;
if (targ->client && attacker == targ)
kvel = normalized * (1600.0f * knockback / mass); // the rocket jump hack...
else
kvel = normalized * (500.0f * knockback / mass);
targ->velocity += kvel;
}
}
take = damage;
save = 0;
// check for godmode
if ((targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION))
{
take = 0;
save = damage;
SpawnDamage(te_sparks, point, normal, save);
}
// check for invincibility
// ROGUE
if (!(dflags & DAMAGE_NO_PROTECTION) &&
(((client && client->invincible_time > level.time)) ||
((targ->svflags & SVF_MONSTER) && targ->monsterinfo.invincible_time > level.time)))
// ROGUE
{
if (targ->pain_debounce_time < level.time)
{
gi.sound(targ, CHAN_ITEM, gi.soundindex("items/protect4.wav"), 1, ATTN_NORM, 0);
targ->pain_debounce_time = level.time + 2_sec;
}
take = 0;
save = damage;
}
// ZOID
// team armor protect
if (G_TeamplayEnabled() && targ->client && attacker->client &&
targ->client->resp.ctf_team == attacker->client->resp.ctf_team && targ != attacker &&
g_teamplay_armor_protect->integer)
{
psave = asave = 0;
}
else
{
// ZOID
psave = CheckPowerArmor(targ, point, normal, take, dflags);
take -= psave;
asave = CheckArmor(targ, point, normal, take, te_sparks, dflags);
take -= asave;
}
// treat cheat/powerup savings the same as armor
asave += save;
// ZOID
// resistance tech
take = CTFApplyResistance(targ, take);
// ZOID
// ZOID
CTFCheckHurtCarrier(targ, attacker);
// ZOID
// ROGUE - this option will do damage both to the armor and person. originally for DPU rounds
if (dflags & DAMAGE_DESTROY_ARMOR)
{
if (!(targ->flags & FL_GODMODE) && !(dflags & DAMAGE_NO_PROTECTION) &&
!(client && client->invincible_time > level.time))
{
take = damage;
}
}
// ROGUE
// [Paril-KEX] player hit markers
if (targ != attacker && attacker->client && targ->health > 0 && !((targ->svflags & SVF_DEADMONSTER) || (targ->flags & FL_NO_DAMAGE_EFFECTS)) && mod.id != MOD_TARGET_LASER)
attacker->client->ps.stats[STAT_HIT_MARKER] += take + psave + asave;
// do the damage
if (take)
{
if (!(targ->flags & FL_NO_DAMAGE_EFFECTS))
{
// ROGUE
if (targ->flags & FL_MECHANICAL)
SpawnDamage(TE_ELECTRIC_SPARKS, point, normal, take);
// ROGUE
else if ((targ->svflags & SVF_MONSTER) || (client))
{
// XATRIX
if (strcmp(targ->classname, "monster_gekk") == 0)
SpawnDamage(TE_GREENBLOOD, point, normal, take);
// XATRIX
// ROGUE
else if (mod.id == MOD_CHAINFIST)
SpawnDamage(TE_MOREBLOOD, point, normal, 255);
// ROGUE
else
SpawnDamage(TE_BLOOD, point, normal, take);
}
else
SpawnDamage(te_sparks, point, normal, take);
}
if (!CTFMatchSetup())
targ->health = targ->health - take;
if ((targ->flags & FL_IMMORTAL) && targ->health <= 0)
targ->health = 1;
// PGM - spheres need to know who to shoot at
if (client && client->owned_sphere)
{
sphere_notified = true;
if (client->owned_sphere->pain)
client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod);
}
// PGM
if (targ->health <= 0)
{
if ((targ->svflags & SVF_MONSTER) || (client))
{
targ->flags |= FL_ALIVE_KNOCKBACK_ONLY;
targ->dead_time = level.time;
}
targ->monsterinfo.damage_blood += take;
targ->monsterinfo.damage_attacker = attacker;
targ->monsterinfo.damage_inflictor = inflictor;
targ->monsterinfo.damage_from = point;
targ->monsterinfo.damage_mod = mod;
targ->monsterinfo.damage_knockback += knockback;
Killed(targ, inflictor, attacker, take, point, mod);
return;
}
}
// PGM - spheres need to know who to shoot at
if (!sphere_notified)
{
if (client && client->owned_sphere)
{
sphere_notified = true;
if (client->owned_sphere->pain)
client->owned_sphere->pain(client->owned_sphere, attacker, 0, 0, mod);
}
}
// PGM
if ( targ->client ) {
targ->client->last_attacker_time = level.time;
}
if (targ->svflags & SVF_MONSTER)
{
if (damage > 0)
{
M_ReactToDamage(targ, attacker, inflictor);
targ->monsterinfo.damage_attacker = attacker;
targ->monsterinfo.damage_inflictor = inflictor;
targ->monsterinfo.damage_blood += take;
targ->monsterinfo.damage_from = point;
targ->monsterinfo.damage_mod = mod;
targ->monsterinfo.damage_knockback += knockback;
}
if (targ->monsterinfo.setskin)
targ->monsterinfo.setskin(targ);
}
else if (take && targ->pain)
targ->pain(targ, attacker, (float) knockback, take, mod);
// add to the damage inflicted on a player this frame
// the total will be turned into screen blends and view angle kicks
// at the end of the frame
if (client)
{
client->damage_parmor += psave;
client->damage_armor += asave;
client->damage_blood += take;
client->damage_knockback += knockback;
client->damage_from = point;
client->last_damage_time = level.time + COOP_DAMAGE_RESPAWN_TIME;
if (!(dflags & DAMAGE_NO_INDICATOR) && inflictor != world && attacker != world && (take || psave || asave))
{
damage_indicator_t *indicator = nullptr;
size_t i;
for (i = 0; i < client->num_damage_indicators; i++)
{
if ((point - client->damage_indicators[i].from).length() < 32.f)
{
indicator = &client->damage_indicators[i];
break;
}
}
if (!indicator && i != MAX_DAMAGE_INDICATORS)
{
indicator = &client->damage_indicators[i];
// for projectile direct hits, use the attacker; otherwise
// use the inflictor (rocket splash should point to the rocket)
indicator->from = (dflags & DAMAGE_RADIUS) ? inflictor->s.origin : attacker->s.origin;
indicator->health = indicator->armor = indicator->power = 0;
client->num_damage_indicators++;
}
if (indicator)
{
indicator->health += take;
indicator->power += psave;
indicator->armor += asave;
}
}
}
}
/*
============
T_RadiusDamage
============
*/
void T_RadiusDamage(edict_t *inflictor, edict_t *attacker, float damage, edict_t *ignore, float radius, damageflags_t dflags, mod_t mod)
{
float points;
edict_t *ent = nullptr;
vec3_t v;
vec3_t dir;
vec3_t inflictor_center;
if (inflictor->linked)
inflictor_center = (inflictor->absmax + inflictor->absmin) * 0.5f;
else
inflictor_center = inflictor->s.origin;
while ((ent = findradius(ent, inflictor_center, radius)) != nullptr)
{
if (ent == ignore)
continue;
if (!ent->takedamage)
continue;
if (ent->solid == SOLID_BSP && ent->linked)
v = closest_point_to_box(inflictor_center, ent->absmin, ent->absmax);
else
{
v = ent->mins + ent->maxs;
v = ent->s.origin + (v * 0.5f);
}
v = inflictor_center - v;
points = damage - 0.5f * v.length();
if (ent == attacker)
points = points * 0.5f;
if (points > 0)
{
if (CanDamage(ent, inflictor))
{
dir = (ent->s.origin - inflictor_center).normalized();
// [Paril-KEX] use closest point on bbox to explosion position
// to spawn damage effect
T_Damage(ent, inflictor, attacker, dir, closest_point_to_box(inflictor_center, ent->absmin, ent->absmax), dir, (int) points, (int) points,
dflags | DAMAGE_RADIUS, mod);
}
}
}
}

2897
rerelease/g_func.cpp Normal file

File diff suppressed because it is too large Load Diff

4019
rerelease/g_items.cpp Normal file

File diff suppressed because it is too large Load Diff

3500
rerelease/g_local.h Normal file

File diff suppressed because it is too large Load Diff

1051
rerelease/g_main.cpp Normal file

File diff suppressed because it is too large Load Diff

2485
rerelease/g_misc.cpp Normal file

File diff suppressed because it is too large Load Diff

1630
rerelease/g_monster.cpp Normal file

File diff suppressed because it is too large Load Diff

1044
rerelease/g_phys.cpp Normal file

File diff suppressed because it is too large Load Diff

2473
rerelease/g_save.cpp Normal file

File diff suppressed because it is too large Load Diff

1714
rerelease/g_spawn.cpp Normal file

File diff suppressed because it is too large Load Diff

63
rerelease/g_statusbar.h Normal file
View File

@@ -0,0 +1,63 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include <sstream>
// easy statusbar wrapper
struct statusbar_t
{
std::stringstream sb;
inline auto &yb(int32_t offset) { sb << "yb " << offset << ' '; return *this; }
inline auto &yt(int32_t offset) { sb << "yt " << offset << ' '; return *this; }
inline auto &yv(int32_t offset) { sb << "yv " << offset << ' '; return *this; }
inline auto &xl(int32_t offset) { sb << "xl " << offset << ' '; return *this; }
inline auto &xr(int32_t offset) { sb << "xr " << offset << ' '; return *this; }
inline auto &xv(int32_t offset) { sb << "xv " << offset << ' '; return *this; }
inline auto &ifstat(player_stat_t stat) { sb << "if " << stat << ' '; return *this; }
inline auto &endifstat() { sb << "endif "; return *this; }
inline auto &pic(player_stat_t stat) { sb << "pic " << stat << ' '; return *this; }
inline auto &picn(const char *icon) { sb << "picn " << icon << ' '; return *this; }
inline auto &anum() { sb << "anum "; return *this; }
inline auto &rnum() { sb << "rnum "; return *this; }
inline auto &hnum() { sb << "hnum "; return *this; }
inline auto &num(int32_t width, player_stat_t stat) { sb << "num " << width << ' ' << stat << ' '; return *this; }
inline auto &loc_stat_string(player_stat_t stat) { sb << "loc_stat_string " << stat << ' '; return *this; }
inline auto &loc_stat_rstring(player_stat_t stat) { sb << "loc_stat_rstring " << stat << ' '; return *this; }
inline auto &stat_string(player_stat_t stat) { sb << "stat_string " << stat << ' '; return *this; }
inline auto &loc_stat_cstring2(player_stat_t stat) { sb << "loc_stat_cstring2 " << stat << ' '; return *this; }
inline auto &string2(const char *str)
{
if (str[0] != '"' && (strchr(str, ' ') || strchr(str, '\n')))
sb << "string2 \"" << str << "\" ";
else
sb << "string2 " << str << ' ';
return *this;
}
inline auto &string(const char *str)
{
if (str[0] != '"' && (strchr(str, ' ') || strchr(str, '\n')))
sb << "string \"" << str << "\" ";
else
sb << "string " << str << ' ';
return *this;
}
inline auto &loc_rstring(const char *str)
{
if (str[0] != '"' && (strchr(str, ' ') || strchr(str, '\n')))
sb << "loc_rstring 0 \"" << str << "\" ";
else
sb << "loc_rstring 0 " << str << ' ';
return *this;
}
inline auto &lives_num(player_stat_t stat) { sb << "lives_num " << stat << ' '; return *this; }
inline auto &stat_pname(player_stat_t stat) { sb << "stat_pname " << stat << ' '; return *this; }
inline auto &health_bars() { sb << "health_bars "; return *this; }
inline auto &story() { sb << "story "; return *this; }
};

302
rerelease/g_svcmds.cpp Normal file
View File

@@ -0,0 +1,302 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "g_local.h"
void Svcmd_Test_f()
{
gi.LocClient_Print(nullptr, PRINT_HIGH, "Svcmd_Test_f()\n");
}
/*
==============================================================================
PACKET FILTERING
You can add or remove addresses from the filter list with:
addip <ip>
removeip <ip>
The ip address is specified in dot format, and any unspecified digits will match any value, so you can specify an entire
class C network with "addip 192.246.40".
Removeip will only remove an address specified exactly the same way. You cannot addip a subnet, then removeip a single
host.
listip
Prints the current list of filters.
writeip
Dumps "addip <ip>" commands to listip.cfg so it can be execed at a later date. The filter lists are not saved and
restored by default, because I beleive it would cause too much confusion.
filterban <0 or 1>
If 1 (the default), then ip addresses matching the current list will be prohibited from entering the game. This is the
default setting.
If 0, then only addresses matching the list will be allowed. This lets you easily set up a private game, or a game that
only allows players from your local network.
==============================================================================
*/
struct ipfilter_t
{
unsigned mask;
unsigned compare;
};
constexpr size_t MAX_IPFILTERS = 1024;
ipfilter_t ipfilters[MAX_IPFILTERS];
int numipfilters;
/*
=================
StringToFilter
=================
*/
static bool StringToFilter(const char *s, ipfilter_t *f)
{
char num[128];
int i, j;
byte b[4];
byte m[4];
for (i = 0; i < 4; i++)
{
b[i] = 0;
m[i] = 0;
}
for (i = 0; i < 4; i++)
{
if (*s < '0' || *s > '9')
{
gi.LocClient_Print(nullptr, PRINT_HIGH, "Bad filter address: {}\n", s);
return false;
}
j = 0;
while (*s >= '0' && *s <= '9')
{
num[j++] = *s++;
}
num[j] = 0;
b[i] = atoi(num);
if (b[i] != 0)
m[i] = 255;
if (!*s)
break;
s++;
}
f->mask = *(unsigned *) m;
f->compare = *(unsigned *) b;
return true;
}
/*
=================
SV_FilterPacket
=================
*/
bool SV_FilterPacket(const char *from)
{
int i;
unsigned in;
byte m[4];
const char *p;
i = 0;
p = from;
while (*p && i < 4)
{
m[i] = 0;
while (*p >= '0' && *p <= '9')
{
m[i] = m[i] * 10 + (*p - '0');
p++;
}
if (!*p || *p == ':')
break;
i++;
p++;
}
in = *(unsigned *) m;
for (i = 0; i < numipfilters; i++)
if ((in & ipfilters[i].mask) == ipfilters[i].compare)
return filterban->integer;
return !filterban->integer;
}
/*
=================
SV_AddIP_f
=================
*/
void SVCmd_AddIP_f()
{
int i;
if (gi.argc() < 3)
{
gi.LocClient_Print(nullptr, PRINT_HIGH, "Usage: addip <ip-mask>\n");
return;
}
for (i = 0; i < numipfilters; i++)
if (ipfilters[i].compare == 0xffffffff)
break; // free spot
if (i == numipfilters)
{
if (numipfilters == MAX_IPFILTERS)
{
gi.LocClient_Print(nullptr, PRINT_HIGH, "IP filter list is full\n");
return;
}
numipfilters++;
}
if (!StringToFilter(gi.argv(2), &ipfilters[i]))
ipfilters[i].compare = 0xffffffff;
}
/*
=================
SV_RemoveIP_f
=================
*/
void SVCmd_RemoveIP_f()
{
ipfilter_t f;
int i, j;
if (gi.argc() < 3)
{
gi.LocClient_Print(nullptr, PRINT_HIGH, "Usage: sv removeip <ip-mask>\n");
return;
}
if (!StringToFilter(gi.argv(2), &f))
return;
for (i = 0; i < numipfilters; i++)
if (ipfilters[i].mask == f.mask && ipfilters[i].compare == f.compare)
{
for (j = i + 1; j < numipfilters; j++)
ipfilters[j - 1] = ipfilters[j];
numipfilters--;
gi.LocClient_Print(nullptr, PRINT_HIGH, "Removed.\n");
return;
}
gi.LocClient_Print(nullptr, PRINT_HIGH, "Didn't find {}.\n", gi.argv(2));
}
/*
=================
SV_ListIP_f
=================
*/
void SVCmd_ListIP_f()
{
int i;
byte b[4];
gi.LocClient_Print(nullptr, PRINT_HIGH, "Filter list:\n");
for (i = 0; i < numipfilters; i++)
{
*(unsigned *) b = ipfilters[i].compare;
gi.LocClient_Print(nullptr, PRINT_HIGH, "{}.{}.{}.{}\n", b[0], b[1], b[2], b[3]);
}
}
// [Paril-KEX]
void SVCmd_NextMap_f()
{
gi.LocBroadcast_Print(PRINT_HIGH, "$g_map_ended_by_server");
EndDMLevel();
}
/*
=================
SV_WriteIP_f
=================
*/
void SVCmd_WriteIP_f(void)
{
// KEX_FIXME: Sys_FOpen isn't available atm, just commenting this out since i don't think we even need this functionality - sponge
/*
FILE* f;
byte b[4];
int i;
cvar_t* game;
game = gi.cvar("game", "", 0);
std::string name;
if (!*game->string)
name = std::string(GAMEVERSION) + "/listip.cfg";
else
name = std::string(game->string) + "/listip.cfg";
gi.LocClient_Print(nullptr, PRINT_HIGH, "Writing {}.\n", name.c_str());
f = Sys_FOpen(name.c_str(), "wb");
if (!f)
{
gi.LocClient_Print(nullptr, PRINT_HIGH, "Couldn't open {}\n", name.c_str());
return;
}
fprintf(f, "set filterban %d\n", filterban->integer);
for (i = 0; i < numipfilters; i++)
{
*(unsigned*)b = ipfilters[i].compare;
fprintf(f, "sv addip %i.%i.%i.%i\n", b[0], b[1], b[2], b[3]);
}
fclose(f);
*/
}
/*
=================
ServerCommand
ServerCommand will be called when an "sv" command is issued.
The game can issue gi.argc() / gi.argv() commands to get the rest
of the parameters
=================
*/
void ServerCommand()
{
const char *cmd;
cmd = gi.argv(1);
if (Q_strcasecmp(cmd, "test") == 0)
Svcmd_Test_f();
else if (Q_strcasecmp(cmd, "addip") == 0)
SVCmd_AddIP_f();
else if (Q_strcasecmp(cmd, "removeip") == 0)
SVCmd_RemoveIP_f();
else if (Q_strcasecmp(cmd, "listip") == 0)
SVCmd_ListIP_f();
else if (Q_strcasecmp(cmd, "writeip") == 0)
SVCmd_WriteIP_f();
else if (Q_strcasecmp(cmd, "nextmap") == 0)
SVCmd_NextMap_f();
else
gi.LocClient_Print(nullptr, PRINT_HIGH, "Unknown server command \"{}\"\n", cmd);
}

2059
rerelease/g_target.cpp Normal file

File diff suppressed because it is too large Load Diff

1333
rerelease/g_trigger.cpp Normal file

File diff suppressed because it is too large Load Diff

661
rerelease/g_turret.cpp Normal file
View File

@@ -0,0 +1,661 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_turret.c
#include "g_local.h"
constexpr spawnflags_t SPAWNFLAG_TURRET_BREACH_FIRE = 65536_spawnflag;
void AnglesNormalize(vec3_t &vec)
{
while (vec[0] > 360)
vec[0] -= 360;
while (vec[0] < 0)
vec[0] += 360;
while (vec[1] > 360)
vec[1] -= 360;
while (vec[1] < 0)
vec[1] += 360;
}
MOVEINFO_BLOCKED(turret_blocked) (edict_t *self, edict_t *other) -> void
{
edict_t *attacker;
if (other->takedamage)
{
if (self->teammaster->owner)
attacker = self->teammaster->owner;
else
attacker = self->teammaster;
T_Damage(other, self, attacker, vec3_origin, other->s.origin, vec3_origin, self->teammaster->dmg, 10, DAMAGE_NONE, MOD_CRUSH);
}
}
/*QUAKED turret_breach (0 0 0) ?
This portion of the turret can change both pitch and yaw.
The model should be made with a flat pitch.
It (and the associated base) need to be oriented towards 0.
Use "angle" to set the starting angle.
"speed" default 50
"dmg" default 10
"angle" point this forward
"target" point this at an info_notnull at the muzzle tip
"minpitch" min acceptable pitch angle : default -30
"maxpitch" max acceptable pitch angle : default 30
"minyaw" min acceptable yaw angle : default 0
"maxyaw" max acceptable yaw angle : default 360
*/
void turret_breach_fire(edict_t *self)
{
vec3_t f, r, u;
vec3_t start;
int damage;
int speed;
AngleVectors(self->s.angles, f, r, u);
start = self->s.origin + (f * self->move_origin[0]);
start += (r * self->move_origin[1]);
start += (u * self->move_origin[2]);
if (self->count)
damage = self->count;
else
damage = (int) frandom(100, 150);
speed = 550 + 50 * skill->integer;
edict_t *rocket = fire_rocket(self->teammaster->owner->activator ? self->teammaster->owner->activator : self->teammaster->owner, start, f, damage, speed, 150, damage);
rocket->s.scale = self->teammaster->dmg_radius;
gi.positioned_sound(start, self, CHAN_WEAPON, gi.soundindex("weapons/rocklf1a.wav"), 1, ATTN_NORM, 0);
}
THINK(turret_breach_think) (edict_t *self) -> void
{
edict_t *ent;
vec3_t current_angles;
vec3_t delta;
current_angles = self->s.angles;
AnglesNormalize(current_angles);
AnglesNormalize(self->move_angles);
if (self->move_angles[PITCH] > 180)
self->move_angles[PITCH] -= 360;
// clamp angles to mins & maxs
if (self->move_angles[PITCH] > self->pos1[PITCH])
self->move_angles[PITCH] = self->pos1[PITCH];
else if (self->move_angles[PITCH] < self->pos2[PITCH])
self->move_angles[PITCH] = self->pos2[PITCH];
if ((self->move_angles[YAW] < self->pos1[YAW]) || (self->move_angles[YAW] > self->pos2[YAW]))
{
float dmin, dmax;
dmin = fabsf(self->pos1[YAW] - self->move_angles[YAW]);
if (dmin < -180)
dmin += 360;
else if (dmin > 180)
dmin -= 360;
dmax = fabsf(self->pos2[YAW] - self->move_angles[YAW]);
if (dmax < -180)
dmax += 360;
else if (dmax > 180)
dmax -= 360;
if (fabsf(dmin) < fabsf(dmax))
self->move_angles[YAW] = self->pos1[YAW];
else
self->move_angles[YAW] = self->pos2[YAW];
}
delta = self->move_angles - current_angles;
if (delta[0] < -180)
delta[0] += 360;
else if (delta[0] > 180)
delta[0] -= 360;
if (delta[1] < -180)
delta[1] += 360;
else if (delta[1] > 180)
delta[1] -= 360;
delta[2] = 0;
if (delta[0] > self->speed * gi.frame_time_s)
delta[0] = self->speed * gi.frame_time_s;
if (delta[0] < -1 * self->speed * gi.frame_time_s)
delta[0] = -1 * self->speed * gi.frame_time_s;
if (delta[1] > self->speed * gi.frame_time_s)
delta[1] = self->speed * gi.frame_time_s;
if (delta[1] < -1 * self->speed * gi.frame_time_s)
delta[1] = -1 * self->speed * gi.frame_time_s;
for (ent = self->teammaster; ent; ent = ent->teamchain)
{
if (ent->noise_index)
{
if (delta[0] || delta[1])
{
ent->s.sound = ent->noise_index;
ent->s.loop_attenuation = ATTN_NORM;
}
else
ent->s.sound = 0;
}
}
self->avelocity = delta * (1.0f / gi.frame_time_s);
self->nextthink = level.time + FRAME_TIME_S;
for (ent = self->teammaster; ent; ent = ent->teamchain)
ent->avelocity[1] = self->avelocity[1];
// if we have a driver, adjust his velocities
if (self->owner)
{
float angle;
float target_z;
float diff;
vec3_t target;
vec3_t dir;
// angular is easy, just copy ours
self->owner->avelocity[0] = self->avelocity[0];
self->owner->avelocity[1] = self->avelocity[1];
// x & y
angle = self->s.angles[1] + self->owner->move_origin[1];
angle *= (float) (PI * 2 / 360);
target[0] = self->s.origin[0] + cosf(angle) * self->owner->move_origin[0];
target[1] = self->s.origin[1] + sinf(angle) * self->owner->move_origin[0];
target[2] = self->owner->s.origin[2];
dir = target - self->owner->s.origin;
self->owner->velocity[0] = dir[0] * 1.0f / gi.frame_time_s;
self->owner->velocity[1] = dir[1] * 1.0f / gi.frame_time_s;
// z
angle = self->s.angles[PITCH] * (float) (PI * 2 / 360);
target_z = self->s.origin[2] + self->owner->move_origin[0] * tan(angle) + self->owner->move_origin[2];
diff = target_z - self->owner->s.origin[2];
self->owner->velocity[2] = diff * 1.0f / gi.frame_time_s;
if (self->spawnflags.has(SPAWNFLAG_TURRET_BREACH_FIRE))
{
turret_breach_fire(self);
self->spawnflags &= ~SPAWNFLAG_TURRET_BREACH_FIRE;
}
}
}
THINK(turret_breach_finish_init) (edict_t *self) -> void
{
// get and save info for muzzle location
if (!self->target)
{
gi.Com_PrintFmt("{}: needs a target\n", *self);
}
else
{
self->target_ent = G_PickTarget(self->target);
if (self->target_ent)
{
self->move_origin = self->target_ent->s.origin - self->s.origin;
G_FreeEdict(self->target_ent);
}
else
gi.Com_PrintFmt("{}: could not find target entity \"{}\"\n", *self, self->target);
}
self->teammaster->dmg = self->dmg;
self->teammaster->dmg_radius = self->dmg_radius; // scale
self->think = turret_breach_think;
self->think(self);
}
void SP_turret_breach(edict_t *self)
{
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
if (st.noise)
self->noise_index = gi.soundindex(st.noise);
gi.setmodel(self, self->model);
if (!self->speed)
self->speed = 50;
if (!self->dmg)
self->dmg = 10;
if (!st.minpitch)
st.minpitch = -30;
if (!st.maxpitch)
st.maxpitch = 30;
if (!st.maxyaw)
st.maxyaw = 360;
self->pos1[PITCH] = -1 * st.minpitch;
self->pos1[YAW] = st.minyaw;
self->pos2[PITCH] = -1 * st.maxpitch;
self->pos2[YAW] = st.maxyaw;
// scale used for rocket scale
self->dmg_radius = self->s.scale;
self->s.scale = 0;
self->ideal_yaw = self->s.angles[YAW];
self->move_angles[YAW] = self->ideal_yaw;
self->moveinfo.blocked = turret_blocked;
self->think = turret_breach_finish_init;
self->nextthink = level.time + FRAME_TIME_S;
gi.linkentity(self);
}
/*QUAKED turret_base (0 0 0) ?
This portion of the turret changes yaw only.
MUST be teamed with a turret_breach.
*/
void SP_turret_base(edict_t *self)
{
self->solid = SOLID_BSP;
self->movetype = MOVETYPE_PUSH;
if (st.noise)
self->noise_index = gi.soundindex(st.noise);
gi.setmodel(self, self->model);
self->moveinfo.blocked = turret_blocked;
gi.linkentity(self);
}
/*QUAKED turret_driver (1 .5 0) (-16 -16 -24) (16 16 32)
Must NOT be on the team with the rest of the turret parts.
Instead it must target the turret_breach.
*/
void infantry_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod);
void infantry_stand(edict_t *self);
void infantry_pain(edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod);
void infantry_setskin(edict_t *self);
DIE(turret_driver_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (!self->deadflag)
{
edict_t *ent;
// level the gun
self->target_ent->move_angles[0] = 0;
// remove the driver from the end of them team chain
for (ent = self->target_ent->teammaster; ent->teamchain != self; ent = ent->teamchain)
;
ent->teamchain = nullptr;
self->teammaster = nullptr;
self->flags &= ~FL_TEAMSLAVE;
self->target_ent->owner = nullptr;
self->target_ent->teammaster->owner = nullptr;
self->target_ent->moveinfo.blocked = nullptr;
// clear pitch
self->s.angles[0] = 0;
self->movetype = MOVETYPE_STEP;
self->think = monster_think;
}
infantry_die(self, inflictor, attacker, damage, point, mod);
G_FixStuckObject(self, self->s.origin);
AngleVectors(self->s.angles, self->velocity, nullptr, nullptr);
self->velocity *= -50;
self->velocity.z += 110.f;
}
bool FindTarget(edict_t *self);
THINK(turret_driver_think) (edict_t *self) -> void
{
vec3_t target;
vec3_t dir;
self->nextthink = level.time + FRAME_TIME_S;
if (self->enemy && (!self->enemy->inuse || self->enemy->health <= 0))
self->enemy = nullptr;
if (!self->enemy)
{
if (!FindTarget(self))
return;
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
else
{
if (visible(self, self->enemy))
{
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
{
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
}
else
{
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
return;
}
}
// let the turret know where we want it to aim
target = self->enemy->s.origin;
target[2] += self->enemy->viewheight;
dir = target - self->target_ent->s.origin;
self->target_ent->move_angles = vectoangles(dir);
// decide if we should shoot
if (level.time < self->monsterinfo.attack_finished)
return;
gtime_t reaction_time = gtime_t::from_sec(3 - skill->integer);
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
return;
self->monsterinfo.attack_finished = level.time + reaction_time + 1_sec;
// FIXME how do we really want to pass this along?
self->target_ent->spawnflags |= SPAWNFLAG_TURRET_BREACH_FIRE;
}
THINK(turret_driver_link) (edict_t *self) -> void
{
vec3_t vec;
edict_t *ent;
self->think = turret_driver_think;
self->nextthink = level.time + FRAME_TIME_S;
self->target_ent = G_PickTarget(self->target);
self->target_ent->owner = self;
self->target_ent->teammaster->owner = self;
self->s.angles = self->target_ent->s.angles;
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
vec[2] = 0;
self->move_origin[0] = vec.length();
vec = self->s.origin - self->target_ent->s.origin;
vec = vectoangles(vec);
AnglesNormalize(vec);
self->move_origin[1] = vec[1];
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
// add the driver to the end of them team chain
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
;
ent->teamchain = self;
self->teammaster = self->target_ent->teammaster;
self->flags |= FL_TEAMSLAVE;
}
void InfantryPrecache();
void SP_turret_driver(edict_t *self)
{
if (deathmatch->integer)
{
G_FreeEdict(self);
return;
}
InfantryPrecache();
self->movetype = MOVETYPE_PUSH;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 32 };
self->health = self->max_health = 100;
self->gib_health = -40;
self->mass = 200;
self->viewheight = 24;
self->pain = infantry_pain;
self->die = turret_driver_die;
self->monsterinfo.stand = infantry_stand;
self->flags |= FL_NO_KNOCKBACK;
if (g_debug_monster_kills->integer)
level.monsters_registered[level.total_monsters] = self;
level.total_monsters++;
self->svflags |= SVF_MONSTER;
self->takedamage = true;
self->use = monster_use;
self->clipmask = MASK_MONSTERSOLID;
self->s.old_origin = self->s.origin;
self->monsterinfo.aiflags |= AI_STAND_GROUND;
self->monsterinfo.setskin = infantry_setskin;
if (st.item)
{
self->item = FindItemByClassname(st.item);
if (!self->item)
gi.Com_PrintFmt("{}: bad item: {}\n", *self, st.item);
}
self->think = turret_driver_link;
self->nextthink = level.time + FRAME_TIME_S;
gi.linkentity(self);
}
//============
// ROGUE
// invisible turret drivers so we can have unmanned turrets.
// originally designed to shoot at func_trains and such, so they
// fire at the center of the bounding box, rather than the entity's
// origin.
constexpr spawnflags_t SPAWNFLAG_TURRET_BRAIN_IGNORE_SIGHT = 1_spawnflag;
THINK(turret_brain_think) (edict_t *self) -> void
{
vec3_t target;
vec3_t dir;
vec3_t endpos;
trace_t trace;
self->nextthink = level.time + FRAME_TIME_S;
if (self->enemy)
{
if (!self->enemy->inuse)
self->enemy = nullptr;
else if (self->enemy->takedamage && self->enemy->health <= 0)
self->enemy = nullptr;
}
if (!self->enemy)
{
if (!FindTarget(self))
return;
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
endpos = self->enemy->absmax + self->enemy->absmin;
endpos *= 0.5f;
if (!self->spawnflags.has(SPAWNFLAG_TURRET_BRAIN_IGNORE_SIGHT))
{
trace = gi.traceline(self->target_ent->s.origin, endpos, self->target_ent, MASK_SHOT);
if (trace.fraction == 1 || trace.ent == self->enemy)
{
if (self->monsterinfo.aiflags & AI_LOST_SIGHT)
{
self->monsterinfo.trail_time = level.time;
self->monsterinfo.aiflags &= ~AI_LOST_SIGHT;
}
}
else
{
self->monsterinfo.aiflags |= AI_LOST_SIGHT;
return;
}
}
// let the turret know where we want it to aim
target = endpos;
dir = target - self->target_ent->s.origin;
self->target_ent->move_angles = vectoangles(dir);
// decide if we should shoot
if (level.time < self->monsterinfo.attack_finished)
return;
gtime_t reaction_time;
if (self->delay)
reaction_time = gtime_t::from_sec(self->delay);
else
reaction_time = gtime_t::from_sec(3 - skill->integer);
if ((level.time - self->monsterinfo.trail_time) < reaction_time)
return;
self->monsterinfo.attack_finished = level.time + reaction_time + 1_sec;
// FIXME how do we really want to pass this along?
self->target_ent->spawnflags |= SPAWNFLAG_TURRET_BREACH_FIRE;
}
// =================
// =================
THINK(turret_brain_link) (edict_t *self) -> void
{
vec3_t vec;
edict_t *ent;
if (self->killtarget)
{
self->enemy = G_PickTarget(self->killtarget);
}
self->think = turret_brain_think;
self->nextthink = level.time + FRAME_TIME_S;
self->target_ent = G_PickTarget(self->target);
self->target_ent->owner = self;
self->target_ent->teammaster->owner = self;
self->s.angles = self->target_ent->s.angles;
vec[0] = self->target_ent->s.origin[0] - self->s.origin[0];
vec[1] = self->target_ent->s.origin[1] - self->s.origin[1];
vec[2] = 0;
self->move_origin[0] = vec.length();
vec = self->s.origin - self->target_ent->s.origin;
vec = vectoangles(vec);
AnglesNormalize(vec);
self->move_origin[1] = vec[1];
self->move_origin[2] = self->s.origin[2] - self->target_ent->s.origin[2];
// add the driver to the end of them team chain
for (ent = self->target_ent->teammaster; ent->teamchain; ent = ent->teamchain)
ent->activator = self->activator; // pass along activator to breach, etc
ent->teamchain = self;
self->teammaster = self->target_ent->teammaster;
self->flags |= FL_TEAMSLAVE;
}
// =================
// =================
USE(turret_brain_deactivate) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
self->think = nullptr;
self->nextthink = 0_ms;
}
// =================
// =================
USE(turret_brain_activate) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
if (!self->enemy)
self->enemy = activator;
// wait at least 3 seconds to fire.
if (self->wait)
self->monsterinfo.attack_finished = level.time + gtime_t::from_sec(self->wait);
else
self->monsterinfo.attack_finished = level.time + 3_sec;
self->use = turret_brain_deactivate;
// Paril NOTE: rhangar1 has a turret_invisible_brain that breaks the
// hangar ceiling; once the final rocket explodes the barrier,
// it attempts to print "Barrier neutralized." to the rocket owner
// who happens to be this brain rather than the player that activated
// the turret. this resolves this by passing it along to fire_rocket.
self->activator = activator;
self->think = turret_brain_link;
self->nextthink = level.time + FRAME_TIME_S;
}
/*QUAKED turret_invisible_brain (1 .5 0) (-16 -16 -16) (16 16 16)
Invisible brain to drive the turret.
Does not search for targets. If targeted, can only be turned on once
and then off once. After that they are completely disabled.
"delay" the delay between firing (default ramps for skill level)
"Target" the turret breach
"Killtarget" the item you want it to attack.
Target the brain if you want it activated later, instead of immediately. It will wait 3 seconds
before firing to acquire the target.
*/
void SP_turret_invisible_brain(edict_t *self)
{
if (!self->killtarget)
{
gi.Com_Print("turret_invisible_brain with no killtarget!\n");
G_FreeEdict(self);
return;
}
if (!self->target)
{
gi.Com_Print("turret_invisible_brain with no target!\n");
G_FreeEdict(self);
return;
}
if (self->targetname)
{
self->use = turret_brain_activate;
}
else
{
self->think = turret_brain_link;
self->nextthink = level.time + FRAME_TIME_S;
}
self->movetype = MOVETYPE_PUSH;
gi.linkentity(self);
}
// ROGUE
//============

546
rerelease/g_utils.cpp Normal file
View File

@@ -0,0 +1,546 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_utils.c -- misc utility functions for game module
#include "g_local.h"
/*
=============
G_Find
Searches all active entities for the next one that validates the given callback.
Searches beginning at the edict after from, or the beginning if nullptr
nullptr will be returned if the end of the list is reached.
=============
*/
edict_t *G_Find(edict_t *from, std::function<bool(edict_t *e)> matcher)
{
if (!from)
from = g_edicts;
else
from++;
for (; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
continue;
if (matcher(from))
return from;
}
return nullptr;
}
/*
=================
findradius
Returns entities that have origins within a spherical area
findradius (origin, radius)
=================
*/
edict_t *findradius(edict_t *from, const vec3_t &org, float rad)
{
vec3_t eorg;
int j;
if (!from)
from = g_edicts;
else
from++;
for (; from < &g_edicts[globals.num_edicts]; from++)
{
if (!from->inuse)
continue;
if (from->solid == SOLID_NOT)
continue;
for (j = 0; j < 3; j++)
eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j]) * 0.5f);
if (eorg.length() > rad)
continue;
return from;
}
return nullptr;
}
/*
=============
G_PickTarget
Searches all active entities for the next one that holds
the matching string at fieldofs in the structure.
Searches beginning at the edict after from, or the beginning if nullptr
nullptr will be returned if the end of the list is reached.
=============
*/
constexpr size_t MAXCHOICES = 8;
edict_t *G_PickTarget(const char *targetname)
{
edict_t *ent = nullptr;
int num_choices = 0;
edict_t *choice[MAXCHOICES];
if (!targetname)
{
gi.Com_Print("G_PickTarget called with nullptr targetname\n");
return nullptr;
}
while (1)
{
ent = G_FindByString<&edict_t::targetname>(ent, targetname);
if (!ent)
break;
choice[num_choices++] = ent;
if (num_choices == MAXCHOICES)
break;
}
if (!num_choices)
{
gi.Com_PrintFmt("G_PickTarget: target {} not found\n", targetname);
return nullptr;
}
return choice[irandom(num_choices)];
}
THINK(Think_Delay) (edict_t *ent) -> void
{
G_UseTargets(ent, ent->activator);
G_FreeEdict(ent);
}
void G_PrintActivationMessage(edict_t *ent, edict_t *activator, bool coop_global)
{
//
// print the message
//
if ((ent->message) && !(activator->svflags & SVF_MONSTER))
{
if (coop_global && coop->integer)
gi.LocBroadcast_Print(PRINT_CENTER, "{}", ent->message);
else
gi.LocCenter_Print(activator, "{}", ent->message);
// [Paril-KEX] allow non-noisy centerprints
if (ent->noise_index >= 0)
{
if (ent->noise_index)
gi.sound(activator, CHAN_AUTO, ent->noise_index, 1, ATTN_NORM, 0);
else
gi.sound(activator, CHAN_AUTO, gi.soundindex("misc/talk1.wav"), 1, ATTN_NORM, 0);
}
}
}
void G_MonsterKilled(edict_t *self);
/*
==============================
G_UseTargets
the global "activator" should be set to the entity that initiated the firing.
If self.delay is set, a DelayedUse entity will be created that will actually
do the SUB_UseTargets after that many seconds have passed.
Centerprints any self.message to the activator.
Search for (string)targetname in all entities that
match (string)self.target and call their .use function
==============================
*/
void G_UseTargets(edict_t *ent, edict_t *activator)
{
edict_t *t;
//
// check for a delay
//
if (ent->delay)
{
// create a temp object to fire at a later time
t = G_Spawn();
t->classname = "DelayedUse";
t->nextthink = level.time + gtime_t::from_sec(ent->delay);
t->think = Think_Delay;
t->activator = activator;
if (!activator)
gi.Com_Print("Think_Delay with no activator\n");
t->message = ent->message;
t->target = ent->target;
t->killtarget = ent->killtarget;
return;
}
//
// print the message
//
G_PrintActivationMessage(ent, activator, true);
//
// kill killtargets
//
if (ent->killtarget)
{
t = nullptr;
while ((t = G_FindByString<&edict_t::targetname>(t, ent->killtarget)))
{
if (t->teammaster)
{
// PMM - if this entity is part of a chain, cleanly remove it
if (t->flags & FL_TEAMSLAVE)
{
for (edict_t *master = t->teammaster; master; master = master->teamchain)
{
if (master->teamchain == t)
{
master->teamchain = t->teamchain;
break;
}
}
}
// [Paril-KEX] remove teammaster too
else if (t->flags & FL_TEAMMASTER)
{
t->teammaster->flags &= ~FL_TEAMMASTER;
edict_t *new_master = t->teammaster->teamchain;
if (new_master)
{
new_master->flags |= FL_TEAMMASTER;
new_master->flags &= ~FL_TEAMSLAVE;
for (edict_t *m = new_master; m; m = m->teamchain)
m->teammaster = new_master;
}
}
}
// [Paril-KEX] if we killtarget a monster, clean up properly
if (t->svflags & SVF_MONSTER)
{
if (!t->deadflag && !(t->monsterinfo.aiflags & AI_DO_NOT_COUNT) && !(t->spawnflags & SPAWNFLAG_MONSTER_DEAD))
G_MonsterKilled(t);
}
// PMM
G_FreeEdict(t);
if (!ent->inuse)
{
gi.Com_Print("entity was removed while using killtargets\n");
return;
}
}
}
//
// fire targets
//
if (ent->target)
{
t = nullptr;
while ((t = G_FindByString<&edict_t::targetname>(t, ent->target)))
{
// doors fire area portals in a specific way
if (!Q_strcasecmp(t->classname, "func_areaportal") &&
(!Q_strcasecmp(ent->classname, "func_door") || !Q_strcasecmp(ent->classname, "func_door_rotating")
|| !Q_strcasecmp(ent->classname, "func_door_secret") || !Q_strcasecmp(ent->classname, "func_water")))
continue;
if (t == ent)
{
gi.Com_Print("WARNING: Entity used itself.\n");
}
else
{
if (t->use)
t->use(t, ent, activator);
}
if (!ent->inuse)
{
gi.Com_Print("entity was removed while using targets\n");
return;
}
}
}
}
constexpr vec3_t VEC_UP = { 0, -1, 0 };
constexpr vec3_t MOVEDIR_UP = { 0, 0, 1 };
constexpr vec3_t VEC_DOWN = { 0, -2, 0 };
constexpr vec3_t MOVEDIR_DOWN = { 0, 0, -1 };
void G_SetMovedir(vec3_t &angles, vec3_t &movedir)
{
if (angles == VEC_UP)
{
movedir = MOVEDIR_UP;
}
else if (angles == VEC_DOWN)
{
movedir = MOVEDIR_DOWN;
}
else
{
AngleVectors(angles, movedir, nullptr, nullptr);
}
angles = {};
}
char *G_CopyString(const char *in, int32_t tag)
{
if(!in)
return nullptr;
const size_t amt = strlen(in) + 1;
char *const out = static_cast<char *>(gi.TagMalloc(amt, tag));
Q_strlcpy(out, in, amt);
return out;
}
void G_InitEdict(edict_t *e)
{
// ROGUE
// FIXME -
// this fixes a bug somewhere that is setting "nextthink" for an entity that has
// already been released. nextthink is being set to FRAME_TIME_S after level.time,
// since freetime = nextthink - FRAME_TIME_S
if (e->nextthink)
e->nextthink = 0_ms;
// ROGUE
e->inuse = true;
e->sv.init = false;
e->classname = "noclass";
e->gravity = 1.0;
e->s.number = e - g_edicts;
// PGM - do this before calling the spawn function so it can be overridden.
e->gravityVector[0] = 0.0;
e->gravityVector[1] = 0.0;
e->gravityVector[2] = -1.0;
// PGM
}
/*
=================
G_Spawn
Either finds a free edict, or allocates a new one.
Try to avoid reusing an entity that was recently freed, because it
can cause the client to think the entity morphed into something else
instead of being removed and recreated, which can cause interpolated
angles and bad trails.
=================
*/
edict_t *G_Spawn()
{
uint32_t i;
edict_t *e;
e = &g_edicts[game.maxclients + 1];
for (i = game.maxclients + 1; i < globals.num_edicts; i++, e++)
{
// the first couple seconds of server time can involve a lot of
// freeing and allocating, so relax the replacement policy
if (!e->inuse && (e->freetime < 2_sec || level.time - e->freetime > 500_ms))
{
G_InitEdict(e);
return e;
}
}
if (i == game.maxentities)
gi.Com_Error("ED_Alloc: no free edicts");
globals.num_edicts++;
G_InitEdict(e);
return e;
}
/*
=================
G_FreeEdict
Marks the edict as free
=================
*/
THINK(G_FreeEdict) (edict_t *ed) -> void
{
// already freed
if (!ed->inuse)
return;
gi.unlinkentity(ed); // unlink from world
if ((ed - g_edicts) <= (ptrdiff_t) (game.maxclients + BODY_QUEUE_SIZE))
{
#ifdef _DEBUG
gi.Com_Print("tried to free special edict\n");
#endif
return;
}
gi.Bot_UnRegisterEdict( ed );
int32_t id = ed->spawn_count + 1;
memset(ed, 0, sizeof(*ed));
ed->s.number = ed - g_edicts;
ed->classname = "freed";
ed->freetime = level.time;
ed->inuse = false;
ed->spawn_count = id;
ed->sv.init = false;
}
BoxEdictsResult_t G_TouchTriggers_BoxFilter(edict_t *hit, void *)
{
if (!hit->touch)
return BoxEdictsResult_t::Skip;
return BoxEdictsResult_t::Keep;
}
/*
============
G_TouchTriggers
============
*/
void G_TouchTriggers(edict_t *ent)
{
int i, num;
static edict_t *touch[MAX_EDICTS];
edict_t *hit;
// dead things don't activate triggers!
if ((ent->client || (ent->svflags & SVF_MONSTER)) && (ent->health <= 0))
return;
num = gi.BoxEdicts(ent->absmin, ent->absmax, touch, MAX_EDICTS, AREA_TRIGGERS, G_TouchTriggers_BoxFilter, nullptr);
// be careful, it is possible to have an entity in this
// list removed before we get to it (killtriggered)
for (i = 0; i < num; i++)
{
hit = touch[i];
if (!hit->inuse)
continue;
if (!hit->touch)
continue;
hit->touch(hit, ent, null_trace, true);
}
}
// [Paril-KEX] scan for projectiles between our movement positions
// to see if we need to collide against them
void G_TouchProjectiles(edict_t *ent, vec3_t previous_origin)
{
// a bit ugly, but we'll store projectiles we are ignoring here.
static std::vector<edict_t *> skipped;
while (true)
{
trace_t tr = gi.trace(previous_origin, ent->mins, ent->maxs, ent->s.origin, ent, ent->clipmask | CONTENTS_PROJECTILE);
if (tr.fraction == 1.0f)
break;
else if (!(tr.ent->svflags & SVF_PROJECTILE))
break;
// 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);
// 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))
continue;
G_Impact(ent, tr);
}
for (auto &skip : skipped)
skip->svflags |= SVF_PROJECTILE;
skipped.clear();
}
/*
==============================================================================
Kill box
==============================================================================
*/
/*
=================
KillBox
Kills all entities that would touch the proposed new positioning
of ent.
=================
*/
BoxEdictsResult_t KillBox_BoxFilter(edict_t *hit, void *)
{
if (!hit->solid || !hit->takedamage || hit->solid == SOLID_TRIGGER)
return BoxEdictsResult_t::Skip;
return BoxEdictsResult_t::Keep;
}
bool KillBox(edict_t *ent, bool from_spawning, mod_id_t mod, bool bsp_clipping)
{
// don't telefrag as spectator...
if (ent->movetype == MOVETYPE_NOCLIP)
return true;
contents_t mask = CONTENTS_MONSTER | CONTENTS_PLAYER;
// [Paril-KEX] don't gib other players in coop if we're not colliding
if (from_spawning && ent->client && coop->integer && !G_ShouldPlayersCollide(false))
mask &= ~CONTENTS_PLAYER;
int i, num;
static edict_t *touch[MAX_EDICTS];
edict_t *hit;
num = gi.BoxEdicts(ent->absmin, ent->absmax, touch, MAX_EDICTS, AREA_SOLID, KillBox_BoxFilter, nullptr);
for (i = 0; i < num; i++)
{
hit = touch[i];
if (hit == ent)
continue;
else if (!hit->inuse || !hit->takedamage || !hit->solid || hit->solid == SOLID_TRIGGER)
continue;
else if (hit->client && !(mask & CONTENTS_PLAYER))
continue;
if ((ent->solid == SOLID_BSP || (ent->svflags & SVF_HULL)) && bsp_clipping)
{
trace_t clip = gi.clip(ent, hit->s.origin, hit->mins, hit->maxs, hit->s.origin, G_GetClipMask(hit));
if (clip.fraction == 1.0f)
continue;
}
T_Damage(hit, ent, ent, vec3_origin, ent->s.origin, vec3_origin, 100000, 0, DAMAGE_NO_PROTECTION, mod);
}
return true; // all clear
}

1217
rerelease/g_weapon.cpp Normal file

File diff suppressed because it is too large Load Diff

2315
rerelease/game.h Normal file

File diff suppressed because it is too large Load Diff

25
rerelease/game.sln Normal file
View File

@@ -0,0 +1,25 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.33808.371
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "game", "game.vcxproj", "{C994B5EA-3058-403C-953D-3673C2C4D64E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|x64 = Debug|x64
Release|x64 = Release|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C994B5EA-3058-403C-953D-3673C2C4D64E}.Debug|x64.ActiveCfg = Debug|x64
{C994B5EA-3058-403C-953D-3673C2C4D64E}.Debug|x64.Build.0 = Debug|x64
{C994B5EA-3058-403C-953D-3673C2C4D64E}.Release|x64.ActiveCfg = Release|x64
{C994B5EA-3058-403C-953D-3673C2C4D64E}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {5E7547B5-6A49-45CD-97AA-956B8460495D}
EndGlobalSection
EndGlobal

246
rerelease/game.vcxproj Normal file
View File

@@ -0,0 +1,246 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{c994b5ea-3058-403c-953d-3673c2c4d64e}</ProjectGuid>
<RootNamespace>game</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>../</OutDir>
<TargetName>$(ProjectName)_x64</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>../</OutDir>
<TargetName>$(ProjectName)_x64</TargetName>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<VcpkgUseStatic>true</VcpkgUseStatic>
</PropertyGroup>
<PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<VcpkgUseStatic>true</VcpkgUseStatic>
</PropertyGroup>
<PropertyGroup Label="Vcpkg">
<VcpkgEnableManifest>true</VcpkgEnableManifest>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<DisableSpecificWarnings>4267;4244</DisableSpecificWarnings>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>NotSet</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>KEX_Q2_GAME;KEX_Q2GAME_EXPORTS;NO_FMT_SOURCE;KEX_Q2GAME_DYNAMIC;_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
<DisableSpecificWarnings>4267;4244</DisableSpecificWarnings>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
</ClCompile>
<Link>
<SubSystem>NotSet</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="bg_local.h" />
<ClInclude Include="bots\bot_debug.h" />
<ClInclude Include="bots\bot_exports.h" />
<ClInclude Include="bots\bot_includes.h" />
<ClInclude Include="bots\bot_think.h" />
<ClInclude Include="bots\bot_utils.h" />
<ClInclude Include="cg_local.h" />
<ClInclude Include="ctf\g_ctf.h" />
<ClInclude Include="ctf\p_ctf_menu.h" />
<ClInclude Include="game.h" />
<ClInclude Include="g_local.h" />
<ClInclude Include="g_statusbar.h" />
<ClInclude Include="m_actor.h" />
<ClInclude Include="m_arachnid.h" />
<ClInclude Include="m_berserk.h" />
<ClInclude Include="m_boss2.h" />
<ClInclude Include="m_boss31.h" />
<ClInclude Include="m_boss32.h" />
<ClInclude Include="m_brain.h" />
<ClInclude Include="m_chick.h" />
<ClInclude Include="m_flash.h" />
<ClInclude Include="m_flipper.h" />
<ClInclude Include="m_float.h" />
<ClInclude Include="m_flyer.h" />
<ClInclude Include="m_gladiator.h" />
<ClInclude Include="m_guardian.h" />
<ClInclude Include="m_gunner.h" />
<ClInclude Include="m_hover.h" />
<ClInclude Include="m_infantry.h" />
<ClInclude Include="m_insane.h" />
<ClInclude Include="m_medic.h" />
<ClInclude Include="m_mutant.h" />
<ClInclude Include="m_parasite.h" />
<ClInclude Include="m_player.h" />
<ClInclude Include="m_rider.h" />
<ClInclude Include="m_shambler.h" />
<ClInclude Include="m_soldier.h" />
<ClInclude Include="m_supertank.h" />
<ClInclude Include="m_tank.h" />
<ClInclude Include="q_std.h" />
<ClInclude Include="q_vec3.h" />
<ClInclude Include="rogue\m_rogue_carrier.h" />
<ClInclude Include="rogue\m_rogue_stalker.h" />
<ClInclude Include="rogue\m_rogue_turret.h" />
<ClInclude Include="rogue\m_rogue_widow.h" />
<ClInclude Include="rogue\m_rogue_widow2.h" />
<ClInclude Include="xatrix\m_xatrix_fixbot.h" />
<ClInclude Include="xatrix\m_xatrix_gekk.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="bots\bot_debug.cpp" />
<ClCompile Include="bots\bot_exports.cpp" />
<ClCompile Include="bots\bot_think.cpp" />
<ClCompile Include="bots\bot_utils.cpp" />
<ClCompile Include="cg_main.cpp" />
<ClCompile Include="cg_screen.cpp" />
<ClCompile Include="ctf\g_ctf.cpp" />
<ClCompile Include="ctf\p_ctf_menu.cpp" />
<ClCompile Include="g_ai.cpp" />
<ClCompile Include="g_chase.cpp" />
<ClCompile Include="g_cmds.cpp" />
<ClCompile Include="g_combat.cpp" />
<ClCompile Include="g_func.cpp" />
<ClCompile Include="g_items.cpp" />
<ClCompile Include="g_main.cpp" />
<ClCompile Include="g_misc.cpp" />
<ClCompile Include="g_monster.cpp" />
<ClCompile Include="g_phys.cpp" />
<ClCompile Include="g_save.cpp" />
<ClCompile Include="g_spawn.cpp" />
<ClCompile Include="g_svcmds.cpp" />
<ClCompile Include="g_target.cpp" />
<ClCompile Include="g_trigger.cpp" />
<ClCompile Include="g_turret.cpp" />
<ClCompile Include="g_utils.cpp" />
<ClCompile Include="g_weapon.cpp" />
<ClCompile Include="m_actor.cpp" />
<ClCompile Include="m_arachnid.cpp" />
<ClCompile Include="m_berserk.cpp" />
<ClCompile Include="m_boss2.cpp" />
<ClCompile Include="m_boss3.cpp" />
<ClCompile Include="m_boss31.cpp" />
<ClCompile Include="m_boss32.cpp" />
<ClCompile Include="m_brain.cpp" />
<ClCompile Include="m_chick.cpp" />
<ClCompile Include="m_flipper.cpp" />
<ClCompile Include="m_float.cpp" />
<ClCompile Include="m_flyer.cpp" />
<ClCompile Include="m_gladiator.cpp" />
<ClCompile Include="m_guardian.cpp" />
<ClCompile Include="m_guncmdr.cpp" />
<ClCompile Include="m_gunner.cpp" />
<ClCompile Include="m_hover.cpp" />
<ClCompile Include="m_infantry.cpp" />
<ClCompile Include="m_insane.cpp" />
<ClCompile Include="m_medic.cpp" />
<ClCompile Include="m_move.cpp" />
<ClCompile Include="m_mutant.cpp" />
<ClCompile Include="m_parasite.cpp" />
<ClCompile Include="m_shambler.cpp" />
<ClCompile Include="m_soldier.cpp" />
<ClCompile Include="m_supertank.cpp" />
<ClCompile Include="m_tank.cpp" />
<ClCompile Include="p_client.cpp" />
<ClCompile Include="p_hud.cpp" />
<ClCompile Include="p_move.cpp" />
<ClCompile Include="p_trail.cpp" />
<ClCompile Include="p_view.cpp" />
<ClCompile Include="p_weapon.cpp" />
<ClCompile Include="q_std.cpp" />
<ClCompile Include="rogue\g_rogue_combat.cpp" />
<ClCompile Include="rogue\g_rogue_func.cpp" />
<ClCompile Include="rogue\g_rogue_items.cpp" />
<ClCompile Include="rogue\g_rogue_misc.cpp" />
<ClCompile Include="rogue\g_rogue_monster.cpp" />
<ClCompile Include="rogue\g_rogue_newai.cpp" />
<ClCompile Include="rogue\g_rogue_newdm.cpp" />
<ClCompile Include="rogue\g_rogue_newfnc.cpp" />
<ClCompile Include="rogue\g_rogue_newtarg.cpp" />
<ClCompile Include="rogue\g_rogue_newtrig.cpp" />
<ClCompile Include="rogue\g_rogue_newweap.cpp" />
<ClCompile Include="rogue\g_rogue_phys.cpp" />
<ClCompile Include="rogue\g_rogue_spawn.cpp" />
<ClCompile Include="rogue\g_rogue_sphere.cpp" />
<ClCompile Include="rogue\g_rogue_utils.cpp" />
<ClCompile Include="rogue\m_rogue_carrier.cpp" />
<ClCompile Include="rogue\m_rogue_stalker.cpp" />
<ClCompile Include="rogue\m_rogue_turret.cpp" />
<ClCompile Include="rogue\m_rogue_widow.cpp" />
<ClCompile Include="rogue\m_rogue_widow2.cpp" />
<ClCompile Include="rogue\p_rogue_weapon.cpp" />
<ClCompile Include="rogue\rogue_dm_ball.cpp" />
<ClCompile Include="rogue\rogue_dm_tag.cpp" />
<ClCompile Include="xatrix\g_xatrix_func.cpp" />
<ClCompile Include="xatrix\g_xatrix_items.cpp" />
<ClCompile Include="xatrix\g_xatrix_misc.cpp" />
<ClCompile Include="xatrix\g_xatrix_monster.cpp" />
<ClCompile Include="xatrix\g_xatrix_target.cpp" />
<ClCompile Include="xatrix\g_xatrix_weapon.cpp" />
<ClCompile Include="xatrix\m_xatrix_fixbot.cpp" />
<ClCompile Include="xatrix\m_xatrix_gekk.cpp" />
<ClCompile Include="xatrix\p_xatrix_weapon.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,265 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<ClInclude Include="bg_local.h" />
<ClInclude Include="cg_local.h" />
<ClInclude Include="g_local.h" />
<ClInclude Include="g_statusbar.h" />
<ClInclude Include="game.h" />
<ClInclude Include="m_actor.h" />
<ClInclude Include="m_arachnid.h" />
<ClInclude Include="m_berserk.h" />
<ClInclude Include="m_boss2.h" />
<ClInclude Include="m_boss31.h" />
<ClInclude Include="m_boss32.h" />
<ClInclude Include="m_brain.h" />
<ClInclude Include="m_chick.h" />
<ClInclude Include="m_flash.h" />
<ClInclude Include="m_flipper.h" />
<ClInclude Include="m_float.h" />
<ClInclude Include="m_flyer.h" />
<ClInclude Include="m_gladiator.h" />
<ClInclude Include="m_guardian.h" />
<ClInclude Include="m_gunner.h" />
<ClInclude Include="m_hover.h" />
<ClInclude Include="m_infantry.h" />
<ClInclude Include="m_insane.h" />
<ClInclude Include="m_medic.h" />
<ClInclude Include="m_mutant.h" />
<ClInclude Include="m_parasite.h" />
<ClInclude Include="m_player.h" />
<ClInclude Include="m_rider.h" />
<ClInclude Include="m_shambler.h" />
<ClInclude Include="m_soldier.h" />
<ClInclude Include="m_supertank.h" />
<ClInclude Include="m_tank.h" />
<ClInclude Include="q_std.h" />
<ClInclude Include="q_vec3.h" />
<ClInclude Include="bots\bot_debug.h">
<Filter>bots</Filter>
</ClInclude>
<ClInclude Include="bots\bot_exports.h">
<Filter>bots</Filter>
</ClInclude>
<ClInclude Include="bots\bot_includes.h">
<Filter>bots</Filter>
</ClInclude>
<ClInclude Include="bots\bot_think.h">
<Filter>bots</Filter>
</ClInclude>
<ClInclude Include="bots\bot_utils.h">
<Filter>bots</Filter>
</ClInclude>
<ClInclude Include="ctf\g_ctf.h">
<Filter>ctf</Filter>
</ClInclude>
<ClInclude Include="ctf\p_ctf_menu.h">
<Filter>ctf</Filter>
</ClInclude>
<ClInclude Include="rogue\m_rogue_carrier.h">
<Filter>rogue</Filter>
</ClInclude>
<ClInclude Include="rogue\m_rogue_stalker.h">
<Filter>rogue</Filter>
</ClInclude>
<ClInclude Include="rogue\m_rogue_turret.h">
<Filter>rogue</Filter>
</ClInclude>
<ClInclude Include="rogue\m_rogue_widow.h">
<Filter>rogue</Filter>
</ClInclude>
<ClInclude Include="rogue\m_rogue_widow2.h">
<Filter>rogue</Filter>
</ClInclude>
<ClInclude Include="xatrix\m_xatrix_fixbot.h">
<Filter>xatrix</Filter>
</ClInclude>
<ClInclude Include="xatrix\m_xatrix_gekk.h">
<Filter>xatrix</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="cg_main.cpp" />
<ClCompile Include="cg_screen.cpp" />
<ClCompile Include="g_ai.cpp" />
<ClCompile Include="g_chase.cpp" />
<ClCompile Include="g_cmds.cpp" />
<ClCompile Include="g_combat.cpp" />
<ClCompile Include="g_func.cpp" />
<ClCompile Include="g_items.cpp" />
<ClCompile Include="g_main.cpp" />
<ClCompile Include="g_misc.cpp" />
<ClCompile Include="g_monster.cpp" />
<ClCompile Include="g_phys.cpp" />
<ClCompile Include="g_save.cpp" />
<ClCompile Include="g_spawn.cpp" />
<ClCompile Include="g_svcmds.cpp" />
<ClCompile Include="g_target.cpp" />
<ClCompile Include="g_trigger.cpp" />
<ClCompile Include="g_turret.cpp" />
<ClCompile Include="g_utils.cpp" />
<ClCompile Include="g_weapon.cpp" />
<ClCompile Include="m_actor.cpp" />
<ClCompile Include="m_arachnid.cpp" />
<ClCompile Include="m_berserk.cpp" />
<ClCompile Include="m_boss2.cpp" />
<ClCompile Include="m_boss3.cpp" />
<ClCompile Include="m_boss31.cpp" />
<ClCompile Include="m_boss32.cpp" />
<ClCompile Include="m_brain.cpp" />
<ClCompile Include="m_chick.cpp" />
<ClCompile Include="m_flipper.cpp" />
<ClCompile Include="m_float.cpp" />
<ClCompile Include="m_flyer.cpp" />
<ClCompile Include="m_gladiator.cpp" />
<ClCompile Include="m_guardian.cpp" />
<ClCompile Include="m_guncmdr.cpp" />
<ClCompile Include="m_gunner.cpp" />
<ClCompile Include="m_hover.cpp" />
<ClCompile Include="m_infantry.cpp" />
<ClCompile Include="m_insane.cpp" />
<ClCompile Include="m_medic.cpp" />
<ClCompile Include="m_move.cpp" />
<ClCompile Include="m_mutant.cpp" />
<ClCompile Include="m_parasite.cpp" />
<ClCompile Include="m_shambler.cpp" />
<ClCompile Include="m_soldier.cpp" />
<ClCompile Include="m_supertank.cpp" />
<ClCompile Include="m_tank.cpp" />
<ClCompile Include="p_client.cpp" />
<ClCompile Include="p_hud.cpp" />
<ClCompile Include="p_move.cpp" />
<ClCompile Include="p_trail.cpp" />
<ClCompile Include="p_view.cpp" />
<ClCompile Include="p_weapon.cpp" />
<ClCompile Include="q_std.cpp" />
<ClCompile Include="bots\bot_debug.cpp">
<Filter>bots</Filter>
</ClCompile>
<ClCompile Include="bots\bot_exports.cpp">
<Filter>bots</Filter>
</ClCompile>
<ClCompile Include="bots\bot_think.cpp">
<Filter>bots</Filter>
</ClCompile>
<ClCompile Include="bots\bot_utils.cpp">
<Filter>bots</Filter>
</ClCompile>
<ClCompile Include="ctf\g_ctf.cpp">
<Filter>ctf</Filter>
</ClCompile>
<ClCompile Include="ctf\p_ctf_menu.cpp">
<Filter>ctf</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_combat.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_func.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_items.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_misc.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_monster.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_newai.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_newdm.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_newfnc.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_newtarg.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_newtrig.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_newweap.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_phys.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_spawn.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_sphere.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\g_rogue_utils.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\m_rogue_carrier.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\m_rogue_stalker.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\m_rogue_turret.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\m_rogue_widow.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\m_rogue_widow2.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\p_rogue_weapon.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\rogue_dm_ball.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="rogue\rogue_dm_tag.cpp">
<Filter>rogue</Filter>
</ClCompile>
<ClCompile Include="xatrix\g_xatrix_func.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\g_xatrix_items.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\g_xatrix_misc.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\g_xatrix_monster.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\g_xatrix_target.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\g_xatrix_weapon.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\m_xatrix_fixbot.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\m_xatrix_gekk.cpp">
<Filter>xatrix</Filter>
</ClCompile>
<ClCompile Include="xatrix\p_xatrix_weapon.cpp">
<Filter>xatrix</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<Filter Include="bots">
<UniqueIdentifier>{185665ce-b604-4d9a-b22b-02a83797d112}</UniqueIdentifier>
</Filter>
<Filter Include="ctf">
<UniqueIdentifier>{2b0fdaa0-3de9-4bbd-9bc6-3cadf798c291}</UniqueIdentifier>
</Filter>
<Filter Include="rogue">
<UniqueIdentifier>{76ef0be8-1c1d-472a-ada1-3aa1b354e022}</UniqueIdentifier>
</Filter>
<Filter Include="xatrix">
<UniqueIdentifier>{6565427e-a805-4dc7-ba57-3ce0b62e4336}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

564
rerelease/m_actor.cpp Normal file
View File

@@ -0,0 +1,564 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// g_actor.c
#include "g_local.h"
#include "m_actor.h"
#include "m_flash.h"
constexpr const char *actor_names[] = {
"Hellrot",
"Tokay",
"Killme",
"Disruptor",
"Adrianator",
"Rambear",
"Titus",
"Bitterman"
};
mframe_t actor_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(actor_move_stand) = { FRAME_stand101, FRAME_stand140, actor_frames_stand, nullptr };
MONSTERINFO_STAND(actor_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &actor_move_stand);
// randomize on startup
if (level.time < 1_sec)
self->s.frame = irandom(self->monsterinfo.active_move->firstframe, self->monsterinfo.active_move->lastframe + 1);
}
mframe_t actor_frames_walk[] = {
{ ai_walk },
{ ai_walk, 6 },
{ ai_walk, 10 },
{ ai_walk, 3 },
{ ai_walk, 2 },
{ ai_walk, 7 },
{ ai_walk, 10 },
{ ai_walk, 1 }
};
MMOVE_T(actor_move_walk) = { FRAME_walk01, FRAME_walk08, actor_frames_walk, nullptr };
MONSTERINFO_WALK(actor_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &actor_move_walk);
}
mframe_t actor_frames_run[] = {
{ ai_run, 4 },
{ ai_run, 15 },
{ ai_run, 15 },
{ ai_run, 8 },
{ ai_run, 20 },
{ ai_run, 15 }
};
MMOVE_T(actor_move_run) = { FRAME_run02, FRAME_run07, actor_frames_run, nullptr };
MONSTERINFO_RUN(actor_run) (edict_t *self) -> void
{
if ((level.time < self->pain_debounce_time) && (!self->enemy))
{
if (self->movetarget)
actor_walk(self);
else
actor_stand(self);
return;
}
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
actor_stand(self);
return;
}
M_SetAnimation(self, &actor_move_run);
}
mframe_t actor_frames_pain1[] = {
{ ai_move, -5 },
{ ai_move, 4 },
{ ai_move, 1 }
};
MMOVE_T(actor_move_pain1) = { FRAME_pain101, FRAME_pain103, actor_frames_pain1, actor_run };
mframe_t actor_frames_pain2[] = {
{ ai_move, -4 },
{ ai_move, 4 },
{ ai_move }
};
MMOVE_T(actor_move_pain2) = { FRAME_pain201, FRAME_pain203, actor_frames_pain2, actor_run };
mframe_t actor_frames_pain3[] = {
{ ai_move, -1 },
{ ai_move, 1 },
{ ai_move, 0 }
};
MMOVE_T(actor_move_pain3) = { FRAME_pain301, FRAME_pain303, actor_frames_pain3, actor_run };
mframe_t actor_frames_flipoff[] = {
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn }
};
MMOVE_T(actor_move_flipoff) = { FRAME_flip01, FRAME_flip14, actor_frames_flipoff, actor_run };
mframe_t actor_frames_taunt[] = {
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn },
{ ai_turn }
};
MMOVE_T(actor_move_taunt) = { FRAME_taunt01, FRAME_taunt17, actor_frames_taunt, actor_run };
const char *messages[] = {
"Watch it",
"#$@*&",
"Idiot",
"Check your targets"
};
PAIN(actor_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
int n;
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
// gi.sound (self, CHAN_VOICE, actor.sound_pain, 1, ATTN_NORM, 0);
if ((other->client) && (frandom() < 0.4f))
{
vec3_t v;
const char *name;
v = other->s.origin - self->s.origin;
self->ideal_yaw = vectoyaw(v);
if (frandom() < 0.5f)
M_SetAnimation(self, &actor_move_flipoff);
else
M_SetAnimation(self, &actor_move_taunt);
name = actor_names[(self - g_edicts) % q_countof(actor_names)];
gi.LocClient_Print(other, PRINT_CHAT, "{}: {}!\n", name, random_element(messages));
return;
}
n = irandom(3);
if (n == 0)
M_SetAnimation(self, &actor_move_pain1);
else if (n == 1)
M_SetAnimation(self, &actor_move_pain2);
else
M_SetAnimation(self, &actor_move_pain3);
}
MONSTERINFO_SETSKIN(actor_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
void actorMachineGun(edict_t *self)
{
vec3_t start, target;
vec3_t forward, right;
AngleVectors(self->s.angles, forward, right, nullptr);
start = G_ProjectSource(self->s.origin, monster_flash_offset[MZ2_ACTOR_MACHINEGUN_1], forward, right);
if (self->enemy)
{
if (self->enemy->health > 0)
{
target = self->enemy->s.origin + (self->enemy->velocity * -0.2f);
target[2] += self->enemy->viewheight;
}
else
{
target = self->enemy->absmin;
target[2] += (self->enemy->size[2] / 2) + 1;
}
forward = target - start;
forward.normalize();
}
else
{
AngleVectors(self->s.angles, forward, nullptr, nullptr);
}
monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_ACTOR_MACHINEGUN_1);
}
void actor_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
self->movetype = MOVETYPE_TOSS;
self->svflags |= SVF_DEADMONSTER;
self->nextthink = 0_ms;
gi.linkentity(self);
}
mframe_t actor_frames_death1[] = {
{ ai_move },
{ ai_move },
{ ai_move, -13 },
{ ai_move, 14 },
{ ai_move, 3 },
{ ai_move, -2 },
{ ai_move, 1 }
};
MMOVE_T(actor_move_death1) = { FRAME_death101, FRAME_death107, actor_frames_death1, actor_dead };
mframe_t actor_frames_death2[] = {
{ ai_move },
{ ai_move, 7 },
{ ai_move, -6 },
{ ai_move, -5 },
{ ai_move, 1 },
{ ai_move },
{ ai_move, -1 },
{ ai_move, -2 },
{ ai_move, -1 },
{ ai_move, -9 },
{ ai_move, -13 },
{ ai_move, -13 },
{ ai_move }
};
MMOVE_T(actor_move_death2) = { FRAME_death201, FRAME_death213, actor_frames_death2, actor_dead };
DIE(actor_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
// check for gib
if (self->health <= -80)
{
// gi.sound (self, CHAN_VOICE, actor.sound_gib, 1, ATTN_NORM, 0);
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 4, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/objects/gibs/head2/tris.md2", GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
// gi.sound (self, CHAN_VOICE, actor.sound_die, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
if (brandom())
M_SetAnimation(self, &actor_move_death1);
else
M_SetAnimation(self, &actor_move_death2);
}
void actor_fire(edict_t *self)
{
actorMachineGun(self);
if (level.time >= self->monsterinfo.fire_wait)
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
else
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
}
mframe_t actor_frames_attack[] = {
{ ai_charge, -2, actor_fire },
{ ai_charge, -2 },
{ ai_charge, 3 },
{ ai_charge, 2 }
};
MMOVE_T(actor_move_attack) = { FRAME_attak01, FRAME_attak04, actor_frames_attack, actor_run };
MONSTERINFO_ATTACK(actor_attack) (edict_t *self) -> void
{
M_SetAnimation(self, &actor_move_attack);
self->monsterinfo.fire_wait = level.time + random_time(1_sec, 2.6_sec);
}
USE(actor_use) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
vec3_t v;
self->goalentity = self->movetarget = G_PickTarget(self->target);
if ((!self->movetarget) || (strcmp(self->movetarget->classname, "target_actor") != 0))
{
gi.Com_PrintFmt("{}: bad target {}\n", *self, self->target);
self->target = nullptr;
self->monsterinfo.pausetime = HOLD_FOREVER;
self->monsterinfo.stand(self);
return;
}
v = self->goalentity->s.origin - self->s.origin;
self->ideal_yaw = self->s.angles[YAW] = vectoyaw(v);
self->monsterinfo.walk(self);
self->target = nullptr;
}
/*QUAKED misc_actor (1 .5 0) (-16 -16 -24) (16 16 32)
*/
void SP_misc_actor(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict(self);
return;
}
if (!self->targetname)
{
gi.Com_PrintFmt("{}: no targetname\n", *self);
G_FreeEdict(self);
return;
}
if (!self->target)
{
gi.Com_PrintFmt("{}: no target\n", *self);
G_FreeEdict(self);
return;
}
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("players/male/tris.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 32 };
if (!self->health)
self->health = 100;
self->mass = 200;
self->pain = actor_pain;
self->die = actor_die;
self->monsterinfo.stand = actor_stand;
self->monsterinfo.walk = actor_walk;
self->monsterinfo.run = actor_run;
self->monsterinfo.attack = actor_attack;
self->monsterinfo.melee = nullptr;
self->monsterinfo.sight = nullptr;
self->monsterinfo.setskin = actor_setskin;
self->monsterinfo.aiflags |= AI_GOOD_GUY;
gi.linkentity(self);
M_SetAnimation(self, &actor_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
walkmonster_start(self);
// actors always start in a dormant state, they *must* be used to get going
self->use = actor_use;
}
/*QUAKED target_actor (.5 .3 0) (-8 -8 -8) (8 8 8) JUMP SHOOT ATTACK x HOLD BRUTAL
JUMP jump in set direction upon reaching this target
SHOOT take a single shot at the pathtarget
ATTACK attack pathtarget until it or actor is dead
"target" next target_actor
"pathtarget" target of any action to be taken at this point
"wait" amount of time actor should pause at this point
"message" actor will "say" this to the player
for JUMP only:
"speed" speed thrown forward (default 200)
"height" speed thrown upwards (default 200)
*/
constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_JUMP = 1_spawnflag;
constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_SHOOT = 2_spawnflag;
constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_ATTACK = 4_spawnflag;
constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_HOLD = 16_spawnflag;
constexpr spawnflags_t SPAWNFLAG_TARGET_ACTOR_BRUTAL = 32_spawnflag;
TOUCH(target_actor_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
vec3_t v;
if (other->movetarget != self)
return;
if (other->enemy)
return;
other->goalentity = other->movetarget = nullptr;
if (self->message)
{
edict_t *ent;
for (uint32_t n = 1; n <= game.maxclients; n++)
{
ent = &g_edicts[n];
if (!ent->inuse)
continue;
gi.LocClient_Print(ent, PRINT_CHAT, "{}: {}\n", actor_names[(other - g_edicts) % q_countof(actor_names)], self->message);
}
}
if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_JUMP)) // jump
{
other->velocity[0] = self->movedir[0] * self->speed;
other->velocity[1] = self->movedir[1] * self->speed;
if (other->groundentity)
{
other->groundentity = nullptr;
other->velocity[2] = self->movedir[2];
gi.sound(other, CHAN_VOICE, gi.soundindex("player/male/jump1.wav"), 1, ATTN_NORM, 0);
}
}
if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_SHOOT)) // shoot
{
}
else if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_ATTACK)) // attack
{
other->enemy = G_PickTarget(self->pathtarget);
if (other->enemy)
{
other->goalentity = other->enemy;
if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_BRUTAL))
other->monsterinfo.aiflags |= AI_BRUTAL;
if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_HOLD))
{
other->monsterinfo.aiflags |= AI_STAND_GROUND;
actor_stand(other);
}
else
{
actor_run(other);
}
}
}
if (!self->spawnflags.has((SPAWNFLAG_TARGET_ACTOR_ATTACK | SPAWNFLAG_TARGET_ACTOR_SHOOT)) && (self->pathtarget))
{
const char *savetarget;
savetarget = self->target;
self->target = self->pathtarget;
G_UseTargets(self, other);
self->target = savetarget;
}
other->movetarget = G_PickTarget(self->target);
if (!other->goalentity)
other->goalentity = other->movetarget;
if (!other->movetarget && !other->enemy)
{
other->monsterinfo.pausetime = HOLD_FOREVER;
other->monsterinfo.stand(other);
}
else if (other->movetarget == other->goalentity)
{
v = other->movetarget->s.origin - other->s.origin;
other->ideal_yaw = vectoyaw(v);
}
}
void SP_target_actor(edict_t *self)
{
if (!self->targetname)
gi.Com_PrintFmt("{}: no targetname\n", *self);
self->solid = SOLID_TRIGGER;
self->touch = target_actor_touch;
self->mins = { -8, -8, -8 };
self->maxs = { 8, 8, 8 };
self->svflags = SVF_NOCLIENT;
if (self->spawnflags.has(SPAWNFLAG_TARGET_ACTOR_JUMP))
{
if (!self->speed)
self->speed = 200;
if (!st.height)
st.height = 200;
if (self->s.angles[YAW] == 0)
self->s.angles[YAW] = 360;
G_SetMovedir(self->s.angles, self->movedir);
self->movedir[2] = (float) st.height;
}
gi.linkentity(self);
}

492
rerelease/m_actor.h Normal file
View File

@@ -0,0 +1,492 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/player_y
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_attak01,
FRAME_attak02,
FRAME_attak03,
FRAME_attak04,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_death206,
FRAME_death207,
FRAME_death208,
FRAME_death209,
FRAME_death210,
FRAME_death211,
FRAME_death212,
FRAME_death213,
FRAME_death301,
FRAME_death302,
FRAME_death303,
FRAME_death304,
FRAME_death305,
FRAME_death306,
FRAME_death307,
FRAME_death308,
FRAME_death309,
FRAME_death310,
FRAME_death311,
FRAME_death312,
FRAME_death313,
FRAME_death314,
FRAME_death315,
FRAME_flip01,
FRAME_flip02,
FRAME_flip03,
FRAME_flip04,
FRAME_flip05,
FRAME_flip06,
FRAME_flip07,
FRAME_flip08,
FRAME_flip09,
FRAME_flip10,
FRAME_flip11,
FRAME_flip12,
FRAME_flip13,
FRAME_flip14,
FRAME_grenad01,
FRAME_grenad02,
FRAME_grenad03,
FRAME_grenad04,
FRAME_grenad05,
FRAME_grenad06,
FRAME_grenad07,
FRAME_grenad08,
FRAME_grenad09,
FRAME_grenad10,
FRAME_grenad11,
FRAME_grenad12,
FRAME_grenad13,
FRAME_grenad14,
FRAME_grenad15,
FRAME_jump01,
FRAME_jump02,
FRAME_jump03,
FRAME_jump04,
FRAME_jump05,
FRAME_jump06,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_push01,
FRAME_push02,
FRAME_push03,
FRAME_push04,
FRAME_push05,
FRAME_push06,
FRAME_push07,
FRAME_push08,
FRAME_push09,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_run07,
FRAME_run08,
FRAME_run09,
FRAME_run10,
FRAME_run11,
FRAME_run12,
FRAME_runs01,
FRAME_runs02,
FRAME_runs03,
FRAME_runs04,
FRAME_runs05,
FRAME_runs06,
FRAME_runs07,
FRAME_runs08,
FRAME_runs09,
FRAME_runs10,
FRAME_runs11,
FRAME_runs12,
FRAME_salute01,
FRAME_salute02,
FRAME_salute03,
FRAME_salute04,
FRAME_salute05,
FRAME_salute06,
FRAME_salute07,
FRAME_salute08,
FRAME_salute09,
FRAME_salute10,
FRAME_salute11,
FRAME_salute12,
FRAME_stand101,
FRAME_stand102,
FRAME_stand103,
FRAME_stand104,
FRAME_stand105,
FRAME_stand106,
FRAME_stand107,
FRAME_stand108,
FRAME_stand109,
FRAME_stand110,
FRAME_stand111,
FRAME_stand112,
FRAME_stand113,
FRAME_stand114,
FRAME_stand115,
FRAME_stand116,
FRAME_stand117,
FRAME_stand118,
FRAME_stand119,
FRAME_stand120,
FRAME_stand121,
FRAME_stand122,
FRAME_stand123,
FRAME_stand124,
FRAME_stand125,
FRAME_stand126,
FRAME_stand127,
FRAME_stand128,
FRAME_stand129,
FRAME_stand130,
FRAME_stand131,
FRAME_stand132,
FRAME_stand133,
FRAME_stand134,
FRAME_stand135,
FRAME_stand136,
FRAME_stand137,
FRAME_stand138,
FRAME_stand139,
FRAME_stand140,
FRAME_stand201,
FRAME_stand202,
FRAME_stand203,
FRAME_stand204,
FRAME_stand205,
FRAME_stand206,
FRAME_stand207,
FRAME_stand208,
FRAME_stand209,
FRAME_stand210,
FRAME_stand211,
FRAME_stand212,
FRAME_stand213,
FRAME_stand214,
FRAME_stand215,
FRAME_stand216,
FRAME_stand217,
FRAME_stand218,
FRAME_stand219,
FRAME_stand220,
FRAME_stand221,
FRAME_stand222,
FRAME_stand223,
FRAME_swim01,
FRAME_swim02,
FRAME_swim03,
FRAME_swim04,
FRAME_swim05,
FRAME_swim06,
FRAME_swim07,
FRAME_swim08,
FRAME_swim09,
FRAME_swim10,
FRAME_swim11,
FRAME_swim12,
FRAME_sw_atk01,
FRAME_sw_atk02,
FRAME_sw_atk03,
FRAME_sw_atk04,
FRAME_sw_atk05,
FRAME_sw_atk06,
FRAME_sw_pan01,
FRAME_sw_pan02,
FRAME_sw_pan03,
FRAME_sw_pan04,
FRAME_sw_pan05,
FRAME_sw_std01,
FRAME_sw_std02,
FRAME_sw_std03,
FRAME_sw_std04,
FRAME_sw_std05,
FRAME_sw_std06,
FRAME_sw_std07,
FRAME_sw_std08,
FRAME_sw_std09,
FRAME_sw_std10,
FRAME_sw_std11,
FRAME_sw_std12,
FRAME_sw_std13,
FRAME_sw_std14,
FRAME_sw_std15,
FRAME_sw_std16,
FRAME_sw_std17,
FRAME_sw_std18,
FRAME_sw_std19,
FRAME_sw_std20,
FRAME_taunt01,
FRAME_taunt02,
FRAME_taunt03,
FRAME_taunt04,
FRAME_taunt05,
FRAME_taunt06,
FRAME_taunt07,
FRAME_taunt08,
FRAME_taunt09,
FRAME_taunt10,
FRAME_taunt11,
FRAME_taunt12,
FRAME_taunt13,
FRAME_taunt14,
FRAME_taunt15,
FRAME_taunt16,
FRAME_taunt17,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_wave01,
FRAME_wave02,
FRAME_wave03,
FRAME_wave04,
FRAME_wave05,
FRAME_wave06,
FRAME_wave07,
FRAME_wave08,
FRAME_wave09,
FRAME_wave10,
FRAME_wave11,
FRAME_wave12,
FRAME_wave13,
FRAME_wave14,
FRAME_wave15,
FRAME_wave16,
FRAME_wave17,
FRAME_wave18,
FRAME_wave19,
FRAME_wave20,
FRAME_wave21,
FRAME_bl_atk01,
FRAME_bl_atk02,
FRAME_bl_atk03,
FRAME_bl_atk04,
FRAME_bl_atk05,
FRAME_bl_atk06,
FRAME_bl_flp01,
FRAME_bl_flp02,
FRAME_bl_flp13,
FRAME_bl_flp14,
FRAME_bl_flp15,
FRAME_bl_jmp01,
FRAME_bl_jmp02,
FRAME_bl_jmp03,
FRAME_bl_jmp04,
FRAME_bl_jmp05,
FRAME_bl_jmp06,
FRAME_bl_pn101,
FRAME_bl_pn102,
FRAME_bl_pn103,
FRAME_bl_pn201,
FRAME_bl_pn202,
FRAME_bl_pn203,
FRAME_bl_pn301,
FRAME_bl_pn302,
FRAME_bl_pn303,
FRAME_bl_psh08,
FRAME_bl_psh09,
FRAME_bl_run01,
FRAME_bl_run02,
FRAME_bl_run03,
FRAME_bl_run04,
FRAME_bl_run05,
FRAME_bl_run06,
FRAME_bl_run07,
FRAME_bl_run08,
FRAME_bl_run09,
FRAME_bl_run10,
FRAME_bl_run11,
FRAME_bl_run12,
FRAME_bl_rns03,
FRAME_bl_rns04,
FRAME_bl_rns05,
FRAME_bl_rns06,
FRAME_bl_rns07,
FRAME_bl_rns08,
FRAME_bl_rns09,
FRAME_bl_sal10,
FRAME_bl_sal11,
FRAME_bl_sal12,
FRAME_bl_std01,
FRAME_bl_std02,
FRAME_bl_std03,
FRAME_bl_std04,
FRAME_bl_std05,
FRAME_bl_std06,
FRAME_bl_std07,
FRAME_bl_std08,
FRAME_bl_std09,
FRAME_bl_std10,
FRAME_bl_std11,
FRAME_bl_std12,
FRAME_bl_std13,
FRAME_bl_std14,
FRAME_bl_std15,
FRAME_bl_std16,
FRAME_bl_std17,
FRAME_bl_std18,
FRAME_bl_std19,
FRAME_bl_std20,
FRAME_bl_std21,
FRAME_bl_std22,
FRAME_bl_std23,
FRAME_bl_std24,
FRAME_bl_std25,
FRAME_bl_std26,
FRAME_bl_std27,
FRAME_bl_std28,
FRAME_bl_std29,
FRAME_bl_std30,
FRAME_bl_std31,
FRAME_bl_std32,
FRAME_bl_std33,
FRAME_bl_std34,
FRAME_bl_std35,
FRAME_bl_std36,
FRAME_bl_std37,
FRAME_bl_std38,
FRAME_bl_std39,
FRAME_bl_std40,
FRAME_bl_swm01,
FRAME_bl_swm02,
FRAME_bl_swm03,
FRAME_bl_swm04,
FRAME_bl_swm05,
FRAME_bl_swm06,
FRAME_bl_swm07,
FRAME_bl_swm08,
FRAME_bl_swm09,
FRAME_bl_swm10,
FRAME_bl_swm11,
FRAME_bl_swm12,
FRAME_bl_swk01,
FRAME_bl_swk02,
FRAME_bl_swk03,
FRAME_bl_swk04,
FRAME_bl_swk05,
FRAME_bl_swk06,
FRAME_bl_swp01,
FRAME_bl_swp02,
FRAME_bl_swp03,
FRAME_bl_swp04,
FRAME_bl_swp05,
FRAME_bl_sws01,
FRAME_bl_sws02,
FRAME_bl_sws03,
FRAME_bl_sws04,
FRAME_bl_sws05,
FRAME_bl_sws06,
FRAME_bl_sws07,
FRAME_bl_sws08,
FRAME_bl_sws09,
FRAME_bl_sws10,
FRAME_bl_sws11,
FRAME_bl_sws12,
FRAME_bl_sws13,
FRAME_bl_sws14,
FRAME_bl_tau14,
FRAME_bl_tau15,
FRAME_bl_tau16,
FRAME_bl_tau17,
FRAME_bl_wlk01,
FRAME_bl_wlk02,
FRAME_bl_wlk03,
FRAME_bl_wlk04,
FRAME_bl_wlk05,
FRAME_bl_wlk06,
FRAME_bl_wlk07,
FRAME_bl_wlk08,
FRAME_bl_wlk09,
FRAME_bl_wlk10,
FRAME_bl_wlk11,
FRAME_bl_wav19,
FRAME_bl_wav20,
FRAME_bl_wav21,
FRAME_cr_atk01,
FRAME_cr_atk02,
FRAME_cr_atk03,
FRAME_cr_atk04,
FRAME_cr_atk05,
FRAME_cr_atk06,
FRAME_cr_atk07,
FRAME_cr_atk08,
FRAME_cr_pan01,
FRAME_cr_pan02,
FRAME_cr_pan03,
FRAME_cr_pan04,
FRAME_cr_std01,
FRAME_cr_std02,
FRAME_cr_std03,
FRAME_cr_std04,
FRAME_cr_std05,
FRAME_cr_std06,
FRAME_cr_std07,
FRAME_cr_std08,
FRAME_cr_wlk01,
FRAME_cr_wlk02,
FRAME_cr_wlk03,
FRAME_cr_wlk04,
FRAME_cr_wlk05,
FRAME_cr_wlk06,
FRAME_cr_wlk07,
FRAME_crbl_a01,
FRAME_crbl_a02,
FRAME_crbl_a03,
FRAME_crbl_a04,
FRAME_crbl_a05,
FRAME_crbl_a06,
FRAME_crbl_a07,
FRAME_crbl_p01,
FRAME_crbl_p02,
FRAME_crbl_p03,
FRAME_crbl_p04,
FRAME_crbl_s01,
FRAME_crbl_s02,
FRAME_crbl_s03,
FRAME_crbl_s04,
FRAME_crbl_s05,
FRAME_crbl_s06,
FRAME_crbl_s07,
FRAME_crbl_s08,
FRAME_crbl_w01,
FRAME_crbl_w02,
FRAME_crbl_w03,
FRAME_crbl_w04,
FRAME_crbl_w05,
FRAME_crbl_w06,
FRAME_crbl_w07
};
constexpr float MODEL_SCALE = 1.000000f;

385
rerelease/m_arachnid.cpp Normal file
View File

@@ -0,0 +1,385 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
TANK
==============================================================================
*/
#include "g_local.h"
#include "m_arachnid.h"
#include "m_flash.h"
static int sound_pain;
static int sound_death;
static int sound_sight;
MONSTERINFO_SIGHT(arachnid_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
//
// stand
//
mframe_t arachnid_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(arachnid_move_stand) = { FRAME_idle1, FRAME_idle13, arachnid_frames_stand, nullptr };
MONSTERINFO_STAND(arachnid_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &arachnid_move_stand);
}
//
// walk
//
static int sound_step;
void arachnid_footstep(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_step, 0.5f, ATTN_IDLE, 0.0f);
}
mframe_t arachnid_frames_walk[] = {
{ ai_walk, 8, arachnid_footstep },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8, arachnid_footstep },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 }
};
MMOVE_T(arachnid_move_walk) = { FRAME_walk1, FRAME_walk10, arachnid_frames_walk, nullptr };
MONSTERINFO_WALK(arachnid_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &arachnid_move_walk);
}
//
// run
//
mframe_t arachnid_frames_run[] = {
{ ai_run, 8, arachnid_footstep },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8, arachnid_footstep },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 }
};
MMOVE_T(arachnid_move_run) = { FRAME_walk1, FRAME_walk10, arachnid_frames_run, nullptr };
MONSTERINFO_RUN(arachnid_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
M_SetAnimation(self, &arachnid_move_stand);
return;
}
M_SetAnimation(self, &arachnid_move_run);
}
//
// pain
//
mframe_t arachnid_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(arachnid_move_pain1) = { FRAME_pain11, FRAME_pain15, arachnid_frames_pain1, arachnid_run };
mframe_t arachnid_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(arachnid_move_pain2) = { FRAME_pain21, FRAME_pain26, arachnid_frames_pain2, arachnid_run };
PAIN(arachnid_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
float r = frandom();
if (r < 0.5f)
M_SetAnimation(self, &arachnid_move_pain1);
else
M_SetAnimation(self, &arachnid_move_pain2);
}
static int sound_charge;
void arachnid_charge_rail(edict_t *self)
{
if (!self->enemy || !self->enemy->inuse)
return;
gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f);
self->pos1 = self->enemy->s.origin;
self->pos1[2] += self->enemy->viewheight;
}
void arachnid_rail(edict_t *self)
{
vec3_t start;
vec3_t dir;
vec3_t forward, right;
monster_muzzleflash_id_t id;
switch (self->s.frame)
{
case FRAME_rails4:
default:
id = MZ2_ARACHNID_RAIL1;
break;
case FRAME_rails8:
id = MZ2_ARACHNID_RAIL2;
break;
case FRAME_rails_up7:
id = MZ2_ARACHNID_RAIL_UP1;
break;
case FRAME_rails_up11:
id = MZ2_ARACHNID_RAIL_UP2;
break;
}
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right);
// calc direction to where we targeted
dir = self->pos1 - start;
dir.normalize();
monster_fire_railgun(self, start, dir, 35, 100, id);
}
mframe_t arachnid_frames_attack1[] = {
{ ai_charge, 0, arachnid_charge_rail },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_rail },
{ ai_charge, 0, arachnid_charge_rail },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_rail },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(arachnid_attack1) = { FRAME_rails1, FRAME_rails11, arachnid_frames_attack1, arachnid_run };
mframe_t arachnid_frames_attack_up1[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_charge_rail },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_rail },
{ ai_charge, 0, arachnid_charge_rail },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_rail },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
};
MMOVE_T(arachnid_attack_up1) = { FRAME_rails_up1, FRAME_rails_up16, arachnid_frames_attack_up1, arachnid_run };
static int sound_melee, sound_melee_hit;
void arachnid_melee_charge(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_melee, 1.f, ATTN_NORM, 0.f);
}
void arachnid_melee_hit(edict_t *self)
{
if (!fire_hit(self, { MELEE_DISTANCE, 0, 0 }, 15, 50))
self->monsterinfo.melee_debounce_time = level.time + 1000_ms;
}
mframe_t arachnid_frames_melee[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_melee_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_melee_hit },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_melee_charge },
{ ai_charge },
{ ai_charge, 0, arachnid_melee_hit },
{ ai_charge }
};
MMOVE_T(arachnid_melee) = { FRAME_melee_atk1, FRAME_melee_atk12, arachnid_frames_melee, arachnid_run };
MONSTERINFO_ATTACK(arachnid_attack) (edict_t *self) -> void
{
if (!self->enemy || !self->enemy->inuse)
return;
if (self->monsterinfo.melee_debounce_time < level.time && range_to(self, self->enemy) < MELEE_DISTANCE)
M_SetAnimation(self, &arachnid_melee);
else if ((self->enemy->s.origin[2] - self->s.origin[2]) > 150.f)
M_SetAnimation(self, &arachnid_attack_up1);
else
M_SetAnimation(self, &arachnid_attack1);
}
//
// death
//
void arachnid_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
self->movetype = MOVETYPE_TOSS;
self->svflags |= SVF_DEADMONSTER;
self->nextthink = 0_ms;
gi.linkentity(self);
}
mframe_t arachnid_frames_death1[] = {
{ ai_move, 0 },
{ ai_move, -1.23f },
{ ai_move, -1.23f },
{ ai_move, -1.23f },
{ ai_move, -1.23f },
{ ai_move, -1.64f },
{ ai_move, -1.64f },
{ ai_move, -2.45f },
{ ai_move, -8.63f },
{ ai_move, -4.0f },
{ ai_move, -4.5f },
{ ai_move, -6.8f },
{ ai_move, -8.0f },
{ ai_move, -5.4f },
{ ai_move, -3.4f },
{ ai_move, -1.9f },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(arachnid_move_death) = { FRAME_death1, FRAME_death20, arachnid_frames_death1, arachnid_dead };
DIE(arachnid_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 4, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/objects/gibs/head2/tris.md2", GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
M_SetAnimation(self, &arachnid_move_death);
}
//
// monster_arachnid
//
/*QUAKED monster_arachnid (1 .5 0) (-48 -48 -20) (48 48 48) Ambush Trigger_Spawn Sight
*/
void SP_monster_arachnid(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
self->s.modelindex = gi.modelindex("models/monsters/arachnid/tris.md2");
self->mins = { -48, -48, -20 };
self->maxs = { 48, 48, 48 };
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->health = 1000 * st.health_multiplier;
self->gib_health = -200;
self->monsterinfo.scale = MODEL_SCALE;
self->mass = 450;
self->pain = arachnid_pain;
self->die = arachnid_die;
self->monsterinfo.stand = arachnid_stand;
self->monsterinfo.walk = arachnid_walk;
self->monsterinfo.run = arachnid_run;
self->monsterinfo.attack = arachnid_attack;
self->monsterinfo.sight = arachnid_sight;
gi.linkentity(self);
M_SetAnimation(self, &arachnid_move_stand);
walkmonster_start(self);
}

142
rerelease/m_arachnid.h Normal file
View File

@@ -0,0 +1,142 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/arachnid
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_rails1,
FRAME_rails2,
FRAME_rails3,
FRAME_rails4,
FRAME_rails5,
FRAME_rails6,
FRAME_rails7,
FRAME_rails8,
FRAME_rails9,
FRAME_rails10,
FRAME_rails11,
FRAME_death1,
FRAME_death2,
FRAME_death3,
FRAME_death4,
FRAME_death5,
FRAME_death6,
FRAME_death7,
FRAME_death8,
FRAME_death9,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_melee_atk1,
FRAME_melee_atk2,
FRAME_melee_atk3,
FRAME_melee_atk4,
FRAME_melee_atk5,
FRAME_melee_atk6,
FRAME_melee_atk7,
FRAME_melee_atk8,
FRAME_melee_atk9,
FRAME_melee_atk10,
FRAME_melee_atk11,
FRAME_melee_atk12,
FRAME_pain11,
FRAME_pain12,
FRAME_pain13,
FRAME_pain14,
FRAME_pain15,
FRAME_idle1,
FRAME_idle2,
FRAME_idle3,
FRAME_idle4,
FRAME_idle5,
FRAME_idle6,
FRAME_idle7,
FRAME_idle8,
FRAME_idle9,
FRAME_idle10,
FRAME_idle11,
FRAME_idle12,
FRAME_idle13,
FRAME_walk1,
FRAME_walk2,
FRAME_walk3,
FRAME_walk4,
FRAME_walk5,
FRAME_walk6,
FRAME_walk7,
FRAME_walk8,
FRAME_walk9,
FRAME_walk10,
FRAME_turn1,
FRAME_turn2,
FRAME_turn3,
FRAME_melee_out1,
FRAME_melee_out2,
FRAME_melee_out3,
FRAME_pain21,
FRAME_pain22,
FRAME_pain23,
FRAME_pain24,
FRAME_pain25,
FRAME_pain26,
FRAME_melee_pain1,
FRAME_melee_pain2,
FRAME_melee_pain3,
FRAME_melee_pain4,
FRAME_melee_pain5,
FRAME_melee_pain6,
FRAME_melee_pain7,
FRAME_melee_pain8,
FRAME_melee_pain9,
FRAME_melee_pain10,
FRAME_melee_pain11,
FRAME_melee_pain12,
FRAME_melee_pain13,
FRAME_melee_pain14,
FRAME_melee_pain15,
FRAME_melee_pain16,
FRAME_melee_in1,
FRAME_melee_in2,
FRAME_melee_in3,
FRAME_melee_in4,
FRAME_melee_in5,
FRAME_melee_in6,
FRAME_melee_in7,
FRAME_melee_in8,
FRAME_melee_in9,
FRAME_melee_in10,
FRAME_melee_in11,
FRAME_melee_in12,
FRAME_melee_in13,
FRAME_melee_in14,
FRAME_melee_in15,
FRAME_melee_in16,
FRAME_rails_up1,
FRAME_rails_up2,
FRAME_rails_up3,
FRAME_rails_up4,
FRAME_rails_up5,
FRAME_rails_up6,
FRAME_rails_up7,
FRAME_rails_up8,
FRAME_rails_up9,
FRAME_rails_up10,
FRAME_rails_up11,
FRAME_rails_up12,
FRAME_rails_up13,
FRAME_rails_up14,
FRAME_rails_up15,
FRAME_rails_up16
};
#define MODEL_SCALE 1.000000f

822
rerelease/m_berserk.cpp Normal file
View File

@@ -0,0 +1,822 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
BERSERK
==============================================================================
*/
#include "g_local.h"
#include "m_berserk.h"
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;
MONSTERINFO_SIGHT(berserk_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_SEARCH(berserk_search) (edict_t *self) -> void
{
if (brandom())
gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
void berserk_fidget(edict_t *self);
mframe_t berserk_frames_stand[] = {
{ ai_stand, 0, berserk_fidget },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(berserk_move_stand) = { FRAME_stand1, FRAME_stand5, berserk_frames_stand, nullptr };
MONSTERINFO_STAND(berserk_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &berserk_move_stand);
}
mframe_t berserk_frames_stand_fidget[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(berserk_move_stand_fidget) = { FRAME_standb1, FRAME_standb20, berserk_frames_stand_fidget, berserk_stand };
void berserk_fidget(edict_t *self)
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
return;
else if (self->enemy)
return;
if (frandom() > 0.15f)
return;
M_SetAnimation(self, &berserk_move_stand_fidget);
gi.sound(self, CHAN_WEAPON, sound_idle, 1, ATTN_IDLE, 0);
}
mframe_t berserk_frames_walk[] = {
{ ai_walk, 9.1f },
{ ai_walk, 6.3f },
{ ai_walk, 4.9f },
{ ai_walk, 6.7f, monster_footstep },
{ ai_walk, 6.0f },
{ ai_walk, 8.2f },
{ ai_walk, 7.2f },
{ ai_walk, 6.1f },
{ ai_walk, 4.9f },
{ ai_walk, 4.7f, monster_footstep },
{ ai_walk, 4.7f }
};
MMOVE_T(berserk_move_walk) = { FRAME_walkc1, FRAME_walkc11, berserk_frames_walk, nullptr };
MONSTERINFO_WALK(berserk_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &berserk_move_walk);
}
/*
*****************************
SKIPPED THIS FOR NOW!
*****************************
Running -> Arm raised in air
void() berserk_runb1 =[ $r_att1 , berserk_runb2 ] {ai_run(21);};
void() berserk_runb2 =[ $r_att2 , berserk_runb3 ] {ai_run(11);};
void() berserk_runb3 =[ $r_att3 , berserk_runb4 ] {ai_run(21);};
void() berserk_runb4 =[ $r_att4 , berserk_runb5 ] {ai_run(25);};
void() berserk_runb5 =[ $r_att5 , berserk_runb6 ] {ai_run(18);};
void() berserk_runb6 =[ $r_att6 , berserk_runb7 ] {ai_run(19);};
// running with arm in air : start loop
void() berserk_runb7 =[ $r_att7 , berserk_runb8 ] {ai_run(21);};
void() berserk_runb8 =[ $r_att8 , berserk_runb9 ] {ai_run(11);};
void() berserk_runb9 =[ $r_att9 , berserk_runb10 ] {ai_run(21);};
void() berserk_runb10 =[ $r_att10 , berserk_runb11 ] {ai_run(25);};
void() berserk_runb11 =[ $r_att11 , berserk_runb12 ] {ai_run(18);};
void() berserk_runb12 =[ $r_att12 , berserk_runb7 ] {ai_run(19);};
// running with arm in air : end loop
*/
mframe_t berserk_frames_run1[] = {
{ ai_run, 21 },
{ ai_run, 11, monster_footstep },
{ ai_run, 21 },
{ ai_run, 25, monster_done_dodge },
{ ai_run, 18, monster_footstep },
{ ai_run, 19 }
};
MMOVE_T(berserk_move_run1) = { FRAME_run1, FRAME_run6, berserk_frames_run1, nullptr };
MONSTERINFO_RUN(berserk_run) (edict_t *self) -> void
{
monster_done_dodge(self);
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &berserk_move_stand);
else
M_SetAnimation(self, &berserk_move_run1);
}
void berserk_attack_spike(edict_t *self)
{
constexpr vec3_t aim = { MELEE_DISTANCE, 0, -24 };
if (!fire_hit(self, aim, irandom(5, 11), 80)) // Faster attack -- upwards and backwards
self->monsterinfo.melee_debounce_time = level.time + 1.2_sec;
}
void berserk_swing(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_punch, 1, ATTN_NORM, 0);
}
mframe_t berserk_frames_attack_spike[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, berserk_swing },
{ ai_charge, 0, berserk_attack_spike },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(berserk_move_attack_spike) = { FRAME_att_c1, FRAME_att_c8, berserk_frames_attack_spike, berserk_run };
void berserk_attack_club(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
if (!fire_hit(self, aim, irandom(15, 21), 400)) // Slower attack
self->monsterinfo.melee_debounce_time = level.time + 2.5_sec;
}
mframe_t berserk_frames_attack_club[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, monster_footstep },
{ ai_charge },
{ ai_charge, 0, berserk_swing },
{ ai_charge },
{ ai_charge, 0, berserk_attack_club },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(berserk_move_attack_club) = { FRAME_att_c9, FRAME_att_c20, berserk_frames_attack_club, berserk_run };
/*
============
T_RadiusDamage
============
*/
void T_SlamRadiusDamage(vec3_t point, edict_t *inflictor, edict_t *attacker, float damage, float kick, edict_t *ignore, float radius, mod_t mod)
{
float points;
edict_t *ent = nullptr;
vec3_t v;
vec3_t dir;
while ((ent = findradius(ent, inflictor->s.origin, radius)) != nullptr)
{
if (ent == ignore)
continue;
if (!ent->takedamage)
continue;
if (!CanDamage(ent, inflictor))
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);
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,
DAMAGE_RADIUS, mod);
if (ent->client)
ent->velocity.z = max(270.f, ent->velocity.z);
}
}
static void berserk_attack_slam(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_BERSERK_SLAM);
vec3_t f, r, start;
AngleVectors(self->s.angles, f, r, nullptr);
start = M_ProjectFlashSource(self, { 20.f, -14.3f, -21.f }, f, r);
trace_t tr = gi.traceline(self->s.origin, start, self, MASK_SOLID);
gi.WritePosition(tr.endpos);
gi.WriteDir({ 0.f, 0.f, 1.f });
gi.multicast(tr.endpos, MULTICAST_PHS, false);
self->gravity = 1.0f;
self->velocity = {};
self->flags |= FL_KILL_VELOCITY;
T_SlamRadiusDamage(tr.endpos, self, self, 35, 150.f, self, 275, MOD_UNKNOWN);
}
TOUCH(berserk_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
if (self->health <= 0)
{
self->touch = nullptr;
return;
}
if (self->groundentity)
{
self->s.frame = FRAME_slam18;
if (self->touch)
berserk_attack_slam(self);
self->touch = nullptr;
}
}
static void berserk_high_gravity(edict_t *self)
{
if (self->velocity[2] < 0)
self->gravity = 2.25f * (800.f / level.gravity);
else
self->gravity = 5.25f * (800.f / level.gravity);
}
void berserk_jump_takeoff(edict_t *self)
{
vec3_t forward;
if (!self->enemy)
return;
// immediately turn to where we need to go
float length = (self->s.origin - self->enemy->s.origin).length();
float fwd_speed = length * 1.95f;
vec3_t dir;
PredictAim(self, self->enemy, self->s.origin, fwd_speed, false, 0.f, &dir, nullptr);
self->s.angles[1] = vectoyaw(dir);
AngleVectors(self->s.angles, forward, nullptr, nullptr);
self->s.origin[2] += 1;
self->velocity = forward * fwd_speed;
self->velocity[2] = 450;
self->groundentity = nullptr;
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);
}
void berserk_check_landing(edict_t *self)
{
berserk_high_gravity(self);
if (self->groundentity)
{
self->monsterinfo.attack_finished = 0_ms;
self->monsterinfo.unduck(self);
self->s.frame = FRAME_slam18;
if (self->touch)
{
berserk_attack_slam(self);
self->touch = nullptr;
}
self->flags &= ~FL_KILL_VELOCITY;
return;
}
if (level.time > self->monsterinfo.attack_finished)
self->monsterinfo.nextframe = FRAME_slam2;
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_move, 0, berserk_high_gravity },
{ ai_move, 0, berserk_check_landing },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep }
};
MMOVE_T(berserk_move_attack_strike) = { FRAME_slam1, FRAME_slam23, berserk_frames_attack_strike, berserk_run };
extern const mmove_t berserk_move_run_attack1;
MONSTERINFO_MELEE(berserk_melee) (edict_t *self) -> void
{
if (self->monsterinfo.melee_debounce_time > level.time)
return;
// if we're *almost* ready to land down the hammer from run-attack
// don't switch us
else if (self->monsterinfo.active_move == &berserk_move_run_attack1 && self->s.frame >= FRAME_r_att13)
{
self->monsterinfo.attack_state = AS_STRAIGHT;
self->monsterinfo.attack_finished = 0_ms;
return;
}
monster_done_dodge(self);
if (brandom())
M_SetAnimation(self, &berserk_move_attack_spike);
else
M_SetAnimation(self, &berserk_move_attack_club);
}
static void berserk_run_attack_speed(edict_t *self)
{
if (self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
{
self->monsterinfo.nextframe = self->s.frame + 6;
monster_done_dodge(self);
}
}
static void berserk_run_swing(edict_t *self)
{
berserk_swing(self);
self->monsterinfo.melee_debounce_time = level.time + 0.6_sec;
if (self->monsterinfo.attack_state == AS_SLIDING)
{
self->monsterinfo.attack_state = AS_STRAIGHT;
monster_done_dodge(self);
}
}
mframe_t berserk_frames_run_attack1[] = {
{ ai_run, 21, berserk_run_attack_speed },
{ ai_run, 11, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } },
{ ai_run, 21, berserk_run_attack_speed },
{ ai_run, 25, [](edict_t *self) { berserk_run_attack_speed(self); monster_done_dodge(self); } },
{ ai_run, 18, [](edict_t *self) { berserk_run_attack_speed(self); monster_footstep(self); } },
{ ai_run, 19, berserk_run_attack_speed },
{ ai_run, 21 },
{ ai_run, 11, monster_footstep },
{ ai_run, 21 },
{ ai_run, 25 },
{ ai_run, 18, monster_footstep },
{ ai_run, 19 },
{ ai_run, 21, berserk_run_swing },
{ ai_run, 11, monster_footstep },
{ ai_run, 21 },
{ ai_run, 25 },
{ ai_run, 18, monster_footstep },
{ ai_run, 19, berserk_attack_club }
};
MMOVE_T(berserk_move_run_attack1) = { FRAME_r_att1, FRAME_r_att18, berserk_frames_run_attack1, berserk_run };
MONSTERINFO_ATTACK(berserk_attack) (edict_t *self) -> void
{
if (self->monsterinfo.melee_debounce_time <= level.time && (range_to(self, self->enemy) < MELEE_DISTANCE))
berserk_melee(self);
// only jump if they are far enough away for it to make sense (otherwise
// it gets annoying to have them keep hopping over and over again)
else if (!self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING) && (self->timestamp < level.time && brandom()) && range_to(self, self->enemy) > 150.f)
{
M_SetAnimation(self, &berserk_move_attack_strike);
// don't do this for a while, otherwise we just keep doing it
self->timestamp = level.time + 5_sec;
}
else if (self->monsterinfo.active_move == &berserk_move_run1 && (range_to(self, self->enemy) <= RANGE_NEAR))
{
M_SetAnimation(self, &berserk_move_run_attack1);
self->monsterinfo.nextframe = FRAME_r_att1 + (self->s.frame - FRAME_run1) + 1;
}
}
mframe_t berserk_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(berserk_move_pain1) = { FRAME_painc1, FRAME_painc4, berserk_frames_pain1, berserk_run };
mframe_t berserk_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep }
};
MMOVE_T(berserk_move_pain2) = { FRAME_painb1, FRAME_painb20, berserk_frames_pain2, berserk_run };
extern const mmove_t berserk_move_jump, berserk_move_jump2;
PAIN(berserk_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
// if we're jumping, don't pain
if ((self->monsterinfo.active_move == &berserk_move_jump) ||
(self->monsterinfo.active_move == &berserk_move_jump2) ||
(self->monsterinfo.active_move == &berserk_move_attack_strike))
{
return;
}
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
monster_done_dodge(self);
if ((damage <= 50) || (frandom() < 0.5f))
M_SetAnimation(self, &berserk_move_pain1);
else
M_SetAnimation(self, &berserk_move_pain2);
}
MONSTERINFO_SETSKIN(berserk_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
void berserk_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
monster_dead(self);
}
static void berserk_shrink(edict_t *self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t berserk_frames_death1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move, 0, berserk_shrink },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(berserk_move_death1) = { FRAME_death1, FRAME_death13, berserk_frames_death1, berserk_dead };
mframe_t berserk_frames_death2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, berserk_shrink },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(berserk_move_death2) = { FRAME_deathc1, FRAME_deathc8, berserk_frames_death2, berserk_dead };
DIE(berserk_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
self->s.skinnum = 0;
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 3, "models/objects/gibs/sm_meat/tris.md2" },
{ 1, "models/objects/gibs/gear/tris.md2" },
{ "models/monsters/berserk/gibs/chest.md2", GIB_SKINNED },
{ "models/monsters/berserk/gibs/hammer.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/berserk/gibs/thigh.md2", GIB_SKINNED },
{ "models/monsters/berserk/gibs/head.md2", GIB_HEAD | GIB_SKINNED }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
if (damage >= 50)
M_SetAnimation(self, &berserk_move_death1);
else
M_SetAnimation(self, &berserk_move_death2);
}
//===========
// PGM
void berserk_jump_now(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 100);
self->velocity += (up * 300);
}
void berserk_jump2_now(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 150);
self->velocity += (up * 400);
}
void berserk_jump_wait_land(edict_t *self)
{
if (self->groundentity == nullptr)
{
self->monsterinfo.nextframe = self->s.frame;
if (monster_jump_finished(self))
self->monsterinfo.nextframe = self->s.frame + 1;
}
else
self->monsterinfo.nextframe = self->s.frame + 1;
}
mframe_t berserk_frames_jump[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, berserk_jump_now },
{ ai_move },
{ ai_move },
{ ai_move, 0, berserk_jump_wait_land },
{ ai_move },
{ ai_move }
};
MMOVE_T(berserk_move_jump) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump, berserk_run };
mframe_t berserk_frames_jump2[] = {
{ ai_move, -8 },
{ ai_move, -4 },
{ ai_move, -4 },
{ ai_move, 0, berserk_jump2_now },
{ ai_move },
{ ai_move },
{ ai_move, 0, berserk_jump_wait_land },
{ ai_move },
{ ai_move }
};
MMOVE_T(berserk_move_jump2) = { FRAME_jump1, FRAME_jump9, berserk_frames_jump2, berserk_run };
void berserk_jump(edict_t *self, blocked_jump_result_t result)
{
if (!self->enemy)
return;
if (result == blocked_jump_result_t::JUMP_JUMP_UP)
M_SetAnimation(self, &berserk_move_jump2);
else
M_SetAnimation(self, &berserk_move_jump);
}
MONSTERINFO_BLOCKED(berserk_blocked) (edict_t *self, float dist) -> bool
{
if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
{
if (result != blocked_jump_result_t::JUMP_TURN)
berserk_jump(self, result);
return true;
}
if (blocked_checkplat(self, dist))
return true;
return false;
}
// PGM
//===========
MONSTERINFO_SIDESTEP(berserk_sidestep) (edict_t *self) -> bool
{
// if we're jumping or in long pain, don't dodge
if ((self->monsterinfo.active_move == &berserk_move_jump) ||
(self->monsterinfo.active_move == &berserk_move_jump2) ||
(self->monsterinfo.active_move == &berserk_move_attack_strike) ||
(self->monsterinfo.active_move == &berserk_move_pain2))
return false;
if (self->monsterinfo.active_move != &berserk_move_run1)
M_SetAnimation(self, &berserk_move_run1);
return true;
}
mframe_t berserk_frames_duck[] = {
{ ai_move, 0, monster_duck_down },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_duck_hold },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_duck_up },
{ ai_move },
{ ai_move }
};
MMOVE_T(berserk_move_duck) = { FRAME_duck1, FRAME_duck10, berserk_frames_duck, berserk_run };
mframe_t berserk_frames_duck2[] = {
{ ai_move, 21, monster_duck_down },
{ ai_move, 28 },
{ ai_move, 20 },
{ ai_move, 12, monster_footstep },
{ ai_move, 7 },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move, 0, monster_duck_hold },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0, monster_footstep },
{ ai_move, 0, monster_duck_up },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
};
MMOVE_T(berserk_move_duck2) = { FRAME_fall2, FRAME_fall18, berserk_frames_duck2, berserk_run };
MONSTERINFO_DUCK(berserk_duck) (edict_t *self, gtime_t eta) -> bool
{
// berserk only dives forward, and very rarely
if (frandom() >= 0.05f)
{
return false;
}
// if we're jumping, don't dodge
if ((self->monsterinfo.active_move == &berserk_move_jump) ||
(self->monsterinfo.active_move == &berserk_move_jump2))
{
return false;
}
M_SetAnimation(self, &berserk_move_duck2);
return true;
}
/*QUAKED monster_berserk (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
*/
void SP_monster_berserk(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
// 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");
self->s.modelindex = gi.modelindex("models/monsters/berserk/tris.md2");
gi.modelindex("models/monsters/berserk/gibs/head.md2");
gi.modelindex("models/monsters/berserk/gibs/chest.md2");
gi.modelindex("models/monsters/berserk/gibs/hammer.md2");
gi.modelindex("models/monsters/berserk/gibs/thigh.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 32 };
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->health = 240 * st.health_multiplier;
self->gib_health = -60;
self->mass = 250;
self->pain = berserk_pain;
self->die = berserk_die;
self->monsterinfo.stand = berserk_stand;
self->monsterinfo.walk = berserk_walk;
self->monsterinfo.run = berserk_run;
// pmm
self->monsterinfo.dodge = M_MonsterDodge;
self->monsterinfo.duck = berserk_duck;
self->monsterinfo.unduck = monster_duck_up;
self->monsterinfo.sidestep = berserk_sidestep;
self->monsterinfo.blocked = berserk_blocked; // PGM
// pmm
self->monsterinfo.attack = berserk_attack;
self->monsterinfo.melee = berserk_melee;
self->monsterinfo.sight = berserk_sight;
self->monsterinfo.search = berserk_search;
self->monsterinfo.setskin = berserk_setskin;
M_SetAnimation(self, &berserk_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
self->monsterinfo.combat_style = COMBAT_MELEE;
self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_BERSERK_NOJUMPING);
self->monsterinfo.drop_height = 256;
self->monsterinfo.jump_height = 40;
gi.linkentity(self);
walkmonster_start(self);
}

266
rerelease/m_berserk.h Normal file
View File

@@ -0,0 +1,266 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/berserk
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_stand1,
FRAME_stand2,
FRAME_stand3,
FRAME_stand4,
FRAME_stand5,
FRAME_standb1,
FRAME_standb2,
FRAME_standb3,
FRAME_standb4,
FRAME_standb5,
FRAME_standb6,
FRAME_standb7,
FRAME_standb8,
FRAME_standb9,
FRAME_standb10,
FRAME_standb11,
FRAME_standb12,
FRAME_standb13,
FRAME_standb14,
FRAME_standb15,
FRAME_standb16,
FRAME_standb17,
FRAME_standb18,
FRAME_standb19,
FRAME_standb20,
FRAME_walkc1,
FRAME_walkc2,
FRAME_walkc3,
FRAME_walkc4,
FRAME_walkc5,
FRAME_walkc6,
FRAME_walkc7,
FRAME_walkc8,
FRAME_walkc9,
FRAME_walkc10,
FRAME_walkc11,
FRAME_run1,
FRAME_run2,
FRAME_run3,
FRAME_run4,
FRAME_run5,
FRAME_run6,
FRAME_att_a1,
FRAME_att_a2,
FRAME_att_a3,
FRAME_att_a4,
FRAME_att_a5,
FRAME_att_a6,
FRAME_att_a7,
FRAME_att_a8,
FRAME_att_a9,
FRAME_att_a10,
FRAME_att_a11,
FRAME_att_a12,
FRAME_att_a13,
FRAME_att_b1,
FRAME_att_b2,
FRAME_att_b3,
FRAME_att_b4,
FRAME_att_b5,
FRAME_att_b6,
FRAME_att_b7,
FRAME_att_b8,
FRAME_att_b9,
FRAME_att_b10,
FRAME_att_b11,
FRAME_att_b12,
FRAME_att_b13,
FRAME_att_b14,
FRAME_att_b15,
FRAME_att_b16,
FRAME_att_b17,
FRAME_att_b18,
FRAME_att_b19,
FRAME_att_b20,
FRAME_att_b21,
FRAME_att_c1,
FRAME_att_c2,
FRAME_att_c3,
FRAME_att_c4,
FRAME_att_c5,
FRAME_att_c6,
FRAME_att_c7,
FRAME_att_c8,
FRAME_att_c9,
FRAME_att_c10,
FRAME_att_c11,
FRAME_att_c12,
FRAME_att_c13,
FRAME_att_c14,
FRAME_att_c15,
FRAME_att_c16,
FRAME_att_c17,
FRAME_att_c18,
FRAME_att_c19,
FRAME_att_c20,
FRAME_att_c21,
FRAME_att_c22,
FRAME_att_c23,
FRAME_att_c24,
FRAME_att_c25,
FRAME_att_c26,
FRAME_att_c27,
FRAME_att_c28,
FRAME_att_c29,
FRAME_att_c30,
FRAME_att_c31,
FRAME_att_c32,
FRAME_att_c33,
FRAME_att_c34,
FRAME_r_att1,
FRAME_r_att2,
FRAME_r_att3,
FRAME_r_att4,
FRAME_r_att5,
FRAME_r_att6,
FRAME_r_att7,
FRAME_r_att8,
FRAME_r_att9,
FRAME_r_att10,
FRAME_r_att11,
FRAME_r_att12,
FRAME_r_att13,
FRAME_r_att14,
FRAME_r_att15,
FRAME_r_att16,
FRAME_r_att17,
FRAME_r_att18,
FRAME_r_attb1,
FRAME_r_attb2,
FRAME_r_attb3,
FRAME_r_attb4,
FRAME_r_attb5,
FRAME_r_attb6,
FRAME_r_attb7,
FRAME_r_attb8,
FRAME_r_attb9,
FRAME_r_attb10,
FRAME_r_attb11,
FRAME_r_attb12,
FRAME_r_attb13,
FRAME_r_attb14,
FRAME_r_attb15,
FRAME_r_attb16,
FRAME_r_attb17,
FRAME_r_attb18,
FRAME_slam1,
FRAME_slam2,
FRAME_slam3,
FRAME_slam4,
FRAME_slam5,
FRAME_slam6,
FRAME_slam7,
FRAME_slam8,
FRAME_slam9,
FRAME_slam10,
FRAME_slam11,
FRAME_slam12,
FRAME_slam13,
FRAME_slam14,
FRAME_slam15,
FRAME_slam16,
FRAME_slam17,
FRAME_slam18,
FRAME_slam19,
FRAME_slam20,
FRAME_slam21,
FRAME_slam22,
FRAME_slam23,
FRAME_duck1,
FRAME_duck2,
FRAME_duck3,
FRAME_duck4,
FRAME_duck5,
FRAME_duck6,
FRAME_duck7,
FRAME_duck8,
FRAME_duck9,
FRAME_duck10,
FRAME_fall1,
FRAME_fall2,
FRAME_fall3,
FRAME_fall4,
FRAME_fall5,
FRAME_fall6,
FRAME_fall7,
FRAME_fall8,
FRAME_fall9,
FRAME_fall10,
FRAME_fall11,
FRAME_fall12,
FRAME_fall13,
FRAME_fall14,
FRAME_fall15,
FRAME_fall16,
FRAME_fall17,
FRAME_fall18,
FRAME_fall19,
FRAME_fall20,
FRAME_painc1,
FRAME_painc2,
FRAME_painc3,
FRAME_painc4,
FRAME_painb1,
FRAME_painb2,
FRAME_painb3,
FRAME_painb4,
FRAME_painb5,
FRAME_painb6,
FRAME_painb7,
FRAME_painb8,
FRAME_painb9,
FRAME_painb10,
FRAME_painb11,
FRAME_painb12,
FRAME_painb13,
FRAME_painb14,
FRAME_painb15,
FRAME_painb16,
FRAME_painb17,
FRAME_painb18,
FRAME_painb19,
FRAME_painb20,
FRAME_death1,
FRAME_death2,
FRAME_death3,
FRAME_death4,
FRAME_death5,
FRAME_death6,
FRAME_death7,
FRAME_death8,
FRAME_death9,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_deathc1,
FRAME_deathc2,
FRAME_deathc3,
FRAME_deathc4,
FRAME_deathc5,
FRAME_deathc6,
FRAME_deathc7,
FRAME_deathc8,
// PGM
FRAME_jump1,
FRAME_jump2,
FRAME_jump3,
FRAME_jump4,
FRAME_jump5,
FRAME_jump6,
FRAME_jump7,
FRAME_jump8,
FRAME_jump9
// PGM
};
constexpr float MODEL_SCALE = 1.000000f;

768
rerelease/m_boss2.cpp Normal file
View File

@@ -0,0 +1,768 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
boss2
==============================================================================
*/
#include "g_local.h"
#include "m_boss2.h"
#include "m_flash.h"
// [Paril-KEX]
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;
MONSTERINFO_SEARCH(boss2_search) (edict_t *self) -> void
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NONE, 0);
}
void boss2_run(edict_t *self);
void boss2_dead(edict_t *self);
void boss2_attack_mg(edict_t *self);
void boss2_reattack_mg(edict_t *self);
constexpr int32_t BOSS2_ROCKET_SPEED = 750;
void Boss2PredictiveRocket(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
vec3_t dir;
AngleVectors(self->s.angles, forward, right, nullptr);
// 1
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right);
PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, -0.10f, &dir, nullptr);
monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1);
// 2
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right);
PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, -0.05f, &dir, nullptr);
monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_2);
// 3
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right);
PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, 0.05f, &dir, nullptr);
monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_3);
// 4
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right);
PredictAim(self, self->enemy, start, BOSS2_ROCKET_SPEED, false, 0.10f, &dir, nullptr);
monster_fire_rocket(self, start, dir, 50, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_4);
}
void Boss2Rocket(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
vec3_t dir;
vec3_t vec;
if (self->enemy)
{
if (self->enemy->client && frandom() < 0.9f)
{
Boss2PredictiveRocket(self);
return;
}
}
AngleVectors(self->s.angles, forward, right, nullptr);
// 1
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right);
vec = self->enemy->s.origin;
vec[2] -= 15;
dir = vec - start;
dir.normalize();
dir += (right * 0.4f);
dir.normalize();
monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_1);
// 2
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_2], forward, right);
vec = self->enemy->s.origin;
dir = vec - start;
dir.normalize();
dir += (right * 0.025f);
dir.normalize();
monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_2);
// 3
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_3], forward, right);
vec = self->enemy->s.origin;
dir = vec - start;
dir.normalize();
dir += (right * -0.025f);
dir.normalize();
monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_3);
// 4
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_4], forward, right);
vec = self->enemy->s.origin;
vec[2] -= 15;
dir = vec - start;
dir.normalize();
dir += (right * -0.4f);
dir.normalize();
monster_fire_rocket(self, start, dir, 50, 500, MZ2_BOSS2_ROCKET_4);
}
void Boss2Rocket64(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
vec3_t dir;
vec3_t vec;
float time, dist;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_ROCKET_1], forward, right);
float scale = self->s.scale ? self->s.scale : 1;
start[2] += 10.f * scale;
start -= right * 2.f * scale;
start -= right * ((self->count++ % 4) * 8.f * scale);
if (self->enemy && self->enemy->client && frandom() < 0.9f)
{
// 1
dir = self->enemy->s.origin - start;
dist = dir.length();
time = dist / BOSS2_ROCKET_SPEED;
vec = self->enemy->s.origin + (self->enemy->velocity * (time - 0.3f));
}
else
{
// 1
vec = self->enemy->s.origin;
vec[2] -= 15;
}
dir = vec - start;
dir.normalize();
monster_fire_rocket(self, start, dir, 35, BOSS2_ROCKET_SPEED, MZ2_BOSS2_ROCKET_1);
}
void boss2_firebullet_right(edict_t *self)
{
vec3_t forward, right, start;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_R1], forward, right);
PredictAim(self, self->enemy, start, 0, true, -0.2f, &forward, nullptr);
monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_R1);
}
void boss2_firebullet_left(edict_t *self)
{
vec3_t forward, right, start;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_BOSS2_MACHINEGUN_L1], forward, right);
PredictAim(self, self->enemy, start, 0, true, -0.2f, &forward, nullptr);
monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, DEFAULT_BULLET_VSPREAD, MZ2_BOSS2_MACHINEGUN_L1);
}
void Boss2MachineGun(edict_t *self)
{
boss2_firebullet_left(self);
boss2_firebullet_right(self);
}
mframe_t boss2_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(boss2_move_stand) = { FRAME_stand30, FRAME_stand50, boss2_frames_stand, nullptr };
mframe_t boss2_frames_walk[] = {
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 },
{ ai_walk, 10 }
};
MMOVE_T(boss2_move_walk) = { FRAME_walk1, FRAME_walk20, boss2_frames_walk, nullptr };
mframe_t boss2_frames_run[] = {
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 }
};
MMOVE_T(boss2_move_run) = { FRAME_walk1, FRAME_walk20, boss2_frames_run, nullptr };
mframe_t boss2_frames_attack_pre_mg[] = {
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2, boss2_attack_mg }
};
MMOVE_T(boss2_move_attack_pre_mg) = { FRAME_attack1, FRAME_attack9, boss2_frames_attack_pre_mg, nullptr };
// Loop this
mframe_t boss2_frames_attack_mg[] = {
{ ai_charge, 2, Boss2MachineGun },
{ ai_charge, 2, Boss2MachineGun },
{ ai_charge, 2, Boss2MachineGun },
{ ai_charge, 2, Boss2MachineGun },
{ ai_charge, 2, Boss2MachineGun },
{ ai_charge, 2, boss2_reattack_mg }
};
MMOVE_T(boss2_move_attack_mg) = { FRAME_attack10, FRAME_attack15, boss2_frames_attack_mg, nullptr };
// [Paril-KEX]
void Boss2HyperBlaster(edict_t *self)
{
vec3_t forward, right, target;
vec3_t start;
monster_muzzleflash_id_t id = (self->s.frame & 1) ? MZ2_BOSS2_MACHINEGUN_L2 : MZ2_BOSS2_MACHINEGUN_R2;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right);
target = self->enemy->s.origin;
target[2] += self->enemy->viewheight;
forward = target - start;
forward.normalize();
monster_fire_blaster(self, start, forward, 2, 1000, id, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
}
mframe_t boss2_frames_attack_hb[] = {
{ ai_charge, 2, Boss2HyperBlaster },
{ ai_charge, 2, Boss2HyperBlaster },
{ ai_charge, 2, Boss2HyperBlaster },
{ ai_charge, 2, Boss2HyperBlaster },
{ ai_charge, 2, Boss2HyperBlaster },
{ ai_charge, 2, [](edict_t *self) -> void { Boss2HyperBlaster(self); boss2_reattack_mg(self); } }
};
MMOVE_T(boss2_move_attack_hb) = { FRAME_attack10, FRAME_attack15, boss2_frames_attack_hb, nullptr };
mframe_t boss2_frames_attack_post_mg[] = {
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 }
};
MMOVE_T(boss2_move_attack_post_mg) = { FRAME_attack16, FRAME_attack19, boss2_frames_attack_post_mg, boss2_run };
mframe_t boss2_frames_attack_rocket[] = {
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_move, -5, Boss2Rocket },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 }
};
MMOVE_T(boss2_move_attack_rocket) = { FRAME_attack20, FRAME_attack40, boss2_frames_attack_rocket, boss2_run };
// [Paril-KEX] n64 rocket behavior
mframe_t boss2_frames_attack_rocket2[] = {
{ ai_charge, 2, Boss2Rocket64 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2, Boss2Rocket64 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2, Boss2Rocket64 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2, Boss2Rocket64 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2, Boss2Rocket64 },
{ ai_charge, 2 },
{ ai_charge, 2 },
{ ai_charge, 2 }
};
MMOVE_T(boss2_move_attack_rocket2) = { FRAME_attack20, FRAME_attack39, boss2_frames_attack_rocket2, boss2_run };
mframe_t boss2_frames_pain_heavy[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(boss2_move_pain_heavy) = { FRAME_pain2, FRAME_pain19, boss2_frames_pain_heavy, boss2_run };
mframe_t boss2_frames_pain_light[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(boss2_move_pain_light) = { FRAME_pain20, FRAME_pain23, boss2_frames_pain_light, boss2_run };
static void boss2_shrink(edict_t *self)
{
self->maxs.z = 50.f;
gi.linkentity(self);
}
mframe_t boss2_frames_death[] = {
{ ai_move, 0, BossExplode },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, boss2_shrink },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(boss2_move_death) = { FRAME_death2, FRAME_death50, boss2_frames_death, boss2_dead };
MONSTERINFO_STAND(boss2_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &boss2_move_stand);
}
MONSTERINFO_RUN(boss2_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &boss2_move_stand);
else
M_SetAnimation(self, &boss2_move_run);
}
MONSTERINFO_WALK(boss2_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &boss2_move_walk);
}
MONSTERINFO_ATTACK(boss2_attack) (edict_t *self) -> void
{
vec3_t vec;
float range;
vec = self->enemy->s.origin - self->s.origin;
range = vec.length();
if (range <= 125 || frandom() <= 0.6f)
M_SetAnimation(self, self->spawnflags.has(SPAWNFLAG_BOSS2_N64) ? &boss2_move_attack_hb : &boss2_move_attack_pre_mg);
else
M_SetAnimation(self, self->spawnflags.has(SPAWNFLAG_BOSS2_N64) ? &boss2_move_attack_rocket2 : &boss2_move_attack_rocket);
}
void boss2_attack_mg(edict_t *self)
{
M_SetAnimation(self, self->spawnflags.has(SPAWNFLAG_BOSS2_N64) ? &boss2_move_attack_hb : &boss2_move_attack_mg);
}
void boss2_reattack_mg(edict_t *self)
{
if (infront(self, self->enemy) && frandom() <= 0.7f)
boss2_attack_mg(self);
else
M_SetAnimation(self, &boss2_move_attack_post_mg);
}
PAIN(boss2_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
// American wanted these at no attenuation
if (damage < 10)
gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NONE, 0);
else if (damage < 30)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NONE, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NONE, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (damage < 10)
M_SetAnimation(self, &boss2_move_pain_light);
else if (damage < 30)
M_SetAnimation(self, &boss2_move_pain_light);
else
M_SetAnimation(self, &boss2_move_pain_heavy);
}
MONSTERINFO_SETSKIN(boss2_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
static void boss2_gib(edict_t *self)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1_BIG);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
self->s.sound = 0;
self->s.skinnum /= 2;
self->gravityVector.z = -1.0f;
ThrowGibs(self, 500, {
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
{ "models/monsters/boss2/gibs/chest.md2", GIB_SKINNED },
{ 2, "models/monsters/boss2/gibs/chaingun.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/cpu.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/engine.md2", GIB_SKINNED },
{ "models/monsters/boss2/gibs/rocket.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/spine.md2", GIB_SKINNED },
{ 2, "models/monsters/boss2/gibs/wing.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/larm.md2", 2.0f, GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/rarm.md2", 2.0f, GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/larm.md2", 1.35f, GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/rarm.md2", 1.35f, GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss2/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD }
});
}
void boss2_dead(edict_t *self)
{
// no blowy on deady
if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD))
{
self->deadflag = false;
self->takedamage = true;
return;
}
boss2_gib(self);
}
DIE(boss2_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD))
{
// check for gib
if (M_CheckGib(self, mod))
{
boss2_gib(self);
self->deadflag = true;
return;
}
if (self->deadflag)
return;
}
else
{
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
self->deadflag = true;
self->takedamage = false;
self->count = 0;
self->velocity = {};
self->gravityVector.z *= 0.30f;
}
M_SetAnimation(self, &boss2_move_death);
}
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;
}
/*QUAKED monster_boss2 (1 .5 0) (-56 -56 0) (56 56 80) Ambush Trigger_Spawn Sight Hyperblaster
*/
void SP_monster_boss2(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
gi.soundindex("tank/rocket.wav");
if (self->spawnflags.has(SPAWNFLAG_BOSS2_N64))
gi.soundindex("flyer/flyatck3.wav");
else
gi.soundindex("infantry/infatck1.wav");
self->monsterinfo.weapon_sound = gi.soundindex("bosshovr/bhvengn1.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/boss2/tris.md2");
gi.modelindex("models/monsters/boss2/gibs/chaingun.md2");
gi.modelindex("models/monsters/boss2/gibs/chest.md2");
gi.modelindex("models/monsters/boss2/gibs/cpu.md2");
gi.modelindex("models/monsters/boss2/gibs/engine.md2");
gi.modelindex("models/monsters/boss2/gibs/head.md2");
gi.modelindex("models/monsters/boss2/gibs/larm.md2");
gi.modelindex("models/monsters/boss2/gibs/rarm.md2");
gi.modelindex("models/monsters/boss2/gibs/rocket.md2");
gi.modelindex("models/monsters/boss2/gibs/spine.md2");
gi.modelindex("models/monsters/boss2/gibs/wing.md2");
self->mins = { -56, -56, 0 };
self->maxs = { 56, 56, 80 };
self->health = 2000 * st.health_multiplier;
self->gib_health = -200;
self->mass = 1000;
self->yaw_speed = 50;
self->flags |= FL_IMMUNE_LASER;
self->pain = boss2_pain;
self->die = boss2_die;
self->monsterinfo.stand = boss2_stand;
self->monsterinfo.walk = boss2_walk;
self->monsterinfo.run = boss2_run;
self->monsterinfo.attack = boss2_attack;
self->monsterinfo.search = boss2_search;
self->monsterinfo.checkattack = Boss2_CheckAttack;
self->monsterinfo.setskin = boss2_setskin;
gi.linkentity(self);
M_SetAnimation(self, &boss2_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
// [Paril-KEX]
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
flymonster_start(self);
}

192
rerelease/m_boss2.h Normal file
View File

@@ -0,0 +1,192 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/boss2
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_stand46,
FRAME_stand47,
FRAME_stand48,
FRAME_stand49,
FRAME_stand50,
FRAME_stand1,
FRAME_stand2,
FRAME_stand3,
FRAME_stand4,
FRAME_stand5,
FRAME_stand6,
FRAME_stand7,
FRAME_stand8,
FRAME_stand9,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_walk1,
FRAME_walk2,
FRAME_walk3,
FRAME_walk4,
FRAME_walk5,
FRAME_walk6,
FRAME_walk7,
FRAME_walk8,
FRAME_walk9,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_attack1,
FRAME_attack2,
FRAME_attack3,
FRAME_attack4,
FRAME_attack5,
FRAME_attack6,
FRAME_attack7,
FRAME_attack8,
FRAME_attack9,
FRAME_attack10,
FRAME_attack11,
FRAME_attack12,
FRAME_attack13,
FRAME_attack14,
FRAME_attack15,
FRAME_attack16,
FRAME_attack17,
FRAME_attack18,
FRAME_attack19,
FRAME_attack20,
FRAME_attack21,
FRAME_attack22,
FRAME_attack23,
FRAME_attack24,
FRAME_attack25,
FRAME_attack26,
FRAME_attack27,
FRAME_attack28,
FRAME_attack29,
FRAME_attack30,
FRAME_attack31,
FRAME_attack32,
FRAME_attack33,
FRAME_attack34,
FRAME_attack35,
FRAME_attack36,
FRAME_attack37,
FRAME_attack38,
FRAME_attack39,
FRAME_attack40,
FRAME_pain2,
FRAME_pain3,
FRAME_pain4,
FRAME_pain5,
FRAME_pain6,
FRAME_pain7,
FRAME_pain8,
FRAME_pain9,
FRAME_pain10,
FRAME_pain11,
FRAME_pain12,
FRAME_pain13,
FRAME_pain14,
FRAME_pain15,
FRAME_pain16,
FRAME_pain17,
FRAME_pain18,
FRAME_pain19,
FRAME_pain20,
FRAME_pain21,
FRAME_pain22,
FRAME_pain23,
FRAME_death2,
FRAME_death3,
FRAME_death4,
FRAME_death5,
FRAME_death6,
FRAME_death7,
FRAME_death8,
FRAME_death9,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_death23,
FRAME_death24,
FRAME_death25,
FRAME_death26,
FRAME_death27,
FRAME_death28,
FRAME_death29,
FRAME_death30,
FRAME_death31,
FRAME_death32,
FRAME_death33,
FRAME_death34,
FRAME_death35,
FRAME_death36,
FRAME_death37,
FRAME_death38,
FRAME_death39,
FRAME_death40,
FRAME_death41,
FRAME_death42,
FRAME_death43,
FRAME_death44,
FRAME_death45,
FRAME_death46,
FRAME_death47,
FRAME_death48,
FRAME_death49,
FRAME_death50
};
constexpr float MODEL_SCALE = 1.000000f;

61
rerelease/m_boss3.cpp Normal file
View File

@@ -0,0 +1,61 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
boss3
==============================================================================
*/
#include "g_local.h"
#include "m_boss32.h"
USE(Use_Boss3) (edict_t *self, edict_t *other, edict_t *activator) -> void
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_BOSSTPORT);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
// just hide, don't kill ent so we can trigger it again
self->svflags |= SVF_NOCLIENT;
self->solid = SOLID_NOT;
}
THINK(Think_Boss3Stand) (edict_t *self) -> void
{
if (self->s.frame == FRAME_stand260)
self->s.frame = FRAME_stand201;
else
self->s.frame++;
self->nextthink = level.time + 10_hz;
}
/*QUAKED monster_boss3_stand (1 .5 0) (-32 -32 0) (32 32 90)
Just stands and cycles in one place until targeted, then teleports away.
*/
void SP_monster_boss3_stand(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->model = "models/monsters/boss3/rider/tris.md2";
self->s.modelindex = gi.modelindex(self->model);
self->s.frame = FRAME_stand201;
gi.soundindex("misc/bigtele.wav");
self->mins = { -32, -32, 0 };
self->maxs = { 32, 32, 90 };
self->use = Use_Boss3;
self->think = Think_Boss3Stand;
self->nextthink = level.time + FRAME_TIME_S;
gi.linkentity(self);
}

720
rerelease/m_boss31.cpp Normal file
View File

@@ -0,0 +1,720 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
jorg
==============================================================================
*/
#include "g_local.h"
#include "m_boss31.h"
#include "m_flash.h"
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;
void MakronToss(edict_t *self);
void jorg_attack1_end_sound(edict_t *self)
{
if (self->monsterinfo.weapon_sound)
{
gi.sound(self, CHAN_WEAPON, sound_attack1_end, 1, ATTN_NORM, 0);
self->monsterinfo.weapon_sound = 0;
}
}
MONSTERINFO_SEARCH(jorg_search) (edict_t *self) -> void
{
float r;
r = frandom();
if (r <= 0.3f)
gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
else if (r <= 0.6f)
gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_search3, 1, ATTN_NORM, 0);
}
void jorg_dead(edict_t *self);
void jorgBFG(edict_t *self);
void jorg_firebullet(edict_t *self);
void jorg_reattack1(edict_t *self);
void jorg_attack1(edict_t *self);
void jorg_idle(edict_t *self);
void jorg_step_left(edict_t *self);
void jorg_step_right(edict_t *self);
void jorg_death_hit(edict_t *self);
//
// stand
//
mframe_t jorg_frames_stand[] = {
{ ai_stand, 0, jorg_idle },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 10
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 20
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 30
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 19 },
{ ai_stand, 11, jorg_step_left },
{ ai_stand },
{ ai_stand },
{ ai_stand, 6 },
{ ai_stand, 9, jorg_step_right },
{ ai_stand }, // 40
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, -2, nullptr },
{ ai_stand, -17, jorg_step_left },
{ ai_stand },
{ ai_stand, -12 }, // 50
{ ai_stand, -14, jorg_step_right } // 51
};
MMOVE_T(jorg_move_stand) = { FRAME_stand01, FRAME_stand51, jorg_frames_stand, nullptr };
void jorg_idle (edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_NORM, 0);
}
void jorg_death_hit(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_death_hit, 1, ATTN_NORM, 0);
}
void jorg_step_left(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0);
}
void jorg_step_right(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0);
}
MONSTERINFO_STAND(jorg_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &jorg_move_stand);
jorg_attack1_end_sound(self);
}
mframe_t jorg_frames_run[] = {
{ ai_run, 17, jorg_step_left },
{ ai_run },
{ ai_run },
{ ai_run },
{ ai_run, 12 },
{ ai_run, 8 },
{ ai_run, 10 },
{ ai_run, 33, jorg_step_right },
{ ai_run },
{ ai_run },
{ ai_run },
{ ai_run, 9 },
{ ai_run, 9 },
{ ai_run, 9 }
};
MMOVE_T(jorg_move_run) = { FRAME_walk06, FRAME_walk19, jorg_frames_run, nullptr };
//
// walk
//
#if 0
mframe_t jorg_frames_start_walk[] = {
{ ai_walk, 5 },
{ ai_walk, 6 },
{ ai_walk, 7 },
{ ai_walk, 9 },
{ ai_walk, 15 }
};
MMOVE_T(jorg_move_start_walk) = { FRAME_walk01, FRAME_walk05, jorg_frames_start_walk, nullptr };
#endif
mframe_t jorg_frames_walk[] = {
{ ai_walk, 17 },
{ ai_walk },
{ ai_walk },
{ ai_walk },
{ ai_walk, 12 },
{ ai_walk, 8 },
{ ai_walk, 10 },
{ ai_walk, 33 },
{ ai_walk },
{ ai_walk },
{ ai_walk },
{ ai_walk, 9 },
{ ai_walk, 9 },
{ ai_walk, 9 }
};
MMOVE_T(jorg_move_walk) = { FRAME_walk06, FRAME_walk19, jorg_frames_walk, nullptr };
#if 0
mframe_t jorg_frames_end_walk[] = {
{ ai_walk, 11 },
{ ai_walk },
{ ai_walk },
{ ai_walk },
{ ai_walk, 8 },
{ ai_walk, -8 }
};
MMOVE_T(jorg_move_end_walk) = { FRAME_walk20, FRAME_walk25, jorg_frames_end_walk, nullptr };
#endif
MONSTERINFO_WALK(jorg_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &jorg_move_walk);
}
MONSTERINFO_RUN(jorg_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &jorg_move_stand);
else
M_SetAnimation(self, &jorg_move_run);
jorg_attack1_end_sound(self);
}
mframe_t jorg_frames_pain3[] = {
{ ai_move, -28 },
{ ai_move, -6 },
{ ai_move, -3, jorg_step_left },
{ ai_move, -9 },
{ ai_move, 0, jorg_step_right },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -7 },
{ ai_move, 1 },
{ ai_move, -11 },
{ ai_move, -4 },
{ ai_move },
{ ai_move },
{ ai_move, 10 },
{ ai_move, 11 },
{ ai_move },
{ ai_move, 10 },
{ ai_move, 3 },
{ ai_move, 10 },
{ ai_move, 7, jorg_step_left },
{ ai_move, 17 },
{ ai_move },
{ ai_move },
{ ai_move, 0, jorg_step_right }
};
MMOVE_T(jorg_move_pain3) = { FRAME_pain301, FRAME_pain325, jorg_frames_pain3, jorg_run };
mframe_t jorg_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(jorg_move_pain2) = { FRAME_pain201, FRAME_pain203, jorg_frames_pain2, jorg_run };
mframe_t jorg_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(jorg_move_pain1) = { FRAME_pain101, FRAME_pain103, jorg_frames_pain1, jorg_run };
mframe_t jorg_frames_death1[] = {
{ ai_move, 0, BossExplode },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -2 },
{ ai_move, -5 },
{ ai_move, -8 },
{ ai_move, -15, jorg_step_left },
{ ai_move }, // 10
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -11 },
{ ai_move, -25 },
{ ai_move, -10, jorg_step_right },
{ ai_move },
{ ai_move }, // 20
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -21 },
{ ai_move, -10 },
{ ai_move, -16, jorg_step_left },
{ ai_move },
{ ai_move },
{ ai_move }, // 30
{ ai_move },
{ ai_move },
{ ai_move, 22 },
{ ai_move, 33, jorg_step_left },
{ ai_move },
{ ai_move },
{ ai_move, 28 },
{ ai_move, 28, jorg_step_right },
{ ai_move },
{ ai_move }, // 40
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -19 },
{ ai_move, 0, jorg_death_hit },
{ ai_move },
{ ai_move } // 50
};
MMOVE_T(jorg_move_death) = { FRAME_death01, FRAME_death50, jorg_frames_death1, jorg_dead };
mframe_t jorg_frames_attack2[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, jorgBFG },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(jorg_move_attack2) = { FRAME_attak201, FRAME_attak213, jorg_frames_attack2, jorg_run };
mframe_t jorg_frames_start_attack1[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(jorg_move_start_attack1) = { FRAME_attak101, FRAME_attak108, jorg_frames_start_attack1, jorg_attack1 };
mframe_t jorg_frames_attack1[] = {
{ ai_charge, 0, jorg_firebullet },
{ ai_charge, 0, jorg_firebullet },
{ ai_charge, 0, jorg_firebullet },
{ ai_charge, 0, jorg_firebullet },
{ ai_charge, 0, jorg_firebullet },
{ ai_charge, 0, jorg_firebullet }
};
MMOVE_T(jorg_move_attack1) = { FRAME_attak109, FRAME_attak114, jorg_frames_attack1, jorg_reattack1 };
mframe_t jorg_frames_end_attack1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(jorg_move_end_attack1) = { FRAME_attak115, FRAME_attak118, jorg_frames_end_attack1, jorg_run };
void jorg_reattack1(edict_t *self)
{
if (visible(self, self->enemy))
{
if (frandom() < 0.9f)
M_SetAnimation(self, &jorg_move_attack1);
else
{
M_SetAnimation(self, &jorg_move_end_attack1);
jorg_attack1_end_sound(self);
}
}
else
{
M_SetAnimation(self, &jorg_move_end_attack1);
jorg_attack1_end_sound(self);
}
}
void jorg_attack1(edict_t *self)
{
M_SetAnimation(self, &jorg_move_attack1);
}
PAIN(jorg_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->pain_debounce_time)
return;
// Lessen the chance of him going into his pain frames if he takes little damage
if (mod.id != MOD_CHAINFIST)
{
if (damage <= 40)
if (frandom() <= 0.6f)
return;
/*
If he's entering his attack1 or using attack1, lessen the chance of him
going into pain
*/
if ((self->s.frame >= FRAME_attak101) && (self->s.frame <= FRAME_attak108))
if (frandom() <= 0.005f)
return;
if ((self->s.frame >= FRAME_attak109) && (self->s.frame <= FRAME_attak114))
if (frandom() <= 0.00005f)
return;
if ((self->s.frame >= FRAME_attak201) && (self->s.frame <= FRAME_attak208))
if (frandom() <= 0.005f)
return;
}
self->pain_debounce_time = level.time + 3_sec;
bool do_pain3 = false;
if (damage > 50)
{
if (damage <= 100)
{
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
}
else
{
if (frandom() <= 0.3f)
{
do_pain3 = true;
gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
}
}
}
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
jorg_attack1_end_sound(self);
if (damage <= 50)
M_SetAnimation(self, &jorg_move_pain1);
else if (damage <= 100)
M_SetAnimation(self, &jorg_move_pain2);
else if (do_pain3)
M_SetAnimation(self, &jorg_move_pain3);
}
MONSTERINFO_SETSKIN(jorg_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
void jorgBFG(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
vec3_t dir;
vec3_t vec;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_JORG_BFG_1], forward, right);
vec = self->enemy->s.origin;
vec[2] += self->enemy->viewheight;
dir = vec - start;
dir.normalize();
gi.sound(self, CHAN_WEAPON, sound_bfg_fire, 1, ATTN_NORM, 0);
monster_fire_bfg(self, start, dir, 50, 300, 100, 200, MZ2_JORG_BFG_1);
}
void jorg_firebullet_right(edict_t *self)
{
vec3_t forward, right, start;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_JORG_MACHINEGUN_R1], forward, right);
PredictAim(self, self->enemy, start, 0, false, -0.2f, &forward, nullptr);
monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_R1);
}
void jorg_firebullet_left(edict_t *self)
{
vec3_t forward, right, start;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_JORG_MACHINEGUN_L1], forward, right);
PredictAim(self, self->enemy, start, 0, false, 0.2f, &forward, nullptr);
monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MZ2_JORG_MACHINEGUN_L1);
}
void jorg_firebullet(edict_t *self)
{
jorg_firebullet_left(self);
jorg_firebullet_right(self);
};
MONSTERINFO_ATTACK(jorg_attack) (edict_t *self) -> void
{
if (frandom() <= 0.75f)
{
gi.sound(self, CHAN_WEAPON, sound_attack1, 1, ATTN_NORM, 0);
self->monsterinfo.weapon_sound = gi.soundindex("boss3/w_loop.wav");
M_SetAnimation(self, &jorg_move_start_attack1);
}
else
{
gi.sound(self, CHAN_VOICE, sound_attack2, 1, ATTN_NORM, 0);
M_SetAnimation(self, &jorg_move_attack2);
}
}
void jorg_dead(edict_t *self)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1_BIG);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
self->s.sound = 0;
self->s.skinnum /= 2;
ThrowGibs(self, 500, {
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
{ "models/monsters/boss3/jorg/gibs/chest.md2", GIB_SKINNED },
{ 2, "models/monsters/boss3/jorg/gibs/foot.md2", GIB_SKINNED },
{ 2, "models/monsters/boss3/jorg/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT },
{ 2, "models/monsters/boss3/jorg/gibs/thigh.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss3/jorg/gibs/spine.md2", GIB_SKINNED | GIB_UPRIGHT },
{ 4, "models/monsters/boss3/jorg/gibs/tube.md2", GIB_SKINNED },
{ 6, "models/monsters/boss3/jorg/gibs/spike.md2", GIB_SKINNED },
{ "models/monsters/boss3/jorg/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD }
});
MakronToss(self);
}
DIE(jorg_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
jorg_attack1_end_sound(self);
self->deadflag = true;
self->takedamage = false;
self->count = 0;
M_SetAnimation(self, &jorg_move_death);
}
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;
}
void MakronPrecache();
/*QUAKED monster_jorg (1 .5 0) (-80 -80 0) (90 90 140) Ambush Trigger_Spawn Sight
*/
void SP_monster_jorg(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
MakronPrecache();
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/boss3/jorg/tris.md2");
self->s.modelindex2 = gi.modelindex("models/monsters/boss3/rider/tris.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/chest.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/foot.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/gun.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/head.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/spike.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/spine.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/thigh.md2");
gi.modelindex("models/monsters/boss3/jorg/gibs/tube.md2");
self->mins = { -80, -80, 0 };
self->maxs = { 80, 80, 140 };
self->health = 8000 * st.health_multiplier;
self->gib_health = -2000;
self->mass = 1000;
self->pain = jorg_pain;
self->die = jorg_die;
self->monsterinfo.stand = jorg_stand;
self->monsterinfo.walk = jorg_walk;
self->monsterinfo.run = jorg_run;
self->monsterinfo.dodge = nullptr;
self->monsterinfo.attack = jorg_attack;
self->monsterinfo.search = jorg_search;
self->monsterinfo.melee = nullptr;
self->monsterinfo.sight = nullptr;
self->monsterinfo.checkattack = Jorg_CheckAttack;
self->monsterinfo.setskin = jorg_setskin;
gi.linkentity(self);
M_SetAnimation(self, &jorg_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
walkmonster_start(self);
// PMM
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
// pmm
self->monsterinfo.aiflags |= AI_DOUBLE_TROUBLE;
}

199
rerelease/m_boss31.h Normal file
View File

@@ -0,0 +1,199 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/boss3/jorg
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak116,
FRAME_attak117,
FRAME_attak118,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_death23,
FRAME_death24,
FRAME_death25,
FRAME_death26,
FRAME_death27,
FRAME_death28,
FRAME_death29,
FRAME_death30,
FRAME_death31,
FRAME_death32,
FRAME_death33,
FRAME_death34,
FRAME_death35,
FRAME_death36,
FRAME_death37,
FRAME_death38,
FRAME_death39,
FRAME_death40,
FRAME_death41,
FRAME_death42,
FRAME_death43,
FRAME_death44,
FRAME_death45,
FRAME_death46,
FRAME_death47,
FRAME_death48,
FRAME_death49,
FRAME_death50,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_pain310,
FRAME_pain311,
FRAME_pain312,
FRAME_pain313,
FRAME_pain314,
FRAME_pain315,
FRAME_pain316,
FRAME_pain317,
FRAME_pain318,
FRAME_pain319,
FRAME_pain320,
FRAME_pain321,
FRAME_pain322,
FRAME_pain323,
FRAME_pain324,
FRAME_pain325,
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_stand46,
FRAME_stand47,
FRAME_stand48,
FRAME_stand49,
FRAME_stand50,
FRAME_stand51,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_walk21,
FRAME_walk22,
FRAME_walk23,
FRAME_walk24,
FRAME_walk25
};
constexpr float MODEL_SCALE = 1.000000f;

905
rerelease/m_boss32.cpp Normal file
View File

@@ -0,0 +1,905 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
Makron -- Final Boss
==============================================================================
*/
#include "g_local.h"
#include "m_boss32.h"
#include "m_flash.h"
void MakronRailgun(edict_t *self);
void MakronSaveloc(edict_t *self);
void MakronHyperblaster(edict_t *self);
void makron_step_left(edict_t *self);
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;
void makron_taunt(edict_t *self)
{
float r;
r = frandom();
if (r <= 0.3f)
gi.sound(self, CHAN_AUTO, sound_taunt1, 1, ATTN_NONE, 0);
else if (r <= 0.6f)
gi.sound(self, CHAN_AUTO, sound_taunt2, 1, ATTN_NONE, 0);
else
gi.sound(self, CHAN_AUTO, sound_taunt3, 1, ATTN_NONE, 0);
}
//
// stand
//
mframe_t makron_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 10
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 20
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 30
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 40
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 50
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand } // 60
};
MMOVE_T(makron_move_stand) = { FRAME_stand201, FRAME_stand260, makron_frames_stand, nullptr };
MONSTERINFO_STAND(makron_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &makron_move_stand);
}
mframe_t makron_frames_run[] = {
{ ai_run, 3, makron_step_left },
{ ai_run, 12 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8, makron_step_right },
{ ai_run, 6 },
{ ai_run, 12 },
{ ai_run, 9 },
{ ai_run, 6 },
{ ai_run, 12 }
};
MMOVE_T(makron_move_run) = { FRAME_walk204, FRAME_walk213, makron_frames_run, nullptr };
void makron_hit(edict_t *self)
{
gi.sound(self, CHAN_AUTO, sound_hit, 1, ATTN_NONE, 0);
}
void makron_popup(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_popup, 1, ATTN_NONE, 0);
}
void makron_step_left(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_step_left, 1, ATTN_NORM, 0);
}
void makron_step_right(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_step_right, 1, ATTN_NORM, 0);
}
void makron_brainsplorch(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_brainsplorch, 1, ATTN_NORM, 0);
}
void makron_prerailgun(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_prerailgun, 1, ATTN_NORM, 0);
}
mframe_t makron_frames_walk[] = {
{ ai_walk, 3, makron_step_left },
{ ai_walk, 12 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8, makron_step_right },
{ ai_walk, 6 },
{ ai_walk, 12 },
{ ai_walk, 9 },
{ ai_walk, 6 },
{ ai_walk, 12 }
};
MMOVE_T(makron_move_walk) = { FRAME_walk204, FRAME_walk213, makron_frames_run, nullptr };
MONSTERINFO_WALK(makron_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &makron_move_walk);
}
MONSTERINFO_RUN(makron_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &makron_move_stand);
else
M_SetAnimation(self, &makron_move_run);
}
mframe_t makron_frames_pain6[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 10
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, makron_popup },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 20
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, makron_taunt },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_pain6) = { FRAME_pain601, FRAME_pain627, makron_frames_pain6, makron_run };
mframe_t makron_frames_pain5[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_pain5) = { FRAME_pain501, FRAME_pain504, makron_frames_pain5, makron_run };
mframe_t makron_frames_pain4[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_pain4) = { FRAME_pain401, FRAME_pain404, makron_frames_pain4, makron_run };
/*
---
Makron Torso. This needs to be spawned in
---
*/
THINK(makron_torso_think) (edict_t *self) -> void
{
if (++self->s.frame >= 365)
self->s.frame = 346;
self->nextthink = level.time + 10_hz;
if (self->s.angles[0] > 0)
self->s.angles[0] = max(0.f, self->s.angles[0] - 15);
}
void makron_torso(edict_t *ent)
{
ent->s.frame = 346;
ent->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2");
ent->s.skinnum = 1;
ent->think = makron_torso_think;
ent->nextthink = level.time + 10_hz;
ent->s.sound = gi.soundindex("makron/spine.wav");
ent->movetype = MOVETYPE_TOSS;
ent->s.effects = EF_GIB;
vec3_t forward, up;
AngleVectors(ent->s.angles, forward, nullptr, up);
ent->velocity += (up * 120);
ent->velocity += (forward * -120);
ent->s.origin += (forward * -10);
ent->s.angles[0] = 90;
ent->avelocity = {};
gi.linkentity(ent);
}
void makron_spawn_torso(edict_t *self)
{
edict_t *tempent = ThrowGib(self, "models/monsters/boss3/rider/tris.md2", 0, GIB_NONE, self->s.scale);
tempent->s.origin = self->s.origin;
tempent->s.angles = self->s.angles;
self->maxs[2] -= tempent->maxs[2];
tempent->s.origin[2] += self->maxs[2] - 15;
makron_torso(tempent);
}
mframe_t makron_frames_death2[] = {
{ ai_move, -15 },
{ ai_move, 3 },
{ ai_move, -12 },
{ ai_move, 0, makron_step_left },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 10
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 11 },
{ ai_move, 12 },
{ ai_move, 11, makron_step_right },
{ ai_move },
{ ai_move }, // 20
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 30
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 5 },
{ ai_move, 7 },
{ ai_move, 6, makron_step_left },
{ ai_move },
{ ai_move },
{ ai_move, -1 },
{ ai_move, 2 }, // 40
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 50
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -6 },
{ ai_move, -4 },
{ ai_move, -6, makron_step_right },
{ ai_move, -4 },
{ ai_move, -4, makron_step_left },
{ ai_move },
{ ai_move }, // 60
{ ai_move },
{ ai_move },
{ ai_move, -2 },
{ ai_move, -5 },
{ ai_move, -3, makron_step_right },
{ ai_move, -8 },
{ ai_move, -3, makron_step_left },
{ ai_move, -7 },
{ ai_move, -4 },
{ ai_move, -4, makron_step_right }, // 70
{ ai_move, -6 },
{ ai_move, -7 },
{ ai_move, 0, makron_step_left },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 80
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -2 },
{ ai_move },
{ ai_move },
{ ai_move, 2 },
{ ai_move }, // 90
{ ai_move, 27, makron_hit },
{ ai_move, 26 },
{ ai_move, 0, makron_brainsplorch },
{ ai_move },
{ ai_move } // 95
};
MMOVE_T(makron_move_death2) = { FRAME_death201, FRAME_death295, makron_frames_death2, makron_dead };
#if 0
mframe_t makron_frames_death3[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_death3) = { FRAME_death301, FRAME_death320, makron_frames_death3, nullptr };
#endif
mframe_t makron_frames_sight[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_sight) = { FRAME_active01, FRAME_active13, makron_frames_sight, makron_run };
void makronBFG(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
vec3_t dir;
vec3_t vec;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_MAKRON_BFG], forward, right);
vec = self->enemy->s.origin;
vec[2] += self->enemy->viewheight;
dir = vec - start;
dir.normalize();
gi.sound(self, CHAN_VOICE, sound_attack_bfg, 1, ATTN_NORM, 0);
monster_fire_bfg(self, start, dir, 50, 300, 100, 300, MZ2_MAKRON_BFG);
}
mframe_t makron_frames_attack3[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, makronBFG },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_attack3) = { FRAME_attak301, FRAME_attak308, makron_frames_attack3, makron_run };
mframe_t makron_frames_attack4[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move, 0, MakronHyperblaster }, // fire
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_attack4) = { FRAME_attak401, FRAME_attak426, makron_frames_attack4, makron_run };
mframe_t makron_frames_attack5[] = {
{ ai_charge, 0, makron_prerailgun },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, MakronSaveloc },
{ ai_move, 0, MakronRailgun }, // Fire railgun
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(makron_move_attack5) = { FRAME_attak501, FRAME_attak516, makron_frames_attack5, makron_run };
void MakronSaveloc(edict_t *self)
{
self->pos1 = self->enemy->s.origin; // save for aiming the shot
self->pos1[2] += self->enemy->viewheight;
};
void MakronRailgun(edict_t *self)
{
vec3_t start;
vec3_t dir;
vec3_t forward, right;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_MAKRON_RAILGUN_1], forward, right);
// calc direction to where we targted
dir = self->pos1 - start;
dir.normalize();
monster_fire_railgun(self, start, dir, 50, 100, MZ2_MAKRON_RAILGUN_1);
}
void MakronHyperblaster(edict_t *self)
{
vec3_t dir;
vec3_t vec;
vec3_t start;
vec3_t forward, right;
monster_muzzleflash_id_t flash_number = (monster_muzzleflash_id_t) (MZ2_MAKRON_BLASTER_1 + (self->s.frame - FRAME_attak405));
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
if (self->enemy)
{
vec = self->enemy->s.origin;
vec[2] += self->enemy->viewheight;
vec -= start;
vec = vectoangles(vec);
dir[0] = vec[0];
}
else
{
dir[0] = 0;
}
if (self->s.frame <= FRAME_attak413)
dir[1] = self->s.angles[1] - 10 * (self->s.frame - FRAME_attak413);
else
dir[1] = self->s.angles[1] + 10 * (self->s.frame - FRAME_attak421);
dir[2] = 0;
AngleVectors(dir, forward, nullptr, nullptr);
monster_fire_blaster(self, start, forward, 15, 1000, flash_number, EF_BLASTER);
}
PAIN(makron_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (self->monsterinfo.active_move == &makron_move_sight)
return;
if (level.time < self->pain_debounce_time)
return;
// Lessen the chance of him going into his pain frames
if (mod.id != MOD_CHAINFIST && damage <= 25)
if (frandom() < 0.2f)
return;
self->pain_debounce_time = level.time + 3_sec;
bool do_pain6 = false;
if (damage <= 40)
gi.sound(self, CHAN_VOICE, sound_pain4, 1, ATTN_NONE, 0);
else if (damage <= 110)
gi.sound(self, CHAN_VOICE, sound_pain5, 1, ATTN_NONE, 0);
else
{
if (damage <= 150)
{
if (frandom() <= 0.45f)
{
do_pain6 = true;
gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0);
}
}
else
{
if (frandom() <= 0.35f)
{
do_pain6 = true;
gi.sound(self, CHAN_VOICE, sound_pain6, 1, ATTN_NONE, 0);
}
}
}
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (damage <= 40)
M_SetAnimation(self, &makron_move_pain4);
else if (damage <= 110)
M_SetAnimation(self, &makron_move_pain5);
else if (do_pain6)
M_SetAnimation(self, &makron_move_pain6);
}
MONSTERINFO_SETSKIN(makron_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
MONSTERINFO_SIGHT(makron_sight) (edict_t *self, edict_t *other) -> void
{
M_SetAnimation(self, &makron_move_sight);
}
MONSTERINFO_ATTACK(makron_attack) (edict_t *self) -> void
{
float r;
r = frandom();
if (r <= 0.3f)
M_SetAnimation(self, &makron_move_attack3);
else if (r <= 0.6f)
M_SetAnimation(self, &makron_move_attack4);
else
M_SetAnimation(self, &makron_move_attack5);
}
//
// death
//
void makron_dead(edict_t *self)
{
self->mins = { -60, -60, 0 };
self->maxs = { 60, 60, 24 };
self->movetype = MOVETYPE_TOSS;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
monster_dead(self);
}
DIE(makron_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
self->s.sound = 0;
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
ThrowGibs(self, damage, {
{ "models/objects/gibs/sm_meat/tris.md2" },
{ 4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
{ "models/objects/gibs/gear/tris.md2", GIB_METALLIC | GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NONE, 0);
self->deadflag = true;
self->takedamage = true;
self->svflags |= SVF_DEADMONSTER;
M_SetAnimation(self, &makron_move_death2);
makron_spawn_torso(self);
self->mins = { -60, -60, 0 };
self->maxs = { 60, 60, 48 };
}
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;
}
//
// monster_makron
//
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");
gi.modelindex("models/monsters/boss3/rider/tris.md2");
}
/*QUAKED monster_makron (1 .5 0) (-30 -30 0) (30 30 90) Ambush Trigger_Spawn Sight
*/
void SP_monster_makron(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
MakronPrecache();
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/boss3/rider/tris.md2");
self->mins = { -30, -30, 0 };
self->maxs = { 30, 30, 90 };
self->health = 3000 * st.health_multiplier;
self->gib_health = -2000;
self->mass = 500;
self->pain = makron_pain;
self->die = makron_die;
self->monsterinfo.stand = makron_stand;
self->monsterinfo.walk = makron_walk;
self->monsterinfo.run = makron_run;
self->monsterinfo.dodge = nullptr;
self->monsterinfo.attack = makron_attack;
self->monsterinfo.melee = nullptr;
self->monsterinfo.sight = makron_sight;
self->monsterinfo.checkattack = Makron_CheckAttack;
self->monsterinfo.setskin = makron_setskin;
gi.linkentity(self);
// M_SetAnimation(self, &makron_move_stand);
M_SetAnimation(self, &makron_move_sight);
self->monsterinfo.scale = MODEL_SCALE;
walkmonster_start(self);
// PMM
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
// pmm
}
/*
=================
MakronSpawn
=================
*/
THINK(MakronSpawn) (edict_t *self) -> void
{
vec3_t vec;
edict_t *player;
SP_monster_makron(self);
self->think(self);
// jump at player
if (self->enemy && self->enemy->inuse && self->enemy->health > 0)
player = self->enemy;
else
player = AI_GetSightClient(self);
if (!player)
return;
vec = player->s.origin - self->s.origin;
self->s.angles[YAW] = vectoyaw(vec);
vec.normalize();
self->velocity = vec * 400;
self->velocity[2] = 200;
self->groundentity = nullptr;
self->enemy = player;
FoundTarget(self);
self->monsterinfo.sight(self, self->enemy);
self->s.frame = self->monsterinfo.nextframe = FRAME_active01; // FIXME: why????
}
/*
=================
MakronToss
Jorg is just about dead, so set up to launch Makron out
=================
*/
void MakronToss(edict_t *self)
{
edict_t *ent = G_Spawn();
ent->classname = "monster_makron";
ent->target = self->target;
ent->s.origin = self->s.origin;
ent->enemy = self->enemy;
MakronSpawn(ent);
// [Paril-KEX] set health bar over to Makron when we throw him out
for (size_t i = 0; i < 2; i++)
if (level.health_bar_entities[i] && level.health_bar_entities[i]->enemy == self)
level.health_bar_entities[i]->enemy = ent;
}

502
rerelease/m_boss32.h Normal file
View File

@@ -0,0 +1,502 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/boss3/rider
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak116,
FRAME_attak117,
FRAME_attak118,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_death23,
FRAME_death24,
FRAME_death25,
FRAME_death26,
FRAME_death27,
FRAME_death28,
FRAME_death29,
FRAME_death30,
FRAME_death31,
FRAME_death32,
FRAME_death33,
FRAME_death34,
FRAME_death35,
FRAME_death36,
FRAME_death37,
FRAME_death38,
FRAME_death39,
FRAME_death40,
FRAME_death41,
FRAME_death42,
FRAME_death43,
FRAME_death44,
FRAME_death45,
FRAME_death46,
FRAME_death47,
FRAME_death48,
FRAME_death49,
FRAME_death50,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_pain310,
FRAME_pain311,
FRAME_pain312,
FRAME_pain313,
FRAME_pain314,
FRAME_pain315,
FRAME_pain316,
FRAME_pain317,
FRAME_pain318,
FRAME_pain319,
FRAME_pain320,
FRAME_pain321,
FRAME_pain322,
FRAME_pain323,
FRAME_pain324,
FRAME_pain325,
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_stand46,
FRAME_stand47,
FRAME_stand48,
FRAME_stand49,
FRAME_stand50,
FRAME_stand51,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_walk21,
FRAME_walk22,
FRAME_walk23,
FRAME_walk24,
FRAME_walk25,
FRAME_active01,
FRAME_active02,
FRAME_active03,
FRAME_active04,
FRAME_active05,
FRAME_active06,
FRAME_active07,
FRAME_active08,
FRAME_active09,
FRAME_active10,
FRAME_active11,
FRAME_active12,
FRAME_active13,
FRAME_attak301,
FRAME_attak302,
FRAME_attak303,
FRAME_attak304,
FRAME_attak305,
FRAME_attak306,
FRAME_attak307,
FRAME_attak308,
FRAME_attak401,
FRAME_attak402,
FRAME_attak403,
FRAME_attak404,
FRAME_attak405,
FRAME_attak406,
FRAME_attak407,
FRAME_attak408,
FRAME_attak409,
FRAME_attak410,
FRAME_attak411,
FRAME_attak412,
FRAME_attak413,
FRAME_attak414,
FRAME_attak415,
FRAME_attak416,
FRAME_attak417,
FRAME_attak418,
FRAME_attak419,
FRAME_attak420,
FRAME_attak421,
FRAME_attak422,
FRAME_attak423,
FRAME_attak424,
FRAME_attak425,
FRAME_attak426,
FRAME_attak501,
FRAME_attak502,
FRAME_attak503,
FRAME_attak504,
FRAME_attak505,
FRAME_attak506,
FRAME_attak507,
FRAME_attak508,
FRAME_attak509,
FRAME_attak510,
FRAME_attak511,
FRAME_attak512,
FRAME_attak513,
FRAME_attak514,
FRAME_attak515,
FRAME_attak516,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_death206,
FRAME_death207,
FRAME_death208,
FRAME_death209,
FRAME_death210,
FRAME_death211,
FRAME_death212,
FRAME_death213,
FRAME_death214,
FRAME_death215,
FRAME_death216,
FRAME_death217,
FRAME_death218,
FRAME_death219,
FRAME_death220,
FRAME_death221,
FRAME_death222,
FRAME_death223,
FRAME_death224,
FRAME_death225,
FRAME_death226,
FRAME_death227,
FRAME_death228,
FRAME_death229,
FRAME_death230,
FRAME_death231,
FRAME_death232,
FRAME_death233,
FRAME_death234,
FRAME_death235,
FRAME_death236,
FRAME_death237,
FRAME_death238,
FRAME_death239,
FRAME_death240,
FRAME_death241,
FRAME_death242,
FRAME_death243,
FRAME_death244,
FRAME_death245,
FRAME_death246,
FRAME_death247,
FRAME_death248,
FRAME_death249,
FRAME_death250,
FRAME_death251,
FRAME_death252,
FRAME_death253,
FRAME_death254,
FRAME_death255,
FRAME_death256,
FRAME_death257,
FRAME_death258,
FRAME_death259,
FRAME_death260,
FRAME_death261,
FRAME_death262,
FRAME_death263,
FRAME_death264,
FRAME_death265,
FRAME_death266,
FRAME_death267,
FRAME_death268,
FRAME_death269,
FRAME_death270,
FRAME_death271,
FRAME_death272,
FRAME_death273,
FRAME_death274,
FRAME_death275,
FRAME_death276,
FRAME_death277,
FRAME_death278,
FRAME_death279,
FRAME_death280,
FRAME_death281,
FRAME_death282,
FRAME_death283,
FRAME_death284,
FRAME_death285,
FRAME_death286,
FRAME_death287,
FRAME_death288,
FRAME_death289,
FRAME_death290,
FRAME_death291,
FRAME_death292,
FRAME_death293,
FRAME_death294,
FRAME_death295,
FRAME_death301,
FRAME_death302,
FRAME_death303,
FRAME_death304,
FRAME_death305,
FRAME_death306,
FRAME_death307,
FRAME_death308,
FRAME_death309,
FRAME_death310,
FRAME_death311,
FRAME_death312,
FRAME_death313,
FRAME_death314,
FRAME_death315,
FRAME_death316,
FRAME_death317,
FRAME_death318,
FRAME_death319,
FRAME_death320,
FRAME_jump01,
FRAME_jump02,
FRAME_jump03,
FRAME_jump04,
FRAME_jump05,
FRAME_jump06,
FRAME_jump07,
FRAME_jump08,
FRAME_jump09,
FRAME_jump10,
FRAME_jump11,
FRAME_jump12,
FRAME_jump13,
FRAME_pain401,
FRAME_pain402,
FRAME_pain403,
FRAME_pain404,
FRAME_pain501,
FRAME_pain502,
FRAME_pain503,
FRAME_pain504,
FRAME_pain601,
FRAME_pain602,
FRAME_pain603,
FRAME_pain604,
FRAME_pain605,
FRAME_pain606,
FRAME_pain607,
FRAME_pain608,
FRAME_pain609,
FRAME_pain610,
FRAME_pain611,
FRAME_pain612,
FRAME_pain613,
FRAME_pain614,
FRAME_pain615,
FRAME_pain616,
FRAME_pain617,
FRAME_pain618,
FRAME_pain619,
FRAME_pain620,
FRAME_pain621,
FRAME_pain622,
FRAME_pain623,
FRAME_pain624,
FRAME_pain625,
FRAME_pain626,
FRAME_pain627,
FRAME_stand201,
FRAME_stand202,
FRAME_stand203,
FRAME_stand204,
FRAME_stand205,
FRAME_stand206,
FRAME_stand207,
FRAME_stand208,
FRAME_stand209,
FRAME_stand210,
FRAME_stand211,
FRAME_stand212,
FRAME_stand213,
FRAME_stand214,
FRAME_stand215,
FRAME_stand216,
FRAME_stand217,
FRAME_stand218,
FRAME_stand219,
FRAME_stand220,
FRAME_stand221,
FRAME_stand222,
FRAME_stand223,
FRAME_stand224,
FRAME_stand225,
FRAME_stand226,
FRAME_stand227,
FRAME_stand228,
FRAME_stand229,
FRAME_stand230,
FRAME_stand231,
FRAME_stand232,
FRAME_stand233,
FRAME_stand234,
FRAME_stand235,
FRAME_stand236,
FRAME_stand237,
FRAME_stand238,
FRAME_stand239,
FRAME_stand240,
FRAME_stand241,
FRAME_stand242,
FRAME_stand243,
FRAME_stand244,
FRAME_stand245,
FRAME_stand246,
FRAME_stand247,
FRAME_stand248,
FRAME_stand249,
FRAME_stand250,
FRAME_stand251,
FRAME_stand252,
FRAME_stand253,
FRAME_stand254,
FRAME_stand255,
FRAME_stand256,
FRAME_stand257,
FRAME_stand258,
FRAME_stand259,
FRAME_stand260,
FRAME_walk201,
FRAME_walk202,
FRAME_walk203,
FRAME_walk204,
FRAME_walk205,
FRAME_walk206,
FRAME_walk207,
FRAME_walk208,
FRAME_walk209,
FRAME_walk210,
FRAME_walk211,
FRAME_walk212,
FRAME_walk213,
FRAME_walk214,
FRAME_walk215,
FRAME_walk216,
FRAME_walk217
};
constexpr float MODEL_SCALE = 1.000000f;

798
rerelease/m_brain.cpp Normal file
View File

@@ -0,0 +1,798 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
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;
MONSTERINFO_SIGHT(brain_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_SEARCH(brain_search) (edict_t *self) -> void
{
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
void brain_run(edict_t *self);
void brain_dead(edict_t *self);
constexpr spawnflags_t SPAWNFLAG_BRAIN_NO_LASERS = 8_spawnflag;
//
// STAND
//
mframe_t brain_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(brain_move_stand) = { FRAME_stand01, FRAME_stand30, brain_frames_stand, nullptr };
MONSTERINFO_STAND(brain_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &brain_move_stand);
}
//
// IDLE
//
mframe_t brain_frames_idle[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(brain_move_idle) = { FRAME_stand31, FRAME_stand60, brain_frames_idle, brain_stand };
MONSTERINFO_IDLE(brain_idle) (edict_t *self) -> void
{
gi.sound(self, CHAN_AUTO, sound_idle3, 1, ATTN_IDLE, 0);
M_SetAnimation(self, &brain_move_idle);
}
//
// WALK
//
mframe_t brain_frames_walk1[] = {
{ ai_walk, 7 },
{ ai_walk, 2 },
{ ai_walk, 3 },
{ ai_walk, 3, monster_footstep },
{ ai_walk, 1 },
{ ai_walk },
{ ai_walk },
{ ai_walk, 9 },
{ ai_walk, -4 },
{ ai_walk, -1, monster_footstep },
{ ai_walk, 2 }
};
MMOVE_T(brain_move_walk1) = { FRAME_walk101, FRAME_walk111, brain_frames_walk1, nullptr };
MONSTERINFO_WALK(brain_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &brain_move_walk1);
}
#if 0
mframe_t brain_frames_defense[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(brain_move_defense) = { FRAME_defens01, FRAME_defens08, brain_frames_defense, nullptr };
#endif
mframe_t brain_frames_pain3[] = {
{ ai_move, -2 },
{ ai_move, 2 },
{ ai_move, 1 },
{ ai_move, 3 },
{ ai_move },
{ ai_move, -4 }
};
MMOVE_T(brain_move_pain3) = { FRAME_pain301, FRAME_pain306, brain_frames_pain3, brain_run };
mframe_t brain_frames_pain2[] = {
{ ai_move, -2 },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 3 },
{ ai_move, 1 },
{ ai_move, -2 }
};
MMOVE_T(brain_move_pain2) = { FRAME_pain201, FRAME_pain208, brain_frames_pain2, brain_run };
mframe_t brain_frames_pain1[] = {
{ ai_move, -6 },
{ ai_move, -2 },
{ ai_move, -6, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 2 },
{ ai_move },
{ ai_move, 2 },
{ ai_move, 1 },
{ ai_move, 7 },
{ ai_move },
{ ai_move, 3, monster_footstep },
{ ai_move, -1 }
};
MMOVE_T(brain_move_pain1) = { FRAME_pain101, FRAME_pain121, brain_frames_pain1, brain_run };
mframe_t brain_frames_duck[] = {
{ ai_move },
{ ai_move, -2, [](edict_t *self) { monster_duck_down(self); monster_footstep(self); } },
{ ai_move, 17, monster_duck_hold },
{ ai_move, -3 },
{ ai_move, -1, monster_duck_up },
{ ai_move, -5 },
{ ai_move, -6 },
{ ai_move, -6, monster_footstep }
};
MMOVE_T(brain_move_duck) = { FRAME_duck01, FRAME_duck08, brain_frames_duck, brain_run };
static void brain_shrink(edict_t *self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t brain_frames_death2[] = {
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move, 0, brain_shrink },
{ ai_move, 9 },
{ ai_move }
};
MMOVE_T(brain_move_death2) = { FRAME_death201, FRAME_death205, brain_frames_death2, brain_dead };
mframe_t brain_frames_death1[] = {
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move, -2 },
{ ai_move, 9, [](edict_t *self) { brain_shrink(self); monster_footstep(self); } },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move }
};
MMOVE_T(brain_move_death1) = { FRAME_death101, FRAME_death118, brain_frames_death1, brain_dead };
//
// MELEE
//
void brain_swing_right(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_melee1, 1, ATTN_NORM, 0);
}
void brain_hit_right(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 8 };
if (fire_hit(self, aim, irandom(15, 20), 40))
gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
else
self->monsterinfo.melee_debounce_time = level.time + 3_sec;
}
void brain_swing_left(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_melee2, 1, ATTN_NORM, 0);
}
void brain_hit_left(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->mins[0], 8 };
if (fire_hit(self, aim, irandom(15, 20), 40))
gi.sound(self, CHAN_WEAPON, sound_melee3, 1, ATTN_NORM, 0);
else
self->monsterinfo.melee_debounce_time = level.time + 3_sec;
}
mframe_t brain_frames_attack1[] = {
{ ai_charge, 8 },
{ ai_charge, 3 },
{ ai_charge, 5 },
{ ai_charge, 0, monster_footstep },
{ ai_charge, -3, brain_swing_right },
{ ai_charge },
{ ai_charge, -5 },
{ ai_charge, -7, brain_hit_right },
{ ai_charge },
{ ai_charge, 6, brain_swing_left },
{ ai_charge, 1 },
{ ai_charge, 2, brain_hit_left },
{ ai_charge, -3 },
{ ai_charge, 6 },
{ ai_charge, -1 },
{ ai_charge, -3 },
{ ai_charge, 2 },
{ ai_charge, -11, monster_footstep }
};
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->monsterinfo.power_armor_type = IT_NULL;
gi.sound(self, CHAN_BODY, sound_chest_open, 1, ATTN_NORM, 0);
}
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;
else
self->monsterinfo.melee_debounce_time = level.time + 3_sec;
gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
}
void brain_chest_closed(edict_t *self)
{
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN;
if (self->spawnflags.has(SPAWNFLAG_BRAIN_TENTACLES_HIT))
{
self->spawnflags &= ~SPAWNFLAG_BRAIN_TENTACLES_HIT;
M_SetAnimation(self, &brain_move_attack1);
}
}
mframe_t brain_frames_attack2[] = {
{ ai_charge, 5 },
{ ai_charge, -4 },
{ ai_charge, -4 },
{ ai_charge, -3 },
{ ai_charge, 0, brain_chest_open },
{ ai_charge },
{ ai_charge, 13, brain_tentacle_attack },
{ ai_charge },
{ ai_charge, 2 },
{ ai_charge },
{ ai_charge, -9, brain_chest_closed },
{ ai_charge },
{ ai_charge, 4 },
{ ai_charge, 3 },
{ ai_charge, 2 },
{ ai_charge, -3 },
{ ai_charge, -6 }
};
MMOVE_T(brain_move_attack2) = { FRAME_attak201, FRAME_attak217, brain_frames_attack2, brain_run };
MONSTERINFO_MELEE(brain_melee) (edict_t *self) -> void
{
if (frandom() <= 0.5f)
M_SetAnimation(self, &brain_move_attack1);
else
M_SetAnimation(self, &brain_move_attack2);
}
// RAFAEL
static bool brain_tounge_attack_ok(const vec3_t &start, const vec3_t &end)
{
vec3_t dir, angles;
// check for max distance
dir = start - end;
if (dir.length() > 512)
return false;
// check for min/max pitch
angles = vectoangles(dir);
if (angles[0] < -180)
angles[0] += 360;
if (fabsf(angles[0]) > 30)
return false;
return true;
}
void brain_tounge_attack(edict_t *self)
{
vec3_t offset, start, f, r, end, dir;
trace_t tr;
int damage;
AngleVectors(self->s.angles, f, r, nullptr);
// offset = { 24, 0, 6 };
offset = { 24, 0, 16 };
start = M_ProjectFlashSource(self, offset, f, r);
end = self->enemy->s.origin;
if (!brain_tounge_attack_ok(start, end))
{
end[2] = self->enemy->s.origin[2] + self->enemy->maxs[2] - 8;
if (!brain_tounge_attack_ok(start, end))
{
end[2] = self->enemy->s.origin[2] + self->enemy->mins[2] + 8;
if (!brain_tounge_attack_ok(start, end))
return;
}
}
end = self->enemy->s.origin;
tr = gi.traceline(start, end, self, MASK_PROJECTILE);
if (tr.ent != self->enemy)
return;
damage = 5;
gi.sound(self, CHAN_WEAPON, sound_tentacles_retract, 1, ATTN_NORM, 0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_PARASITE_ATTACK);
gi.WriteEntity(self);
gi.WritePosition(start);
gi.WritePosition(end);
gi.multicast(self->s.origin, MULTICAST_PVS, false);
dir = start - end;
T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, damage, 0, DAMAGE_NO_KNOCKBACK, MOD_BRAINTENTACLE);
// pull the enemy in
vec3_t forward;
self->s.origin[2] += 1;
AngleVectors(self->s.angles, forward, nullptr, nullptr);
self->enemy->velocity = forward * -1200;
}
// Brian right eye center
constexpr vec3_t brain_reye[] = {
{ 0.746700f, 0.238370f, 34.167690f },
{ -1.076390f, 0.238370f, 33.386372f },
{ -1.335500f, 5.334300f, 32.177170f },
{ -0.175360f, 8.846370f, 30.635479f },
{ -2.757590f, 7.804610f, 30.150860f },
{ -5.575090f, 5.152840f, 30.056160f },
{ -7.017550f, 3.262470f, 30.552521f },
{ -7.915740f, 0.638800f, 33.176189f },
{ -3.915390f, 8.285730f, 33.976349f },
{ -0.913540f, 10.933030f, 34.141811f },
{ -0.369900f, 8.923900f, 34.189079f }
};
// Brain left eye center
constexpr vec3_t brain_leye[] = {
{ -3.364710f, 0.327750f, 33.938381f },
{ -5.140450f, 0.493480f, 32.659851f },
{ -5.341980f, 5.646980f, 31.277901f },
{ -4.134480f, 9.277440f, 29.925621f },
{ -6.598340f, 6.815090f, 29.322620f },
{ -8.610840f, 2.529650f, 29.251591f },
{ -9.231360f, 0.093280f, 29.747959f },
{ -11.004110f, 1.936930f, 32.395260f },
{ -7.878310f, 7.648190f, 33.148151f },
{ -4.947370f, 11.430050f, 33.313610f },
{ -4.332820f, 9.444570f, 33.526340f }
};
PRETHINK(brain_right_eye_laser_update) (edict_t *laser) -> void
{
edict_t *self = laser->owner;
vec3_t start, forward, right, up, dir;
// check for max distance
AngleVectors(self->s.angles, forward, right, up);
// dis is my right eye
start = self->s.origin + (right * brain_reye[self->s.frame - FRAME_walk101].x);
start += forward * brain_reye[self->s.frame - FRAME_walk101].y;
start += up * brain_reye[self->s.frame - FRAME_walk101].z;
PredictAim(self, self->enemy, start, 0, false, frandom(0.1f, 0.2f), &dir, nullptr);
laser->s.origin = start;
laser->movedir = dir;
gi.linkentity(laser);
}
PRETHINK(brain_left_eye_laser_update) (edict_t *laser) -> void
{
edict_t *self = laser->owner;
vec3_t start, forward, right, up, dir;
// check for max distance
AngleVectors(self->s.angles, forward, right, up);
// dis is my right eye
start = self->s.origin + (right * brain_leye[self->s.frame - FRAME_walk101].x);
start += forward * brain_leye[self->s.frame - FRAME_walk101].y;
start += up * brain_leye[self->s.frame - FRAME_walk101].z;
PredictAim(self, self->enemy, start, 0, false, frandom(0.1f, 0.2f), &dir, nullptr);
laser->s.origin = start;
laser->movedir = dir;
gi.linkentity(laser);
dabeam_update(laser, false);
}
void brain_laserbeam(edict_t *self)
{
// dis is my right eye
monster_fire_dabeam(self, 1, false, brain_right_eye_laser_update);
// dis is me left eye
monster_fire_dabeam(self, 1, true, brain_left_eye_laser_update);
}
void brain_laserbeam_reattack(edict_t *self)
{
if (frandom() < 0.5f)
if (visible(self, self->enemy))
if (self->enemy->health > 0)
self->s.frame = FRAME_walk101;
}
mframe_t brain_frames_attack3[] = {
{ ai_charge, 5 },
{ ai_charge, -4 },
{ ai_charge, -4 },
{ ai_charge, -3 },
{ ai_charge, 0, brain_chest_open },
{ ai_charge, 0, brain_tounge_attack },
{ ai_charge, 13 },
{ ai_charge, 0, brain_tentacle_attack },
{ ai_charge, 2 },
{ ai_charge, 0, brain_tounge_attack },
{ ai_charge, -9, brain_chest_closed },
{ ai_charge },
{ ai_charge, 4 },
{ ai_charge, 3 },
{ ai_charge, 2 },
{ ai_charge, -3 },
{ ai_charge, -6 }
};
MMOVE_T(brain_move_attack3) = { FRAME_attak201, FRAME_attak217, brain_frames_attack3, brain_run };
mframe_t brain_frames_attack4[] = {
{ ai_charge, 9, brain_laserbeam },
{ ai_charge, 2, brain_laserbeam },
{ ai_charge, 3, brain_laserbeam },
{ ai_charge, 3, brain_laserbeam },
{ ai_charge, 1, brain_laserbeam },
{ ai_charge, 0, brain_laserbeam },
{ ai_charge, 0, brain_laserbeam },
{ ai_charge, 10, brain_laserbeam },
{ ai_charge, -4, brain_laserbeam },
{ ai_charge, -1, brain_laserbeam },
{ ai_charge, 2, brain_laserbeam_reattack }
};
MMOVE_T(brain_move_attack4) = { FRAME_walk101, FRAME_walk111, brain_frames_attack4, brain_run };
// RAFAEL
MONSTERINFO_ATTACK(brain_attack) (edict_t *self) -> void
{
float r = range_to(self, self->enemy);
if (r <= RANGE_NEAR)
{
if (frandom() < 0.5f)
M_SetAnimation(self, &brain_move_attack3);
else if (!self->spawnflags.has(SPAWNFLAG_BRAIN_NO_LASERS))
M_SetAnimation(self, &brain_move_attack4);
}
else if (!self->spawnflags.has(SPAWNFLAG_BRAIN_NO_LASERS))
M_SetAnimation(self, &brain_move_attack4);
}
// RAFAEL
//
// RUN
//
mframe_t brain_frames_run[] = {
{ ai_run, 9 },
{ ai_run, 2 },
{ ai_run, 3 },
{ ai_run, 3 },
{ ai_run, 1 },
{ ai_run },
{ ai_run },
{ ai_run, 10 },
{ ai_run, -4 },
{ ai_run, -1 },
{ ai_run, 2 }
};
MMOVE_T(brain_move_run) = { FRAME_walk101, FRAME_walk111, brain_frames_run, nullptr };
MONSTERINFO_RUN(brain_run) (edict_t *self) -> void
{
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &brain_move_stand);
else
M_SetAnimation(self, &brain_move_run);
}
PAIN(brain_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
float r;
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
r = frandom();
if (r < 0.33f)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else if (r < 0.66f)
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (r < 0.33f)
M_SetAnimation(self, &brain_move_pain1);
else if (r < 0.66f)
M_SetAnimation(self, &brain_move_pain2);
else
M_SetAnimation(self, &brain_move_pain3);
// PMM - clear duck flag
if (self->monsterinfo.aiflags & AI_DUCKED)
monster_duck_up(self);
}
MONSTERINFO_SETSKIN(brain_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
void brain_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
monster_dead(self);
}
DIE(brain_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
self->s.effects = EF_NONE;
self->monsterinfo.power_armor_type = IT_NULL;
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
self->s.skinnum /= 2;
if (self->beam)
{
G_FreeEdict(self->beam);
self->beam = nullptr;
}
if (self->beam2)
{
G_FreeEdict(self->beam2);
self->beam2 = nullptr;
}
ThrowGibs(self, damage, {
{ 1, "models/objects/gibs/bone/tris.md2" },
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ 2, "models/monsters/brain/gibs/arm.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/brain/gibs/boot.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/brain/gibs/pelvis.md2", GIB_SKINNED },
{ "models/monsters/brain/gibs/chest.md2", GIB_SKINNED },
{ 2, "models/monsters/brain/gibs/door.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/brain/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
if (frandom() <= 0.5f)
M_SetAnimation(self, &brain_move_death1);
else
M_SetAnimation(self, &brain_move_death2);
}
MONSTERINFO_DUCK(brain_duck) (edict_t *self, gtime_t eta) -> bool
{
M_SetAnimation(self, &brain_move_duck);
return true;
}
/*QUAKED monster_brain (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
*/
void SP_monster_brain(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/brain/tris.md2");
gi.modelindex("models/monsters/brain/gibs/arm.md2");
gi.modelindex("models/monsters/brain/gibs/boot.md2");
gi.modelindex("models/monsters/brain/gibs/chest.md2");
gi.modelindex("models/monsters/brain/gibs/door.md2");
gi.modelindex("models/monsters/brain/gibs/head.md2");
gi.modelindex("models/monsters/brain/gibs/pelvis.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 32 };
self->health = 300 * st.health_multiplier;
self->gib_health = -150;
self->mass = 400;
self->pain = brain_pain;
self->die = brain_die;
self->monsterinfo.stand = brain_stand;
self->monsterinfo.walk = brain_walk;
self->monsterinfo.run = brain_run;
// PMM
self->monsterinfo.dodge = M_MonsterDodge;
self->monsterinfo.duck = brain_duck;
self->monsterinfo.unduck = monster_duck_up;
// pmm
// RAFAEL
self->monsterinfo.attack = brain_attack;
// RAFAEL
self->monsterinfo.melee = brain_melee;
self->monsterinfo.sight = brain_sight;
self->monsterinfo.search = brain_search;
self->monsterinfo.idle = brain_idle;
self->monsterinfo.setskin = brain_setskin;
if (!st.was_key_specified("power_armor_type"))
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN;
if (!st.was_key_specified("power_armor_power"))
self->monsterinfo.power_armor_power = 100;
gi.linkentity(self);
M_SetAnimation(self, &brain_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
walkmonster_start(self);
}

233
rerelease/m_brain.h Normal file
View File

@@ -0,0 +1,233 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/brain
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_walk101,
FRAME_walk102,
FRAME_walk103,
FRAME_walk104,
FRAME_walk105,
FRAME_walk106,
FRAME_walk107,
FRAME_walk108,
FRAME_walk109,
FRAME_walk110,
FRAME_walk111,
FRAME_walk112,
FRAME_walk113,
FRAME_walk201,
FRAME_walk202,
FRAME_walk203,
FRAME_walk204,
FRAME_walk205,
FRAME_walk206,
FRAME_walk207,
FRAME_walk208,
FRAME_walk209,
FRAME_walk210,
FRAME_walk211,
FRAME_walk212,
FRAME_walk213,
FRAME_walk214,
FRAME_walk215,
FRAME_walk216,
FRAME_walk217,
FRAME_walk218,
FRAME_walk219,
FRAME_walk220,
FRAME_walk221,
FRAME_walk222,
FRAME_walk223,
FRAME_walk224,
FRAME_walk225,
FRAME_walk226,
FRAME_walk227,
FRAME_walk228,
FRAME_walk229,
FRAME_walk230,
FRAME_walk231,
FRAME_walk232,
FRAME_walk233,
FRAME_walk234,
FRAME_walk235,
FRAME_walk236,
FRAME_walk237,
FRAME_walk238,
FRAME_walk239,
FRAME_walk240,
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak116,
FRAME_attak117,
FRAME_attak118,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_attak214,
FRAME_attak215,
FRAME_attak216,
FRAME_attak217,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain106,
FRAME_pain107,
FRAME_pain108,
FRAME_pain109,
FRAME_pain110,
FRAME_pain111,
FRAME_pain112,
FRAME_pain113,
FRAME_pain114,
FRAME_pain115,
FRAME_pain116,
FRAME_pain117,
FRAME_pain118,
FRAME_pain119,
FRAME_pain120,
FRAME_pain121,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain206,
FRAME_pain207,
FRAME_pain208,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death108,
FRAME_death109,
FRAME_death110,
FRAME_death111,
FRAME_death112,
FRAME_death113,
FRAME_death114,
FRAME_death115,
FRAME_death116,
FRAME_death117,
FRAME_death118,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_duck01,
FRAME_duck02,
FRAME_duck03,
FRAME_duck04,
FRAME_duck05,
FRAME_duck06,
FRAME_duck07,
FRAME_duck08,
FRAME_defens01,
FRAME_defens02,
FRAME_defens03,
FRAME_defens04,
FRAME_defens05,
FRAME_defens06,
FRAME_defens07,
FRAME_defens08,
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_stand46,
FRAME_stand47,
FRAME_stand48,
FRAME_stand49,
FRAME_stand50,
FRAME_stand51,
FRAME_stand52,
FRAME_stand53,
FRAME_stand54,
FRAME_stand55,
FRAME_stand56,
FRAME_stand57,
FRAME_stand58,
FRAME_stand59,
FRAME_stand60
};
constexpr float MODEL_SCALE = 1.000000f;

869
rerelease/m_chick.cpp Normal file
View File

@@ -0,0 +1,869 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
chick
==============================================================================
*/
#include "g_local.h"
#include "m_chick.h"
#include "m_flash.h"
void chick_stand(edict_t *self);
void chick_run(edict_t *self);
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;
void ChickMoan(edict_t *self)
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_idle1, 1, ATTN_IDLE, 0);
else
gi.sound(self, CHAN_VOICE, sound_idle2, 1, ATTN_IDLE, 0);
}
mframe_t chick_frames_fidget[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, ChickMoan },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(chick_move_fidget) = { FRAME_stand201, FRAME_stand230, chick_frames_fidget, chick_stand };
void chick_fidget(edict_t *self)
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
return;
else if (self->enemy)
return;
if (frandom() <= 0.3f)
M_SetAnimation(self, &chick_move_fidget);
}
mframe_t chick_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, chick_fidget },
};
MMOVE_T(chick_move_stand) = { FRAME_stand101, FRAME_stand130, chick_frames_stand, nullptr };
MONSTERINFO_STAND(chick_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &chick_move_stand);
}
mframe_t chick_frames_start_run[] = {
{ ai_run, 1 },
{ ai_run },
{ ai_run, 0, monster_footstep },
{ ai_run, -1 },
{ ai_run, -1, monster_footstep },
{ ai_run },
{ ai_run, 1 },
{ ai_run, 3 },
{ ai_run, 6 },
{ ai_run, 3 }
};
MMOVE_T(chick_move_start_run) = { FRAME_walk01, FRAME_walk10, chick_frames_start_run, chick_run };
mframe_t chick_frames_run[] = {
{ ai_run, 6 },
{ ai_run, 8, monster_footstep },
{ ai_run, 13 },
{ ai_run, 5, monster_done_dodge }, // make sure to clear dodge bit
{ ai_run, 7 },
{ ai_run, 4 },
{ ai_run, 11, monster_footstep },
{ ai_run, 5 },
{ ai_run, 9 },
{ ai_run, 7 }
};
MMOVE_T(chick_move_run) = { FRAME_walk11, FRAME_walk20, chick_frames_run, nullptr };
mframe_t chick_frames_walk[] = {
{ ai_walk, 6 },
{ ai_walk, 8, monster_footstep },
{ ai_walk, 13 },
{ ai_walk, 5 },
{ ai_walk, 7 },
{ ai_walk, 4 },
{ ai_walk, 11, monster_footstep },
{ ai_walk, 5 },
{ ai_walk, 9 },
{ ai_walk, 7 }
};
MMOVE_T(chick_move_walk) = { FRAME_walk11, FRAME_walk20, chick_frames_walk, nullptr };
MONSTERINFO_WALK(chick_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &chick_move_walk);
}
MONSTERINFO_RUN(chick_run) (edict_t *self) -> void
{
monster_done_dodge(self);
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
M_SetAnimation(self, &chick_move_stand);
return;
}
if (self->monsterinfo.active_move == &chick_move_walk ||
self->monsterinfo.active_move == &chick_move_start_run)
{
M_SetAnimation(self, &chick_move_run);
}
else
{
M_SetAnimation(self, &chick_move_start_run);
}
}
mframe_t chick_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(chick_move_pain1) = { FRAME_pain101, FRAME_pain105, chick_frames_pain1, chick_run };
mframe_t chick_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(chick_move_pain2) = { FRAME_pain201, FRAME_pain205, chick_frames_pain2, chick_run };
mframe_t chick_frames_pain3[] = {
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move, -6 },
{ ai_move, 3, monster_footstep },
{ ai_move, 11 },
{ ai_move, 3, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move, 4 },
{ ai_move, 1 },
{ ai_move },
{ ai_move, -3 },
{ ai_move, -4 },
{ ai_move, 5 },
{ ai_move, 7 },
{ ai_move, -2 },
{ ai_move, 3 },
{ ai_move, -5 },
{ ai_move, -2 },
{ ai_move, -8 },
{ ai_move, 2, monster_footstep }
};
MMOVE_T(chick_move_pain3) = { FRAME_pain301, FRAME_pain321, chick_frames_pain3, chick_run };
PAIN(chick_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
float r;
monster_done_dodge(self);
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
r = frandom();
if (r < 0.33f)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else if (r < 0.66f)
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
// PMM - clear this from blindfire
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
if (damage <= 10)
M_SetAnimation(self, &chick_move_pain1);
else if (damage <= 25)
M_SetAnimation(self, &chick_move_pain2);
else
M_SetAnimation(self, &chick_move_pain3);
// PMM - clear duck flag
if (self->monsterinfo.aiflags & AI_DUCKED)
monster_duck_up(self);
}
MONSTERINFO_SETSKIN(chick_setpain) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum |= 1;
else
self->s.skinnum &= ~1;
}
void chick_dead(edict_t *self)
{
self->mins = { -16, -16, 0 };
self->maxs = { 16, 16, 8 };
monster_dead(self);
}
static void chick_shrink(edict_t *self)
{
self->maxs[2] = 12;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t chick_frames_death2[] = {
{ ai_move, -6 },
{ ai_move },
{ ai_move, -1 },
{ ai_move, -5, monster_footstep },
{ ai_move },
{ ai_move, -1 },
{ ai_move, -2 },
{ ai_move, 1 },
{ ai_move, 10 },
{ ai_move, 2 },
{ ai_move, 3, monster_footstep },
{ ai_move, 1 },
{ ai_move, 2 },
{ ai_move },
{ ai_move, 3 },
{ ai_move, 3 },
{ ai_move, 1, monster_footstep },
{ ai_move, -3 },
{ ai_move, -5 },
{ ai_move, 4 },
{ ai_move, 15, chick_shrink },
{ ai_move, 14, monster_footstep },
{ ai_move, 1 }
};
MMOVE_T(chick_move_death2) = { FRAME_death201, FRAME_death223, chick_frames_death2, chick_dead };
mframe_t chick_frames_death1[] = {
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move, -7 },
{ ai_move, 4, monster_footstep },
{ ai_move, 11, chick_shrink },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move }
};
MMOVE_T(chick_move_death1) = { FRAME_death101, FRAME_death112, chick_frames_death1, chick_dead };
DIE(chick_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
int n;
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
self->s.skinnum /= 2;
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 3, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/monsters/bitch/gibs/arm.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/bitch/gibs/foot.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/bitch/gibs/tube.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/bitch/gibs/chest.md2", GIB_SKINNED },
{ "models/monsters/bitch/gibs/head.md2", GIB_HEAD | GIB_SKINNED }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
self->deadflag = true;
self->takedamage = true;
n = brandom();
if (n == 0)
{
M_SetAnimation(self, &chick_move_death1);
gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
}
else
{
M_SetAnimation(self, &chick_move_death2);
gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
}
}
// PMM - changes to duck code for new dodge
mframe_t chick_frames_duck[] = {
{ ai_move, 0, monster_duck_down },
{ ai_move, 1 },
{ ai_move, 4, monster_duck_hold },
{ ai_move, -4 },
{ ai_move, -5, monster_duck_up },
{ ai_move, 3 },
{ ai_move, 1 }
};
MMOVE_T(chick_move_duck) = { FRAME_duck01, FRAME_duck07, chick_frames_duck, chick_run };
void ChickSlash(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->mins[0], 10 };
gi.sound(self, CHAN_WEAPON, sound_melee_swing, 1, ATTN_NORM, 0);
fire_hit(self, aim, irandom(10, 16), 100);
}
void ChickRocket(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
vec3_t dir;
vec3_t vec;
trace_t trace; // PMM - check target
int rocketSpeed;
// pmm - blindfire
vec3_t target;
bool blindfire = false;
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
blindfire = true;
else
blindfire = false;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_CHICK_ROCKET_1], forward, right);
// [Paril-KEX]
if (self->s.skinnum > 1)
rocketSpeed = 500;
else
rocketSpeed = 650;
// PMM
if (blindfire)
target = self->monsterinfo.blind_fire_target;
else
target = self->enemy->s.origin;
// pmm
// PGM
// PMM - blindfire shooting
if (blindfire)
{
vec = target;
dir = vec - start;
}
// pmm
// don't shoot at feet if they're above where i'm shooting from.
else if (frandom() < 0.33f || (start[2] < self->enemy->absmin[2]))
{
vec = target;
vec[2] += self->enemy->viewheight;
dir = vec - start;
}
else
{
vec = target;
vec[2] = self->enemy->absmin[2] + 1;
dir = vec - start;
}
// PGM
//======
// PMM - lead target (not when blindfiring)
// 20, 35, 50, 65 chance of leading
if ((!blindfire) && (frandom() < 0.35f))
PredictAim(self, self->enemy, start, rocketSpeed, false, 0.f, &dir, &vec);
// PMM - lead target
//======
dir.normalize();
// pmm blindfire doesn't check target (done in checkattack)
// paranoia, make sure we're not shooting a target right next to us
trace = gi.traceline(start, vec, self, MASK_PROJECTILE);
if (blindfire)
{
// blindfire has different fail criteria for the trace
if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f)))
{
// RAFAEL
if (self->s.skinnum > 1)
monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.075f);
else
// RAFAEL
monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
}
else
{
// geez, this is bad. she's avoiding about 80% of her blindfires due to hitting things.
// hunt around for a good shot
// try shifting the target to the left a little (to help counter her large offset)
vec = target;
vec += (right * -10);
dir = vec - start;
dir.normalize();
trace = gi.traceline(start, vec, self, MASK_PROJECTILE);
if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f)))
{
// RAFAEL
if (self->s.skinnum > 1)
monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.075f);
else
// RAFAEL
monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
}
else
{
// ok, that failed. try to the right
vec = target;
vec += (right * 10);
dir = vec - start;
dir.normalize();
trace = gi.traceline(start, vec, self, MASK_PROJECTILE);
if (!(trace.startsolid || trace.allsolid || (trace.fraction < 0.5f)))
{
// RAFAEL
if (self->s.skinnum > 1)
monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.075f);
else
// RAFAEL
monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
}
}
}
}
else
{
if (trace.fraction > 0.5f || trace.ent->solid != SOLID_BSP)
{
// RAFAEL
if (self->s.skinnum > 1)
monster_fire_heat(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1, 0.15f);
else
// RAFAEL
monster_fire_rocket(self, start, dir, 50, rocketSpeed, MZ2_CHICK_ROCKET_1);
}
}
}
void Chick_PreAttack1(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_missile_prelaunch, 1, ATTN_NORM, 0);
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
{
vec3_t aim = self->monsterinfo.blind_fire_target - self->s.origin;
self->ideal_yaw = vectoyaw(aim);
}
}
void ChickReload(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_missile_reload, 1, ATTN_NORM, 0);
}
mframe_t chick_frames_start_attack1[] = {
{ ai_charge, 0, Chick_PreAttack1 },
{ ai_charge },
{ ai_charge },
{ ai_charge, 4 },
{ ai_charge },
{ ai_charge, -3 },
{ ai_charge, 3 },
{ ai_charge, 5 },
{ ai_charge, 7, monster_footstep },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, chick_attack1 }
};
MMOVE_T(chick_move_start_attack1) = { FRAME_attak101, FRAME_attak113, chick_frames_start_attack1, nullptr };
mframe_t chick_frames_attack1[] = {
{ ai_charge, 19, ChickRocket },
{ ai_charge, -6, monster_footstep },
{ ai_charge, -5 },
{ ai_charge, -2 },
{ ai_charge, -7, monster_footstep },
{ ai_charge },
{ ai_charge, 1 },
{ ai_charge, 10, ChickReload },
{ ai_charge, 4 },
{ ai_charge, 5, monster_footstep },
{ ai_charge, 6 },
{ ai_charge, 6 },
{ ai_charge, 4 },
{ ai_charge, 3, [](edict_t *self) { chick_rerocket(self); monster_footstep(self); } }
};
MMOVE_T(chick_move_attack1) = { FRAME_attak114, FRAME_attak127, chick_frames_attack1, nullptr };
mframe_t chick_frames_end_attack1[] = {
{ ai_charge, -3 },
{ ai_charge },
{ ai_charge, -6 },
{ ai_charge, -4 },
{ ai_charge, -2, monster_footstep }
};
MMOVE_T(chick_move_end_attack1) = { FRAME_attak128, FRAME_attak132, chick_frames_end_attack1, chick_run };
void chick_rerocket(edict_t *self)
{
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
{
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
M_SetAnimation(self, &chick_move_end_attack1);
return;
}
if (!M_CheckClearShot(self, monster_flash_offset[MZ2_CHICK_ROCKET_1]))
{
M_SetAnimation(self, &chick_move_end_attack1);
return;
}
if (self->enemy->health > 0)
{
if (range_to(self, self->enemy) > RANGE_MELEE)
if (visible(self, self->enemy))
if (frandom() <= 0.7f)
{
M_SetAnimation(self, &chick_move_attack1);
return;
}
}
M_SetAnimation(self, &chick_move_end_attack1);
}
void chick_attack1(edict_t *self)
{
M_SetAnimation(self, &chick_move_attack1);
}
mframe_t chick_frames_slash[] = {
{ ai_charge, 1 },
{ ai_charge, 7, ChickSlash },
{ ai_charge, -7, monster_footstep },
{ ai_charge, 1 },
{ ai_charge, -1 },
{ ai_charge, 1 },
{ ai_charge },
{ ai_charge, 1 },
{ ai_charge, -2, chick_reslash }
};
MMOVE_T(chick_move_slash) = { FRAME_attak204, FRAME_attak212, chick_frames_slash, nullptr };
mframe_t chick_frames_end_slash[] = {
{ ai_charge, -6 },
{ ai_charge, -1 },
{ ai_charge, -6 },
{ ai_charge, 0, monster_footstep }
};
MMOVE_T(chick_move_end_slash) = { FRAME_attak213, FRAME_attak216, chick_frames_end_slash, chick_run };
void chick_reslash(edict_t *self)
{
if (self->enemy->health > 0)
{
if (range_to(self, self->enemy) <= RANGE_MELEE)
{
if (frandom() <= 0.9f)
{
M_SetAnimation(self, &chick_move_slash);
return;
}
else
{
M_SetAnimation(self, &chick_move_end_slash);
return;
}
}
}
M_SetAnimation(self, &chick_move_end_slash);
}
void chick_slash(edict_t *self)
{
M_SetAnimation(self, &chick_move_slash);
}
mframe_t chick_frames_start_slash[] = {
{ ai_charge, 1 },
{ ai_charge, 8 },
{ ai_charge, 3 }
};
MMOVE_T(chick_move_start_slash) = { FRAME_attak201, FRAME_attak203, chick_frames_start_slash, chick_slash };
MONSTERINFO_MELEE(chick_melee) (edict_t *self) -> void
{
M_SetAnimation(self, &chick_move_start_slash);
}
MONSTERINFO_ATTACK(chick_attack) (edict_t *self) -> void
{
if (!M_CheckClearShot(self, monster_flash_offset[MZ2_CHICK_ROCKET_1]))
return;
float r, chance;
monster_done_dodge(self);
// PMM
if (self->monsterinfo.attack_state == AS_BLIND)
{
// setup shot probabilities
if (self->monsterinfo.blind_fire_delay < 1.0_sec)
chance = 1.0;
else if (self->monsterinfo.blind_fire_delay < 7.5_sec)
chance = 0.4f;
else
chance = 0.1f;
r = frandom();
// minimum of 5.5 seconds, plus 0-1, after the shots are done
self->monsterinfo.blind_fire_delay += random_time(5.5_sec, 6.5_sec);
// don't shoot at the origin
if (!self->monsterinfo.blind_fire_target)
return;
// don't shoot if the dice say not to
if (r > chance)
return;
// turn on manual steering to signal both manual steering and blindfire
self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
M_SetAnimation(self, &chick_move_start_attack1);
self->monsterinfo.attack_finished = level.time + random_time(2_sec);
return;
}
// pmm
M_SetAnimation(self, &chick_move_start_attack1);
}
MONSTERINFO_SIGHT(chick_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
//===========
// PGM
MONSTERINFO_BLOCKED(chick_blocked) (edict_t *self, float dist) -> bool
{
if (blocked_checkplat(self, dist))
return true;
return false;
}
// PGM
//===========
MONSTERINFO_DUCK(chick_duck) (edict_t *self, gtime_t eta) -> bool
{
if ((self->monsterinfo.active_move == &chick_move_start_attack1) ||
(self->monsterinfo.active_move == &chick_move_attack1))
{
// if we're shooting don't dodge
self->monsterinfo.unduck(self);
return false;
}
M_SetAnimation(self, &chick_move_duck);
return true;
}
MONSTERINFO_SIDESTEP(chick_sidestep) (edict_t *self) -> bool
{
if ((self->monsterinfo.active_move == &chick_move_start_attack1) ||
(self->monsterinfo.active_move == &chick_move_attack1) ||
(self->monsterinfo.active_move == &chick_move_pain3))
{
// if we're shooting, don't dodge
return false;
}
if (self->monsterinfo.active_move != &chick_move_run)
M_SetAnimation(self, &chick_move_run);
return true;
}
/*QUAKED monster_chick (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
*/
void SP_monster_chick(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/bitch/tris.md2");
gi.modelindex("models/monsters/bitch/gibs/arm.md2");
gi.modelindex("models/monsters/bitch/gibs/chest.md2");
gi.modelindex("models/monsters/bitch/gibs/foot.md2");
gi.modelindex("models/monsters/bitch/gibs/head.md2");
gi.modelindex("models/monsters/bitch/gibs/tube.md2");
self->mins = { -16, -16, 0 };
self->maxs = { 16, 16, 56 };
self->health = 175 * st.health_multiplier;
self->gib_health = -70;
self->mass = 200;
self->pain = chick_pain;
self->die = chick_die;
self->monsterinfo.stand = chick_stand;
self->monsterinfo.walk = chick_walk;
self->monsterinfo.run = chick_run;
// pmm
self->monsterinfo.dodge = M_MonsterDodge;
self->monsterinfo.duck = chick_duck;
self->monsterinfo.unduck = monster_duck_up;
self->monsterinfo.sidestep = chick_sidestep;
self->monsterinfo.blocked = chick_blocked; // PGM
// pmm
self->monsterinfo.attack = chick_attack;
self->monsterinfo.melee = chick_melee;
self->monsterinfo.sight = chick_sight;
self->monsterinfo.setskin = chick_setpain;
gi.linkentity(self);
M_SetAnimation(self, &chick_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
// PMM
self->monsterinfo.blindfire = true;
// pmm
walkmonster_start(self);
}
// RAFAEL
/*QUAKED monster_chick_heat (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
*/
void SP_monster_chick_heat(edict_t *self)
{
SP_monster_chick(self);
self->s.skinnum = 2;
gi.soundindex("weapons/railgr1a.wav");
}
// RAFAEL

299
rerelease/m_chick.h Normal file
View File

@@ -0,0 +1,299 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/bitch
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak116,
FRAME_attak117,
FRAME_attak118,
FRAME_attak119,
FRAME_attak120,
FRAME_attak121,
FRAME_attak122,
FRAME_attak123,
FRAME_attak124,
FRAME_attak125,
FRAME_attak126,
FRAME_attak127,
FRAME_attak128,
FRAME_attak129,
FRAME_attak130,
FRAME_attak131,
FRAME_attak132,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_attak214,
FRAME_attak215,
FRAME_attak216,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death108,
FRAME_death109,
FRAME_death110,
FRAME_death111,
FRAME_death112,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_death206,
FRAME_death207,
FRAME_death208,
FRAME_death209,
FRAME_death210,
FRAME_death211,
FRAME_death212,
FRAME_death213,
FRAME_death214,
FRAME_death215,
FRAME_death216,
FRAME_death217,
FRAME_death218,
FRAME_death219,
FRAME_death220,
FRAME_death221,
FRAME_death222,
FRAME_death223,
FRAME_duck01,
FRAME_duck02,
FRAME_duck03,
FRAME_duck04,
FRAME_duck05,
FRAME_duck06,
FRAME_duck07,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_pain310,
FRAME_pain311,
FRAME_pain312,
FRAME_pain313,
FRAME_pain314,
FRAME_pain315,
FRAME_pain316,
FRAME_pain317,
FRAME_pain318,
FRAME_pain319,
FRAME_pain320,
FRAME_pain321,
FRAME_stand101,
FRAME_stand102,
FRAME_stand103,
FRAME_stand104,
FRAME_stand105,
FRAME_stand106,
FRAME_stand107,
FRAME_stand108,
FRAME_stand109,
FRAME_stand110,
FRAME_stand111,
FRAME_stand112,
FRAME_stand113,
FRAME_stand114,
FRAME_stand115,
FRAME_stand116,
FRAME_stand117,
FRAME_stand118,
FRAME_stand119,
FRAME_stand120,
FRAME_stand121,
FRAME_stand122,
FRAME_stand123,
FRAME_stand124,
FRAME_stand125,
FRAME_stand126,
FRAME_stand127,
FRAME_stand128,
FRAME_stand129,
FRAME_stand130,
FRAME_stand201,
FRAME_stand202,
FRAME_stand203,
FRAME_stand204,
FRAME_stand205,
FRAME_stand206,
FRAME_stand207,
FRAME_stand208,
FRAME_stand209,
FRAME_stand210,
FRAME_stand211,
FRAME_stand212,
FRAME_stand213,
FRAME_stand214,
FRAME_stand215,
FRAME_stand216,
FRAME_stand217,
FRAME_stand218,
FRAME_stand219,
FRAME_stand220,
FRAME_stand221,
FRAME_stand222,
FRAME_stand223,
FRAME_stand224,
FRAME_stand225,
FRAME_stand226,
FRAME_stand227,
FRAME_stand228,
FRAME_stand229,
FRAME_stand230,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_walk21,
FRAME_walk22,
FRAME_walk23,
FRAME_walk24,
FRAME_walk25,
FRAME_walk26,
FRAME_walk27,
FRAME_recln201,
FRAME_recln202,
FRAME_recln203,
FRAME_recln204,
FRAME_recln205,
FRAME_recln206,
FRAME_recln207,
FRAME_recln208,
FRAME_recln209,
FRAME_recln210,
FRAME_recln211,
FRAME_recln212,
FRAME_recln213,
FRAME_recln214,
FRAME_recln215,
FRAME_recln216,
FRAME_recln217,
FRAME_recln218,
FRAME_recln219,
FRAME_recln220,
FRAME_recln221,
FRAME_recln222,
FRAME_recln223,
FRAME_recln224,
FRAME_recln225,
FRAME_recln226,
FRAME_recln227,
FRAME_recln228,
FRAME_recln229,
FRAME_recln230,
FRAME_recln231,
FRAME_recln232,
FRAME_recln233,
FRAME_recln234,
FRAME_recln235,
FRAME_recln236,
FRAME_recln237,
FRAME_recln238,
FRAME_recln239,
FRAME_recln240,
FRAME_recln101,
FRAME_recln102,
FRAME_recln103,
FRAME_recln104,
FRAME_recln105,
FRAME_recln106,
FRAME_recln107,
FRAME_recln108,
FRAME_recln109,
FRAME_recln110,
FRAME_recln111,
FRAME_recln112,
FRAME_recln113,
FRAME_recln114,
FRAME_recln115,
FRAME_recln116,
FRAME_recln117,
FRAME_recln118,
FRAME_recln119,
FRAME_recln120,
FRAME_recln121,
FRAME_recln122,
FRAME_recln123,
FRAME_recln124,
FRAME_recln125,
FRAME_recln126,
FRAME_recln127,
FRAME_recln128,
FRAME_recln129,
FRAME_recln130,
FRAME_recln131,
FRAME_recln132,
FRAME_recln133,
FRAME_recln134,
FRAME_recln135,
FRAME_recln136,
FRAME_recln137,
FRAME_recln138,
FRAME_recln139,
FRAME_recln140
};
constexpr float MODEL_SCALE = 1.000000f;

619
rerelease/m_flash.h Normal file
View File

@@ -0,0 +1,619 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// m_flash.h
// this file is included in both the game dll and quake2,
// the game needs it to source shot locations, the client
// needs it to position muzzle flashes
constexpr vec3_t monster_flash_offset[] = {
// flash 0 is not used
{ 0.0f, 0.0f, 0.0f },
// MZ2_TANK_BLASTER_1 1
{ 28.7f, -18.5f, 28.7f },
// MZ2_TANK_BLASTER_2 2
{ 24.6f, -21.5f, 30.1f },
// MZ2_TANK_BLASTER_3 3
{ 19.8f, -23.9f, 32.1f },
// MZ2_TANK_MACHINEGUN_1 4
{ 22.9f, -0.7f, 25.3f },
// MZ2_TANK_MACHINEGUN_2 5
{ 22.2f, 6.2f, 22.3f },
// MZ2_TANK_MACHINEGUN_3 6
{ 19.4f, 13.1f, 18.6f },
// MZ2_TANK_MACHINEGUN_4 7
{ 19.4f, 18.8f, 18.6f },
// MZ2_TANK_MACHINEGUN_5 8
{ 17.9f, 25.0f, 18.6f },
// MZ2_TANK_MACHINEGUN_6 9
{ 14.1f, 30.5f, 20.6f },
// MZ2_TANK_MACHINEGUN_7 10
{ 9.3f, 35.3f, 22.1f },
// MZ2_TANK_MACHINEGUN_8 11
{ 4.7f, 38.4f, 22.1f },
// MZ2_TANK_MACHINEGUN_9 12
{ -1.1f, 40.4f, 24.1f },
// MZ2_TANK_MACHINEGUN_10 13
{ -6.5f, 41.2f, 24.1f },
// MZ2_TANK_MACHINEGUN_11 14
{ 3.2f, 40.1f, 24.7f },
// MZ2_TANK_MACHINEGUN_12 15
{ 11.7f, 36.7f, 26.0f },
// MZ2_TANK_MACHINEGUN_13 16
{ 18.9f, 31.3f, 26.0f },
// MZ2_TANK_MACHINEGUN_14 17
{ 24.4f, 24.4f, 26.4f },
// MZ2_TANK_MACHINEGUN_15 18
{ 27.1f, 17.1f, 27.2f },
// MZ2_TANK_MACHINEGUN_16 19
{ 28.5f, 9.1f, 28.0f },
// MZ2_TANK_MACHINEGUN_17 20
{ 27.1f, 2.2f, 28.0f },
// MZ2_TANK_MACHINEGUN_18 21
{ 24.9f, -2.8f, 28.0f },
// MZ2_TANK_MACHINEGUN_19 22
{ 21.6f, -7.0f, 26.4f },
// MZ2_TANK_ROCKET_1 23
{ 6.2f, 29.1f, 49.1f },
// MZ2_TANK_ROCKET_2 24
{ 6.9f, 23.8f, 49.1f },
// MZ2_TANK_ROCKET_3 25
{ 8.3f, 17.8f, 49.5f },
// MZ2_INFANTRY_MACHINEGUN_1 26
{ 26.6f, 7.1f, 13.1f },
// MZ2_INFANTRY_MACHINEGUN_2 27
{ 18.2f, 7.5f, 15.4f },
// MZ2_INFANTRY_MACHINEGUN_3 28
{ 17.2f, 10.3f, 17.9f },
// MZ2_INFANTRY_MACHINEGUN_4 29
{ 17.0f, 12.8f, 20.1f },
// MZ2_INFANTRY_MACHINEGUN_5 30
{ 15.1f, 14.1f, 21.8f },
// MZ2_INFANTRY_MACHINEGUN_6 31
{ 11.8f, 17.2f, 23.1f },
// MZ2_INFANTRY_MACHINEGUN_7 32
{ 11.4f, 20.2f, 21.0f },
// MZ2_INFANTRY_MACHINEGUN_8 33
{ 9.0f, 23.0f, 18.9f },
// MZ2_INFANTRY_MACHINEGUN_9 34
{ 13.9f, 18.6f, 17.7f },
// MZ2_INFANTRY_MACHINEGUN_10 35
{ 15.4f, 15.6f, 15.8f },
// MZ2_INFANTRY_MACHINEGUN_11 36
{ 10.2f, 15.2f, 25.1f },
// MZ2_INFANTRY_MACHINEGUN_12 37
{ -1.9f, 15.1f, 28.2f },
// MZ2_INFANTRY_MACHINEGUN_13 38
{ -12.4f, 13.0f, 20.2f },
// MZ2_SOLDIER_BLASTER_1 39
{ 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f },
// MZ2_SOLDIER_BLASTER_2 40
{ 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_1 41
{ 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_2 42
{ 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_1 43
{ 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_2 44
{ 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f },
// MZ2_GUNNER_MACHINEGUN_1 45
{ 30.1f * 1.15f, 3.9f * 1.15f, 19.6f * 1.15f },
// MZ2_GUNNER_MACHINEGUN_2 46
{ 29.1f * 1.15f, 2.5f * 1.15f, 20.7f * 1.15f },
// MZ2_GUNNER_MACHINEGUN_3 47
{ 28.2f * 1.15f, 2.5f * 1.15f, 22.2f * 1.15f },
// MZ2_GUNNER_MACHINEGUN_4 48
{ 28.2f * 1.15f, 3.6f * 1.15f, 22.0f * 1.15f },
// MZ2_GUNNER_MACHINEGUN_5 49
{ 26.9f * 1.15f, 2.0f * 1.15f, 23.4f * 1.15f },
// MZ2_GUNNER_MACHINEGUN_6 50
{ 26.5f * 1.15f, 0.6f * 1.15f, 20.8f * 1.15f },
// MZ2_GUNNER_MACHINEGUN_7 51
{ 26.9f * 1.15f, 0.5f * 1.15f, 21.5f * 1.15f },
// MZ2_GUNNER_MACHINEGUN_8 52
{ 29.0f * 1.15f, 2.4f * 1.15f, 19.5f * 1.15f },
// MZ2_GUNNER_GRENADE_1 53
{ 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f },
// MZ2_GUNNER_GRENADE_2 54
{ 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f },
// MZ2_GUNNER_GRENADE_3 55
{ 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f },
// MZ2_GUNNER_GRENADE_4 56
{ 4.6f * 1.15f, -16.8f * 1.15f, 7.3f * 1.15f },
// MZ2_CHICK_ROCKET_1 57
// -24.8f, -9.0f, 39.0f,
{ 24.8f, -9.0f, 39.0f }, // PGM - this was incorrect in Q2
// MZ2_FLYER_BLASTER_1 58
{ 14.1f, 13.4f, -7.0f },
// MZ2_FLYER_BLASTER_2 59
{ 14.1f, -13.4f, -7.0f },
// MZ2_MEDIC_BLASTER_1 60
{ 44.0f, 3.0f, 14.4f },
// MZ2_GLADIATOR_RAILGUN_1 61
{ 30.0f, 18.0f, 28.0f },
// MZ2_HOVER_BLASTER_1 62
{ 1.7f, 7.0f, 11.3f },
// MZ2_ACTOR_MACHINEGUN_1 63
{ 18.4f, 7.4f, 9.6f },
// MZ2_SUPERTANK_MACHINEGUN_1 64
{ 30.0f, 39.0f, 85.5f },
// MZ2_SUPERTANK_MACHINEGUN_2 65
{ 30.0f, 39.0f, 85.5f },
// MZ2_SUPERTANK_MACHINEGUN_3 66
{ 30.0f, 39.0f, 85.5f },
// MZ2_SUPERTANK_MACHINEGUN_4 67
{ 30.0f, 39.0f, 85.5f },
// MZ2_SUPERTANK_MACHINEGUN_5 68
{ 30.0f, 39.0f, 85.5f },
// MZ2_SUPERTANK_MACHINEGUN_6 69
{ 30.0f, 39.0f, 85.5f },
// MZ2_SUPERTANK_ROCKET_1 70
{ 16.0f, -22.5f, 108.7f },
// MZ2_SUPERTANK_ROCKET_2 71
{ 16.0f, -33.4f, 106.7f },
// MZ2_SUPERTANK_ROCKET_3 72
{ 16.0f, -42.8f, 104.7f },
// --- Start Xian Stuff ---
// MZ2_BOSS2_MACHINEGUN_L1 73
{ 32.f, -40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_L2 74
{ 32.f, -40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_L3 75
{ 32.f, -40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_L4 76
{ 32.f, -40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_L5 77
{ 32.f, -40.f, 70.f },
// --- End Xian Stuff
// MZ2_BOSS2_ROCKET_1 78
{ 22.0f, 16.0f, 10.0f },
// MZ2_BOSS2_ROCKET_2 79
{ 22.0f, 8.0f, 10.0f },
// MZ2_BOSS2_ROCKET_3 80
{ 22.0f, -8.0f, 10.0f },
// MZ2_BOSS2_ROCKET_4 81
{ 22.0f, -16.0f, 10.0f },
// MZ2_FLOAT_BLASTER_1 82
{ 32.5f, -0.8f, 10.f },
// MZ2_SOLDIER_BLASTER_3 83
{ 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_3 84
{ 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_3 85
{ 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f },
// MZ2_SOLDIER_BLASTER_4 86
{ 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_4 87
{ 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_4 88
{ 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f },
// MZ2_SOLDIER_BLASTER_5 89
{ 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_5 90
{ 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_5 91
{ 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f },
// MZ2_SOLDIER_BLASTER_6 92
{ 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_6 93
{ 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_6 94
{ 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f },
// MZ2_SOLDIER_BLASTER_7 95
{ 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_7 96
{ 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_7 97
{ 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f },
// MZ2_SOLDIER_BLASTER_8 98
{ 31.5f * 1.2f, 9.6f * 1.2f, 10.1f * 1.2f },
// MZ2_SOLDIER_SHOTGUN_8 99
{ 34.5f * 1.2f, 9.6f * 1.2f, 6.1f * 1.2f },
// MZ2_SOLDIER_MACHINEGUN_8 100
{ 34.5f * 1.2f, 9.6f * 1.2f, 6.1f * 1.2f },
// --- Xian shit below ---
// MZ2_MAKRON_BFG 101
{ 17.f, -19.5f, 62.9f },
// MZ2_MAKRON_BLASTER_1 102
{ -3.6f, -24.1f, 59.5f },
// MZ2_MAKRON_BLASTER_2 103
{ -1.6f, -19.3f, 59.5f },
// MZ2_MAKRON_BLASTER_3 104
{ -0.1f, -14.4f, 59.5f },
// MZ2_MAKRON_BLASTER_4 105
{ 2.0f, -7.6f, 59.5f },
// MZ2_MAKRON_BLASTER_5 106
{ 3.4f, 1.3f, 59.5f },
// MZ2_MAKRON_BLASTER_6 107
{ 3.7f, 11.1f, 59.5f },
// MZ2_MAKRON_BLASTER_7 108
{ -0.3f, 22.3f, 59.5f },
// MZ2_MAKRON_BLASTER_8 109
{ -6.f, 33.f, 59.5f },
// MZ2_MAKRON_BLASTER_9 110
{ -9.3f, 36.4f, 59.5f },
// MZ2_MAKRON_BLASTER_10 111
{ -7.f, 35.f, 59.5f },
// MZ2_MAKRON_BLASTER_11 112
{ -2.1f, 29.f, 59.5f },
// MZ2_MAKRON_BLASTER_12 113
{ 3.9f, 17.3f, 59.5f },
// MZ2_MAKRON_BLASTER_13 114
{ 6.1f, 5.8f, 59.5f },
// MZ2_MAKRON_BLASTER_14 115
{ 5.9f, -4.4f, 59.5f },
// MZ2_MAKRON_BLASTER_15 116
{ 4.2f, -14.1f, 59.5f },
// MZ2_MAKRON_BLASTER_16 117
{ 2.4f, -18.8f, 59.5f },
// MZ2_MAKRON_BLASTER_17 118
{ -1.8f, -25.5f, 59.5f },
// MZ2_MAKRON_RAILGUN_1 119
{ 18.1f, 7.8f, 74.4f },
// MZ2_JORG_MACHINEGUN_L1 120
{ 78.5f, -47.1f, 96.f },
// MZ2_JORG_MACHINEGUN_L2 121
{ 78.5f, -47.1f, 96.f },
// MZ2_JORG_MACHINEGUN_L3 122
{ 78.5f, -47.1f, 96.f },
// MZ2_JORG_MACHINEGUN_L4 123
{ 78.5f, -47.1f, 96.f },
// MZ2_JORG_MACHINEGUN_L5 124
{ 78.5f, -47.1f, 96.f },
// MZ2_JORG_MACHINEGUN_L6 125
{ 78.5f, -47.1f, 96.f },
// MZ2_JORG_MACHINEGUN_R1 126
{ 78.5f, 46.7f, 96.f },
// MZ2_JORG_MACHINEGUN_R2 127
{ 78.5f, 46.7f, 96.f },
// MZ2_JORG_MACHINEGUN_R3 128
{ 78.5f, 46.7f, 96.f },
// MZ2_JORG_MACHINEGUN_R4 129
{ 78.5f, 46.7f, 96.f },
// MZ2_JORG_MACHINEGUN_R5 130
{ 78.5f, 46.7f, 96.f },
// MZ2_JORG_MACHINEGUN_R6 131
{ 78.5f, 46.7f, 96.f },
// MZ2_JORG_BFG_1 132
{ 6.3f, -9.f, 111.2f },
// MZ2_BOSS2_MACHINEGUN_R1 73
{ 32.f, 40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_R2 74
{ 32.f, 40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_R3 75
{ 32.f, 40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_R4 76
{ 32.f, 40.f, 70.f },
// MZ2_BOSS2_MACHINEGUN_R5 77
{ 32.f, 40.f, 70.f },
// --- End Xian Shit ---
// ROGUE
// note that the above really ends at 137
// carrier machineguns
// MZ2_CARRIER_MACHINEGUN_L1
{ 56.f, -32.f, 32.f },
// MZ2_CARRIER_MACHINEGUN_R1
{ 56.f, 32.f, 32.f },
// MZ2_CARRIER_GRENADE
{ 42.f, 24.f, 50.f },
// MZ2_TURRET_MACHINEGUN 141
{ 20.f, 0.f, 0.f },
// MZ2_TURRET_ROCKET 142
{ 20.f, 0.f, 0.f },
// MZ2_TURRET_BLASTER 143
{ 20.f, 0.f, 0.f },
// MZ2_STALKER_BLASTER 144
{ 24.f, 0.f, 6.f },
// MZ2_DAEDALUS_BLASTER 145
{ 1.7f, 7.0f, 11.3f },
// MZ2_MEDIC_BLASTER_2 146
{ 44.0f, 3.0f, 14.4f },
// MZ2_CARRIER_RAILGUN 147
{ 32.f, 0.f, 6.f },
// MZ2_WIDOW_DISRUPTOR 148
{ 64.72f, 14.50f, 88.81f },
// MZ2_WIDOW_BLASTER 149
{ 56.f, 32.f, 32.f },
// MZ2_WIDOW_RAIL 150
{ 62.f, -20.f, 84.f },
// MZ2_WIDOW_PLASMABEAM 151 // PMM - not used!
{ 32.f, 0.f, 6.f },
// MZ2_CARRIER_MACHINEGUN_L2 152
{ 61.f, -32.f, 12.f },
// MZ2_CARRIER_MACHINEGUN_R2 153
{ 61.f, 32.f, 12.f },
// MZ2_WIDOW_RAIL_LEFT 154
{ 17.f, -62.f, 91.f },
// MZ2_WIDOW_RAIL_RIGHT 155
{ 68.f, 12.f, 86.f },
// MZ2_WIDOW_BLASTER_SWEEP1 156 pmm - the sweeps need to be in sequential order
{ 47.5f, 56.f, 89.f },
// MZ2_WIDOW_BLASTER_SWEEP2 157
{ 54.f, 52.f, 91.f },
// MZ2_WIDOW_BLASTER_SWEEP3 158
{ 58.f, 40.f, 91.f },
// MZ2_WIDOW_BLASTER_SWEEP4 159
{ 68.f, 30.f, 88.f },
// MZ2_WIDOW_BLASTER_SWEEP5 160
{ 74.f, 20.f, 88.f },
// MZ2_WIDOW_BLASTER_SWEEP6 161
{ 73.f, 11.f, 87.f },
// MZ2_WIDOW_BLASTER_SWEEP7 162
{ 73.f, 3.f, 87.f },
// MZ2_WIDOW_BLASTER_SWEEP8 163
{ 70.f, -12.f, 87.f },
// MZ2_WIDOW_BLASTER_SWEEP9 164
{ 67.f, -20.f, 90.f },
// MZ2_WIDOW_BLASTER_100 165
{ -20.f, 76.f, 90.f },
// MZ2_WIDOW_BLASTER_90 166
{ -8.f, 74.f, 90.f },
// MZ2_WIDOW_BLASTER_80 167
{ 0.f, 72.f, 90.f },
// MZ2_WIDOW_BLASTER_70 168 d06
{ 10.f, 71.f, 89.f },
// MZ2_WIDOW_BLASTER_60 169 d07
{ 23.f, 70.f, 87.f },
// MZ2_WIDOW_BLASTER_50 170 d08
{ 32.f, 64.f, 85.f },
// MZ2_WIDOW_BLASTER_40 171
{ 40.f, 58.f, 84.f },
// MZ2_WIDOW_BLASTER_30 172 d10
{ 48.f, 50.f, 83.f },
// MZ2_WIDOW_BLASTER_20 173
{ 54.f, 42.f, 82.f },
// MZ2_WIDOW_BLASTER_10 174 d12
{ 56.f, 34.f, 82.f },
// MZ2_WIDOW_BLASTER_0 175
{ 58.f, 26.f, 82.f },
// MZ2_WIDOW_BLASTER_10L 176 d14
{ 60.f, 16.f, 82.f },
// MZ2_WIDOW_BLASTER_20L 177
{ 59.f, 6.f, 81.f },
// MZ2_WIDOW_BLASTER_30L 178 d16
{ 58.f, -2.f, 80.f },
// MZ2_WIDOW_BLASTER_40L 179
{ 57.f, -10.f, 79.f },
// MZ2_WIDOW_BLASTER_50L 180 d18
{ 54.f, -18.f, 78.f },
// MZ2_WIDOW_BLASTER_60L 181
{ 42.f, -32.f, 80.f },
// MZ2_WIDOW_BLASTER_70L 182 d20
{ 36.f, -40.f, 78.f },
// MZ2_WIDOW_RUN_1 183
{ 68.4f, 10.88f, 82.08f },
// MZ2_WIDOW_RUN_2 184
{ 68.51f, 8.64f, 85.14f },
// MZ2_WIDOW_RUN_3 185
{ 68.66f, 6.38f, 88.78f },
// MZ2_WIDOW_RUN_4 186
{ 68.73f, 5.1f, 84.47f },
// MZ2_WIDOW_RUN_5 187
{ 68.82f, 4.79f, 80.52f },
// MZ2_WIDOW_RUN_6 188
{ 68.77f, 6.11f, 85.37f },
// MZ2_WIDOW_RUN_7 189
{ 68.67f, 7.99f, 90.24f },
// MZ2_WIDOW_RUN_8 190
{ 68.55f, 9.54f, 87.36f },
// MZ2_CARRIER_ROCKET_1 191
{ 0.f, 0.f, -5.f },
// MZ2_CARRIER_ROCKET_2 192
{ 0.f, 0.f, -5.f },
// MZ2_CARRIER_ROCKET_3 193
{ 0.f, 0.f, -5.f },
// MZ2_CARRIER_ROCKET_4 194
{ 0.f, 0.f, -5.f },
// MZ2_WIDOW2_BEAMER_1 195
// 72.13f, -17.63f, 93.77f,
{ 69.00f, -17.63f, 93.77f },
// MZ2_WIDOW2_BEAMER_2 196
// 71.46f, -17.08f, 89.82f,
{ 69.00f, -17.08f, 89.82f },
// MZ2_WIDOW2_BEAMER_3 197
// 71.47f, -18.40f, 90.70f,
{ 69.00f, -18.40f, 90.70f },
// MZ2_WIDOW2_BEAMER_4 198
// 71.96f, -18.34f, 94.32f,
{ 69.00f, -18.34f, 94.32f },
// MZ2_WIDOW2_BEAMER_5 199
// 72.25f, -18.30f, 97.98f,
{ 69.00f, -18.30f, 97.98f },
// MZ2_WIDOW2_BEAM_SWEEP_1 200
{ 45.04f, -59.02f, 92.24f },
// MZ2_WIDOW2_BEAM_SWEEP_2 201
{ 50.68f, -54.70f, 91.96f },
// MZ2_WIDOW2_BEAM_SWEEP_3 202
{ 56.57f, -47.72f, 91.65f },
// MZ2_WIDOW2_BEAM_SWEEP_4 203
{ 61.75f, -38.75f, 91.38f },
// MZ2_WIDOW2_BEAM_SWEEP_5 204
{ 65.55f, -28.76f, 91.24f },
// MZ2_WIDOW2_BEAM_SWEEP_6 205
{ 67.79f, -18.90f, 91.22f },
// MZ2_WIDOW2_BEAM_SWEEP_7 206
{ 68.60f, -9.52f, 91.23f },
// MZ2_WIDOW2_BEAM_SWEEP_8 207
{ 68.08f, 0.18f, 91.32f },
// MZ2_WIDOW2_BEAM_SWEEP_9 208
{ 66.14f, 9.79f, 91.44f },
// MZ2_WIDOW2_BEAM_SWEEP_10 209
{ 62.77f, 18.91f, 91.65f },
// MZ2_WIDOW2_BEAM_SWEEP_11 210
{ 58.29f, 27.11f, 92.00f },
// MZ2_SOLDIER_RIPPER_1 211
{ 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f },
// MZ2_SOLDIER_RIPPER_2 212
{ 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f },
// MZ2_SOLDIER_RIPPER_3 213
{ 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f },
// MZ2_SOLDIER_RIPPER_4 214
{ 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f },
// MZ2_SOLDIER_RIPPER_5 215
{ 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f },
// MZ2_SOLDIER_RIPPER_6 216
{ 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f },
// MZ2_SOLDIER_RIPPER_7 217
{ 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f },
// MZ2_SOLDIER_RIPPER_8 218
{ 31.5f * 1.2f, 9.6f * 1.2f, 10.1f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_1 219
{ 10.6f * 1.2f, 7.7f * 1.2f, 7.8f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_2 220
{ 25.1f * 1.2f, 3.6f * 1.2f, 19.0f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_3 221
{ 20.8f * 1.2f, 10.1f * 1.2f, -2.7f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_4 222
{ 7.6f * 1.2f, 9.3f * 1.2f, 0.8f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_5 223
{ 30.5f * 1.2f, 9.9f * 1.2f, -18.7f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_6 224
{ 27.6f * 1.2f, 3.4f * 1.2f, -10.4f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_7 225
{ 28.9f * 1.2f, 4.6f * 1.2f, -8.1f * 1.2f },
// MZ2_SOLDIER_HYPERGUN_8 226
{ 31.5f * 1.2f, 9.6f * 1.2f, 10.1f * 1.2f },
// MZ2_GUARDIAN_BLASTER 227
{ 88.f, 50.f, 60.f },
// MZ2_ARACHNID_RAIL1 228
{ 58.f, 20.f, 17.2f },
// MZ2_ARACHNID_RAIL2 229
{ 64.f, -22.f, 24.f },
// MZ2_ARACHNID_RAIL_UP1 230
{ 37.f, 13.f, 72.f },
// MZ2_ARACHNID_RAIL_UP2 231
{ 58.f, -25.f, 72.f },
// MZ2_INFANTRY_MACHINEGUN_14 232
{ 34.f, 11.f, 13.f },
// MZ2_INFANTRY_MACHINEGUN_15 233
{ 28.f, 13.f, 10.5f },
// MZ2_INFANTRY_MACHINEGUN_16 234
{ 29.f, 13.f, 8.5f },
// MZ2_INFANTRY_MACHINEGUN_17 235
{ 30.f, 12.5f, 12.f },
// MZ2_INFANTRY_MACHINEGUN_18 236
{ 29.f, 12.5f, 14.7f },
// MZ2_INFANTRY_MACHINEGUN_19 237
{ 30.f, 6.5f, 12.f },
// MZ2_INFANTRY_MACHINEGUN_20 238
{ 29.f, 1.5f, 8.5f },
// MZ2_INFANTRY_MACHINEGUN_21 239
{ 29.f, 6.0f, 10.f },
// MZ2_GUNCMDR_CHAINGUN_1 240
{ 25.0f, 11.f, 21.f },
// MZ2_GUNCMDR_CHAINGUN_2 241
{ 26.5f, 5.f, 21.f },
// MZ2_GUNCMDR_GRENADE_MORTAR_1 242
{ 27.f, 6.5f, 4.0f },
// MZ2_GUNCMDR_GRENADE_MORTAR_2 243
{ 28.f, 4.f, 4.0f },
// MZ2_GUNCMDR_GRENADE_MORTAR_3 244
{ 27.f, 1.7f, 4.0f },
// MZ2_GUNCMDR_GRENADE_FRONT_1 245
{ 21.7f, -1.5f, 22.5f },
// MZ2_GUNCMDR_GRENADE_FRONT_2 246
{ 22.f, 0.f, 20.5f },
// MZ2_GUNCMDR_GRENADE_FRONT_3 247
{ 22.5f, 3.7f, 20.5f },
// MZ2_GUNCMDR_GRENADE_CROUCH_1 248
{ 8.0f, 40.0f, 18.0f },
// MZ2_GUNCMDR_GRENADE_CROUCH_2 249
{ 29.0f, 16.0f, 19.0f },
// MZ2_GUNCMDR_GRENADE_CROUCH_3 250
{ 4.7f, -30.0f, 20.0f },
// MZ2_SOLDIER_BLASTER_9 251
{ 36.33f, 12.24f, -17.39f },
// MZ2_SOLDIER_SHOTGUN_9 252
{ 36.33f, 12.24f, -17.39f },
// MZ2_SOLDIER_MACHINEGUN_9 253
{ 36.33f, 12.24f, -17.39f },
// MZ2_SOLDIER_RIPPER_9 254
{ 36.33f, 12.24f, -17.39f },
// MZ2_SOLDIER_HYPERGUN_9 255
{ 36.33f, 12.24f, -17.39f },
// MZ2_GUNNER_GRENADE2_1
{ 36.f, -6.2f, 19.59f },
// MZ2_GUNNER_GRENADE2_2
{ 36.f, -6.2f, 19.59f },
// MZ2_GUNNER_GRENADE2_3
{ 36.f, -6.2f, 19.59f },
// MZ2_GUNNER_GRENADE2_4
{ 36.f, -6.2f, 19.59f },
// MZ2_INFANTRY_MACHINEGUN_22
{ 14.8f, 10.5f, 8.82f },
// MZ2_SUPERTANK_GRENADE_1
{ 31.31f, -37.f, 54.32f },
// MZ2_SUPERTANK_GRENADE_2
{ 31.31f, 37.f, 54.32f },
// MZ2_HOVER_BLASTER_2
{ 1.7f, -7.0f, 11.3f },
// MZ2_DAEDALUS_BLASTER_2
{ 1.7f, -7.0f, 11.3f },
// MZ2_MEDIC_HYPERBLASTER1_1-12
{ 33.0f + 1.f, 12.5f, 15.0f },
{ 32.4f + 1.f, 11.2f, 15.0f },
{ 35.6f + 1.f, 7.4f, 15.0f },
{ 34.0f + 1.f, 4.1f, 15.0f },
{ 36.6f + 1.f, 1.0f, 15.0f },
{ 34.7f + 1.f, -1.9f, 15.0f },
{ 36.6f + 1.f, -0.5f, 15.0f },
{ 34.2f + 1.f, 2.8f, 15.0f },
{ 36.5f + 1.f, 3.8f, 15.0f },
{ 33.5f + 1.f, 6.9f, 15.0f },
{ 32.7f + 1.f, 9.9f, 15.0f },
{ 34.5f + 1.f, 11.0f, 15.0f },
// MZ2_MEDIC_HYPERBLASTER2_1-12
{ 33.0f + 1.f, 12.5f, 15.0f },
{ 32.4f + 1.f, 11.2f, 15.0f },
{ 35.6f + 1.f, 7.4f, 15.0f },
{ 34.0f + 1.f, 4.1f, 15.0f },
{ 36.6f + 1.f, 1.0f, 15.0f },
{ 34.7f + 1.f, -1.9f, 15.0f },
{ 36.6f + 1.f, -0.5f, 15.0f },
{ 34.2f + 1.f, 2.8f, 15.0f },
{ 36.5f + 1.f, 3.8f, 15.0f },
{ 33.5f + 1.f, 6.9f, 15.0f },
{ 32.7f + 1.f, 9.9f, 15.0f },
{ 34.5f + 1.f, 11.0f, 15.0f },
// end of table
{ 0.0f, 0.0f, 0.0f }
};
static_assert(q_countof(monster_flash_offset) - 1 == MZ2_LAST);

384
rerelease/m_flipper.cpp Normal file
View File

@@ -0,0 +1,384 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
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;
mframe_t flipper_frames_stand[] = {
{ ai_stand }
};
MMOVE_T(flipper_move_stand) = { FRAME_flphor01, FRAME_flphor01, flipper_frames_stand, nullptr };
MONSTERINFO_STAND(flipper_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &flipper_move_stand);
}
constexpr float FLIPPER_RUN_SPEED = 24;
mframe_t flipper_frames_run[] = {
{ ai_run, FLIPPER_RUN_SPEED }, // 6
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED }, // 10
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED }, // 20
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED },
{ ai_run, FLIPPER_RUN_SPEED } // 29
};
MMOVE_T(flipper_move_run_loop) = { FRAME_flpver06, FRAME_flpver29, flipper_frames_run, nullptr };
void flipper_run_loop(edict_t *self)
{
M_SetAnimation(self, &flipper_move_run_loop);
}
mframe_t flipper_frames_run_start[] = {
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 }
};
MMOVE_T(flipper_move_run_start) = { FRAME_flpver01, FRAME_flpver06, flipper_frames_run_start, flipper_run_loop };
void flipper_run(edict_t *self)
{
M_SetAnimation(self, &flipper_move_run_start);
}
/* Standard Swimming */
mframe_t flipper_frames_walk[] = {
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 }
};
MMOVE_T(flipper_move_walk) = { FRAME_flphor01, FRAME_flphor24, flipper_frames_walk, nullptr };
MONSTERINFO_WALK(flipper_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &flipper_move_walk);
}
mframe_t flipper_frames_start_run[] = {
{ ai_run },
{ ai_run },
{ ai_run },
{ ai_run },
{ ai_run, 8, flipper_run }
};
MMOVE_T(flipper_move_start_run) = { FRAME_flphor01, FRAME_flphor05, flipper_frames_start_run, nullptr };
MONSTERINFO_RUN(flipper_start_run) (edict_t *self) -> void
{
M_SetAnimation(self, &flipper_move_start_run);
}
mframe_t flipper_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flipper_move_pain2) = { FRAME_flppn101, FRAME_flppn105, flipper_frames_pain2, flipper_run };
mframe_t flipper_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flipper_move_pain1) = { FRAME_flppn201, FRAME_flppn205, flipper_frames_pain1, flipper_run };
void flipper_bite(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, 0, 0 };
fire_hit(self, aim, 5, 0);
}
void flipper_preattack(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_chomp, 1, ATTN_NORM, 0);
}
mframe_t flipper_frames_attack[] = {
{ ai_charge, 0, flipper_preattack },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, flipper_bite },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, flipper_bite },
{ ai_charge }
};
MMOVE_T(flipper_move_attack) = { FRAME_flpbit01, FRAME_flpbit20, flipper_frames_attack, flipper_run };
MONSTERINFO_MELEE(flipper_melee) (edict_t *self) -> void
{
M_SetAnimation(self, &flipper_move_attack);
}
PAIN(flipper_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
int n;
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
n = brandom();
if (n == 0)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (n == 0)
M_SetAnimation(self, &flipper_move_pain1);
else
M_SetAnimation(self, &flipper_move_pain2);
}
MONSTERINFO_SETSKIN(flipper_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
void flipper_dead(edict_t *self)
{
self->mins = { -16, -16, -8 };
self->maxs = { 16, 16, 8 };
monster_dead(self);
}
mframe_t flipper_frames_death[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flipper_move_death) = { FRAME_flpdth01, FRAME_flpdth56, flipper_frames_death, flipper_dead };
MONSTERINFO_SIGHT(flipper_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
DIE(flipper_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/objects/gibs/head2/tris.md2", GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
self->svflags |= SVF_DEADMONSTER;
M_SetAnimation(self, &flipper_move_death);
}
static void flipper_set_fly_parameters(edict_t *self)
{
self->monsterinfo.fly_thrusters = false;
self->monsterinfo.fly_acceleration = 30.f;
self->monsterinfo.fly_speed = 110.f;
// only melee, so get in close
self->monsterinfo.fly_min_distance = 10.f;
self->monsterinfo.fly_max_distance = 10.f;
}
/*QUAKED monster_flipper (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
*/
void SP_monster_flipper(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/flipper/tris.md2");
self->mins = { -16, -16, -8 };
self->maxs = { 16, 16, 20 };
self->health = 50 * st.health_multiplier;
self->gib_health = -30;
self->mass = 100;
self->pain = flipper_pain;
self->die = flipper_die;
self->monsterinfo.stand = flipper_stand;
self->monsterinfo.walk = flipper_walk;
self->monsterinfo.run = flipper_start_run;
self->monsterinfo.melee = flipper_melee;
self->monsterinfo.sight = flipper_sight;
self->monsterinfo.setskin = flipper_setskin;
gi.linkentity(self);
M_SetAnimation(self, &flipper_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
self->monsterinfo.aiflags |= AI_ALTERNATE_FLY;
flipper_set_fly_parameters(self);
swimmonster_start(self);
}

171
rerelease/m_flipper.h Normal file
View File

@@ -0,0 +1,171 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/flipper
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_flpbit01,
FRAME_flpbit02,
FRAME_flpbit03,
FRAME_flpbit04,
FRAME_flpbit05,
FRAME_flpbit06,
FRAME_flpbit07,
FRAME_flpbit08,
FRAME_flpbit09,
FRAME_flpbit10,
FRAME_flpbit11,
FRAME_flpbit12,
FRAME_flpbit13,
FRAME_flpbit14,
FRAME_flpbit15,
FRAME_flpbit16,
FRAME_flpbit17,
FRAME_flpbit18,
FRAME_flpbit19,
FRAME_flpbit20,
FRAME_flptal01,
FRAME_flptal02,
FRAME_flptal03,
FRAME_flptal04,
FRAME_flptal05,
FRAME_flptal06,
FRAME_flptal07,
FRAME_flptal08,
FRAME_flptal09,
FRAME_flptal10,
FRAME_flptal11,
FRAME_flptal12,
FRAME_flptal13,
FRAME_flptal14,
FRAME_flptal15,
FRAME_flptal16,
FRAME_flptal17,
FRAME_flptal18,
FRAME_flptal19,
FRAME_flptal20,
FRAME_flptal21,
FRAME_flphor01,
FRAME_flphor02,
FRAME_flphor03,
FRAME_flphor04,
FRAME_flphor05,
FRAME_flphor06,
FRAME_flphor07,
FRAME_flphor08,
FRAME_flphor09,
FRAME_flphor10,
FRAME_flphor11,
FRAME_flphor12,
FRAME_flphor13,
FRAME_flphor14,
FRAME_flphor15,
FRAME_flphor16,
FRAME_flphor17,
FRAME_flphor18,
FRAME_flphor19,
FRAME_flphor20,
FRAME_flphor21,
FRAME_flphor22,
FRAME_flphor23,
FRAME_flphor24,
FRAME_flpver01,
FRAME_flpver02,
FRAME_flpver03,
FRAME_flpver04,
FRAME_flpver05,
FRAME_flpver06,
FRAME_flpver07,
FRAME_flpver08,
FRAME_flpver09,
FRAME_flpver10,
FRAME_flpver11,
FRAME_flpver12,
FRAME_flpver13,
FRAME_flpver14,
FRAME_flpver15,
FRAME_flpver16,
FRAME_flpver17,
FRAME_flpver18,
FRAME_flpver19,
FRAME_flpver20,
FRAME_flpver21,
FRAME_flpver22,
FRAME_flpver23,
FRAME_flpver24,
FRAME_flpver25,
FRAME_flpver26,
FRAME_flpver27,
FRAME_flpver28,
FRAME_flpver29,
FRAME_flppn101,
FRAME_flppn102,
FRAME_flppn103,
FRAME_flppn104,
FRAME_flppn105,
FRAME_flppn201,
FRAME_flppn202,
FRAME_flppn203,
FRAME_flppn204,
FRAME_flppn205,
FRAME_flpdth01,
FRAME_flpdth02,
FRAME_flpdth03,
FRAME_flpdth04,
FRAME_flpdth05,
FRAME_flpdth06,
FRAME_flpdth07,
FRAME_flpdth08,
FRAME_flpdth09,
FRAME_flpdth10,
FRAME_flpdth11,
FRAME_flpdth12,
FRAME_flpdth13,
FRAME_flpdth14,
FRAME_flpdth15,
FRAME_flpdth16,
FRAME_flpdth17,
FRAME_flpdth18,
FRAME_flpdth19,
FRAME_flpdth20,
FRAME_flpdth21,
FRAME_flpdth22,
FRAME_flpdth23,
FRAME_flpdth24,
FRAME_flpdth25,
FRAME_flpdth26,
FRAME_flpdth27,
FRAME_flpdth28,
FRAME_flpdth29,
FRAME_flpdth30,
FRAME_flpdth31,
FRAME_flpdth32,
FRAME_flpdth33,
FRAME_flpdth34,
FRAME_flpdth35,
FRAME_flpdth36,
FRAME_flpdth37,
FRAME_flpdth38,
FRAME_flpdth39,
FRAME_flpdth40,
FRAME_flpdth41,
FRAME_flpdth42,
FRAME_flpdth43,
FRAME_flpdth44,
FRAME_flpdth45,
FRAME_flpdth46,
FRAME_flpdth47,
FRAME_flpdth48,
FRAME_flpdth49,
FRAME_flpdth50,
FRAME_flpdth51,
FRAME_flpdth52,
FRAME_flpdth53,
FRAME_flpdth54,
FRAME_flpdth55,
FRAME_flpdth56
};
constexpr float MODEL_SCALE = 1.000000f;

716
rerelease/m_float.cpp Normal file
View File

@@ -0,0 +1,716 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
floater
==============================================================================
*/
#include "g_local.h"
#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;
MONSTERINFO_SIGHT(floater_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_IDLE(floater_idle) (edict_t *self) -> void
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
void floater_dead(edict_t *self);
void floater_run(edict_t *self);
void floater_wham(edict_t *self);
void floater_zap(edict_t *self);
void floater_fire_blaster(edict_t *self)
{
vec3_t start;
vec3_t forward, right;
vec3_t end;
vec3_t dir;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_FLOAT_BLASTER_1], forward, right);
end = self->enemy->s.origin;
end[2] += self->enemy->viewheight;
dir = end - start;
dir.normalize();
monster_fire_blaster(self, start, dir, 1, 1000, MZ2_FLOAT_BLASTER_1, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
}
mframe_t floater_frames_stand1[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(floater_move_stand1) = { FRAME_stand101, FRAME_stand152, floater_frames_stand1, nullptr };
mframe_t floater_frames_stand2[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(floater_move_stand2) = { FRAME_stand201, FRAME_stand252, floater_frames_stand2, nullptr };
mframe_t floater_frames_pop[] = {
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{},
{}
};
MMOVE_T(floater_move_pop) = { FRAME_actvat05, FRAME_actvat31, floater_frames_pop, floater_run };
mframe_t floater_frames_disguise[] = {
{ ai_stand }
};
MMOVE_T(floater_move_disguise) = { FRAME_actvat01, FRAME_actvat01, floater_frames_disguise, nullptr };
MONSTERINFO_STAND(floater_stand) (edict_t *self) -> void
{
if (self->monsterinfo.active_move == &floater_move_disguise)
M_SetAnimation(self, &floater_move_disguise);
else if (frandom() <= 0.5f)
M_SetAnimation(self, &floater_move_stand1);
else
M_SetAnimation(self, &floater_move_stand2);
}
mframe_t floater_frames_attack1[] = {
{ ai_charge }, // Blaster attack
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, floater_fire_blaster }, // BOOM (0, -25.8, 32.5) -- LOOP Starts
{ ai_charge, 0, floater_fire_blaster },
{ ai_charge, 0, floater_fire_blaster },
{ ai_charge, 0, floater_fire_blaster },
{ ai_charge, 0, floater_fire_blaster },
{ ai_charge, 0, floater_fire_blaster },
{ ai_charge, 0, floater_fire_blaster },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge } // -- LOOP Ends
};
MMOVE_T(floater_move_attack1) = { FRAME_attak101, FRAME_attak114, floater_frames_attack1, floater_run };
// PMM - circle strafe frames
mframe_t floater_frames_attack1a[] = {
{ ai_charge, 10 }, // Blaster attack
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10, floater_fire_blaster }, // BOOM (0, -25.8, 32.5) -- LOOP Starts
{ ai_charge, 10, floater_fire_blaster },
{ ai_charge, 10, floater_fire_blaster },
{ ai_charge, 10, floater_fire_blaster },
{ ai_charge, 10, floater_fire_blaster },
{ ai_charge, 10, floater_fire_blaster },
{ ai_charge, 10, floater_fire_blaster },
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10 } // -- LOOP Ends
};
MMOVE_T(floater_move_attack1a) = { FRAME_attak101, FRAME_attak114, floater_frames_attack1a, floater_run };
// pmm
mframe_t floater_frames_attack2[] = {
{ ai_charge }, // Claws
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, floater_wham }, // WHAM (0, -45, 29.6) -- LOOP Starts
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }, // -- LOOP Ends
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(floater_move_attack2) = { FRAME_attak201, FRAME_attak225, floater_frames_attack2, floater_run };
mframe_t floater_frames_attack3[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, floater_zap }, // -- LOOP Starts
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }, // -- LOOP Ends
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(floater_move_attack3) = { FRAME_attak301, FRAME_attak334, floater_frames_attack3, floater_run };
#if 0
mframe_t floater_frames_death[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(floater_move_death) = { FRAME_death01, FRAME_death13, floater_frames_death, floater_dead };
#endif
mframe_t floater_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(floater_move_pain1) = { FRAME_pain101, FRAME_pain107, floater_frames_pain1, floater_run };
mframe_t floater_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(floater_move_pain2) = { FRAME_pain201, FRAME_pain208, floater_frames_pain2, floater_run };
#if 0
mframe_t floater_frames_pain3[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(floater_move_pain3) = { FRAME_pain301, FRAME_pain312, floater_frames_pain3, floater_run };
#endif
mframe_t floater_frames_walk[] = {
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 }
};
MMOVE_T(floater_move_walk) = { FRAME_stand101, FRAME_stand152, floater_frames_walk, nullptr };
mframe_t floater_frames_run[] = {
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 },
{ ai_run, 13 }
};
MMOVE_T(floater_move_run) = { FRAME_stand101, FRAME_stand152, floater_frames_run, nullptr };
MONSTERINFO_RUN(floater_run) (edict_t *self) -> void
{
if (self->monsterinfo.active_move == &floater_move_disguise)
M_SetAnimation(self, &floater_move_pop);
else if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &floater_move_stand1);
else
M_SetAnimation(self, &floater_move_run);
}
MONSTERINFO_WALK(floater_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &floater_move_walk);
}
void floater_wham(edict_t *self)
{
constexpr vec3_t aim = { MELEE_DISTANCE, 0, 0 };
gi.sound(self, CHAN_WEAPON, sound_attack3, 1, ATTN_NORM, 0);
if (!fire_hit(self, aim, irandom(5, 11), -50))
self->monsterinfo.melee_debounce_time = level.time + 3_sec;
}
void floater_zap(edict_t *self)
{
vec3_t forward, right;
vec3_t origin;
vec3_t dir;
vec3_t offset;
dir = self->enemy->s.origin - self->s.origin;
AngleVectors(self->s.angles, forward, right, nullptr);
// FIXME use a flash and replace these two lines with the commented one
offset = { 18.5f, -0.9f, 10 };
origin = M_ProjectFlashSource(self, offset, forward, right);
gi.sound(self, CHAN_WEAPON, sound_attack2, 1, ATTN_NORM, 0);
// FIXME use the flash, Luke
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_SPLASH);
gi.WriteByte(32);
gi.WritePosition(origin);
gi.WriteDir(dir);
gi.WriteByte(SPLASH_SPARKS);
gi.multicast(origin, MULTICAST_PVS, false);
T_Damage(self->enemy, self, self, dir, self->enemy->s.origin, vec3_origin, irandom(5, 11), -10, DAMAGE_ENERGY, MOD_UNKNOWN);
}
MONSTERINFO_ATTACK(floater_attack) (edict_t *self) -> void
{
float chance = 0.5f;
if (frandom() > chance)
{
self->monsterinfo.attack_state = AS_STRAIGHT;
M_SetAnimation(self, &floater_move_attack1);
}
else // circle strafe
{
if (frandom() <= 0.5f) // switch directions
self->monsterinfo.lefty = !self->monsterinfo.lefty;
self->monsterinfo.attack_state = AS_SLIDING;
M_SetAnimation(self, &floater_move_attack1a);
}
}
MONSTERINFO_MELEE(floater_melee) (edict_t *self) -> void
{
if (frandom() < 0.5f)
M_SetAnimation(self, &floater_move_attack3);
else
M_SetAnimation(self, &floater_move_attack2);
}
PAIN(floater_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
int n;
if (level.time < self->pain_debounce_time)
return;
// no pain anims if poppin'
if (self->monsterinfo.active_move == &floater_move_disguise ||
self->monsterinfo.active_move == &floater_move_pop)
return;
n = irandom(3);
if (n == 0)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 3_sec;
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (n == 0)
M_SetAnimation(self, &floater_move_pain1);
else
M_SetAnimation(self, &floater_move_pain2);
}
MONSTERINFO_SETSKIN(floater_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
void floater_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
self->movetype = MOVETYPE_TOSS;
self->svflags |= SVF_DEADMONSTER;
self->nextthink = 0_ms;
gi.linkentity(self);
}
DIE(floater_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
self->s.skinnum /= 2;
ThrowGibs(self, 55, {
{ 2, "models/objects/gibs/sm_metal/tris.md2" },
{ 3, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/monsters/float/gibs/piece.md2", GIB_SKINNED },
{ "models/monsters/float/gibs/gun.md2", GIB_SKINNED },
{ "models/monsters/float/gibs/base.md2", GIB_SKINNED },
{ "models/monsters/float/gibs/jar.md2", GIB_SKINNED | GIB_HEAD }
});
}
static void float_set_fly_parameters(edict_t *self)
{
self->monsterinfo.fly_thrusters = false;
self->monsterinfo.fly_acceleration = 10.f;
self->monsterinfo.fly_speed = 100.f;
// Technician gets in closer because he has two melee attacks
self->monsterinfo.fly_min_distance = 20.f;
self->monsterinfo.fly_max_distance = 200.f;
}
constexpr spawnflags_t SPAWNFLAG_FLOATER_DISGUISE = 8_spawnflag;
/*QUAKED monster_floater (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight Disguise
*/
void SP_monster_floater(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
gi.soundindex("floater/fltatck1.wav");
self->monsterinfo.engine_sound = gi.soundindex("floater/fltsrch1.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/float/tris.md2");
gi.modelindex("models/monsters/float/gibs/base.md2");
gi.modelindex("models/monsters/float/gibs/gun.md2");
gi.modelindex("models/monsters/float/gibs/jar.md2");
gi.modelindex("models/monsters/float/gibs/piece.md2");
self->mins = { -24, -24, -24 };
self->maxs = { 24, 24, 48 };
self->health = 200 * st.health_multiplier;
self->gib_health = -80;
self->mass = 300;
self->pain = floater_pain;
self->die = floater_die;
self->monsterinfo.stand = floater_stand;
self->monsterinfo.walk = floater_walk;
self->monsterinfo.run = floater_run;
self->monsterinfo.attack = floater_attack;
self->monsterinfo.melee = floater_melee;
self->monsterinfo.sight = floater_sight;
self->monsterinfo.idle = floater_idle;
self->monsterinfo.setskin = floater_setskin;
gi.linkentity(self);
if (self->spawnflags.has(SPAWNFLAG_FLOATER_DISGUISE))
M_SetAnimation(self, &floater_move_disguise);
else if (frandom() <= 0.5f)
M_SetAnimation(self, &floater_move_stand1);
else
M_SetAnimation(self, &floater_move_stand2);
self->monsterinfo.scale = MODEL_SCALE;
self->monsterinfo.aiflags |= AI_ALTERNATE_FLY;
float_set_fly_parameters(self);
flymonster_start(self);
}

259
rerelease/m_float.h Normal file
View File

@@ -0,0 +1,259 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/float
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_actvat01,
FRAME_actvat02,
FRAME_actvat03,
FRAME_actvat04,
FRAME_actvat05,
FRAME_actvat06,
FRAME_actvat07,
FRAME_actvat08,
FRAME_actvat09,
FRAME_actvat10,
FRAME_actvat11,
FRAME_actvat12,
FRAME_actvat13,
FRAME_actvat14,
FRAME_actvat15,
FRAME_actvat16,
FRAME_actvat17,
FRAME_actvat18,
FRAME_actvat19,
FRAME_actvat20,
FRAME_actvat21,
FRAME_actvat22,
FRAME_actvat23,
FRAME_actvat24,
FRAME_actvat25,
FRAME_actvat26,
FRAME_actvat27,
FRAME_actvat28,
FRAME_actvat29,
FRAME_actvat30,
FRAME_actvat31,
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_attak214,
FRAME_attak215,
FRAME_attak216,
FRAME_attak217,
FRAME_attak218,
FRAME_attak219,
FRAME_attak220,
FRAME_attak221,
FRAME_attak222,
FRAME_attak223,
FRAME_attak224,
FRAME_attak225,
FRAME_attak301,
FRAME_attak302,
FRAME_attak303,
FRAME_attak304,
FRAME_attak305,
FRAME_attak306,
FRAME_attak307,
FRAME_attak308,
FRAME_attak309,
FRAME_attak310,
FRAME_attak311,
FRAME_attak312,
FRAME_attak313,
FRAME_attak314,
FRAME_attak315,
FRAME_attak316,
FRAME_attak317,
FRAME_attak318,
FRAME_attak319,
FRAME_attak320,
FRAME_attak321,
FRAME_attak322,
FRAME_attak323,
FRAME_attak324,
FRAME_attak325,
FRAME_attak326,
FRAME_attak327,
FRAME_attak328,
FRAME_attak329,
FRAME_attak330,
FRAME_attak331,
FRAME_attak332,
FRAME_attak333,
FRAME_attak334,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain106,
FRAME_pain107,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain206,
FRAME_pain207,
FRAME_pain208,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_pain310,
FRAME_pain311,
FRAME_pain312,
FRAME_stand101,
FRAME_stand102,
FRAME_stand103,
FRAME_stand104,
FRAME_stand105,
FRAME_stand106,
FRAME_stand107,
FRAME_stand108,
FRAME_stand109,
FRAME_stand110,
FRAME_stand111,
FRAME_stand112,
FRAME_stand113,
FRAME_stand114,
FRAME_stand115,
FRAME_stand116,
FRAME_stand117,
FRAME_stand118,
FRAME_stand119,
FRAME_stand120,
FRAME_stand121,
FRAME_stand122,
FRAME_stand123,
FRAME_stand124,
FRAME_stand125,
FRAME_stand126,
FRAME_stand127,
FRAME_stand128,
FRAME_stand129,
FRAME_stand130,
FRAME_stand131,
FRAME_stand132,
FRAME_stand133,
FRAME_stand134,
FRAME_stand135,
FRAME_stand136,
FRAME_stand137,
FRAME_stand138,
FRAME_stand139,
FRAME_stand140,
FRAME_stand141,
FRAME_stand142,
FRAME_stand143,
FRAME_stand144,
FRAME_stand145,
FRAME_stand146,
FRAME_stand147,
FRAME_stand148,
FRAME_stand149,
FRAME_stand150,
FRAME_stand151,
FRAME_stand152,
FRAME_stand201,
FRAME_stand202,
FRAME_stand203,
FRAME_stand204,
FRAME_stand205,
FRAME_stand206,
FRAME_stand207,
FRAME_stand208,
FRAME_stand209,
FRAME_stand210,
FRAME_stand211,
FRAME_stand212,
FRAME_stand213,
FRAME_stand214,
FRAME_stand215,
FRAME_stand216,
FRAME_stand217,
FRAME_stand218,
FRAME_stand219,
FRAME_stand220,
FRAME_stand221,
FRAME_stand222,
FRAME_stand223,
FRAME_stand224,
FRAME_stand225,
FRAME_stand226,
FRAME_stand227,
FRAME_stand228,
FRAME_stand229,
FRAME_stand230,
FRAME_stand231,
FRAME_stand232,
FRAME_stand233,
FRAME_stand234,
FRAME_stand235,
FRAME_stand236,
FRAME_stand237,
FRAME_stand238,
FRAME_stand239,
FRAME_stand240,
FRAME_stand241,
FRAME_stand242,
FRAME_stand243,
FRAME_stand244,
FRAME_stand245,
FRAME_stand246,
FRAME_stand247,
FRAME_stand248,
FRAME_stand249,
FRAME_stand250,
FRAME_stand251,
FRAME_stand252
};
constexpr float MODEL_SCALE = 1.000000f;

784
rerelease/m_flyer.cpp Normal file
View File

@@ -0,0 +1,784 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
flyer
==============================================================================
*/
#include "g_local.h"
#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;
void flyer_check_melee(edict_t *self);
void flyer_loop_melee(edict_t *self);
void flyer_setstart(edict_t *self);
// ROGUE - kamikaze stuff
void flyer_kamikaze(edict_t *self);
void flyer_kamikaze_check(edict_t *self);
void flyer_die(edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod);
MONSTERINFO_SIGHT(flyer_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_IDLE(flyer_idle) (edict_t *self) -> void
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
void flyer_pop_blades(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_sproing, 1, ATTN_NORM, 0);
}
mframe_t flyer_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(flyer_move_stand) = { FRAME_stand01, FRAME_stand45, flyer_frames_stand, nullptr };
mframe_t flyer_frames_walk[] = {
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 5 }
};
MMOVE_T(flyer_move_walk) = { FRAME_stand01, FRAME_stand45, flyer_frames_walk, nullptr };
mframe_t flyer_frames_run[] = {
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 }
};
MMOVE_T(flyer_move_run) = { FRAME_stand01, FRAME_stand45, flyer_frames_run, nullptr };
mframe_t flyer_frames_kamizake[] = {
{ ai_charge, 40, flyer_kamikaze_check },
{ ai_charge, 40, flyer_kamikaze_check },
{ ai_charge, 40, flyer_kamikaze_check },
{ ai_charge, 40, flyer_kamikaze_check },
{ ai_charge, 40, flyer_kamikaze_check }
};
MMOVE_T(flyer_move_kamikaze) = { FRAME_rollr02, FRAME_rollr06, flyer_frames_kamizake, flyer_kamikaze };
MONSTERINFO_RUN(flyer_run) (edict_t *self) -> void
{
if (self->mass > 50)
M_SetAnimation(self, &flyer_move_kamikaze);
else if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &flyer_move_stand);
else
M_SetAnimation(self, &flyer_move_run);
}
MONSTERINFO_WALK(flyer_walk) (edict_t *self) -> void
{
if (self->mass > 50)
flyer_run(self);
else
M_SetAnimation(self, &flyer_move_walk);
}
MONSTERINFO_STAND(flyer_stand) (edict_t *self) -> void
{
if (self->mass > 50)
flyer_run(self);
else
M_SetAnimation(self, &flyer_move_stand);
}
// ROGUE - kamikaze stuff
void flyer_kamikaze_explode(edict_t *self)
{
vec3_t dir;
if (self->monsterinfo.commander && self->monsterinfo.commander->inuse &&
!strcmp(self->monsterinfo.commander->classname, "monster_carrier"))
self->monsterinfo.commander->monsterinfo.monster_slots++;
if (self->enemy)
{
dir = self->enemy->s.origin - self->s.origin;
T_Damage(self->enemy, self, self, dir, self->s.origin, vec3_origin, (int) 50, (int) 50, DAMAGE_RADIUS, MOD_UNKNOWN);
}
flyer_die(self, nullptr, nullptr, 0, dir, MOD_EXPLOSIVE);
}
void flyer_kamikaze(edict_t *self)
{
M_SetAnimation(self, &flyer_move_kamikaze);
}
void flyer_kamikaze_check(edict_t *self)
{
float dist;
// PMM - this needed because we could have gone away before we get here (blocked code)
if (!self->inuse)
return;
if ((!self->enemy) || (!self->enemy->inuse))
{
flyer_kamikaze_explode(self);
return;
}
self->s.angles[0] = vectoangles(self->enemy->s.origin - self->s.origin).x;
self->goalentity = self->enemy;
dist = realrange(self, self->enemy);
if (dist < 90)
flyer_kamikaze_explode(self);
}
#if 0
mframe_t flyer_frames_rollright[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_rollright) = { FRAME_rollr01, FRAME_rollr09, flyer_frames_rollright, nullptr };
mframe_t flyer_frames_rollleft[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_rollleft) = { FRAME_rollf01, FRAME_rollf09, flyer_frames_rollleft, nullptr };
#endif
mframe_t flyer_frames_pain3[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_pain3) = { FRAME_pain301, FRAME_pain304, flyer_frames_pain3, flyer_run };
mframe_t flyer_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_pain2) = { FRAME_pain201, FRAME_pain204, flyer_frames_pain2, flyer_run };
mframe_t flyer_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_pain1) = { FRAME_pain101, FRAME_pain109, flyer_frames_pain1, flyer_run };
#if 0
mframe_t flyer_frames_defense[] = {
{ ai_move },
{ ai_move },
{ ai_move }, // Hold this frame
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_defense) = { FRAME_defens01, FRAME_defens06, flyer_frames_defense, nullptr };
mframe_t flyer_frames_bankright[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_bankright) = { FRAME_bankr01, FRAME_bankr07, flyer_frames_bankright, nullptr };
mframe_t flyer_frames_bankleft[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(flyer_move_bankleft) = { FRAME_bankl01, FRAME_bankl07, flyer_frames_bankleft, nullptr };
#endif
void flyer_fire(edict_t *self, monster_muzzleflash_id_t flash_number)
{
vec3_t start;
vec3_t forward, right;
vec3_t end;
vec3_t dir;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
end = self->enemy->s.origin;
end[2] += self->enemy->viewheight;
dir = end - start;
dir.normalize();
monster_fire_blaster(self, start, dir, 1, 1000, flash_number, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
}
void flyer_fireleft(edict_t *self)
{
flyer_fire(self, MZ2_FLYER_BLASTER_1);
}
void flyer_fireright(edict_t *self)
{
flyer_fire(self, MZ2_FLYER_BLASTER_2);
}
mframe_t flyer_frames_attack2[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, -10, flyer_fireleft }, // left gun
{ ai_charge, -10, flyer_fireright }, // right gun
{ ai_charge, -10, flyer_fireleft }, // left gun
{ ai_charge, -10, flyer_fireright }, // right gun
{ ai_charge, -10, flyer_fireleft }, // left gun
{ ai_charge, -10, flyer_fireright }, // right gun
{ ai_charge, -10, flyer_fireleft }, // left gun
{ ai_charge, -10, flyer_fireright }, // right gun
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(flyer_move_attack2) = { FRAME_attak201, FRAME_attak217, flyer_frames_attack2, flyer_run };
// PMM
// circle strafe frames
mframe_t flyer_frames_attack3[] = {
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10, flyer_fireleft }, // left gun
{ ai_charge, 10, flyer_fireright }, // right gun
{ ai_charge, 10, flyer_fireleft }, // left gun
{ ai_charge, 10, flyer_fireright }, // right gun
{ ai_charge, 10, flyer_fireleft }, // left gun
{ ai_charge, 10, flyer_fireright }, // right gun
{ ai_charge, 10, flyer_fireleft }, // left gun
{ ai_charge, 10, flyer_fireright }, // right gun
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10 },
{ ai_charge, 10 }
};
MMOVE_T(flyer_move_attack3) = { FRAME_attak201, FRAME_attak217, flyer_frames_attack3, flyer_run };
// pmm
void flyer_slash_left(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->mins[0], 0 };
if (!fire_hit(self, aim, 5, 0))
self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
}
void flyer_slash_right(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 0 };
if (!fire_hit(self, aim, 5, 0))
self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
gi.sound(self, CHAN_WEAPON, sound_slash, 1, ATTN_NORM, 0);
}
mframe_t flyer_frames_start_melee[] = {
{ ai_charge, 0, flyer_pop_blades },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(flyer_move_start_melee) = { FRAME_attak101, FRAME_attak106, flyer_frames_start_melee, flyer_loop_melee };
mframe_t flyer_frames_end_melee[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(flyer_move_end_melee) = { FRAME_attak119, FRAME_attak121, flyer_frames_end_melee, flyer_run };
mframe_t flyer_frames_loop_melee[] = {
{ ai_charge }, // Loop Start
{ ai_charge },
{ ai_charge, 0, flyer_slash_left }, // Left Wing Strike
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, flyer_slash_right }, // Right Wing Strike
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge } // Loop Ends
};
MMOVE_T(flyer_move_loop_melee) = { FRAME_attak107, FRAME_attak118, flyer_frames_loop_melee, flyer_check_melee };
void flyer_loop_melee(edict_t *self)
{
M_SetAnimation(self, &flyer_move_loop_melee);
}
static void flyer_set_fly_parameters(edict_t *self, bool melee)
{
if (melee)
{
// engage thrusters for a slice
self->monsterinfo.fly_pinned = false;
self->monsterinfo.fly_thrusters = true;
self->monsterinfo.fly_position_time = 0_sec;
self->monsterinfo.fly_acceleration = 20.f;
self->monsterinfo.fly_speed = 210.f;
self->monsterinfo.fly_min_distance = 0.f;
self->monsterinfo.fly_max_distance = 10.f;
}
else
{
self->monsterinfo.fly_thrusters = false;
self->monsterinfo.fly_acceleration = 15.f;
self->monsterinfo.fly_speed = 165.f;
self->monsterinfo.fly_min_distance = 45.f;
self->monsterinfo.fly_max_distance = 200.f;
}
}
MONSTERINFO_ATTACK(flyer_attack) (edict_t *self) -> void
{
if (self->mass > 50)
{
flyer_run(self);
return;
}
float range = range_to(self, self->enemy);
if (self->enemy && visible(self, self->enemy) && range <= 225.f && frandom() > (range / 225.f) * 0.35f)
{
// fly-by slicing!
self->monsterinfo.attack_state = AS_STRAIGHT;
M_SetAnimation(self, &flyer_move_start_melee);
flyer_set_fly_parameters(self, true);
}
else
{
self->monsterinfo.attack_state = AS_STRAIGHT;
M_SetAnimation(self, &flyer_move_attack2);
}
// [Paril-KEX] for alternate fly mode, sometimes we'll pin us
// down, kind of like a pseudo-stand ground
if (!self->monsterinfo.fly_pinned && brandom() && self->enemy && visible(self, self->enemy))
{
self->monsterinfo.fly_pinned = true;
self->monsterinfo.fly_position_time = max(self->monsterinfo.fly_position_time, self->monsterinfo.fly_position_time + 1.7_sec); // make sure there's enough time for attack2/3
if (brandom())
self->monsterinfo.fly_ideal_position = self->s.origin + (self->velocity * frandom()); // pin to our current position
else
self->monsterinfo.fly_ideal_position += self->enemy->s.origin; // make un-relative
}
// if we're currently pinned, fly_position_time will unpin us eventually
}
MONSTERINFO_MELEE(flyer_melee) (edict_t *self) -> void
{
if (self->mass > 50)
flyer_run(self);
else
{
M_SetAnimation(self, &flyer_move_start_melee);
flyer_set_fly_parameters(self, true);
}
}
void flyer_check_melee(edict_t *self)
{
if (range_to(self, self->enemy) <= RANGE_MELEE)
{
if (self->monsterinfo.melee_debounce_time <= level.time)
{
M_SetAnimation(self, &flyer_move_loop_melee);
return;
}
}
M_SetAnimation(self, &flyer_move_end_melee);
flyer_set_fly_parameters(self, false);
}
PAIN(flyer_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
int n;
// pmm - kamikaze's don't feel pain
if (self->mass != 50)
return;
// pmm
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
n = irandom(3);
if (n == 0)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else if (n == 1)
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
flyer_set_fly_parameters(self, false);
if (n == 0)
M_SetAnimation(self, &flyer_move_pain1);
else if (n == 1)
M_SetAnimation(self, &flyer_move_pain2);
else
M_SetAnimation(self, &flyer_move_pain3);
}
MONSTERINFO_SETSKIN(flyer_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
DIE(flyer_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
self->s.skinnum /= 2;
ThrowGibs(self, 55, {
{ 2, "models/objects/gibs/sm_metal/tris.md2" },
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/monsters/flyer/gibs/base.md2", GIB_SKINNED },
{ 2, "models/monsters/flyer/gibs/gun.md2", GIB_SKINNED },
{ 2, "models/monsters/flyer/gibs/wing.md2", GIB_SKINNED },
{ "models/monsters/flyer/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
});
self->touch = nullptr;
}
// PMM - kamikaze code .. blow up if blocked
MONSTERINFO_BLOCKED(flyer_blocked) (edict_t *self, float dist) -> bool
{
// kamikaze = 100, normal = 50
if (self->mass == 100)
{
flyer_kamikaze_check(self);
// if the above didn't blow us up (i.e. I got blocked by the player)
if (self->inuse)
T_Damage(self, self, self, vec3_origin, self->s.origin, vec3_origin, 9999, 100, DAMAGE_NONE, MOD_UNKNOWN);
return true;
}
return false;
}
TOUCH(kamikaze_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
T_Damage(ent, ent, ent, ent->velocity.normalized(), ent->s.origin, ent->velocity.normalized(), 9999, 100, DAMAGE_NONE, MOD_UNKNOWN);
}
TOUCH(flyer_touch) (edict_t *ent, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
if ((other->monsterinfo.aiflags & AI_ALTERNATE_FLY) && (other->flags & FL_FLY) &&
(ent->monsterinfo.duck_wait_time < level.time))
{
ent->monsterinfo.duck_wait_time = level.time + 1_sec;
ent->monsterinfo.fly_thrusters = false;
vec3_t dir = (ent->s.origin - other->s.origin).normalized();
ent->velocity = dir * 500.f;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_SPLASH);
gi.WriteByte(32);
gi.WritePosition(tr.endpos);
gi.WriteDir(dir);
gi.WriteByte(SPLASH_SPARKS);
gi.multicast(tr.endpos, MULTICAST_PVS, false);
}
}
/*QUAKED monster_flyer (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
*/
void SP_monster_flyer(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
gi.soundindex("flyer/flyatck3.wav");
self->s.modelindex = gi.modelindex("models/monsters/flyer/tris.md2");
gi.modelindex("models/monsters/flyer/gibs/base.md2");
gi.modelindex("models/monsters/flyer/gibs/wing.md2");
gi.modelindex("models/monsters/flyer/gibs/gun.md2");
gi.modelindex("models/monsters/flyer/gibs/head.md2");
self->mins = { -16, -16, -24 };
// PMM - shortened to 16 from 32
self->maxs = { 16, 16, 16 };
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->viewheight = 12;
self->monsterinfo.engine_sound = gi.soundindex("flyer/flyidle1.wav");
self->health = 50 * st.health_multiplier;
self->mass = 50;
self->pain = flyer_pain;
self->die = flyer_die;
self->monsterinfo.stand = flyer_stand;
self->monsterinfo.walk = flyer_walk;
self->monsterinfo.run = flyer_run;
self->monsterinfo.attack = flyer_attack;
self->monsterinfo.melee = flyer_melee;
self->monsterinfo.sight = flyer_sight;
self->monsterinfo.idle = flyer_idle;
self->monsterinfo.blocked = flyer_blocked;
self->monsterinfo.setskin = flyer_setskin;
gi.linkentity(self);
M_SetAnimation(self, &flyer_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
if (self->s.effects & EF_ROCKET)
{
// PMM - normal flyer has mass of 50
self->mass = 100;
self->yaw_speed = 5;
self->touch = kamikaze_touch;
}
else
{
self->monsterinfo.aiflags |= AI_ALTERNATE_FLY;
self->monsterinfo.fly_buzzard = true;
flyer_set_fly_parameters(self, false);
self->touch = flyer_touch;
}
flymonster_start(self);
}
// PMM - suicide fliers
void SP_monster_kamikaze(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
self->s.effects |= EF_ROCKET;
SP_monster_flyer(self);
}

161
rerelease/m_flyer.h Normal file
View File

@@ -0,0 +1,161 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/flyer
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_start01,
FRAME_start02,
FRAME_start03,
FRAME_start04,
FRAME_start05,
FRAME_start06,
FRAME_stop01,
FRAME_stop02,
FRAME_stop03,
FRAME_stop04,
FRAME_stop05,
FRAME_stop06,
FRAME_stop07,
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak116,
FRAME_attak117,
FRAME_attak118,
FRAME_attak119,
FRAME_attak120,
FRAME_attak121,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_attak214,
FRAME_attak215,
FRAME_attak216,
FRAME_attak217,
FRAME_bankl01,
FRAME_bankl02,
FRAME_bankl03,
FRAME_bankl04,
FRAME_bankl05,
FRAME_bankl06,
FRAME_bankl07,
FRAME_bankr01,
FRAME_bankr02,
FRAME_bankr03,
FRAME_bankr04,
FRAME_bankr05,
FRAME_bankr06,
FRAME_bankr07,
FRAME_rollf01,
FRAME_rollf02,
FRAME_rollf03,
FRAME_rollf04,
FRAME_rollf05,
FRAME_rollf06,
FRAME_rollf07,
FRAME_rollf08,
FRAME_rollf09,
FRAME_rollr01,
FRAME_rollr02,
FRAME_rollr03,
FRAME_rollr04,
FRAME_rollr05,
FRAME_rollr06,
FRAME_rollr07,
FRAME_rollr08,
FRAME_rollr09,
FRAME_defens01,
FRAME_defens02,
FRAME_defens03,
FRAME_defens04,
FRAME_defens05,
FRAME_defens06,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain106,
FRAME_pain107,
FRAME_pain108,
FRAME_pain109,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304
};
constexpr float MODEL_SCALE = 1.000000f;

489
rerelease/m_gladiator.cpp Normal file
View File

@@ -0,0 +1,489 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
GLADIATOR
==============================================================================
*/
#include "g_local.h"
#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;
MONSTERINFO_IDLE(gladiator_idle) (edict_t *self) -> void
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
MONSTERINFO_SIGHT(gladiator_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_SEARCH(gladiator_search) (edict_t *self) -> void
{
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
void gladiator_cleaver_swing(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_cleaver_swing, 1, ATTN_NORM, 0);
}
mframe_t gladiator_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(gladiator_move_stand) = { FRAME_stand1, FRAME_stand7, gladiator_frames_stand, nullptr };
MONSTERINFO_STAND(gladiator_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &gladiator_move_stand);
}
mframe_t gladiator_frames_walk[] = {
{ ai_walk, 15 },
{ ai_walk, 7 },
{ ai_walk, 6 },
{ ai_walk, 5 },
{ ai_walk, 2, monster_footstep },
{ ai_walk },
{ ai_walk, 2 },
{ ai_walk, 8 },
{ ai_walk, 12 },
{ ai_walk, 8 },
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, 2, monster_footstep },
{ ai_walk, 2 },
{ ai_walk, 1 },
{ ai_walk, 8 }
};
MMOVE_T(gladiator_move_walk) = { FRAME_walk1, FRAME_walk16, gladiator_frames_walk, nullptr };
MONSTERINFO_WALK(gladiator_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &gladiator_move_walk);
}
mframe_t gladiator_frames_run[] = {
{ ai_run, 23 },
{ ai_run, 14 },
{ ai_run, 14, monster_footstep },
{ ai_run, 21 },
{ ai_run, 12 },
{ ai_run, 13, monster_footstep }
};
MMOVE_T(gladiator_move_run) = { FRAME_run1, FRAME_run6, gladiator_frames_run, nullptr };
MONSTERINFO_RUN(gladiator_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &gladiator_move_stand);
else
M_SetAnimation(self, &gladiator_move_run);
}
void GladiatorMelee(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
if (fire_hit(self, aim, irandom(20, 25), 300))
gi.sound(self, CHAN_AUTO, sound_cleaver_hit, 1, ATTN_NORM, 0);
else
{
gi.sound(self, CHAN_AUTO, sound_cleaver_miss, 1, ATTN_NORM, 0);
self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
}
}
mframe_t gladiator_frames_attack_melee[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, gladiator_cleaver_swing },
{ ai_charge },
{ ai_charge, 0, GladiatorMelee },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, gladiator_cleaver_swing },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GladiatorMelee },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(gladiator_move_attack_melee) = { FRAME_melee3, FRAME_melee16, gladiator_frames_attack_melee, gladiator_run };
MONSTERINFO_MELEE(gladiator_melee) (edict_t *self) -> void
{
M_SetAnimation(self, &gladiator_move_attack_melee);
}
void GladiatorGun(edict_t *self)
{
vec3_t start;
vec3_t dir;
vec3_t forward, right;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right);
// calc direction to where we targted
dir = self->pos1 - start;
dir.normalize();
monster_fire_railgun(self, start, dir, 50, 100, MZ2_GLADIATOR_RAILGUN_1);
}
mframe_t gladiator_frames_attack_gun[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GladiatorGun },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, monster_footstep },
{ ai_charge }
};
MMOVE_T(gladiator_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladiator_frames_attack_gun, gladiator_run };
// RAFAEL
void gladbGun(edict_t *self)
{
vec3_t start;
vec3_t dir;
vec3_t forward, right;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1], forward, right);
// calc direction to where we targeted
dir = self->pos1 - start;
dir.normalize();
int damage = 35;
int radius_damage = 45;
if (self->s.frame > FRAME_attack3)
{
damage /= 2;
radius_damage /= 2;
}
fire_plasma(self, start, dir, damage, 725, radius_damage, radius_damage);
// save for aiming the shot
self->pos1 = self->enemy->s.origin;
self->pos1[2] += self->enemy->viewheight;
}
void gladbGun_check(edict_t *self)
{
if (skill->integer == 3)
gladbGun(self);
}
mframe_t gladb_frames_attack_gun[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, gladbGun },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, gladbGun },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, gladbGun_check }
};
MMOVE_T(gladb_move_attack_gun) = { FRAME_attack1, FRAME_attack9, gladb_frames_attack_gun, gladiator_run };
// RAFAEL
MONSTERINFO_ATTACK(gladiator_attack) (edict_t *self) -> void
{
float range;
vec3_t v;
// a small safe zone
v = self->s.origin - self->enemy->s.origin;
range = v.length();
if (range <= (MELEE_DISTANCE + 32) && self->monsterinfo.melee_debounce_time <= level.time)
return;
else if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GLADIATOR_RAILGUN_1]))
return;
// charge up the railgun
self->pos1 = self->enemy->s.origin; // save for aiming the shot
self->pos1[2] += self->enemy->viewheight;
// RAFAEL
if (self->style == 1)
{
gi.sound(self, CHAN_WEAPON, sound_gunb, 1, ATTN_NORM, 0);
M_SetAnimation(self, &gladb_move_attack_gun);
}
else
{
// RAFAEL
gi.sound(self, CHAN_WEAPON, sound_gun, 1, ATTN_NORM, 0);
M_SetAnimation(self, &gladiator_move_attack_gun);
// RAFAEL
}
// RAFAEL
}
mframe_t gladiator_frames_pain[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(gladiator_move_pain) = { FRAME_pain2, FRAME_pain5, gladiator_frames_pain, gladiator_run };
mframe_t gladiator_frames_pain_air[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(gladiator_move_pain_air) = { FRAME_painup2, FRAME_painup6, gladiator_frames_pain_air, gladiator_run };
PAIN(gladiator_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->pain_debounce_time)
{
if ((self->velocity[2] > 100) && (self->monsterinfo.active_move == &gladiator_move_pain))
M_SetAnimation(self, &gladiator_move_pain_air);
return;
}
self->pain_debounce_time = level.time + 3_sec;
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (self->velocity[2] > 100)
M_SetAnimation(self, &gladiator_move_pain_air);
else
M_SetAnimation(self, &gladiator_move_pain);
}
MONSTERINFO_SETSKIN(gladiator_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum |= 1;
else
self->s.skinnum &= ~1;
}
void gladiator_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
monster_dead(self);
}
static void gladiator_shrink(edict_t *self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t gladiator_frames_death[] = {
{ ai_move },
{ ai_move },
{ ai_move, 0, gladiator_shrink },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(gladiator_move_death) = { FRAME_death2, FRAME_death22, gladiator_frames_death, gladiator_dead };
DIE(gladiator_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
self->s.skinnum /= 2;
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ 2, "models/monsters/gladiatr/gibs/thigh.md2", GIB_SKINNED },
{ "models/monsters/gladiatr/gibs/larm.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/gladiatr/gibs/rarm.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/gladiatr/gibs/chest.md2", GIB_SKINNED },
{ "models/monsters/gladiatr/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_BODY, sound_die, 1, ATTN_NORM, 0);
if (brandom())
gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
M_SetAnimation(self, &gladiator_move_death);
}
//===========
// PGM
MONSTERINFO_BLOCKED(gladiator_blocked) (edict_t *self, float dist) -> bool
{
if (blocked_checkplat(self, dist))
return true;
return false;
}
// PGM
//===========
/*QUAKED monster_gladiator (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
*/
void SP_monster_gladiator(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/gladiatr/tris.md2");
gi.modelindex("models/monsters/gladiatr/gibs/chest.md2");
gi.modelindex("models/monsters/gladiatr/gibs/head.md2");
gi.modelindex("models/monsters/gladiatr/gibs/larm.md2");
gi.modelindex("models/monsters/gladiatr/gibs/rarm.md2");
gi.modelindex("models/monsters/gladiatr/gibs/thigh.md2");
// RAFAEL
if (strcmp(self->classname, "monster_gladb") == 0)
{
sound_gunb = gi.soundindex("weapons/plasshot.wav");
self->health = 250 * st.health_multiplier;
self->mass = 350;
if (!st.was_key_specified("power_armor_type"))
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD;
if (!st.was_key_specified("power_armor_power"))
self->monsterinfo.power_armor_power = 250;
self->s.skinnum = 2;
self->style = 1;
self->monsterinfo.weapon_sound = gi.soundindex("weapons/phaloop.wav");
}
else
{
// RAFAEL
sound_gun = gi.soundindex("gladiator/railgun.wav");
self->health = 400 * st.health_multiplier;
self->mass = 400;
// RAFAEL
self->monsterinfo.weapon_sound = gi.soundindex("weapons/rg_hum.wav");
}
// RAFAEL
self->gib_health = -175;
self->mins = { -32, -32, -24 };
self->maxs = { 32, 32, 42 };
self->pain = gladiator_pain;
self->die = gladiator_die;
self->monsterinfo.stand = gladiator_stand;
self->monsterinfo.walk = gladiator_walk;
self->monsterinfo.run = gladiator_run;
self->monsterinfo.dodge = nullptr;
self->monsterinfo.attack = gladiator_attack;
self->monsterinfo.melee = gladiator_melee;
self->monsterinfo.sight = gladiator_sight;
self->monsterinfo.idle = gladiator_idle;
self->monsterinfo.search = gladiator_search;
self->monsterinfo.blocked = gladiator_blocked; // PGM
self->monsterinfo.setskin = gladiator_setskin;
gi.linkentity(self);
M_SetAnimation(self, &gladiator_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
walkmonster_start(self);
}
//
// monster_gladb
// RAFAEL
//
/*QUAKED monster_gladb (1 .5 0) (-32 -32 -24) (32 32 64) Ambush Trigger_Spawn Sight
*/
void SP_monster_gladb(edict_t *self)
{
SP_monster_gladiator(self);
}

101
rerelease/m_gladiator.h Normal file
View File

@@ -0,0 +1,101 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/gladiatr
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_stand1,
FRAME_stand2,
FRAME_stand3,
FRAME_stand4,
FRAME_stand5,
FRAME_stand6,
FRAME_stand7,
FRAME_walk1,
FRAME_walk2,
FRAME_walk3,
FRAME_walk4,
FRAME_walk5,
FRAME_walk6,
FRAME_walk7,
FRAME_walk8,
FRAME_walk9,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_run1,
FRAME_run2,
FRAME_run3,
FRAME_run4,
FRAME_run5,
FRAME_run6,
FRAME_melee1,
FRAME_melee2,
FRAME_melee3,
FRAME_melee4,
FRAME_melee5,
FRAME_melee6,
FRAME_melee7,
FRAME_melee8,
FRAME_melee9,
FRAME_melee10,
FRAME_melee11,
FRAME_melee12,
FRAME_melee13,
FRAME_melee14,
FRAME_melee15,
FRAME_melee16,
FRAME_melee17,
FRAME_attack1,
FRAME_attack2,
FRAME_attack3,
FRAME_attack4,
FRAME_attack5,
FRAME_attack6,
FRAME_attack7,
FRAME_attack8,
FRAME_attack9,
FRAME_pain1,
FRAME_pain2,
FRAME_pain3,
FRAME_pain4,
FRAME_pain5,
FRAME_pain6,
FRAME_death1,
FRAME_death2,
FRAME_death3,
FRAME_death4,
FRAME_death5,
FRAME_death6,
FRAME_death7,
FRAME_death8,
FRAME_death9,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_painup1,
FRAME_painup2,
FRAME_painup3,
FRAME_painup4,
FRAME_painup5,
FRAME_painup6,
FRAME_painup7
};
constexpr float MODEL_SCALE = 1.000000f;

523
rerelease/m_guardian.cpp Normal file
View File

@@ -0,0 +1,523 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
GUARDIAN
==============================================================================
*/
#include "g_local.h"
#include "m_guardian.h"
#include "m_flash.h"
//
// stand
//
mframe_t guardian_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(guardian_move_stand) = { FRAME_idle1, FRAME_idle52, guardian_frames_stand, nullptr };
MONSTERINFO_STAND(guardian_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &guardian_move_stand);
}
//
// walk
//
static int sound_step;
void guardian_footstep(edict_t *self)
{
gi.sound(self, CHAN_BODY, sound_step, 1.f, ATTN_NORM, 0.0f);
}
mframe_t guardian_frames_walk[] = {
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8, guardian_footstep },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8 },
{ ai_walk, 8, guardian_footstep },
{ ai_walk, 8 }
};
MMOVE_T(guardian_move_walk) = { FRAME_walk1, FRAME_walk19, guardian_frames_walk, nullptr };
MONSTERINFO_WALK(guardian_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &guardian_move_walk);
}
//
// run
//
mframe_t guardian_frames_run[] = {
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8, guardian_footstep },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8 },
{ ai_run, 8, guardian_footstep },
{ ai_run, 8 }
};
MMOVE_T(guardian_move_run) = { FRAME_walk1, FRAME_walk19, guardian_frames_run, nullptr };
MONSTERINFO_RUN(guardian_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
M_SetAnimation(self, &guardian_move_stand);
return;
}
M_SetAnimation(self, &guardian_move_run);
}
//
// pain
//
mframe_t guardian_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(guardian_move_pain1) = { FRAME_pain1_1, FRAME_pain1_8, guardian_frames_pain1, guardian_run };
PAIN(guardian_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (mod.id != MOD_CHAINFIST && damage <= 10)
return;
if (level.time < self->pain_debounce_time)
return;
if (mod.id != MOD_CHAINFIST && damage <= 75)
if (frandom() > 0.2f)
return;
// don't go into pain while attacking
if ((self->s.frame >= FRAME_atk1_spin1) && (self->s.frame <= FRAME_atk1_spin15))
return;
if ((self->s.frame >= FRAME_atk2_fire1) && (self->s.frame <= FRAME_atk2_fire4))
return;
if ((self->s.frame >= FRAME_kick_in1) && (self->s.frame <= FRAME_kick_in13))
return;
self->pain_debounce_time = level.time + 3_sec;
//gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
M_SetAnimation(self, &guardian_move_pain1);
self->monsterinfo.weapon_sound = 0;
}
mframe_t guardian_frames_atk1_out[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(guardian_atk1_out) = { FRAME_atk1_out1, FRAME_atk1_out3, guardian_frames_atk1_out, guardian_run };
void guardian_atk1_finish(edict_t *self)
{
M_SetAnimation(self, &guardian_atk1_out);
self->monsterinfo.weapon_sound = 0;
}
static int sound_charge;
static int sound_spin_loop;
void guardian_atk1_charge(edict_t *self)
{
self->monsterinfo.weapon_sound = sound_spin_loop;
gi.sound(self, CHAN_WEAPON, sound_charge, 1.f, ATTN_NORM, 0.f);
}
void guardian_fire_blaster(edict_t *self)
{
vec3_t forward, right, target;
vec3_t start;
monster_muzzleflash_id_t id = MZ2_GUARDIAN_BLASTER;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[id], forward, right);
target = self->enemy->s.origin;
target[2] += self->enemy->viewheight;
for (int i = 0; i < 3; i++)
target[i] += crandom_open() * 5.f;
forward = target - start;
forward.normalize();
monster_fire_blaster(self, start, forward, 2, 1000, id, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
if (self->enemy && self->enemy->health > 0 &&
self->s.frame == FRAME_atk1_spin12 && self->timestamp > level.time && visible(self, self->enemy))
self->monsterinfo.nextframe = FRAME_atk1_spin5;
}
mframe_t guardian_frames_atk1_spin[] = {
{ ai_charge, 0, guardian_atk1_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0, guardian_fire_blaster },
{ ai_charge, 0 },
{ ai_charge, 0 },
{ ai_charge, 0 }
};
MMOVE_T(guardian_move_atk1_spin) = { FRAME_atk1_spin1, FRAME_atk1_spin15, guardian_frames_atk1_spin, guardian_atk1_finish };
void guardian_atk1(edict_t *self)
{
M_SetAnimation(self, &guardian_move_atk1_spin);
self->timestamp = level.time + 650_ms + random_time(1.5_sec);
}
mframe_t guardian_frames_atk1_in[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(guardian_move_atk1_in) = { FRAME_atk1_in1, FRAME_atk1_in3, guardian_frames_atk1_in, guardian_atk1 };
mframe_t guardian_frames_atk2_out[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, guardian_footstep },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(guardian_move_atk2_out) = { FRAME_atk2_out1, FRAME_atk2_out7, guardian_frames_atk2_out, guardian_run };
void guardian_atk2_out(edict_t *self)
{
M_SetAnimation(self, &guardian_move_atk2_out);
}
static int sound_laser;
constexpr vec3_t laser_positions[] = {
{ 125.0f, -70.f, 60.f },
{ 112.0f, -62.f, 60.f }
};
PRETHINK(guardian_fire_update) (edict_t *laser) -> void
{
edict_t *self = laser->owner;
vec3_t forward, right, target;
vec3_t start;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, laser_positions[1 - (self->s.frame & 1)], forward, right);
target = self->enemy->s.origin + self->enemy->mins;
for (int i = 0; i < 3; i++)
target[i] += frandom() * self->enemy->size[i];
forward = target - start;
forward.normalize();
laser->s.origin = start;
laser->movedir = forward;
gi.linkentity(laser);
dabeam_update(laser, false);
}
void guardian_laser_fire(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_laser, 1.f, ATTN_NORM, 0.f);
monster_fire_dabeam(self, 25, self->s.frame & 1, guardian_fire_update);
}
mframe_t guardian_frames_atk2_fire[] = {
{ ai_charge, 0, guardian_laser_fire },
{ ai_charge, 0, guardian_laser_fire },
{ ai_charge, 0, guardian_laser_fire },
{ ai_charge, 0, guardian_laser_fire }
};
MMOVE_T(guardian_move_atk2_fire) = { FRAME_atk2_fire1, FRAME_atk2_fire4, guardian_frames_atk2_fire, guardian_atk2_out };
void guardian_atk2(edict_t *self)
{
M_SetAnimation(self, &guardian_move_atk2_fire);
}
mframe_t guardian_frames_atk2_in[] = {
{ ai_charge, 0, guardian_footstep },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, guardian_footstep },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, guardian_footstep },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(guardian_move_atk2_in) = { FRAME_atk2_in1, FRAME_atk2_in12, guardian_frames_atk2_in, guardian_atk2 };
void guardian_kick(edict_t *self)
{
if (!fire_hit(self, { MELEE_DISTANCE, 0, -80 }, 85, 700))
self->monsterinfo.melee_debounce_time = level.time + 1000_ms;
}
mframe_t guardian_frames_kick[] = {
{ ai_charge },
{ ai_charge, 0, guardian_footstep },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, guardian_kick },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, guardian_footstep },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(guardian_move_kick) = { FRAME_kick_in1, FRAME_kick_in13, guardian_frames_kick, guardian_run };
MONSTERINFO_ATTACK(guardian_attack) (edict_t *self) -> void
{
if (!self->enemy || !self->enemy->inuse)
return;
float r = range_to(self, self->enemy);
if (r > RANGE_NEAR)
M_SetAnimation(self, &guardian_move_atk2_in);
else if (self->monsterinfo.melee_debounce_time < level.time && r < 120.f)
M_SetAnimation(self, &guardian_move_kick);
else
M_SetAnimation(self, &guardian_move_atk1_in);
}
//
// death
//
void guardian_explode(edict_t *self)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1_BIG);
gi.WritePosition((self->s.origin + self->mins) + vec3_t { frandom() * self->size[0], frandom() * self->size[1], frandom() * self->size[2] });
gi.multicast(self->s.origin, MULTICAST_ALL, false);
}
constexpr const char *gibs[] = {
"models/monsters/guardian/gib1.md2",
"models/monsters/guardian/gib2.md2",
"models/monsters/guardian/gib3.md2",
"models/monsters/guardian/gib4.md2",
"models/monsters/guardian/gib5.md2",
"models/monsters/guardian/gib6.md2",
"models/monsters/guardian/gib7.md2"
};
void guardian_dead(edict_t *self)
{
for (int i = 0; i < 3; i++)
guardian_explode(self);
ThrowGibs(self, 125, {
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ 4, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
{ 2, gibs[0], GIB_METALLIC },
{ 2, gibs[1], GIB_METALLIC },
{ 2, gibs[2], GIB_METALLIC },
{ 2, gibs[3], GIB_METALLIC },
{ 2, gibs[4], GIB_METALLIC },
{ 2, gibs[5], GIB_METALLIC },
{ gibs[6], GIB_METALLIC | GIB_HEAD }
});
}
mframe_t guardian_frames_death1[FRAME_death26 - FRAME_death1 + 1] = {
{ ai_move, 0, BossExplode },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(guardian_move_death) = { FRAME_death1, FRAME_death26, guardian_frames_death1, guardian_dead };
DIE(guardian_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
// regular death
//gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
self->monsterinfo.weapon_sound = 0;
self->deadflag = true;
self->takedamage = true;
M_SetAnimation(self, &guardian_move_death);
}
//
// monster_tank
//
/*QUAKED monster_guardian (1 .5 0) (-96 -96 -66) (96 96 62) Ambush Trigger_Spawn Sight
*/
void SP_monster_guardian(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
for (auto &gib : gibs)
gi.modelindex(gib);
self->s.modelindex = gi.modelindex("models/monsters/guardian/tris.md2");
self->mins = { -96, -96, -66 };
self->maxs = { 96, 96, 62 };
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->health = 2500 * st.health_multiplier;
self->gib_health = -200;
self->monsterinfo.scale = MODEL_SCALE;
self->mass = 850;
self->pain = guardian_pain;
self->die = guardian_die;
self->monsterinfo.stand = guardian_stand;
self->monsterinfo.walk = guardian_walk;
self->monsterinfo.run = guardian_run;
self->monsterinfo.attack = guardian_attack;
gi.linkentity(self);
M_SetAnimation(self, &guardian_move_stand);
walkmonster_start(self);
}

225
rerelease/m_guardian.h Normal file
View File

@@ -0,0 +1,225 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/guardian
// This file generated by ModelGen - Do NOT Modify
enum {
FRAME_sleep1,
FRAME_sleep2,
FRAME_sleep3,
FRAME_sleep4,
FRAME_sleep5,
FRAME_sleep6,
FRAME_sleep7,
FRAME_sleep8,
FRAME_sleep9,
FRAME_sleep10,
FRAME_sleep11,
FRAME_sleep12,
FRAME_sleep13,
FRAME_sleep14,
FRAME_death1,
FRAME_death2,
FRAME_death3,
FRAME_death4,
FRAME_death5,
FRAME_death6,
FRAME_death7,
FRAME_death8,
FRAME_death9,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_death23,
FRAME_death24,
FRAME_death25,
FRAME_death26,
FRAME_atk1_out1,
FRAME_atk1_out2,
FRAME_atk1_out3,
FRAME_atk2_out1,
FRAME_atk2_out2,
FRAME_atk2_out3,
FRAME_atk2_out4,
FRAME_atk2_out5,
FRAME_atk2_out6,
FRAME_atk2_out7,
FRAME_kick_out1,
FRAME_kick_out2,
FRAME_kick_out3,
FRAME_kick_out4,
FRAME_kick_out5,
FRAME_kick_out6,
FRAME_kick_out7,
FRAME_kick_out8,
FRAME_kick_out9,
FRAME_kick_out10,
FRAME_kick_out11,
FRAME_kick_out12,
FRAME_pain1_1,
FRAME_pain1_2,
FRAME_pain1_3,
FRAME_pain1_4,
FRAME_pain1_5,
FRAME_pain1_6,
FRAME_pain1_7,
FRAME_pain1_8,
FRAME_idle1,
FRAME_idle2,
FRAME_idle3,
FRAME_idle4,
FRAME_idle5,
FRAME_idle6,
FRAME_idle7,
FRAME_idle8,
FRAME_idle9,
FRAME_idle10,
FRAME_idle11,
FRAME_idle12,
FRAME_idle13,
FRAME_idle14,
FRAME_idle15,
FRAME_idle16,
FRAME_idle17,
FRAME_idle18,
FRAME_idle19,
FRAME_idle20,
FRAME_idle21,
FRAME_idle22,
FRAME_idle23,
FRAME_idle24,
FRAME_idle25,
FRAME_idle26,
FRAME_idle27,
FRAME_idle28,
FRAME_idle29,
FRAME_idle30,
FRAME_idle31,
FRAME_idle32,
FRAME_idle33,
FRAME_idle34,
FRAME_idle35,
FRAME_idle36,
FRAME_idle37,
FRAME_idle38,
FRAME_idle39,
FRAME_idle40,
FRAME_idle41,
FRAME_idle42,
FRAME_idle43,
FRAME_idle44,
FRAME_idle45,
FRAME_idle46,
FRAME_idle47,
FRAME_idle48,
FRAME_idle49,
FRAME_idle50,
FRAME_idle51,
FRAME_idle52,
FRAME_atk1_in1,
FRAME_atk1_in2,
FRAME_atk1_in3,
FRAME_kick_in1,
FRAME_kick_in2,
FRAME_kick_in3,
FRAME_kick_in4,
FRAME_kick_in5,
FRAME_kick_in6,
FRAME_kick_in7,
FRAME_kick_in8,
FRAME_kick_in9,
FRAME_kick_in10,
FRAME_kick_in11,
FRAME_kick_in12,
FRAME_kick_in13,
FRAME_walk1,
FRAME_walk2,
FRAME_walk3,
FRAME_walk4,
FRAME_walk5,
FRAME_walk6,
FRAME_walk7,
FRAME_walk8,
FRAME_walk9,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_wake1,
FRAME_wake2,
FRAME_wake3,
FRAME_wake4,
FRAME_wake5,
FRAME_atk1_spin1,
FRAME_atk1_spin2,
FRAME_atk1_spin3,
FRAME_atk1_spin4,
FRAME_atk1_spin5,
FRAME_atk1_spin6,
FRAME_atk1_spin7,
FRAME_atk1_spin8,
FRAME_atk1_spin9,
FRAME_atk1_spin10,
FRAME_atk1_spin11,
FRAME_atk1_spin12,
FRAME_atk1_spin13,
FRAME_atk1_spin14,
FRAME_atk1_spin15,
FRAME_atk2_fire1,
FRAME_atk2_fire2,
FRAME_atk2_fire3,
FRAME_atk2_fire4,
FRAME_turnl_1,
FRAME_turnl_2,
FRAME_turnl_3,
FRAME_turnl_4,
FRAME_turnl_5,
FRAME_turnl_6,
FRAME_turnl_7,
FRAME_turnl_8,
FRAME_turnl_9,
FRAME_turnl_10,
FRAME_turnl_11,
FRAME_turnr_1,
FRAME_turnr_2,
FRAME_turnr_3,
FRAME_turnr_4,
FRAME_turnr_5,
FRAME_turnr_6,
FRAME_turnr_7,
FRAME_turnr_8,
FRAME_turnr_9,
FRAME_turnr_10,
FRAME_turnr_11,
FRAME_atk2_in1,
FRAME_atk2_in2,
FRAME_atk2_in3,
FRAME_atk2_in4,
FRAME_atk2_in5,
FRAME_atk2_in6,
FRAME_atk2_in7,
FRAME_atk2_in8,
FRAME_atk2_in9,
FRAME_atk2_in10,
FRAME_atk2_in11,
FRAME_atk2_in12
};
constexpr float MODEL_SCALE = 1.000000;

1473
rerelease/m_guncmdr.cpp Normal file

File diff suppressed because it is too large Load Diff

920
rerelease/m_gunner.cpp Normal file
View File

@@ -0,0 +1,920 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
GUNNER
==============================================================================
*/
#include "g_local.h"
#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;
void gunner_idlesound(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
MONSTERINFO_SIGHT(gunner_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_SEARCH(gunner_search) (edict_t *self) -> void
{
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
void GunnerGrenade(edict_t *self);
void GunnerFire(edict_t *self);
void gunner_fire_chain(edict_t *self);
void gunner_refire_chain(edict_t *self);
void gunner_stand(edict_t *self);
mframe_t gunner_frames_fidget[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, gunner_idlesound },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(gunner_move_fidget) = { FRAME_stand31, FRAME_stand70, gunner_frames_fidget, gunner_stand };
void gunner_fidget(edict_t *self)
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
return;
else if (self->enemy)
return;
if (frandom() <= 0.05f)
M_SetAnimation(self, &gunner_move_fidget);
}
mframe_t gunner_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, gunner_fidget },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, gunner_fidget },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, gunner_fidget }
};
MMOVE_T(gunner_move_stand) = { FRAME_stand01, FRAME_stand30, gunner_frames_stand, nullptr };
MONSTERINFO_STAND(gunner_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &gunner_move_stand);
}
mframe_t gunner_frames_walk[] = {
{ ai_walk },
{ ai_walk, 3 },
{ ai_walk, 4 },
{ ai_walk, 5 },
{ ai_walk, 7 },
{ ai_walk, 2, monster_footstep },
{ ai_walk, 6 },
{ ai_walk, 4 },
{ ai_walk, 2 },
{ ai_walk, 7 },
{ ai_walk, 5 },
{ ai_walk, 7 },
{ ai_walk, 4, monster_footstep }
};
MMOVE_T(gunner_move_walk) = { FRAME_walk07, FRAME_walk19, gunner_frames_walk, nullptr };
MONSTERINFO_WALK(gunner_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &gunner_move_walk);
}
mframe_t gunner_frames_run[] = {
{ ai_run, 26 },
{ ai_run, 9, monster_footstep },
{ ai_run, 9 },
{ ai_run, 9, monster_done_dodge },
{ ai_run, 15 },
{ ai_run, 10, monster_footstep },
{ ai_run, 13 },
{ ai_run, 6 }
};
MMOVE_T(gunner_move_run) = { FRAME_run01, FRAME_run08, gunner_frames_run, nullptr };
MONSTERINFO_RUN(gunner_run) (edict_t *self) -> void
{
monster_done_dodge(self);
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &gunner_move_stand);
else
M_SetAnimation(self, &gunner_move_run);
}
mframe_t gunner_frames_runandshoot[] = {
{ ai_run, 32 },
{ ai_run, 15 },
{ ai_run, 10 },
{ ai_run, 18 },
{ ai_run, 8 },
{ ai_run, 20 }
};
MMOVE_T(gunner_move_runandshoot) = { FRAME_runs01, FRAME_runs06, gunner_frames_runandshoot, nullptr };
void gunner_runandshoot(edict_t *self)
{
M_SetAnimation(self, &gunner_move_runandshoot);
}
mframe_t gunner_frames_pain3[] = {
{ ai_move, -3 },
{ ai_move, 1 },
{ ai_move, 1 },
{ ai_move },
{ ai_move, 1 }
};
MMOVE_T(gunner_move_pain3) = { FRAME_pain301, FRAME_pain305, gunner_frames_pain3, gunner_run };
mframe_t gunner_frames_pain2[] = {
{ ai_move, -2 },
{ ai_move, 11 },
{ ai_move, 6, monster_footstep },
{ ai_move, 2 },
{ ai_move, -1 },
{ ai_move, -7 },
{ ai_move, -2 },
{ ai_move, -7, monster_footstep }
};
MMOVE_T(gunner_move_pain2) = { FRAME_pain201, FRAME_pain208, gunner_frames_pain2, gunner_run };
mframe_t gunner_frames_pain1[] = {
{ ai_move, 2 },
{ ai_move },
{ ai_move, -5 },
{ ai_move, 3 },
{ ai_move, -1, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 1 },
{ ai_move, 1 },
{ ai_move, 2 },
{ ai_move, 1, monster_footstep },
{ ai_move },
{ ai_move, -2 },
{ ai_move, -2 },
{ ai_move },
{ ai_move, 0, monster_footstep }
};
MMOVE_T(gunner_move_pain1) = { FRAME_pain101, FRAME_pain118, gunner_frames_pain1, gunner_run };
extern const mmove_t gunner_move_jump;
extern const mmove_t gunner_move_jump2;
PAIN(gunner_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
monster_done_dodge(self);
if (self->monsterinfo.active_move == &gunner_move_jump ||
self->monsterinfo.active_move == &gunner_move_jump2)
return;
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
if (brandom())
gi.sound(self, CHAN_VOICE, sound_pain, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (damage <= 10)
M_SetAnimation(self, &gunner_move_pain3);
else if (damage <= 25)
M_SetAnimation(self, &gunner_move_pain2);
else
M_SetAnimation(self, &gunner_move_pain1);
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
// PMM - clear duck flag
if (self->monsterinfo.aiflags & AI_DUCKED)
monster_duck_up(self);
}
MONSTERINFO_SETSKIN(gunner_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
void gunner_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
monster_dead(self);
}
static void gunner_shrink(edict_t *self)
{
self->maxs[2] = -4;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t gunner_frames_death[] = {
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move, -7, gunner_shrink },
{ ai_move, -3 },
{ ai_move, -5 },
{ ai_move, 8 },
{ ai_move, 6 },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move }
};
MMOVE_T(gunner_move_death) = { FRAME_death01, FRAME_death11, gunner_frames_death, gunner_dead };
DIE(gunner_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
self->s.skinnum /= 2;
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/monsters/gunner/gibs/chest.md2", GIB_SKINNED },
{ "models/monsters/gunner/gibs/garm.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/gunner/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/gunner/gibs/foot.md2", GIB_SKINNED },
{ "models/monsters/gunner/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
M_SetAnimation(self, &gunner_move_death);
}
// PMM - changed to duck code for new dodge
mframe_t gunner_frames_duck[] = {
{ ai_move, 1, monster_duck_down },
{ ai_move, 1 },
{ ai_move, 1, monster_duck_hold },
{ ai_move },
{ ai_move, -1 },
{ ai_move, -1 },
{ ai_move, 0, monster_duck_up },
{ ai_move, -1 }
};
MMOVE_T(gunner_move_duck) = { FRAME_duck01, FRAME_duck08, gunner_frames_duck, gunner_run };
// PMM - gunner dodge moved below so I know about attack sequences
void gunner_opengun(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_open, 1, ATTN_IDLE, 0);
}
void GunnerFire(edict_t *self)
{
vec3_t start;
vec3_t forward, right;
vec3_t aim;
monster_muzzleflash_id_t flash_number;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_GUNNER_MACHINEGUN_1 + (self->s.frame - FRAME_attak216));
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
PredictAim(self, self->enemy, start, 0, true, -0.2f, &aim, nullptr);
monster_fire_bullet(self, start, aim, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
}
bool gunner_grenade_check(edict_t *self)
{
vec3_t dir;
if (!self->enemy)
return false;
vec3_t start;
if (!M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_GRENADE_1], start))
return false;
vec3_t target;
// check for flag telling us that we're blindfiring
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
target = self->monsterinfo.blind_fire_target;
else
target = self->enemy->s.origin;
// see if we're too close
dir = target - start;
if (dir.length() < 100)
return false;
// check to see that we can trace to the player before we start
// tossing grenades around.
vec3_t aim = dir.normalized();
return M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false);
}
void GunnerGrenade(edict_t *self)
{
vec3_t start;
vec3_t forward, right, up;
vec3_t aim;
monster_muzzleflash_id_t flash_number;
float spread;
float pitch = 0;
// PMM
vec3_t target;
bool blindfire = false;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
// pmm
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
blindfire = true;
if (self->s.frame == FRAME_attak105 || self->s.frame == FRAME_attak309)
{
spread = -0.10f;
flash_number = MZ2_GUNNER_GRENADE_1;
}
else if (self->s.frame == FRAME_attak108 || self->s.frame == FRAME_attak312)
{
spread = -0.05f;
flash_number = MZ2_GUNNER_GRENADE_2;
}
else if (self->s.frame == FRAME_attak111 || self->s.frame == FRAME_attak315)
{
spread = 0.05f;
flash_number = MZ2_GUNNER_GRENADE_3;
}
else // (self->s.frame == FRAME_attak114)
{
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
spread = 0.10f;
flash_number = MZ2_GUNNER_GRENADE_4;
}
if (self->s.frame >= FRAME_attak301 && self->s.frame <= FRAME_attak324)
flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_GUNNER_GRENADE2_1 + (MZ2_GUNNER_GRENADE_4 - flash_number));
// pmm
// if we're shooting blind and we still can't see our enemy
if ((blindfire) && (!visible(self, self->enemy)))
{
// and we have a valid blind_fire_target
if (!self->monsterinfo.blind_fire_target)
return;
target = self->monsterinfo.blind_fire_target;
}
else
target = self->enemy->s.origin;
// pmm
AngleVectors(self->s.angles, forward, right, up); // PGM
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
// PGM
if (self->enemy)
{
float dist;
aim = target - self->s.origin;
dist = aim.length();
// aim up if they're on the same level as me and far away.
if ((dist > 512) && (aim[2] < 64) && (aim[2] > -64))
{
aim[2] += (dist - 512);
}
aim.normalize();
pitch = aim[2];
if (pitch > 0.4f)
pitch = 0.4f;
else if (pitch < -0.5f)
pitch = -0.5f;
}
// PGM
aim = forward + (right * spread);
aim += (up * pitch);
// try search for best pitch
if (M_CalculatePitchToFire(self, target, start, aim, 600, 2.5f, false))
monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), frandom() * 10.f);
else
// normal shot
monster_fire_grenade(self, start, aim, 50, 600, flash_number, (crandom_open() * 10.0f), 200.f + (crandom_open() * 10.0f));
}
mframe_t gunner_frames_attack_chain[] = {
{ ai_charge, 0, gunner_opengun },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(gunner_move_attack_chain) = { FRAME_attak209, FRAME_attak215, gunner_frames_attack_chain, gunner_fire_chain };
mframe_t gunner_frames_fire_chain[] = {
{ ai_charge, 0, GunnerFire },
{ ai_charge, 0, GunnerFire },
{ ai_charge, 0, GunnerFire },
{ ai_charge, 0, GunnerFire },
{ ai_charge, 0, GunnerFire },
{ ai_charge, 0, GunnerFire },
{ ai_charge, 0, GunnerFire },
{ ai_charge, 0, GunnerFire }
};
MMOVE_T(gunner_move_fire_chain) = { FRAME_attak216, FRAME_attak223, gunner_frames_fire_chain, gunner_refire_chain };
mframe_t gunner_frames_endfire_chain[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, monster_footstep }
};
MMOVE_T(gunner_move_endfire_chain) = { FRAME_attak224, FRAME_attak230, gunner_frames_endfire_chain, gunner_run };
void gunner_blind_check(edict_t *self)
{
vec3_t aim;
if (self->monsterinfo.aiflags & AI_MANUAL_STEERING)
{
aim = self->monsterinfo.blind_fire_target - self->s.origin;
self->ideal_yaw = vectoyaw(aim);
}
}
mframe_t gunner_frames_attack_grenade[] = {
{ ai_charge, 0, gunner_blind_check },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(gunner_move_attack_grenade) = { FRAME_attak101, FRAME_attak121, gunner_frames_attack_grenade, gunner_run };
mframe_t gunner_frames_attack_grenade2[] = {
//{ ai_charge },
//{ ai_charge },
//{ ai_charge },
//{ ai_charge },
{ ai_charge, 0, gunner_blind_check },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, GunnerGrenade },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge }
};
MMOVE_T(gunner_move_attack_grenade2) = { FRAME_attak305, FRAME_attak324, gunner_frames_attack_grenade2, gunner_run };
MONSTERINFO_ATTACK(gunner_attack) (edict_t *self) -> void
{
float chance, r;
monster_done_dodge(self);
// PMM
if (self->monsterinfo.attack_state == AS_BLIND)
{
if (self->timestamp > level.time)
return;
// setup shot probabilities
if (self->monsterinfo.blind_fire_delay < 1_sec)
chance = 1.0f;
else if (self->monsterinfo.blind_fire_delay < 7.5_sec)
chance = 0.4f;
else
chance = 0.1f;
r = frandom();
// minimum of 4.1 seconds, plus 0-3, after the shots are done
self->monsterinfo.blind_fire_delay += 4.1_sec + random_time(3_sec);
// don't shoot at the origin
if (!self->monsterinfo.blind_fire_target)
return;
// don't shoot if the dice say not to
if (r > chance)
return;
// turn on manual steering to signal both manual steering and blindfire
self->monsterinfo.aiflags |= AI_MANUAL_STEERING;
if (gunner_grenade_check(self))
{
// if the check passes, go for the attack
M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade);
self->monsterinfo.attack_finished = level.time + random_time(2_sec);
}
else
// turn off blindfire flag
self->monsterinfo.aiflags &= ~AI_MANUAL_STEERING;
self->timestamp = level.time + random_time(2_sec, 3_sec);
return;
}
// pmm
// PGM - gunner needs to use his chaingun if he's being attacked by a tesla.
if (self->bad_area || self->timestamp > level.time ||
(range_to(self, self->enemy) <= RANGE_NEAR * 0.35f && M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1])))
{
M_SetAnimation(self, &gunner_move_attack_chain);
}
else
{
if (self->timestamp <= level.time && frandom() <= 0.5f && gunner_grenade_check(self))
{
M_SetAnimation(self, brandom() ? &gunner_move_attack_grenade2 : &gunner_move_attack_grenade);
self->timestamp = level.time + random_time(2_sec, 3_sec);
}
else if (M_CheckClearShot(self, monster_flash_offset[MZ2_GUNNER_MACHINEGUN_1]))
M_SetAnimation(self, &gunner_move_attack_chain);
}
}
void gunner_fire_chain(edict_t *self)
{
M_SetAnimation(self, &gunner_move_fire_chain);
}
void gunner_refire_chain(edict_t *self)
{
if (self->enemy->health > 0)
if (visible(self, self->enemy))
if (frandom() <= 0.5f)
{
M_SetAnimation(self, &gunner_move_fire_chain, false);
return;
}
M_SetAnimation(self, &gunner_move_endfire_chain, false);
}
//===========
// PGM
void gunner_jump_now(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 100);
self->velocity += (up * 300);
}
void gunner_jump2_now(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 150);
self->velocity += (up * 400);
}
void gunner_jump_wait_land(edict_t *self)
{
if (self->groundentity == nullptr)
{
self->monsterinfo.nextframe = self->s.frame;
if (monster_jump_finished(self))
self->monsterinfo.nextframe = self->s.frame + 1;
}
else
self->monsterinfo.nextframe = self->s.frame + 1;
}
mframe_t gunner_frames_jump[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, gunner_jump_now },
{ ai_move },
{ ai_move },
{ ai_move, 0, gunner_jump_wait_land },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(gunner_move_jump) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump, gunner_run };
mframe_t gunner_frames_jump2[] = {
{ ai_move, -8 },
{ ai_move, -4 },
{ ai_move, -4 },
{ ai_move, 0, gunner_jump2_now },
{ ai_move },
{ ai_move },
{ ai_move, 0, gunner_jump_wait_land },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(gunner_move_jump2) = { FRAME_jump01, FRAME_jump10, gunner_frames_jump2, gunner_run };
void gunner_jump(edict_t *self, blocked_jump_result_t result)
{
if (!self->enemy)
return;
monster_done_dodge(self);
if (result == blocked_jump_result_t::JUMP_JUMP_UP)
M_SetAnimation(self, &gunner_move_jump2);
else
M_SetAnimation(self, &gunner_move_jump);
}
//===========
// PGM
MONSTERINFO_BLOCKED(gunner_blocked) (edict_t *self, float dist) -> bool
{
if (blocked_checkplat(self, dist))
return true;
if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
{
if (result != blocked_jump_result_t::JUMP_TURN)
gunner_jump(self, result);
return true;
}
return false;
}
// PGM
//===========
// PMM - new duck code
MONSTERINFO_DUCK(gunner_duck) (edict_t *self, gtime_t eta) -> bool
{
if ((self->monsterinfo.active_move == &gunner_move_jump2) ||
(self->monsterinfo.active_move == &gunner_move_jump))
{
return false;
}
if ((self->monsterinfo.active_move == &gunner_move_attack_chain) ||
(self->monsterinfo.active_move == &gunner_move_fire_chain) ||
(self->monsterinfo.active_move == &gunner_move_attack_grenade) ||
(self->monsterinfo.active_move == &gunner_move_attack_grenade2))
{
// if we're shooting don't dodge
self->monsterinfo.unduck(self);
return false;
}
if (frandom() > 0.5f)
GunnerGrenade(self);
M_SetAnimation(self, &gunner_move_duck);
return true;
}
MONSTERINFO_SIDESTEP(gunner_sidestep) (edict_t *self) -> bool
{
if ((self->monsterinfo.active_move == &gunner_move_jump2) ||
(self->monsterinfo.active_move == &gunner_move_jump) ||
(self->monsterinfo.active_move == &gunner_move_pain1))
return false;
if ((self->monsterinfo.active_move == &gunner_move_attack_chain) ||
(self->monsterinfo.active_move == &gunner_move_fire_chain) ||
(self->monsterinfo.active_move == &gunner_move_attack_grenade) ||
(self->monsterinfo.active_move == &gunner_move_attack_grenade2))
{
// if we're shooting, don't dodge
return false;
}
if (self->monsterinfo.active_move != &gunner_move_run)
M_SetAnimation(self, &gunner_move_run);
return true;
}
constexpr spawnflags_t SPAWNFLAG_GUNNER_NOJUMPING = 8_spawnflag;
/*QUAKED monster_gunner (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping
model="models/monsters/gunner/tris.md2"
*/
void SP_monster_gunner(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
gi.soundindex("gunner/gunatck2.wav");
gi.soundindex("gunner/gunatck3.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/gunner/tris.md2");
gi.modelindex("models/monsters/gunner/gibs/chest.md2");
gi.modelindex("models/monsters/gunner/gibs/foot.md2");
gi.modelindex("models/monsters/gunner/gibs/garm.md2");
gi.modelindex("models/monsters/gunner/gibs/gun.md2");
gi.modelindex("models/monsters/gunner/gibs/head.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 36 };
self->health = 175 * st.health_multiplier;
self->gib_health = -70;
self->mass = 200;
self->pain = gunner_pain;
self->die = gunner_die;
self->monsterinfo.stand = gunner_stand;
self->monsterinfo.walk = gunner_walk;
self->monsterinfo.run = gunner_run;
// pmm
self->monsterinfo.dodge = M_MonsterDodge;
self->monsterinfo.duck = gunner_duck;
self->monsterinfo.unduck = monster_duck_up;
self->monsterinfo.sidestep = gunner_sidestep;
self->monsterinfo.blocked = gunner_blocked; // PGM
// pmm
self->monsterinfo.attack = gunner_attack;
self->monsterinfo.melee = nullptr;
self->monsterinfo.sight = gunner_sight;
self->monsterinfo.search = gunner_search;
self->monsterinfo.setskin = gunner_setskin;
gi.linkentity(self);
M_SetAnimation(self, &gunner_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
// PMM
self->monsterinfo.blindfire = true;
self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_GUNNER_NOJUMPING);
self->monsterinfo.drop_height = 192;
self->monsterinfo.jump_height = 40;
walkmonster_start(self);
}

809
rerelease/m_gunner.h Normal file
View File

@@ -0,0 +1,809 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// E:\G Drive\md2f\quake2\baseq2\models/monsters/gunner
// This file generated by qdata - Do NOT Modify
enum {
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_stand46,
FRAME_stand47,
FRAME_stand48,
FRAME_stand49,
FRAME_stand50,
FRAME_stand51,
FRAME_stand52,
FRAME_stand53,
FRAME_stand54,
FRAME_stand55,
FRAME_stand56,
FRAME_stand57,
FRAME_stand58,
FRAME_stand59,
FRAME_stand60,
FRAME_stand61,
FRAME_stand62,
FRAME_stand63,
FRAME_stand64,
FRAME_stand65,
FRAME_stand66,
FRAME_stand67,
FRAME_stand68,
FRAME_stand69,
FRAME_stand70,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_walk21,
FRAME_walk22,
FRAME_walk23,
FRAME_walk24,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_run07,
FRAME_run08,
FRAME_runs01,
FRAME_runs02,
FRAME_runs03,
FRAME_runs04,
FRAME_runs05,
FRAME_runs06,
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak116,
FRAME_attak117,
FRAME_attak118,
FRAME_attak119,
FRAME_attak120,
FRAME_attak121,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_attak214,
FRAME_attak215,
FRAME_attak216,
FRAME_attak217,
FRAME_attak218,
FRAME_attak219,
FRAME_attak220,
FRAME_attak221,
FRAME_attak222,
FRAME_attak223,
FRAME_attak224,
FRAME_attak225,
FRAME_attak226,
FRAME_attak227,
FRAME_attak228,
FRAME_attak229,
FRAME_attak230,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain106,
FRAME_pain107,
FRAME_pain108,
FRAME_pain109,
FRAME_pain110,
FRAME_pain111,
FRAME_pain112,
FRAME_pain113,
FRAME_pain114,
FRAME_pain115,
FRAME_pain116,
FRAME_pain117,
FRAME_pain118,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain206,
FRAME_pain207,
FRAME_pain208,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11,
FRAME_duck01,
FRAME_duck02,
FRAME_duck03,
FRAME_duck04,
FRAME_duck05,
FRAME_duck06,
FRAME_duck07,
FRAME_duck08,
FRAME_jump01,
FRAME_jump02,
FRAME_jump03,
FRAME_jump04,
FRAME_jump05,
FRAME_jump06,
FRAME_jump07,
FRAME_jump08,
FRAME_jump09,
FRAME_jump10,
FRAME_shield01,
FRAME_shield02,
FRAME_shield03,
FRAME_shield04,
FRAME_shield05,
FRAME_shield06,
FRAME_attak301,
FRAME_attak302,
FRAME_attak303,
FRAME_attak304,
FRAME_attak305,
FRAME_attak306,
FRAME_attak307,
FRAME_attak308,
FRAME_attak309,
FRAME_attak310,
FRAME_attak311,
FRAME_attak312,
FRAME_attak313,
FRAME_attak314,
FRAME_attak315,
FRAME_attak316,
FRAME_attak317,
FRAME_attak318,
FRAME_attak319,
FRAME_attak320,
FRAME_attak321,
FRAME_attak322,
FRAME_attak323,
FRAME_attak324,
FRAME_c_stand101,
FRAME_c_stand102,
FRAME_c_stand103,
FRAME_c_stand104,
FRAME_c_stand105,
FRAME_c_stand106,
FRAME_c_stand107,
FRAME_c_stand108,
FRAME_c_stand109,
FRAME_c_stand110,
FRAME_c_stand111,
FRAME_c_stand112,
FRAME_c_stand113,
FRAME_c_stand114,
FRAME_c_stand115,
FRAME_c_stand116,
FRAME_c_stand117,
FRAME_c_stand118,
FRAME_c_stand119,
FRAME_c_stand120,
FRAME_c_stand121,
FRAME_c_stand122,
FRAME_c_stand123,
FRAME_c_stand124,
FRAME_c_stand125,
FRAME_c_stand126,
FRAME_c_stand127,
FRAME_c_stand128,
FRAME_c_stand129,
FRAME_c_stand130,
FRAME_c_stand131,
FRAME_c_stand132,
FRAME_c_stand133,
FRAME_c_stand134,
FRAME_c_stand135,
FRAME_c_stand136,
FRAME_c_stand137,
FRAME_c_stand138,
FRAME_c_stand139,
FRAME_c_stand140,
FRAME_c_stand201,
FRAME_c_stand202,
FRAME_c_stand203,
FRAME_c_stand204,
FRAME_c_stand205,
FRAME_c_stand206,
FRAME_c_stand207,
FRAME_c_stand208,
FRAME_c_stand209,
FRAME_c_stand210,
FRAME_c_stand211,
FRAME_c_stand212,
FRAME_c_stand213,
FRAME_c_stand214,
FRAME_c_stand215,
FRAME_c_stand216,
FRAME_c_stand217,
FRAME_c_stand218,
FRAME_c_stand219,
FRAME_c_stand220,
FRAME_c_stand221,
FRAME_c_stand222,
FRAME_c_stand223,
FRAME_c_stand224,
FRAME_c_stand225,
FRAME_c_stand226,
FRAME_c_stand227,
FRAME_c_stand228,
FRAME_c_stand229,
FRAME_c_stand230,
FRAME_c_stand231,
FRAME_c_stand232,
FRAME_c_stand233,
FRAME_c_stand234,
FRAME_c_stand235,
FRAME_c_stand236,
FRAME_c_stand237,
FRAME_c_stand238,
FRAME_c_stand239,
FRAME_c_stand240,
FRAME_c_stand241,
FRAME_c_stand242,
FRAME_c_stand243,
FRAME_c_stand244,
FRAME_c_stand245,
FRAME_c_stand246,
FRAME_c_stand247,
FRAME_c_stand248,
FRAME_c_stand249,
FRAME_c_stand250,
FRAME_c_stand251,
FRAME_c_stand252,
FRAME_c_stand253,
FRAME_c_stand254,
FRAME_c_attack101,
FRAME_c_attack102,
FRAME_c_attack103,
FRAME_c_attack104,
FRAME_c_attack105,
FRAME_c_attack106,
FRAME_c_attack107,
FRAME_c_attack108,
FRAME_c_attack109,
FRAME_c_attack110,
FRAME_c_attack111,
FRAME_c_attack112,
FRAME_c_attack113,
FRAME_c_attack114,
FRAME_c_attack115,
FRAME_c_attack116,
FRAME_c_attack117,
FRAME_c_attack118,
FRAME_c_attack119,
FRAME_c_attack120,
FRAME_c_attack121,
FRAME_c_attack122,
FRAME_c_attack123,
FRAME_c_attack124,
FRAME_c_jump01,
FRAME_c_jump02,
FRAME_c_jump03,
FRAME_c_jump04,
FRAME_c_jump05,
FRAME_c_jump06,
FRAME_c_jump07,
FRAME_c_jump08,
FRAME_c_jump09,
FRAME_c_jump10,
FRAME_c_attack201,
FRAME_c_attack202,
FRAME_c_attack203,
FRAME_c_attack204,
FRAME_c_attack205,
FRAME_c_attack206,
FRAME_c_attack207,
FRAME_c_attack208,
FRAME_c_attack209,
FRAME_c_attack210,
FRAME_c_attack211,
FRAME_c_attack212,
FRAME_c_attack213,
FRAME_c_attack214,
FRAME_c_attack215,
FRAME_c_attack216,
FRAME_c_attack217,
FRAME_c_attack218,
FRAME_c_attack219,
FRAME_c_attack220,
FRAME_c_attack221,
FRAME_c_attack301,
FRAME_c_attack302,
FRAME_c_attack303,
FRAME_c_attack304,
FRAME_c_attack305,
FRAME_c_attack306,
FRAME_c_attack307,
FRAME_c_attack308,
FRAME_c_attack309,
FRAME_c_attack310,
FRAME_c_attack311,
FRAME_c_attack312,
FRAME_c_attack313,
FRAME_c_attack314,
FRAME_c_attack315,
FRAME_c_attack316,
FRAME_c_attack317,
FRAME_c_attack318,
FRAME_c_attack319,
FRAME_c_attack320,
FRAME_c_attack321,
FRAME_c_attack401,
FRAME_c_attack402,
FRAME_c_attack403,
FRAME_c_attack404,
FRAME_c_attack405,
FRAME_c_attack501,
FRAME_c_attack502,
FRAME_c_attack503,
FRAME_c_attack504,
FRAME_c_attack505,
FRAME_c_attack601,
FRAME_c_attack602,
FRAME_c_attack603,
FRAME_c_attack604,
FRAME_c_attack605,
FRAME_c_attack701,
FRAME_c_attack702,
FRAME_c_attack703,
FRAME_c_attack704,
FRAME_c_attack705,
FRAME_c_pain101,
FRAME_c_pain102,
FRAME_c_pain103,
FRAME_c_pain104,
FRAME_c_pain201,
FRAME_c_pain202,
FRAME_c_pain203,
FRAME_c_pain204,
FRAME_c_pain301,
FRAME_c_pain302,
FRAME_c_pain303,
FRAME_c_pain304,
FRAME_c_pain401,
FRAME_c_pain402,
FRAME_c_pain403,
FRAME_c_pain404,
FRAME_c_pain405,
FRAME_c_pain406,
FRAME_c_pain407,
FRAME_c_pain408,
FRAME_c_pain409,
FRAME_c_pain410,
FRAME_c_pain411,
FRAME_c_pain412,
FRAME_c_pain413,
FRAME_c_pain414,
FRAME_c_pain415,
FRAME_c_pain501,
FRAME_c_pain502,
FRAME_c_pain503,
FRAME_c_pain504,
FRAME_c_pain505,
FRAME_c_pain506,
FRAME_c_pain507,
FRAME_c_pain508,
FRAME_c_pain509,
FRAME_c_pain510,
FRAME_c_pain511,
FRAME_c_pain512,
FRAME_c_pain513,
FRAME_c_pain514,
FRAME_c_pain515,
FRAME_c_pain516,
FRAME_c_pain517,
FRAME_c_pain518,
FRAME_c_pain519,
FRAME_c_pain520,
FRAME_c_pain521,
FRAME_c_pain522,
FRAME_c_pain523,
FRAME_c_pain524,
FRAME_c_death101,
FRAME_c_death102,
FRAME_c_death103,
FRAME_c_death104,
FRAME_c_death105,
FRAME_c_death106,
FRAME_c_death107,
FRAME_c_death108,
FRAME_c_death109,
FRAME_c_death110,
FRAME_c_death111,
FRAME_c_death112,
FRAME_c_death113,
FRAME_c_death114,
FRAME_c_death115,
FRAME_c_death116,
FRAME_c_death117,
FRAME_c_death118,
FRAME_c_death201,
FRAME_c_death202,
FRAME_c_death203,
FRAME_c_death204,
FRAME_c_death301,
FRAME_c_death302,
FRAME_c_death303,
FRAME_c_death304,
FRAME_c_death305,
FRAME_c_death306,
FRAME_c_death307,
FRAME_c_death308,
FRAME_c_death309,
FRAME_c_death310,
FRAME_c_death311,
FRAME_c_death312,
FRAME_c_death313,
FRAME_c_death314,
FRAME_c_death315,
FRAME_c_death316,
FRAME_c_death317,
FRAME_c_death318,
FRAME_c_death319,
FRAME_c_death320,
FRAME_c_death321,
FRAME_c_death401,
FRAME_c_death402,
FRAME_c_death403,
FRAME_c_death404,
FRAME_c_death405,
FRAME_c_death406,
FRAME_c_death407,
FRAME_c_death408,
FRAME_c_death409,
FRAME_c_death410,
FRAME_c_death411,
FRAME_c_death412,
FRAME_c_death413,
FRAME_c_death414,
FRAME_c_death415,
FRAME_c_death416,
FRAME_c_death417,
FRAME_c_death418,
FRAME_c_death419,
FRAME_c_death420,
FRAME_c_death421,
FRAME_c_death422,
FRAME_c_death423,
FRAME_c_death424,
FRAME_c_death425,
FRAME_c_death426,
FRAME_c_death427,
FRAME_c_death428,
FRAME_c_death429,
FRAME_c_death430,
FRAME_c_death431,
FRAME_c_death432,
FRAME_c_death433,
FRAME_c_death434,
FRAME_c_death435,
FRAME_c_death436,
FRAME_c_death501,
FRAME_c_death502,
FRAME_c_death503,
FRAME_c_death504,
FRAME_c_death505,
FRAME_c_death506,
FRAME_c_death507,
FRAME_c_death508,
FRAME_c_death509,
FRAME_c_death510,
FRAME_c_death511,
FRAME_c_death512,
FRAME_c_death513,
FRAME_c_death514,
FRAME_c_death515,
FRAME_c_death516,
FRAME_c_death517,
FRAME_c_death518,
FRAME_c_death519,
FRAME_c_death520,
FRAME_c_death521,
FRAME_c_death522,
FRAME_c_death523,
FRAME_c_death524,
FRAME_c_death525,
FRAME_c_death526,
FRAME_c_death527,
FRAME_c_death528,
FRAME_c_run101,
FRAME_c_run102,
FRAME_c_run103,
FRAME_c_run104,
FRAME_c_run105,
FRAME_c_run106,
FRAME_c_run201,
FRAME_c_run202,
FRAME_c_run203,
FRAME_c_run204,
FRAME_c_run205,
FRAME_c_run206,
FRAME_c_run301,
FRAME_c_run302,
FRAME_c_run303,
FRAME_c_run304,
FRAME_c_run305,
FRAME_c_run306,
FRAME_c_walk101,
FRAME_c_walk102,
FRAME_c_walk103,
FRAME_c_walk104,
FRAME_c_walk105,
FRAME_c_walk106,
FRAME_c_walk107,
FRAME_c_walk108,
FRAME_c_walk109,
FRAME_c_walk110,
FRAME_c_walk111,
FRAME_c_walk112,
FRAME_c_walk113,
FRAME_c_walk114,
FRAME_c_walk115,
FRAME_c_walk116,
FRAME_c_walk117,
FRAME_c_walk118,
FRAME_c_walk119,
FRAME_c_walk120,
FRAME_c_walk121,
FRAME_c_walk122,
FRAME_c_walk123,
FRAME_c_walk124,
FRAME_c_pain601,
FRAME_c_pain602,
FRAME_c_pain603,
FRAME_c_pain604,
FRAME_c_pain605,
FRAME_c_pain606,
FRAME_c_pain607,
FRAME_c_pain608,
FRAME_c_pain609,
FRAME_c_pain610,
FRAME_c_pain611,
FRAME_c_pain612,
FRAME_c_pain613,
FRAME_c_pain614,
FRAME_c_pain615,
FRAME_c_pain616,
FRAME_c_pain617,
FRAME_c_pain618,
FRAME_c_pain619,
FRAME_c_pain620,
FRAME_c_pain621,
FRAME_c_pain622,
FRAME_c_pain623,
FRAME_c_pain624,
FRAME_c_pain625,
FRAME_c_pain626,
FRAME_c_pain627,
FRAME_c_pain628,
FRAME_c_pain629,
FRAME_c_pain630,
FRAME_c_pain631,
FRAME_c_pain632,
FRAME_c_death601,
FRAME_c_death602,
FRAME_c_death603,
FRAME_c_death604,
FRAME_c_death605,
FRAME_c_death606,
FRAME_c_death607,
FRAME_c_death608,
FRAME_c_death609,
FRAME_c_death610,
FRAME_c_death611,
FRAME_c_death612,
FRAME_c_death613,
FRAME_c_death614,
FRAME_c_death701,
FRAME_c_death702,
FRAME_c_death703,
FRAME_c_death704,
FRAME_c_death705,
FRAME_c_death706,
FRAME_c_death707,
FRAME_c_death708,
FRAME_c_death709,
FRAME_c_death710,
FRAME_c_death711,
FRAME_c_death712,
FRAME_c_death713,
FRAME_c_death714,
FRAME_c_death715,
FRAME_c_death716,
FRAME_c_death717,
FRAME_c_death718,
FRAME_c_death719,
FRAME_c_death720,
FRAME_c_death721,
FRAME_c_death722,
FRAME_c_death723,
FRAME_c_death724,
FRAME_c_death725,
FRAME_c_death726,
FRAME_c_death727,
FRAME_c_death728,
FRAME_c_death729,
FRAME_c_death730,
FRAME_c_pain701,
FRAME_c_pain702,
FRAME_c_pain703,
FRAME_c_pain704,
FRAME_c_pain705,
FRAME_c_pain706,
FRAME_c_pain707,
FRAME_c_pain708,
FRAME_c_pain709,
FRAME_c_pain710,
FRAME_c_pain711,
FRAME_c_pain712,
FRAME_c_pain713,
FRAME_c_pain714,
FRAME_c_attack801,
FRAME_c_attack802,
FRAME_c_attack803,
FRAME_c_attack804,
FRAME_c_attack805,
FRAME_c_attack806,
FRAME_c_attack807,
FRAME_c_attack808,
FRAME_c_attack809,
FRAME_c_attack901,
FRAME_c_attack902,
FRAME_c_attack903,
FRAME_c_attack904,
FRAME_c_attack905,
FRAME_c_attack906,
FRAME_c_attack907,
FRAME_c_attack908,
FRAME_c_attack909,
FRAME_c_attack910,
FRAME_c_attack911,
FRAME_c_attack912,
FRAME_c_attack913,
FRAME_c_attack914,
FRAME_c_attack915,
FRAME_c_attack916,
FRAME_c_attack917,
FRAME_c_attack918,
FRAME_c_attack919,
FRAME_c_duck01,
FRAME_c_duck02,
FRAME_c_duckstep01,
FRAME_c_duckstep02,
FRAME_c_duckstep03,
FRAME_c_duckstep04,
FRAME_c_duckstep05,
FRAME_c_duckstep06,
FRAME_c_duckpain01,
FRAME_c_duckpain02,
FRAME_c_duckpain03,
FRAME_c_duckpain04,
FRAME_c_duckpain05,
FRAME_c_duckdeath01,
FRAME_c_duckdeath02,
FRAME_c_duckdeath03,
FRAME_c_duckdeath04,
FRAME_c_duckdeath05,
FRAME_c_duckdeath06,
FRAME_c_duckdeath07,
FRAME_c_duckdeath08,
FRAME_c_duckdeath09,
FRAME_c_duckdeath10,
FRAME_c_duckdeath11,
FRAME_c_duckdeath12,
FRAME_c_duckdeath13,
FRAME_c_duckdeath14,
FRAME_c_duckdeath15,
FRAME_c_duckdeath16,
FRAME_c_duckdeath17,
FRAME_c_duckdeath18,
FRAME_c_duckdeath19,
FRAME_c_duckdeath20,
FRAME_c_duckdeath21,
FRAME_c_duckdeath22,
FRAME_c_duckdeath23,
FRAME_c_duckdeath24,
FRAME_c_duckdeath25,
FRAME_c_duckdeath26,
FRAME_c_duckdeath27,
FRAME_c_duckdeath28,
FRAME_c_duckdeath29
};
constexpr float MODEL_SCALE = 1.150000f;

662
rerelease/m_hover.cpp Normal file
View File

@@ -0,0 +1,662 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
hover
==============================================================================
*/
#include "g_local.h"
#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;
// 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;
// ROGUE
MONSTERINFO_SIGHT(hover_sight) (edict_t *self, edict_t *other) -> void
{
// PMM - daedalus sounds
if (self->mass < 225)
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, daed_sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_SEARCH(hover_search) (edict_t *self) -> void
{
// PMM - daedalus sounds
if (self->mass < 225)
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
}
else
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, daed_sound_search1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, daed_sound_search2, 1, ATTN_NORM, 0);
}
}
void hover_run(edict_t *self);
void hover_dead(edict_t *self);
void hover_attack(edict_t *self);
void hover_reattack(edict_t *self);
void hover_fire_blaster(edict_t *self);
mframe_t hover_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(hover_move_stand) = { FRAME_stand01, FRAME_stand30, hover_frames_stand, nullptr };
mframe_t hover_frames_pain3[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(hover_move_pain3) = { FRAME_pain301, FRAME_pain309, hover_frames_pain3, hover_run };
mframe_t hover_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(hover_move_pain2) = { FRAME_pain201, FRAME_pain212, hover_frames_pain2, hover_run };
mframe_t hover_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move, 2 },
{ ai_move, -8 },
{ ai_move, -4 },
{ ai_move, -6 },
{ ai_move, -4 },
{ ai_move, -3 },
{ ai_move, 1 },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 3 },
{ ai_move, 1 },
{ ai_move },
{ ai_move, 2 },
{ ai_move, 3 },
{ ai_move, 2 },
{ ai_move, 7 },
{ ai_move, 1 },
{ ai_move },
{ ai_move },
{ ai_move, 2 },
{ ai_move },
{ ai_move },
{ ai_move, 5 },
{ ai_move, 3 },
{ ai_move, 4 }
};
MMOVE_T(hover_move_pain1) = { FRAME_pain101, FRAME_pain128, hover_frames_pain1, hover_run };
mframe_t hover_frames_walk[] = {
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 }
};
MMOVE_T(hover_move_walk) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_walk, nullptr };
mframe_t hover_frames_run[] = {
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 },
{ ai_run, 10 }
};
MMOVE_T(hover_move_run) = { FRAME_forwrd01, FRAME_forwrd35, hover_frames_run, nullptr };
static void hover_gib(edict_t *self)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
self->s.skinnum /= 2;
ThrowGibs(self, 150, {
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
{ "models/monsters/hover/gibs/chest.md2", GIB_SKINNED },
{ 2, "models/monsters/hover/gibs/ring.md2", GIB_SKINNED | GIB_METALLIC },
{ 2, "models/monsters/hover/gibs/foot.md2", GIB_SKINNED },
{ "models/monsters/hover/gibs/head.md2", GIB_SKINNED | GIB_HEAD },
});
}
THINK(hover_deadthink) (edict_t *self) -> void
{
if (!self->groundentity && level.time < self->timestamp)
{
self->nextthink = level.time + FRAME_TIME_S;
return;
}
hover_gib(self);
}
void hover_dying(edict_t *self)
{
if (self->groundentity)
{
hover_deadthink(self);
return;
}
if (brandom())
return;
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_PLAIN_EXPLOSION);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
if (brandom())
ThrowGibs(self, 120, {
{ "models/objects/gibs/sm_meat/tris.md2" }
});
else
ThrowGibs(self, 120, {
{ "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC }
});
}
mframe_t hover_frames_death1[] = {
{ ai_move },
{ ai_move, 0.f, hover_dying },
{ ai_move },
{ ai_move, 0.f, hover_dying },
{ ai_move },
{ ai_move, 0.f, hover_dying },
{ ai_move, -10, hover_dying },
{ ai_move, 3 },
{ ai_move, 5, hover_dying },
{ ai_move, 4, hover_dying },
{ ai_move, 7 }
};
MMOVE_T(hover_move_death1) = { FRAME_death101, FRAME_death111, hover_frames_death1, hover_dead };
mframe_t hover_frames_start_attack[] = {
{ ai_charge, 1 },
{ ai_charge, 1 },
{ ai_charge, 1 }
};
MMOVE_T(hover_move_start_attack) = { FRAME_attak101, FRAME_attak103, hover_frames_start_attack, hover_attack };
mframe_t hover_frames_attack1[] = {
{ ai_charge, -10, hover_fire_blaster },
{ ai_charge, -10, hover_fire_blaster },
{ ai_charge, 0, hover_reattack },
};
MMOVE_T(hover_move_attack1) = { FRAME_attak104, FRAME_attak106, hover_frames_attack1, nullptr };
mframe_t hover_frames_end_attack[] = {
{ ai_charge, 1 },
{ ai_charge, 1 }
};
MMOVE_T(hover_move_end_attack) = { FRAME_attak107, FRAME_attak108, hover_frames_end_attack, hover_run };
/* PMM - circle strafing code */
#if 0
mframe_t hover_frames_start_attack2[] = {
{ ai_charge, 15 },
{ ai_charge, 15 },
{ ai_charge, 15 }
};
MMOVE_T(hover_move_start_attack2) = { FRAME_attak101, FRAME_attak103, hover_frames_start_attack2, hover_attack };
#endif
mframe_t hover_frames_attack2[] = {
{ ai_charge, 10, hover_fire_blaster },
{ ai_charge, 10, hover_fire_blaster },
{ ai_charge, 10, hover_reattack },
};
MMOVE_T(hover_move_attack2) = { FRAME_attak104, FRAME_attak106, hover_frames_attack2, nullptr };
#if 0
mframe_t hover_frames_end_attack2[] = {
{ ai_charge, 15 },
{ ai_charge, 15 }
};
MMOVE_T(hover_move_end_attack2) = { FRAME_attak107, FRAME_attak108, hover_frames_end_attack2, hover_run };
#endif
// end of circle strafe
void hover_reattack(edict_t *self)
{
if (self->enemy->health > 0)
if (visible(self, self->enemy))
if (frandom() <= 0.6f)
{
if (self->monsterinfo.attack_state == AS_STRAIGHT)
{
M_SetAnimation(self, &hover_move_attack1);
return;
}
else if (self->monsterinfo.attack_state == AS_SLIDING)
{
M_SetAnimation(self, &hover_move_attack2);
return;
}
else
gi.Com_PrintFmt("hover_reattack: unexpected state {}\n", (int32_t) self->monsterinfo.attack_state);
}
M_SetAnimation(self, &hover_move_end_attack);
}
void hover_fire_blaster(edict_t *self)
{
vec3_t start;
vec3_t forward, right;
vec3_t end;
vec3_t dir;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
AngleVectors(self->s.angles, forward, right, nullptr);
vec3_t o = monster_flash_offset[(self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1];
start = M_ProjectFlashSource(self, o, forward, right);
end = self->enemy->s.origin;
end[2] += self->enemy->viewheight;
dir = end - start;
dir.normalize();
// PGM - daedalus fires blaster2
if (self->mass < 200)
monster_fire_blaster(self, start, dir, 1, 1000, (self->s.frame & 1) ? MZ2_HOVER_BLASTER_2 : MZ2_HOVER_BLASTER_1, (self->s.frame % 4) ? EF_NONE : EF_HYPERBLASTER);
else
monster_fire_blaster2(self, start, dir, 1, 1000, (self->s.frame & 1) ? MZ2_DAEDALUS_BLASTER_2 : MZ2_DAEDALUS_BLASTER, (self->s.frame % 4) ? EF_NONE : EF_BLASTER);
// PGM
}
MONSTERINFO_STAND(hover_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &hover_move_stand);
}
MONSTERINFO_RUN(hover_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &hover_move_stand);
else
M_SetAnimation(self, &hover_move_run);
}
MONSTERINFO_WALK(hover_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &hover_move_walk);
}
MONSTERINFO_ATTACK(hover_start_attack) (edict_t *self) -> void
{
M_SetAnimation(self, &hover_move_start_attack);
}
void hover_attack(edict_t *self)
{
float chance = 0.5f;
if (self->mass > 150) // the daedalus strafes more
chance += 0.1f;
if (frandom() > chance)
{
M_SetAnimation(self, &hover_move_attack1);
self->monsterinfo.attack_state = AS_STRAIGHT;
}
else // circle strafe
{
if (frandom() <= 0.5f) // switch directions
self->monsterinfo.lefty = !self->monsterinfo.lefty;
M_SetAnimation(self, &hover_move_attack2);
self->monsterinfo.attack_state = AS_SLIDING;
}
}
PAIN(hover_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
float r = frandom();
//====
if (r < 0.5f)
{
// PMM - daedalus sounds
if (self->mass < 225)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, daed_sound_pain1, 1, ATTN_NORM, 0);
}
else
{
// PMM - daedalus sounds
if (self->mass < 225)
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, daed_sound_pain2, 1, ATTN_NORM, 0);
}
// PGM
//====
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
r = frandom();
if (damage <= 25)
{
if (r < 0.5f)
M_SetAnimation(self, &hover_move_pain3);
else
M_SetAnimation(self, &hover_move_pain2);
}
else
{
//====
// PGM pain sequence is WAY too long
if (r < 0.3f)
M_SetAnimation(self, &hover_move_pain1);
else
M_SetAnimation(self, &hover_move_pain2);
// PGM
//====
}
}
MONSTERINFO_SETSKIN(hover_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum |= 1; // PGM support for skins 2 & 3.
else
self->s.skinnum &= ~1; // PGM support for skins 2 & 3.
}
void hover_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
self->movetype = MOVETYPE_TOSS;
self->think = hover_deadthink;
self->nextthink = level.time + FRAME_TIME_S;
self->timestamp = level.time + 15_sec;
gi.linkentity(self);
}
DIE(hover_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
self->s.effects = EF_NONE;
self->monsterinfo.power_armor_type = IT_NULL;
if (M_CheckGib(self, mod))
{
hover_gib(self);
return;
}
if (self->deadflag)
return;
// regular death
// PMM - daedalus sounds
if (self->mass < 225)
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_death1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_death2, 1, ATTN_NORM, 0);
}
else
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, daed_sound_death1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, daed_sound_death2, 1, ATTN_NORM, 0);
}
self->deadflag = true;
self->takedamage = true;
M_SetAnimation(self, &hover_move_death1);
}
static void hover_set_fly_parameters(edict_t *self)
{
self->monsterinfo.fly_thrusters = false;
self->monsterinfo.fly_acceleration = 20.f;
self->monsterinfo.fly_speed = 120.f;
// Icarus prefers to keep its distance, but flies slower than the flyer.
// he never pins because of this.
self->monsterinfo.fly_min_distance = 150.f;
self->monsterinfo.fly_max_distance = 350.f;
}
/*QUAKED monster_hover (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
*/
/*QUAKED monster_daedalus (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight
This is the improved icarus monster.
*/
void SP_monster_hover(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/hover/tris.md2");
gi.modelindex("models/monsters/hover/gibs/chest.md2");
gi.modelindex("models/monsters/hover/gibs/foot.md2");
gi.modelindex("models/monsters/hover/gibs/head.md2");
gi.modelindex("models/monsters/hover/gibs/ring.md2");
self->mins = { -24, -24, -24 };
self->maxs = { 24, 24, 32 };
self->health = 240 * st.health_multiplier;
self->gib_health = -100;
self->mass = 150;
self->pain = hover_pain;
self->die = hover_die;
self->monsterinfo.stand = hover_stand;
self->monsterinfo.walk = hover_walk;
self->monsterinfo.run = hover_run;
self->monsterinfo.attack = hover_start_attack;
self->monsterinfo.sight = hover_sight;
self->monsterinfo.search = hover_search;
self->monsterinfo.setskin = hover_setskin;
// PGM
if (strcmp(self->classname, "monster_daedalus") == 0)
{
self->health = 450 * st.health_multiplier;
self->mass = 225;
self->yaw_speed = 23;
if (!st.was_key_specified("power_armor_type"))
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SCREEN;
if (!st.was_key_specified("power_armor_power"))
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");
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");
gi.soundindex("hover/hovatck1.wav");
self->monsterinfo.engine_sound = gi.soundindex("hover/hovidle1.wav");
}
// PGM
gi.linkentity(self);
M_SetAnimation(self, &hover_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
flymonster_start(self);
// PGM
if (strcmp(self->classname, "monster_daedalus") == 0)
self->s.skinnum = 2;
// PGM
self->monsterinfo.aiflags |= AI_ALTERNATE_FLY;
hover_set_fly_parameters(self);
}

216
rerelease/m_hover.h Normal file
View File

@@ -0,0 +1,216 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/hover
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_forwrd01,
FRAME_forwrd02,
FRAME_forwrd03,
FRAME_forwrd04,
FRAME_forwrd05,
FRAME_forwrd06,
FRAME_forwrd07,
FRAME_forwrd08,
FRAME_forwrd09,
FRAME_forwrd10,
FRAME_forwrd11,
FRAME_forwrd12,
FRAME_forwrd13,
FRAME_forwrd14,
FRAME_forwrd15,
FRAME_forwrd16,
FRAME_forwrd17,
FRAME_forwrd18,
FRAME_forwrd19,
FRAME_forwrd20,
FRAME_forwrd21,
FRAME_forwrd22,
FRAME_forwrd23,
FRAME_forwrd24,
FRAME_forwrd25,
FRAME_forwrd26,
FRAME_forwrd27,
FRAME_forwrd28,
FRAME_forwrd29,
FRAME_forwrd30,
FRAME_forwrd31,
FRAME_forwrd32,
FRAME_forwrd33,
FRAME_forwrd34,
FRAME_forwrd35,
FRAME_stop101,
FRAME_stop102,
FRAME_stop103,
FRAME_stop104,
FRAME_stop105,
FRAME_stop106,
FRAME_stop107,
FRAME_stop108,
FRAME_stop109,
FRAME_stop201,
FRAME_stop202,
FRAME_stop203,
FRAME_stop204,
FRAME_stop205,
FRAME_stop206,
FRAME_stop207,
FRAME_stop208,
FRAME_takeof01,
FRAME_takeof02,
FRAME_takeof03,
FRAME_takeof04,
FRAME_takeof05,
FRAME_takeof06,
FRAME_takeof07,
FRAME_takeof08,
FRAME_takeof09,
FRAME_takeof10,
FRAME_takeof11,
FRAME_takeof12,
FRAME_takeof13,
FRAME_takeof14,
FRAME_takeof15,
FRAME_takeof16,
FRAME_takeof17,
FRAME_takeof18,
FRAME_takeof19,
FRAME_takeof20,
FRAME_takeof21,
FRAME_takeof22,
FRAME_takeof23,
FRAME_takeof24,
FRAME_takeof25,
FRAME_takeof26,
FRAME_takeof27,
FRAME_takeof28,
FRAME_takeof29,
FRAME_takeof30,
FRAME_land01,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain106,
FRAME_pain107,
FRAME_pain108,
FRAME_pain109,
FRAME_pain110,
FRAME_pain111,
FRAME_pain112,
FRAME_pain113,
FRAME_pain114,
FRAME_pain115,
FRAME_pain116,
FRAME_pain117,
FRAME_pain118,
FRAME_pain119,
FRAME_pain120,
FRAME_pain121,
FRAME_pain122,
FRAME_pain123,
FRAME_pain124,
FRAME_pain125,
FRAME_pain126,
FRAME_pain127,
FRAME_pain128,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain206,
FRAME_pain207,
FRAME_pain208,
FRAME_pain209,
FRAME_pain210,
FRAME_pain211,
FRAME_pain212,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death108,
FRAME_death109,
FRAME_death110,
FRAME_death111,
FRAME_backwd01,
FRAME_backwd02,
FRAME_backwd03,
FRAME_backwd04,
FRAME_backwd05,
FRAME_backwd06,
FRAME_backwd07,
FRAME_backwd08,
FRAME_backwd09,
FRAME_backwd10,
FRAME_backwd11,
FRAME_backwd12,
FRAME_backwd13,
FRAME_backwd14,
FRAME_backwd15,
FRAME_backwd16,
FRAME_backwd17,
FRAME_backwd18,
FRAME_backwd19,
FRAME_backwd20,
FRAME_backwd21,
FRAME_backwd22,
FRAME_backwd23,
FRAME_backwd24,
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108
};
constexpr float MODEL_SCALE = 1.000000f;

928
rerelease/m_infantry.cpp Normal file
View File

@@ -0,0 +1,928 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
INFANTRY
==============================================================================
*/
#include "g_local.h"
#include "m_infantry.h"
#include "m_flash.h"
void InfantryMachineGun(edict_t *self);
static int sound_pain1;
static int sound_pain2;
static int sound_die1;
static int 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;
// range at which we'll try to initiate a run-attack to close distance
constexpr float RANGE_RUN_ATTACK = RANGE_NEAR * 0.75f;
mframe_t infantry_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(infantry_move_stand) = { FRAME_stand50, FRAME_stand71, infantry_frames_stand, nullptr };
MONSTERINFO_STAND(infantry_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &infantry_move_stand);
}
mframe_t infantry_frames_fidget[] = {
{ ai_stand, 1 },
{ ai_stand },
{ ai_stand, 1 },
{ ai_stand, 3 },
{ ai_stand, 6 },
{ ai_stand, 3, monster_footstep },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 1 },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 1 },
{ ai_stand },
{ ai_stand, -1 },
{ ai_stand },
{ ai_stand },
{ ai_stand, 1 },
{ ai_stand },
{ ai_stand, -2 },
{ ai_stand, 1 },
{ ai_stand, 1 },
{ ai_stand, 1 },
{ ai_stand, -1 },
{ ai_stand },
{ ai_stand },
{ ai_stand, -1 },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, -1 },
{ ai_stand },
{ ai_stand },
{ ai_stand, 1 },
{ ai_stand },
{ ai_stand },
{ ai_stand, -1 },
{ ai_stand, -1 },
{ ai_stand },
{ ai_stand, -3 },
{ ai_stand, -2 },
{ ai_stand, -3 },
{ ai_stand, -3, monster_footstep },
{ ai_stand, -2 }
};
MMOVE_T(infantry_move_fidget) = { FRAME_stand01, FRAME_stand49, infantry_frames_fidget, infantry_stand };
MONSTERINFO_IDLE(infantry_fidget) (edict_t *self) -> void
{
if (self->enemy)
return;
M_SetAnimation(self, &infantry_move_fidget);
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
mframe_t infantry_frames_walk[] = {
{ ai_walk, 5, monster_footstep },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 5 },
{ ai_walk, 4 },
{ ai_walk, 5 },
{ ai_walk, 6, monster_footstep },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 5 }
};
MMOVE_T(infantry_move_walk) = { FRAME_walk03, FRAME_walk14, infantry_frames_walk, nullptr };
MONSTERINFO_WALK(infantry_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &infantry_move_walk);
}
mframe_t infantry_frames_run[] = {
{ ai_run, 10 },
{ ai_run, 15, monster_footstep },
{ ai_run, 5 },
{ ai_run, 7, monster_done_dodge },
{ ai_run, 18 },
{ ai_run, 20, monster_footstep },
{ ai_run, 2 },
{ ai_run, 6 }
};
MMOVE_T(infantry_move_run) = { FRAME_run01, FRAME_run08, infantry_frames_run, nullptr };
MONSTERINFO_RUN(infantry_run) (edict_t *self) -> void
{
monster_done_dodge(self);
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &infantry_move_stand);
else
M_SetAnimation(self, &infantry_move_run);
}
mframe_t infantry_frames_pain1[] = {
{ ai_move, -3 },
{ ai_move, -2 },
{ ai_move, -1 },
{ ai_move, -2 },
{ ai_move, -1, monster_footstep },
{ ai_move, 1 },
{ ai_move, -1 },
{ ai_move, 1 },
{ ai_move, 6 },
{ ai_move, 2, monster_footstep }
};
MMOVE_T(infantry_move_pain1) = { FRAME_pain101, FRAME_pain110, infantry_frames_pain1, infantry_run };
mframe_t infantry_frames_pain2[] = {
{ ai_move, -3 },
{ ai_move, -3 },
{ ai_move },
{ ai_move, -1 },
{ ai_move, -2, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move, 2 },
{ ai_move, 5 },
{ ai_move, 2, monster_footstep }
};
MMOVE_T(infantry_move_pain2) = { FRAME_pain201, FRAME_pain210, infantry_frames_pain2, infantry_run };
extern const mmove_t infantry_move_jump;
extern const mmove_t infantry_move_jump2;
PAIN(infantry_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
int n;
// allow turret to pain
if ((self->monsterinfo.active_move == &infantry_move_jump ||
self->monsterinfo.active_move == &infantry_move_jump2) && self->think == monster_think)
return;
monster_done_dodge(self);
if (level.time < self->pain_debounce_time)
{
if (self->think == monster_think && frandom() < 0.33f)
self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false);
return;
}
self->pain_debounce_time = level.time + 3_sec;
n = brandom();
if (n == 0)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
if (self->think != monster_think)
return;
if (!M_ShouldReactToPain(self, mod))
{
if (self->think == monster_think && frandom() < 0.33f)
self->monsterinfo.dodge(self, other, FRAME_TIME_S, nullptr, false);
return; // no pain anims in nightmare
}
if (n == 0)
M_SetAnimation(self, &infantry_move_pain1);
else
M_SetAnimation(self, &infantry_move_pain2);
// PMM - clear duck flag
if (self->monsterinfo.aiflags & AI_DUCKED)
monster_duck_up(self);
}
MONSTERINFO_SETSKIN(infantry_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
constexpr vec3_t aimangles[] = {
{ 0.0f, 5.0f, 0.0f },
{ 10.0f, 15.0f, 0.0f },
{ 20.0f, 25.0f, 0.0f },
{ 25.0f, 35.0f, 0.0f },
{ 30.0f, 40.0f, 0.0f },
{ 30.0f, 45.0f, 0.0f },
{ 25.0f, 50.0f, 0.0f },
{ 20.0f, 40.0f, 0.0f },
{ 15.0f, 35.0f, 0.0f },
{ 40.0f, 35.0f, 0.0f },
{ 70.0f, 35.0f, 0.0f },
{ 90.0f, 35.0f, 0.0f }
};
void InfantryMachineGun(edict_t *self)
{
vec3_t start;
vec3_t forward, right;
vec3_t vec;
monster_muzzleflash_id_t flash_number;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
bool is_run_attack = (self->s.frame >= FRAME_run201 && self->s.frame <= FRAME_run208);
if (self->s.frame == FRAME_attak103 || self->s.frame == FRAME_attak311 || is_run_attack || self->s.frame == FRAME_attak416)
{
if (is_run_attack)
flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_INFANTRY_MACHINEGUN_14 + (self->s.frame - MZ2_INFANTRY_MACHINEGUN_14));
else if (self->s.frame == FRAME_attak416)
flash_number = MZ2_INFANTRY_MACHINEGUN_22;
else
flash_number = MZ2_INFANTRY_MACHINEGUN_1;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
if (self->enemy)
PredictAim(self, self->enemy, start, 0, true, -0.2f, &forward, nullptr);
else
{
AngleVectors(self->s.angles, forward, right, nullptr);
}
}
else
{
flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_INFANTRY_MACHINEGUN_2 + (self->s.frame - FRAME_death211));
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
vec = self->s.angles - aimangles[flash_number - MZ2_INFANTRY_MACHINEGUN_2];
AngleVectors(vec, forward, nullptr, nullptr);
}
monster_fire_bullet(self, start, forward, 3, 4, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, flash_number);
}
MONSTERINFO_SIGHT(infantry_sight) (edict_t *self, edict_t *other) -> void
{
if (brandom())
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
void infantry_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
monster_dead(self);
}
static void infantry_shrink(edict_t *self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t infantry_frames_death1[] = {
{ ai_move, -4, nullptr, FRAME_death102 },
{ ai_move },
{ ai_move },
{ ai_move, -1 },
{ ai_move, -4, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, -1, monster_footstep },
{ ai_move, 3 },
{ ai_move, 1 },
{ ai_move, 1 },
{ ai_move, -2 },
{ ai_move, 2 },
{ ai_move, 2 },
{ ai_move, 9, [](edict_t *self) { infantry_shrink(self); monster_footstep(self); } },
{ ai_move, 9 },
{ ai_move, 5, monster_footstep },
{ ai_move, -3 },
{ ai_move, -3 }
};
MMOVE_T(infantry_move_death1) = { FRAME_death101, FRAME_death120, infantry_frames_death1, infantry_dead };
// Off with his head
mframe_t infantry_frames_death2[] = {
{ ai_move, 0, nullptr, FRAME_death202 },
{ ai_move, 1 },
{ ai_move, 5 },
{ ai_move, -1 },
{ ai_move },
{ ai_move, 1, monster_footstep },
{ ai_move, 1, monster_footstep },
{ ai_move, 4 },
{ ai_move, 3 },
{ ai_move },
{ ai_move, -2, InfantryMachineGun },
{ ai_move, -2, InfantryMachineGun },
{ ai_move, -3, InfantryMachineGun },
{ ai_move, -1, InfantryMachineGun },
{ ai_move, -2, InfantryMachineGun },
{ ai_move, 0, InfantryMachineGun },
{ ai_move, 2, InfantryMachineGun },
{ ai_move, 2, InfantryMachineGun },
{ ai_move, 3, InfantryMachineGun },
{ ai_move, -10, InfantryMachineGun },
{ ai_move, -7, InfantryMachineGun },
{ ai_move, -8, InfantryMachineGun },
{ ai_move, -6, [](edict_t *self) { infantry_shrink(self); monster_footstep(self); } },
{ ai_move, 4 },
{ ai_move }
};
MMOVE_T(infantry_move_death2) = { FRAME_death201, FRAME_death225, infantry_frames_death2, infantry_dead };
mframe_t infantry_frames_death3[] = {
{ ai_move, 0 },
{ ai_move },
{ ai_move, 0, [](edict_t *self) { infantry_shrink(self); monster_footstep(self); } },
{ ai_move, -6 },
{ ai_move, -11, [](edict_t *self) { self->monsterinfo.nextframe = FRAME_death307; } },
{ ai_move, -3 },
{ ai_move, -11 },
{ ai_move, 0, monster_footstep },
{ ai_move }
};
MMOVE_T(infantry_move_death3) = { FRAME_death301, FRAME_death309, infantry_frames_death3, infantry_dead };
DIE(infantry_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
int n;
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
const char *head_gib = (self->monsterinfo.active_move != &infantry_move_death3) ? "models/objects/gibs/sm_meat/tris.md2" : "models/monsters/infantry/gibs/head.md2";
self->s.skinnum /= 2;
ThrowGibs(self, damage, {
{ "models/objects/gibs/bone/tris.md2" },
{ 3, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/monsters/infantry/gibs/chest.md2", GIB_SKINNED },
{ "models/monsters/infantry/gibs/gun.md2", GIB_SKINNED | GIB_UPRIGHT },
{ 2, "models/monsters/infantry/gibs/foot.md2", GIB_SKINNED },
{ 2, "models/monsters/infantry/gibs/arm.md2", GIB_SKINNED },
{ head_gib, GIB_HEAD | GIB_SKINNED }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
self->deadflag = true;
self->takedamage = true;
n = irandom(3);
if (n == 0)
{
M_SetAnimation(self, &infantry_move_death1);
gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
}
else if (n == 1)
{
M_SetAnimation(self, &infantry_move_death2);
gi.sound(self, CHAN_VOICE, sound_die1, 1, ATTN_NORM, 0);
}
else
{
M_SetAnimation(self, &infantry_move_death3);
gi.sound(self, CHAN_VOICE, sound_die2, 1, ATTN_NORM, 0);
}
// don't always pop a head gib, it gets old
if (n != 2 && frandom() <= 0.25f)
{
edict_t *head = ThrowGib(self, "models/monsters/infantry/gibs/head.md2", damage, GIB_NONE, self->s.scale);
if (head)
{
head->s.angles = self->s.angles;
head->s.origin = self->s.origin + vec3_t{0, 0, 32.f};
vec3_t headDir = (self->s.origin - inflictor->s.origin);
head->velocity = headDir / headDir.length() * 100.0f;
head->velocity[2] = 200.0f;
head->avelocity *= 0.15f;
head->s.skinnum = 0;
gi.linkentity(head);
}
}
}
mframe_t infantry_frames_duck[] = {
{ ai_move, -2, monster_duck_down },
{ ai_move, -5, monster_duck_hold },
{ ai_move, 3 },
{ ai_move, 4, monster_duck_up },
{ ai_move }
};
MMOVE_T(infantry_move_duck) = { FRAME_duck01, FRAME_duck05, infantry_frames_duck, infantry_run };
// PMM - dodge code moved below so I can see the attack frames
extern const mmove_t infantry_move_attack4;
void infantry_set_firetime(edict_t *self)
{
self->monsterinfo.fire_wait = level.time + random_time(0.7_sec, 2_sec);
if (!(self->monsterinfo.aiflags & AI_STAND_GROUND) && self->enemy && range_to(self, self->enemy) >= RANGE_RUN_ATTACK && ai_check_move(self, 8.0f))
M_SetAnimation(self, &infantry_move_attack4, false);
}
void infantry_cock_gun(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_weapon_cock, 1, ATTN_NORM, 0);
// gun cocked
self->count = 1;
}
void infantry_fire(edict_t *self);
// cock-less attack, used if he has already cocked his gun
mframe_t infantry_frames_attack1[] = {
{ ai_charge },
{ ai_charge, 6, [](edict_t *self) { infantry_set_firetime(self); monster_footstep(self); } },
{ ai_charge, 0, infantry_fire },
{ ai_charge },
{ ai_charge, 1 },
{ ai_charge, -7 },
{ ai_charge, -6, [](edict_t *self) { self->monsterinfo.nextframe = FRAME_attak114; monster_footstep(self); } },
// dead frames start
{ ai_charge, -1 },
{ ai_charge, 0, infantry_cock_gun },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
// dead frames end
{ ai_charge, -1 },
{ ai_charge, -1 }
};
MMOVE_T(infantry_move_attack1) = { FRAME_attak101, FRAME_attak115, infantry_frames_attack1, infantry_run };
// old animation, full cock + shoot
mframe_t infantry_frames_attack3[] = {
{ ai_charge, 4, NULL },
{ ai_charge, -1, NULL },
{ ai_charge, -1, NULL },
{ ai_charge, 0, infantry_cock_gun },
{ ai_charge, -1, NULL },
{ ai_charge, 1, NULL },
{ ai_charge, 1, NULL },
{ ai_charge, 2, NULL },
{ ai_charge, -2, NULL },
{ ai_charge, -3, [](edict_t *self) { infantry_set_firetime(self); monster_footstep(self); } },
{ ai_charge, 1, infantry_fire },
{ ai_charge, 5, NULL },
{ ai_charge, -1, NULL },
{ ai_charge, -2, NULL },
{ ai_charge, -3, NULL },
};
MMOVE_T(infantry_move_attack3) = { FRAME_attak301, FRAME_attak315, infantry_frames_attack3, infantry_run };
// even older animation, full cock + shoot
mframe_t infantry_frames_attack5[] = {
// skipped frames
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, nullptr },
{ ai_charge, 0, monster_footstep },
{ ai_charge, 0, infantry_cock_gun },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, [](edict_t *self) { self->monsterinfo.nextframe = self->s.frame + 1; } },
{ ai_charge, 0, NULL }, // skipped frame
{ ai_charge, 0, NULL },
{ ai_charge, 0, nullptr },
{ ai_charge, 0, infantry_set_firetime },
{ ai_charge, 0, infantry_fire },
// skipped frames
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, NULL },
{ ai_charge, 0, monster_footstep }
};
MMOVE_T(infantry_move_attack5) = { FRAME_attak401, FRAME_attak423, infantry_frames_attack5, infantry_run };
extern const mmove_t infantry_move_attack4;
void infantry_fire(edict_t *self)
{
InfantryMachineGun(self);
// we fired, so we must cock again before firing
self->count = 0;
// check if we ran out of firing time
if (self->monsterinfo.active_move == &infantry_move_attack4)
{
if (level.time >= self->monsterinfo.fire_wait)
{
monster_done_dodge(self);
M_SetAnimation(self, &infantry_move_attack1, false);
self->monsterinfo.nextframe = FRAME_attak114;
}
// got close to an edge
else if (!ai_check_move(self, 8.0f))
{
M_SetAnimation(self, &infantry_move_attack1, false);
self->monsterinfo.nextframe = FRAME_attak103;
monster_done_dodge(self);
self->monsterinfo.attack_state = AS_STRAIGHT;
}
}
else if ((self->s.frame >= FRAME_attak101 && self->s.frame <= FRAME_attak115) ||
(self->s.frame >= FRAME_attak301 && self->s.frame <= FRAME_attak315) ||
(self->s.frame >= FRAME_attak401 && self->s.frame <= FRAME_attak424))
{
if (level.time >= self->monsterinfo.fire_wait)
{
self->monsterinfo.aiflags &= ~AI_HOLD_FRAME;
if (self->s.frame == FRAME_attak416)
self->monsterinfo.nextframe = FRAME_attak420;
}
else
self->monsterinfo.aiflags |= AI_HOLD_FRAME;
}
}
void infantry_swing(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_punch_swing, 1, ATTN_NORM, 0);
}
void infantry_smack(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, 0, 0 };
if (fire_hit(self, aim, irandom(5, 10), 50))
gi.sound(self, CHAN_WEAPON, sound_punch_hit, 1, ATTN_NORM, 0);
else
self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
}
mframe_t infantry_frames_attack2[] = {
{ ai_charge, 3 },
{ ai_charge, 6 },
{ ai_charge, 0, infantry_swing },
{ ai_charge, 8, monster_footstep },
{ ai_charge, 5 },
{ ai_charge, 8, infantry_smack },
{ ai_charge, 6 },
{ ai_charge, 3 }
};
MMOVE_T(infantry_move_attack2) = { FRAME_attak201, FRAME_attak208, infantry_frames_attack2, infantry_run };
// [Paril-KEX] run-attack, inspired by q2test
void infantry_attack4_refire(edict_t *self)
{
// ran out of firing time
if (level.time >= self->monsterinfo.fire_wait)
{
monster_done_dodge(self);
M_SetAnimation(self, &infantry_move_attack1, false);
self->monsterinfo.nextframe = FRAME_attak114;
}
// we got too close, or we can't move forward, switch us back to regular attack
else if ((self->monsterinfo.aiflags & AI_STAND_GROUND) || (self->enemy && (range_to(self, self->enemy) < RANGE_RUN_ATTACK || !ai_check_move(self, 8.0f))))
{
M_SetAnimation(self, &infantry_move_attack1, false);
self->monsterinfo.nextframe = FRAME_attak103;
monster_done_dodge(self);
self->monsterinfo.attack_state = AS_STRAIGHT;
}
else
self->monsterinfo.nextframe = FRAME_run201;
infantry_fire(self);
}
mframe_t infantry_frames_attack4[] = {
{ ai_charge, 16, infantry_fire },
{ ai_charge, 16, [](edict_t *self) { monster_footstep(self); infantry_fire(self); } },
{ ai_charge, 13, infantry_fire },
{ ai_charge, 10, infantry_fire },
{ ai_charge, 16, infantry_fire },
{ ai_charge, 16, [](edict_t *self) { monster_footstep(self); infantry_fire(self); } },
{ ai_charge, 16, infantry_fire },
{ ai_charge, 16, infantry_attack4_refire }
};
MMOVE_T(infantry_move_attack4) = { FRAME_run201, FRAME_run208, infantry_frames_attack4, infantry_run, 0.5f };
MONSTERINFO_ATTACK(infantry_attack) (edict_t *self) -> void
{
monster_done_dodge(self);
float r = range_to(self, self->enemy);
if (r <= RANGE_MELEE && self->monsterinfo.melee_debounce_time <= level.time)
M_SetAnimation(self, &infantry_move_attack2);
else if (M_CheckClearShot(self, monster_flash_offset[MZ2_INFANTRY_MACHINEGUN_1]))
{
if (self->count)
M_SetAnimation(self, &infantry_move_attack1);
else
{
M_SetAnimation(self, frandom() <= 0.1f ? &infantry_move_attack5 : &infantry_move_attack3);
self->monsterinfo.nextframe = FRAME_attak405;
}
}
}
//===========
// PGM
void infantry_jump_now(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 100);
self->velocity += (up * 300);
}
void infantry_jump2_now(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 150);
self->velocity += (up * 400);
}
void infantry_jump_wait_land(edict_t *self)
{
if (self->groundentity == nullptr)
{
self->monsterinfo.nextframe = self->s.frame;
if (monster_jump_finished(self))
self->monsterinfo.nextframe = self->s.frame + 1;
}
else
self->monsterinfo.nextframe = self->s.frame + 1;
}
mframe_t infantry_frames_jump[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, infantry_jump_now },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, infantry_jump_wait_land },
{ ai_move },
{ ai_move }
};
MMOVE_T(infantry_move_jump) = { FRAME_jump01, FRAME_jump10, infantry_frames_jump, infantry_run };
mframe_t infantry_frames_jump2[] = {
{ ai_move, -8 },
{ ai_move, -4 },
{ ai_move, -4 },
{ ai_move, 0, infantry_jump2_now },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, infantry_jump_wait_land },
{ ai_move },
{ ai_move }
};
MMOVE_T(infantry_move_jump2) = { FRAME_jump01, FRAME_jump10, infantry_frames_jump2, infantry_run };
void infantry_jump(edict_t *self, blocked_jump_result_t result)
{
if (!self->enemy)
return;
monster_done_dodge(self);
if (result == blocked_jump_result_t::JUMP_JUMP_UP)
M_SetAnimation(self, &infantry_move_jump2);
else
M_SetAnimation(self, &infantry_move_jump);
}
MONSTERINFO_BLOCKED(infantry_blocked) (edict_t *self, float dist) -> bool
{
if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
{
if (result != blocked_jump_result_t::JUMP_TURN)
infantry_jump(self, result);
return true;
}
if (blocked_checkplat(self, dist))
return true;
return false;
}
MONSTERINFO_DUCK(infantry_duck) (edict_t *self, gtime_t eta) -> bool
{
// if we're jumping, don't dodge
if ((self->monsterinfo.active_move == &infantry_move_jump) ||
(self->monsterinfo.active_move == &infantry_move_jump2))
{
return false;
}
// don't duck during our firing or melee frames
if (self->s.frame == FRAME_attak103 ||
self->s.frame == FRAME_attak315 ||
(self->monsterinfo.active_move == &infantry_move_attack2))
{
self->monsterinfo.unduck(self);
return false;
}
M_SetAnimation(self, &infantry_move_duck);
return true;
}
MONSTERINFO_SIDESTEP(infantry_sidestep) (edict_t *self) -> bool
{
// if we're jumping, don't dodge
if ((self->monsterinfo.active_move == &infantry_move_jump) ||
(self->monsterinfo.active_move == &infantry_move_jump2))
{
return false;
}
if (self->monsterinfo.active_move == &infantry_move_run)
return true;
// Don't sidestep if we're already sidestepping, and def not unless we're actually shooting
// or if we already cocked
if (self->monsterinfo.active_move != &infantry_move_attack4 &&
self->monsterinfo.next_move != &infantry_move_attack4 &&
((self->s.frame == FRAME_attak103 ||
self->s.frame == FRAME_attak311 ||
self->s.frame == FRAME_attak416) &&
!self->count))
{
// give us a fire time boost so we don't end up firing for 1 frame
self->monsterinfo.fire_wait += random_time(300_ms, 600_ms);
M_SetAnimation(self, &infantry_move_attack4, false);
}
return true;
}
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_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_sight = gi.soundindex("infantry/infsght1.wav");
sound_search = gi.soundindex("infantry/infsrch1.wav");
sound_idle = gi.soundindex("infantry/infidle1.wav");
}
constexpr spawnflags_t SPAWNFLAG_INFANTRY_NOJUMPING = 8_spawnflag;
/*QUAKED monster_infantry (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping
*/
void SP_monster_infantry(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
InfantryPrecache();
self->monsterinfo.aiflags |= AI_STINKY;
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/infantry/tris.md2");
gi.modelindex("models/monsters/infantry/gibs/head.md2");
gi.modelindex("models/monsters/infantry/gibs/chest.md2");
gi.modelindex("models/monsters/infantry/gibs/gun.md2");
gi.modelindex("models/monsters/infantry/gibs/arm.md2");
gi.modelindex("models/monsters/infantry/gibs/foot.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 32 };
self->health = 100 * st.health_multiplier;
self->gib_health = -65;
self->mass = 200;
self->pain = infantry_pain;
self->die = infantry_die;
self->monsterinfo.combat_style = COMBAT_MIXED;
self->monsterinfo.stand = infantry_stand;
self->monsterinfo.walk = infantry_walk;
self->monsterinfo.run = infantry_run;
// pmm
self->monsterinfo.dodge = M_MonsterDodge;
self->monsterinfo.duck = infantry_duck;
self->monsterinfo.unduck = monster_duck_up;
self->monsterinfo.sidestep = infantry_sidestep;
self->monsterinfo.blocked = infantry_blocked;
// pmm
self->monsterinfo.attack = infantry_attack;
self->monsterinfo.melee = nullptr;
self->monsterinfo.sight = infantry_sight;
self->monsterinfo.idle = infantry_fidget;
self->monsterinfo.setskin = infantry_setskin;
gi.linkentity(self);
M_SetAnimation(self, &infantry_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_INFANTRY_NOJUMPING);
self->monsterinfo.drop_height = 192;
self->monsterinfo.jump_height = 40;
walkmonster_start(self);
}

280
rerelease/m_infantry.h Normal file
View File

@@ -0,0 +1,280 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/infantry
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_gun02,
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_stand46,
FRAME_stand47,
FRAME_stand48,
FRAME_stand49,
FRAME_stand50,
FRAME_stand51,
FRAME_stand52,
FRAME_stand53,
FRAME_stand54,
FRAME_stand55,
FRAME_stand56,
FRAME_stand57,
FRAME_stand58,
FRAME_stand59,
FRAME_stand60,
FRAME_stand61,
FRAME_stand62,
FRAME_stand63,
FRAME_stand64,
FRAME_stand65,
FRAME_stand66,
FRAME_stand67,
FRAME_stand68,
FRAME_stand69,
FRAME_stand70,
FRAME_stand71,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_run07,
FRAME_run08,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain106,
FRAME_pain107,
FRAME_pain108,
FRAME_pain109,
FRAME_pain110,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain206,
FRAME_pain207,
FRAME_pain208,
FRAME_pain209,
FRAME_pain210,
FRAME_duck01,
FRAME_duck02,
FRAME_duck03,
FRAME_duck04,
FRAME_duck05,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death108,
FRAME_death109,
FRAME_death110,
FRAME_death111,
FRAME_death112,
FRAME_death113,
FRAME_death114,
FRAME_death115,
FRAME_death116,
FRAME_death117,
FRAME_death118,
FRAME_death119,
FRAME_death120,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_death206,
FRAME_death207,
FRAME_death208,
FRAME_death209,
FRAME_death210,
FRAME_death211,
FRAME_death212,
FRAME_death213,
FRAME_death214,
FRAME_death215,
FRAME_death216,
FRAME_death217,
FRAME_death218,
FRAME_death219,
FRAME_death220,
FRAME_death221,
FRAME_death222,
FRAME_death223,
FRAME_death224,
FRAME_death225,
FRAME_death301,
FRAME_death302,
FRAME_death303,
FRAME_death304,
FRAME_death305,
FRAME_death306,
FRAME_death307,
FRAME_death308,
FRAME_death309,
FRAME_block01,
FRAME_block02,
FRAME_block03,
FRAME_block04,
FRAME_block05,
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
// ROGUE
FRAME_jump01,
FRAME_jump02,
FRAME_jump03,
FRAME_jump04,
FRAME_jump05,
FRAME_jump06,
FRAME_jump07,
FRAME_jump08,
FRAME_jump09,
FRAME_jump10,
// ROGUE
// [Paril-KEX] old attack, for demos
FRAME_attak301,
FRAME_attak302,
FRAME_attak303,
FRAME_attak304,
FRAME_attak305,
FRAME_attak306,
FRAME_attak307,
FRAME_attak308,
FRAME_attak309,
FRAME_attak310,
FRAME_attak311,
FRAME_attak312,
FRAME_attak313,
FRAME_attak314,
FRAME_attak315,
// [Paril-KEX] run attack
FRAME_run201,
FRAME_run202,
FRAME_run203,
FRAME_run204,
FRAME_run205,
FRAME_run206,
FRAME_run207,
FRAME_run208,
// [Paril-KEX]
FRAME_attak401,
FRAME_attak402,
FRAME_attak403,
FRAME_attak404,
FRAME_attak405,
FRAME_attak406,
FRAME_attak407,
FRAME_attak408,
FRAME_attak409,
FRAME_attak410,
FRAME_attak411,
FRAME_attak412,
FRAME_attak413,
FRAME_attak414,
FRAME_attak415,
FRAME_attak416,
FRAME_attak417,
FRAME_attak418,
FRAME_attak419,
FRAME_attak420,
FRAME_attak421,
FRAME_attak422,
FRAME_attak423,
FRAME_attak424
};
constexpr float MODEL_SCALE = 1.000000f;

692
rerelease/m_insane.cpp Normal file
View File

@@ -0,0 +1,692 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
insane
==============================================================================
*/
#include "g_local.h"
#include "m_insane.h"
constexpr spawnflags_t SPAWNFLAG_INSANE_CRAWL = 4_spawnflag;
constexpr spawnflags_t SPAWNFLAG_INSANE_CRUCIFIED = 8_spawnflag;
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];
void insane_fist(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_fist, 1, ATTN_IDLE, 0);
}
void insane_shake(edict_t *self)
{
if (!self->spawnflags.has(SPAWNFLAG_INSANE_QUIET))
gi.sound(self, CHAN_VOICE, sound_shake, 1, ATTN_IDLE, 0);
}
extern const mmove_t insane_move_cross, insane_move_struggle_cross;
void insane_moan(edict_t *self)
{
if (self->spawnflags.has(SPAWNFLAG_INSANE_QUIET))
return;
// Paril: don't moan every second
if (self->monsterinfo.attack_finished < level.time)
{
gi.sound(self, CHAN_VOICE, sound_moan, 1, ATTN_IDLE, 0);
self->monsterinfo.attack_finished = level.time + random_time(1_sec, 3_sec);
}
}
void insane_scream(edict_t *self)
{
if (self->spawnflags.has(SPAWNFLAG_INSANE_QUIET))
return;
// Paril: don't moan every second
if (self->monsterinfo.attack_finished < level.time)
{
gi.sound(self, CHAN_VOICE, random_element(sound_scream), 1, ATTN_IDLE, 0);
self->monsterinfo.attack_finished = level.time + random_time(1_sec, 3_sec);
}
}
void insane_stand(edict_t *self);
void insane_dead(edict_t *self);
void insane_cross(edict_t *self);
void insane_walk(edict_t *self);
void insane_run(edict_t *self);
void insane_checkdown(edict_t *self);
void insane_checkup(edict_t *self);
void insane_onground(edict_t *self);
// Paril: unused atm because it breaks N64.
// may fix later
void insane_shrink(edict_t *self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t insane_frames_stand_normal[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, insane_checkdown }
};
MMOVE_T(insane_move_stand_normal) = { FRAME_stand60, FRAME_stand65, insane_frames_stand_normal, insane_stand };
mframe_t insane_frames_stand_insane[] = {
{ ai_stand, 0, insane_shake },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, insane_checkdown }
};
MMOVE_T(insane_move_stand_insane) = { FRAME_stand65, FRAME_stand94, insane_frames_stand_insane, insane_stand };
mframe_t insane_frames_uptodown[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, insane_moan },
{ ai_move },//, 0, monster_duck_down },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 2.7f },
{ ai_move, 4.1f },
{ ai_move, 6 },
{ ai_move, 7.6f },
{ ai_move, 3.6f },
{ ai_move },
{ ai_move },
{ ai_move, 0, insane_fist },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, insane_fist },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(insane_move_uptodown) = { FRAME_stand1, FRAME_stand40, insane_frames_uptodown, insane_onground };
mframe_t insane_frames_downtoup[] = {
{ ai_move, -0.7f }, // 41
{ ai_move, -1.2f }, // 42
{ ai_move, -1.5f }, // 43
{ ai_move, -4.5f }, // 44
{ ai_move, -3.5f }, // 45
{ ai_move, -0.2f }, // 46
{ ai_move }, // 47
{ ai_move, -1.3f }, // 48
{ ai_move, -3 }, // 49
{ ai_move, -2 }, // 50
{ ai_move },//, 0, monster_duck_up }, // 51
{ ai_move }, // 52
{ ai_move }, // 53
{ ai_move, -3.3f }, // 54
{ ai_move, -1.6f }, // 55
{ ai_move, -0.3f }, // 56
{ ai_move }, // 57
{ ai_move }, // 58
{ ai_move } // 59
};
MMOVE_T(insane_move_downtoup) = { FRAME_stand41, FRAME_stand59, insane_frames_downtoup, insane_stand };
mframe_t insane_frames_jumpdown[] = {
{ ai_move, 0.2f },
{ ai_move, 11.5f },
{ ai_move, 5.1f },
{ ai_move, 7.1f },
{ ai_move }
};
MMOVE_T(insane_move_jumpdown) = { FRAME_stand96, FRAME_stand100, insane_frames_jumpdown, insane_onground };
mframe_t insane_frames_down[] = {
{ ai_move }, // 100
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 110
{ ai_move, -1.7f },
{ ai_move, -1.6f },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, insane_fist },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 120
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 130
{ ai_move },
{ ai_move },
{ ai_move, 0, insane_moan },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 140
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }, // 150
{ ai_move, 0.5f },
{ ai_move },
{ ai_move, -0.2f, insane_scream },
{ ai_move },
{ ai_move, 0.2f },
{ ai_move, 0.4f },
{ ai_move, 0.6f },
{ ai_move, 0.8f },
{ ai_move, 0.7f },
{ ai_move, 0, insane_checkup } // 160
};
MMOVE_T(insane_move_down) = { FRAME_stand100, FRAME_stand160, insane_frames_down, insane_onground };
mframe_t insane_frames_walk_normal[] = {
{ ai_walk, 0, insane_scream },
{ ai_walk, 2.5f },
{ ai_walk, 3.5f },
{ ai_walk, 1.7f },
{ ai_walk, 2.3f },
{ ai_walk, 2.4f },
{ ai_walk, 2.2f, monster_footstep },
{ ai_walk, 4.2f },
{ ai_walk, 5.6f },
{ ai_walk, 3.3f },
{ ai_walk, 2.4f },
{ ai_walk, 0.9f },
{ ai_walk, 0, monster_footstep }
};
MMOVE_T(insane_move_walk_normal) = { FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_walk };
MMOVE_T(insane_move_run_normal) = { FRAME_walk27, FRAME_walk39, insane_frames_walk_normal, insane_run };
mframe_t insane_frames_walk_insane[] = {
{ ai_walk, 0, insane_scream }, // walk 1
{ ai_walk, 3.4f }, // walk 2
{ ai_walk, 3.6f }, // 3
{ ai_walk, 2.9f }, // 4
{ ai_walk, 2.2f }, // 5
{ ai_walk, 2.6f, monster_footstep }, // 6
{ ai_walk }, // 7
{ ai_walk, 0.7f }, // 8
{ ai_walk, 4.8f }, // 9
{ ai_walk, 5.3f }, // 10
{ ai_walk, 1.1f }, // 11
{ ai_walk, 2, monster_footstep }, // 12
{ ai_walk, 0.5f }, // 13
{ ai_walk }, // 14
{ ai_walk }, // 15
{ ai_walk, 4.9f }, // 16
{ ai_walk, 6.7f }, // 17
{ ai_walk, 3.8f }, // 18
{ ai_walk, 2, monster_footstep }, // 19
{ ai_walk, 0.2f }, // 20
{ ai_walk }, // 21
{ ai_walk, 3.4f }, // 22
{ ai_walk, 6.4f }, // 23
{ ai_walk, 5 }, // 24
{ ai_walk, 1.8f, monster_footstep }, // 25
{ ai_walk } // 26
};
MMOVE_T(insane_move_walk_insane) = { FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_walk };
MMOVE_T(insane_move_run_insane) = { FRAME_walk1, FRAME_walk26, insane_frames_walk_insane, insane_run };
mframe_t insane_frames_stand_pain[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep }
};
MMOVE_T(insane_move_stand_pain) = { FRAME_st_pain2, FRAME_st_pain12, insane_frames_stand_pain, insane_run };
mframe_t insane_frames_stand_death[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(insane_move_stand_death) = { FRAME_st_death2, FRAME_st_death18, insane_frames_stand_death, insane_dead };
mframe_t insane_frames_crawl[] = {
{ ai_walk, 0, insane_scream },
{ ai_walk, 1.5f },
{ ai_walk, 2.1f },
{ ai_walk, 3.6f },
{ ai_walk, 2, monster_footstep },
{ ai_walk, 0.9f },
{ ai_walk, 3 },
{ ai_walk, 3.4f },
{ ai_walk, 2.4f, monster_footstep }
};
MMOVE_T(insane_move_crawl) = { FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, nullptr };
MMOVE_T(insane_move_runcrawl) = { FRAME_crawl1, FRAME_crawl9, insane_frames_crawl, nullptr };
mframe_t insane_frames_crawl_pain[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(insane_move_crawl_pain) = { FRAME_cr_pain2, FRAME_cr_pain10, insane_frames_crawl_pain, insane_run };
mframe_t insane_frames_crawl_death[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(insane_move_crawl_death) = { FRAME_cr_death10, FRAME_cr_death16, insane_frames_crawl_death, insane_dead };
mframe_t insane_frames_cross[] = {
{ ai_move, 0, insane_moan },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(insane_move_cross) = { FRAME_cross1, FRAME_cross15, insane_frames_cross, insane_cross };
mframe_t insane_frames_struggle_cross[] = {
{ ai_move, 0, insane_scream },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(insane_move_struggle_cross) = { FRAME_cross16, FRAME_cross30, insane_frames_struggle_cross, insane_cross };
void insane_cross(edict_t *self)
{
if (frandom() < 0.8f)
M_SetAnimation(self, &insane_move_cross);
else
M_SetAnimation(self, &insane_move_struggle_cross);
}
MONSTERINFO_WALK(insane_walk) (edict_t *self) -> void
{
if (self->spawnflags.has(SPAWNFLAG_INSANE_STAND_GROUND)) // Hold Ground?
if (self->s.frame == FRAME_cr_pain10)
{
M_SetAnimation(self, &insane_move_down);
//monster_duck_down(self);
return;
}
if (self->spawnflags.has(SPAWNFLAG_INSANE_CRAWL))
M_SetAnimation(self, &insane_move_crawl);
else if (frandom() <= 0.5f)
M_SetAnimation(self, &insane_move_walk_normal);
else
M_SetAnimation(self, &insane_move_walk_insane);
}
MONSTERINFO_RUN(insane_run) (edict_t *self) -> void
{
if (self->spawnflags.has(SPAWNFLAG_INSANE_STAND_GROUND)) // Hold Ground?
if (self->s.frame == FRAME_cr_pain10)
{
M_SetAnimation(self, &insane_move_down);
//monster_duck_down(self);
return;
}
if (self->spawnflags.has(SPAWNFLAG_INSANE_CRAWL) || (self->s.frame >= FRAME_cr_pain2 && self->s.frame <= FRAME_cr_pain10) || (self->s.frame >= FRAME_crawl1 && self->s.frame <= FRAME_crawl9) ||
(self->s.frame >= FRAME_stand99 && self->s.frame <= FRAME_stand160)) // Crawling?
M_SetAnimation(self, &insane_move_runcrawl);
else if (frandom() <= 0.5f) // Else, mix it up
{
M_SetAnimation(self, &insane_move_run_normal);
//monster_duck_up(self);
}
else
{
M_SetAnimation(self, &insane_move_run_insane);
//monster_duck_up(self);
}
}
PAIN(insane_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
int l, r;
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
r = 1 + brandom();
if (self->health < 25)
l = 25;
else if (self->health < 50)
l = 50;
else if (self->health < 75)
l = 75;
else
l = 100;
gi.sound(self, CHAN_VOICE, gi.soundindex(G_Fmt("player/male/pain{}_{}.wav", l, r).data()), 1, ATTN_IDLE, 0);
// Don't go into pain frames if crucified.
if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED))
{
M_SetAnimation(self, &insane_move_struggle_cross);
return;
}
if (((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)) || ((self->s.frame >= FRAME_stand1 && self->s.frame <= FRAME_stand40)))
{
M_SetAnimation(self, &insane_move_crawl_pain);
}
else
{
M_SetAnimation(self, &insane_move_stand_pain);
//monster_duck_up(self);
}
}
void insane_onground(edict_t *self)
{
M_SetAnimation(self, &insane_move_down);
//monster_duck_down(self);
}
void insane_checkdown(edict_t *self)
{
// if ( (self->s.frame == FRAME_stand94) || (self->s.frame == FRAME_stand65) )
if (self->spawnflags.has(SPAWNFLAG_INSANE_ALWAYS_STAND)) // Always stand
return;
if (frandom() < 0.3f)
{
if (frandom() < 0.5f)
M_SetAnimation(self, &insane_move_uptodown);
else
M_SetAnimation(self, &insane_move_jumpdown);
}
}
void insane_checkup(edict_t *self)
{
// If Hold_Ground and Crawl are set
if (self->spawnflags.has_all(SPAWNFLAG_INSANE_CRAWL | SPAWNFLAG_INSANE_STAND_GROUND))
return;
if (frandom() < 0.5f)
{
M_SetAnimation(self, &insane_move_downtoup);
//monster_duck_up(self);
}
}
MONSTERINFO_STAND(insane_stand) (edict_t *self) -> void
{
if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED)) // If crucified
{
M_SetAnimation(self, &insane_move_cross);
self->monsterinfo.aiflags |= AI_STAND_GROUND;
}
// If Hold_Ground and Crawl are set
else if (self->spawnflags.has_all(SPAWNFLAG_INSANE_CRAWL | SPAWNFLAG_INSANE_STAND_GROUND))
{
M_SetAnimation(self, &insane_move_down);
//monster_duck_down(self);
}
else if (frandom() < 0.5f)
M_SetAnimation(self, &insane_move_stand_normal);
else
M_SetAnimation(self, &insane_move_stand_insane);
}
void insane_dead(edict_t *self)
{
if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED))
{
self->flags |= FL_FLY;
}
else
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
self->movetype = MOVETYPE_TOSS;
}
monster_dead(self);
}
DIE(insane_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_IDLE, 0);
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 4, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/objects/gibs/head2/tris.md2", GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
gi.sound(self, CHAN_VOICE, gi.soundindex(G_Fmt("player/male/death{}.wav", irandom(1, 5)).data()), 1, ATTN_IDLE, 0);
self->deadflag = true;
self->takedamage = true;
if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED))
{
insane_dead(self);
}
else
{
if (((self->s.frame >= FRAME_crawl1) && (self->s.frame <= FRAME_crawl9)) || ((self->s.frame >= FRAME_stand99) && (self->s.frame <= FRAME_stand160)))
M_SetAnimation(self, &insane_move_crawl_death);
else
M_SetAnimation(self, &insane_move_stand_death);
}
}
/*QUAKED misc_insane (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn CRAWL CRUCIFIED STAND_GROUND ALWAYS_STAND QUIET
*/
void SP_misc_insane(edict_t *self)
{
// static int skin = 0; //@@
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
sound_fist = gi.soundindex("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");
}
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/insane/tris.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 32 };
self->health = 100 * st.health_multiplier;
self->gib_health = -50;
self->mass = 300;
self->pain = insane_pain;
self->die = insane_die;
self->monsterinfo.stand = insane_stand;
self->monsterinfo.walk = insane_walk;
self->monsterinfo.run = insane_run;
self->monsterinfo.dodge = nullptr;
self->monsterinfo.attack = nullptr;
self->monsterinfo.melee = nullptr;
self->monsterinfo.sight = nullptr;
self->monsterinfo.aiflags |= AI_GOOD_GUY;
//@@
// self->s.skinnum = skin;
// skin++;
// if (skin > 12)
// skin = 0;
gi.linkentity(self);
if (self->spawnflags.has(SPAWNFLAG_INSANE_STAND_GROUND)) // Stand Ground
self->monsterinfo.aiflags |= AI_STAND_GROUND;
M_SetAnimation(self, &insane_move_stand_normal);
self->monsterinfo.scale = MODEL_SCALE;
if (self->spawnflags.has(SPAWNFLAG_INSANE_CRUCIFIED)) // Crucified ?
{
self->flags |= FL_NO_KNOCKBACK | FL_STATIONARY;
stationarymonster_start(self);
}
else
walkmonster_start(self);
self->s.skinnum = irandom(3);
}

293
rerelease/m_insane.h Normal file
View File

@@ -0,0 +1,293 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/insane
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_stand1,
FRAME_stand2,
FRAME_stand3,
FRAME_stand4,
FRAME_stand5,
FRAME_stand6,
FRAME_stand7,
FRAME_stand8,
FRAME_stand9,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_stand41,
FRAME_stand42,
FRAME_stand43,
FRAME_stand44,
FRAME_stand45,
FRAME_stand46,
FRAME_stand47,
FRAME_stand48,
FRAME_stand49,
FRAME_stand50,
FRAME_stand51,
FRAME_stand52,
FRAME_stand53,
FRAME_stand54,
FRAME_stand55,
FRAME_stand56,
FRAME_stand57,
FRAME_stand58,
FRAME_stand59,
FRAME_stand60,
FRAME_stand61,
FRAME_stand62,
FRAME_stand63,
FRAME_stand64,
FRAME_stand65,
FRAME_stand66,
FRAME_stand67,
FRAME_stand68,
FRAME_stand69,
FRAME_stand70,
FRAME_stand71,
FRAME_stand72,
FRAME_stand73,
FRAME_stand74,
FRAME_stand75,
FRAME_stand76,
FRAME_stand77,
FRAME_stand78,
FRAME_stand79,
FRAME_stand80,
FRAME_stand81,
FRAME_stand82,
FRAME_stand83,
FRAME_stand84,
FRAME_stand85,
FRAME_stand86,
FRAME_stand87,
FRAME_stand88,
FRAME_stand89,
FRAME_stand90,
FRAME_stand91,
FRAME_stand92,
FRAME_stand93,
FRAME_stand94,
FRAME_stand95,
FRAME_stand96,
FRAME_stand97,
FRAME_stand98,
FRAME_stand99,
FRAME_stand100,
FRAME_stand101,
FRAME_stand102,
FRAME_stand103,
FRAME_stand104,
FRAME_stand105,
FRAME_stand106,
FRAME_stand107,
FRAME_stand108,
FRAME_stand109,
FRAME_stand110,
FRAME_stand111,
FRAME_stand112,
FRAME_stand113,
FRAME_stand114,
FRAME_stand115,
FRAME_stand116,
FRAME_stand117,
FRAME_stand118,
FRAME_stand119,
FRAME_stand120,
FRAME_stand121,
FRAME_stand122,
FRAME_stand123,
FRAME_stand124,
FRAME_stand125,
FRAME_stand126,
FRAME_stand127,
FRAME_stand128,
FRAME_stand129,
FRAME_stand130,
FRAME_stand131,
FRAME_stand132,
FRAME_stand133,
FRAME_stand134,
FRAME_stand135,
FRAME_stand136,
FRAME_stand137,
FRAME_stand138,
FRAME_stand139,
FRAME_stand140,
FRAME_stand141,
FRAME_stand142,
FRAME_stand143,
FRAME_stand144,
FRAME_stand145,
FRAME_stand146,
FRAME_stand147,
FRAME_stand148,
FRAME_stand149,
FRAME_stand150,
FRAME_stand151,
FRAME_stand152,
FRAME_stand153,
FRAME_stand154,
FRAME_stand155,
FRAME_stand156,
FRAME_stand157,
FRAME_stand158,
FRAME_stand159,
FRAME_stand160,
FRAME_walk27,
FRAME_walk28,
FRAME_walk29,
FRAME_walk30,
FRAME_walk31,
FRAME_walk32,
FRAME_walk33,
FRAME_walk34,
FRAME_walk35,
FRAME_walk36,
FRAME_walk37,
FRAME_walk38,
FRAME_walk39,
FRAME_walk1,
FRAME_walk2,
FRAME_walk3,
FRAME_walk4,
FRAME_walk5,
FRAME_walk6,
FRAME_walk7,
FRAME_walk8,
FRAME_walk9,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_walk21,
FRAME_walk22,
FRAME_walk23,
FRAME_walk24,
FRAME_walk25,
FRAME_walk26,
FRAME_st_pain2,
FRAME_st_pain3,
FRAME_st_pain4,
FRAME_st_pain5,
FRAME_st_pain6,
FRAME_st_pain7,
FRAME_st_pain8,
FRAME_st_pain9,
FRAME_st_pain10,
FRAME_st_pain11,
FRAME_st_pain12,
FRAME_st_death2,
FRAME_st_death3,
FRAME_st_death4,
FRAME_st_death5,
FRAME_st_death6,
FRAME_st_death7,
FRAME_st_death8,
FRAME_st_death9,
FRAME_st_death10,
FRAME_st_death11,
FRAME_st_death12,
FRAME_st_death13,
FRAME_st_death14,
FRAME_st_death15,
FRAME_st_death16,
FRAME_st_death17,
FRAME_st_death18,
FRAME_crawl1,
FRAME_crawl2,
FRAME_crawl3,
FRAME_crawl4,
FRAME_crawl5,
FRAME_crawl6,
FRAME_crawl7,
FRAME_crawl8,
FRAME_crawl9,
FRAME_cr_pain2,
FRAME_cr_pain3,
FRAME_cr_pain4,
FRAME_cr_pain5,
FRAME_cr_pain6,
FRAME_cr_pain7,
FRAME_cr_pain8,
FRAME_cr_pain9,
FRAME_cr_pain10,
FRAME_cr_death10,
FRAME_cr_death11,
FRAME_cr_death12,
FRAME_cr_death13,
FRAME_cr_death14,
FRAME_cr_death15,
FRAME_cr_death16,
FRAME_cross1,
FRAME_cross2,
FRAME_cross3,
FRAME_cross4,
FRAME_cross5,
FRAME_cross6,
FRAME_cross7,
FRAME_cross8,
FRAME_cross9,
FRAME_cross10,
FRAME_cross11,
FRAME_cross12,
FRAME_cross13,
FRAME_cross14,
FRAME_cross15,
FRAME_cross16,
FRAME_cross17,
FRAME_cross18,
FRAME_cross19,
FRAME_cross20,
FRAME_cross21,
FRAME_cross22,
FRAME_cross23,
FRAME_cross24,
FRAME_cross25,
FRAME_cross26,
FRAME_cross27,
FRAME_cross28,
FRAME_cross29,
FRAME_cross30
};
constexpr float MODEL_SCALE = 1.000000f;

1643
rerelease/m_medic.cpp Normal file

File diff suppressed because it is too large Load Diff

248
rerelease/m_medic.h Normal file
View File

@@ -0,0 +1,248 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/medic
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_walk1,
FRAME_walk2,
FRAME_walk3,
FRAME_walk4,
FRAME_walk5,
FRAME_walk6,
FRAME_walk7,
FRAME_walk8,
FRAME_walk9,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_wait1,
FRAME_wait2,
FRAME_wait3,
FRAME_wait4,
FRAME_wait5,
FRAME_wait6,
FRAME_wait7,
FRAME_wait8,
FRAME_wait9,
FRAME_wait10,
FRAME_wait11,
FRAME_wait12,
FRAME_wait13,
FRAME_wait14,
FRAME_wait15,
FRAME_wait16,
FRAME_wait17,
FRAME_wait18,
FRAME_wait19,
FRAME_wait20,
FRAME_wait21,
FRAME_wait22,
FRAME_wait23,
FRAME_wait24,
FRAME_wait25,
FRAME_wait26,
FRAME_wait27,
FRAME_wait28,
FRAME_wait29,
FRAME_wait30,
FRAME_wait31,
FRAME_wait32,
FRAME_wait33,
FRAME_wait34,
FRAME_wait35,
FRAME_wait36,
FRAME_wait37,
FRAME_wait38,
FRAME_wait39,
FRAME_wait40,
FRAME_wait41,
FRAME_wait42,
FRAME_wait43,
FRAME_wait44,
FRAME_wait45,
FRAME_wait46,
FRAME_wait47,
FRAME_wait48,
FRAME_wait49,
FRAME_wait50,
FRAME_wait51,
FRAME_wait52,
FRAME_wait53,
FRAME_wait54,
FRAME_wait55,
FRAME_wait56,
FRAME_wait57,
FRAME_wait58,
FRAME_wait59,
FRAME_wait60,
FRAME_wait61,
FRAME_wait62,
FRAME_wait63,
FRAME_wait64,
FRAME_wait65,
FRAME_wait66,
FRAME_wait67,
FRAME_wait68,
FRAME_wait69,
FRAME_wait70,
FRAME_wait71,
FRAME_wait72,
FRAME_wait73,
FRAME_wait74,
FRAME_wait75,
FRAME_wait76,
FRAME_wait77,
FRAME_wait78,
FRAME_wait79,
FRAME_wait80,
FRAME_wait81,
FRAME_wait82,
FRAME_wait83,
FRAME_wait84,
FRAME_wait85,
FRAME_wait86,
FRAME_wait87,
FRAME_wait88,
FRAME_wait89,
FRAME_wait90,
FRAME_run1,
FRAME_run2,
FRAME_run3,
FRAME_run4,
FRAME_run5,
FRAME_run6,
FRAME_paina1,
FRAME_paina2,
FRAME_paina3,
FRAME_paina4,
FRAME_paina5,
FRAME_paina6,
FRAME_paina7,
FRAME_paina8,
FRAME_painb1,
FRAME_painb2,
FRAME_painb3,
FRAME_painb4,
FRAME_painb5,
FRAME_painb6,
FRAME_painb7,
FRAME_painb8,
FRAME_painb9,
FRAME_painb10,
FRAME_painb11,
FRAME_painb12,
FRAME_painb13,
FRAME_painb14,
FRAME_painb15,
FRAME_duck1,
FRAME_duck2,
FRAME_duck3,
FRAME_duck4,
FRAME_duck5,
FRAME_duck6,
FRAME_duck7,
FRAME_duck8,
FRAME_duck9,
FRAME_duck10,
FRAME_duck11,
FRAME_duck12,
FRAME_duck13,
FRAME_duck14,
FRAME_duck15,
FRAME_duck16,
FRAME_death1,
FRAME_death2,
FRAME_death3,
FRAME_death4,
FRAME_death5,
FRAME_death6,
FRAME_death7,
FRAME_death8,
FRAME_death9,
FRAME_death10,
FRAME_death11,
FRAME_death12,
FRAME_death13,
FRAME_death14,
FRAME_death15,
FRAME_death16,
FRAME_death17,
FRAME_death18,
FRAME_death19,
FRAME_death20,
FRAME_death21,
FRAME_death22,
FRAME_death23,
FRAME_death24,
FRAME_death25,
FRAME_death26,
FRAME_death27,
FRAME_death28,
FRAME_death29,
FRAME_death30,
FRAME_attack1,
FRAME_attack2,
FRAME_attack3,
FRAME_attack4,
FRAME_attack5,
FRAME_attack6,
FRAME_attack7,
FRAME_attack8,
FRAME_attack9,
FRAME_attack10,
FRAME_attack11,
FRAME_attack12,
FRAME_attack13,
FRAME_attack14,
FRAME_attack15,
FRAME_attack16,
FRAME_attack17,
FRAME_attack18,
FRAME_attack19,
FRAME_attack20,
FRAME_attack21,
FRAME_attack22,
FRAME_attack23,
FRAME_attack24,
FRAME_attack25,
FRAME_attack26,
FRAME_attack27,
FRAME_attack28,
FRAME_attack29,
FRAME_attack30,
FRAME_attack31,
FRAME_attack32,
FRAME_attack33,
FRAME_attack34,
FRAME_attack35,
FRAME_attack36,
FRAME_attack37,
FRAME_attack38,
FRAME_attack39,
FRAME_attack40,
FRAME_attack41,
FRAME_attack42,
FRAME_attack43,
FRAME_attack44,
FRAME_attack45,
FRAME_attack46,
FRAME_attack47,
FRAME_attack48,
FRAME_attack49,
FRAME_attack50,
FRAME_attack51,
FRAME_attack52,
FRAME_attack53,
FRAME_attack54,
FRAME_attack55,
FRAME_attack56,
FRAME_attack57,
FRAME_attack58,
FRAME_attack59,
FRAME_attack60
};
constexpr float MODEL_SCALE = 1.000000f;

1497
rerelease/m_move.cpp Normal file

File diff suppressed because it is too large Load Diff

739
rerelease/m_mutant.cpp Normal file
View File

@@ -0,0 +1,739 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
mutant
==============================================================================
*/
#include "g_local.h"
#include "m_mutant.h"
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;
//
// SOUNDS
//
void mutant_step(edict_t *self)
{
int n = irandom(3);
if (n == 0)
gi.sound(self, CHAN_BODY, sound_step1, 1, ATTN_NORM, 0);
else if (n == 1)
gi.sound(self, CHAN_BODY, sound_step2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_BODY, sound_step3, 1, ATTN_NORM, 0);
}
MONSTERINFO_SIGHT(mutant_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
MONSTERINFO_SEARCH(mutant_search) (edict_t *self) -> void
{
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
void mutant_swing(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_swing, 1, ATTN_NORM, 0);
}
//
// STAND
//
mframe_t mutant_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 10
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 20
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 30
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 40
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // 50
{ ai_stand }
};
MMOVE_T(mutant_move_stand) = { FRAME_stand101, FRAME_stand151, mutant_frames_stand, nullptr };
MONSTERINFO_STAND(mutant_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &mutant_move_stand);
}
//
// IDLE
//
void mutant_idle_loop(edict_t *self)
{
if (frandom() < 0.75f)
self->monsterinfo.nextframe = FRAME_stand155;
}
mframe_t mutant_frames_idle[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }, // scratch loop start
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, mutant_idle_loop }, // scratch loop end
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(mutant_move_idle) = { FRAME_stand152, FRAME_stand164, mutant_frames_idle, mutant_stand };
MONSTERINFO_IDLE(mutant_idle) (edict_t *self) -> void
{
M_SetAnimation(self, &mutant_move_idle);
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
//
// WALK
//
mframe_t mutant_frames_walk[] = {
{ ai_walk, 3 },
{ ai_walk, 1 },
{ ai_walk, 5 },
{ ai_walk, 10 },
{ ai_walk, 13 },
{ ai_walk, 10 },
{ ai_walk },
{ ai_walk, 5 },
{ ai_walk, 6 },
{ ai_walk, 16 },
{ ai_walk, 15 },
{ ai_walk, 6 }
};
MMOVE_T(mutant_move_walk) = { FRAME_walk05, FRAME_walk16, mutant_frames_walk, nullptr };
void mutant_walk_loop(edict_t *self)
{
M_SetAnimation(self, &mutant_move_walk);
}
mframe_t mutant_frames_start_walk[] = {
{ ai_walk, 5 },
{ ai_walk, 5 },
{ ai_walk, -2 },
{ ai_walk, 1 }
};
MMOVE_T(mutant_move_start_walk) = { FRAME_walk01, FRAME_walk04, mutant_frames_start_walk, mutant_walk_loop };
MONSTERINFO_WALK(mutant_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &mutant_move_start_walk);
}
//
// RUN
//
mframe_t mutant_frames_run[] = {
{ ai_run, 40 },
{ ai_run, 40, mutant_step },
{ ai_run, 24 },
{ ai_run, 5, mutant_step },
{ ai_run, 17 },
{ ai_run, 10 }
};
MMOVE_T(mutant_move_run) = { FRAME_run03, FRAME_run08, mutant_frames_run, nullptr };
MONSTERINFO_RUN(mutant_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &mutant_move_stand);
else
M_SetAnimation(self, &mutant_move_run);
}
//
// MELEE
//
void mutant_hit_left(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->mins[0], 8 };
if (fire_hit(self, aim, irandom(5, 15), 100))
gi.sound(self, CHAN_WEAPON, sound_hit, 1, ATTN_NORM, 0);
else
{
gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
}
}
void mutant_hit_right(edict_t *self)
{
vec3_t aim = { MELEE_DISTANCE, self->maxs[0], 8 };
if (fire_hit(self, aim, irandom(5, 15), 100))
gi.sound(self, CHAN_WEAPON, sound_hit2, 1, ATTN_NORM, 0);
else
{
gi.sound(self, CHAN_WEAPON, sound_swing, 1, ATTN_NORM, 0);
self->monsterinfo.melee_debounce_time = level.time + 1.5_sec;
}
}
void mutant_check_refire(edict_t *self)
{
if (!self->enemy || !self->enemy->inuse || self->enemy->health <= 0)
return;
if ((self->monsterinfo.melee_debounce_time <= level.time) && ((frandom() < 0.5f) || (range_to(self, self->enemy) <= RANGE_MELEE)))
self->monsterinfo.nextframe = FRAME_attack09;
}
mframe_t mutant_frames_attack[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, mutant_hit_left },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, mutant_hit_right },
{ ai_charge, 0, mutant_check_refire }
};
MMOVE_T(mutant_move_attack) = { FRAME_attack09, FRAME_attack15, mutant_frames_attack, mutant_run };
MONSTERINFO_MELEE(mutant_melee) (edict_t *self) -> void
{
M_SetAnimation(self, &mutant_move_attack);
}
//
// ATTACK
//
TOUCH(mutant_jump_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
if (self->health <= 0)
{
self->touch = nullptr;
return;
}
if (self->style == 1 && other->takedamage)
{
if (self->velocity.length() > 400)
{
vec3_t point;
vec3_t normal;
int damage;
normal = self->velocity;
normal.normalize();
point = self->s.origin + (normal * self->maxs[0]);
damage = (int) frandom(40, 50);
T_Damage(other, self, self, self->velocity, point, normal, damage, damage, DAMAGE_NONE, MOD_UNKNOWN);
self->style = 0;
}
}
if (!M_CheckBottom(self))
{
if (self->groundentity)
{
self->monsterinfo.nextframe = FRAME_attack02;
self->touch = nullptr;
}
return;
}
self->touch = nullptr;
}
void mutant_jump_takeoff(edict_t *self)
{
vec3_t forward;
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->groundentity = nullptr;
self->monsterinfo.aiflags |= AI_DUCKED;
self->monsterinfo.attack_finished = level.time + 3_sec;
self->style = 1;
self->touch = mutant_jump_touch;
}
void mutant_check_landing(edict_t *self)
{
monster_jump_finished(self);
if (self->groundentity)
{
gi.sound(self, CHAN_WEAPON, sound_thud, 1, ATTN_NORM, 0);
self->monsterinfo.attack_finished = level.time + random_time(500_ms, 1.5_sec);
if (self->monsterinfo.unduck)
self->monsterinfo.unduck(self);
if (range_to(self, self->enemy) <= RANGE_MELEE * 2.f)
self->monsterinfo.melee(self);
return;
}
if (level.time > self->monsterinfo.attack_finished)
self->monsterinfo.nextframe = FRAME_attack02;
else
self->monsterinfo.nextframe = FRAME_attack05;
}
mframe_t mutant_frames_jump[] = {
{ ai_charge },
{ ai_charge, 17 },
{ ai_charge, 15, mutant_jump_takeoff },
{ ai_charge, 15 },
{ ai_charge, 15, mutant_check_landing },
{ ai_charge },
{ ai_charge, 3 },
{ ai_charge }
};
MMOVE_T(mutant_move_jump) = { FRAME_attack01, FRAME_attack08, mutant_frames_jump, mutant_run };
MONSTERINFO_ATTACK(mutant_jump) (edict_t *self) -> void
{
M_SetAnimation(self, &mutant_move_jump);
}
//
// CHECKATTACK
//
bool mutant_check_melee(edict_t *self)
{
return range_to(self, self->enemy) <= RANGE_MELEE && self->monsterinfo.melee_debounce_time <= level.time;
}
bool mutant_check_jump(edict_t *self)
{
vec3_t v;
float distance;
// Paril: no harm in letting them jump down if you're below them
// if (self->absmin[2] > (self->enemy->absmin[2] + 0.75 * self->enemy->size[2]))
// return false;
// don't jump if there's no way we can reach standing height
if (self->absmin[2] + 125 < self->enemy->absmin[2])
return false;
v[0] = self->s.origin[0] - self->enemy->s.origin[0];
v[1] = self->s.origin[1] - self->enemy->s.origin[1];
v[2] = 0;
distance = v.length();
// if we're not trying to avoid a melee, then don't jump
if (distance < 100 && self->monsterinfo.melee_debounce_time <= level.time)
return false;
// only use it to close distance gaps
if (distance > 265)
return false;
return self->monsterinfo.attack_finished < level.time && brandom();
}
MONSTERINFO_CHECKATTACK(mutant_checkattack) (edict_t *self) -> bool
{
if (!self->enemy || self->enemy->health <= 0)
return false;
if (mutant_check_melee(self))
{
self->monsterinfo.attack_state = AS_MELEE;
return true;
}
if (!self->spawnflags.has(SPAWNFLAG_MUTANT_NOJUMPING) && mutant_check_jump(self))
{
self->monsterinfo.attack_state = AS_MISSILE;
return true;
}
return false;
}
//
// PAIN
//
mframe_t mutant_frames_pain1[] = {
{ ai_move, 4 },
{ ai_move, -3 },
{ ai_move, -8 },
{ ai_move, 2 },
{ ai_move, 5 }
};
MMOVE_T(mutant_move_pain1) = { FRAME_pain101, FRAME_pain105, mutant_frames_pain1, mutant_run };
mframe_t mutant_frames_pain2[] = {
{ ai_move, -24 },
{ ai_move, 11 },
{ ai_move, 5 },
{ ai_move, -2 },
{ ai_move, 6 },
{ ai_move, 4 }
};
MMOVE_T(mutant_move_pain2) = { FRAME_pain201, FRAME_pain206, mutant_frames_pain2, mutant_run };
mframe_t mutant_frames_pain3[] = {
{ ai_move, -22 },
{ ai_move, 3 },
{ ai_move, 3 },
{ ai_move, 2 },
{ ai_move, 1 },
{ ai_move, 1 },
{ ai_move, 6 },
{ ai_move, 3 },
{ ai_move, 2 },
{ ai_move },
{ ai_move, 1 }
};
MMOVE_T(mutant_move_pain3) = { FRAME_pain301, FRAME_pain311, mutant_frames_pain3, mutant_run };
PAIN(mutant_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
float r;
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 3_sec;
r = frandom();
if (r < 0.33f)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else if (r < 0.66f)
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (r < 0.33f)
M_SetAnimation(self, &mutant_move_pain1);
else if (r < 0.66f)
M_SetAnimation(self, &mutant_move_pain2);
else
M_SetAnimation(self, &mutant_move_pain3);
}
MONSTERINFO_SETSKIN(mutant_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
//
// 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;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
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 }
};
MMOVE_T(mutant_move_death1) = { FRAME_death101, FRAME_death109, mutant_frames_death1, mutant_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 }
};
MMOVE_T(mutant_move_death2) = { FRAME_death201, FRAME_death210, mutant_frames_death2, mutant_dead };
DIE(mutant_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
self->s.skinnum /= 2;
ThrowGibs(self, damage, {
{ 2, "models/objects/gibs/bone/tris.md2" },
{ 4, "models/objects/gibs/sm_meat/tris.md2" },
{ 2, "models/monsters/mutant/gibs/hand.md2", GIB_SKINNED | GIB_UPRIGHT },
{ 2, "models/monsters/mutant/gibs/foot.md2", GIB_SKINNED },
{ "models/monsters/mutant/gibs/chest.md2", GIB_SKINNED },
{ "models/monsters/mutant/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
if (frandom() < 0.5f)
M_SetAnimation(self, &mutant_move_death1);
else
M_SetAnimation(self, &mutant_move_death2);
}
//================
// ROGUE
void mutant_jump_down(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 100);
self->velocity += (up * 300);
}
void mutant_jump_up(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 200);
self->velocity += (up * 450);
}
void mutant_jump_wait_land(edict_t *self)
{
if (!monster_jump_finished(self) && self->groundentity == nullptr)
self->monsterinfo.nextframe = self->s.frame;
else
self->monsterinfo.nextframe = self->s.frame + 1;
}
mframe_t mutant_frames_jump_up[] = {
{ ai_move, -8 },
{ ai_move, -8, mutant_jump_up },
{ ai_move, 0, mutant_jump_wait_land },
{ ai_move },
{ ai_move }
};
MMOVE_T(mutant_move_jump_up) = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_up, mutant_run };
mframe_t mutant_frames_jump_down[] = {
{ ai_move },
{ ai_move, 0, mutant_jump_down },
{ ai_move, 0, mutant_jump_wait_land },
{ ai_move },
{ ai_move }
};
MMOVE_T(mutant_move_jump_down) = { FRAME_jump01, FRAME_jump05, mutant_frames_jump_down, mutant_run };
void mutant_jump_updown(edict_t *self, blocked_jump_result_t result)
{
if (!self->enemy)
return;
if (result == blocked_jump_result_t::JUMP_JUMP_UP)
M_SetAnimation(self, &mutant_move_jump_up);
else
M_SetAnimation(self, &mutant_move_jump_down);
}
/*
===
Blocked
===
*/
MONSTERINFO_BLOCKED(mutant_blocked) (edict_t *self, float dist) -> bool
{
if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
{
if (result != blocked_jump_result_t::JUMP_TURN)
mutant_jump_updown(self, result);
return true;
}
if (blocked_checkplat(self, dist))
return true;
return false;
}
// ROGUE
//================
//
// SPAWN
//
/*QUAKED monster_mutant (1 .5 0) (-32 -32 -24) (32 32 32) Ambush Trigger_Spawn Sight NoJumping
model="models/monsters/mutant/tris.md2"
*/
void SP_monster_mutant(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
self->monsterinfo.aiflags |= AI_STINKY;
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/mutant/tris.md2");
gi.modelindex("models/monsters/mutant/gibs/head.md2");
gi.modelindex("models/monsters/mutant/gibs/chest.md2");
gi.modelindex("models/monsters/mutant/gibs/hand.md2");
gi.modelindex("models/monsters/mutant/gibs/foot.md2");
self->mins = { -18, -18, -24 };
self->maxs = { 18, 18, 30 };
self->health = 300 * st.health_multiplier;
self->gib_health = -120;
self->mass = 300;
self->pain = mutant_pain;
self->die = mutant_die;
self->monsterinfo.stand = mutant_stand;
self->monsterinfo.walk = mutant_walk;
self->monsterinfo.run = mutant_run;
self->monsterinfo.dodge = nullptr;
self->monsterinfo.attack = mutant_jump;
self->monsterinfo.melee = mutant_melee;
self->monsterinfo.sight = mutant_sight;
self->monsterinfo.search = mutant_search;
self->monsterinfo.idle = mutant_idle;
self->monsterinfo.checkattack = mutant_checkattack;
self->monsterinfo.blocked = mutant_blocked; // PGM
self->monsterinfo.setskin = mutant_setskin;
gi.linkentity(self);
M_SetAnimation(self, &mutant_move_stand);
self->monsterinfo.combat_style = COMBAT_MELEE;
self->monsterinfo.scale = MODEL_SCALE;
self->monsterinfo.can_jump = !(self->spawnflags & SPAWNFLAG_MUTANT_NOJUMPING);
self->monsterinfo.drop_height = 256;
self->monsterinfo.jump_height = 68;
walkmonster_start(self);
}

167
rerelease/m_mutant.h Normal file
View File

@@ -0,0 +1,167 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/mutant
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_attack01,
FRAME_attack02,
FRAME_attack03,
FRAME_attack04,
FRAME_attack05,
FRAME_attack06,
FRAME_attack07,
FRAME_attack08,
FRAME_attack09,
FRAME_attack10,
FRAME_attack11,
FRAME_attack12,
FRAME_attack13,
FRAME_attack14,
FRAME_attack15,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death108,
FRAME_death109,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_death206,
FRAME_death207,
FRAME_death208,
FRAME_death209,
FRAME_death210,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain206,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_pain310,
FRAME_pain311,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_run07,
FRAME_run08,
FRAME_stand101,
FRAME_stand102,
FRAME_stand103,
FRAME_stand104,
FRAME_stand105,
FRAME_stand106,
FRAME_stand107,
FRAME_stand108,
FRAME_stand109,
FRAME_stand110,
FRAME_stand111,
FRAME_stand112,
FRAME_stand113,
FRAME_stand114,
FRAME_stand115,
FRAME_stand116,
FRAME_stand117,
FRAME_stand118,
FRAME_stand119,
FRAME_stand120,
FRAME_stand121,
FRAME_stand122,
FRAME_stand123,
FRAME_stand124,
FRAME_stand125,
FRAME_stand126,
FRAME_stand127,
FRAME_stand128,
FRAME_stand129,
FRAME_stand130,
FRAME_stand131,
FRAME_stand132,
FRAME_stand133,
FRAME_stand134,
FRAME_stand135,
FRAME_stand136,
FRAME_stand137,
FRAME_stand138,
FRAME_stand139,
FRAME_stand140,
FRAME_stand141,
FRAME_stand142,
FRAME_stand143,
FRAME_stand144,
FRAME_stand145,
FRAME_stand146,
FRAME_stand147,
FRAME_stand148,
FRAME_stand149,
FRAME_stand150,
FRAME_stand151,
FRAME_stand152,
FRAME_stand153,
FRAME_stand154,
FRAME_stand155,
FRAME_stand156,
FRAME_stand157,
FRAME_stand158,
FRAME_stand159,
FRAME_stand160,
FRAME_stand161,
FRAME_stand162,
FRAME_stand163,
FRAME_stand164,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_walk21,
FRAME_walk22,
FRAME_walk23,
// ROGUE
FRAME_jump01,
FRAME_jump02,
FRAME_jump03,
FRAME_jump04,
FRAME_jump05
// ROGUE
};
constexpr float MODEL_SCALE = 1.000000f;

965
rerelease/m_parasite.cpp Normal file
View File

@@ -0,0 +1,965 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
parasite
==============================================================================
*/
#include "g_local.h"
#include "m_parasite.h"
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;
void parasite_stand(edict_t *self);
void parasite_start_run(edict_t *self);
void parasite_run(edict_t *self);
void parasite_walk(edict_t *self);
void parasite_end_fidget(edict_t *self);
void parasite_do_fidget(edict_t *self);
void parasite_refidget(edict_t *self);
void parasite_launch(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_launch, 1, ATTN_NORM, 0);
}
void parasite_reel_in(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_reelin, 1, ATTN_NORM, 0);
}
MONSTERINFO_SIGHT(parasite_sight) (edict_t *self, edict_t *other) -> void
{
gi.sound(self, CHAN_WEAPON, sound_sight, 1, ATTN_NORM, 0);
}
void parasite_tap(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_tap, 0.75f, 2.75f, 0);
}
void parasite_scratch(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_scratch, 0.75f, 2.75f, 0);
}
#if 0
void parasite_search(edict_t *self)
{
gi.sound(self, CHAN_WEAPON, sound_search, 1, ATTN_IDLE, 0);
}
#endif
mframe_t parasite_frames_start_fidget[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(parasite_move_start_fidget) = { FRAME_stand18, FRAME_stand21, parasite_frames_start_fidget, parasite_do_fidget };
mframe_t parasite_frames_fidget[] = {
{ ai_stand, 0, parasite_scratch },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, parasite_scratch },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(parasite_move_fidget) = { FRAME_stand22, FRAME_stand27, parasite_frames_fidget, parasite_refidget };
mframe_t parasite_frames_end_fidget[] = {
{ ai_stand, 0, parasite_scratch },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(parasite_move_end_fidget) = { FRAME_stand28, FRAME_stand35, parasite_frames_end_fidget, parasite_stand };
void parasite_end_fidget(edict_t *self)
{
M_SetAnimation(self, &parasite_move_end_fidget);
}
void parasite_do_fidget(edict_t *self)
{
M_SetAnimation(self, &parasite_move_fidget);
}
void parasite_refidget(edict_t *self)
{
if (frandom() <= 0.8f)
M_SetAnimation(self, &parasite_move_fidget);
else
M_SetAnimation(self, &parasite_move_end_fidget);
}
MONSTERINFO_IDLE(parasite_idle) (edict_t *self) -> void
{
if (self->enemy)
return;
M_SetAnimation(self, &parasite_move_start_fidget);
}
mframe_t parasite_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, parasite_tap },
{ ai_stand },
{ ai_stand, 0, parasite_tap },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, parasite_tap },
{ ai_stand },
{ ai_stand, 0, parasite_tap },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand, 0, parasite_tap },
{ ai_stand },
{ ai_stand, 0, parasite_tap }
};
MMOVE_T(parasite_move_stand) = { FRAME_stand01, FRAME_stand17, parasite_frames_stand, parasite_stand };
MONSTERINFO_STAND(parasite_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &parasite_move_stand);
}
mframe_t parasite_frames_run[] = {
{ ai_run, 30 },
{ ai_run, 30 },
{ ai_run, 22, monster_footstep },
{ ai_run, 19, monster_footstep },
{ ai_run, 24 },
{ ai_run, 28, monster_footstep },
{ ai_run, 25, monster_footstep }
};
MMOVE_T(parasite_move_run) = { FRAME_run03, FRAME_run09, parasite_frames_run, nullptr };
mframe_t parasite_frames_start_run[] = {
{ ai_run },
{ ai_run, 30 },
};
MMOVE_T(parasite_move_start_run) = { FRAME_run01, FRAME_run02, parasite_frames_start_run, parasite_run };
#if 0
mframe_t parasite_frames_stop_run[] = {
{ ai_run, 20 },
{ ai_run, 20 },
{ ai_run, 12 },
{ ai_run, 10 },
{ ai_run },
{ ai_run }
};
MMOVE_T(parasite_move_stop_run) = { FRAME_run10, FRAME_run15, parasite_frames_stop_run, nullptr };
#endif
MONSTERINFO_RUN(parasite_start_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &parasite_move_stand);
else
M_SetAnimation(self, &parasite_move_start_run);
}
static void proboscis_retract(edict_t *self);
void parasite_run(edict_t *self)
{
if (self->proboscus && self->proboscus->style != 2)
proboscis_retract(self->proboscus);
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &parasite_move_stand);
else
M_SetAnimation(self, &parasite_move_run);
}
mframe_t parasite_frames_walk[] = {
{ ai_walk, 30 },
{ ai_walk, 30 },
{ ai_walk, 22, monster_footstep },
{ ai_walk, 19, monster_footstep },
{ ai_walk, 24 },
{ ai_walk, 28, monster_footstep },
{ ai_walk, 25, monster_footstep }
};
MMOVE_T(parasite_move_walk) = { FRAME_run03, FRAME_run09, parasite_frames_walk, parasite_walk };
mframe_t parasite_frames_start_walk[] = {
{ ai_walk, 0 },
{ ai_walk, 30, parasite_walk }
};
MMOVE_T(parasite_move_start_walk) = { FRAME_run01, FRAME_run02, parasite_frames_start_walk, nullptr };
#if 0
mframe_t parasite_frames_stop_walk[] = {
{ ai_walk, 20 },
{ ai_walk, 20 },
{ ai_walk, 12 },
{ ai_walk, 10 },
{ ai_walk },
{ ai_walk }
};
MMOVE_T(parasite_move_stop_walk) = { FRAME_run10, FRAME_run15, parasite_frames_stop_walk, nullptr };
#endif
MONSTERINFO_WALK(parasite_start_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &parasite_move_start_walk);
}
void parasite_walk(edict_t *self)
{
M_SetAnimation(self, &parasite_move_walk);
}
// hard reset on proboscis; like we never existed
THINK(proboscis_reset) (edict_t *self) -> void
{
self->owner->proboscus = nullptr;
G_FreeEdict(self->proboscus);
G_FreeEdict(self);
}
DIE(proboscis_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (mod.id == MOD_CRUSH)
proboscis_reset(self);
}
extern const mmove_t parasite_move_fire_proboscis;
static void parasite_break_wait(edict_t *self)
{
// prob exploded?
if (self->proboscus && self->proboscus->style != 3)
self->monsterinfo.nextframe = FRAME_break19;
else if (brandom())
{
// don't get hurt
parasite_reel_in(self);
self->monsterinfo.nextframe = FRAME_break31;
}
}
static void proboscis_retract(edict_t *self)
{
// start retract animation
if (self->owner->monsterinfo.active_move == &parasite_move_fire_proboscis)
self->owner->monsterinfo.nextframe = FRAME_drain12;
// mark as retracting
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
// come back real hard
if (self->style != 2)
self->speed *= g_athena_parasite_proboscis_retract_modifier;
self->style = 2;
gi.linkentity(self);
}
static void parasite_break_retract(edict_t *self)
{
if (self->proboscus)
proboscis_retract(self->proboscus);
}
static void parasite_break_sound(edict_t *self)
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 3_sec;
}
void proboscis_segment_draw(edict_t *self);
static void parasite_charge_proboscis(edict_t *self, float dist)
{
if (self->s.frame >= FRAME_break01 && self->s.frame <= FRAME_break32)
ai_move(self, dist);
else
ai_charge(self, dist);
if (self->proboscus)
proboscis_segment_draw(self->proboscus->proboscus);
}
static void parasite_break_noise(edict_t *self)
{
gi.sound(self, CHAN_VOICE, sound_search, 1, ATTN_NORM, 0);
}
constexpr mframe_t parasite_frames_break[] = {
{ parasite_charge_proboscis },
{ parasite_charge_proboscis, -3, parasite_break_noise },
{ parasite_charge_proboscis, 1 },
{ parasite_charge_proboscis, 2 },
{ parasite_charge_proboscis, -3 },
{ parasite_charge_proboscis, 1 },
{ parasite_charge_proboscis, 1 },
{ parasite_charge_proboscis, 3 },
{ parasite_charge_proboscis, 0, parasite_break_noise },
{ parasite_charge_proboscis, -18 },
{ parasite_charge_proboscis, 3 },
{ parasite_charge_proboscis, 9 },
{ parasite_charge_proboscis, 6 },
{ parasite_charge_proboscis },
{ parasite_charge_proboscis, -18 },
{ parasite_charge_proboscis },
{ parasite_charge_proboscis, 8, parasite_break_retract },
{ parasite_charge_proboscis, 9 },
{ parasite_charge_proboscis, 0, parasite_break_wait },
{ parasite_charge_proboscis, -18, parasite_break_sound },
{ parasite_charge_proboscis },
{ parasite_charge_proboscis }, // airborne
{ parasite_charge_proboscis }, // airborne
{ parasite_charge_proboscis }, // slides
{ parasite_charge_proboscis }, // slides
{ parasite_charge_proboscis }, // slides
{ parasite_charge_proboscis }, // slides
{ parasite_charge_proboscis, 4 },
{ parasite_charge_proboscis, 11 },
{ parasite_charge_proboscis, -2 },
{ parasite_charge_proboscis, -5 },
{ parasite_charge_proboscis, 1 }
};
MMOVE_T(parasite_move_break) = { FRAME_break01, FRAME_break32, parasite_frames_break, parasite_start_run };
TOUCH(proboscis_touch) (edict_t *self, edict_t *other, const trace_t &tr, bool other_touching_self) -> void
{
// owner isn't trying to probe any more, don't touch anything
if (self->owner->monsterinfo.active_move != &parasite_move_fire_proboscis)
return;
vec3_t p;
// hit what we want to succ
if ((other->svflags & SVF_PLAYER) || other == self->owner->enemy)
{
if (tr.startsolid)
p = tr.endpos;
else
p = tr.endpos - ((self->s.origin - tr.endpos).normalized() * 12);
self->owner->monsterinfo.nextframe = FRAME_drain06;
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
self->style = 1;
// stick to this guy
self->move_origin = p - other->s.origin;
self->enemy = other;
self->s.alpha = 0.35f;
gi.sound(self, CHAN_WEAPON, sound_suck, 1, ATTN_NORM, 0);
}
else
{
p = tr.endpos + tr.plane.normal;
// hit monster, don't suck but do small damage
// and retract immediately
if (other->svflags & (SVF_MONSTER | SVF_DEADMONSTER))
proboscis_retract(self);
else
{
// hit wall; stick to it and do break animation
self->owner->monsterinfo.active_move = &parasite_move_break;
self->movetype = MOVETYPE_NONE;
self->solid = SOLID_NOT;
self->style = 1;
self->owner->s.angles[YAW] = self->s.angles[YAW];
}
}
if (other->takedamage)
T_Damage(other, self, self->owner, tr.plane.normal, tr.endpos, tr.plane.normal, 5, 0, DAMAGE_NONE, MOD_UNKNOWN);
gi.positioned_sound(tr.endpos, self->owner, CHAN_AUTO, sound_impact, 1, ATTN_NORM, 0);
self->s.origin = p;
self->nextthink = level.time + FRAME_TIME_S; // start doing stuff on next frame
gi.linkentity(self);
}
// from break01
constexpr vec3_t parasite_break_offsets[] = {
{ 7.0f, 0, 7.0f },
{ 6.3f, 14.5f, 4.0f },
{ 8.5f, 0, 5.6f },
{ 5.0f, -15.25f, 4.0f, },
{ 9.5f, -1.8f, 5.9f },
{ 6.2f, 14.f, 4.0f },
{ 12.25f, 7.5f, 1.4f },
{ 13.8f, 0, -2.4f },
{ 13.8f, 0, -4.0f },
{ 0.1f, 0, -0.7f },
{ 5.0f, 0, 3.7f },
{ 11.f, 0, 4.f },
{ 13.5f, 0, -4.0f },
{ 13.5f, 0, -4.0f },
{ 0.2f, 0, -0.7f },
{ 3.9f, 0, 3.6f },
{ 8.5f, 0, 5.0f },
{ 14.0f, 0, -4.f },
{ 14.0f, 0, -4.f },
{ 0.1f, 0, -0.5f }
};
// from drain01
constexpr vec3_t parasite_drain_offsets[] = {
{ -1.7f, 0, 1.2f },
{ -2.2f, 0, -0.6f },
{ 7.7f, 0, 7.2f },
{ 7.2f, 0, 5.7f },
{ 6.2f, 0, 7.8f },
{ 4.7f, 0, 6.7f },
{ 5.0f, 0, 9.0f },
{ 5.0f, 0, 7.0f },
{ 5.0f, 0, 10.5f },
{ 4.5f, 0, 9.7f },
{ 1.5f, 0, 12.0f },
{ 2.9f, 0, 11.0f },
{ 2.1f, 0, 7.6f },
};
vec3_t parasite_get_proboscis_start(edict_t *self)
{
vec3_t f, r, start;
AngleVectors(self->s.angles, f, r, nullptr);
vec3_t offset;
if (self->s.frame >= FRAME_break01 && self->s.frame < FRAME_break01 + q_countof(parasite_break_offsets))
offset = parasite_break_offsets[self->s.frame - FRAME_break01];
else if (self->s.frame >= FRAME_drain01 && self->s.frame < FRAME_drain01 + q_countof(parasite_drain_offsets))
offset = parasite_drain_offsets[self->s.frame - FRAME_drain01];
else
offset = { 8, 0, 6 };
start = M_ProjectFlashSource(self, offset, f, r);
return start;
}
THINK(proboscis_think) (edict_t *self) -> void
{
self->nextthink = level.time + FRAME_TIME_S; // start doing stuff on next frame
// retracting; keep pulling until we hit the parasite
if (self->style == 2)
{
vec3_t start = parasite_get_proboscis_start(self->owner);
vec3_t dir = (self->s.origin - start);
float dist = dir.normalize();
if (dist <= (self->speed * 2) * gi.frame_time_s)
{
// reached target; free self on next frame, let parasite know
self->style = 3;
self->think = proboscis_reset;
self->s.origin = start;
gi.linkentity(self);
return;
}
// pull us in
self->s.origin -= dir * (self->speed * gi.frame_time_s);
gi.linkentity(self);
}
// stuck on target; do damage, suck health
// and check if target goes away
else if (self->style == 1)
{
if (!self->enemy)
{
// stuck in wall
}
else if (!self->enemy->inuse || self->enemy->health <= 0 || !self->enemy->takedamage)
{
// target gone, retract early
proboscis_retract(self);
}
else
{
// update our position
self->s.origin = self->enemy->s.origin + self->move_origin;
vec3_t start = parasite_get_proboscis_start(self->owner);
self->s.angles = vectoangles((self->s.origin - start).normalized());
// see if we got cut by the world
trace_t tr = gi.traceline(start, self->s.origin, nullptr, MASK_SOLID);
if (tr.fraction != 1.0f)
{
// blocked, so retract
proboscis_retract(self);
self->s.origin = self->s.old_origin;
}
else
{
// succ & drain
if (self->timestamp <= level.time)
{
T_Damage(self->enemy, self, self->owner, tr.plane.normal, tr.endpos, tr.plane.normal, 2, 0, DAMAGE_NONE, MOD_UNKNOWN);
self->owner->health = min(self->owner->max_health, self->owner->health + 2);
self->owner->monsterinfo.setskin(self->owner);
self->timestamp = level.time + 10_hz;
}
}
gi.linkentity(self);
}
}
// flying
else if (self->style == 0)
{
// owner gone away?
if (!self->owner->enemy || !self->owner->enemy->inuse || self->owner->enemy->health <= 0)
{
proboscis_retract(self);
return;
}
// if we're well behind our target and missed by 2x velocity,
// be smart enough to pull in automatically
vec3_t to_target = (self->s.origin - self->owner->enemy->s.origin);
float dist_to_target = to_target.normalize();
if (dist_to_target > (self->speed * 2) / 15.f)
{
vec3_t from_owner = (self->s.origin - self->owner->s.origin).normalized();
float dot = to_target.dot(from_owner);
if (dot > 0.f)
{
proboscis_retract(self);
return;
}
}
}
}
PRETHINK(proboscis_segment_draw) (edict_t *self) -> void
{
vec3_t start = parasite_get_proboscis_start(self->owner->owner);
self->s.origin = start;
self->s.old_origin = self->owner->s.origin - ((self->owner->s.origin - start).normalized() * 8.f);
gi.linkentity(self);
}
static void fire_proboscis(edict_t *self, vec3_t start, vec3_t dir, float speed)
{
edict_t *tip = G_Spawn();
tip->s.angles = vectoangles(dir);
tip->s.modelindex = gi.modelindex("models/monsters/parasite/tip/tris.md2");
tip->movetype = MOVETYPE_FLYMISSILE;
tip->owner = self;
self->proboscus = tip;
tip->clipmask = MASK_PROJECTILE & ~CONTENTS_DEADMONSTER;
tip->s.origin = tip->s.old_origin = start;
tip->speed = speed;
tip->velocity = dir * speed;
tip->solid = SOLID_BBOX;
tip->takedamage = true;
tip->flags |= FL_NO_DAMAGE_EFFECTS | FL_NO_KNOCKBACK;
tip->die = proboscis_die;
tip->touch = proboscis_touch;
tip->think = proboscis_think;
tip->nextthink = level.time + FRAME_TIME_S; // start doing stuff on next frame
tip->svflags |= SVF_PROJECTILE;
edict_t *segment = G_Spawn();
segment->s.modelindex = gi.modelindex("models/monsters/parasite/segment/tris.md2");
segment->s.renderfx = RF_BEAM;
segment->postthink = proboscis_segment_draw;
tip->proboscus = segment;
segment->owner = tip;
trace_t tr = gi.traceline(tip->s.origin, tip->s.origin + (tip->velocity * gi.frame_time_s), self, tip->clipmask);
if (tr.startsolid)
{
tr.plane.normal = -dir;
tr.endpos = start;
tip->touch(tip, tr.ent, tr, false);
}
else if (tr.fraction < 1.0f)
tip->touch(tip, tr.ent, tr, false);
segment->s.origin = start;
segment->s.old_origin = tip->s.origin + ((tip->s.origin - start).normalized() * 8.f);
gi.linkentity(tip);
gi.linkentity(segment);
}
static void parasite_fire_proboscis(edict_t *self)
{
if (self->proboscus && self->proboscus->style != 2)
proboscis_reset(self->proboscus);
vec3_t start = parasite_get_proboscis_start(self);
vec3_t dir;
PredictAim(self, self->enemy, start, g_athena_parasite_proboscis_speed, false, crandom_open() * g_athena_parasite_miss_chance, &dir, nullptr);
fire_proboscis(self, start, dir, g_athena_parasite_proboscis_speed);
}
static void parasite_proboscis_wait(edict_t *self)
{
// loop frames while we wait
if (self->s.frame == FRAME_drain04)
self->monsterinfo.nextframe = FRAME_drain05;
else
self->monsterinfo.nextframe = FRAME_drain04;
}
static void parasite_proboscis_pull_wait(edict_t *self)
{
// prob exploded?
if (!self->proboscus || self->proboscus->style == 3)
{
self->monsterinfo.nextframe = FRAME_drain14;
return;
}
// being pulled in, so wait until we get destroyed
if (self->s.frame == FRAME_drain12)
self->monsterinfo.nextframe = FRAME_drain13;
else
self->monsterinfo.nextframe = FRAME_drain12;
if (self->proboscus->style != 2)
proboscis_retract(self->proboscus);
}
mframe_t parasite_frames_fire_proboscis[] = {
{ parasite_charge_proboscis, 0, parasite_launch },
{ parasite_charge_proboscis },
{ parasite_charge_proboscis, 15, parasite_fire_proboscis }, // Target hits
{ parasite_charge_proboscis, 0, parasite_proboscis_wait }, // drain
{ parasite_charge_proboscis, 0, parasite_proboscis_wait }, // drain
{ parasite_charge_proboscis, 0 }, // drain
{ parasite_charge_proboscis, 0 }, // drain
{ parasite_charge_proboscis, -2 }, // drain
{ parasite_charge_proboscis, -2 }, // drain
{ parasite_charge_proboscis, -3 }, // drain
{ parasite_charge_proboscis, -2 }, // drain
{ parasite_charge_proboscis, 0, parasite_proboscis_pull_wait }, // drain
{ parasite_charge_proboscis, -1, parasite_proboscis_pull_wait }, // drain
{ parasite_charge_proboscis, 0, parasite_reel_in }, // let go
{ parasite_charge_proboscis, -2 },
{ parasite_charge_proboscis, -2 },
{ parasite_charge_proboscis, -3 },
{ parasite_charge_proboscis }
};
MMOVE_T(parasite_move_fire_proboscis) = { FRAME_drain01, FRAME_drain18, parasite_frames_fire_proboscis, parasite_start_run };
MONSTERINFO_ATTACK(parasite_attack) (edict_t *self) -> void
{
if (!M_CheckClearShot(self, parasite_drain_offsets[0]))
return;
if (self->proboscus && self->proboscus->style != 2)
proboscis_retract(self->proboscus);
M_SetAnimation(self, &parasite_move_fire_proboscis);
}
//================
// ROGUE
void parasite_jump_down(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 100);
self->velocity += (up * 300);
}
void parasite_jump_up(edict_t *self)
{
vec3_t forward, up;
AngleVectors(self->s.angles, forward, nullptr, up);
self->velocity += (forward * 200);
self->velocity += (up * 450);
}
void parasite_jump_wait_land(edict_t *self)
{
if (self->groundentity == nullptr)
{
self->monsterinfo.nextframe = self->s.frame;
if (monster_jump_finished(self))
self->monsterinfo.nextframe = self->s.frame + 1;
}
else
self->monsterinfo.nextframe = self->s.frame + 1;
}
mframe_t parasite_frames_jump_up[] = {
{ ai_move, -8 },
{ ai_move, -8 },
{ ai_move, -8 },
{ ai_move, -8, parasite_jump_up },
{ ai_move },
{ ai_move },
{ ai_move, 0, parasite_jump_wait_land },
{ ai_move }
};
MMOVE_T(parasite_move_jump_up) = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_up, parasite_run };
mframe_t parasite_frames_jump_down[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, parasite_jump_down },
{ ai_move },
{ ai_move },
{ ai_move, 0, parasite_jump_wait_land },
{ ai_move }
};
MMOVE_T(parasite_move_jump_down) = { FRAME_jump01, FRAME_jump08, parasite_frames_jump_down, parasite_run };
void parasite_jump(edict_t *self, blocked_jump_result_t result)
{
if (!self->enemy)
return;
if (result == blocked_jump_result_t::JUMP_JUMP_UP)
M_SetAnimation(self, &parasite_move_jump_up);
else
M_SetAnimation(self, &parasite_move_jump_down);
}
/*
===
Blocked
===
*/
MONSTERINFO_BLOCKED(parasite_blocked) (edict_t *self, float dist) -> bool
{
if (auto result = blocked_checkjump(self, dist); result != blocked_jump_result_t::NO_JUMP)
{
if (result != blocked_jump_result_t::JUMP_TURN)
parasite_jump(self, result);
return true;
}
if (blocked_checkplat(self, dist))
return true;
return false;
}
// ROGUE
//================
/*
===
Death Stuff Starts
===
*/
void parasite_dead(edict_t *self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -8 };
monster_dead(self);
}
static void parasite_shrink(edict_t *self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t parasite_frames_death[] = {
{ ai_move, 0, nullptr, FRAME_stand01 },
{ ai_move },
{ ai_move },
{ ai_move, 0, parasite_shrink },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move }
};
MMOVE_T(parasite_move_death) = { FRAME_death101, FRAME_death107, parasite_frames_death, parasite_dead };
DIE(parasite_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (self->proboscus && self->proboscus->style != 2)
proboscis_reset(self->proboscus);
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
self->s.skinnum /= 2;
ThrowGibs(self, damage, {
{ 1, "models/objects/gibs/bone/tris.md2" },
{ 3, "models/objects/gibs/sm_meat/tris.md2" },
{ "models/monsters/parasite/gibs/chest.md2", GIB_SKINNED },
{ 2, "models/monsters/parasite/gibs/bleg.md2", GIB_SKINNED | GIB_UPRIGHT },
{ 2, "models/monsters/parasite/gibs/fleg.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/parasite/gibs/head.md2", GIB_SKINNED | GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
M_SetAnimation(self, &parasite_move_death);
}
/*
===
End Death Stuff
===
*/
mframe_t parasite_frames_pain1[] = {
{ ai_move, 0, nullptr, FRAME_stand01 },
{ ai_move },
{ ai_move, 0, [](edict_t *self) { self->monsterinfo.nextframe = FRAME_pain105; } },
{ ai_move, 0, monster_footstep },
{ ai_move },
{ ai_move },
{ ai_move, 6, monster_footstep },
{ ai_move, 16 },
{ ai_move, -6, monster_footstep },
{ ai_move, -7 },
{ ai_move }
};
MMOVE_T(parasite_move_pain1) = { FRAME_pain101, FRAME_pain111, parasite_frames_pain1, parasite_start_run };
PAIN(parasite_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->pain_debounce_time)
return;
if (self->proboscus && self->proboscus->style != 2)
proboscis_retract(self->proboscus);
self->pain_debounce_time = level.time + 3_sec;
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
M_SetAnimation(self, &parasite_move_pain1);
}
MONSTERINFO_SETSKIN(parasite_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum = 1;
else
self->s.skinnum = 0;
}
constexpr spawnflags_t SPAWNFLAG_PARASITE_NOJUMPING = 8_spawnflag;
/*QUAKED monster_parasite (1 .5 0) (-16 -16 -24) (16 16 32) Ambush Trigger_Spawn Sight NoJumping
*/
void SP_monster_parasite(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
gi.modelindex("models/monsters/parasite/tip/tris.md2");
gi.modelindex("models/monsters/parasite/segment/tris.md2");
self->s.modelindex = gi.modelindex("models/monsters/parasite/tris.md2");
gi.modelindex("models/monsters/parasite/gibs/head.md2");
gi.modelindex("models/monsters/parasite/gibs/chest.md2");
gi.modelindex("models/monsters/parasite/gibs/bleg.md2");
gi.modelindex("models/monsters/parasite/gibs/fleg.md2");
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, 24 };
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->health = 175 * st.health_multiplier;
self->gib_health = -50;
self->mass = 250;
self->pain = parasite_pain;
self->die = parasite_die;
self->monsterinfo.stand = parasite_stand;
self->monsterinfo.walk = parasite_start_walk;
self->monsterinfo.run = parasite_start_run;
self->monsterinfo.attack = parasite_attack;
self->monsterinfo.sight = parasite_sight;
self->monsterinfo.idle = parasite_idle;
self->monsterinfo.blocked = parasite_blocked; // PGM
self->monsterinfo.setskin = parasite_setskin;
gi.linkentity(self);
M_SetAnimation(self, &parasite_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
self->yaw_speed = 30;
self->monsterinfo.can_jump = !self->spawnflags.has(SPAWNFLAG_PARASITE_NOJUMPING);
self->monsterinfo.drop_height = 256;
self->monsterinfo.jump_height = 68;
walkmonster_start(self);
}

139
rerelease/m_parasite.h Normal file
View File

@@ -0,0 +1,139 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/parasite
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_break01,
FRAME_break02,
FRAME_break03,
FRAME_break04,
FRAME_break05,
FRAME_break06,
FRAME_break07,
FRAME_break08,
FRAME_break09,
FRAME_break10,
FRAME_break11,
FRAME_break12,
FRAME_break13,
FRAME_break14,
FRAME_break15,
FRAME_break16,
FRAME_break17,
FRAME_break18,
FRAME_break19,
FRAME_break20,
FRAME_break21,
FRAME_break22,
FRAME_break23,
FRAME_break24,
FRAME_break25,
FRAME_break26,
FRAME_break27,
FRAME_break28,
FRAME_break29,
FRAME_break30,
FRAME_break31,
FRAME_break32,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_drain01,
FRAME_drain02,
FRAME_drain03,
FRAME_drain04,
FRAME_drain05,
FRAME_drain06,
FRAME_drain07,
FRAME_drain08,
FRAME_drain09,
FRAME_drain10,
FRAME_drain11,
FRAME_drain12,
FRAME_drain13,
FRAME_drain14,
FRAME_drain15,
FRAME_drain16,
FRAME_drain17,
FRAME_drain18,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain106,
FRAME_pain107,
FRAME_pain108,
FRAME_pain109,
FRAME_pain110,
FRAME_pain111,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_run07,
FRAME_run08,
FRAME_run09,
FRAME_run10,
FRAME_run11,
FRAME_run12,
FRAME_run13,
FRAME_run14,
FRAME_run15,
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
// ROGUE
FRAME_jump01,
FRAME_jump02,
FRAME_jump03,
FRAME_jump04,
FRAME_jump05,
FRAME_jump06,
FRAME_jump07,
FRAME_jump08
// ROGUE
};
constexpr float MODEL_SCALE = 1.000000f;

209
rerelease/m_player.h Normal file
View File

@@ -0,0 +1,209 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/player_x/frames
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_stand31,
FRAME_stand32,
FRAME_stand33,
FRAME_stand34,
FRAME_stand35,
FRAME_stand36,
FRAME_stand37,
FRAME_stand38,
FRAME_stand39,
FRAME_stand40,
FRAME_run1,
FRAME_run2,
FRAME_run3,
FRAME_run4,
FRAME_run5,
FRAME_run6,
FRAME_attack1,
FRAME_attack2,
FRAME_attack3,
FRAME_attack4,
FRAME_attack5,
FRAME_attack6,
FRAME_attack7,
FRAME_attack8,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_jump1,
FRAME_jump2,
FRAME_jump3,
FRAME_jump4,
FRAME_jump5,
FRAME_jump6,
FRAME_flip01,
FRAME_flip02,
FRAME_flip03,
FRAME_flip04,
FRAME_flip05,
FRAME_flip06,
FRAME_flip07,
FRAME_flip08,
FRAME_flip09,
FRAME_flip10,
FRAME_flip11,
FRAME_flip12,
FRAME_salute01,
FRAME_salute02,
FRAME_salute03,
FRAME_salute04,
FRAME_salute05,
FRAME_salute06,
FRAME_salute07,
FRAME_salute08,
FRAME_salute09,
FRAME_salute10,
FRAME_salute11,
FRAME_taunt01,
FRAME_taunt02,
FRAME_taunt03,
FRAME_taunt04,
FRAME_taunt05,
FRAME_taunt06,
FRAME_taunt07,
FRAME_taunt08,
FRAME_taunt09,
FRAME_taunt10,
FRAME_taunt11,
FRAME_taunt12,
FRAME_taunt13,
FRAME_taunt14,
FRAME_taunt15,
FRAME_taunt16,
FRAME_taunt17,
FRAME_wave01,
FRAME_wave02,
FRAME_wave03,
FRAME_wave04,
FRAME_wave05,
FRAME_wave06,
FRAME_wave07,
FRAME_wave08,
FRAME_wave09,
FRAME_wave10,
FRAME_wave11,
FRAME_point01,
FRAME_point02,
FRAME_point03,
FRAME_point04,
FRAME_point05,
FRAME_point06,
FRAME_point07,
FRAME_point08,
FRAME_point09,
FRAME_point10,
FRAME_point11,
FRAME_point12,
FRAME_crstnd01,
FRAME_crstnd02,
FRAME_crstnd03,
FRAME_crstnd04,
FRAME_crstnd05,
FRAME_crstnd06,
FRAME_crstnd07,
FRAME_crstnd08,
FRAME_crstnd09,
FRAME_crstnd10,
FRAME_crstnd11,
FRAME_crstnd12,
FRAME_crstnd13,
FRAME_crstnd14,
FRAME_crstnd15,
FRAME_crstnd16,
FRAME_crstnd17,
FRAME_crstnd18,
FRAME_crstnd19,
FRAME_crwalk1,
FRAME_crwalk2,
FRAME_crwalk3,
FRAME_crwalk4,
FRAME_crwalk5,
FRAME_crwalk6,
FRAME_crattak1,
FRAME_crattak2,
FRAME_crattak3,
FRAME_crattak4,
FRAME_crattak5,
FRAME_crattak6,
FRAME_crattak7,
FRAME_crattak8,
FRAME_crattak9,
FRAME_crpain1,
FRAME_crpain2,
FRAME_crpain3,
FRAME_crpain4,
FRAME_crdeath1,
FRAME_crdeath2,
FRAME_crdeath3,
FRAME_crdeath4,
FRAME_crdeath5,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_death206,
FRAME_death301,
FRAME_death302,
FRAME_death303,
FRAME_death304,
FRAME_death305,
FRAME_death306,
FRAME_death307,
FRAME_death308
};
constexpr float MODEL_SCALE = 1.000000f;

71
rerelease/m_rider.h Normal file
View File

@@ -0,0 +1,71 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/boss3/rider
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_stand201,
FRAME_stand202,
FRAME_stand203,
FRAME_stand204,
FRAME_stand205,
FRAME_stand206,
FRAME_stand207,
FRAME_stand208,
FRAME_stand209,
FRAME_stand210,
FRAME_stand211,
FRAME_stand212,
FRAME_stand213,
FRAME_stand214,
FRAME_stand215,
FRAME_stand216,
FRAME_stand217,
FRAME_stand218,
FRAME_stand219,
FRAME_stand220,
FRAME_stand221,
FRAME_stand222,
FRAME_stand223,
FRAME_stand224,
FRAME_stand225,
FRAME_stand226,
FRAME_stand227,
FRAME_stand228,
FRAME_stand229,
FRAME_stand230,
FRAME_stand231,
FRAME_stand232,
FRAME_stand233,
FRAME_stand234,
FRAME_stand235,
FRAME_stand236,
FRAME_stand237,
FRAME_stand238,
FRAME_stand239,
FRAME_stand240,
FRAME_stand241,
FRAME_stand242,
FRAME_stand243,
FRAME_stand244,
FRAME_stand245,
FRAME_stand246,
FRAME_stand247,
FRAME_stand248,
FRAME_stand249,
FRAME_stand250,
FRAME_stand251,
FRAME_stand252,
FRAME_stand253,
FRAME_stand254,
FRAME_stand255,
FRAME_stand256,
FRAME_stand257,
FRAME_stand258,
FRAME_stand259,
FRAME_stand260
};
constexpr float MODEL_SCALE = 1.000000;

598
rerelease/m_shambler.cpp Normal file
View File

@@ -0,0 +1,598 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
SHAMBLER
==============================================================================
*/
#include "g_local.h"
#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;
//
// misc
//
MONSTERINFO_SIGHT(shambler_sight) (edict_t* self, edict_t* other) -> void
{
gi.sound(self, CHAN_VOICE, sound_sight, 1, ATTN_NORM, 0);
}
constexpr vec3_t lightning_left_hand[] = {
{ 44, 36, 25 },
{ 10, 44, 57 },
{ -1, 40, 70 },
{ -10, 34, 75 },
{ 7.4f, 24, 89 }
};
constexpr vec3_t lightning_right_hand[] = {
{ 28, -38, 25 },
{ 31, -7, 70 },
{ 20, 0, 80 },
{ 16, 1.2f, 81 },
{ 27, -11, 83 }
};
static void shambler_lightning_update(edict_t *self)
{
edict_t *lightning = self->beam;
if (self->s.frame >= FRAME_magic01 + q_countof(lightning_left_hand))
{
G_FreeEdict(lightning);
self->beam = nullptr;
return;
}
vec3_t f, r;
AngleVectors(self->s.angles, f, r, nullptr);
lightning->s.origin = M_ProjectFlashSource(self, lightning_left_hand[self->s.frame - FRAME_magic01], f, r);
lightning->s.old_origin = M_ProjectFlashSource(self, lightning_right_hand[self->s.frame - FRAME_magic01], f, r);
gi.linkentity(lightning);
}
void shambler_windup(edict_t* self)
{
gi.sound(self, CHAN_WEAPON, sound_windup, 1, ATTN_NORM, 0);
edict_t *lightning = self->beam = G_Spawn();
lightning->s.modelindex = gi.modelindex("models/proj/lightning/tris.md2");
lightning->s.renderfx |= RF_BEAM;
lightning->owner = self;
shambler_lightning_update(self);
}
MONSTERINFO_IDLE(shambler_idle) (edict_t* self) -> void
{
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
void shambler_maybe_idle(edict_t* self)
{
if (frandom() > 0.8)
gi.sound(self, CHAN_VOICE, sound_idle, 1, ATTN_IDLE, 0);
}
//
// stand
//
mframe_t shambler_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(shambler_move_stand) = { FRAME_stand01, FRAME_stand17, shambler_frames_stand, nullptr };
MONSTERINFO_STAND(shambler_stand) (edict_t* self) -> void
{
M_SetAnimation(self, &shambler_move_stand);
}
//
// walk
//
void shambler_walk(edict_t* self);
mframe_t shambler_frames_walk[] = {
{ ai_walk, 10 }, // FIXME: add footsteps?
{ ai_walk, 9 },
{ ai_walk, 9 },
{ ai_walk, 5 },
{ ai_walk, 6 },
{ ai_walk, 12 },
{ ai_walk, 8 },
{ ai_walk, 3 },
{ ai_walk, 13 },
{ ai_walk, 9 },
{ ai_walk, 7, shambler_maybe_idle },
{ ai_walk, 5 },
};
MMOVE_T(shambler_move_walk) = { FRAME_walk01, FRAME_walk12, shambler_frames_walk, nullptr };
MONSTERINFO_WALK(shambler_walk) (edict_t* self) -> void
{
M_SetAnimation(self, &shambler_move_walk);
}
//
// run
//
void shambler_run(edict_t* self);
mframe_t shambler_frames_run[] = {
{ ai_run, 20 }, // FIXME: add footsteps?
{ ai_run, 24 },
{ ai_run, 20 },
{ ai_run, 20 },
{ ai_run, 24 },
{ ai_run, 20, shambler_maybe_idle },
};
MMOVE_T(shambler_move_run) = { FRAME_run01, FRAME_run06, shambler_frames_run, nullptr };
MONSTERINFO_RUN(shambler_run) (edict_t* self) -> void
{
if (self->enemy && self->enemy->client)
self->monsterinfo.aiflags |= AI_BRUTAL;
else
self->monsterinfo.aiflags &= ~AI_BRUTAL;
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
{
M_SetAnimation(self, &shambler_move_stand);
return;
}
M_SetAnimation(self, &shambler_move_run);
}
//
// pain
//
// FIXME: needs halved explosion damage
mframe_t shambler_frames_pain[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
};
MMOVE_T(shambler_move_pain) = { FRAME_pain01, FRAME_pain06, shambler_frames_pain, shambler_run };
PAIN(shambler_pain) (edict_t* self, edict_t* other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->timestamp)
return;
self->timestamp = level.time + 1_ms;
gi.sound(self, CHAN_AUTO, sound_pain, 1, ATTN_NORM, 0);
if (mod.id != MOD_CHAINFIST && damage <= 30 && frandom() > 0.2f)
return;
// If hard or nightmare, don't go into pain while attacking
if (skill->integer >= 2)
{
if ((self->s.frame >= FRAME_smash01) && (self->s.frame <= FRAME_smash12))
return;
if ((self->s.frame >= FRAME_swingl01) && (self->s.frame <= FRAME_swingl09))
return;
if ((self->s.frame >= FRAME_swingr01) && (self->s.frame <= FRAME_swingr09))
return;
}
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (level.time < self->pain_debounce_time)
return;
self->pain_debounce_time = level.time + 2_sec;
M_SetAnimation(self, &shambler_move_pain);
}
MONSTERINFO_SETSKIN(shambler_setskin) (edict_t* self) -> void
{
// FIXME: create pain skin?
//if (self->health < (self->max_health / 2))
// self->s.skinnum |= 1;
//else
// self->s.skinnum &= ~1;
}
//
// attacks
//
/*
void() sham_magic3 =[ $magic3, sham_magic4 ] {
ai_face();
self.nextthink = self.nextthink + 0.2;
local entity o;
self.effects = self.effects | EF_MUZZLEFLASH;
ai_face();
self.owner = spawn();
o = self.owner;
setmodel (o, "progs/s_light.mdl");
setorigin (o, self.origin);
o.angles = self.angles;
o.nextthink = time + 0.7;
o.think = SUB_Remove;
};
*/
void ShamblerSaveLoc(edict_t* self)
{
self->pos1 = self->enemy->s.origin; // save for aiming the shot
self->pos1[2] += self->enemy->viewheight;
self->monsterinfo.nextframe = FRAME_magic09;
gi.sound(self, CHAN_WEAPON, sound_boom, 1, ATTN_NORM, 0);
shambler_lightning_update(self);
}
constexpr spawnflags_t SPAWNFLAG_SHAMBLER_PRECISE = 1_spawnflag;
vec3_t FindShamblerOffset(edict_t *self)
{
vec3_t offset = { 0, 0, 48.f };
for (int i = 0; i < 8; i++)
{
if (M_CheckClearShot(self, offset))
return offset;
offset.z -= 4.f;
}
return { 0, 0, 48.f };
}
void ShamblerCastLightning(edict_t* self)
{
if (!self->enemy)
return;
vec3_t start;
vec3_t dir;
vec3_t forward, right;
vec3_t offset = FindShamblerOffset(self);
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, offset, forward, right);
// calc direction to where we targted
PredictAim(self, self->enemy, start, 0, false, self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE) ? 0.f : 0.1f, &dir, nullptr);
vec3_t end = start + (dir * 8192);
trace_t tr = gi.traceline(start, end, self, MASK_PROJECTILE | CONTENTS_SLIME | CONTENTS_LAVA);
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_LIGHTNING);
gi.WriteEntity(self); // source entity
gi.WriteEntity(world); // destination entity
gi.WritePosition(start);
gi.WritePosition(tr.endpos);
gi.multicast(start, MULTICAST_PVS, false);
fire_bullet(self, start, dir, irandom(8, 12), 15, 0, 0, MOD_TESLA);
}
mframe_t shambler_frames_magic[] = {
{ ai_charge, 0, shambler_windup },
{ ai_charge, 0, shambler_lightning_update },
{ ai_charge, 0, shambler_lightning_update },
{ ai_move, 0, shambler_lightning_update },
{ ai_move, 0, shambler_lightning_update },
{ ai_move, 0, ShamblerSaveLoc},
{ ai_move },
{ ai_charge },
{ ai_move, 0, ShamblerCastLightning },
{ ai_move, 0, ShamblerCastLightning },
{ ai_move, 0, ShamblerCastLightning },
{ ai_move },
};
MMOVE_T(shambler_attack_magic) = { FRAME_magic01, FRAME_magic12, shambler_frames_magic, shambler_run };
MONSTERINFO_ATTACK(shambler_attack) (edict_t* self) -> void
{
M_SetAnimation(self, &shambler_attack_magic);
}
//
// melee
//
void shambler_melee1(edict_t* self)
{
gi.sound(self, CHAN_WEAPON, sound_melee1, 1, ATTN_NORM, 0);
}
void shambler_melee2(edict_t* self)
{
gi.sound(self, CHAN_WEAPON, sound_melee2, 1, ATTN_NORM, 0);
}
void sham_swingl9(edict_t* self);
void sham_swingr9(edict_t* self);
void sham_smash10(edict_t* self)
{
if (!self->enemy)
return;
ai_charge(self, 0);
if (!CanDamage(self->enemy, self))
return;
vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
bool hit = fire_hit(self, aim, irandom(110, 120), 120); // Slower attack
if (hit)
gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
// SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right);
// SpawnMeatSpray(self.origin + v_forward * 16, crandom() * 100 * v_right);
};
void ShamClaw(edict_t* self)
{
if (!self->enemy)
return;
ai_charge(self, 10);
if (!CanDamage(self->enemy, self))
return;
vec3_t aim = { MELEE_DISTANCE, self->mins[0], -4 };
bool hit = fire_hit(self, aim, irandom(70, 80), 80); // Slower attack
if (hit)
gi.sound(self, CHAN_WEAPON, sound_smack, 1, ATTN_NORM, 0);
// 250 if left, -250 if right
/*
if (side)
{
makevectorsfixed(self.angles);
SpawnMeatSpray(self.origin + v_forward * 16, side * v_right);
}
*/
};
mframe_t shambler_frames_smash[] = {
{ ai_charge, 2, shambler_melee1 },
{ ai_charge, 6 },
{ ai_charge, 6 },
{ ai_charge, 5 },
{ ai_charge, 4 },
{ ai_charge, 1 },
{ ai_charge, 0 },
{ ai_charge, 0 },
{ ai_charge, 0 },
{ ai_charge, 0, sham_smash10 },
{ ai_charge, 5 },
{ ai_charge, 4 },
};
MMOVE_T(shambler_attack_smash) = { FRAME_smash01, FRAME_smash12, shambler_frames_smash, shambler_run };
mframe_t shambler_frames_swingl[] = {
{ ai_charge, 5, shambler_melee1 },
{ ai_charge, 3 },
{ ai_charge, 7 },
{ ai_charge, 3 },
{ ai_charge, 7 },
{ ai_charge, 9 },
{ ai_charge, 5, ShamClaw },
{ ai_charge, 4 },
{ ai_charge, 8, sham_swingl9 },
};
MMOVE_T(shambler_attack_swingl) = { FRAME_swingl01, FRAME_swingl09, shambler_frames_swingl, shambler_run };
mframe_t shambler_frames_swingr[] = {
{ ai_charge, 1, shambler_melee2 },
{ ai_charge, 8 },
{ ai_charge, 14 },
{ ai_charge, 7 },
{ ai_charge, 3 },
{ ai_charge, 6 },
{ ai_charge, 6, ShamClaw },
{ ai_charge, 3 },
{ ai_charge, 8, sham_swingr9 },
};
MMOVE_T(shambler_attack_swingr) = { FRAME_swingr01, FRAME_swingr09, shambler_frames_swingr, shambler_run };
void sham_swingl9(edict_t* self)
{
ai_charge(self, 8);
if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
M_SetAnimation(self, &shambler_attack_swingr);
}
void sham_swingr9(edict_t* self)
{
ai_charge(self, 1);
ai_charge(self, 10);
if (brandom() && self->enemy && range_to(self, self->enemy) < MELEE_DISTANCE)
M_SetAnimation(self, &shambler_attack_swingl);
}
MONSTERINFO_MELEE(shambler_melee) (edict_t* self) -> void
{
float chance = frandom();
if (chance > 0.6 || self->health == 600)
M_SetAnimation(self, &shambler_attack_smash);
else if (chance > 0.3)
M_SetAnimation(self, &shambler_attack_swingl);
else
M_SetAnimation(self, &shambler_attack_swingr);
}
//
// death
//
void shambler_dead(edict_t* self)
{
self->mins = { -16, -16, -24 };
self->maxs = { 16, 16, -0 };
monster_dead(self);
}
static void shambler_shrink(edict_t* self)
{
self->maxs[2] = 0;
self->svflags |= SVF_DEADMONSTER;
gi.linkentity(self);
}
mframe_t shambler_frames_death[] = {
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0, shambler_shrink },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 },
{ ai_move, 0 }, // FIXME: thud?
};
MMOVE_T(shambler_move_death) = { FRAME_death01, FRAME_death11, shambler_frames_death, shambler_dead };
DIE(shambler_die) (edict_t* self, edict_t* inflictor, edict_t* attacker, int damage, const vec3_t& point, const mod_t &mod) -> void
{
if (self->beam)
{
G_FreeEdict(self->beam);
self->beam = nullptr;
}
if (self->beam2)
{
G_FreeEdict(self->beam2);
self->beam2 = nullptr;
}
// check for gib
if (M_CheckGib(self, mod))
{
gi.sound(self, CHAN_VOICE, gi.soundindex("misc/udeath.wav"), 1, ATTN_NORM, 0);
// FIXME: better gibs for shambler, shambler head
ThrowGibs(self, damage, {
{ "models/objects/gibs/sm_meat/tris.md2" },
{ "models/objects/gibs/chest/tris.md2" },
{ "models/objects/gibs/head2/tris.md2", GIB_HEAD }
});
self->deadflag = true;
return;
}
if (self->deadflag)
return;
// regular death
gi.sound(self, CHAN_VOICE, sound_die, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = true;
M_SetAnimation(self, &shambler_move_death);
}
void SP_monster_shambler(edict_t* self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( self );
return;
}
self->s.modelindex = gi.modelindex("models/monsters/shambler/tris.md2");
self->mins = { -32, -32, -24 };
self->maxs = { 32, 32, 64 };
self->movetype = MOVETYPE_STEP;
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");
self->health = 600 * st.health_multiplier;
self->gib_health = -60;
self->mass = 500;
self->pain = shambler_pain;
self->die = shambler_die;
self->monsterinfo.stand = shambler_stand;
self->monsterinfo.walk = shambler_walk;
self->monsterinfo.run = shambler_run;
self->monsterinfo.dodge = nullptr;
self->monsterinfo.attack = shambler_attack;
self->monsterinfo.melee = shambler_melee;
self->monsterinfo.sight = shambler_sight;
self->monsterinfo.idle = shambler_idle;
self->monsterinfo.blocked = nullptr;
self->monsterinfo.setskin = shambler_setskin;
gi.linkentity(self);
if (self->spawnflags.has(SPAWNFLAG_SHAMBLER_PRECISE))
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
M_SetAnimation(self, &shambler_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
walkmonster_start(self);
}

106
rerelease/m_shambler.h Normal file
View File

@@ -0,0 +1,106 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/tank
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_smash01,
FRAME_smash02,
FRAME_smash03,
FRAME_smash04,
FRAME_smash05,
FRAME_smash06,
FRAME_smash07,
FRAME_smash08,
FRAME_smash09,
FRAME_smash10,
FRAME_smash11,
FRAME_smash12,
FRAME_swingr01,
FRAME_swingr02,
FRAME_swingr03,
FRAME_swingr04,
FRAME_swingr05,
FRAME_swingr06,
FRAME_swingr07,
FRAME_swingr08,
FRAME_swingr09,
FRAME_swingl01,
FRAME_swingl02,
FRAME_swingl03,
FRAME_swingl04,
FRAME_swingl05,
FRAME_swingl06,
FRAME_swingl07,
FRAME_swingl08,
FRAME_swingl09,
FRAME_magic01,
FRAME_magic02,
FRAME_magic03,
FRAME_magic04,
FRAME_magic05,
FRAME_magic06,
FRAME_magic07,
FRAME_magic08,
FRAME_magic09,
FRAME_magic10,
FRAME_magic11,
FRAME_magic12,
FRAME_pain01,
FRAME_pain02,
FRAME_pain03,
FRAME_pain04,
FRAME_pain05,
FRAME_pain06,
FRAME_death01,
FRAME_death02,
FRAME_death03,
FRAME_death04,
FRAME_death05,
FRAME_death06,
FRAME_death07,
FRAME_death08,
FRAME_death09,
FRAME_death10,
FRAME_death11
};
constexpr float MODEL_SCALE = 1.000000f;

2042
rerelease/m_soldier.cpp Normal file

File diff suppressed because it is too large Load Diff

585
rerelease/m_soldier.h Normal file
View File

@@ -0,0 +1,585 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// E:\G Drive\md2f\quake2\baseq2\models/monsters/soldier
// This file generated by qdata - Do NOT Modify
enum {
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_attak214,
FRAME_attak215,
FRAME_attak216,
FRAME_attak217,
FRAME_attak218,
FRAME_attak301,
FRAME_attak302,
FRAME_attak303,
FRAME_attak304,
FRAME_attak305,
FRAME_attak306,
FRAME_attak307,
FRAME_attak308,
FRAME_attak309,
FRAME_attak401,
FRAME_attak402,
FRAME_attak403,
FRAME_attak404,
FRAME_attak405,
FRAME_attak406,
FRAME_duck01,
FRAME_duck02,
FRAME_duck03,
FRAME_duck04,
FRAME_duck05,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain105,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain206,
FRAME_pain207,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_pain310,
FRAME_pain311,
FRAME_pain312,
FRAME_pain313,
FRAME_pain314,
FRAME_pain315,
FRAME_pain316,
FRAME_pain317,
FRAME_pain318,
FRAME_pain401,
FRAME_pain402,
FRAME_pain403,
FRAME_pain404,
FRAME_pain405,
FRAME_pain406,
FRAME_pain407,
FRAME_pain408,
FRAME_pain409,
FRAME_pain410,
FRAME_pain411,
FRAME_pain412,
FRAME_pain413,
FRAME_pain414,
FRAME_pain415,
FRAME_pain416,
FRAME_pain417,
FRAME_run01,
FRAME_run02,
FRAME_run03,
FRAME_run04,
FRAME_run05,
FRAME_run06,
FRAME_run07,
FRAME_run08,
FRAME_run09,
FRAME_run10,
FRAME_run11,
FRAME_run12,
FRAME_runs01,
FRAME_runs02,
FRAME_runs03,
FRAME_runs04,
FRAME_runs05,
FRAME_runs06,
FRAME_runs07,
FRAME_runs08,
FRAME_runs09,
FRAME_runs10,
FRAME_runs11,
FRAME_runs12,
FRAME_runs13,
FRAME_runs14,
FRAME_runs15,
FRAME_runs16,
FRAME_runs17,
FRAME_runs18,
FRAME_runt01,
FRAME_runt02,
FRAME_runt03,
FRAME_runt04,
FRAME_runt05,
FRAME_runt06,
FRAME_runt07,
FRAME_runt08,
FRAME_runt09,
FRAME_runt10,
FRAME_runt11,
FRAME_runt12,
FRAME_runt13,
FRAME_runt14,
FRAME_runt15,
FRAME_runt16,
FRAME_runt17,
FRAME_runt18,
FRAME_runt19,
FRAME_stand101,
FRAME_stand102,
FRAME_stand103,
FRAME_stand104,
FRAME_stand105,
FRAME_stand106,
FRAME_stand107,
FRAME_stand108,
FRAME_stand109,
FRAME_stand110,
FRAME_stand111,
FRAME_stand112,
FRAME_stand113,
FRAME_stand114,
FRAME_stand115,
FRAME_stand116,
FRAME_stand117,
FRAME_stand118,
FRAME_stand119,
FRAME_stand120,
FRAME_stand121,
FRAME_stand122,
FRAME_stand123,
FRAME_stand124,
FRAME_stand125,
FRAME_stand126,
FRAME_stand127,
FRAME_stand128,
FRAME_stand129,
FRAME_stand130,
FRAME_stand301,
FRAME_stand302,
FRAME_stand303,
FRAME_stand304,
FRAME_stand305,
FRAME_stand306,
FRAME_stand307,
FRAME_stand308,
FRAME_stand309,
FRAME_stand310,
FRAME_stand311,
FRAME_stand312,
FRAME_stand313,
FRAME_stand314,
FRAME_stand315,
FRAME_stand316,
FRAME_stand317,
FRAME_stand318,
FRAME_stand319,
FRAME_stand320,
FRAME_stand321,
FRAME_stand322,
FRAME_stand323,
FRAME_stand324,
FRAME_stand325,
FRAME_stand326,
FRAME_stand327,
FRAME_stand328,
FRAME_stand329,
FRAME_stand330,
FRAME_stand331,
FRAME_stand332,
FRAME_stand333,
FRAME_stand334,
FRAME_stand335,
FRAME_stand336,
FRAME_stand337,
FRAME_stand338,
FRAME_stand339,
FRAME_walk101,
FRAME_walk102,
FRAME_walk103,
FRAME_walk104,
FRAME_walk105,
FRAME_walk106,
FRAME_walk107,
FRAME_walk108,
FRAME_walk109,
FRAME_walk110,
FRAME_walk111,
FRAME_walk112,
FRAME_walk113,
FRAME_walk114,
FRAME_walk115,
FRAME_walk116,
FRAME_walk117,
FRAME_walk118,
FRAME_walk119,
FRAME_walk120,
FRAME_walk121,
FRAME_walk122,
FRAME_walk123,
FRAME_walk124,
FRAME_walk125,
FRAME_walk126,
FRAME_walk127,
FRAME_walk128,
FRAME_walk129,
FRAME_walk130,
FRAME_walk131,
FRAME_walk132,
FRAME_walk133,
FRAME_walk201,
FRAME_walk202,
FRAME_walk203,
FRAME_walk204,
FRAME_walk205,
FRAME_walk206,
FRAME_walk207,
FRAME_walk208,
FRAME_walk209,
FRAME_walk210,
FRAME_walk211,
FRAME_walk212,
FRAME_walk213,
FRAME_walk214,
FRAME_walk215,
FRAME_walk216,
FRAME_walk217,
FRAME_walk218,
FRAME_walk219,
FRAME_walk220,
FRAME_walk221,
FRAME_walk222,
FRAME_walk223,
FRAME_walk224,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death108,
FRAME_death109,
FRAME_death110,
FRAME_death111,
FRAME_death112,
FRAME_death113,
FRAME_death114,
FRAME_death115,
FRAME_death116,
FRAME_death117,
FRAME_death118,
FRAME_death119,
FRAME_death120,
FRAME_death121,
FRAME_death122,
FRAME_death123,
FRAME_death124,
FRAME_death125,
FRAME_death126,
FRAME_death127,
FRAME_death128,
FRAME_death129,
FRAME_death130,
FRAME_death131,
FRAME_death132,
FRAME_death133,
FRAME_death134,
FRAME_death135,
FRAME_death136,
FRAME_death201,
FRAME_death202,
FRAME_death203,
FRAME_death204,
FRAME_death205,
FRAME_death206,
FRAME_death207,
FRAME_death208,
FRAME_death209,
FRAME_death210,
FRAME_death211,
FRAME_death212,
FRAME_death213,
FRAME_death214,
FRAME_death215,
FRAME_death216,
FRAME_death217,
FRAME_death218,
FRAME_death219,
FRAME_death220,
FRAME_death221,
FRAME_death222,
FRAME_death223,
FRAME_death224,
FRAME_death225,
FRAME_death226,
FRAME_death227,
FRAME_death228,
FRAME_death229,
FRAME_death230,
FRAME_death231,
FRAME_death232,
FRAME_death233,
FRAME_death234,
FRAME_death235,
FRAME_death301,
FRAME_death302,
FRAME_death303,
FRAME_death304,
FRAME_death305,
FRAME_death306,
FRAME_death307,
FRAME_death308,
FRAME_death309,
FRAME_death310,
FRAME_death311,
FRAME_death312,
FRAME_death313,
FRAME_death314,
FRAME_death315,
FRAME_death316,
FRAME_death317,
FRAME_death318,
FRAME_death319,
FRAME_death320,
FRAME_death321,
FRAME_death322,
FRAME_death323,
FRAME_death324,
FRAME_death325,
FRAME_death326,
FRAME_death327,
FRAME_death328,
FRAME_death329,
FRAME_death330,
FRAME_death331,
FRAME_death332,
FRAME_death333,
FRAME_death334,
FRAME_death335,
FRAME_death336,
FRAME_death337,
FRAME_death338,
FRAME_death339,
FRAME_death340,
FRAME_death341,
FRAME_death342,
FRAME_death343,
FRAME_death344,
FRAME_death345,
FRAME_death401,
FRAME_death402,
FRAME_death403,
FRAME_death404,
FRAME_death405,
FRAME_death406,
FRAME_death407,
FRAME_death408,
FRAME_death409,
FRAME_death410,
FRAME_death411,
FRAME_death412,
FRAME_death413,
FRAME_death414,
FRAME_death415,
FRAME_death416,
FRAME_death417,
FRAME_death418,
FRAME_death419,
FRAME_death420,
FRAME_death421,
FRAME_death422,
FRAME_death423,
FRAME_death424,
FRAME_death425,
FRAME_death426,
FRAME_death427,
FRAME_death428,
FRAME_death429,
FRAME_death430,
FRAME_death431,
FRAME_death432,
FRAME_death433,
FRAME_death434,
FRAME_death435,
FRAME_death436,
FRAME_death437,
FRAME_death438,
FRAME_death439,
FRAME_death440,
FRAME_death441,
FRAME_death442,
FRAME_death443,
FRAME_death444,
FRAME_death445,
FRAME_death446,
FRAME_death447,
FRAME_death448,
FRAME_death449,
FRAME_death450,
FRAME_death451,
FRAME_death452,
FRAME_death453,
FRAME_death501,
FRAME_death502,
FRAME_death503,
FRAME_death504,
FRAME_death505,
FRAME_death506,
FRAME_death507,
FRAME_death508,
FRAME_death509,
FRAME_death510,
FRAME_death511,
FRAME_death512,
FRAME_death513,
FRAME_death514,
FRAME_death515,
FRAME_death516,
FRAME_death517,
FRAME_death518,
FRAME_death519,
FRAME_death520,
FRAME_death521,
FRAME_death522,
FRAME_death523,
FRAME_death524,
FRAME_death601,
FRAME_death602,
FRAME_death603,
FRAME_death604,
FRAME_death605,
FRAME_death606,
FRAME_death607,
FRAME_death608,
FRAME_death609,
FRAME_death610,
FRAME_stand401,
FRAME_stand402,
FRAME_stand403,
FRAME_stand404,
FRAME_stand405,
FRAME_stand406,
FRAME_stand407,
FRAME_stand408,
FRAME_stand409,
FRAME_stand410,
FRAME_stand411,
FRAME_stand412,
FRAME_stand413,
FRAME_stand414,
FRAME_stand415,
FRAME_stand416,
FRAME_stand417,
FRAME_stand418,
FRAME_stand419,
FRAME_stand420,
FRAME_stand421,
FRAME_stand422,
FRAME_stand423,
FRAME_stand424,
FRAME_stand425,
FRAME_stand426,
FRAME_stand427,
FRAME_stand428,
FRAME_stand429,
FRAME_stand430,
FRAME_stand431,
FRAME_stand432,
FRAME_stand433,
FRAME_stand434,
FRAME_stand435,
FRAME_stand436,
FRAME_stand437,
FRAME_stand438,
FRAME_stand439,
FRAME_stand440,
FRAME_stand441,
FRAME_stand442,
FRAME_stand443,
FRAME_stand444,
FRAME_stand445,
FRAME_stand446,
FRAME_stand447,
FRAME_stand448,
FRAME_stand449,
FRAME_stand450,
FRAME_stand451,
FRAME_stand452,
FRAME_stand201,
FRAME_stand202,
FRAME_stand203,
FRAME_stand204,
FRAME_stand205,
FRAME_stand206,
FRAME_stand207,
FRAME_stand208,
FRAME_stand209,
FRAME_stand210,
FRAME_stand211,
FRAME_stand212,
FRAME_stand213,
FRAME_stand214,
FRAME_stand215,
FRAME_stand216,
FRAME_stand217,
FRAME_stand218,
FRAME_stand219,
FRAME_stand220,
FRAME_stand221,
FRAME_stand222,
FRAME_stand223,
FRAME_stand224,
FRAME_stand225,
FRAME_stand226,
FRAME_stand227,
FRAME_stand228,
FRAME_stand229,
FRAME_stand230,
FRAME_stand231,
FRAME_stand232,
FRAME_stand233,
FRAME_stand234,
FRAME_stand235,
FRAME_stand236,
FRAME_stand237,
FRAME_stand238,
FRAME_stand239,
FRAME_stand240,
FRAME_attak501,
FRAME_attak502,
FRAME_attak503,
FRAME_attak504,
FRAME_attak505,
FRAME_attak506,
FRAME_attak507,
FRAME_attak508
};
constexpr float MODEL_SCALE = 1.2f;

731
rerelease/m_supertank.cpp Normal file
View File

@@ -0,0 +1,731 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
/*
==============================================================================
SUPERTANK
==============================================================================
*/
#include "g_local.h"
#include "m_supertank.h"
#include "m_flash.h"
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 int tread_sound;
void TreadSound(edict_t *self)
{
gi.sound(self, CHAN_BODY, tread_sound, 1, ATTN_NORM, 0);
}
MONSTERINFO_SEARCH(supertank_search) (edict_t *self) -> void
{
if (frandom() < 0.5f)
gi.sound(self, CHAN_VOICE, sound_search1, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_search2, 1, ATTN_NORM, 0);
}
void supertank_dead(edict_t *self);
void supertankRocket(edict_t *self);
void supertankMachineGun(edict_t *self);
void supertank_reattack1(edict_t *self);
//
// stand
//
mframe_t supertank_frames_stand[] = {
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand },
{ ai_stand }
};
MMOVE_T(supertank_move_stand) = { FRAME_stand_1, FRAME_stand_60, supertank_frames_stand, nullptr };
MONSTERINFO_STAND(supertank_stand) (edict_t *self) -> void
{
M_SetAnimation(self, &supertank_move_stand);
}
mframe_t supertank_frames_run[] = {
{ ai_run, 12, TreadSound },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 },
{ ai_run, 12 }
};
MMOVE_T(supertank_move_run) = { FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_run, nullptr };
//
// walk
//
mframe_t supertank_frames_forward[] = {
{ ai_walk, 4, TreadSound },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 },
{ ai_walk, 4 }
};
MMOVE_T(supertank_move_forward) = { FRAME_forwrd_1, FRAME_forwrd_18, supertank_frames_forward, nullptr };
void supertank_forward(edict_t *self)
{
M_SetAnimation(self, &supertank_move_forward);
}
MONSTERINFO_WALK(supertank_walk) (edict_t *self) -> void
{
M_SetAnimation(self, &supertank_move_forward);
}
MONSTERINFO_RUN(supertank_run) (edict_t *self) -> void
{
if (self->monsterinfo.aiflags & AI_STAND_GROUND)
M_SetAnimation(self, &supertank_move_stand);
else
M_SetAnimation(self, &supertank_move_run);
}
#if 0
mframe_t supertank_frames_turn_right[] = {
{ ai_move, 0, TreadSound },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_turn_right) = { FRAME_right_1, FRAME_right_18, supertank_frames_turn_right, supertank_run };
mframe_t supertank_frames_turn_left[] = {
{ ai_move, 0, TreadSound },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_turn_left) = { FRAME_left_1, FRAME_left_18, supertank_frames_turn_left, supertank_run };
#endif
mframe_t supertank_frames_pain3[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_pain3) = { FRAME_pain3_9, FRAME_pain3_12, supertank_frames_pain3, supertank_run };
mframe_t supertank_frames_pain2[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_pain2) = { FRAME_pain2_5, FRAME_pain2_8, supertank_frames_pain2, supertank_run };
mframe_t supertank_frames_pain1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_pain1) = { FRAME_pain1_1, FRAME_pain1_4, supertank_frames_pain1, supertank_run };
static void BossLoop(edict_t *self)
{
if (!(self->spawnflags & SPAWNFLAG_SUPERTANK_LONG_DEATH))
return;
if (self->count)
self->count--;
else
self->spawnflags &= ~SPAWNFLAG_SUPERTANK_LONG_DEATH;
self->monsterinfo.nextframe = FRAME_death_19;
}
static void supertankGrenade(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
monster_muzzleflash_id_t flash_number;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
if (self->s.frame == FRAME_attak4_1)
flash_number = MZ2_SUPERTANK_GRENADE_1;
else
flash_number = MZ2_SUPERTANK_GRENADE_2;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
vec3_t aim_point;
PredictAim(self, self->enemy, start, 0, false, crandom_open() * 0.1f, &forward, &aim_point);
for (float speed = 500.f; speed < 1000.f; speed += 100.f)
{
if (!M_CalculatePitchToFire(self, aim_point, start, forward, speed, 2.5f, true))
continue;
monster_fire_grenade(self, start, forward, 50, speed, flash_number, 0.f, 0.f);
break;
}
}
mframe_t supertank_frames_death1[] = {
{ ai_move, 0, BossExplode },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move, 0, BossLoop }
};
MMOVE_T(supertank_move_death) = { FRAME_death_1, FRAME_death_24, supertank_frames_death1, supertank_dead };
mframe_t supertank_frames_attack4[] = {
{ ai_move, 0, supertankGrenade },
{ ai_move },
{ ai_move },
{ ai_move, 0, supertankGrenade },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_attack4) = { FRAME_attak4_1, FRAME_attak4_6, supertank_frames_attack4, supertank_run };
mframe_t supertank_frames_attack2[] = {
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, supertankRocket },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, supertankRocket },
{ ai_charge },
{ ai_charge },
{ ai_charge, 0, supertankRocket },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_charge },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_attack2) = { FRAME_attak2_1, FRAME_attak2_27, supertank_frames_attack2, supertank_run };
mframe_t supertank_frames_attack1[] = {
{ ai_charge, 0, supertankMachineGun },
{ ai_charge, 0, supertankMachineGun },
{ ai_charge, 0, supertankMachineGun },
{ ai_charge, 0, supertankMachineGun },
{ ai_charge, 0, supertankMachineGun },
{ ai_charge, 0, supertankMachineGun },
};
MMOVE_T(supertank_move_attack1) = { FRAME_attak1_1, FRAME_attak1_6, supertank_frames_attack1, supertank_reattack1 };
mframe_t supertank_frames_end_attack1[] = {
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move },
{ ai_move }
};
MMOVE_T(supertank_move_end_attack1) = { FRAME_attak1_7, FRAME_attak1_20, supertank_frames_end_attack1, supertank_run };
void supertank_reattack1(edict_t *self)
{
if (visible(self, self->enemy))
{
if (self->timestamp >= level.time || frandom() < 0.3f)
M_SetAnimation(self, &supertank_move_attack1);
else
M_SetAnimation(self, &supertank_move_end_attack1);
}
else
M_SetAnimation(self, &supertank_move_end_attack1);
}
PAIN(supertank_pain) (edict_t *self, edict_t *other, float kick, int damage, const mod_t &mod) -> void
{
if (level.time < self->pain_debounce_time)
return;
// Lessen the chance of him going into his pain frames
if (mod.id != MOD_CHAINFIST)
{
if (damage <= 25)
if (frandom() < 0.2f)
return;
// Don't go into pain if he's firing his rockets
if ((self->s.frame >= FRAME_attak2_1) && (self->s.frame <= FRAME_attak2_14))
return;
}
if (damage <= 10)
gi.sound(self, CHAN_VOICE, sound_pain1, 1, ATTN_NORM, 0);
else if (damage <= 25)
gi.sound(self, CHAN_VOICE, sound_pain3, 1, ATTN_NORM, 0);
else
gi.sound(self, CHAN_VOICE, sound_pain2, 1, ATTN_NORM, 0);
self->pain_debounce_time = level.time + 3_sec;
if (!M_ShouldReactToPain(self, mod))
return; // no pain anims in nightmare
if (damage <= 10)
M_SetAnimation(self, &supertank_move_pain1);
else if (damage <= 25)
M_SetAnimation(self, &supertank_move_pain2);
else
M_SetAnimation(self, &supertank_move_pain3);
}
MONSTERINFO_SETSKIN(supertank_setskin) (edict_t *self) -> void
{
if (self->health < (self->max_health / 2))
self->s.skinnum |= 1;
else
self->s.skinnum &= ~1;
}
void supertankRocket(edict_t *self)
{
vec3_t forward, right;
vec3_t start;
vec3_t dir;
vec3_t vec;
monster_muzzleflash_id_t flash_number;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
if (self->s.frame == FRAME_attak2_8)
flash_number = MZ2_SUPERTANK_ROCKET_1;
else if (self->s.frame == FRAME_attak2_11)
flash_number = MZ2_SUPERTANK_ROCKET_2;
else // (self->s.frame == FRAME_attak2_14)
flash_number = MZ2_SUPERTANK_ROCKET_3;
AngleVectors(self->s.angles, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
if (self->spawnflags.has(SPAWNFLAG_SUPERTANK_POWERSHIELD))
{
vec = self->enemy->s.origin;
vec[2] += self->enemy->viewheight;
dir = vec - start;
dir.normalize();
monster_fire_heat(self, start, dir, 40, 500, flash_number, 0.075f);
}
else
{
PredictAim(self, self->enemy, start, 750, false, 0.f, &forward, nullptr);
monster_fire_rocket(self, start, forward, 50, 750, flash_number);
}
}
void supertankMachineGun(edict_t *self)
{
vec3_t dir;
vec3_t start;
vec3_t forward, right;
monster_muzzleflash_id_t flash_number;
if (!self->enemy || !self->enemy->inuse) // PGM
return; // PGM
flash_number = static_cast<monster_muzzleflash_id_t>(MZ2_SUPERTANK_MACHINEGUN_1 + (self->s.frame - FRAME_attak1_1));
dir[0] = 0;
dir[1] = self->s.angles[1];
dir[2] = 0;
AngleVectors(dir, forward, right, nullptr);
start = M_ProjectFlashSource(self, monster_flash_offset[flash_number], forward, right);
PredictAim(self, self->enemy, start, 0, true, -0.1f, &forward, nullptr);
monster_fire_bullet(self, start, forward, 6, 4, DEFAULT_BULLET_HSPREAD * 3, DEFAULT_BULLET_VSPREAD * 3, flash_number);
}
MONSTERINFO_ATTACK(supertank_attack) (edict_t *self) -> void
{
vec3_t vec;
float range;
vec = self->enemy->s.origin - self->s.origin;
range = range_to(self, self->enemy);
// Attack 1 == Chaingun
// Attack 2 == Rocket Launcher
// Attack 3 == Grenade Launcher
bool chaingun_good = M_CheckClearShot(self, monster_flash_offset[MZ2_SUPERTANK_MACHINEGUN_1]);
bool rocket_good = M_CheckClearShot(self, monster_flash_offset[MZ2_SUPERTANK_ROCKET_1]);
bool grenade_good = M_CheckClearShot(self, monster_flash_offset[MZ2_SUPERTANK_GRENADE_1]);
// fire rockets more often at distance
if (chaingun_good && (!rocket_good || range <= 540 || frandom() < 0.3f))
{
// prefer grenade if the enemy is above us
if (grenade_good && (range >= 350 || vec.z > 120.f || frandom() < 0.2f))
M_SetAnimation(self, &supertank_move_attack4);
else
{
M_SetAnimation(self, &supertank_move_attack1);
self->timestamp = level.time + random_time(1500_ms, 2700_ms);
}
}
else if (rocket_good)
{
// prefer grenade if the enemy is above us
if (grenade_good && (vec.z > 120.f || frandom() < 0.2f))
M_SetAnimation(self, &supertank_move_attack4);
else
M_SetAnimation(self, &supertank_move_attack2);
}
else if (grenade_good)
M_SetAnimation(self, &supertank_move_attack4);
}
//
// death
//
static void supertank_gib(edict_t *self)
{
gi.WriteByte(svc_temp_entity);
gi.WriteByte(TE_EXPLOSION1_BIG);
gi.WritePosition(self->s.origin);
gi.multicast(self->s.origin, MULTICAST_PHS, false);
self->s.sound = 0;
self->s.skinnum /= 2;
ThrowGibs(self, 500, {
{ 2, "models/objects/gibs/sm_meat/tris.md2" },
{ 2, "models/objects/gibs/sm_metal/tris.md2", GIB_METALLIC },
{ "models/monsters/boss1/gibs/cgun.md2", GIB_SKINNED | GIB_METALLIC },
{ "models/monsters/boss1/gibs/chest.md2", GIB_SKINNED },
{ "models/monsters/boss1/gibs/core.md2", GIB_SKINNED },
{ "models/monsters/boss1/gibs/ltread.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss1/gibs/rgun.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss1/gibs/rtread.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss1/gibs/tube.md2", GIB_SKINNED | GIB_UPRIGHT },
{ "models/monsters/boss1/gibs/head.md2", GIB_SKINNED | GIB_METALLIC | GIB_HEAD }
});
}
void supertank_dead(edict_t *self)
{
// no blowy on deady
if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD))
{
self->deadflag = false;
self->takedamage = true;
return;
}
supertank_gib(self);
}
DIE(supertank_die) (edict_t *self, edict_t *inflictor, edict_t *attacker, int damage, const vec3_t &point, const mod_t &mod) -> void
{
if (self->spawnflags.has(SPAWNFLAG_MONSTER_DEAD))
{
// check for gib
if (M_CheckGib(self, mod))
{
supertank_gib(self);
self->deadflag = true;
return;
}
if (self->deadflag)
return;
}
else
{
gi.sound(self, CHAN_VOICE, sound_death, 1, ATTN_NORM, 0);
self->deadflag = true;
self->takedamage = false;
}
M_SetAnimation(self, &supertank_move_death);
}
//===========
// PGM
MONSTERINFO_BLOCKED(supertank_blocked) (edict_t *self, float dist) -> bool
{
if (blocked_checkplat(self, dist))
return true;
return false;
}
// PGM
//===========
//
// monster_supertank
//
// RAFAEL (Powershield)
/*QUAKED monster_supertank (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight Powershield LongDeath
*/
void SP_monster_supertank(edict_t *self)
{
if ( !M_AllowSpawn( self ) ) {
G_FreeEdict( 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");
tread_sound = gi.soundindex("bosstank/btkengn1.wav");
gi.soundindex("gunner/gunatck3.wav");
gi.soundindex("infantry/infatck1.wav");
gi.soundindex("tank/rocket.wav");
self->movetype = MOVETYPE_STEP;
self->solid = SOLID_BBOX;
self->s.modelindex = gi.modelindex("models/monsters/boss1/tris.md2");
gi.modelindex("models/monsters/boss1/gibs/cgun.md2");
gi.modelindex("models/monsters/boss1/gibs/chest.md2");
gi.modelindex("models/monsters/boss1/gibs/core.md2");
gi.modelindex("models/monsters/boss1/gibs/head.md2");
gi.modelindex("models/monsters/boss1/gibs/ltread.md2");
gi.modelindex("models/monsters/boss1/gibs/rgun.md2");
gi.modelindex("models/monsters/boss1/gibs/rtread.md2");
gi.modelindex("models/monsters/boss1/gibs/tube.md2");
self->mins = { -64, -64, 0 };
self->maxs = { 64, 64, 112 };
self->health = 1500 * st.health_multiplier;
self->gib_health = -500;
self->mass = 800;
self->pain = supertank_pain;
self->die = supertank_die;
self->monsterinfo.stand = supertank_stand;
self->monsterinfo.walk = supertank_walk;
self->monsterinfo.run = supertank_run;
self->monsterinfo.dodge = nullptr;
self->monsterinfo.attack = supertank_attack;
self->monsterinfo.search = supertank_search;
self->monsterinfo.melee = nullptr;
self->monsterinfo.sight = nullptr;
self->monsterinfo.blocked = supertank_blocked; // PGM
self->monsterinfo.setskin = supertank_setskin;
gi.linkentity(self);
M_SetAnimation(self, &supertank_move_stand);
self->monsterinfo.scale = MODEL_SCALE;
// RAFAEL
if (self->spawnflags.has(SPAWNFLAG_SUPERTANK_POWERSHIELD))
{
if (!st.was_key_specified("power_armor_type"))
self->monsterinfo.power_armor_type = IT_ITEM_POWER_SHIELD;
if (!st.was_key_specified("power_armor_power"))
self->monsterinfo.power_armor_power = 400;
}
// RAFAEL
walkmonster_start(self);
// PMM
self->monsterinfo.aiflags |= AI_IGNORE_SHOTS;
// pmm
// TODO
if (level.is_n64)
{
self->spawnflags |= SPAWNFLAG_SUPERTANK_LONG_DEATH;
self->count = 10;
}
}
//
// monster_boss5
// RAFAEL
//
/*QUAKED monster_boss5 (1 .5 0) (-64 -64 0) (64 64 72) Ambush Trigger_Spawn Sight
*/
void SP_monster_boss5(edict_t *self)
{
self->spawnflags |= SPAWNFLAG_SUPERTANK_POWERSHIELD;
SP_monster_supertank(self);
gi.soundindex("weapons/railgr1a.wav");
self->s.skinnum = 2;
}

265
rerelease/m_supertank.h Normal file
View File

@@ -0,0 +1,265 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/boss1/backup
// This file generated by ModelGen - Do NOT Modify
enum
{
FRAME_attak1_1,
FRAME_attak1_2,
FRAME_attak1_3,
FRAME_attak1_4,
FRAME_attak1_5,
FRAME_attak1_6,
FRAME_attak1_7,
FRAME_attak1_8,
FRAME_attak1_9,
FRAME_attak1_10,
FRAME_attak1_11,
FRAME_attak1_12,
FRAME_attak1_13,
FRAME_attak1_14,
FRAME_attak1_15,
FRAME_attak1_16,
FRAME_attak1_17,
FRAME_attak1_18,
FRAME_attak1_19,
FRAME_attak1_20,
FRAME_attak2_1,
FRAME_attak2_2,
FRAME_attak2_3,
FRAME_attak2_4,
FRAME_attak2_5,
FRAME_attak2_6,
FRAME_attak2_7,
FRAME_attak2_8,
FRAME_attak2_9,
FRAME_attak2_10,
FRAME_attak2_11,
FRAME_attak2_12,
FRAME_attak2_13,
FRAME_attak2_14,
FRAME_attak2_15,
FRAME_attak2_16,
FRAME_attak2_17,
FRAME_attak2_18,
FRAME_attak2_19,
FRAME_attak2_20,
FRAME_attak2_21,
FRAME_attak2_22,
FRAME_attak2_23,
FRAME_attak2_24,
FRAME_attak2_25,
FRAME_attak2_26,
FRAME_attak2_27,
FRAME_attak3_1,
FRAME_attak3_2,
FRAME_attak3_3,
FRAME_attak3_4,
FRAME_attak3_5,
FRAME_attak3_6,
FRAME_attak3_7,
FRAME_attak3_8,
FRAME_attak3_9,
FRAME_attak3_10,
FRAME_attak3_11,
FRAME_attak3_12,
FRAME_attak3_13,
FRAME_attak3_14,
FRAME_attak3_15,
FRAME_attak3_16,
FRAME_attak3_17,
FRAME_attak3_18,
FRAME_attak3_19,
FRAME_attak3_20,
FRAME_attak3_21,
FRAME_attak3_22,
FRAME_attak3_23,
FRAME_attak3_24,
FRAME_attak3_25,
FRAME_attak3_26,
FRAME_attak3_27,
FRAME_attak4_1,
FRAME_attak4_2,
FRAME_attak4_3,
FRAME_attak4_4,
FRAME_attak4_5,
FRAME_attak4_6,
FRAME_backwd_1,
FRAME_backwd_2,
FRAME_backwd_3,
FRAME_backwd_4,
FRAME_backwd_5,
FRAME_backwd_6,
FRAME_backwd_7,
FRAME_backwd_8,
FRAME_backwd_9,
FRAME_backwd_10,
FRAME_backwd_11,
FRAME_backwd_12,
FRAME_backwd_13,
FRAME_backwd_14,
FRAME_backwd_15,
FRAME_backwd_16,
FRAME_backwd_17,
FRAME_backwd_18,
FRAME_death_1,
FRAME_death_2,
FRAME_death_3,
FRAME_death_4,
FRAME_death_5,
FRAME_death_6,
FRAME_death_7,
FRAME_death_8,
FRAME_death_9,
FRAME_death_10,
FRAME_death_11,
FRAME_death_12,
FRAME_death_13,
FRAME_death_14,
FRAME_death_15,
FRAME_death_16,
FRAME_death_17,
FRAME_death_18,
FRAME_death_19,
FRAME_death_20,
FRAME_death_21,
FRAME_death_22,
FRAME_death_23,
FRAME_death_24,
FRAME_death_31,
FRAME_death_32,
FRAME_death_33,
FRAME_death_45,
FRAME_death_46,
FRAME_death_47,
FRAME_forwrd_1,
FRAME_forwrd_2,
FRAME_forwrd_3,
FRAME_forwrd_4,
FRAME_forwrd_5,
FRAME_forwrd_6,
FRAME_forwrd_7,
FRAME_forwrd_8,
FRAME_forwrd_9,
FRAME_forwrd_10,
FRAME_forwrd_11,
FRAME_forwrd_12,
FRAME_forwrd_13,
FRAME_forwrd_14,
FRAME_forwrd_15,
FRAME_forwrd_16,
FRAME_forwrd_17,
FRAME_forwrd_18,
FRAME_left_1,
FRAME_left_2,
FRAME_left_3,
FRAME_left_4,
FRAME_left_5,
FRAME_left_6,
FRAME_left_7,
FRAME_left_8,
FRAME_left_9,
FRAME_left_10,
FRAME_left_11,
FRAME_left_12,
FRAME_left_13,
FRAME_left_14,
FRAME_left_15,
FRAME_left_16,
FRAME_left_17,
FRAME_left_18,
FRAME_pain1_1,
FRAME_pain1_2,
FRAME_pain1_3,
FRAME_pain1_4,
FRAME_pain2_5,
FRAME_pain2_6,
FRAME_pain2_7,
FRAME_pain2_8,
FRAME_pain3_9,
FRAME_pain3_10,
FRAME_pain3_11,
FRAME_pain3_12,
FRAME_right_1,
FRAME_right_2,
FRAME_right_3,
FRAME_right_4,
FRAME_right_5,
FRAME_right_6,
FRAME_right_7,
FRAME_right_8,
FRAME_right_9,
FRAME_right_10,
FRAME_right_11,
FRAME_right_12,
FRAME_right_13,
FRAME_right_14,
FRAME_right_15,
FRAME_right_16,
FRAME_right_17,
FRAME_right_18,
FRAME_stand_1,
FRAME_stand_2,
FRAME_stand_3,
FRAME_stand_4,
FRAME_stand_5,
FRAME_stand_6,
FRAME_stand_7,
FRAME_stand_8,
FRAME_stand_9,
FRAME_stand_10,
FRAME_stand_11,
FRAME_stand_12,
FRAME_stand_13,
FRAME_stand_14,
FRAME_stand_15,
FRAME_stand_16,
FRAME_stand_17,
FRAME_stand_18,
FRAME_stand_19,
FRAME_stand_20,
FRAME_stand_21,
FRAME_stand_22,
FRAME_stand_23,
FRAME_stand_24,
FRAME_stand_25,
FRAME_stand_26,
FRAME_stand_27,
FRAME_stand_28,
FRAME_stand_29,
FRAME_stand_30,
FRAME_stand_31,
FRAME_stand_32,
FRAME_stand_33,
FRAME_stand_34,
FRAME_stand_35,
FRAME_stand_36,
FRAME_stand_37,
FRAME_stand_38,
FRAME_stand_39,
FRAME_stand_40,
FRAME_stand_41,
FRAME_stand_42,
FRAME_stand_43,
FRAME_stand_44,
FRAME_stand_45,
FRAME_stand_46,
FRAME_stand_47,
FRAME_stand_48,
FRAME_stand_49,
FRAME_stand_50,
FRAME_stand_51,
FRAME_stand_52,
FRAME_stand_53,
FRAME_stand_54,
FRAME_stand_55,
FRAME_stand_56,
FRAME_stand_57,
FRAME_stand_58,
FRAME_stand_59,
FRAME_stand_60
};
constexpr float MODEL_SCALE = 1.000000f;

1162
rerelease/m_tank.cpp Normal file

File diff suppressed because it is too large Load Diff

305
rerelease/m_tank.h Normal file
View File

@@ -0,0 +1,305 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
// G:\quake2\baseq2\models/monsters/tank
// This file generated by qdata - Do NOT Modify
enum
{
FRAME_stand01,
FRAME_stand02,
FRAME_stand03,
FRAME_stand04,
FRAME_stand05,
FRAME_stand06,
FRAME_stand07,
FRAME_stand08,
FRAME_stand09,
FRAME_stand10,
FRAME_stand11,
FRAME_stand12,
FRAME_stand13,
FRAME_stand14,
FRAME_stand15,
FRAME_stand16,
FRAME_stand17,
FRAME_stand18,
FRAME_stand19,
FRAME_stand20,
FRAME_stand21,
FRAME_stand22,
FRAME_stand23,
FRAME_stand24,
FRAME_stand25,
FRAME_stand26,
FRAME_stand27,
FRAME_stand28,
FRAME_stand29,
FRAME_stand30,
FRAME_walk01,
FRAME_walk02,
FRAME_walk03,
FRAME_walk04,
FRAME_walk05,
FRAME_walk06,
FRAME_walk07,
FRAME_walk08,
FRAME_walk09,
FRAME_walk10,
FRAME_walk11,
FRAME_walk12,
FRAME_walk13,
FRAME_walk14,
FRAME_walk15,
FRAME_walk16,
FRAME_walk17,
FRAME_walk18,
FRAME_walk19,
FRAME_walk20,
FRAME_walk21,
FRAME_walk22,
FRAME_walk23,
FRAME_walk24,
FRAME_walk25,
FRAME_attak101,
FRAME_attak102,
FRAME_attak103,
FRAME_attak104,
FRAME_attak105,
FRAME_attak106,
FRAME_attak107,
FRAME_attak108,
FRAME_attak109,
FRAME_attak110,
FRAME_attak111,
FRAME_attak112,
FRAME_attak113,
FRAME_attak114,
FRAME_attak115,
FRAME_attak116,
FRAME_attak117,
FRAME_attak118,
FRAME_attak119,
FRAME_attak120,
FRAME_attak121,
FRAME_attak122,
FRAME_attak201,
FRAME_attak202,
FRAME_attak203,
FRAME_attak204,
FRAME_attak205,
FRAME_attak206,
FRAME_attak207,
FRAME_attak208,
FRAME_attak209,
FRAME_attak210,
FRAME_attak211,
FRAME_attak212,
FRAME_attak213,
FRAME_attak214,
FRAME_attak215,
FRAME_attak216,
FRAME_attak217,
FRAME_attak218,
FRAME_attak219,
FRAME_attak220,
FRAME_attak221,
FRAME_attak222,
FRAME_attak223,
FRAME_attak224,
FRAME_attak225,
FRAME_attak226,
FRAME_attak227,
FRAME_attak228,
FRAME_attak229,
FRAME_attak230,
FRAME_attak231,
FRAME_attak232,
FRAME_attak233,
FRAME_attak234,
FRAME_attak235,
FRAME_attak236,
FRAME_attak237,
FRAME_attak238,
FRAME_attak301,
FRAME_attak302,
FRAME_attak303,
FRAME_attak304,
FRAME_attak305,
FRAME_attak306,
FRAME_attak307,
FRAME_attak308,
FRAME_attak309,
FRAME_attak310,
FRAME_attak311,
FRAME_attak312,
FRAME_attak313,
FRAME_attak314,
FRAME_attak315,
FRAME_attak316,
FRAME_attak317,
FRAME_attak318,
FRAME_attak319,
FRAME_attak320,
FRAME_attak321,
FRAME_attak322,
FRAME_attak323,
FRAME_attak324,
FRAME_attak325,
FRAME_attak326,
FRAME_attak327,
FRAME_attak328,
FRAME_attak329,
FRAME_attak330,
FRAME_attak331,
FRAME_attak332,
FRAME_attak333,
FRAME_attak334,
FRAME_attak335,
FRAME_attak336,
FRAME_attak337,
FRAME_attak338,
FRAME_attak339,
FRAME_attak340,
FRAME_attak341,
FRAME_attak342,
FRAME_attak343,
FRAME_attak344,
FRAME_attak345,
FRAME_attak346,
FRAME_attak347,
FRAME_attak348,
FRAME_attak349,
FRAME_attak350,
FRAME_attak351,
FRAME_attak352,
FRAME_attak353,
FRAME_attak401,
FRAME_attak402,
FRAME_attak403,
FRAME_attak404,
FRAME_attak405,
FRAME_attak406,
FRAME_attak407,
FRAME_attak408,
FRAME_attak409,
FRAME_attak410,
FRAME_attak411,
FRAME_attak412,
FRAME_attak413,
FRAME_attak414,
FRAME_attak415,
FRAME_attak416,
FRAME_attak417,
FRAME_attak418,
FRAME_attak419,
FRAME_attak420,
FRAME_attak421,
FRAME_attak422,
FRAME_attak423,
FRAME_attak424,
FRAME_attak425,
FRAME_attak426,
FRAME_attak427,
FRAME_attak428,
FRAME_attak429,
FRAME_pain101,
FRAME_pain102,
FRAME_pain103,
FRAME_pain104,
FRAME_pain201,
FRAME_pain202,
FRAME_pain203,
FRAME_pain204,
FRAME_pain205,
FRAME_pain301,
FRAME_pain302,
FRAME_pain303,
FRAME_pain304,
FRAME_pain305,
FRAME_pain306,
FRAME_pain307,
FRAME_pain308,
FRAME_pain309,
FRAME_pain310,
FRAME_pain311,
FRAME_pain312,
FRAME_pain313,
FRAME_pain314,
FRAME_pain315,
FRAME_pain316,
FRAME_death101,
FRAME_death102,
FRAME_death103,
FRAME_death104,
FRAME_death105,
FRAME_death106,
FRAME_death107,
FRAME_death108,
FRAME_death109,
FRAME_death110,
FRAME_death111,
FRAME_death112,
FRAME_death113,
FRAME_death114,
FRAME_death115,
FRAME_death116,
FRAME_death117,
FRAME_death118,
FRAME_death119,
FRAME_death120,
FRAME_death121,
FRAME_death122,
FRAME_death123,
FRAME_death124,
FRAME_death125,
FRAME_death126,
FRAME_death127,
FRAME_death128,
FRAME_death129,
FRAME_death130,
FRAME_death131,
FRAME_death132,
FRAME_recln101,
FRAME_recln102,
FRAME_recln103,
FRAME_recln104,
FRAME_recln105,
FRAME_recln106,
FRAME_recln107,
FRAME_recln108,
FRAME_recln109,
FRAME_recln110,
FRAME_recln111,
FRAME_recln112,
FRAME_recln113,
FRAME_recln114,
FRAME_recln115,
FRAME_recln116,
FRAME_recln117,
FRAME_recln118,
FRAME_recln119,
FRAME_recln120,
FRAME_recln121,
FRAME_recln122,
FRAME_recln123,
FRAME_recln124,
FRAME_recln125,
FRAME_recln126,
FRAME_recln127,
FRAME_recln128,
FRAME_recln129,
FRAME_recln130,
FRAME_recln131,
FRAME_recln132,
FRAME_recln133,
FRAME_recln134,
FRAME_recln135,
FRAME_recln136,
FRAME_recln137,
FRAME_recln138,
FRAME_recln139,
FRAME_recln140
};
constexpr float MODEL_SCALE = 1.000000f;

3792
rerelease/p_client.cpp Normal file

File diff suppressed because it is too large Load Diff

1124
rerelease/p_hud.cpp Normal file

File diff suppressed because it is too large Load Diff

1695
rerelease/p_move.cpp Normal file

File diff suppressed because it is too large Load Diff

153
rerelease/p_trail.cpp Normal file
View File

@@ -0,0 +1,153 @@
// Copyright (c) ZeniMax Media Inc.
// Licensed under the GNU General Public License 2.0.
#include "g_local.h"
/*
==============================================================================
PLAYER TRAIL
==============================================================================
This is a two-way list containing the a list of points of where
the player has been recently. It is used by monsters for pursuit.
This is improved from vanilla; now, the list itself is stored in
client data so it can be stored for multiple clients.
chain = next
enemy = prev
The head node will always have a null "chain", the tail node
will always have a null "enemy".
*/
constexpr size_t TRAIL_LENGTH = 8;
// places a new entity at the head of the player trail.
// the tail entity may be moved to the front if the length
// is at the end.
static edict_t *PlayerTrail_Spawn(edict_t *owner)
{
size_t len = 0;
for (edict_t *tail = owner->client->trail_tail; tail; tail = tail->chain)
len++;
edict_t *trail;
// move the tail to the head
if (len == TRAIL_LENGTH)
{
// unlink the old tail
trail = owner->client->trail_tail;
owner->client->trail_tail = trail->chain;
owner->client->trail_tail->enemy = nullptr;
trail->chain = trail->enemy = nullptr;
}
else
{
// spawn a new head
trail = G_Spawn();
trail->classname = "player_trail";
}
// link as new head
if (owner->client->trail_head)
owner->client->trail_head->chain = trail;
trail->enemy = owner->client->trail_head;
owner->client->trail_head = trail;
// if there's no tail, we become the tail too
if (!owner->client->trail_tail)
owner->client->trail_tail = trail;
return trail;
}
// destroys all player trail entities in the map.
// we don't want these to stay around across level loads.
void PlayerTrail_Destroy(edict_t *player)
{
for (size_t i = 0; i < globals.num_edicts; i++)
if (g_edicts[i].classname && strcmp(g_edicts[i].classname, "player_trail") == 0)
if (!player || g_edicts[i].owner == player)
G_FreeEdict(&g_edicts[i]);
if (player)
player->client->trail_head = player->client->trail_tail = nullptr;
else for (size_t i = 0; i < game.maxclients; i++)
game.clients[i].trail_head = game.clients[i].trail_tail = nullptr;
}
// check to see if we can add a new player trail spot
// for this player.
void PlayerTrail_Add(edict_t *player)
{
// if we can still see the head, we don't want a new one.
if (player->client->trail_head && visible(player, player->client->trail_head))
return;
// don't spawn trails in intermission, if we're dead, if we're noclipping or not on ground yet
else if (level.intermissiontime || player->health <= 0 || player->movetype == MOVETYPE_NOCLIP ||
!player->groundentity)
return;
edict_t *trail = PlayerTrail_Spawn(player);
trail->s.origin = player->s.old_origin;
trail->timestamp = level.time;
trail->owner = player;
}
// pick a trail node that matches the player
// we're hunting that is visible to us.
edict_t *PlayerTrail_Pick(edict_t *self, bool next)
{
// not player or doesn't have a trail yet
if (!self->enemy->client || !self->enemy->client->trail_head)
return nullptr;
// find which marker head that was dropped while we
// were searching for this enemy
edict_t *marker;
for (marker = self->enemy->client->trail_head; marker; marker = marker->enemy)
{
if (marker->timestamp <= self->monsterinfo.trail_time)
continue;
break;
}
if (next)
{
// find the marker we're closest to
float closest_dist = std::numeric_limits<float>::infinity();
edict_t *closest = nullptr;
for (edict_t *m2 = marker; m2; m2 = m2->enemy)
{
float len = (m2->s.origin - self->s.origin).lengthSquared();
if (len < closest_dist)
{
closest_dist = len;
closest = m2;
}
}
// should never happen
if (!closest)
return nullptr;
// use the next one from the closest one
marker = closest->chain;
}
else
{
// from that marker, find the first one we can see
for (; marker && !visible(self, marker); marker = marker->enemy)
continue;
}
return marker;
}

1527
rerelease/p_view.cpp Normal file

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More