// Created by Ivan_the_B // #include "Actor.h" #include "Entity.h" #include "Projectile.h" #include "script/Script_Thread.h" #include "gamesys/SysCvar.h" #include "Game_local.h" #include "Moveable.h" #include "AI.h" #include "AI_bot.h" /* =============================================================================== idAI_Bot =============================================================================== */ const idEventDef AI_Bot_SelectWeapon( "selectBotWeapon", "d", 'd' ); const idEventDef AI_Bot_SelectAnotherWeapon( "selectAnotherWeapon", "dd", 'd' ); const idEventDef AI_Bot_GetCurrentWeapon( "getCurrentBotWeapon", NULL, 'd' ); const idEventDef AI_Bot_GetWeaponNumByName( "getBotWeaponNumByName", "s", 'd' ); const idEventDef AI_Bot_FireWeapon( "fireWeapon", NULL, 'e' ); const idEventDef AI_Bot_CanHitEnemyFromCurrentWeapon( "canHitEnemyFromCurrentWeapon", NULL, 'd' ); const idEventDef AI_Bot_CanFireToEnemyNoSelfDamage( "canFireToEnemyNoSelfDamage", "d", 'd' ); const idEventDef AI_Bot_CanHitEnemyFromFireAnim( "canHitEnemyFromFireAnim", "d", 'd' ); const idEventDef AI_Bot_GetIdleAnim( "getIdleAnim", NULL, 's' ); const idEventDef AI_Bot_GetReloadAnim( "getReloadAnim", NULL, 's' ); const idEventDef AI_Bot_GetFireAnim( "getFireAnim", NULL, 's' ); const idEventDef AI_Bot_ReloadWeapon( "reloadWeapon" ); const idEventDef AI_Bot_GetAmmoInClip( "getAmmoInClip", NULL, 'd' ); const idEventDef AI_Bot_GetClipSize( "getClipSize", NULL, 'd' ); const idEventDef AI_Bot_CheckReloadTolerance( "checkReloadTolerance", NULL, 'd' ); const idEventDef AI_Bot_GetRandomTargetTypePrefix( "getRandomTargetTypePrefix", "ss", 'e' ); const idEventDef AI_Bot_LostTimeMoreThan( "lostTimeMoreThan", "d", 'd' ); const idEventDef AI_Bot_WeaponChangedMoreThan( "weaponChangedMoreThan", "d", 'd' ); const idEventDef AI_Bot_PlayAnimOnWeapon( "playAnimOnWeapon", "s" ); const idEventDef AI_Bot_ReleaseNode( "releaseCurrentNode" ); const idEventDef AI_Bot_TryLockNode( "tryLockNode", "e", 'd' ); const idEventDef AI_Bot_FindEnemyAIorPL( "findEnemyAIorPL", "d", 'e' ); /* //redefined: const idEventDef AI_CanHitEnemyFromAnim( "canHitEnemyFromAnim", "s", 'd' ); const idEventDef AI_CreateMissile( "createMissile", "s", 'e' ); const idEventDef AI_LaunchMissile( "launchMissile", "vv", 'e' ); const idEventDef AI_CanHitEnemyFromJoint( "canHitEnemyFromJoint", "s", 'd' ); */ CLASS_DECLARATION( idAI, idAI_Bot ) EVENT( AI_Bot_SelectWeapon, idAI_Bot::Event_SelectWeapon ) EVENT( AI_Bot_SelectAnotherWeapon, idAI_Bot::Event_SelectAnotherWeapon ) EVENT( AI_Bot_GetCurrentWeapon, idAI_Bot::Event_GetCurrentWeapon ) EVENT( AI_Bot_GetWeaponNumByName, idAI_Bot::Event_GetWeaponNumByName ) EVENT( AI_Bot_FireWeapon, idAI_Bot::Event_FireWeapon ) EVENT( AI_Bot_CanHitEnemyFromCurrentWeapon, idAI_Bot::Event_CanHitEnemyFromCurrentWeapon ) EVENT( AI_Bot_CanFireToEnemyNoSelfDamage, idAI_Bot::Event_CanFireToEnemyNoSelfDamage ) EVENT( AI_Bot_CanHitEnemyFromFireAnim, idAI_Bot::Event_CanHitEnemyFromFireAnim ) EVENT( AI_Bot_GetIdleAnim, idAI_Bot::Event_GetIdleAnim ) EVENT( AI_Bot_GetReloadAnim, idAI_Bot::Event_GetReloadAnim ) EVENT( AI_Bot_GetFireAnim, idAI_Bot::Event_GetFireAnim ) EVENT( AI_Bot_ReloadWeapon, idAI_Bot::Event_ReloadWeapon ) EVENT( AI_Bot_GetAmmoInClip, idAI_Bot::Event_GetAmmoInClip ) EVENT( AI_Bot_GetClipSize, idAI_Bot::Event_GetClipSize ) EVENT( AI_Bot_CheckReloadTolerance, idAI_Bot::Event_CheckReloadTolerance ) EVENT( AI_Bot_LostTimeMoreThan, idAI_Bot::Event_LostTimeMoreThan ) EVENT( AI_Bot_WeaponChangedMoreThan, idAI_Bot::Event_WeaponChangedMoreThan ) EVENT( AI_Bot_PlayAnimOnWeapon, idAI_Bot::Event_PlayAnimOnWeapon ) EVENT( AI_Bot_ReleaseNode, idAI_Bot::Event_ReleaseNode ) EVENT( AI_Bot_TryLockNode, idAI_Bot::Event_TryLockNode ) EVENT( AI_Bot_FindEnemyAIorPL, idAI_Bot::Event_FindEnemyAIorPL ) EVENT( AI_CanHitEnemyFromAnim, idAI_Bot::Event_CanHitEnemyFromAnim ) //redefined EVENT( AI_CreateMissile, idAI_Bot::Event_CreateMissile ) //redefined EVENT( AI_LaunchMissile, idAI_Bot::Event_LaunchMissile ) //redefined EVENT( AI_CanHitEnemyFromJoint, idAI_Bot::Event_CanHitEnemyFromJoint ) //redefined EVENT( AI_Burn, idAI_Bot::Event_Burn ) END_CLASS /* =============================================================================== Constructor and Destructor =============================================================================== */ /* ===================== idAI_Bot::idAI_Bot ===================== */ idAI_Bot::idAI_Bot() { currentWeapon = -1; //nothing lastHitFCWCheckResult = false; lastHitFCWCheckTime = 0; lastHitNSDCheckResult = false; lastHitNSDCheckTime = 0; lastVisibleEnemyTime = 0; lastWeaponChangedTime = 0; currentNode = NULL; weapons.SetGranularity( 1 ); } /* ===================== idAI_Bot::~idAI_Bot ===================== */ idAI_Bot::~idAI_Bot() { int i; idEntity *ent; //gameLocal.Printf("~idAI_Bot!\n"); DeconstructScriptObject(); scriptObject.Free(); // remove any attached entities for( i = 0; i < weapons.Num(); i++ ) { //weapon entity ent = weapons[ i ].ent.GetEntity(); if ( ent ) { ent->PostEventMS( &EV_Remove, 0 ); } //projectileClipModel delete weapons[ i ].projectileClipModel; } } /* ===================== idAI_Bot::Save ===================== */ void idAI_Bot::Save( idSaveGame *savefile ) const { int i; currentNode.Save( savefile ); savefile->WriteInt( currentWeapon ); savefile->WriteInt( lastVisibleEnemyTime ); savefile->WriteBool( lastHitFCWCheckResult ); savefile->WriteInt( lastHitFCWCheckTime ); savefile->WriteBool( lastHitNSDCheckResult ); savefile->WriteInt( lastHitNSDCheckTime ); savefile->WriteInt( lastWeaponChangedTime ); savefile->WriteInt( weapons.Num() ); for( i = 0; i < weapons.Num(); i++ ) { //spawn settings weapons[i].ent.Save( savefile ); savefile->WriteInt( weapons[i].channel ); savefile->WriteJoint( weapons[i].fireJoint ); savefile->WriteJoint( weapons[i].bindJoint ); savefile->WriteJoint( weapons[i].flashJoint ); //weapon savefile->WriteInt( weapons[i].priorityLevel ); savefile->WriteBool( weapons[i].enabled ); savefile->WriteString( weapons[i].weaponName ); savefile->WriteFloat( weapons[i].minSelfDmgDistance ); //projectile - const idDict * projectileDef idStr projectileName; idEntity *ent; ent = weapons[ i ].ent.GetEntity(); ent->spawnArgs.GetString( "def_projectile", "", projectileName ); savefile->WriteString( projectileName ); //projectile - other stuff savefile->WriteFloat( weapons[i].projectileRadius ); savefile->WriteFloat( weapons[i].projectileSpeed ); savefile->WriteVec3( weapons[i].projectileVelocity ); savefile->WriteVec3( weapons[i].projectileGravity ); //mutable idClipModel *projectileClipModel; NOT SAVED //attack settings savefile->WriteFloat( weapons[i].attack_accuracy ); savefile->WriteFloat( weapons[i].projectile_spread ); savefile->WriteInt( weapons[i].num_projectiles ); //clip savefile->WriteInt( weapons[i].clipSize ); savefile->WriteInt( weapons[i].ammoInClip ); savefile->WriteInt( weapons[i].reloadTolerance ); //light savefile->WriteMaterial( weapons[i].shader ); savefile->WriteVec3( weapons[i].flashColor ); savefile->WriteFloat( weapons[i].flashRadius ); savefile->WriteInt( weapons[i].flashTime ); } } /* ===================== idAI_Bot::Restore ===================== */ void idAI_Bot::Restore( idRestoreGame *savefile ) { int num; int i; //gameLocal.Printf("idAI_Bot::Restore\n"); currentNode.Restore( savefile ); savefile->ReadInt( currentWeapon ); savefile->ReadInt( lastVisibleEnemyTime ); savefile->ReadBool( lastHitFCWCheckResult ); savefile->ReadInt( lastHitFCWCheckTime ); savefile->ReadBool( lastHitNSDCheckResult ); savefile->ReadInt( lastHitNSDCheckTime ); savefile->ReadInt( lastWeaponChangedTime ); savefile->ReadInt( num ); weapons.SetGranularity( 1 ); weapons.SetNum( num ); for( i = 0; i < num; i++ ) { //spawn settings weapons[i].ent.Restore( savefile ); savefile->ReadInt( weapons[i].channel ); savefile->ReadJoint( weapons[i].fireJoint ); savefile->ReadJoint( weapons[i].bindJoint ); savefile->ReadJoint( weapons[i].flashJoint ); //weapon savefile->ReadInt( weapons[i].priorityLevel ); savefile->ReadBool( weapons[i].enabled ); savefile->ReadString( weapons[i].weaponName ); savefile->ReadFloat( weapons[i].minSelfDmgDistance ); //projectile - const idDict * projectileDef idStr projectileName; savefile->ReadString( projectileName ); if ( projectileName.Length() ) { weapons[ i ].projectileDef = gameLocal.FindEntityDefDict( projectileName ); } else { weapons[ i ].projectileDef = NULL; } //projectile - other stuff savefile->ReadFloat( weapons[i].projectileRadius ); savefile->ReadFloat( weapons[i].projectileSpeed ); savefile->ReadVec3( weapons[i].projectileVelocity ); savefile->ReadVec3( weapons[i].projectileGravity ); //mutable idClipModel *projectileClipModel; NOT SAVED weapons[ i ].projectileClipModel = NULL; //attack settings savefile->ReadFloat( weapons[i].attack_accuracy ); savefile->ReadFloat( weapons[i].projectile_spread ); savefile->ReadInt( weapons[i].num_projectiles ); //clip savefile->ReadInt( weapons[i].clipSize ); savefile->ReadInt( weapons[i].ammoInClip ); savefile->ReadInt( weapons[i].reloadTolerance ); //light savefile->ReadMaterial( weapons[i].shader ); savefile->ReadVec3( weapons[i].flashColor ); savefile->ReadFloat( weapons[i].flashRadius ); savefile->ReadInt( weapons[i].flashTime ); // Link the script variables back to the scriptobject LinkScriptVariables(); //upd muzzle light setWeaponMuzzleFlash(); } } /* =============================================================================== Pain and talk stuff =============================================================================== */ /* ===================== idAI::Pain ===================== bool idAI::Pain( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { SAY_DAMAGE = true; return idAI::Pain( inflictor, attacker, damage, dir, location ); } */ /* =============================================================================== Spawn and remove stuff =============================================================================== */ /* ===================== idAI_Bot::Spawn ===================== */ void idAI_Bot::Spawn( void ) { //gameLocal.Printf("idAI_Bot::Spawn\n"); CreateWeapons(); SelectInitialWeapon(); ShowOnlyCurrentWeapon(); LinkScriptVariables(); /* //Warning: idAI_Bot::CalculateAttackOffsets is called by idAI::Spawn --> weapons are no available yet! //CalculateAttackOffsets(); */ } /* ===================== idAI_Bot::LinkScriptVariables ===================== */ void idAI_Bot::LinkScriptVariables( void ) { //idAI::LinkScriptVariables(); //useless because idAI::spawn() also calls idAI::LinkScriptVariables() AI_WEAPON_CHANGED.LinkTo( scriptObject, "AI_WEAPON_CHANGED" ); AI_WEAPON_NEED_RELOAD.LinkTo( scriptObject, "AI_WEAPON_NEED_RELOAD" ); AI_LEAVE_NODE_TO_FIGHT.LinkTo( scriptObject, "AI_LEAVE_NODE_TO_FIGHT" ); AI_SAY_DAMAGED.LinkTo( scriptObject, "AI_SAY_DAMAGED" ); } /* ================ idAI_Bot::CreateWeapons ================ */ void idAI_Bot::CreateWeapons( void ) { idEntity *ent; idStr weaponName; // spawn any weapons we might have const idKeyValue *kv = spawnArgs.MatchPrefix( "def_weapon_", NULL ); while ( kv ) { idDict args; args.Set( "classname", kv->GetValue().c_str() ); //TODO: check it!? // make items non-touchable so the player can't take them out of the character's hands args.Set( "no_touch", "1" ); // don't let them drop to the floor args.Set( "dropToFloor", "0" ); gameLocal.SpawnEntityDef( args, &ent ); if ( !ent ) { gameLocal.Error( "Couldn't spawn '%s' to attach to entity '%s'", kv->GetValue().c_str(), name.c_str() ); } else { // get the weapon name weaponName = kv->GetKey(); weaponName.Strip( "def_weapon_" ); AttachWeapon( ent, weaponName ); } kv = spawnArgs.MatchPrefix( "def_weapon_", kv ); } } /* ================ idAI_Bot::AttachWeapon ================ */ void idAI_Bot::AttachWeapon( idEntity *ent, idStr weaponName) { //TODO: spawn only the enabled ones idVec3 origin; idMat3 axis; jointHandle_t bindJoint; //jointHandle_t fireJoint; idStr jointName; idBotWeapon &attach = weapons.Alloc(); idAngles angleOffset; idVec3 originOffset; idStr projectileName; idAnimator *entAnimator; //weapon number currentWeapon = weapons.NumAllocated()-1; //needed to make CreateProjectile working properly //bindJoint jointName = ent->spawnArgs.GetString( "bindJoint" ); bindJoint = animator.GetJointHandle( jointName ); if ( bindJoint == INVALID_JOINT ) { gameLocal.Error( "Joint '%s' not found for attaching '%s' on '%s'", jointName.c_str(), ent->GetClassname(), name.c_str() ); } attach.bindJoint = bindJoint; angleOffset = ent->spawnArgs.GetAngles( "angles" ); originOffset = ent->spawnArgs.GetVector( "origin" ); attach.channel = animator.GetChannelForJoint( bindJoint ); GetJointWorldTransform( bindJoint, gameLocal.time, origin, axis ); attach.ent = ent; ent->SetOrigin( origin + originOffset * renderEntity.axis ); idMat3 rotate = angleOffset.ToMat3(); idMat3 newAxis = rotate * axis; ent->SetAxis( newAxis ); ent->BindToJoint( this, bindJoint, true ); ent->cinematic = cinematic; //fire joint jointName = ent->spawnArgs.GetString( "fireJoint", "barrel"); //default is barrel entAnimator = ent->GetAnimator(); if ( entAnimator && entAnimator->ModelHandle() ) { // check if the entity has an MD5 model attach.fireJoint = entAnimator->GetJointHandle( jointName ); //this could also be INVALID_JOINT! attach.flashJoint = entAnimator->GetJointHandle( "flash" ); //this could also be INVALID_JOINT! }else{ attach.fireJoint = INVALID_JOINT; attach.flashJoint = INVALID_JOINT; } /* //test only if ( attach.fireJoint == INVALID_JOINT ) { gameLocal.Printf("fireJoint == INVALID_JOINT\n"); }else{ gameLocal.Printf("fireJoint is valid: %d \n", attach.fireJoint); }*/ //name attach.weaponName = weaponName; //enabled attach.enabled = spawnArgs.GetBool(attach.weaponName.c_str(),"0"); //default disabled //projectile if ( ent->spawnArgs.GetString( "def_projectile", "", projectileName ) && projectileName.Length() ) { //gameLocal.Printf("def_projectile found\n"); attach.projectileDef = gameLocal.FindEntityDefDict( projectileName ); CreateProjectile( vec3_origin, viewAxis[ 0 ] ); //this sets the variable projectile attach.projectileRadius = projectile.GetEntity()->GetPhysics()->GetClipModel()->GetBounds().GetRadius(); attach.projectileVelocity = idProjectile::GetVelocity( attach.projectileDef ); attach.projectileGravity = idProjectile::GetGravity( attach.projectileDef ); attach.projectileSpeed = projectileVelocity.Length(); delete projectile.GetEntity(); projectile = NULL; attach.attack_accuracy = ent->spawnArgs.GetFloat( "attack_accuracy", "1" ); //was 7 attach.projectile_spread = ent->spawnArgs.GetFloat( "projectile_spread", "0" ); attach.num_projectiles = ent->spawnArgs.GetInt( "num_projectiles", "1" ); attach.clipSize = ent->spawnArgs.GetInt( "clipSize", "0" ); //0 = unlimited if(attach.clipSize < 0){ attach.clipSize = 0; } attach.ammoInClip = attach.clipSize; //start with full ammo attach.reloadTolerance = ent->spawnArgs.GetInt( "reloadTolerance", "0" ); attach.minSelfDmgDistance = ent->spawnArgs.GetFloat( "minSelfDmgDistance", "0" ); //light attach.flashColor = ent->spawnArgs.GetVector( "flashColor", "0 0 0" ); attach.flashRadius = ent->spawnArgs.GetFloat( "flashRadius" ); attach.flashTime = SEC2MS( ent->spawnArgs.GetFloat( "flashTime", "0.25" ) ); const char *shader; spawnArgs.GetString( "mtr_flashShader", "muzzleflash", &shader ); attach.shader = declManager->FindMaterial( shader, false ); }else{ //gameLocal.Printf("def_projectile not found\n"); attach.projectileDef = NULL; attach.projectileRadius = 0.0f; attach.projectileSpeed = 0.0f; attach.projectileVelocity = vec3_origin; attach.projectileGravity = vec3_origin; attach.attack_accuracy = 0.0f; attach.projectile_spread = 0.0f; attach.num_projectiles = 0; attach.clipSize = 0; attach.ammoInClip = 0; attach.reloadTolerance = 0; attach.minSelfDmgDistance = 0; attach.flashColor = vec3_origin; attach.flashRadius = 0.0f; attach.flashTime = 0; attach.shader = NULL; } attach.priorityLevel = ent->spawnArgs.GetInt( "priorityLevel", "0" ); attach.projectileClipModel = NULL; //will be initialized later //gameLocal.Printf("fireJoint check: %d \n", weapons[ currentWeapon ].fireJoint); } /* =================== idAI_Bot::CalculateAttackOffsets calculate joint positions on attack frames so we can do proper "can hit" tests =================== */ void idAI_Bot::CalculateAttackOffsets( void ) { //Warning: this is called by idAI::Spawn --> weapons are no available yet! const idDeclModelDef *modelDef; int num; int i; int frame; const frameCommand_t *command; idMat3 axis; const idAnim *anim; jointHandle_t joint; //gameLocal.Printf("idAI_Bot::CalculateAttackOffsets \n"); modelDef = animator.ModelDef(); if ( !modelDef ) { return; } num = modelDef->NumAnims(); // needs to be off while getting the offsets so that we account for the distance the monster moves in the attack anim animator.RemoveOriginOffset( false ); // anim number 0 is reserved for non-existant anims. to avoid off by one issues, just allocate an extra spot for // launch offsets so that anim number can be used without subtracting 1. missileLaunchOffset.SetGranularity( 1 ); missileLaunchOffset.SetNum( num + 1 ); missileLaunchOffset[ 0 ].Zero(); for( i = 1; i <= num; i++ ) { missileLaunchOffset[ i ].Zero(); anim = modelDef->GetAnim( i ); if ( anim ) { /* frame = anim->FindFrameForFrameCommand( FC_LAUNCHMISSILE, &command ); if ( frame >= 0 ) { joint = animator.GetJointHandle( command->string->c_str() ); if ( joint == INVALID_JOINT ) { gameLocal.Error( "Invalid joint '%s' on 'launch_missile' frame command on frame %d of model '%s'", command->string->c_str(), frame, modelDef->GetName() ); } GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis ); }*/ //ivan - fix for new framecommand --> this allows CanHitEnemyFromAnim to work frame = anim->FindFrameForFrameCommand( FC_FIREWEAPON, &command ); if ( frame >= 0 ) { //gameLocal.Printf("FC_FIREWEAPON trovato: '%s' \n", anim->Name()); joint = animator.GetJointHandle( "PISTOL_ATTACHER" ); //TODO: fix this hack. if ( joint == INVALID_JOINT ) { gameLocal.Error( "Invalid joint on 'fire_weapon' frame command on frame %d of model '%s'", frame, modelDef->GetName() ); } GetJointTransformForAnim( joint, i, FRAME2MS( frame ), missileLaunchOffset[ i ], axis ); }else{ //gameLocal.Printf("FC_FIREWEAPON non trovato: '%s' \n", anim->Name()); } //ivan end } } animator.RemoveOriginOffset( true ); } /* ===================== idAI_Bot::DropWeapon ===================== */ void idAI_Bot::DropWeapon( int weaponNum ) { int ammoToAdd; idVec3 velocity; idEntity *myweapon; idEntity *item; if(weaponNum < 0 || weaponNum >= weapons.Num() ){ return; } myweapon = weapons[ weaponNum ].ent.GetEntity(); item = NULL; const char *classname = myweapon->spawnArgs.GetString( "def_dropItem" ); if ( !classname[0] ) { return; } velocity = 150.0f * idAngles( -60, current_yaw, 0 ).ToForward(); item = idMoveableItem::DropItem( classname, myweapon->GetPhysics()->GetOrigin(), myweapon->GetPhysics()->GetAxis(), velocity , 200, AI_WEAPON_DROP_TIME ); if ( !item ) { return; } if(weapons[ weaponNum ].clipSize <= 0){ //do nothing if clipSize is unlimited //gameLocal.Printf("clipSize <= 0\n"); return; } const idKeyValue * keyval = item->spawnArgs.MatchPrefix( "inv_ammo_" ); if ( keyval ) { ammoToAdd = weapons[ weaponNum ].ammoInClip; if(ammoToAdd <= 0){ ammoToAdd = 1; } //at least 1 //gameLocal.Printf("clipSize %d \n",ammoToAdd); item->spawnArgs.SetInt( keyval->GetKey(), ammoToAdd ); } } /* ===================== idAI_Bot::Killed ===================== */ void idAI_Bot::Killed( idEntity *inflictor, idEntity *attacker, int damage, const idVec3 &dir, int location ) { idAI::Killed( inflictor, attacker, damage, dir, location ); // drop the current weapon DropWeapon(currentWeapon); //now we can remove all the weapons RemoveWeapons(); //be sure... flashJointWorld = INVALID_JOINT; //release the current node if ( currentNode.GetEntity() ) { currentNode.GetEntity()->Release(); currentNode = NULL; } } /* ================ idAI_Bot::RemoveWeapons ================ */ void idAI_Bot::RemoveWeapons( void ) { int i; idEntity *ent; // remove any attached weapons for( i = 0; i < weapons.Num(); i++ ) { //weapon entity ent = weapons[ i ].ent.GetEntity(); if ( ent ) { ent->PostEventMS( &EV_Remove, 0 ); } //projectileClipModel delete weapons[ i ].projectileClipModel; } weapons.Clear(); currentWeapon = -1; } /* =============================================================================== Weapon selection and utilities =============================================================================== */ /* ================ idAI_Bot::SelectInitialWeapon ================ */ void idAI_Bot::SelectInitialWeapon( void ) { int weaponNum; idStr initialweaponValue; currentWeapon = -1; //start without weapons weaponNum = -1; //default is none //get the initialweapon key initialweaponValue = spawnArgs.GetString( "initialweapon", "" ); if( initialweaponValue.Length() ){ weaponNum = GetWeaponNumByName( initialweaponValue ); //gameLocal.Printf("The initial requested weapon is: %d \n" ,weaponNum); SelectWeapon(weaponNum); //could upd currentWeapon } //still no weapon selected... try to select the higher one. if(currentWeapon < 0){ for( weaponNum = weapons.Num()-1; weaponNum >= 0; weaponNum-- ) { if ( weapons[ weaponNum ].enabled) { SelectWeapon(weaponNum); //currentWeapon = weaponNum; break; } } } //gameLocal.Printf("The initial weapon is: %d \n" ,currentWeapon); } /* ================ idAI_Bot::GetWeaponNumByName ================ */ int idAI_Bot::GetWeaponNumByName(idStr name) { int i; for( i = 0; i < weapons.Num(); i++ ) { if ( weapons[ i ].weaponName == name) { return i; } } return -1; //no weapon } /* ================ idAI_Bot::SelectWeapon ================ */ bool idAI_Bot::SelectWeapon( int weaponNum ) { //gameLocal.Printf("SelectWeapon requested: %d \n" ,weaponNum); if (weaponNum == currentWeapon){ //gameLocal.Printf("The requested weapon:%d is already selected\n" ,weaponNum); return false; } if( weaponNum < -1 || weaponNum >= weapons.Num()){ //if 3 allocated, max allowed value is 2. Note: -1 is accepted because means 'no weapon' gameLocal.Warning("The requested weapon: %d is out of range" ,weaponNum); return false; } if( (weaponNum >= 0) && !weapons[ weaponNum ].enabled){ gameLocal.Warning("The requested weapon: %d is disabled" ,weaponNum); return false; } //hide the current one if(currentWeapon >= 0){ weapons[ currentWeapon ].ent.GetEntity()->Hide(); } //show the new one if(weaponNum >= 0){ weapons[ weaponNum ].ent.GetEntity()->Show(); //ammo if(weapons[ weaponNum ].clipSize > 0){ //only if limited clip size if(weapons[ weaponNum ].ammoInClip <= 0){ AI_WEAPON_NEED_RELOAD = true; }else{ AI_WEAPON_NEED_RELOAD = false; } } }else{ //no weapon selected AI_WEAPON_NEED_RELOAD = false; } //remove the current projectileClipModel so that it'll be recreated the next time GetAimDir is called //DeleteProjectileClipModel(); //upd the currentWeapon currentWeapon = weaponNum; //gameLocal.Printf("SelectWeapon done: %d \n" ,currentWeapon); //remember this moment lastWeaponChangedTime = gameLocal.time; //flash joint if(currentWeapon >= 0){ flashJointWorld = weapons[ currentWeapon ].flashJoint; }else{ flashJointWorld = INVALID_JOINT; } //upd light setWeaponMuzzleFlash(); //ok because currentWeapon already updated //upd script AI_WEAPON_CHANGED = true; return true; } /* ================ idAI_Bot::ShowOnlyCurrentWeapon ================ */ void idAI_Bot::ShowOnlyCurrentWeapon( void ) { int i; idEntity *ent; // remove any attached entities for( i = 0; i < weapons.Num(); i++ ) { ent = weapons[ i ].ent.GetEntity(); if (i == currentWeapon ) { ent->Show(); }else{ ent->Hide(); } } } /* ================ idAI_Bot::TriggerWeaponEffects ================ */ void idAI_Bot::TriggerWeaponEffects( const idVec3 &muzzle ) { idVec3 org; idMat3 axis; idEntity *myWeaponEnt; if ( !g_muzzleFlash.GetBool() ) { return; } myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity(); if(myWeaponEnt->GetRenderEntity()){ myWeaponEnt->GetRenderEntity()->shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); myWeaponEnt->GetRenderEntity()->shaderParms[ SHADERPARM_DIVERSITY ] = gameLocal.random.CRandomFloat(); } if ( flashJointWorld != INVALID_JOINT ) { //flashJointWorld has been updated on weapon selection //was: GetJointWorldTransform( flashJointWorld, gameLocal.time, org, axis ); //get the reference to the weapon animator idAnimator *myWeaponAnimator; myWeaponAnimator = myWeaponEnt->GetAnimator(); //do the equivalent of GetJointWorldTransform even without being casted to idAnimatedEntity myWeaponAnimator->GetJointTransform( flashJointWorld, gameLocal.time, org, axis ); myWeaponEnt->ConvertLocalToWorldTransform( org, axis ); if ( worldMuzzleFlash.lightRadius.x > 0.0f ) { worldMuzzleFlash.axis = axis; worldMuzzleFlash.shaderParms[SHADERPARM_TIMEOFFSET] = -MS2SEC( gameLocal.time ); if ( worldMuzzleFlashHandle != - 1 ) { gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); } else { worldMuzzleFlashHandle = gameRenderWorld->AddLightDef( &worldMuzzleFlash ); } muzzleFlashEnd = gameLocal.time + flashTime; UpdateVisuals(); } } } /* ================ idAI_Bot::UpdateMuzzleFlash ================ */ void idAI_Bot::UpdateMuzzleFlash( void ) { if ( worldMuzzleFlashHandle != -1 ) { if ( gameLocal.time >= muzzleFlashEnd ) { gameRenderWorld->FreeLightDef( worldMuzzleFlashHandle ); worldMuzzleFlashHandle = -1; } else { if ( flashJointWorld != INVALID_JOINT ) { //flashJointWorld has been updated on weapon selection idVec3 muzzle; idEntity *myWeaponEnt; idAnimator *myWeaponAnimator; //get the reference to the weapon and its animator myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity(); myWeaponAnimator = myWeaponEnt->GetAnimator(); //do the equivalent of GetJointWorldTransform even without being casted to idAnimatedEntity myWeaponAnimator->GetJointTransform( flashJointWorld, gameLocal.time, muzzle, worldMuzzleFlash.axis ); myWeaponEnt->ConvertLocalToWorldTransform( muzzle, worldMuzzleFlash.axis ); worldMuzzleFlash.origin = muzzle; gameRenderWorld->UpdateLightDef( worldMuzzleFlashHandle, &worldMuzzleFlash ); } } } } /* =================== idAI_Bot::InitMuzzleFlash =================== */ void idAI_Bot::InitMuzzleFlash( void ) { //called on spawn and on relaod. NOTE: in both cases weapons are not created yet --> just set default values. //gameLocal.Printf("idAI_Bot::InitMuzzleFlash\n"); flashTime = 0; memset( &worldMuzzleFlash, 0, sizeof ( worldMuzzleFlash ) ); worldMuzzleFlash.pointLight = true; worldMuzzleFlash.shader = NULL; worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = 0.0f; worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = 0.0f; worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = 0.0f; worldMuzzleFlash.shaderParms[ SHADERPARM_ALPHA ] = 1.0f; worldMuzzleFlash.shaderParms[ SHADERPARM_TIMESCALE ] = 1.0f; worldMuzzleFlash.lightRadius[0] = 0.0f; worldMuzzleFlash.lightRadius[1] = 0.0f; worldMuzzleFlash.lightRadius[2] = 0.0f; worldMuzzleFlash.noShadows = spawnArgs.GetBool( "flashNoShadows" ); worldMuzzleFlashHandle = -1; } /* ================ idAI_Bot::setWeaponMuzzleFlash ================ */ void idAI_Bot::setWeaponMuzzleFlash( void ) { const idMaterial * shader; idVec3 flashColor; float flashRadius; if(currentWeapon < 0 || currentWeapon >= weapons.Num()){ shader = NULL; flashColor = vec3_origin; flashRadius = 0.0f; flashTime = 0; //global }else{ shader = weapons[ currentWeapon ].shader; flashColor = weapons[ currentWeapon ].flashColor; flashRadius = weapons[ currentWeapon ].flashRadius; flashTime = weapons[ currentWeapon ].flashTime; //global } worldMuzzleFlash.shader = shader; worldMuzzleFlash.shaderParms[ SHADERPARM_RED ] = flashColor[0]; worldMuzzleFlash.shaderParms[ SHADERPARM_GREEN ] = flashColor[1]; worldMuzzleFlash.shaderParms[ SHADERPARM_BLUE ] = flashColor[2]; worldMuzzleFlash.lightRadius[0] = flashRadius; worldMuzzleFlash.lightRadius[1] = flashRadius; worldMuzzleFlash.lightRadius[2] = flashRadius; } /* =============================================================================== Projectiles stuff =============================================================================== */ /* ===================== idAI_Bot::DeleteProjectileClipModel ===================== void idAI_Bot::DeleteProjectileClipModel( void ) const { //remove the current projectileClipModel so that it'll be recreated the next time GetAimDir or other functions are called delete projectileClipModel; projectileClipModel = NULL; } */ /* ===================== idAI_Bot::CreateProjectileClipModel ===================== */ void idAI_Bot::CreateProjectileClipModel( void ) const { //warning! a weapon needs to be selected when you call this! if ( weapons[ currentWeapon ].projectileClipModel == NULL ) { idBounds projectileBounds( vec3_origin ); projectileBounds.ExpandSelf( weapons[ currentWeapon ].projectileRadius ); weapons[ currentWeapon ].projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) ); } } /* ===================== idAI_Bot::CreateProjectileClipModel ===================== */ void idAI_Bot::CreateProjectileClipModel( int requestedWeapon ) const { //warning! make sure the requested weapon exists when you call this! if ( weapons[ requestedWeapon ].projectileClipModel == NULL ) { idBounds projectileBounds( vec3_origin ); projectileBounds.ExpandSelf( weapons[ requestedWeapon ].projectileRadius ); weapons[ requestedWeapon ].projectileClipModel = new idClipModel( idTraceModel( projectileBounds ) ); } } /* ===================== idAI_Bot::CreateProjectile ===================== */ idProjectile *idAI_Bot::CreateProjectile( const idVec3 &pos, const idVec3 &dir ) { //NOTE: make sure a weapon is selected and it has a weapon before calling this idEntity *ent; const char *clsname; const idDict * currentProjectileDef; if ( !projectile.GetEntity() ) { currentProjectileDef = weapons[ currentWeapon ].projectileDef; gameLocal.SpawnEntityDef( *currentProjectileDef, &ent, false ); if ( !ent ) { clsname = currentProjectileDef->GetString( "classname" ); gameLocal.Error( "Could not spawn entityDef '%s'", clsname ); } if ( !ent->IsType( idProjectile::Type ) ) { clsname = ent->GetClassname(); gameLocal.Error( "'%s' is not an idProjectile", clsname ); } projectile = ( idProjectile * )ent; } projectile.GetEntity()->Create( this, pos, dir ); return projectile.GetEntity(); } /* ===================== idAI_Bot::LaunchProjectile ===================== */ idProjectile *idAI_Bot::LaunchProjectile( const char *jointname, idEntity *target, bool clampToAttackCone ) { idVec3 muzzle; idVec3 dir; idVec3 start; trace_t tr; idBounds projBounds; float distance; const idClipModel *projClip; float attack_accuracy; float attack_cone; float projectile_spread; float diff; float angle; float spin; idAngles ang; int num_projectiles; int i; idMat3 axis; idVec3 tmp; idProjectile *lastProjectile; if ( currentWeapon < 0){ gameLocal.Warning( "%s (%s) is trying to fire but doesn't have a weapon selected", name.c_str(), GetEntityDefName() ); return NULL; } if ( !weapons[ currentWeapon ].projectileDef ) { gameLocal.Warning( "%s (%s) : the current weapon doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); return NULL; } attack_accuracy = weapons[ currentWeapon ].attack_accuracy; attack_cone = spawnArgs.GetFloat( "attack_cone", "70" ); projectile_spread = weapons[ currentWeapon ].projectile_spread; num_projectiles = weapons[ currentWeapon ].num_projectiles; GetMuzzle( jointname, muzzle, axis ); if ( !projectile.GetEntity() ) { CreateProjectile( muzzle, axis[ 0 ] ); } lastProjectile = projectile.GetEntity(); if ( target != NULL ) { tmp = target->GetPhysics()->GetAbsBounds().GetCenter() - muzzle; tmp.Normalize(); axis = tmp.ToMat3(); } else { axis = viewAxis; } // rotate it because the cone points up by default tmp = axis[2]; axis[2] = axis[0]; axis[0] = -tmp; // make sure the projectile starts inside the monster bounding box const idBounds &ownerBounds = physicsObj.GetAbsBounds(); projClip = lastProjectile->GetPhysics()->GetClipModel(); projBounds = projClip->GetBounds().Rotate( axis ); // check if the owner bounds is bigger than the projectile bounds if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if ( (ownerBounds - projBounds).RayIntersection( muzzle, viewAxis[ 0 ], distance ) ) { start = muzzle + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, muzzle, projClip, axis, MASK_SHOT_RENDERMODEL, this ); muzzle = tr.endpos; // set aiming direction GetAimDirByWeapon( muzzle, target, this, dir, currentWeapon); ang = dir.ToAngles(); // adjust his aim so it's not perfect. uses sine based movement so the tracers appear less random in their spread. float t = MS2SEC( gameLocal.time + entityNumber * 497 ); ang.pitch += idMath::Sin16( t * 5.1 ) * attack_accuracy; ang.yaw += idMath::Sin16( t * 6.7 ) * attack_accuracy; if ( clampToAttackCone ) { // clamp the attack direction to be within monster's attack cone so he doesn't do // things like throw the missile backwards if you're behind him diff = idMath::AngleDelta( ang.yaw, current_yaw ); if ( diff > attack_cone ) { ang.yaw = current_yaw + attack_cone; } else if ( diff < -attack_cone ) { ang.yaw = current_yaw - attack_cone; } } axis = ang.ToMat3(); float spreadRad = DEG2RAD( projectile_spread ); for( i = 0; i < num_projectiles; i++ ) { // spread the projectiles out angle = idMath::Sin( spreadRad * gameLocal.random.RandomFloat() ); spin = (float)DEG2RAD( 360.0f ) * gameLocal.random.RandomFloat(); dir = axis[ 0 ] + axis[ 2 ] * ( angle * idMath::Sin( spin ) ) - axis[ 1 ] * ( angle * idMath::Cos( spin ) ); dir.Normalize(); // launch the projectile if ( !projectile.GetEntity() ) { CreateProjectile( muzzle, dir ); } lastProjectile = projectile.GetEntity(); lastProjectile->Launch( muzzle, dir, vec3_origin ); projectile = NULL; } //weapon fxs TriggerWeaponEffects( muzzle ); //ammo if(weapons[ currentWeapon ].clipSize > 0){ //only if limited clip size weapons[ currentWeapon ].ammoInClip--; if(weapons[ currentWeapon ].ammoInClip <= 0){ AI_WEAPON_NEED_RELOAD = true; } } lastAttackTime = gameLocal.time; return lastProjectile; } /* =============================================================================== Misc =============================================================================== */ /* ===================== idAI_Bot::GetAimDir ===================== */ bool idAI_Bot::GetAimDir( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir ) const { return GetAimDirByWeapon( firePos, aimAtEnt, ignore, aimDir, currentWeapon); } /* ===================== idAI_Bot::GetAimDirByWeapon ===================== */ bool idAI_Bot::GetAimDirByWeapon( const idVec3 &firePos, idEntity *aimAtEnt, const idEntity *ignore, idVec3 &aimDir, int weaponToTest) const { idVec3 targetPos1; idVec3 targetPos2; idVec3 delta; float max_height; bool result; //if no weapon is selected if ( weaponToTest < 0 || weaponToTest >= weapons.Num()){ gameLocal.Warning( "idAI_Bot::GetAimDirByWeapon: the weapon to test is out of range" ); return false; } // if no aimAtEnt or projectile set if ( !aimAtEnt || !weapons[ weaponToTest ].projectileDef ) { aimDir = viewAxis[ 0 ] * physicsObj.GetGravityAxis(); return false; } if ( weapons[ weaponToTest ].projectileClipModel == NULL ) { CreateProjectileClipModel( weaponToTest ); } if ( aimAtEnt == enemy.GetEntity() ) { static_cast( aimAtEnt )->GetAIAimTargets( lastVisibleEnemyPos, targetPos1, targetPos2 ); } else if ( aimAtEnt->IsType( idActor::Type ) ) { static_cast( aimAtEnt )->GetAIAimTargets( aimAtEnt->GetPhysics()->GetOrigin(), targetPos1, targetPos2 ); } else { targetPos1 = aimAtEnt->GetPhysics()->GetAbsBounds().GetCenter(); targetPos2 = targetPos1; } // try aiming for chest delta = firePos - targetPos2; max_height = delta.LengthFast() * projectile_height_to_distance_ratio; result = PredictTrajectory( firePos, targetPos2, weapons[ weaponToTest ].projectileSpeed, weapons[ weaponToTest ].projectileGravity, weapons[ weaponToTest ].projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); if ( result || !aimAtEnt->IsType( idActor::Type ) ) { return result; } // try aiming for head delta = firePos - targetPos1; max_height = delta.LengthFast() * projectile_height_to_distance_ratio; result = PredictTrajectory( firePos, targetPos1, weapons[ weaponToTest ].projectileSpeed, weapons[ weaponToTest ].projectileGravity, weapons[ weaponToTest ].projectileClipModel, MASK_SHOT_RENDERMODEL, max_height, ignore, aimAtEnt, ai_debugTrajectory.GetBool() ? 1000 : 0, aimDir ); return result; } /* ================ idAI_Bot::GetMuzzle ================ */ void idAI_Bot::GetMuzzle( const char *jointname, idVec3 &muzzle, idMat3 &axis ) { jointHandle_t joint; if ( !jointname || !jointname[ 0 ] ) { //new behaviour: is nothing is passed, use the current weapon settings if(currentWeapon >= 0){ //WEAPON CASE 1: weapon fireJoint! if(weapons[ currentWeapon ].fireJoint != INVALID_JOINT){ //if fireJoint is valid, we are sure the entity has an MD5 model idEntity *myWeaponEnt; idAnimator *myWeaponAnimator; //get the reference to the weapon and its animator myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity(); myWeaponAnimator = myWeaponEnt->GetAnimator(); //use the fireJoint! joint = weapons[ currentWeapon ].fireJoint; //myWeaponAnimator->GetJointHandle( "barrel" ); // //do the equivalent of GetJointWorldTransform even without being casted to idAnimatedEntity myWeaponAnimator->GetJointTransform( joint, gameLocal.time, muzzle, axis ); myWeaponEnt->ConvertLocalToWorldTransform( muzzle, axis ); //gameLocal.Printf("GetMuzzle che usa fireJoint\n"); } //WEAPON CASE 2: my bindJoint ! else{ joint = weapons[ currentWeapon ].bindJoint; //bindJoint is always valid GetJointWorldTransform( joint, gameLocal.time, muzzle, axis ); //gameLocal.Printf("GetMuzzle che usa bindJoint\n"); } //NO-WEAPON CASE 1: my origin }else{ muzzle = physicsObj.GetOrigin() + viewAxis[ 0 ] * physicsObj.GetGravityAxis() * 14; muzzle -= physicsObj.GetGravityNormal() * physicsObj.GetBounds()[ 1 ].z * 0.5f; } //NO-WEAPON CASE 2: my specified joint } else { joint = animator.GetJointHandle( jointname ); if ( joint == INVALID_JOINT ) { gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); } GetJointWorldTransform( joint, gameLocal.time, muzzle, axis ); } } /* ================ idAI_Bot::Show ================ */ void idAI_Bot::Show( void ) { idAI::Show(); ShowOnlyCurrentWeapon(); } /* =============================================================================== Weapon events =============================================================================== */ /* ================ idAI_Bot::Event_SelectWeapon ================ */ void idAI_Bot::Event_SelectWeapon( int weaponNum ){ //gameLocal.Printf("Event_SelectWeapon requested: %d \n" ,weaponNum); idThread::ReturnInt( SelectWeapon( weaponNum ) ); } /* ================ idAI_Bot::Event_SelectAnotherWeapon ================ */ void idAI_Bot::Event_SelectAnotherWeapon( int idealLevel, int minLevel ){ int testWeapon; int nextWeapon; int wCounter; int wNum; wNum = weapons.Num(); //ideal loop testWeapon = currentWeapon; nextWeapon = currentWeapon; for( wCounter = 0; wCounter < wNum; wCounter++ ) { //do exactly a complete loop (also test the current weapon so that it's ok even if we start from -1) //find the next one testWeapon++; if(testWeapon >= wNum){testWeapon = 0;} //test it if ( (weapons[ testWeapon ].enabled) && (weapons[ testWeapon ].priorityLevel >= idealLevel) ) { nextWeapon = testWeapon; break; } } if(nextWeapon != currentWeapon){ //found! idThread::ReturnInt( SelectWeapon( nextWeapon ) ); //it's enabled and on range, so no way this could be false. return; } //minlevel loop testWeapon = currentWeapon; nextWeapon = currentWeapon; for( wCounter = 0; wCounter < wNum; wCounter++ ) { //do exactly a complete loop (also test the current weapon so that it's ok even if we start from -1) //find the next one testWeapon++; if(testWeapon >= wNum){testWeapon = 0;} //test it if ( (weapons[ testWeapon ].enabled) && (weapons[ testWeapon ].priorityLevel >= minLevel) ) { nextWeapon = testWeapon; break; } } if(nextWeapon != currentWeapon){ //found! idThread::ReturnInt( SelectWeapon( nextWeapon ) ); //it's enabled and on range, so no way this can be false. return; } //nothing can satisfy the conditions idThread::ReturnInt( false ); } /* ================ idAI_Bot::Event_ReloadWeapon ================ */ void idAI_Bot::Event_ReloadWeapon( void ){ AI_WEAPON_NEED_RELOAD = false; if( currentWeapon < 0) return; weapons[ currentWeapon ].ammoInClip = weapons[ currentWeapon ].clipSize; } /* ================ idAI_Bot::Event_CheckReloadTolerance ================ */ void idAI_Bot::Event_CheckReloadTolerance( void ){ //returns true if we should reload if( currentWeapon < 0){ //no weapon selected idThread::ReturnInt( false ); return; } if( weapons[ currentWeapon ].clipSize == 0){ //unlimited clip idThread::ReturnInt( false ); return; } if( weapons[ currentWeapon ].ammoInClip > weapons[ currentWeapon ].reloadTolerance ){ //still enough ammo idThread::ReturnInt( false ); return; } //reloading is a good idea at this point... idThread::ReturnInt( true ); return; } /* ================ idAI_Bot::Event_GetAmmoInClip ================ */ void idAI_Bot::Event_GetAmmoInClip( void ){ if( currentWeapon < 0){ idThread::ReturnInt( 0 ); return; } idThread::ReturnInt( weapons[ currentWeapon ].ammoInClip ); } /* ================ idAI_Bot::Event_GetClipSize ================ */ void idAI_Bot::Event_GetClipSize( void ){ if( currentWeapon < 0){ idThread::ReturnInt( 0 ); return; } idThread::ReturnInt( weapons[ currentWeapon ].clipSize ); } /* ================ idAI_Bot::Event_GetCurrentWeapon ================ */ void idAI_Bot::Event_GetCurrentWeapon( void ){ idThread::ReturnInt( currentWeapon ); } /* ================ idAI_Bot::Event_GetWeaponNumByName ================ */ void idAI_Bot::Event_GetWeaponNumByName( const char *weaponName ){ int weaponNum; weaponNum = GetWeaponNumByName( idStr( weaponName ) ); idThread::ReturnInt( weaponNum ); } /* =============================================================================== Anims events =============================================================================== */ /* ===================== idAI_Bot::Event_GetIdleAnim ===================== */ void idAI_Bot::Event_GetIdleAnim( void ) { const char *animname; if(currentWeapon < 0){ animname = "idle"; }else{ animname = va( "%s_idle", weapons[ currentWeapon ].weaponName.c_str() ); } idThread::ReturnString( animname ); } /* ===================== idAI_Bot::Event_GetReloadAnim ===================== */ void idAI_Bot::Event_GetReloadAnim( void ) { const char *animname; if(currentWeapon < 0){ animname = "idle"; //in case someone tries to reload but no weapon is selected :) }else{ animname = va( "%s_reload", weapons[ currentWeapon ].weaponName.c_str() ); } idThread::ReturnString( animname ); } /* ===================== idAI_Bot::Event_GetReloadAnim ===================== */ void idAI_Bot::Event_GetFireAnim( void ) { const char *animname; if(currentWeapon < 0){ animname = "idle"; //in case someone tries to fire but no weapon is selected :) }else{ animname = va( "%s_fire", weapons[ currentWeapon ].weaponName.c_str() ); } idThread::ReturnString( animname ); } /* =============================================================================== Fire events =============================================================================== */ /* ===================== idAI_Bot::Event_FireWeapon ===================== */ void idAI_Bot::Event_FireWeapon( void ) { idProjectile *proj; proj = LaunchProjectile( "", enemy.GetEntity(), true ); //"" will force the use of weapon specific settings idThread::ReturnEntity( proj ); } /* ===================== idAI_Bot::Event_CreateMissile ===================== */ void idAI_Bot::Event_CreateMissile( const char *jointname ) { idVec3 muzzle; idMat3 axis; //gameLocal.Printf("idAI_Bot::Event_CreateMissile \n"); //ivan start if ( currentWeapon < 0){ gameLocal.Warning( "idAI_Bot::Event_CreateMissile: %s (%s) is trying to fire but doesn't have a weapon selected", name.c_str(), GetEntityDefName() ); return idThread::ReturnEntity( NULL ); } if ( !weapons[ currentWeapon ].projectileDef ) { gameLocal.Warning( "idAI_Bot::Event_CreateMissile: %s (%s) : the current weapon doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); return idThread::ReturnEntity( NULL ); } //ivan end GetMuzzle( jointname, muzzle, axis ); CreateProjectile( muzzle, viewAxis[ 0 ] * physicsObj.GetGravityAxis() ); if ( projectile.GetEntity() ) { if ( !jointname || !jointname[ 0 ] ) { projectile.GetEntity()->Bind( this, true ); } else { projectile.GetEntity()->BindToJoint( this, jointname, true ); } } idThread::ReturnEntity( projectile.GetEntity() ); } /* ===================== idAI_Bot::Event_LaunchMissile ===================== */ void idAI_Bot::Event_LaunchMissile( const idVec3 &org, const idAngles &ang ) { idVec3 start; trace_t tr; idBounds projBounds; const idClipModel *projClip; idMat3 axis; float distance; //gameLocal.Printf("idAI_Bot::Event_LaunchMissile \n"); //ivan start if ( currentWeapon < 0){ gameLocal.Warning( "idAI_Bot::Event_LaunchMissile: %s (%s) is trying to fire but doesn't have a weapon selected", name.c_str(), GetEntityDefName() ); idThread::ReturnEntity( NULL ); return; } if ( !weapons[ currentWeapon ].projectileDef ) { gameLocal.Warning( "idAI_Bot::Event_LaunchMissile: %s (%s) : the current weapon doesn't have a projectile specified", name.c_str(), GetEntityDefName() ); idThread::ReturnEntity( NULL ); return; } //ivan end axis = ang.ToMat3(); if ( !projectile.GetEntity() ) { CreateProjectile( org, axis[ 0 ] ); } // make sure the projectile starts inside the monster bounding box const idBounds &ownerBounds = physicsObj.GetAbsBounds(); projClip = projectile.GetEntity()->GetPhysics()->GetClipModel(); projBounds = projClip->GetBounds().Rotate( projClip->GetAxis() ); // check if the owner bounds is bigger than the projectile bounds if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, org, projClip, projClip->GetAxis(), MASK_SHOT_RENDERMODEL, this ); // launch the projectile idThread::ReturnEntity( projectile.GetEntity() ); projectile.GetEntity()->Launch( tr.endpos, axis[ 0 ], vec3_origin ); projectile = NULL; TriggerWeaponEffects( tr.endpos ); lastAttackTime = gameLocal.time; } /* =============================================================================== CanHit events =============================================================================== */ /* ===================== idAI_Bot::Event_CanHitEnemyFromCurrentWeapon ===================== */ void idAI_Bot::Event_CanHitEnemyFromCurrentWeapon( void ) { trace_t tr; idVec3 muzzle; idMat3 axis; idVec3 start; //float distance; idActor *enemyEnt = enemy.GetEntity(); if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { idThread::ReturnInt( false ); return; } //ivan start if( currentWeapon < 0 ){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromCurrentWeapon: no weapon selected!"); idThread::ReturnInt( false ); return; } if( !weapons[ currentWeapon ].projectileDef ){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromCurrentWeapon: no projectile defined!"); idThread::ReturnInt( false ); return; } //ivan end // don't check twice per frame - makes sense because no parameters can be passed if ( gameLocal.time == lastHitFCWCheckTime ) { idThread::ReturnInt( lastHitFCWCheckResult ); return; } lastHitFCWCheckTime = gameLocal.time; //get muzzle and destination positions GetMuzzle( "", muzzle, axis ); //"" will force use the weapon fire position idVec3 toPos = enemyEnt->GetEyePosition(); if ( weapons[ currentWeapon ].projectileClipModel == NULL ) { CreateProjectileClipModel(); } gameLocal.clip.Translation( tr, muzzle, toPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { //gameLocal.Printf("OK FromCurrentWeapon!\n"); lastHitFCWCheckResult = true; } else { //gameLocal.Printf("NO FromCurrentWeapon!\n"); lastHitFCWCheckResult = false; } idThread::ReturnInt( lastHitFCWCheckResult ); } /* ===================== idAI_Bot::Event_CanFireToEnemyNoSelfDamage ===================== */ void idAI_Bot::Event_CanFireToEnemyNoSelfDamage( int requireVisible ) { trace_t tr; idVec3 muzzle; idMat3 axis; idVec3 start; //float distance; idActor *enemyEnt = enemy.GetEntity(); if ( (!AI_ENEMY_VISIBLE && requireVisible != 0 ) || !enemyEnt ) { idThread::ReturnInt( false ); return; } //ivan start if( currentWeapon < 0 ){ gameLocal.Warning("idAI_Bot::Event_CanFireToEnemyNoSelfDamage: no weapon selected!"); idThread::ReturnInt( false ); return; } if( !weapons[ currentWeapon ].projectileDef ){ gameLocal.Warning("idAI_Bot::Event_CanFireToEnemyNoSelfDamage: no projectile defined!"); idThread::ReturnInt( false ); return; } //ivan end // don't check twice per frame - makes sense because if (requireVisible = true && !AI_ENEMY_VISIBLE) the timer is not updated, so in any case if we are here we are doing the same thing again if ( gameLocal.time == lastHitNSDCheckTime ) { idThread::ReturnInt( lastHitNSDCheckResult ); return; } lastHitNSDCheckTime = gameLocal.time; //get muzzle and destination positions GetMuzzle( "", muzzle, axis ); //"" will force use the weapon fire position idVec3 toPos = enemyEnt->GetEyePosition(); if ( weapons[ currentWeapon ].projectileClipModel == NULL ) { CreateProjectileClipModel(); } gameLocal.clip.Translation( tr, muzzle, toPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); float dist = ( muzzle - tr.endpos ).LengthFast(); if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { //gameLocal.Printf("OK NoSelfDamage!\n"); lastHitNSDCheckResult = true; } else if (dist > weapons[ currentWeapon ].minSelfDmgDistance ){ //60.0f //gameLocal.Printf("OK NoSelfDamage perche distante %f!\n", dist); lastHitNSDCheckResult = true; } else { //gameLocal.Printf("NO NoSelfDamage!\n"); lastHitNSDCheckResult = false; } idThread::ReturnInt( lastHitNSDCheckResult ); } /* ===================== idAI_Bot::Event_CanFireNoSelfDamageFromAnim ===================== void idAI_Bot::Event_CanFireNoSelfDamageFromAnim( const char *animname, int requireVisible ) { } */ /* ===================== idAI_Bot::Event_CanHitEnemyFromFireAnim ===================== */ void idAI_Bot::Event_CanHitEnemyFromFireAnim( int weaponToTest ) { //note: it uses the proj of the requested weapon int anim; idVec3 dir; idVec3 local_dir; idVec3 fromPos; idMat3 axis; idVec3 start; trace_t tr; float distance; const char *animname; //gameLocal.Printf("idAI_Bot::Event_CanHitEnemyFromFireAnim called! \n" ); idActor *enemyEnt = enemy.GetEntity(); if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { idThread::ReturnInt( false ); return; } //ivan start if(weaponToTest < 0 || weaponToTest >= weapons.Num()){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromFireAnim: requested weapon is out of range!"); idThread::ReturnInt( false ); return; } if( !weapons[ weaponToTest ].projectileDef ){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromFireAnim: requested weapon has no projectile defined!"); idThread::ReturnInt( false ); return; } //ivan end animname = va( "%s_fire", weapons[ weaponToTest ].weaponName.c_str() ); anim = GetAnim( ANIMCHANNEL_LEGS, animname ); if ( !anim ) { gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromFireAnim: requested weapon has no fire anim defined!"); idThread::ReturnInt( false ); return; } // just do a ray test if close enough if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { Event_CanHitEnemy(); return; } // calculate the world transform of the launch position const idVec3 &org = physicsObj.GetOrigin(); dir = lastVisibleEnemyPos - org; physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); local_dir.z = 0.0f; local_dir.ToVec2().Normalize(); axis = local_dir.ToMat3(); fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis; //ivan: //fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * viewAxis * physicsObj.GetGravityAxis(); if ( weapons[ weaponToTest ].projectileClipModel == NULL ) { //TODO: use the requested one!!! CreateProjectileClipModel(weaponToTest); } // check if the owner bounds is bigger than the projectile bounds const idBounds &ownerBounds = physicsObj.GetAbsBounds(); const idBounds &projBounds = weapons[ weaponToTest ].projectileClipModel->GetBounds(); if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, fromPos, weapons[ weaponToTest ].projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this ); fromPos = tr.endpos; if ( GetAimDirByWeapon( fromPos, enemy.GetEntity(), this, dir, weaponToTest) ) { //gameLocal.Printf("idAI_Bot::Event_CanHitEnemyFromAnim true!\n"); idThread::ReturnInt( true ); } else { //gameLocal.Printf("idAI_Bot::Event_CanHitEnemyFromAnim false!\n"); idThread::ReturnInt( false ); } } /* ===================== idAI_Bot::Event_CanHitEnemyFromAnim ===================== */ void idAI_Bot::Event_CanHitEnemyFromAnim( const char *animname ) { //deprecated: it uses the current projectile! int anim; idVec3 dir; idVec3 local_dir; idVec3 fromPos; idMat3 axis; idVec3 start; trace_t tr; float distance; gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromAnim is deprecated: it uses the current projectile!"); idActor *enemyEnt = enemy.GetEntity(); if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { idThread::ReturnInt( false ); return; } //ivan start if( currentWeapon < 0 ){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromAnim: no weapon selected!"); idThread::ReturnInt( false ); return; } if( !weapons[ currentWeapon ].projectileDef ){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromAnim: no projectile defined!"); idThread::ReturnInt( false ); return; } //ivan end anim = GetAnim( ANIMCHANNEL_LEGS, animname ); if ( !anim ) { idThread::ReturnInt( false ); return; } // just do a ray test if close enough if ( enemyEnt->GetPhysics()->GetAbsBounds().IntersectsBounds( physicsObj.GetAbsBounds().Expand( 16.0f ) ) ) { Event_CanHitEnemy(); return; } // calculate the world transform of the launch position const idVec3 &org = physicsObj.GetOrigin(); dir = lastVisibleEnemyPos - org; physicsObj.GetGravityAxis().ProjectVector( dir, local_dir ); local_dir.z = 0.0f; local_dir.ToVec2().Normalize(); axis = local_dir.ToMat3(); fromPos = physicsObj.GetOrigin() + missileLaunchOffset[ anim ] * axis; if ( weapons[ currentWeapon ].projectileClipModel == NULL ) { CreateProjectileClipModel(); } // check if the owner bounds is bigger than the projectile bounds const idBounds &ownerBounds = physicsObj.GetAbsBounds(); const idBounds &projBounds = weapons[ currentWeapon ].projectileClipModel->GetBounds(); if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, fromPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_RENDERMODEL, this ); fromPos = tr.endpos; if ( GetAimDirByWeapon( fromPos, enemy.GetEntity(), this, dir, currentWeapon ) ) { idThread::ReturnInt( true ); } else { idThread::ReturnInt( false ); } } /* ===================== idAI_Bot::Event_CanHitEnemyFromJoint ===================== */ void idAI_Bot::Event_CanHitEnemyFromJoint( const char *jointname ) { //deprecated: it uses the current projectile! trace_t tr; idVec3 muzzle; idMat3 axis; idVec3 start; float distance; gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromJoint is deprecated: it uses the current projectile!"); idActor *enemyEnt = enemy.GetEntity(); if ( !AI_ENEMY_VISIBLE || !enemyEnt ) { idThread::ReturnInt( false ); return; } //ivan start if( currentWeapon < 0 ){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromJoint: no weapon selected!"); idThread::ReturnInt( false ); return; } if( !weapons[ currentWeapon ].projectileDef ){ gameLocal.Warning("idAI_Bot::Event_CanHitEnemyFromJoint: no projectile defined!"); idThread::ReturnInt( false ); return; } //ivan end // don't check twice per frame - we cannot check more than 1 joint per frame! if ( gameLocal.time == lastHitCheckTime ) { idThread::ReturnInt( lastHitCheckResult ); return; } lastHitCheckTime = gameLocal.time; const idVec3 &org = physicsObj.GetOrigin(); idVec3 toPos = enemyEnt->GetEyePosition(); jointHandle_t joint = animator.GetJointHandle( jointname ); if ( joint == INVALID_JOINT ) { gameLocal.Error( "Unknown joint '%s' on %s", jointname, GetEntityDefName() ); } animator.GetJointTransform( joint, gameLocal.time, muzzle, axis ); muzzle = org + ( muzzle + modelOffset ) * viewAxis * physicsObj.GetGravityAxis(); if ( weapons[ currentWeapon ].projectileClipModel == NULL ) { CreateProjectileClipModel(); } // check if the owner bounds is bigger than the projectile bounds const idBounds &ownerBounds = physicsObj.GetAbsBounds(); const idBounds &projBounds = weapons[ currentWeapon ].projectileClipModel->GetBounds(); if ( ( ( ownerBounds[1][0] - ownerBounds[0][0] ) > ( projBounds[1][0] - projBounds[0][0] ) ) && ( ( ownerBounds[1][1] - ownerBounds[0][1] ) > ( projBounds[1][1] - projBounds[0][1] ) ) && ( ( ownerBounds[1][2] - ownerBounds[0][2] ) > ( projBounds[1][2] - projBounds[0][2] ) ) ) { if ( (ownerBounds - projBounds).RayIntersection( org, viewAxis[ 0 ], distance ) ) { start = org + distance * viewAxis[ 0 ]; } else { start = ownerBounds.GetCenter(); } } else { // projectile bounds bigger than the owner bounds, so just start it from the center start = ownerBounds.GetCenter(); } gameLocal.clip.Translation( tr, start, muzzle, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); muzzle = tr.endpos; gameLocal.clip.Translation( tr, muzzle, toPos, weapons[ currentWeapon ].projectileClipModel, mat3_identity, MASK_SHOT_BOUNDINGBOX, this ); if ( tr.fraction >= 1.0f || ( gameLocal.GetTraceEntity( tr ) == enemyEnt ) ) { lastHitCheckResult = true; } else { lastHitCheckResult = false; } idThread::ReturnInt( lastHitCheckResult ); } /* ===================== idAI_Bot::Event_FindEnemyAI ===================== */ void idAI_Bot::Event_FindEnemyAIorPL( int useFOV ) { idEntity *ent; idActor *actor; idActor *bestEnemy; float bestDist; float dist; idVec3 delta; pvsHandle_t pvs; int i; pvs = gameLocal.pvs.SetupCurrentPVS( GetPVSAreas(), GetNumPVSAreas() ); bestDist = idMath::INFINITY; bestEnemy = NULL; for ( ent = gameLocal.activeEntities.Next(); ent != NULL; ent = ent->activeNode.Next() ) { if ( ent->fl.hidden || ent->fl.isDormant || !ent->IsType( idActor::Type ) ) { continue; } actor = static_cast( ent ); if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { continue; } if ( !gameLocal.pvs.InCurrentPVS( pvs, actor->GetPVSAreas(), actor->GetNumPVSAreas() ) ) { continue; } delta = physicsObj.GetOrigin() - actor->GetPhysics()->GetOrigin(); dist = delta.LengthSqr(); if ( ( dist < bestDist ) && CanSee( actor, useFOV != 0 ) ) { bestDist = dist; bestEnemy = actor; } } gameLocal.pvs.FreeCurrentPVS( pvs ); if(bestEnemy != NULL){ idThread::ReturnEntity( bestEnemy ); return; } //else player! if ( gameLocal.InPlayerPVS( this ) ) { for ( i = 0; i < gameLocal.numClients ; i++ ) { ent = gameLocal.entities[ i ]; if ( !ent || !ent->IsType( idActor::Type ) ) { continue; } actor = static_cast( ent ); if ( ( actor->health <= 0 ) || !( ReactionTo( actor ) & ATTACK_ON_SIGHT ) ) { continue; } if ( CanSee( actor, useFOV != 0 ) ) { idThread::ReturnEntity( actor ); return; } } } idThread::ReturnEntity( NULL ); } /* ===================== idAI::Event_Burn ===================== */ void idAI_Bot::Event_Burn( void ) { idAI::Event_Burn(); idEntity *myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity(); if(myWeaponEnt){ myWeaponEnt->GetRenderEntity()->shaderParms[ SHADERPARM_TIME_OF_DEATH ] = gameLocal.time * 0.001f; } } /* =============================================================================== Misc =============================================================================== */ /* ===================== idAI_Bot::GetEnemyRange ===================== float idAI_Bot::GetEnemyRange( void ) { float dist; idActor *enemyEnt = enemy.GetEntity(); if ( enemyEnt ) { dist = ( enemyEnt->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).Length(); } else { // Just some really high number dist = idMath::INFINITY; } return dist; } */ /* ===================== idAI_Bot::SetEnemyPosition ===================== */ void idAI_Bot::SetEnemyPosition( void ) { idAI::SetEnemyPosition(); lastVisibleEnemyTime = gameLocal.time; } /* ===================== idAI_Bot::Event_LostTimeMoreThan ===================== */ void idAI_Bot::Event_LostTimeMoreThan( int time ) { if(lastVisibleEnemyTime + SEC2MS( time ) < gameLocal.time){ //gameLocal.Printf("Event_LostTimeMoreThan true !\n" ); idThread::ReturnInt( true ); }else{ //gameLocal.Printf("Event_LostTimeMoreThan false !\n" ); idThread::ReturnInt( false ); } } /* ===================== idAI_Bot::Event_WeaponChangedMoreThan ===================== */ void idAI_Bot::Event_WeaponChangedMoreThan( int time ) { if(lastWeaponChangedTime + SEC2MS( time ) < gameLocal.time){ //gameLocal.Printf("Event_WeaponChangedMoreThan true !\n" ); idThread::ReturnInt( true ); }else{ //gameLocal.Printf("Event_WeaponChangedMoreThan false !\n" ); idThread::ReturnInt( false ); } } /* ===================== idAI_Bot::Event_PlayAnimOnWeapon ===================== */ void idAI_Bot::Event_PlayAnimOnWeapon( const char *animname ) { if(currentWeapon < 0 ){ return; } int anim; idEntity *myWeaponEnt; idAnimator *myWeaponAnimator; //get the reference to the weapon and its animator myWeaponEnt = weapons[ currentWeapon ].ent.GetEntity(); myWeaponAnimator = myWeaponEnt->GetAnimator(); // only if the entity has an MD5 model if ( myWeaponAnimator && myWeaponAnimator->ModelHandle() ) { anim = myWeaponAnimator->GetAnim( animname ); if ( !anim ) { gameLocal.Warning( "missing '%s' animation on current weapon!", animname ); myWeaponAnimator->Clear( 0, gameLocal.time, 0 ); } else { myWeaponAnimator->PlayAnim( 0, anim, gameLocal.time, 0 ); } } } /* ===================== idAI_Bot::idAI_Bot ===================== */ void idAI_Bot::Event_ReleaseNode( void ) { if ( currentNode.GetEntity() ) { currentNode.GetEntity()->Release(); currentNode = NULL; } } /* ===================== idAI_Bot::Event_TryLockNode ===================== */ void idAI_Bot::Event_TryLockNode( idEntity *node ) { bool resultOK; if( !node || !node->IsType( idBotNode::Type )){ gameLocal.Warning("trylock failed: the entity is not an idBotNode!" ); idThread::ReturnInt( false ); return; } //cast it idBotNode * castedNode; castedNode = static_cast( node ); //try resultOK = castedNode->TryLock(this); if(resultOK){ //release the current node if test ok if( currentNode.GetEntity()){ currentNode.GetEntity()->Release(); } //upd the current node currentNode = castedNode; } idThread::ReturnInt( resultOK ); } /*********************************************************************** idBotNode ***********************************************************************/ const idEventDef EV_BotNode_EvaluateConditions( "evaluateConditions", NULL, 'd' ); const idEventDef EV_BotNode_GetNextUsableNode( "getNextUsableNode", "e", 'e' ); const idEventDef EV_BotNode_CanBeUsedBy( "canBeUsedBy", "e", 'd' ); const idEventDef EV_BotNode_Reached( "reached" ); CLASS_DECLARATION( idEntity, idBotNode ) EVENT( EV_BotNode_EvaluateConditions, idBotNode::Event_EvaluateConditions ) EVENT( EV_BotNode_GetNextUsableNode, idBotNode::Event_GetNextUsableNode ) EVENT( EV_BotNode_CanBeUsedBy, idBotNode::Event_CanBeUsedBy ) EVENT( EV_BotNode_Reached, idBotNode::Event_Reached ) END_CLASS /* ===================== idBotNode::idBotNode ===================== */ idBotNode::idBotNode( void ) { fight_distance_atnode = 0.0f; end_distance_min = 0.0f; end_distance_max = 0.0f; node_distance_max = 0.0f; end_time = 0; end_time_max = 0; end_time_min = 0; end_health_delta_atnode = 0; fight_health_delta_reaching = 0; intial_health = 0; disabled = false; inuse = false; reached = false; ownerBot = NULL; } /* ===================== idBotNode::Save ===================== */ void idBotNode::Save( idSaveGame *savefile ) const { savefile->WriteFloat( fight_distance_atnode ); savefile->WriteFloat( end_distance_min ); savefile->WriteFloat( end_distance_max ); savefile->WriteFloat( node_distance_max ); savefile->WriteInt( end_time_max ); savefile->WriteInt( end_time_min ); savefile->WriteInt( end_time ); savefile->WriteInt( end_health_delta_atnode ); savefile->WriteInt( fight_health_delta_reaching ); savefile->WriteInt( intial_health ); savefile->WriteBool( disabled ); savefile->WriteBool( inuse ); savefile->WriteBool( reached ); ownerBot.Save( savefile ); } /* ===================== idBotNode::Restore ===================== */ void idBotNode::Restore( idRestoreGame *savefile ) { savefile->ReadFloat( fight_distance_atnode ); savefile->ReadFloat( end_distance_min ); savefile->ReadFloat( end_distance_max ); savefile->ReadFloat( node_distance_max ); savefile->ReadInt( end_time_max ); savefile->ReadInt( end_time_min ); savefile->ReadInt( end_time ); savefile->ReadInt( end_health_delta_atnode ); savefile->ReadInt( fight_health_delta_reaching ); savefile->ReadInt( intial_health ); savefile->ReadBool( disabled ); savefile->ReadBool( inuse ); savefile->ReadBool( reached ); ownerBot.Restore( savefile ); } /* ===================== idBotNode::Spawn ===================== */ void idBotNode::Spawn( void ) { fight_distance_atnode = spawnArgs.GetFloat( "fight_distance_atnode" ,"0"); end_distance_min = spawnArgs.GetFloat( "end_distance_min","0" ); end_distance_max = spawnArgs.GetFloat( "end_distance_max","0" ); if( end_distance_min > 0 && (end_distance_min <= fight_distance_atnode)){ gameLocal.Warning("idBotNode '%s' Spawn: end_distance_min <= fight_distance_atnode! --> end_distance_min will never be used", GetName()); gameLocal.Warning("FIX: end_distance_min changed to fight_distance_atnode*2"); end_distance_min = fight_distance_atnode*2; //fix } end_time_max = spawnArgs.GetInt( "end_time_max","0" ); end_time_min = spawnArgs.GetInt( "end_time_min","0" ); if( end_time_min < 0){end_time_min = 0;} if( end_time_max < end_time_min ){end_time_max = end_time_min;} end_health_delta_atnode = spawnArgs.GetInt( "end_health_delta_atnode","0" ); fight_health_delta_reaching = spawnArgs.GetInt( "fight_health_delta_reaching","0" ); disabled = spawnArgs.GetBool( "start_off" ); node_distance_max = spawnArgs.GetFloat( "node_distance_max","0" ); } /* ===================== idBotNode::TryLock ===================== */ bool idBotNode::TryLock( idAI_Bot *activator ) { if( !activator ){ gameLocal.Warning("trylock failed on '%s' : activator not valid!", GetName() ); return false; } if( disabled || inuse ){ gameLocal.Warning("trylock failed on '%s' : disabled or inuse!", GetName() ); return false; } //time if( end_time_min > 0){ end_time = gameLocal.time + SEC2MS( end_time_min + gameLocal.random.RandomInt(end_time_max - end_time_min) ); } //health intial_health = activator->GetHealth(); //new owner ownerBot = activator; inuse = true; reached = false; //gameLocal.Warning("trylock ok on '%s' !", GetName() ); return true; } /* ===================== idBotNode::Release ===================== */ void idBotNode::Release( void ) { if(inuse){ if ( spawnArgs.GetBool( "use_once" ) ) { disabled = true; } inuse = false; reached = false; ownerBot = NULL; //gameLocal.Printf("node '%s' released!\n", GetName() ); } } /* ===================== idBotNode::Event_Reached ===================== */ void idBotNode::Event_Reached( void ) { if(inuse && !reached){ reached = true; intial_health = ownerBot.GetEntity()->GetHealth(); } } /* ===================== idBotNode::Event_CanBeUsedBy ===================== */ void idBotNode::Event_CanBeUsedBy( idEntity *botEntity ) { if(disabled || inuse || !botEntity || !botEntity->IsType( idAI_Bot::Type )){ //gameLocal.Printf("cannot use the node '%s'!\n", GetName() ); idThread::ReturnInt( false ); return; } //gameLocal.Printf("can use the node '%s' !\n", GetName() ); idThread::ReturnInt( true ); } /* ===================== idBotNode::Event_GetNextNode ===================== */ void idBotNode::Event_GetNextUsableNode( idEntity *botEntity ) { if( !botEntity || !botEntity->IsType( idAI_Bot::Type )){ idThread::ReturnInt( false ); return; } idThread::ReturnEntity( GetNextUsableNode( static_cast( botEntity ) , 2 ) ); } /* ===================== idBotNode::AllNextNodesUsed ===================== */ bool idBotNode::AllNextNodesUsed( void ){ int i; idEntity *ent; idBotNode * myNode; bool allUsed = true; //gameLocal.Printf("idBotNode::AllNextNodesUsed called!\n"); for( i = 0; i < targets.Num(); i++ ) { ent = targets[ i ].GetEntity(); if( ent ){ if ( ent->IsType( idBotNode::Type ) ) { myNode = static_cast( ent ); if( !myNode->IsInUse() ){ allUsed = false; //gameLocal.Printf("uno libero trovato: '%s'\n",myNode->GetName() ); break; } } } } return allUsed ; } /* ===================== idBotNode::GetNextUsableNode ===================== */ idEntity * idBotNode::GetNextUsableNode( idAI_Bot *botEntity, int lookAheadMaxLevel ) { int i; int numBotNodes; int numOkEnts; int which; bool addItToList; idBotNode * myNode; //gameLocal.Printf("idBotNode::GetNextUsableNode!\n"); idEntity *ent; idEntity *ents[ MAX_GENTITIES ]; idBotNode *botNodeEnts[ MAX_GENTITIES ]; numBotNodes = 0; //create a list containing the next idBotNodes for( i = 0; i < targets.Num(); i++ ) { ent = targets[ i ].GetEntity(); if( ent ){ if ( ent->IsType( idBotNode::Type ) ) { myNode = static_cast( ent ); botNodeEnts[ numBotNodes++ ] = myNode; if ( numBotNodes >= MAX_GENTITIES ) { break; } } } } if ( !numBotNodes ) { //no nodes next //gameLocal.Printf("no nodi dopo!\n"); return NULL; } numOkEnts = 0; //create a sub list containing the next VALID idBotNodes for( i = 0; i < numBotNodes; i++ ) { myNode = botNodeEnts[ i ]; addItToList = true; if( myNode->IsInUse() || ( myNode->DistanceContidionsTrue( botEntity ) < DISTANCE_COND_NOENEMY )){ addItToList = false; } if (idStr::Cmp( myNode->GetEntityDefName(), "ai_bot_attack_path_corner" ) == 0) { if( myNode->AllNextNodesUsed() ){ addItToList = false; } //if it leads to used targets } if(addItToList){ ents[ numOkEnts++ ] = myNode; if ( numOkEnts >= MAX_GENTITIES ) { break; } } } if ( numOkEnts ) { which = gameLocal.random.RandomInt( numOkEnts ); //gameLocal.Printf("preso uno random dei %d nodi trovati: '%s' \n", numOkEnts, ents[ which ]->GetName() ); return ents[ which ] ; } //nothing? Try looking ahead! lookAheadMaxLevel--; if(lookAheadMaxLevel > 0){ for( i = 0; i < numBotNodes; i++ ) { ent = botNodeEnts[ i ]->GetNextUsableNode( botEntity, lookAheadMaxLevel ); if(ent){ //gameLocal.Printf("trovato uno con ricorsione: '%s' \n", ent->GetName() ); return ent; } } } //nothing //gameLocal.Printf("nothing found :( \n"); return NULL; } /* ===================== idBotNode::DistanceContidionsTrue ===================== */ int idBotNode::DistanceContidionsTrue( idAI_Bot *testEntity ) { idEntity * testEnemy; float entityRange; float enemyRange; //entity stuff if( node_distance_max > 0.0f ){ entityRange = ( testEntity->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthFast(); if( entityRange > node_distance_max ){ //gameLocal.Printf("The node is too far\n" ); return DISTANCE_COND_TOOFAR; } } //enemy stuff testEnemy = testEntity->GetEnemy(); if(!testEnemy){ //gameLocal.Printf("DistanceContidions !testEnemy...\n" ); return DISTANCE_COND_NOENEMY; } enemyRange = ( testEnemy->GetPhysics()->GetOrigin() - GetPhysics()->GetOrigin() ).LengthFast(); if( fight_distance_atnode > 0.0f ){ //this is the most important one and should be the first. if( enemyRange < fight_distance_atnode ){ //gameLocal.Printf("DistanceContidions fight_distance_atnode no\n" ); return DISTANCE_COND_FIGHT_FAILED; } } if( end_distance_min > 0.0f){ // this check is enabled if( enemyRange < end_distance_min ){ //gameLocal.Printf("DistanceContidions end_distance_min no\n" ); return DISTANCE_COND_MIN_FAILED; } } if( end_distance_max > 0.0f ){ //this check is enabled if( enemyRange > end_distance_max ){ //gameLocal.Printf("DistanceContidions end_distance_max no\n" ); return DISTANCE_COND_MAX_FAILED; } } //gameLocal.Printf("DistanceContidions all ok\n" ); return DISTANCE_COND_OK; } /* ===================== idBotNode::Event_EvaluateConditions ===================== */ void idBotNode::Event_EvaluateConditions( void ) { //true if one of them is verified! idAI_Bot* currentOwner; int result; if ( !inuse ) { //gameLocal.Printf("Event_EvaluateConditions: node not in use!\n" ); idThread::ReturnInt( true ); return; } assert( ownerBot.IsValid() ); currentOwner = ownerBot.GetEntity(); //time if( end_time_min > 0){ //not unlimited if(end_time < gameLocal.time){ //gameLocal.Printf("time cond!\n" ); idThread::ReturnInt( true ); return; } } //health int deltaToUse; if(reached){ deltaToUse = end_health_delta_atnode; }else{ deltaToUse = fight_health_delta_reaching; } if( deltaToUse > 0){ if( deltaToUse < (intial_health - currentOwner->GetHealth())){ //gameLocal.Printf("health cond!\n" ); currentOwner->AI_SAY_DAMAGED = true; if(!reached){ currentOwner->AI_LEAVE_NODE_TO_FIGHT = true; } idThread::ReturnInt( true ); return; } } //distance result = DistanceContidionsTrue( currentOwner ); if( result == DISTANCE_COND_FIGHT_FAILED ){ currentOwner->AI_LEAVE_NODE_TO_FIGHT = true; } if( result != DISTANCE_COND_OK && result != DISTANCE_COND_NOENEMY ){ //gameLocal.Printf("distance cond!\n" ); idThread::ReturnInt( true ); return; } //else //gameLocal.Printf("Event_EvaluateConditions all false...go on!\n" ); idThread::ReturnInt( false ); }