aboutsummaryrefslogtreecommitdiff
path: root/src/parsec/g_bot_cl.cpp
diff options
context:
space:
mode:
authorFelix Morgner <felix.morgner@gmail.com>2026-08-24 11:16:07 +0200
committerFelix Morgner <felix.morgner@gmail.com>2026-08-24 11:16:07 +0200
commitc068f22329d5cc722622a2183bbb22eef2093df7 (patch)
tree12d56c1aede67988a55e241364606bfbb4dba933 /src/parsec/g_bot_cl.cpp
downloadopenparsec-c068f22329d5cc722622a2183bbb22eef2093df7.tar.xz
openparsec-c068f22329d5cc722622a2183bbb22eef2093df7.zip
initial importHEADmain
Diffstat (limited to 'src/parsec/g_bot_cl.cpp')
-rw-r--r--src/parsec/g_bot_cl.cpp1028
1 files changed, 1028 insertions, 0 deletions
diff --git a/src/parsec/g_bot_cl.cpp b/src/parsec/g_bot_cl.cpp
new file mode 100644
index 0000000..0e364a2
--- /dev/null
+++ b/src/parsec/g_bot_cl.cpp
@@ -0,0 +1,1028 @@
+/*
+ * PARSEC -
+ *
+ * $Author: uberlinuxguy $ - $Date: 2004/09/26 03:43:36 $
+ *
+ * Orginally written by:
+ * Copyright (c) Clemens Beer <cbx@parsec.org> 2002
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+// C library
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// compilation flags/debug support
+#include "config.h"
+#include "debug.h"
+
+// general definitions
+#include "general.h"
+#include "objstruc.h"
+
+// global externals
+#include "globals.h"
+
+// subsystem headers
+//#include "aud_defs.h"
+#include "inp_defs.h"
+#include "net_defs.h"
+#include "sys_defs.h"
+//#include "vid_defs.h"
+
+// mathematics header
+#include "utl_math.h"
+
+// proprietary headers
+#include "con_arg.h"
+#include "con_int.h"
+#include "con_com.h"
+#include "con_main.h"
+#include "e_callbk.h"
+#include "inp_user.h"
+#include "m_main.h"
+#include "net_conn.h"
+#include "obj_ctrl.h"
+#include "utl_logfile.h"
+#include "g_bot_cl.h"
+#include "obj_game.h"
+#include "net_glob.h"
+#include "od_class.h"
+
+static int botwait = 0;
+
+// do the desired object control ----------------------------------------------
+//
+int OCT_DoControl( object_control_s* objctl )
+{
+ ASSERT( objctl != NULL );
+ ASSERT( ( objctl->rot_x >= -1.0f ) && ( objctl->rot_x <= 1.0f ) );
+ ASSERT( ( objctl->rot_y >= -1.0f ) && ( objctl->rot_y <= 1.0f ) );
+ ASSERT( ( objctl->rot_z >= -1.0f ) && ( objctl->rot_z <= 1.0f ) );
+ ASSERT( ( objctl->accel >= -1.0f ) && ( objctl->accel <= 1.0f ) );
+
+ // check whether we have a valid object to control
+ if ( objctl->pShip == NULL ) {
+ CON_AddMessage( "OCT: not object to control is set." );
+ return FALSE;
+ }
+
+ //FIXME: for now we only support ship objects
+ if ( !OBJECT_TYPE_SHIP( objctl->pShip ) ) {
+ CON_AddMessage( "OCT: controlled object is not a ship." );
+ return FALSE;
+ }
+
+ int ship_controlled = FALSE;
+
+ // check for rotation around x axis - PITCH - ( -1 = pullup, +1 = divedown )
+ if ( objctl->rot_x != 0.0f ) {
+ bams_t _angle = (bams_t)(objctl->rot_x * objctl->pShip->PitchPerRefFrame * CurScreenRefFrames);
+ //ObjRotX( objctl->pShip->ObjPosition, _angle );
+ INP_UserRotX( _angle );
+ ship_controlled = TRUE;
+ }
+
+ // check for rotation around y axis - YAW - ( -1 = right, +1 = left )
+ if ( objctl->rot_y != 0.0f ) {
+ bams_t _angle = (bams_t)(objctl->rot_y * objctl->pShip->YawPerRefFrame * CurScreenRefFrames);
+ //ObjRotY( objctl->pShip->ObjPosition, _angle );
+ INP_UserRotY( _angle );
+ ship_controlled = TRUE;
+ }
+
+ // check for rotation around z axis - ROLL - ( -1 = right, +1 = left )
+ if ( objctl->rot_z != 0.0f ) {
+ bams_t _angle = (bams_t)(objctl->rot_z * objctl->pShip->RollPerRefFrame * CurScreenRefFrames);
+ //ObjRotZ( objctl->pShip->ObjPosition, _angle );
+ INP_UserRotZ( _angle );
+ ship_controlled = TRUE;
+ }
+
+ // check for accel control ( +1 accelerate, -1 decelerate )
+ if ( objctl->accel != 0.0f ) {
+ //fixed_t _accel = FLOAT_TO_FIXED( objctl->accel * (float)(objctl->pShip->SpeedIncPerRefFrame) * CurScreenRefFrames );
+ fixed_t _accel = (fixed_t)(objctl->accel * objctl->pShip->SpeedIncPerRefFrame * CurScreenRefFrames);
+ INP_UserAcceleration( _accel );
+
+/*
+ // alter accel
+ objctl->pShip->CurSpeed += c_speed;
+
+ // check against speed constraints
+ if ( objctl->pShip->CurSpeed > objctl->pShip->MaxSpeed ) {
+ objctl->pShip->CurSpeed = objctl->pShip->MaxSpeed;
+ } else if ( objctl->pShip->CurSpeed < 0 ) {
+ objctl->pShip->CurSpeed = 0;
+ }
+*/
+
+ ship_controlled = TRUE;
+ }
+
+ //// we always move the controlled ship according to the speed
+ //if ( objctl->pShip->CurSpeed != 0 ) {
+ //
+ // // actually move the ship
+ // Vector3 dirvec;
+ // fixed_t speed = objctl->pShip->CurSpeed * CurScreenRefFrames;
+ // DirVctMUL( objctl->pShip->ObjPosition, FIXED_TO_GEOMV( speed ), &dirvec );
+ // objctl->pShip->ObjPosition[ 0 ][ 3 ] += dirvec.X;
+ // objctl->pShip->ObjPosition[ 1 ][ 3 ] += dirvec.Y;
+ // objctl->pShip->ObjPosition[ 2 ][ 3 ] += dirvec.Z;
+ //}
+
+ return ship_controlled;
+}
+
+// dump an object control to the console --------------------------------------
+//
+void OCT_Dump(object_control_s* objctl )
+{
+ ASSERT( objctl != NULL );
+ ASSERT( objctl->pShip != NULL );
+
+ char szBuffer[ 512 ];
+ sprintf( szBuffer, "OCT: ObjID: %03u rotx: %02u roty: %02u rotz: %02u accel: %02u\n",
+ (unsigned int)objctl->pShip->ObjectNumber,
+ (unsigned int)objctl->rot_x,
+ (unsigned int)objctl->rot_y,
+ (unsigned int)objctl->rot_z,
+ (unsigned int)objctl->accel );
+
+ CON_AddMessage( szBuffer );
+}
+
+// ****************************************************************************
+// ****************************************************************************
+// ****************************************************************************
+
+// bot logfile ----------------------------------------------------------------
+//
+#ifdef BOT_LOGFILES
+
+static UTL_LogFile g_BotLog( "bot.log" );
+static UTL_LogFile g_BotPosLog( "bot_pos.log" );
+static UTL_LogFile g_BotXDirLog( "bot_xdir.log" );
+static UTL_LogFile g_BotZDirLog( "bot_zdir.log" );
+static UTL_LogFile g_BotAccelModeLog( "bot_accel.log" );
+
+#define BOT_MsgOut g_BotLog.printf
+#define BOT_Pos_MsgOut g_BotPosLog.printf
+#define BOT_XDir_MsgOut g_BotXDirLog.printf
+#define BOT_ZDir_MsgOut g_BotZDirLog.printf
+#define BOT_AccelMode_MsgOut g_BotAccelModeLog.printf
+
+#endif // !BOT_LOGFILES
+
+// ----------------------------------------------------------------------------
+//
+void UTL_LocomotionController::ControlOjbect( object_control_s* pObjctl, Vector3* pDesiredVelocity, fixed_t _DesiredSpeed )
+{
+ ASSERT( pObjctl != NULL );
+ ASSERT( pDesiredVelocity != NULL );
+
+ Vector3 xDir, yDir, zDir;
+ FetchXVector( pObjctl->pShip->ObjPosition, &xDir );
+ FetchYVector( pObjctl->pShip->ObjPosition, &yDir );
+ FetchZVector( pObjctl->pShip->ObjPosition, &zDir );
+
+ geomv_t len = VctLenX( pDesiredVelocity );
+
+ // stop control if desired velocity is zero
+ if ( len <= GEOMV_VANISHING ) {
+ pObjctl->rot_x = 0;
+ pObjctl->rot_y = 0;
+ pObjctl->accel = -0.82;
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "ControlOjbect() got dimishing desired velocity" );
+#endif // BOT_LOGFILES
+
+ return;
+ } else {
+ Vector3 DesVelNorm;
+ DesVelNorm.X = FLOAT_TO_GEOMV( pDesiredVelocity->X / len );
+ DesVelNorm.Y = FLOAT_TO_GEOMV( pDesiredVelocity->Y / len );
+ DesVelNorm.Z = FLOAT_TO_GEOMV( pDesiredVelocity->Z / len );
+
+ geomv_t yaw_dot = DOT_PRODUCT( &DesVelNorm, &xDir );
+ geomv_t pitch_dot = DOT_PRODUCT( &DesVelNorm, &yDir );
+ geomv_t heading_dot = DOT_PRODUCT( &DesVelNorm, &zDir );
+
+ float fDesiredSpeed = FIXED_TO_FLOAT( _DesiredSpeed );
+ float fCurSpeed = FIXED_TO_FLOAT( pObjctl->pShip->CurSpeed );
+
+ float pitch = 0;
+ float yaw = 0;
+
+ // target is behind us
+ sincosval_s fullturn;
+ GetSinCos( DEG_TO_BAMS( 2 * m_nRelaxedHeadingAngle ), &fullturn );
+ //if ( heading_dot < 0.0f ) {
+ if ( heading_dot < -fullturn.cosval ) {
+
+ // we must initiate a turn, if not already in a turn
+ if ( !pObjctl->IsYaw() || !pObjctl->IsPitch() ) {
+
+ // default to random
+ yaw = (float)( RAND() % 3 ) - 1;
+ pitch = (float)( RAND() % 3 ) - 1;
+
+ // steer towards goal
+ if ( yaw_dot < -GEOMV_VANISHING ) {
+ yaw = OCT_YAW_LEFT;
+ } else if ( yaw_dot > GEOMV_VANISHING ) {
+ yaw = OCT_YAW_RIGHT;
+ }
+ if ( pitch_dot < -GEOMV_VANISHING ) {
+ pitch = OCT_PITCH_UP;
+ } else if ( pitch_dot > GEOMV_VANISHING ) {
+ pitch = OCT_PITCH_DOWN;
+ }
+
+ } else {
+ // reuse prev. turn information
+ pitch = pObjctl->rot_x;
+ yaw = pObjctl->rot_y;
+ }
+
+ // slow down, until in direction of target
+ // pObjctl->accel = heading_dot;
+ //pObjctl->accel = OCT_DECELERATE;
+
+ // also maintain min. speed during turns
+ if ( fCurSpeed > m_fMinSpeedTurn ) {
+ pObjctl->accel = -0.42;
+ }
+
+ } else {
+
+ // determine accel
+ if ( fDesiredSpeed > fCurSpeed ) {
+ // accelerate towards target
+ pObjctl->accel = OCT_ACCELERATE;
+ } else if ( fDesiredSpeed < fCurSpeed ) {
+ // decelerate towards target
+ pObjctl->accel = OCT_DECELERATE;
+ } else {
+ // no accel
+ pObjctl->accel = 0;
+ }
+
+ // heading must be inside of 5 degrees cone angle
+ sincosval_s sincosv;
+ GetSinCos( DEG_TO_BAMS( m_nRelaxedHeadingAngle ), &sincosv );
+ if ( yaw_dot < -sincosv.sinval ) {
+ yaw = OCT_YAW_LEFT;
+ } else if ( yaw_dot > sincosv.sinval ) {
+ yaw = OCT_YAW_RIGHT;
+ }
+ if ( pitch_dot < -sincosv.sinval ) {
+ pitch = OCT_PITCH_UP;
+ } else if ( pitch_dot > sincosv.sinval ) {
+ pitch = OCT_PITCH_DOWN;
+ }
+
+ // if heading outside of 30 degrees cone angle, we decelerate
+ GetSinCos( DEG_TO_BAMS( m_nFullSpeedHeading ), &sincosv );
+ bool_t bYawOutsideHeading = ( ( yaw_dot < -sincosv.sinval ) || ( yaw_dot > sincosv.sinval ) );
+ bool_t bPitchOutsideHeading = ( ( pitch_dot < -sincosv.sinval ) || ( pitch_dot > sincosv.sinval ) );
+ if ( bYawOutsideHeading || bPitchOutsideHeading ) {
+
+ // also maintain min. speed during turns
+ if ( fCurSpeed > min( m_fMinSpeedTurn, fDesiredSpeed ) ) {
+ // decelerate towards target
+ pObjctl->accel = OCT_DECELERATE;
+ }
+ }
+ }
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "heading_dot: %f, yaw_dot: %f, pitch_dot: %f", heading_dot, yaw_dot, pitch_dot );
+#endif // BOT_LOGFILES
+
+ //FIXME: oct must also handle slide horiz./vert.
+
+ pObjctl->rot_x = pitch;
+ pObjctl->rot_y = yaw;
+ }
+}
+
+//----------------------------------------------------------------------------
+
+static int clbot_char_plan_freq = 1;
+static int clbot_char_goal_freq = 10;
+static int clbot_char_input_freq = 10;
+
+PRIVATE
+void RealizeBotChar()
+{
+ TheBot->GetCharacter()->m_fPlanInterval = 1.0f / (float)clbot_char_plan_freq;
+ TheBot->GetCharacter()->m_fGoalCheckInterval = 1.0f / (float)clbot_char_goal_freq;
+ TheBot->GetCharacter()->m_fInputChangeInterval = 1.0f / (float)clbot_char_input_freq;
+}
+
+// ----------------------------------------------------------------------------
+PRIVATE int FetchBotChar_PlanFreq() { return (int)( ( 1.0f / TheBot->GetCharacter()->GetPlanInterval_sec() ) + 0.5f ); }
+PRIVATE int FetchBotChar_GoalFreq() { return (int)( ( 1.0f / TheBot->GetCharacter()->GetGoalCheckInterval_sec() ) + 0.5f ); }
+PRIVATE int FetchBotChar_InputFreq() { return (int)( ( 1.0f / TheBot->GetCharacter()->GetInputChangeInterval_sec() ) + 0.5f ); }
+
+// registration table for bot character variables -----------------------------
+//
+int_command_s botchar_int_commands[] = {
+ { 0x00, "clbot.char.plan_freq", 1, 100, &clbot_char_plan_freq, RealizeBotChar, FetchBotChar_PlanFreq, 0 },
+ { 0x00, "clbot.char.goal_freq", 1, 100, &clbot_char_goal_freq, RealizeBotChar, FetchBotChar_GoalFreq, 0 },
+ { 0x00, "clbot.char.input_freq", 1, 100, &clbot_char_input_freq, RealizeBotChar, FetchBotChar_InputFreq, 0 },
+
+ { 0x00, NULL, 0,0,NULL, NULL, NULL, 0 },
+};
+
+
+// ctor -----------------------------------------------------------------------
+//
+BOT_Character::BOT_Character()
+{
+ Reset();
+
+ // register commands
+ for( int_command_s* cmd = botchar_int_commands; cmd->command != NULL; cmd++ ) {
+ CON_RegisterIntCommand( cmd );
+ }
+}
+
+// reset to defaults ----------------------------------------------------------
+//
+void BOT_Character::Reset()
+{
+ m_fPlanInterval = 1.0f;
+ m_fGoalCheckInterval = 0.1f; // goal checking at 10Hz
+ m_fInputChangeInterval = 0.1f; // input at 10Hz
+}
+
+// select the attack target ---------------------------------------------------
+//
+ShipObject* BOT_Character::SelectAttackTarget( ShipObject* pAttacker )
+{
+ ShipObject* pCurTarget = NULL;
+ int nCurTargetDamageRemain = 0;
+
+ for ( ShipObject* pShip = /*TheWorld->*/FetchFirstShip(); pShip != NULL; ) {
+
+ // do not select self as target
+ if ( pAttacker == pCurTarget )
+ continue;
+
+ if ( pCurTarget == NULL ) {
+ pCurTarget = pShip;
+ nCurTargetDamageRemain = ( pCurTarget->MaxDamage - pCurTarget->CurDamage );
+ } else {
+ // select weakest ship as target
+ int nDamageRemain = ( pShip->MaxDamage - pShip->CurDamage );
+
+ if ( nDamageRemain > nCurTargetDamageRemain ) {
+ pCurTarget = pShip;
+ nCurTargetDamageRemain = nDamageRemain;
+ }
+ }
+
+ pShip = (ShipObject *) pShip->NextObj;
+ }
+
+ return pCurTarget;
+}
+
+
+// main think function, called every render or sim frame ( client or server )
+//
+void BOT_AI::DoThink()
+{
+ ASSERT( NetJoined );
+ // count down fire disable delay
+ // get the agent position for this frame
+ FetchTVector( m_pShip->ObjPosition, &m_AgentPos );
+ // count down fire disable delay
+ if ( FireDisable > 0 ) {
+ FireDisable -= CurScreenRefFrames;
+ }
+
+ // do planning ?
+ if ( m_PlanTimeout.IsTimeout() ) {
+ _DoPlan();
+ }
+
+ // do goal checking ?
+ if ( m_GoalCheckTimeout.IsTimeout() ) {
+ // check goals for completion and define new goals
+ switch ( m_nAgentMode ) {
+ case AGENTMODE_IDLE:
+ _GoalCheck_AgentMode_Idle();
+ break;
+ case AGENTMODE_POWERUP:
+ _GoalCheck_AgentMode_Powerup();
+ break;
+ case AGENTMODE_ATTACK:
+ _GoalCheck_AgentMode_Attack();
+ break;
+ case AGENTMODE_RETREAT:
+ _GoalCheck_AgentMode_Retreat();
+ break;
+ default:
+ ASSERT( FALSE );
+ return;
+ }
+ }
+
+ // change steering ?
+ if ( m_InputTimeout.IsTimeout() ) {
+
+ // modify ObjectControl to steer to position in current goal
+ _SteerToPosition( m_State.GetCurGoal()->GetGoalPosition(), m_State.GetObjectControl() );
+ }
+
+ // actually control the object
+ OCT_DoControl( m_State.GetObjectControl() );
+}
+
+
+// determine in which agentmode we should be ----------------------------------
+//
+void BOT_AI::_DoPlan()
+{
+ ShipObject* pTargetObject;
+ BOT_Goal* pGoal = m_State.GetCurGoal();
+
+ if(m_pShip->CurDamage > (m_pShip->MaxDamage * 0.9)) { //90% damage
+ m_nAgentMode = AGENTMODE_RETREAT;
+ return;
+ }
+ if(m_pShip->CurEnergy < (m_pShip->MaxEnergy * 0.1)) { //10% energy left
+ m_nAgentMode = AGENTMODE_RETREAT;
+ return;
+ }
+ if(NumRemPlayers > 1)
+ pTargetObject = m_Character.SelectAttackTarget( m_pShip ); //Check for target
+ else
+ pTargetObject = NULL;
+ // no target
+ if ( pTargetObject == NULL ) {
+ ExtraObject* pObject = FetchFirstExtra(); //Check for powerups
+ if(pObject != NULL) {
+ m_nAgentMode = AGENTMODE_POWERUP;
+
+ } else {
+ m_nAgentMode = AGENTMODE_IDLE;
+ }
+ } else {
+ m_nAgentMode = AGENTMODE_ATTACK;
+ }
+}
+
+
+// ----------------------------------------------------------------------------
+//
+void BOT_AI::_GoalCheck_AgentMode_Idle()
+{
+
+
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "new goal position: %f %f %f", pGoalPos->X, pGoalPos->Y, pGoalPos->Z );
+#endif // BOT_LOGFILES
+// }
+}
+
+
+// ----------------------------------------------------------------------------
+//
+void BOT_AI::_GoalCheck_AgentMode_Powerup()
+{
+ BOT_Goal* pGoal = m_State.GetCurGoal();
+ Vector3* pGoalPos = pGoal->GetGoalPosition();
+
+ ASSERT( pGoal != NULL );
+ if(pGoal->GetTargetObject() == NULL) {
+ ExtraObject* pObject = FetchFirstExtra();
+ if(pObject == NULL) {
+ m_nAgentMode = AGENTMODE_IDLE;
+ return;
+ }
+ pGoal->SetTargetObject(pObject);
+ FetchTVector( pObject->ObjPosition, pGoalPos );
+#ifdef BOT_LOGFILES
+ BOT_MsgOut("Targetting Extra");
+#endif
+ }
+
+ pGoalPos = pGoal->GetGoalPosition();
+
+ // get vector to target
+ Vector3 vec2Target;
+ VECSUB( &vec2Target, pGoalPos, &m_AgentPos );
+
+ float len = VctLenX( &vec2Target );
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "BOT: distance to goal: %f", len );
+#endif
+ if ( len < 100.0f ) {
+#ifdef BOT_LOGFILES
+ BOT_MsgOut("Clearing Goal");
+ pGoal->SetTargetObject(NULL);
+#endif
+ }
+
+}
+
+
+
+// set the currently used goal position ---------------------------------------
+//
+void BOT_AI::_GoalCheck_AgentMode_Attack()
+{
+ //FIXME: push current goal onto goal stack
+ BOT_Goal* pGoal = m_State.GetCurGoal();
+ ASSERT( pGoal != NULL );
+
+ //FIXME: here we should also evaluate whether we pick another target
+
+ // select a target, if none, or no ship selected as target
+ GenObject* pTargetObject = pGoal->GetTargetObject();
+ Vector3* pGoalPos = pGoal->GetGoalPosition();
+ if( ( pTargetObject == NULL ) || ( OBJECT_TYPE_SHIP( pTargetObject) == FALSE ) ){
+
+ // let the character select a target
+ pTargetObject = m_Character.SelectAttackTarget( m_pShip );
+
+ // no target
+ if ( pTargetObject == NULL ) {
+ //FIXME: transition function
+ m_nAgentMode = AGENTMODE_IDLE;
+ pGoal->SetTargetObject(NULL);
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "switching from ATTACK to IDLE" );
+#endif // BOT_LOGFILES
+
+ return;
+ }
+
+ // set the target object
+ pGoal->SetTargetObject( pTargetObject );
+
+ // set the goal position to where the target is
+ FetchTVector( pTargetObject->ObjPosition, pGoalPos );
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "found new ATTACK target %d", pTargetObject->HostObjNumber );
+#endif // BOT_LOGFILES
+
+ }
+
+ Vector3 TargetPos;
+ FetchTVector( pTargetObject->ObjPosition, &TargetPos );
+
+ // get vector to target
+ Vector3 vec2Target;
+ VECSUB( &vec2Target, &TargetPos, &m_AgentPos );
+
+ float len = VctLenX( &vec2Target );
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "BOT: distance to target: %f", len );
+#endif
+#define MIN_DISTANCE_TO_TARGET 100.0f
+ if ( len < 500.0F) {
+ if ( FireRepeat > 0 ) {
+ FireRepeat -= CurScreenRefFrames;
+ }
+ if ( ( FireRepeat > 0 ) || ( FireDisable > 0 ) ) {
+ //do nothing!
+ } else {
+
+ // create laser
+ OBJ_ShootLaser( m_pShip );
+
+ if ( ( FireRepeat += MyShip->FireRepeatDelay ) <= 0 ) {
+ FireRepeat = 1;
+ }
+ if ( ( FireDisable += MyShip->FireDisableDelay ) <= 0 ) {
+ FireDisable = 1;
+ }
+ }
+ }
+ if ( len < MIN_DISTANCE_TO_TARGET ) {
+
+ // if nearby goal, we stay where we are
+ memcpy( pGoalPos, &m_AgentPos, sizeof( Vector3 ) );
+
+ } else {
+
+ // set the goal position to the position of the target
+ ASSERT( pTargetObject != NULL );
+ FetchTVector( pTargetObject->ObjPosition, pGoalPos );
+ }
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "ATTACK goal is at %f %f %f", pGoalPos->X, pGoalPos->Y, pGoalPos->Z );
+#endif // BOT_LOGFILES
+}
+
+// search for the repair module ---------------------------------------------------
+//
+ExtraObject* BOT_Character::SelectRepairObject()
+{
+ ExtraObject* pCurTarget = NULL;
+
+ for ( ExtraObject* pSearch = FetchFirstExtra(); pSearch != NULL; ) {
+
+ if ( pCurTarget == NULL ) { //Save what we find first, if it matches great if not, search on
+ pCurTarget = pSearch;
+ if(pCurTarget->ObjectType == EXTRA1TYPE) {
+ Extra1Obj *extra1po = (Extra1Obj *) pCurTarget;
+ if( extra1po->ObjectClass == REPAIR_EXTRA_CLASS)
+ return pCurTarget; //found it first try!
+ }
+ } else {
+ //Find Repair module
+ if(pSearch->ObjectType == EXTRA1TYPE) {
+ Extra1Obj *extra1po = (Extra1Obj *) pSearch;
+ if( extra1po->ObjectClass == REPAIR_EXTRA_CLASS)
+ return pSearch; //found it first try!
+ }
+ }
+ pSearch = (ExtraObject *) pSearch->NextObj;
+ }
+ return pCurTarget; //Return Original or NULL search item as we couldnt find the rep module
+}
+
+// search for the energy module ---------------------------------------------------
+//
+ExtraObject* BOT_Character::SelectEnergyObject()
+{
+ ExtraObject* pCurTarget = NULL;
+
+ for ( ExtraObject* pSearch = FetchFirstExtra(); pSearch != NULL; ) {
+
+ if ( pCurTarget == NULL ) { //Save what we find first, if it matches great if not, search on
+ pCurTarget = pSearch;
+ if(pCurTarget->ObjectType == EXTRA1TYPE) {
+ Extra1Obj *extra1po = (Extra1Obj *) pCurTarget;
+ if( extra1po->ObjectClass == ENERGY_EXTRA_CLASS)
+ return pCurTarget; //found it first try!
+ }
+ } else {
+ //Find Repair module
+ if(pSearch->ObjectType == EXTRA1TYPE) {
+ Extra1Obj *extra1po = (Extra1Obj *) pSearch;
+ if( extra1po->ObjectClass == ENERGY_EXTRA_CLASS)
+ return pSearch; //found it first try!
+ }
+ }
+ pSearch = (ExtraObject *) pSearch->NextObj;
+ }
+ return pCurTarget; //Return Original or NULL search item as we couldnt find the energy module
+}
+
+// ----------------------------------------------------------------------------
+//
+void BOT_AI::_GoalCheck_AgentMode_Retreat()
+{
+ BOT_Goal* pGoal = m_State.GetCurGoal();
+ Vector3* pGoalPos = pGoal->GetGoalPosition();
+ ExtraObject* pObject = NULL;
+ ASSERT( pGoal != NULL );
+
+ if(pGoal->GetTargetObject() == NULL) {
+ if(m_pShip->CurDamage > (m_pShip->MaxDamage * 0.9)) {
+ pObject = m_Character.SelectRepairObject();
+ }
+ if(m_pShip->CurEnergy < (m_pShip->MaxEnergy * 0.1)) {
+ pObject = m_Character.SelectEnergyObject();
+ m_nAgentMode = AGENTMODE_RETREAT;
+ }
+ if(pObject == NULL) {
+ m_nAgentMode = AGENTMODE_IDLE;
+ return;
+ }
+ pGoal->SetTargetObject(pObject);
+ FetchTVector( pObject->ObjPosition, pGoalPos );
+#ifdef BOT_LOGFILES
+ BOT_MsgOut("Retreat: Targetting Extra");
+#endif
+ }
+
+ pGoalPos = pGoal->GetGoalPosition();
+
+ // get vector to target
+ Vector3 vec2Target;
+ VECSUB( &vec2Target, pGoalPos, &m_AgentPos );
+
+ float len = VctLenX( &vec2Target );
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "BOT: distance to goal: %f", len );
+#endif
+ if ( len < 100.0f ) {
+#ifdef BOT_LOGFILES
+ BOT_MsgOut("Clearing Goal");
+#endif
+ pGoal->SetTargetObject(NULL);
+
+ }
+
+}
+
+
+// ----------------------------------------------------------------------------
+//
+void BOT_AI::_SteerToPosition( Vector3* targetPos, object_control_s* pObjctl )
+{
+ // get vector to target
+ Vector3 vec2Target;
+ VECSUB( &vec2Target, targetPos, &m_AgentPos );
+
+ float oldaccel = pObjctl->accel;
+
+ UTL_LocomotionController _LomoCtrl;
+ _LomoCtrl.ControlOjbect( pObjctl, &vec2Target, pObjctl->pShip->MaxSpeed );
+
+ if ( pObjctl->accel != oldaccel ) {
+#ifdef BOT_LOGFILES
+ BOT_AccelMode_MsgOut( "%f %f %f", m_AgentPos.X, m_AgentPos.Y, m_AgentPos.Z );
+#endif // BOT_LOGFILES
+ }
+
+ Vector3 xDir, yDir, zDir;
+ FetchXVector( m_pShip->ObjPosition, &xDir );
+ FetchYVector( m_pShip->ObjPosition, &yDir );
+ FetchZVector( m_pShip->ObjPosition, &zDir );
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "pos: %7.2f/%7.2f/%7.2f vec2target: %7.2f/%7.2f/%7.2f xDir: %7.2f/%7.2f/%7.2f yDir: %7.2f/%7.2f/%7.2f rot_x: %7.2f rot_y: %7.2f accel: %7.2f",
+ m_AgentPos.X, m_AgentPos.Y, m_AgentPos.Z,
+ vec2Target.X, vec2Target.Y, vec2Target.Z,
+ xDir.X, xDir.Y, xDir.Z,
+ yDir.X, yDir.Y, yDir.Z,
+ pObjctl->rot_x, pObjctl->rot_y, pObjctl->accel );
+
+ BOT_Pos_MsgOut ( "%f %f %f", m_AgentPos.X, m_AgentPos.Y, m_AgentPos.Z );
+ BOT_XDir_MsgOut( "%f %f %f", xDir.X, xDir.Y, xDir.Z );
+ BOT_ZDir_MsgOut( "%f %f %f", zDir.X, zDir.Y, zDir.Z );
+#endif // BOT_LOGFILES
+
+}
+
+
+
+// check whether current status (connected/joined etc. ) matches the desired one
+//
+void BOT_ClientSide::_CheckStatus()
+{
+ BOT_Goal* pGoal = m_State.GetCurGoal();
+ // check whether to connect
+ if ( ( NetConnected == NETWORK_GAME_OFF ) && m_bWantToBeConnected ) {
+ pGoal->SetTargetObject(NULL);
+ if ( strlen( m_szServer ) > 0 ) {
+ // connect
+ NET_CommandConnect( m_szServer );
+
+ // set the timeout for the next connection attempt
+ if ( !NetConnected ) {
+ m_NextStatusCheckRefFrame = SYSs_GetRefFrameCount() + ( 5 * BOT_STATUS_CHECK_TIMEOUT );
+ }
+
+ return;
+ } else {
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "Error: BOT_ClientSide::_CheckStatus(): missing servername for connect !" );
+#endif // BOT_LOGFILES
+
+ }
+
+ // check whether to disconnect
+ } else if ( ( NetConnected == NETWORK_GAME_ON ) && !m_bWantToBeConnected ) {
+
+ // disconnect
+ NET_CommandDisconnect();
+ return;
+ }
+
+ if ( NetConnected ) {
+ // check for join
+ if ( !NetJoined && m_bWantToBeJoined ) {
+ if(botwait == 1) {
+ botwait = 0;
+ } else {
+ botwait = 1;
+ }
+ // join
+ pGoal->SetTargetObject(NULL);
+ if(botwait == 0) {
+ MENU_FloatingMenuEnterGame();
+ }
+
+ if(!NetJoined) {
+ m_NextStatusCheckRefFrame = SYSs_GetRefFrameCount() + ( 5 * BOT_STATUS_CHECK_TIMEOUT );
+ }
+ return;
+
+ // check for unjoin
+ } else if ( NetJoined && !m_bWantToBeJoined ) {
+
+ // unjoin
+ pGoal->SetTargetObject(NULL);
+ ExitGameLoop = 1;
+ }
+ }
+}
+
+
+// overwritten think method to allow special client-side things ---------------
+//
+void BOT_ClientSide::DoThink()
+{
+ // do status checking ?
+ if ( SYSs_GetRefFrameCount() >= m_NextStatusCheckRefFrame ) {
+ m_NextStatusCheckRefFrame = SYSs_GetRefFrameCount() + BOT_STATUS_CHECK_TIMEOUT;
+ _CheckStatus();
+ }
+
+ // AI only active if connected & joined & we have the full state received from the server
+ if ( NetConnected && NetJoined & HaveFullPlayerState ) {
+ BOT_AI::DoThink();
+ }
+}
+
+// start the bot --------------------------------------------------------------
+//
+void BOT_ClientSide::Start()
+{
+ if ( !m_Started ) {
+ CALLBACK_RegisterCallback( CBTYPE_USER_INPUT | CBFLAG_PERSISTENT, BOT_CallThink, (void *) this );
+ m_Started = TRUE;
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "STARTED" );
+#endif // BOT_LOGFILES
+ }
+}
+
+// stop the bot ---------------------------------------------------------------
+//
+void BOT_ClientSide::Stop()
+{
+ if ( m_Started ) {
+ int numremoved = CALLBACK_DestroyCallback( CBTYPE_USER_INPUT | CBFLAG_PERSISTENT, (void *) this );
+ ASSERT( numremoved <= 1 );
+ m_Started = FALSE;
+
+#ifdef BOT_LOGFILES
+ BOT_MsgOut( "STOPPED" );
+#endif // BOT_LOGFILES
+ }
+}
+
+
+// callback function to call the bot DoThink() function -----------------------
+//
+PRIVATE
+int BOT_CallThink( void* param )
+{
+ ASSERT( param != NULL );
+ BOT_ClientSide* pBot = (BOT_ClientSide*) param;
+
+ pBot->DoThink();
+
+ return TRUE;
+}
+
+
+// console command for starting the client-side bot ---------------------------
+//
+PRIVATE
+int Cmd_CLBOT_START( char* dummystr )
+{
+ ASSERT( dummystr != NULL );
+ HANDLE_COMMAND_DOMAIN_SEP( dummystr );
+
+ TheBot->SetShipObject( MyShip );
+ TheBot->Start();
+
+ return TRUE;
+}
+
+// console command for stopping the client-side bot ---------------------------
+//
+PRIVATE
+int Cmd_CLBOT_STOP( char* dummystr )
+{
+ ASSERT( dummystr != NULL );
+ HANDLE_COMMAND_DOMAIN_SEP( dummystr );
+
+ TheBot->Stop();
+ TheBot->SetShipObject( NULL );
+
+ return TRUE;
+}
+
+
+// console command for setting/getting the server we want to connect to -------
+//
+PRIVATE
+int Cmd_CLBOT_SERVER( char* pszServername )
+{
+ // trim left
+ char *p = NULL;
+ for( p = pszServername; *p == ' '; p++ );
+
+ if ( *p == '\0' ) {
+ char* pszSetServer = TheBot->GetConnectServer();
+ if ( pszSetServer == '\0' ) {
+ CON_AddLine( "no server set." );
+ } else {
+ CON_AddLine( pszSetServer );
+ }
+ } else {
+ TheBot->SetConnectServer( pszServername );
+ }
+
+ return TRUE;
+}
+
+// console command to set/get the desired game status (connected/joined etc ) -
+//
+PRIVATE
+int Cmd_CLBOT_GAMESTATUS( char* pszStatus )
+{
+ ASSERT( pszStatus != NULL );
+ HANDLE_COMMAND_DOMAIN_SEP( pszStatus );
+
+ int status = ( TheBot->GetDesiredConnStatus() << 1 ) | TheBot->GetDesiredJoinStatus();
+ char *scan;
+
+ if ( (scan = QueryIntArgumentEx( pszStatus, "%d", &status )) ) {
+ char *errpart;
+ long sval = strtol( scan, &errpart, 10 );
+ if ( *errpart == 0 ) {
+
+ TheBot->SetDesiredConnStatus( ( sval & 0x2 ) >> 1 );
+ TheBot->SetDesiredJoinStatus( sval & 0x1 );
+ }
+ }
+
+ return TRUE;
+}
+
+
+// module registration function -----------------------------------------------
+//
+REGISTER_MODULE( G_BOT_CL )
+{
+ user_command_s regcom;
+ memset( &regcom, 0, sizeof( user_command_s ) );
+
+ // register "clbot.start" command
+ regcom.command = "clbot.start";
+ regcom.numparams = 0;
+ regcom.execute = Cmd_CLBOT_START;
+ regcom.statedump = NULL;
+ CON_RegisterUserCommand( &regcom );
+
+ // register "clbot.start" command
+ regcom.command = "clbot.stop";
+ regcom.numparams = 0;
+ regcom.execute = Cmd_CLBOT_STOP;
+ regcom.statedump = NULL;
+ CON_RegisterUserCommand( &regcom );
+
+ // register "clbot.setsever" command
+ regcom.command = "clbot.server";
+ regcom.numparams = 1;
+ regcom.execute = Cmd_CLBOT_SERVER;
+ regcom.statedump = NULL;
+ CON_RegisterUserCommand( &regcom );
+
+ // register "clbot.gamestatus" command
+ regcom.command = "clbot.gamestatus";
+ regcom.numparams = 1;
+ regcom.execute = Cmd_CLBOT_GAMESTATUS;
+ regcom.statedump = NULL;
+ CON_RegisterUserCommand( &regcom );
+}
+
+