/******************************* * B_spawn.c * * Description: * * various procedures that the * * bot need to work * *******************************/ #include #include "doomtype.h" #include "doomdef.h" #include "doomstat.h" #include "p_local.h" #include "b_bot.h" #include "g_game.h" #include "m_random.h" #include "r_sky.h" #include "st_stuff.h" #include "stats.h" #include "i_system.h" #include "s_sound.h" #include "d_event.h" static FRandom pr_botdofire ("BotDoFire"); //Checks TRUE reachability from //one looker to another. First mobj (looker) is looker. bool FCajunMaster::Reachable (AActor *looker, AActor *rtarget) { if (looker == rtarget) return false; if ((rtarget->Sector->ceilingplane.ZatPoint (rtarget->x, rtarget->y) - rtarget->Sector->floorplane.ZatPoint (rtarget->x, rtarget->y)) < looker->height) //Where rtarget is, looker can't be. return false; sector_t *last_s = looker->Sector; fixed_t last_z = last_s->floorplane.ZatPoint (looker->x, looker->y); fixed_t estimated_dist = P_AproxDistance (looker->x - rtarget->x, looker->y - rtarget->y); bool reachable = true; FPathTraverse it(looker->x+looker->velx, looker->y+looker->vely, rtarget->x, rtarget->y, PT_ADDLINES|PT_ADDTHINGS); intercept_t *in; while ((in = it.Next())) { fixed_t hitx, hity; fixed_t frac; line_t *line; AActor *thing; fixed_t dist; sector_t *s; frac = in->frac - FixedDiv (4*FRACUNIT, MAX_TRAVERSE_DIST); dist = FixedMul (frac, MAX_TRAVERSE_DIST); hitx = it.Trace().x + FixedMul (looker->velx, frac); hity = it.Trace().y + FixedMul (looker->vely, frac); if (in->isaline) { line = in->d.line; if (!(line->flags & ML_TWOSIDED) || (line->flags & (ML_BLOCKING|ML_BLOCKEVERYTHING|ML_BLOCK_PLAYERS))) { return false; //Cannot continue. } else { //Determine if going to use backsector/frontsector. s = (line->backsector == last_s) ? line->frontsector : line->backsector; fixed_t ceilingheight = s->ceilingplane.ZatPoint (hitx, hity); fixed_t floorheight = s->floorplane.ZatPoint (hitx, hity); if (!bglobal.IsDangerous (s) && //Any nukage/lava? (floorheight <= (last_z+MAXMOVEHEIGHT) && ((ceilingheight == floorheight && line->special) || (ceilingheight - floorheight) >= looker->height))) //Does it fit? { last_z = floorheight; last_s = s; continue; } else { return false; } } } if (dist > estimated_dist) { return true; } thing = in->d.thing; if (thing == looker) //Can't reach self in this case. continue; if (thing == rtarget && (rtarget->Sector->floorplane.ZatPoint (rtarget->x, rtarget->y) <= (last_z+MAXMOVEHEIGHT))) { return true; } reachable = false; } return reachable; } //doesnt check LOS, checks visibility with a set view angle. //B_Checksight checks LOS (straight line) //---------------------------------------------------------------------- //Check if mo1 has free line to mo2 //and if mo2 is within mo1 viewangle (vangle) given with normal degrees. //if these conditions are true, the function returns true. //GOOD TO KNOW is that the player's view angle //in doom is 90 degrees infront. bool FCajunMaster::Check_LOS (AActor *from, AActor *to, angle_t vangle) { if (!P_CheckSight (from, to, SF_SEEPASTBLOCKEVERYTHING)) return false; // out of sight if (vangle == ANGLE_MAX) return true; if (vangle == 0) return false; //Looker seems to be blind. return (angle_t)abs (R_PointToAngle2 (from->x, from->y, to->x, to->y) - from->angle) <= vangle/2; } //------------------------------------- //Bot_Dofire() //------------------------------------- //The bot will check if it's time to fire //and do so if that is the case. void FCajunMaster::Dofire (AActor *actor, ticcmd_t *cmd) { bool no_fire; //used to prevent bot from pumping rockets into nearby walls. int aiming_penalty=0; //For shooting at shading target, if screen is red, MAKEME: When screen red. int aiming_value; //The final aiming value. fixed_t dist; angle_t an; int m; AActor *enemy = actor->player->Bot->enemy; if (!enemy || !(enemy->flags & MF_SHOOTABLE) || enemy->health <= 0) return; if (actor->player->ReadyWeapon == NULL) return; if (actor->player->damagecount > actor->player->Bot->skill.isp) { actor->player->Bot->first_shot = true; return; } //Reaction skill thing. if (actor->player->Bot->first_shot && !(actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_REACTION_SKILL_THING)) { actor->player->Bot->t_react = (100-actor->player->Bot->skill.reaction+1)/((pr_botdofire()%3)+3); } actor->player->Bot->first_shot = false; if (actor->player->Bot->t_react) return; //MAKEME: Decrease the rocket suicides even more. no_fire = true; //actor->player->angle = R_PointToAngle2(actor->x, actor->y, actor->player->enemy->x, actor->player->enemy->y); //Distance to enemy. dist = P_AproxDistance ((actor->x + actor->velx) - (enemy->x + enemy->velx), (actor->y + actor->vely) - (enemy->y + enemy->vely)); //FIRE EACH TYPE OF WEAPON DIFFERENT: Here should all the different weapons go. if (actor->player->ReadyWeapon->WeaponFlags & WIF_MELEEWEAPON) { if ((actor->player->ReadyWeapon->ProjectileType != NULL)) { if (actor->player->ReadyWeapon->CheckAmmo (AWeapon::PrimaryFire, false, true)) { // This weapon can fire a projectile and has enough ammo to do so goto shootmissile; } else if (!(actor->player->ReadyWeapon->WeaponFlags & WIF_AMMO_OPTIONAL)) { // Ammo is required, so don't shoot. This is for weapons that shoot // missiles that die at close range, such as the powered-up Phoneix Rod. return; } } else { //*4 is for atmosphere, the chainsaws sounding and all.. no_fire = (dist > (MELEERANGE*4)); } } else if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_BFG) { //MAKEME: This should be smarter. if ((pr_botdofire()%200)<=actor->player->Bot->skill.reaction) if(Check_LOS(actor, actor->player->Bot->enemy, SHOOTFOV)) no_fire = false; } else if (actor->player->ReadyWeapon->ProjectileType != NULL) { if (actor->player->ReadyWeapon->WeaponFlags & WIF_BOT_EXPLOSIVE) { //Special rules for RL an = FireRox (actor, enemy, cmd); if(an) { actor->player->Bot->angle = an; //have to be somewhat precise. to avoid suicide. if (abs (actor->player->Bot->angle - actor->angle) < 12*ANGLE_1) { actor->player->Bot->t_rocket = 9; no_fire = false; } } } // prediction aiming shootmissile: dist = P_AproxDistance (actor->x - enemy->x, actor->y - enemy->y); m = dist / GetDefaultByType (actor->player->ReadyWeapon->ProjectileType)->Speed; SetBodyAt (enemy->x + enemy->velx*m*2, enemy->y + enemy->vely*m*2, enemy->z, 1); actor->player->Bot->angle = R_PointToAngle2 (actor->x, actor->y, body1->x, body1->y); if (Check_LOS (actor, enemy, SHOOTFOV)) no_fire = false; } else { //Other weapons, mostly instant hit stuff. actor->player->Bot->angle = R_PointToAngle2 (actor->x, actor->y, enemy->x, enemy->y); aiming_penalty = 0; if (enemy->flags & MF_SHADOW) aiming_penalty += (pr_botdofire()%25)+10; if (enemy->Sector->lightlevelplayer->powers & PW_INFRARED)*/) aiming_penalty += pr_botdofire()%40;//Dark if (actor->player->damagecount) aiming_penalty += actor->player->damagecount; //Blood in face makes it hard to aim aiming_value = actor->player->Bot->skill.aiming - aiming_penalty; if (aiming_value <= 0) aiming_value = 1; m = ((SHOOTFOV/2)-(aiming_value*SHOOTFOV/200)); //Higher skill is more accurate if (m <= 0) m = 1; //Prevents lock. if (m) { if (actor->player->Bot->increase) actor->player->Bot->angle += m; else actor->player->Bot->angle -= m; } if (abs (actor->player->Bot->angle - actor->angle) < 4*ANGLE_1) { actor->player->Bot->increase = !actor->player->Bot->increase; } if (Check_LOS (actor, enemy, (SHOOTFOV/2))) no_fire = false; } if (!no_fire) //If going to fire weapon { cmd->ucmd.buttons |= BT_ATTACK; } //Prevents bot from jerking, when firing automatic things with low skill. //actor->angle = R_PointToAngle2(actor->x, actor->y, actor->player->enemy->x, actor->player->enemy->y); } bool FCajunMaster::IsLeader (player_t *player) { DBot *Bot; TThinkerIterator it; while ((Bot = it.Next ()) != NULL) { if (Bot->mate == player->mo) { return true; } } return false; } //This function is called every //tick (for each bot) to set //the mate (teammate coop mate). AActor *FCajunMaster::Choose_Mate (AActor *bot) { int count; fixed_t closest_dist, test; AActor *target; AActor *observer; //is mate alive? if (bot->player->Bot->mate) { if (bot->player->Bot->mate->health <= 0) bot->player->Bot->mate = NULL; else bot->player->Bot->last_mate = bot->player->Bot->mate; } if (bot->player->Bot->mate) //Still is.. return bot->player->Bot->mate; //Check old_mates status. if (bot->player->Bot->last_mate) if (bot->player->Bot->last_mate->health <= 0) bot->player->Bot->last_mate = NULL; target = NULL; closest_dist = FIXED_MAX; if (bot_observer) observer = players[consoleplayer].mo; else observer = NULL; //Check for player friends for (count = 0; count < MAXPLAYERS; count++) { player_t *client = &players[count]; if (playeringame[count] && client->mo && bot != client->mo && (bot->IsTeammate (client->mo) || !deathmatch) && client->mo->health > 0 && client->mo != observer && ((bot->health/2) <= client->mo->health || !deathmatch) && !IsLeader(client)) //taken? { if (P_CheckSight (bot, client->mo, SF_IGNOREVISIBILITY)) { test = P_AproxDistance (client->mo->x - bot->x, client->mo->y - bot->y); if (test < closest_dist) { closest_dist = test; target = client->mo; } } } } /* //Make a introducing to mate. if(target && target!=bot->player->last_mate) { if((P_Random()%(200*bglobal.botnum))<3) { bot->player->chat = c_teamup; if(target->bot) strcpy(bot->player->c_target, botsingame[target->bot_id]); else if(target->player) strcpy(bot->player->c_target, player_names[target->play_id]); } } */ return target; } //MAKEME: Make this a smart decision AActor *FCajunMaster::Find_enemy (AActor *bot) { int count; fixed_t closest_dist, temp; //To target. AActor *target; angle_t vangle; AActor *observer; if (!deathmatch) { // [RH] Take advantage of the Heretic/Hexen code to be a little smarter return P_RoughMonsterSearch (bot, 20); } //Note: It's hard to ambush a bot who is not alone if (bot->player->Bot->allround || bot->player->Bot->mate) vangle = ANGLE_MAX; else vangle = ENEMY_SCAN_FOV; bot->player->Bot->allround = false; target = NULL; closest_dist = FIXED_MAX; if (bot_observer) observer = players[consoleplayer].mo; else observer = NULL; for (count = 0; count < MAXPLAYERS; count++) { player_t *client = &players[count]; if (playeringame[count] && !bot->IsTeammate (client->mo) && client->mo != observer && client->mo->health > 0 && bot != client->mo) { if (Check_LOS (bot, client->mo, vangle)) //Here's a strange one, when bot is standing still, the P_CheckSight within Check_LOS almost always returns false. tought it should be the same checksight as below but.. (below works) something must be fuckin wierd screded up. //if(P_CheckSight(bot, players[count].mo)) { temp = P_AproxDistance (client->mo->x - bot->x, client->mo->y - bot->y); //Too dark? if (temp > DARK_DIST && client->mo->Sector->lightlevel < WHATS_DARK /*&& bot->player->Powers & PW_INFRARED*/) continue; if (temp < closest_dist) { closest_dist = temp; target = client->mo; } } } } return target; } //Creates a temporary mobj (invisible) at the given location. void FCajunMaster::SetBodyAt (fixed_t x, fixed_t y, fixed_t z, int hostnum) { if (hostnum == 1) { if (body1) { body1->SetOrigin (x, y, z); } else { body1 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE); } } else if (hostnum == 2) { if (body2) { body2->SetOrigin (x, y, z); } else { body2 = Spawn ("CajunBodyNode", x, y, z, NO_REPLACE); } } } //------------------------------------------ // FireRox() // //Returns NULL if shouldn't fire //else an angle (in degrees) are given //This function assumes actor->player->angle //has been set an is the main aiming angle. //Emulates missile travel. Returns distance travelled. fixed_t FCajunMaster::FakeFire (AActor *source, AActor *dest, ticcmd_t *cmd) { AActor *th = Spawn ("CajunTrace", source->x, source->y, source->z + 4*8*FRACUNIT, NO_REPLACE); th->target = source; // where it came from float speed = (float)th->Speed; FVector3 velocity; velocity[0] = FIXED2FLOAT(dest->x - source->x); velocity[1] = FIXED2FLOAT(dest->y - source->y); velocity[2] = FIXED2FLOAT(dest->z - source->z); velocity.MakeUnit(); th->velx = FLOAT2FIXED(velocity[0] * speed); th->vely = FLOAT2FIXED(velocity[1] * speed); th->velz = FLOAT2FIXED(velocity[2] * speed); fixed_t dist = 0; while (dist < SAFE_SELF_MISDIST) { dist += th->Speed; th->SetOrigin (th->x + th->velx, th->y + th->vely, th->z + th->velz); if (!CleanAhead (th, th->x, th->y, cmd)) break; } th->Destroy (); return dist; } angle_t FCajunMaster::FireRox (AActor *bot, AActor *enemy, ticcmd_t *cmd) { fixed_t dist; angle_t ang; AActor *actor; int m; SetBodyAt (bot->x + FixedMul(bot->velx, 5*FRACUNIT), bot->y + FixedMul(bot->vely, 5*FRACUNIT), bot->z + (bot->height / 2), 2); actor = bglobal.body2; dist = P_AproxDistance (actor->x-enemy->x, actor->y-enemy->y); if (dist < SAFE_SELF_MISDIST) return 0; //Predict. m = (((dist+1)/FRACUNIT) / GetDefaultByName("Rocket")->Speed); SetBodyAt (enemy->x + FixedMul(enemy->velx, (m+2*FRACUNIT)), enemy->y + FixedMul(enemy->vely, (m+2*FRACUNIT)), ONFLOORZ, 1); dist = P_AproxDistance(actor->x-bglobal.body1->x, actor->y-bglobal.body1->y); //try the predicted location if (P_CheckSight (actor, bglobal.body1, SF_IGNOREVISIBILITY)) //See the predicted location, so give a test missile { FCheckPosition tm; if (SafeCheckPosition (bot, actor->x, actor->y, tm)) { if (FakeFire (actor, bglobal.body1, cmd) >= SAFE_SELF_MISDIST) { ang = R_PointToAngle2 (actor->x, actor->y, bglobal.body1->x, bglobal.body1->y); return ang; } } } //Try fire straight. if (P_CheckSight (actor, enemy, 0)) { if (FakeFire (bot, enemy, cmd) >= SAFE_SELF_MISDIST) { ang = R_PointToAngle2(bot->x, bot->y, enemy->x, enemy->y); return ang; } } return 0; } // [RH] We absolutely do not want to pick things up here. The bot code is // executed apart from all the other simulation code, so we don't want it // creating side-effects during gameplay. bool FCajunMaster::SafeCheckPosition (AActor *actor, fixed_t x, fixed_t y, FCheckPosition &tm) { int savedFlags = actor->flags; actor->flags &= ~MF_PICKUP; bool res = P_CheckPosition (actor, x, y, tm); actor->flags = savedFlags; return res; } void FCajunMaster::StartTravel () { DBot *Bot; TThinkerIterator it; while ((Bot = it.Next ()) != NULL) { Bot->ChangeStatNum (STAT_TRAVELLING); } } void FCajunMaster::FinishTravel () { DBot *Bot; TThinkerIterator it(STAT_TRAVELLING); while ((Bot = it.Next ()) != NULL) { Bot->ChangeStatNum (STAT_DEFAULT); } }