mirror of
https://github.com/id-Software/quake2-rerelease-dll.git
synced 2026-03-20 08:59:28 +01:00
Initial commit
This commit is contained in:
178
rerelease/bots/bot_debug.cpp
Normal file
178
rerelease/bots/bot_debug.cpp
Normal 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 );
|
||||
}
|
||||
6
rerelease/bots/bot_debug.h
Normal file
6
rerelease/bots/bot_debug.h
Normal file
@@ -0,0 +1,6 @@
|
||||
// Copyright (c) ZeniMax Media Inc.
|
||||
// Licensed under the GNU General Public License 2.0.
|
||||
|
||||
#pragma once
|
||||
|
||||
void Bot_UpdateDebug();
|
||||
151
rerelease/bots/bot_exports.cpp
Normal file
151
rerelease/bots/bot_exports.cpp
Normal 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;
|
||||
}
|
||||
9
rerelease/bots/bot_exports.h
Normal file
9
rerelease/bots/bot_exports.h
Normal 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 );
|
||||
9
rerelease/bots/bot_includes.h
Normal file
9
rerelease/bots/bot_includes.h
Normal 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"
|
||||
23
rerelease/bots/bot_think.cpp
Normal file
23
rerelease/bots/bot_think.cpp
Normal 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 ) {
|
||||
|
||||
}
|
||||
7
rerelease/bots/bot_think.h
Normal file
7
rerelease/bots/bot_think.h
Normal 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 );
|
||||
532
rerelease/bots/bot_utils.cpp
Normal file
532
rerelease/bots/bot_utils.cpp
Normal 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;
|
||||
}
|
||||
10
rerelease/bots/bot_utils.h
Normal file
10
rerelease/bots/bot_utils.h
Normal 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 );
|
||||
Reference in New Issue
Block a user