Initial commit

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

View File

@@ -0,0 +1,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 );