From b24da8eff168c88b8e72754d09c347986db5ec4e Mon Sep 17 00:00:00 2001 From: driAn Date: Thu, 1 Jun 2006 22:03:48 +0000 Subject: [PATCH] *** empty log message *** git-svn-id: svn://svn.code.sf.net/p/actiongame/code/trunk/ac@301 e016b1c5-ed68-44aa-a2f7-31cac88cffde --- source/src/bot/ac_bot.cpp | 634 +-- source/src/bot/ac_bot.h | 94 +- source/src/bot/ac_bot_ai.cpp | 668 ++-- source/src/bot/bot.cpp | 990 ++--- source/src/bot/bot.h | 860 ++-- source/src/bot/bot_ai.cpp | 4590 +++++++++++----------- source/src/bot/bot_util.cpp | 1130 +++--- source/src/bot/bot_util.h | 1130 +++--- source/src/bot/bot_waypoint.cpp | 6516 +++++++++++++++---------------- source/src/bot/bot_waypoint.h | 382 +- source/src/bot/botmanager.cpp | 2870 +++++++------- source/src/clients2c.cpp | 48 +- source/src/cube.h | 4 +- source/src/entities.cpp | 12 +- source/src/protos.h | 2 +- source/src/server.cpp | 14 +- source/src/serverutil.cpp | 2 +- source/src/weapon.cpp | 30 +- 18 files changed, 9988 insertions(+), 9988 deletions(-) diff --git a/source/src/bot/ac_bot.cpp b/source/src/bot/ac_bot.cpp index 58e6260d..84300a58 100644 --- a/source/src/bot/ac_bot.cpp +++ b/source/src/bot/ac_bot.cpp @@ -1,317 +1,317 @@ -// -// C++ Implementation: bot -// -// Description: Main bot code for Action Cube -// -// Main bot file -// -// Author: Rick -// -// -// - -#include "../cube.h" - -#ifdef AC_CUBE - -extern vector sents; -extern int triggertime; -extern itemstat itemstats[]; -extern void spawnstate(dynent *d); - -//AC Bot class begin - -void CACBot::Spawn() -{ - // Init all bot variabeles - m_pMyEnt->eyeheight = 4.25f; - m_pMyEnt->aboveeye = 0.7f; - m_pMyEnt->radius = 1.1f; - m_pMyEnt->primary = m_pMyEnt->nextprimary = rnd(3)+GUN_PISTOL; - - spawnplayer(m_pMyEnt); - - m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; - m_pMyEnt->primary = m_pMyEnt->nextprimary = GUN_ASSULT; - m_pMyEnt->move = 0; - m_pMyEnt->enemy = NULL; - m_pMyEnt->maxspeed = 16.0f; - m_pMyEnt->health = 100; - m_pMyEnt->armour = 50; - m_pMyEnt->pitch = 0; - m_pMyEnt->roll = 0; - m_pMyEnt->state = CS_ALIVE; - m_pMyEnt->anger = 0; - m_pMyEnt->pBot = this; -// loopi(NUMGUNS) m_pMyEnt->ammo[i] = m_pMyEnt->mag[i] = 0; - - /* UNDONE - m_pMyEnt->ammo[GUN_FIST] = 1; - if(m_noitems) - { - m_pMyEnt->gunselect = GUN_RIFLE; - m_pMyEnt->armour = 0; - if(m_noitemsrail) - { - m_pMyEnt->health = 1; - m_pMyEnt->ammo[GUN_RIFLE] = 100; - } - else - { - if(gamemode==12) // eihrul's secret "instafist" mode - { - m_pMyEnt->gunselect = GUN_FIST; - } - else - { - m_pMyEnt->health = 256; - if(m_tarena) - { - int gun1 = rnd(4)+1; - botbaseammo(m_pMyEnt->gunselect = gun1, m_pMyEnt); - for(;;) - { - int gun2 = rnd(4)+1; - if(gun1!=gun2) { botbaseammo(gun2, m_pMyEnt); break; }; - } - } - else if(m_arena) // insta arena - { - m_pMyEnt->ammo[GUN_RIFLE] = 100; - } - else // efficiency - { - loopi(4) botbaseammo(i+1, m_pMyEnt); - m_pMyEnt->gunselect = GUN_CG; - } - m_pMyEnt->ammo[GUN_CG] /= 2; - } - } - } - else - { - m_pMyEnt->ammo[GUN_SEMIPISTOL] = 5; - SelectGun(GUN_SG); - }*/ - - m_pMyEnt->ammo[GUN_KNIFE] = 1; - m_pMyEnt->ammo[GUN_PISTOL] = 5; - m_pMyEnt->mag[GUN_PISTOL] = 5; - SelectGun(m_pMyEnt->primary); - - m_eCurrentBotState = STATE_NORMAL; - m_iShootDelay = m_iChangeWeaponDelay = 0; - m_iCheckEnvDelay = 0; - m_vPrevOrigin = g_vecZero; - m_iStuckCheckDelay = lastmillis + 250; - m_bStuck = false; - m_iStuckTime = 0; - m_iStrafeTime = m_iStrafeCheckDelay = 0; - m_iMoveDir = DIR_NONE; - - m_pPrevEnemy = NULL; - m_iCombatNavTime = 0; - m_iSPMoveTime = 0; - m_iEnemySearchDelay = 0; - m_bCombatJump = false; - m_iCombatJumpDelay = 0; - m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); - m_iHuntDelay = 0; - m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; - m_pHuntTarget = NULL; - m_fPrevHuntDist = 0.0f; - m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; - - m_iLastJumpPad = 0; - m_pTargetEnt = NULL; - m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; - m_iCheckEntsDelay = 0; - m_iCheckTriggersDelay = 0; - m_iLookForWaypointTime = 0; - - m_iAimDelay = 0; - m_fYawToTurn = m_fPitchToTurn = 0.0f; - - m_vGoal = m_vWaterGoal = g_vecZero; - - ResetWaypointVars(); -} - -void CACBot::BotPain(int damage, dynent *d) -{ - if(m_pMyEnt->state!=CS_ALIVE || editmode || intermission) return; -//fixmebot - int ad = damage/**(m_pMyEnt->armourtype+1)*20/100*/; // let armour absorb when possible - if(ad>m_pMyEnt->armour) ad = m_pMyEnt->armour; - m_pMyEnt->armour -= ad; - damage -= ad; - float droll = damage/0.5f; - m_pMyEnt->roll += m_pMyEnt->roll>0 ? droll : (m_pMyEnt->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage - if((m_pMyEnt->health -= damage)<=0) - { - if (player1 == d) - { - int frags; - if(isteam(player1->team, m_pMyEnt->team)) - { - frags = -1; - conoutf("you fragged a teammate (%s)", (int)m_pMyEnt->name); - } - else - { - frags = 1; - conoutf("you fragged %s", (int)m_pMyEnt->name); - } - addmsg(1, 2, SV_FRAGS, player1->frags += frags); - addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), -1, false); - } - else if (d->monsterstate) - conoutf("%s got killed by %s", (int)m_pMyEnt->name, (int)d->name); - else - { - int KillerIndex = -1; - - if (d == m_pMyEnt) - { - conoutf("%s suicided", (int)d->name); - KillerIndex = BotManager.GetBotIndex(d); - addmsg(1, 3, SV_BOTFRAGS, KillerIndex, --d->frags); - } - else - { - if (d->bIsBot) - { - KillerIndex = BotManager.GetBotIndex(d); - addmsg(1, 3, SV_BOTFRAGS, KillerIndex, ++d->frags); - } - else - loopv(players){ if (players[i] == d) { KillerIndex = i; break;} } - - if(isteam(m_pMyEnt->team, d->team)) - { - conoutf("%s fragged his teammate (%s)", (int)d->name, - (int)m_pMyEnt->name); - } - else - { - conoutf("%s fragged %s", (int)d->name, (int)m_pMyEnt->name); - } - } - addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), KillerIndex, - d->bIsBot); - } - - m_pMyEnt->lifesequence++; - m_pMyEnt->attacking = false; - m_pMyEnt->state = CS_DEAD; - m_pMyEnt->pitch = 0; - m_pMyEnt->roll = 60; - playsound(S_DIE1+rnd(2), &m_pMyEnt->o); - spawnstate(m_pMyEnt); - m_pMyEnt->lastaction = lastmillis; - } - else - { - playsound(S_PAIN6, &m_pMyEnt->o); - } -} - -void CACBot::CheckItemPickup() -{ - if(editmode) return; - loopv(ents) - { - entity &e = ents[i]; - if(e.type==NOTUSED) continue; - - if (!e.spawned) continue; - if ((i < sents.length()) && (!sents[i].spawned)) continue; - - if(OUTBORD(e.x, e.y)) continue; - vec v = { e.x, e.y, S(e.x, e.y)->floor+m_pMyEnt->eyeheight }; - vdist(dist, t, m_pMyEnt->o, v); - if(dist<2.5) - { - PickUp(i); - } - } -} - -void CACBot::PickUp(int n) -{ - int np = 1; - loopv(players) if(players[i]) np++; - loopv(bots) if(bots[i]) np++; - np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players - int ammo = np*2; - switch(ents[n].type) - { - case I_CLIPS: AddItem(n, m_pMyEnt->ammo[GUN_PISTOL], ammo); break; - case I_AMMO: AddItem(n, m_pMyEnt->ammo[m_pMyEnt->primary], ammo); break; - case I_GRENADE: AddItem(n, m_pMyEnt->ammo[GUN_GRENADE], ammo); break; - -/* case I_HELMET: - // (100h/100g only absorbs 166 damage) - if(m_pMyEnt->armourtype==A_YELLOW && m_pMyEnt->armour>66) break; - AddItem(n, m_pMyEnt->armour, 20); - break; - - case I_ARMOUR: - AddItem(n, m_pMyEnt->armour, 20); - break; - - case OBJ_ITEM: - AddItem(n, m_pMyEnt->quadmillis, 60); - break;*/ - -/* case TRIGGER: - ents[n].spawned = false; - triggertime = lastmillis; - trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer - - // HACK: Reset ent goal if bot was looking for this ent - if (m_pTargetEnt == &ents[n]) - { - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - ResetWaypointVars(); - m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); - } - - BotManager.PickNextTrigger(); - break;*/ - }; -}; - -void CACBot::AddItem(int i, int &v, int spawnsec) -{ - if((i>=sents.length()) || (!sents[i].spawned)) - return; - -//fixmebot - if(v>=itemstats[ents[i].type-1].max) // don't pick up if not needed - return; - - itemstat &is = itemstats[ents[i].type-1]; - ents[i].spawned = false; - v += is.add; - if(v>is.max) v = is.max; - botplaysound(is.sound, m_pMyEnt); - sents[i].spawned = false; - sents[i].spawnsecs = spawnsec; - - addmsg(1, 3, SV_BOTITEMPICKUP, i, m_classicsp ? 100000 : spawnsec); - - // HACK: Reset ent goal if bot was looking for for this ent - if (m_pTargetEnt == &ents[i]) - { - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - ResetWaypointVars(); - m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); - }; -} - -// AC Bot class end - -#endif +// +// C++ Implementation: bot +// +// Description: Main bot code for Action Cube +// +// Main bot file +// +// Author: Rick +// +// +// + +#include "../cube.h" + +#ifdef AC_CUBE + +extern vector sents; +extern int triggertime; +extern itemstat itemstats[]; +extern void spawnstate(dynent *d); + +//AC Bot class begin + +void CACBot::Spawn() +{ + // Init all bot variabeles + m_pMyEnt->eyeheight = 4.25f; + m_pMyEnt->aboveeye = 0.7f; + m_pMyEnt->radius = 1.1f; + m_pMyEnt->primary = m_pMyEnt->nextprimary = rnd(3)+GUN_PISTOL; + + spawnplayer(m_pMyEnt); + + m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; + m_pMyEnt->primary = m_pMyEnt->nextprimary = GUN_ASSULT; + m_pMyEnt->move = 0; + m_pMyEnt->enemy = NULL; + m_pMyEnt->maxspeed = 16.0f; + m_pMyEnt->health = 100; + m_pMyEnt->armour = 50; + m_pMyEnt->pitch = 0; + m_pMyEnt->roll = 0; + m_pMyEnt->state = CS_ALIVE; + m_pMyEnt->anger = 0; + m_pMyEnt->pBot = this; +// loopi(NUMGUNS) m_pMyEnt->ammo[i] = m_pMyEnt->mag[i] = 0; + + /* UNDONE + m_pMyEnt->ammo[GUN_FIST] = 1; + if(m_noitems) + { + m_pMyEnt->gunselect = GUN_RIFLE; + m_pMyEnt->armour = 0; + if(m_noitemsrail) + { + m_pMyEnt->health = 1; + m_pMyEnt->ammo[GUN_RIFLE] = 100; + } + else + { + if(gamemode==12) // eihrul's secret "instafist" mode + { + m_pMyEnt->gunselect = GUN_FIST; + } + else + { + m_pMyEnt->health = 256; + if(m_tarena) + { + int gun1 = rnd(4)+1; + botbaseammo(m_pMyEnt->gunselect = gun1, m_pMyEnt); + for(;;) + { + int gun2 = rnd(4)+1; + if(gun1!=gun2) { botbaseammo(gun2, m_pMyEnt); break; }; + } + } + else if(m_arena) // insta arena + { + m_pMyEnt->ammo[GUN_RIFLE] = 100; + } + else // efficiency + { + loopi(4) botbaseammo(i+1, m_pMyEnt); + m_pMyEnt->gunselect = GUN_CG; + } + m_pMyEnt->ammo[GUN_CG] /= 2; + } + } + } + else + { + m_pMyEnt->ammo[GUN_SEMIPISTOL] = 5; + SelectGun(GUN_SG); + }*/ + + m_pMyEnt->ammo[GUN_KNIFE] = 1; + m_pMyEnt->ammo[GUN_PISTOL] = 5; + m_pMyEnt->mag[GUN_PISTOL] = 5; + SelectGun(m_pMyEnt->primary); + + m_eCurrentBotState = STATE_NORMAL; + m_iShootDelay = m_iChangeWeaponDelay = 0; + m_iCheckEnvDelay = 0; + m_vPrevOrigin = g_vecZero; + m_iStuckCheckDelay = lastmillis + 250; + m_bStuck = false; + m_iStuckTime = 0; + m_iStrafeTime = m_iStrafeCheckDelay = 0; + m_iMoveDir = DIR_NONE; + + m_pPrevEnemy = NULL; + m_iCombatNavTime = 0; + m_iSPMoveTime = 0; + m_iEnemySearchDelay = 0; + m_bCombatJump = false; + m_iCombatJumpDelay = 0; + m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); + m_iHuntDelay = 0; + m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; + m_pHuntTarget = NULL; + m_fPrevHuntDist = 0.0f; + m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; + + m_iLastJumpPad = 0; + m_pTargetEnt = NULL; + m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; + m_iCheckEntsDelay = 0; + m_iCheckTriggersDelay = 0; + m_iLookForWaypointTime = 0; + + m_iAimDelay = 0; + m_fYawToTurn = m_fPitchToTurn = 0.0f; + + m_vGoal = m_vWaterGoal = g_vecZero; + + ResetWaypointVars(); +} + +void CACBot::BotPain(int damage, dynent *d) +{ + if(m_pMyEnt->state!=CS_ALIVE || editmode || intermission) return; +//fixmebot + int ad = damage/**(m_pMyEnt->armourtype+1)*20/100*/; // let armour absorb when possible + if(ad>m_pMyEnt->armour) ad = m_pMyEnt->armour; + m_pMyEnt->armour -= ad; + damage -= ad; + float droll = damage/0.5f; + m_pMyEnt->roll += m_pMyEnt->roll>0 ? droll : (m_pMyEnt->roll<0 ? -droll : (rnd(2) ? droll : -droll)); // give player a kick depending on amount of damage + if((m_pMyEnt->health -= damage)<=0) + { + if (player1 == d) + { + int frags; + if(isteam(player1->team, m_pMyEnt->team)) + { + frags = -1; + conoutf("you fragged a teammate (%s)", (int)m_pMyEnt->name); + } + else + { + frags = 1; + conoutf("you fragged %s", (int)m_pMyEnt->name); + } + addmsg(1, 2, SV_FRAGS, player1->frags += frags); + addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), -1, false); + } + else if (d->monsterstate) + conoutf("%s got killed by %s", (int)m_pMyEnt->name, (int)d->name); + else + { + int KillerIndex = -1; + + if (d == m_pMyEnt) + { + conoutf("%s suicided", (int)d->name); + KillerIndex = BotManager.GetBotIndex(d); + addmsg(1, 3, SV_BOTFRAGS, KillerIndex, --d->frags); + } + else + { + if (d->bIsBot) + { + KillerIndex = BotManager.GetBotIndex(d); + addmsg(1, 3, SV_BOTFRAGS, KillerIndex, ++d->frags); + } + else + loopv(players){ if (players[i] == d) { KillerIndex = i; break;} } + + if(isteam(m_pMyEnt->team, d->team)) + { + conoutf("%s fragged his teammate (%s)", (int)d->name, + (int)m_pMyEnt->name); + } + else + { + conoutf("%s fragged %s", (int)d->name, (int)m_pMyEnt->name); + } + } + addmsg(1, 4, SV_BOTDIED, BotManager.GetBotIndex(m_pMyEnt), KillerIndex, + d->bIsBot); + } + + m_pMyEnt->lifesequence++; + m_pMyEnt->attacking = false; + m_pMyEnt->state = CS_DEAD; + m_pMyEnt->pitch = 0; + m_pMyEnt->roll = 60; + playsound(S_DIE1+rnd(2), &m_pMyEnt->o); + spawnstate(m_pMyEnt); + m_pMyEnt->lastaction = lastmillis; + } + else + { + playsound(S_PAIN6, &m_pMyEnt->o); + } +} + +void CACBot::CheckItemPickup() +{ + if(editmode) return; + loopv(ents) + { + entity &e = ents[i]; + if(e.type==NOTUSED) continue; + + if (!e.spawned) continue; + if ((i < sents.length()) && (!sents[i].spawned)) continue; + + if(OUTBORD(e.x, e.y)) continue; + vec v = { e.x, e.y, S(e.x, e.y)->floor+m_pMyEnt->eyeheight }; + vdist(dist, t, m_pMyEnt->o, v); + if(dist<2.5) + { + PickUp(i); + } + } +} + +void CACBot::PickUp(int n) +{ + int np = 1; + loopv(players) if(players[i]) np++; + loopv(bots) if(bots[i]) np++; + np = np<3 ? 4 : (np>4 ? 2 : 3); // spawn times are dependent on number of players + int ammo = np*2; + switch(ents[n].type) + { + case I_CLIPS: AddItem(n, m_pMyEnt->ammo[GUN_PISTOL], ammo); break; + case I_AMMO: AddItem(n, m_pMyEnt->ammo[m_pMyEnt->primary], ammo); break; + case I_GRENADE: AddItem(n, m_pMyEnt->ammo[GUN_GRENADE], ammo); break; + +/* case I_HELMET: + // (100h/100g only absorbs 166 damage) + if(m_pMyEnt->armourtype==A_YELLOW && m_pMyEnt->armour>66) break; + AddItem(n, m_pMyEnt->armour, 20); + break; + + case I_ARMOUR: + AddItem(n, m_pMyEnt->armour, 20); + break; + + case OBJ_ITEM: + AddItem(n, m_pMyEnt->quadmillis, 60); + break;*/ + +/* case TRIGGER: + ents[n].spawned = false; + triggertime = lastmillis; + trigger(ents[n].attr1, ents[n].attr2, false); // needs to go over server for multiplayer + + // HACK: Reset ent goal if bot was looking for this ent + if (m_pTargetEnt == &ents[n]) + { + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + ResetWaypointVars(); + m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); + } + + BotManager.PickNextTrigger(); + break;*/ + }; +}; + +void CACBot::AddItem(int i, int &v, int spawnsec) +{ + if((i>=sents.length()) || (!sents[i].spawned)) + return; + +//fixmebot + if(v>=itemstats[ents[i].type-1].max) // don't pick up if not needed + return; + + itemstat &is = itemstats[ents[i].type-1]; + ents[i].spawned = false; + v += is.add; + if(v>is.max) v = is.max; + botplaysound(is.sound, m_pMyEnt); + sents[i].spawned = false; + sents[i].spawnsecs = spawnsec; + + addmsg(1, 3, SV_BOTITEMPICKUP, i, m_classicsp ? 100000 : spawnsec); + + // HACK: Reset ent goal if bot was looking for for this ent + if (m_pTargetEnt == &ents[i]) + { + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + ResetWaypointVars(); + m_iCheckEntsDelay = lastmillis + RandomLong(5000, 10000); + }; +} + +// AC Bot class end + +#endif diff --git a/source/src/bot/ac_bot.h b/source/src/bot/ac_bot.h index 502d5627..c83c66c5 100644 --- a/source/src/bot/ac_bot.h +++ b/source/src/bot/ac_bot.h @@ -1,47 +1,47 @@ -// -// C++ Implementation: bot -// -// Description: -// -// Header specific for AC_CUBE -// -// Author: Rick -// -// -// - -#ifndef AC_BOT_H -#define AC_BOT_H - -#ifdef AC_CUBE - -#define MAX_WEAPONS 7 - -class CACBot: public CBot -{ -public: - friend class CBotManager; - friend class CWaypointClass; - - virtual void CheckItemPickup(void); - virtual void PickUp(int n); - virtual void AddItem(int i, int &v, int spawnsec); - - // AI Functions - virtual bool ChoosePreferredWeapon(void); - void Reload(int Gun); - virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, - float flMaxHeight=JUMP_HEIGHT); - virtual bool HeadToTargetEnt(void); - virtual bool DoSPStuff(void); - - virtual void Spawn(void); - virtual void BotPain(int damage, dynent *d); -}; - -inline void AddScreenText(char *t, ...) {}; // UNDONE -inline void AddDebugText(char *t, ...) {}; // UNDONE - -#endif - -#endif +// +// C++ Implementation: bot +// +// Description: +// +// Header specific for AC_CUBE +// +// Author: Rick +// +// +// + +#ifndef AC_BOT_H +#define AC_BOT_H + +#ifdef AC_CUBE + +#define MAX_WEAPONS 7 + +class CACBot: public CBot +{ +public: + friend class CBotManager; + friend class CWaypointClass; + + virtual void CheckItemPickup(void); + virtual void PickUp(int n); + virtual void AddItem(int i, int &v, int spawnsec); + + // AI Functions + virtual bool ChoosePreferredWeapon(void); + void Reload(int Gun); + virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, + float flMaxHeight=JUMP_HEIGHT); + virtual bool HeadToTargetEnt(void); + virtual bool DoSPStuff(void); + + virtual void Spawn(void); + virtual void BotPain(int damage, dynent *d); +}; + +inline void AddScreenText(char *t, ...) {}; // UNDONE +inline void AddDebugText(char *t, ...) {}; // UNDONE + +#endif + +#endif diff --git a/source/src/bot/ac_bot_ai.cpp b/source/src/bot/ac_bot_ai.cpp index 745e45f6..e2520f79 100644 --- a/source/src/bot/ac_bot_ai.cpp +++ b/source/src/bot/ac_bot_ai.cpp @@ -1,334 +1,334 @@ -// -// C++ Implementation: ac_bot_ai -// -// Description: The AI part of the bot for ac is here(navigation, shooting etc) -// -// -// Author: -// - - - -#include "../cube.h" - -#ifdef AC_CUBE - -extern vector sents; -extern vector monsters; - -// UNDONE -weaponinfo_s WeaponInfoTable[MAX_WEAPONS] = -{ - // KNIFE - { TYPE_MELEE, 0.0f, 3.5f, 0.0f, 5.0f, 1 }, - // PISTOL - { TYPE_NORMAL, 5.0f, 12.0f, 0.0f, 50.0f, 6 }, - // SHOTGUN - { TYPE_SHOTGUN, 0.0f, 20.0f, 0.0f, 70.0f, 4 }, - // SUBGUN - { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 15 }, - // SNIPER - { TYPE_SNIPER, 15.0f, 25.0f, 0.0f, 100.0f, 3 }, - // ASSAULT - { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 10 }, - // GRENADE - { TYPE_GRENADE, 12.0f, 25.0f, 0.0f, 50.0f, 1 } -}; - -// Code of CACBot - Start - -bool CACBot::ChoosePreferredWeapon() -{ - printf("chose weapon\n"); - TMultiChoice WeaponChoices; - short sWeaponScore; - float flDist = GetDistance(m_pMyEnt->enemy->o); - - if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) - { - if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) - return true; - } - - // Choose a weapon - for(int i=0;iammo[i] == 0) continue; - - sWeaponScore = 5; // Minimal score for a weapon - - if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && - (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) - { - // In desired range for this weapon - sWeaponScore += 5; // Increase score much - } - else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || - (flDist > WeaponInfoTable[i].flMaxFireDistance)) - continue; // Wrong distance for this weapon - - // The ideal distance would be between the Min and Max desired distance. - // Score on the difference of the avarage of the Min and Max desired distance. - float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + - WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; - float flIdealDiff = fabs(flDist - flAvarage); - - if (flIdealDiff < 0.5f) // Close to ideal distance - sWeaponScore += 4; - else if (flIdealDiff <= 1.0f) - sWeaponScore += 2; - - // Now rate the weapon on available ammo... - if (WeaponInfoTable[i].sMinDesiredAmmo > 0) - { - // Calculate how much percent of the min desired ammo the bot has - float flDesiredPercent = (float(m_pMyEnt->mag[i]) / - float(WeaponInfoTable[i].sMinDesiredAmmo)) * - 100.0f; - - if(flDesiredPercent >= 175.0f) - sWeaponScore += 4; - else if(flDesiredPercent >= 100.0f) - sWeaponScore += 3; - } - - WeaponChoices.Insert(i, sWeaponScore); - } - - int WeaponSelect; - if (WeaponChoices.GetSelection(WeaponSelect)) - { - m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); - m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && - (RandomLong(1, 100) <= - m_pBotSkill->sShootAtFeetWithRLPercent)); - if(SelectGun(WeaponSelect)) - { - if(m_pMyEnt->mag[WeaponSelect]==0) reload(m_pMyEnt); // fixme, more intelligent reload - printf("gun selected %i\n", WeaponSelect); - return true; - } - } - - return false; - - -}; - -void CACBot::Reload(int Gun) -{ - -}; - -entity *CACBot::SearchForEnts(bool bUseWPs, float flRange, float flMaxHeight) -{ - /* Entities are scored on the following things: - - Visibility - - For ammo: Need(ie has this bot much of this type or not) - - distance - */ - - float flNearestDist = 9999, flDist; - entity *pNewTargetEnt = NULL; - waypoint_s *pWptNearBot = NULL, *pBestWpt = NULL; - short sScore, sHighestScore = 0; - - if ((WaypointClass.m_iWaypointCount >= 1) && bUseWPs) - pWptNearBot = GetNearestWaypoint(15.0f); - -#ifdef WP_FLOOD - if (!pWptNearBot && bUseWPs) - pWptNearBot = GetNearestFloodWP(5.0f); -#endif - - loopv(ents) - { - sScore = 0; - entity &e = ents[i]; - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - if (!ents[i].spawned) continue; - if (OUTBORD(e.x, e.y)) continue; - - bool bInteresting = false; - short sAmmo = 0, sMaxAmmo = 0; - //fixmebot - switch(e.type) - { - case I_CLIPS: - sMaxAmmo = itemstats[GUN_PISTOL-1].max; - bInteresting = (m_pMyEnt->ammo[GUN_PISTOL]ammo[GUN_PISTOL]; - break; - case I_AMMO: - sMaxAmmo = itemstats[m_pMyEnt->primary-1].max; - bInteresting = (m_pMyEnt->ammo[m_pMyEnt->primary]ammo[m_pMyEnt->primary]; - break; - case I_GRENADE: - sMaxAmmo = itemstats[GUN_GRENADE-1].max; - bInteresting = (m_pMyEnt->ammo[GUN_GRENADE]ammo[GUN_GRENADE]; - break; - }; - - if (!bInteresting) - continue; // Not an interesting item, skip - - // Score on ammo and need - if (sAmmo == -1) - { - // This entity doesn't have/need ammo - // Score on type instead - switch(e.type) - { // UNDONE - } - } - else - { - // Calculate current percentage of max ammo - float percent = ((float)sAmmo / (float)sMaxAmmo) * 100.0f; - if (percent > 100.0f) percent = 100.0f; - sScore += ((100 - short(percent))/2); - } - - flDist = GetDistance(o); - - if (flDist > flRange) continue; - - // Score on distance - float f = flDist; - if (f > 100.0f) f = 100.0f; - sScore += ((100 - short(f)) / 2); - - waypoint_s *pWptNearEnt = NULL; - // If this entity isn't visible check if there is a nearby waypoint - if (!IsReachable(o, flMaxHeight))//(!IsVisible(o)) - { - if (!pWptNearBot) continue; - -#ifdef WP_FLOOD - if (pWptNearBot->pNode->iFlags & W_FL_FLOOD) - pWptNearEnt = GetNearestFloodWP(o, 8.0f); - else -#endif - pWptNearEnt = GetNearestWaypoint(o, 15.0f); - - if (!pWptNearEnt) continue; - } - - // Score on visibility - if (pWptNearEnt == NULL) // Ent is visible - sScore += 30; - else - sScore += 15; - - if (sScore > sHighestScore) - { - // Found a valid wp near the bot and the ent,so...lets store it :) - if (pWptNearEnt) - pBestWpt = pWptNearEnt; - else - pBestWpt = NULL; // Best ent so far doesn't need any waypoints - - sHighestScore = sScore; - flNearestDist = flDist; - pNewTargetEnt = &ents[i]; - } - } - - if (pNewTargetEnt) - { - // Need waypoints to reach it? - if (pBestWpt) - { - ResetWaypointVars(); - SetCurrentWaypoint(pWptNearBot); - SetCurrentGoalWaypoint(pBestWpt); - } - - m_vGoal.x = pNewTargetEnt->x; - m_vGoal.y = pNewTargetEnt->y; - m_vGoal.z = S(pNewTargetEnt->x, pNewTargetEnt->y)->floor+player1->eyeheight; - } - - return pNewTargetEnt; -} - -bool CACBot::HeadToTargetEnt() -{ - if (m_pTargetEnt) - { - vec o = { m_pTargetEnt->x, m_pTargetEnt->y, - S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; - - if (m_pTargetEnt->spawned && (!UnderWater(m_pMyEnt->o) || - !UnderWater(o))) - { - bool bIsVisible = false; - if (m_pCurrentGoalWaypoint) - { - if ((GetDistance(o) <= 20.0f) && IsReachable(o, 1.0f)) - bIsVisible = true; - else if (HeadToGoal()) - { - //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); - //debugbeam(m_pMyEnt->o, - // m_pCurrentGoalWaypoint->pNode->v_origin); - AddDebugText("Using WPs for ents"); - return true; - } - } - else - bIsVisible = IsVisible(o); - - if (bIsVisible) - { - if (m_pCurrentWaypoint || m_pCurrentGoalWaypoint) - { - condebug("ent is now visible"); - ResetWaypointVars(); - } - - float flHeightDiff = o.z - m_pMyEnt->o.z; - bool bToHigh = false; - if (Get2DDistance(o) <= 2.0f) - { - if (flHeightDiff >= 1.5f) - { - if (flHeightDiff <= JUMP_HEIGHT) - { -#ifndef RELEASE_BUILD - char sz[64]; - sprintf(sz, "Ent z diff: %f", o.z-m_pMyEnt->o.z); - condebug(sz); -#endif - // Jump if close to ent and the ent is high - m_pMyEnt->jumpnext = true; - } - else - bToHigh = true; - } - } - - if (!bToHigh) - { - AimToVec(o); - return true; - } - } - } - } - - return false; -} - -bool CACBot::DoSPStuff() -{ - return false; -} - -// Code of CACBot - End - -#endif +// +// C++ Implementation: ac_bot_ai +// +// Description: The AI part of the bot for ac is here(navigation, shooting etc) +// +// +// Author: +// + + + +#include "../cube.h" + +#ifdef AC_CUBE + +extern vector sents; +extern vector monsters; + +// UNDONE +weaponinfo_s WeaponInfoTable[MAX_WEAPONS] = +{ + // KNIFE + { TYPE_MELEE, 0.0f, 3.5f, 0.0f, 5.0f, 1 }, + // PISTOL + { TYPE_NORMAL, 5.0f, 12.0f, 0.0f, 50.0f, 6 }, + // SHOTGUN + { TYPE_SHOTGUN, 0.0f, 20.0f, 0.0f, 70.0f, 4 }, + // SUBGUN + { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 15 }, + // SNIPER + { TYPE_SNIPER, 15.0f, 25.0f, 0.0f, 100.0f, 3 }, + // ASSAULT + { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 10 }, + // GRENADE + { TYPE_GRENADE, 12.0f, 25.0f, 0.0f, 50.0f, 1 } +}; + +// Code of CACBot - Start + +bool CACBot::ChoosePreferredWeapon() +{ + printf("chose weapon\n"); + TMultiChoice WeaponChoices; + short sWeaponScore; + float flDist = GetDistance(m_pMyEnt->enemy->o); + + if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) + { + if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) + return true; + } + + // Choose a weapon + for(int i=0;iammo[i] == 0) continue; + + sWeaponScore = 5; // Minimal score for a weapon + + if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && + (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) + { + // In desired range for this weapon + sWeaponScore += 5; // Increase score much + } + else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || + (flDist > WeaponInfoTable[i].flMaxFireDistance)) + continue; // Wrong distance for this weapon + + // The ideal distance would be between the Min and Max desired distance. + // Score on the difference of the avarage of the Min and Max desired distance. + float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + + WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; + float flIdealDiff = fabs(flDist - flAvarage); + + if (flIdealDiff < 0.5f) // Close to ideal distance + sWeaponScore += 4; + else if (flIdealDiff <= 1.0f) + sWeaponScore += 2; + + // Now rate the weapon on available ammo... + if (WeaponInfoTable[i].sMinDesiredAmmo > 0) + { + // Calculate how much percent of the min desired ammo the bot has + float flDesiredPercent = (float(m_pMyEnt->mag[i]) / + float(WeaponInfoTable[i].sMinDesiredAmmo)) * + 100.0f; + + if(flDesiredPercent >= 175.0f) + sWeaponScore += 4; + else if(flDesiredPercent >= 100.0f) + sWeaponScore += 3; + } + + WeaponChoices.Insert(i, sWeaponScore); + } + + int WeaponSelect; + if (WeaponChoices.GetSelection(WeaponSelect)) + { + m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); + m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && + (RandomLong(1, 100) <= + m_pBotSkill->sShootAtFeetWithRLPercent)); + if(SelectGun(WeaponSelect)) + { + if(m_pMyEnt->mag[WeaponSelect]==0) reload(m_pMyEnt); // fixme, more intelligent reload + printf("gun selected %i\n", WeaponSelect); + return true; + } + } + + return false; + + +}; + +void CACBot::Reload(int Gun) +{ + +}; + +entity *CACBot::SearchForEnts(bool bUseWPs, float flRange, float flMaxHeight) +{ + /* Entities are scored on the following things: + - Visibility + - For ammo: Need(ie has this bot much of this type or not) + - distance + */ + + float flNearestDist = 9999, flDist; + entity *pNewTargetEnt = NULL; + waypoint_s *pWptNearBot = NULL, *pBestWpt = NULL; + short sScore, sHighestScore = 0; + + if ((WaypointClass.m_iWaypointCount >= 1) && bUseWPs) + pWptNearBot = GetNearestWaypoint(15.0f); + +#ifdef WP_FLOOD + if (!pWptNearBot && bUseWPs) + pWptNearBot = GetNearestFloodWP(5.0f); +#endif + + loopv(ents) + { + sScore = 0; + entity &e = ents[i]; + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + if (!ents[i].spawned) continue; + if (OUTBORD(e.x, e.y)) continue; + + bool bInteresting = false; + short sAmmo = 0, sMaxAmmo = 0; + //fixmebot + switch(e.type) + { + case I_CLIPS: + sMaxAmmo = itemstats[GUN_PISTOL-1].max; + bInteresting = (m_pMyEnt->ammo[GUN_PISTOL]ammo[GUN_PISTOL]; + break; + case I_AMMO: + sMaxAmmo = itemstats[m_pMyEnt->primary-1].max; + bInteresting = (m_pMyEnt->ammo[m_pMyEnt->primary]ammo[m_pMyEnt->primary]; + break; + case I_GRENADE: + sMaxAmmo = itemstats[GUN_GRENADE-1].max; + bInteresting = (m_pMyEnt->ammo[GUN_GRENADE]ammo[GUN_GRENADE]; + break; + }; + + if (!bInteresting) + continue; // Not an interesting item, skip + + // Score on ammo and need + if (sAmmo == -1) + { + // This entity doesn't have/need ammo + // Score on type instead + switch(e.type) + { // UNDONE + } + } + else + { + // Calculate current percentage of max ammo + float percent = ((float)sAmmo / (float)sMaxAmmo) * 100.0f; + if (percent > 100.0f) percent = 100.0f; + sScore += ((100 - short(percent))/2); + } + + flDist = GetDistance(o); + + if (flDist > flRange) continue; + + // Score on distance + float f = flDist; + if (f > 100.0f) f = 100.0f; + sScore += ((100 - short(f)) / 2); + + waypoint_s *pWptNearEnt = NULL; + // If this entity isn't visible check if there is a nearby waypoint + if (!IsReachable(o, flMaxHeight))//(!IsVisible(o)) + { + if (!pWptNearBot) continue; + +#ifdef WP_FLOOD + if (pWptNearBot->pNode->iFlags & W_FL_FLOOD) + pWptNearEnt = GetNearestFloodWP(o, 8.0f); + else +#endif + pWptNearEnt = GetNearestWaypoint(o, 15.0f); + + if (!pWptNearEnt) continue; + } + + // Score on visibility + if (pWptNearEnt == NULL) // Ent is visible + sScore += 30; + else + sScore += 15; + + if (sScore > sHighestScore) + { + // Found a valid wp near the bot and the ent,so...lets store it :) + if (pWptNearEnt) + pBestWpt = pWptNearEnt; + else + pBestWpt = NULL; // Best ent so far doesn't need any waypoints + + sHighestScore = sScore; + flNearestDist = flDist; + pNewTargetEnt = &ents[i]; + } + } + + if (pNewTargetEnt) + { + // Need waypoints to reach it? + if (pBestWpt) + { + ResetWaypointVars(); + SetCurrentWaypoint(pWptNearBot); + SetCurrentGoalWaypoint(pBestWpt); + } + + m_vGoal.x = pNewTargetEnt->x; + m_vGoal.y = pNewTargetEnt->y; + m_vGoal.z = S(pNewTargetEnt->x, pNewTargetEnt->y)->floor+player1->eyeheight; + } + + return pNewTargetEnt; +} + +bool CACBot::HeadToTargetEnt() +{ + if (m_pTargetEnt) + { + vec o = { m_pTargetEnt->x, m_pTargetEnt->y, + S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; + + if (m_pTargetEnt->spawned && (!UnderWater(m_pMyEnt->o) || + !UnderWater(o))) + { + bool bIsVisible = false; + if (m_pCurrentGoalWaypoint) + { + if ((GetDistance(o) <= 20.0f) && IsReachable(o, 1.0f)) + bIsVisible = true; + else if (HeadToGoal()) + { + //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); + //debugbeam(m_pMyEnt->o, + // m_pCurrentGoalWaypoint->pNode->v_origin); + AddDebugText("Using WPs for ents"); + return true; + } + } + else + bIsVisible = IsVisible(o); + + if (bIsVisible) + { + if (m_pCurrentWaypoint || m_pCurrentGoalWaypoint) + { + condebug("ent is now visible"); + ResetWaypointVars(); + } + + float flHeightDiff = o.z - m_pMyEnt->o.z; + bool bToHigh = false; + if (Get2DDistance(o) <= 2.0f) + { + if (flHeightDiff >= 1.5f) + { + if (flHeightDiff <= JUMP_HEIGHT) + { +#ifndef RELEASE_BUILD + char sz[64]; + sprintf(sz, "Ent z diff: %f", o.z-m_pMyEnt->o.z); + condebug(sz); +#endif + // Jump if close to ent and the ent is high + m_pMyEnt->jumpnext = true; + } + else + bToHigh = true; + } + } + + if (!bToHigh) + { + AimToVec(o); + return true; + } + } + } + } + + return false; +} + +bool CACBot::DoSPStuff() +{ + return false; +} + +// Code of CACBot - End + +#endif diff --git a/source/src/bot/bot.cpp b/source/src/bot/bot.cpp index 4cdfcd85..c1c00dbe 100644 --- a/source/src/bot/bot.cpp +++ b/source/src/bot/bot.cpp @@ -1,495 +1,495 @@ -// -// C++ Implementation: bot -// -// Description: -// -// Contains code of the base bot class -// -// Author: Rick -// -// -// - -#include "../cube.h" - -dvector bots; - -extern vector sents; -extern int triggertime; -extern itemstat itemstats[]; -extern ENetHost *clienthost; - -const vec g_vecZero = {0,0,0}; - -//Bot class begin - -CBot::~CBot() -{ - // Delete all waypoints - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - while (m_WaypointList[i][j].Empty() == false) - delete m_WaypointList[i][j].Pop(); - } - } -} - -void CBot::Spawn() -{ - // Init all bot variabeles - m_pMyEnt->eyeheight = 3.2f; - m_pMyEnt->aboveeye = 0.7f; - m_pMyEnt->radius = 1.1f; - - spawnplayer(m_pMyEnt); - - m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; - m_pMyEnt->move = 0; - m_pMyEnt->enemy = NULL; - m_pMyEnt->maxspeed = 22.0f; - m_pMyEnt->health = 100; - m_pMyEnt->armour = 50; - m_pMyEnt->pitch = 0; - m_pMyEnt->roll = 0; - m_pMyEnt->state = CS_ALIVE; - m_pMyEnt->anger = 0; - m_pMyEnt->pBot = this; - - m_eCurrentBotState = STATE_NORMAL; - m_iShootDelay = m_iChangeWeaponDelay = 0; - m_iCheckEnvDelay = 0; - m_vPrevOrigin = g_vecZero; - m_iStuckCheckDelay = lastmillis + 250; - m_bStuck = false; - m_iStuckTime = 0; - m_iStrafeTime = m_iStrafeCheckDelay = 0; - m_iMoveDir = DIR_NONE; - - m_pPrevEnemy = NULL; - m_iCombatNavTime = 0; - m_iSPMoveTime = 0; - m_iEnemySearchDelay = 0; - m_iSawEnemyTime = 0; - m_bCombatJump = false; - m_iCombatJumpDelay = 0; - m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); - m_iHuntDelay = 0; - m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; - m_pHuntTarget = NULL; - m_fPrevHuntDist = 0.0f; - m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; - m_iLookAroundDelay = m_iLookAroundTime = m_iLookAroundUpdateTime = 0; - m_fLookAroundYaw = 0.0f; - m_bLookLeft = false; - - m_iLastJumpPad = 0; - m_pTargetEnt = NULL; - while(!m_UnreachableEnts.Empty()) delete m_UnreachableEnts.Pop(); - m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; - m_iCheckEntsDelay = 0; - m_iCheckTriggersDelay = 0; - m_iLookForWaypointTime = 0; - - m_iAimDelay = 0; - m_fYawToTurn = m_fPitchToTurn = 0.0f; - - m_vGoal = m_vWaterGoal = g_vecZero; - - ResetWaypointVars(); -} - -void CBot::Think() -{ - if (intermission) - return; - - // Bot is dead? - if (m_pMyEnt->state == CS_DEAD) - { - if(lastmillis-m_pMyEnt->lastaction<1200) - { - m_pMyEnt->move = 0; - moveplayer(m_pMyEnt, 1, false); - } - else if (!m_arena) - Spawn(); - - SendBotInfo(); - return; - } - - CheckItemPickup(); - - TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(), *tmp; - while(p) - { - if ((lastmillis - p->Entry->time) > 3500) - { - tmp = p; - p = p->next; - delete tmp->Entry; - m_UnreachableEnts.DeleteNode(tmp); - continue; - } - p = p->next; - } - - if (!BotManager.IdleBots()) - { - MainAI(); - } - else - { - ResetMoveSpeed(); - } - - // Aim to ideal yaw and pitch - AimToIdeal(); - - // Store current location, to see if the bot is stuck - m_vPrevOrigin = m_pMyEnt->o; - - // Don't check for stuck if the bot doesn't want to move - if (!m_pMyEnt->move && !m_pMyEnt->strafe) - m_iStuckCheckDelay = max(m_iStuckCheckDelay, (lastmillis+100)); - - // Move the bot - moveplayer(m_pMyEnt, 1, false); - - // Update bot info on all clients - SendBotInfo(); -} - -void CBot::AimToVec(const vec &o) -{ - m_pMyEnt->targetpitch = atan2(o.z-m_pMyEnt->o.z, GetDistance(o))*180/PI; - m_pMyEnt->targetyaw = -(float)atan2(o.x - m_pMyEnt->o.x, o.y - - m_pMyEnt->o.y)/PI*180+180; -} - -void CBot::AimToIdeal() -{ - if (m_iAimDelay > lastmillis) - return; - - float MaxXSpeed = RandomFloat(m_pBotSkill->flMinAimXSpeed, - m_pBotSkill->flMaxAimXSpeed); - float MaxYSpeed = RandomFloat(m_pBotSkill->flMinAimYSpeed, - m_pBotSkill->flMaxAimYSpeed); - float XOffset = RandomFloat(m_pBotSkill->flMinAimXOffset, - m_pBotSkill->flMaxAimXOffset); - float YOffset = RandomFloat(m_pBotSkill->flMinAimYOffset, - m_pBotSkill->flMaxAimYOffset); - float RealXOffset, RealYOffset; - float AimXSpeed = MaxXSpeed, AimYSpeed = MaxYSpeed; - float XDiff = fabs(m_pMyEnt->targetpitch - m_pMyEnt->pitch); - float YDiff = fabs(m_pMyEnt->targetyaw - m_pMyEnt->yaw); - - // How higher the diff, how higher the offsets and aim speed - - if (XOffset) - { - if (RandomLong(0, 1)) - RealXOffset = XDiff * (XOffset / 100.0f); - else - RealXOffset = -(XDiff * (XOffset / 100.0f)); - } - else - RealXOffset = 0.0f; - - if (YOffset) - { - if (RandomLong(0, 1)) - RealYOffset = YDiff * (YOffset / 100.0f); - else - RealYOffset = -(YDiff * (YOffset / 100.0f)); - } - else - RealYOffset = 0.0f; - - - if (XDiff >= 1.0f) - AimXSpeed = (AimXSpeed * (XDiff / 80.0f)) + (AimXSpeed * 0.25f); - else - AimXSpeed *= 0.01f; - - if (YDiff >= 1.0f) - AimYSpeed = (AimYSpeed * (YDiff / 70.0f)) + (AimYSpeed * 0.25f); - else - AimYSpeed *= 0.015f; - - m_fPitchToTurn = fabs((m_pMyEnt->targetpitch + RealXOffset) - m_pMyEnt->pitch); - m_fYawToTurn = fabs((m_pMyEnt->targetyaw + RealYOffset) - m_pMyEnt->yaw); - - float flIdealPitch = ChangeAngle(AimXSpeed, m_pMyEnt->targetpitch + RealXOffset, m_pMyEnt->pitch); - float flIdealYaw = ChangeAngle(AimYSpeed, m_pMyEnt->targetyaw + RealYOffset, m_pMyEnt->yaw); - -// m_pMyEnt->pitch = WrapXAngle(m_pMyEnt->targetpitch); // Uncomment for instant aiming -// m_pMyEnt->yaw = WrapYZAngle(m_pMyEnt->targetyaw); - - m_pMyEnt->pitch = WrapXAngle(flIdealPitch); - m_pMyEnt->yaw = WrapYZAngle(flIdealYaw); -} - -// Function code by Botman -float CBot::ChangeAngle(float speed, float ideal, float current) -{ - float current_180; // current +/- 180 degrees - float diff; - - // find the difference in the current and ideal angle - diff = fabs(current - ideal); - - // speed that we can turn during this frame... - speed = speed * (float(BotManager.m_iFrameTime)/1000.0f); - - // check if difference is less than the max degrees per turn - if (diff < speed) - speed = diff; // just need to turn a little bit (less than max) - - // check if the bot is already facing the idealpitch direction... - if (diff <= 1.0f) - return ideal; - - if ((current >= 180) && (ideal >= 180)) - { - if (current > ideal) - current -= speed; - else - current += speed; - } - else if ((current >= 180) && (ideal < 180)) - { - current_180 = current - 180; - - if (current_180 > ideal) - current += speed; - else - current -= speed; - } - else if ((current < 180) && (ideal >= 180)) - { - current_180 = current + 180; - - if (current_180 > ideal) - current += speed; - else - current -= speed; - } - else // (current < 180) && (ideal < 180) - { - if (current > ideal) - current -= speed; - else - current += speed; - } - - - return current; -} - -void CBot::SendBotInfo() -{ - if(lastmillis-m_iLastBotUpdate<40) return; // don't update faster than 25fps - m_iLastBotUpdate = lastmillis; - - int x = (int)(m_pMyEnt->o.x*DMF); - int y = (int)(m_pMyEnt->o.y*DMF); - int z = (int)(m_pMyEnt->o.z*DMF); - int yaw = (int)(m_pMyEnt->yaw*DAF); - int pitch = (int)(m_pMyEnt->pitch*DAF); - int roll = (int)(m_pMyEnt->roll*DAF); - int velx = (int)(m_pMyEnt->vel.x*DVF); - int vely = (int)(m_pMyEnt->vel.y*DVF); - int velz = (int)(m_pMyEnt->vel.z*DVF); - // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 - int moveflags = (m_pMyEnt->strafe&3) | ((m_pMyEnt->move&3)<<2) | - (((int)m_pMyEnt->onfloor)<<4) | - ((editmode ? CS_EDITING : - m_pMyEnt->state)<<5); - - ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0); - uchar *start = packet->data; - uchar *p = start+2; - - putint(p, SV_BOTUPDATE); - putint(p, BotManager.GetBotIndex(m_pMyEnt)); - putint(p, x); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes - putint(p, y); - putint(p, z); - putint(p, yaw); - putint(p, pitch); - putint(p, roll); - putint(p, velx); // quantize to 1/100, almost always 1 byte - putint(p, vely); - putint(p, velz); - // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 - putint(p, moveflags); - - if(!m_bSendC2SInit) // tell other clients who I am - { - packet->flags = ENET_PACKET_FLAG_RELIABLE; - m_bSendC2SInit = true; - putint(p, SV_ADDBOT); - putint(p, BotManager.GetBotIndex(m_pMyEnt)); - sendstring(m_pMyEnt->name, p); - sendstring(m_pMyEnt->team, p); - putint(p, m_pMyEnt->lifesequence); - }; - - - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - incomingdemodata(start, p-start, true); - if(clienthost) - { - enet_host_broadcast(clienthost, 0, packet); - enet_host_flush(clienthost); - } - else - localclienttoserver(packet); -} - -float CBot::GetDistance(const vec &o) -{ - vdist(flDist, vecto, m_pMyEnt->o, o); - return flDist; -} - -float CBot::GetDistance(const vec &v1, const vec &v2) -{ - vdist(flDist, vecto, v1, v2); - return flDist; -} - -float CBot::GetDistance(entity *e) -{ - vec v = { e->x, e->y, e->z }; - vdist(flDist, vecto, m_pMyEnt->o, v); - return flDist; -} - -bool CBot::SelectGun(int Gun) -{ - if (m_pMyEnt->ammo[Gun]) - { - //temporary doc out argh -/* if (m_pMyEnt->gunselect != Gun) botplaysound(S_WEAPLOAD, m_pMyEnt); */ - m_pMyEnt->gunselect = Gun; - return true; - } - return false; -} - -bool CBot::IsVisible(entity *e, bool CheckPlayers) -{ - vec v = { e->x, e->y, e->z }; - return ::IsVisible(m_pMyEnt->o, v, (CheckPlayers) ? m_pMyEnt : NULL); -} - -bool CBot::IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist) -{ - static vec angles, end, forward, right, up; - static traceresult_s tr; - - end = o; - angles = GetViewAngles(); - angles.x = 0; - - if (Dir & UP) - angles.x = WrapXAngle(angles.x + 45.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 45.0f); - - if ((Dir & FORWARD) || (Dir & BACKWARD)) - { - if (Dir & BACKWARD) - angles.y = WrapYZAngle(angles.y + 180.0f); - - if (Dir & LEFT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y - 45.0f); - else - angles.y = WrapYZAngle(angles.y + 45.0f); - } - else if (Dir & RIGHT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y + 45.0f); - else - angles.y = WrapYZAngle(angles.y - 45.0f); - } - } - else if (Dir & LEFT) - angles.y = WrapYZAngle(angles.y - 90.0f); - else if (Dir & RIGHT) - angles.y = WrapYZAngle(angles.y + 90.0f); - else if (Dir & UP) - angles.x = WrapXAngle(angles.x + 90.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 90.0f); - - AnglesToVectors(angles, forward, right, up); - - vmul(forward, flDist); - vadd(end, forward); - - TraceLine(o, end, m_pMyEnt, CheckPlayers, &tr); - - if (pEndDist) - *pEndDist = GetDistance(o, tr.end); - - return !tr.collided; -} - -void CBot::SetMoveDir(int iMoveDir, bool add) -{ - if (iMoveDir & FORWARD) - m_pMyEnt->move = 1; - else if (m_iMoveDir & BACKWARD) - m_pMyEnt->move = -1; - else if (!add) - m_pMyEnt->move = 0; - - if (iMoveDir & LEFT) - m_pMyEnt->strafe = 1; - else if (m_iMoveDir & RIGHT) - m_pMyEnt->strafe = -1; - else if (!add) - m_pMyEnt->strafe = 0; - - if (iMoveDir & UP) - m_pMyEnt->jumpnext = true; -} - -// Used when switching to another task/state -void CBot::ResetCurrentTask() -{ - switch (m_eCurrentBotState) - { - case STATE_ENEMY: - m_pMyEnt->enemy = NULL; - m_pTargetEnt = NULL; - m_iCombatNavTime = m_iMoveDir = 0; - m_bCombatJump = false; - m_vGoal = g_vecZero; - break; - case STATE_ENT: - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - break; - case STATE_SP: - m_iSPMoveTime = m_iMoveDir = 0; - m_pTargetEnt = NULL; - m_vGoal = g_vecZero; - break; - case STATE_NORMAL: - m_iStrafeTime = m_iMoveDir = 0; - break; - } -} - -// Bot class end +// +// C++ Implementation: bot +// +// Description: +// +// Contains code of the base bot class +// +// Author: Rick +// +// +// + +#include "../cube.h" + +dvector bots; + +extern vector sents; +extern int triggertime; +extern itemstat itemstats[]; +extern ENetHost *clienthost; + +const vec g_vecZero = {0,0,0}; + +//Bot class begin + +CBot::~CBot() +{ + // Delete all waypoints + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + while (m_WaypointList[i][j].Empty() == false) + delete m_WaypointList[i][j].Pop(); + } + } +} + +void CBot::Spawn() +{ + // Init all bot variabeles + m_pMyEnt->eyeheight = 3.2f; + m_pMyEnt->aboveeye = 0.7f; + m_pMyEnt->radius = 1.1f; + + spawnplayer(m_pMyEnt); + + m_pMyEnt->targetyaw = m_pMyEnt->yaw = m_pMyEnt->targetpitch = m_pMyEnt->pitch = 0.0f; + m_pMyEnt->move = 0; + m_pMyEnt->enemy = NULL; + m_pMyEnt->maxspeed = 22.0f; + m_pMyEnt->health = 100; + m_pMyEnt->armour = 50; + m_pMyEnt->pitch = 0; + m_pMyEnt->roll = 0; + m_pMyEnt->state = CS_ALIVE; + m_pMyEnt->anger = 0; + m_pMyEnt->pBot = this; + + m_eCurrentBotState = STATE_NORMAL; + m_iShootDelay = m_iChangeWeaponDelay = 0; + m_iCheckEnvDelay = 0; + m_vPrevOrigin = g_vecZero; + m_iStuckCheckDelay = lastmillis + 250; + m_bStuck = false; + m_iStuckTime = 0; + m_iStrafeTime = m_iStrafeCheckDelay = 0; + m_iMoveDir = DIR_NONE; + + m_pPrevEnemy = NULL; + m_iCombatNavTime = 0; + m_iSPMoveTime = 0; + m_iEnemySearchDelay = 0; + m_iSawEnemyTime = 0; + m_bCombatJump = false; + m_iCombatJumpDelay = 0; + m_bShootAtFeet = (RandomLong(1, 100) <= m_pBotSkill->sShootAtFeetWithRLPercent); + m_iHuntDelay = 0; + m_vHuntLocation = m_vPrevHuntLocation = g_vecZero; + m_pHuntTarget = NULL; + m_fPrevHuntDist = 0.0f; + m_iHuntLastTurnLessTime = m_iHuntPlayerUpdateTime = m_iHuntPauseTime = 0; + m_iLookAroundDelay = m_iLookAroundTime = m_iLookAroundUpdateTime = 0; + m_fLookAroundYaw = 0.0f; + m_bLookLeft = false; + + m_iLastJumpPad = 0; + m_pTargetEnt = NULL; + while(!m_UnreachableEnts.Empty()) delete m_UnreachableEnts.Pop(); + m_iCheckTeleporterDelay = m_iCheckJumppadsDelay = 0; + m_iCheckEntsDelay = 0; + m_iCheckTriggersDelay = 0; + m_iLookForWaypointTime = 0; + + m_iAimDelay = 0; + m_fYawToTurn = m_fPitchToTurn = 0.0f; + + m_vGoal = m_vWaterGoal = g_vecZero; + + ResetWaypointVars(); +} + +void CBot::Think() +{ + if (intermission) + return; + + // Bot is dead? + if (m_pMyEnt->state == CS_DEAD) + { + if(lastmillis-m_pMyEnt->lastaction<1200) + { + m_pMyEnt->move = 0; + moveplayer(m_pMyEnt, 1, false); + } + else if (!m_arena) + Spawn(); + + SendBotInfo(); + return; + } + + CheckItemPickup(); + + TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(), *tmp; + while(p) + { + if ((lastmillis - p->Entry->time) > 3500) + { + tmp = p; + p = p->next; + delete tmp->Entry; + m_UnreachableEnts.DeleteNode(tmp); + continue; + } + p = p->next; + } + + if (!BotManager.IdleBots()) + { + MainAI(); + } + else + { + ResetMoveSpeed(); + } + + // Aim to ideal yaw and pitch + AimToIdeal(); + + // Store current location, to see if the bot is stuck + m_vPrevOrigin = m_pMyEnt->o; + + // Don't check for stuck if the bot doesn't want to move + if (!m_pMyEnt->move && !m_pMyEnt->strafe) + m_iStuckCheckDelay = max(m_iStuckCheckDelay, (lastmillis+100)); + + // Move the bot + moveplayer(m_pMyEnt, 1, false); + + // Update bot info on all clients + SendBotInfo(); +} + +void CBot::AimToVec(const vec &o) +{ + m_pMyEnt->targetpitch = atan2(o.z-m_pMyEnt->o.z, GetDistance(o))*180/PI; + m_pMyEnt->targetyaw = -(float)atan2(o.x - m_pMyEnt->o.x, o.y - + m_pMyEnt->o.y)/PI*180+180; +} + +void CBot::AimToIdeal() +{ + if (m_iAimDelay > lastmillis) + return; + + float MaxXSpeed = RandomFloat(m_pBotSkill->flMinAimXSpeed, + m_pBotSkill->flMaxAimXSpeed); + float MaxYSpeed = RandomFloat(m_pBotSkill->flMinAimYSpeed, + m_pBotSkill->flMaxAimYSpeed); + float XOffset = RandomFloat(m_pBotSkill->flMinAimXOffset, + m_pBotSkill->flMaxAimXOffset); + float YOffset = RandomFloat(m_pBotSkill->flMinAimYOffset, + m_pBotSkill->flMaxAimYOffset); + float RealXOffset, RealYOffset; + float AimXSpeed = MaxXSpeed, AimYSpeed = MaxYSpeed; + float XDiff = fabs(m_pMyEnt->targetpitch - m_pMyEnt->pitch); + float YDiff = fabs(m_pMyEnt->targetyaw - m_pMyEnt->yaw); + + // How higher the diff, how higher the offsets and aim speed + + if (XOffset) + { + if (RandomLong(0, 1)) + RealXOffset = XDiff * (XOffset / 100.0f); + else + RealXOffset = -(XDiff * (XOffset / 100.0f)); + } + else + RealXOffset = 0.0f; + + if (YOffset) + { + if (RandomLong(0, 1)) + RealYOffset = YDiff * (YOffset / 100.0f); + else + RealYOffset = -(YDiff * (YOffset / 100.0f)); + } + else + RealYOffset = 0.0f; + + + if (XDiff >= 1.0f) + AimXSpeed = (AimXSpeed * (XDiff / 80.0f)) + (AimXSpeed * 0.25f); + else + AimXSpeed *= 0.01f; + + if (YDiff >= 1.0f) + AimYSpeed = (AimYSpeed * (YDiff / 70.0f)) + (AimYSpeed * 0.25f); + else + AimYSpeed *= 0.015f; + + m_fPitchToTurn = fabs((m_pMyEnt->targetpitch + RealXOffset) - m_pMyEnt->pitch); + m_fYawToTurn = fabs((m_pMyEnt->targetyaw + RealYOffset) - m_pMyEnt->yaw); + + float flIdealPitch = ChangeAngle(AimXSpeed, m_pMyEnt->targetpitch + RealXOffset, m_pMyEnt->pitch); + float flIdealYaw = ChangeAngle(AimYSpeed, m_pMyEnt->targetyaw + RealYOffset, m_pMyEnt->yaw); + +// m_pMyEnt->pitch = WrapXAngle(m_pMyEnt->targetpitch); // Uncomment for instant aiming +// m_pMyEnt->yaw = WrapYZAngle(m_pMyEnt->targetyaw); + + m_pMyEnt->pitch = WrapXAngle(flIdealPitch); + m_pMyEnt->yaw = WrapYZAngle(flIdealYaw); +} + +// Function code by Botman +float CBot::ChangeAngle(float speed, float ideal, float current) +{ + float current_180; // current +/- 180 degrees + float diff; + + // find the difference in the current and ideal angle + diff = fabs(current - ideal); + + // speed that we can turn during this frame... + speed = speed * (float(BotManager.m_iFrameTime)/1000.0f); + + // check if difference is less than the max degrees per turn + if (diff < speed) + speed = diff; // just need to turn a little bit (less than max) + + // check if the bot is already facing the idealpitch direction... + if (diff <= 1.0f) + return ideal; + + if ((current >= 180) && (ideal >= 180)) + { + if (current > ideal) + current -= speed; + else + current += speed; + } + else if ((current >= 180) && (ideal < 180)) + { + current_180 = current - 180; + + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else if ((current < 180) && (ideal >= 180)) + { + current_180 = current + 180; + + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else // (current < 180) && (ideal < 180) + { + if (current > ideal) + current -= speed; + else + current += speed; + } + + + return current; +} + +void CBot::SendBotInfo() +{ + if(lastmillis-m_iLastBotUpdate<40) return; // don't update faster than 25fps + m_iLastBotUpdate = lastmillis; + + int x = (int)(m_pMyEnt->o.x*DMF); + int y = (int)(m_pMyEnt->o.y*DMF); + int z = (int)(m_pMyEnt->o.z*DMF); + int yaw = (int)(m_pMyEnt->yaw*DAF); + int pitch = (int)(m_pMyEnt->pitch*DAF); + int roll = (int)(m_pMyEnt->roll*DAF); + int velx = (int)(m_pMyEnt->vel.x*DVF); + int vely = (int)(m_pMyEnt->vel.y*DVF); + int velz = (int)(m_pMyEnt->vel.z*DVF); + // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 + int moveflags = (m_pMyEnt->strafe&3) | ((m_pMyEnt->move&3)<<2) | + (((int)m_pMyEnt->onfloor)<<4) | + ((editmode ? CS_EDITING : + m_pMyEnt->state)<<5); + + ENetPacket *packet = enet_packet_create (NULL, MAXTRANS, 0); + uchar *start = packet->data; + uchar *p = start+2; + + putint(p, SV_BOTUPDATE); + putint(p, BotManager.GetBotIndex(m_pMyEnt)); + putint(p, x); // quantize coordinates to 1/16th of a cube, between 1 and 3 bytes + putint(p, y); + putint(p, z); + putint(p, yaw); + putint(p, pitch); + putint(p, roll); + putint(p, velx); // quantize to 1/100, almost always 1 byte + putint(p, vely); + putint(p, velz); + // pack rest in 1 byte: strafe:2, move:2, onfloor:1, state:3 + putint(p, moveflags); + + if(!m_bSendC2SInit) // tell other clients who I am + { + packet->flags = ENET_PACKET_FLAG_RELIABLE; + m_bSendC2SInit = true; + putint(p, SV_ADDBOT); + putint(p, BotManager.GetBotIndex(m_pMyEnt)); + sendstring(m_pMyEnt->name, p); + sendstring(m_pMyEnt->team, p); + putint(p, m_pMyEnt->lifesequence); + }; + + + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + incomingdemodata(start, p-start, true); + if(clienthost) + { + enet_host_broadcast(clienthost, 0, packet); + enet_host_flush(clienthost); + } + else + localclienttoserver(packet); +} + +float CBot::GetDistance(const vec &o) +{ + vdist(flDist, vecto, m_pMyEnt->o, o); + return flDist; +} + +float CBot::GetDistance(const vec &v1, const vec &v2) +{ + vdist(flDist, vecto, v1, v2); + return flDist; +} + +float CBot::GetDistance(entity *e) +{ + vec v = { e->x, e->y, e->z }; + vdist(flDist, vecto, m_pMyEnt->o, v); + return flDist; +} + +bool CBot::SelectGun(int Gun) +{ + if (m_pMyEnt->ammo[Gun]) + { + //temporary doc out argh +/* if (m_pMyEnt->gunselect != Gun) botplaysound(S_WEAPLOAD, m_pMyEnt); */ + m_pMyEnt->gunselect = Gun; + return true; + } + return false; +} + +bool CBot::IsVisible(entity *e, bool CheckPlayers) +{ + vec v = { e->x, e->y, e->z }; + return ::IsVisible(m_pMyEnt->o, v, (CheckPlayers) ? m_pMyEnt : NULL); +} + +bool CBot::IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist) +{ + static vec angles, end, forward, right, up; + static traceresult_s tr; + + end = o; + angles = GetViewAngles(); + angles.x = 0; + + if (Dir & UP) + angles.x = WrapXAngle(angles.x + 45.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 45.0f); + + if ((Dir & FORWARD) || (Dir & BACKWARD)) + { + if (Dir & BACKWARD) + angles.y = WrapYZAngle(angles.y + 180.0f); + + if (Dir & LEFT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y - 45.0f); + else + angles.y = WrapYZAngle(angles.y + 45.0f); + } + else if (Dir & RIGHT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y + 45.0f); + else + angles.y = WrapYZAngle(angles.y - 45.0f); + } + } + else if (Dir & LEFT) + angles.y = WrapYZAngle(angles.y - 90.0f); + else if (Dir & RIGHT) + angles.y = WrapYZAngle(angles.y + 90.0f); + else if (Dir & UP) + angles.x = WrapXAngle(angles.x + 90.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 90.0f); + + AnglesToVectors(angles, forward, right, up); + + vmul(forward, flDist); + vadd(end, forward); + + TraceLine(o, end, m_pMyEnt, CheckPlayers, &tr); + + if (pEndDist) + *pEndDist = GetDistance(o, tr.end); + + return !tr.collided; +} + +void CBot::SetMoveDir(int iMoveDir, bool add) +{ + if (iMoveDir & FORWARD) + m_pMyEnt->move = 1; + else if (m_iMoveDir & BACKWARD) + m_pMyEnt->move = -1; + else if (!add) + m_pMyEnt->move = 0; + + if (iMoveDir & LEFT) + m_pMyEnt->strafe = 1; + else if (m_iMoveDir & RIGHT) + m_pMyEnt->strafe = -1; + else if (!add) + m_pMyEnt->strafe = 0; + + if (iMoveDir & UP) + m_pMyEnt->jumpnext = true; +} + +// Used when switching to another task/state +void CBot::ResetCurrentTask() +{ + switch (m_eCurrentBotState) + { + case STATE_ENEMY: + m_pMyEnt->enemy = NULL; + m_pTargetEnt = NULL; + m_iCombatNavTime = m_iMoveDir = 0; + m_bCombatJump = false; + m_vGoal = g_vecZero; + break; + case STATE_ENT: + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + break; + case STATE_SP: + m_iSPMoveTime = m_iMoveDir = 0; + m_pTargetEnt = NULL; + m_vGoal = g_vecZero; + break; + case STATE_NORMAL: + m_iStrafeTime = m_iMoveDir = 0; + break; + } +} + +// Bot class end diff --git a/source/src/bot/bot.h b/source/src/bot/bot.h index db4a3c16..d4ad1b1c 100644 --- a/source/src/bot/bot.h +++ b/source/src/bot/bot.h @@ -1,430 +1,430 @@ -// -// C++ Interface: bot -// -// Description: -// -// Main bot header -// -// -// Author: Rick -// -// -// - - -/* - - TODO list - - -bots that "cover" each other in team matches ? - -If a bot gets ammo it should ignore that kind of ammo for the next 10 seconds. ? - -bots should ignore players that are too far away ? - -bots should dodge/jump when shot at - -If the bot's only weapon is the fist it should make looking for ammo its priority - Done ? - -bots should rocket jump to get quads and the 150 armor if normal jumping doesn't - work (only if health is x so they don't suicide). - -Make all the "UNDONE's" done :) - -Finish waypoint navigation - -Make bot personalities - -More advanced enemy scoring - -Bots should less bump into walls - -(Better) Coop support - -Check for reachable instead of visible in GetNearestWaypoint - -Waypoint experience file - -Bots should avoid team mates - -Handle failed goals better(ie remember them) - -Test multiplayer - -Fix bots getting stuck in each other - -Optimize hunting code - -Check/Fix msg code -*/ - -#ifndef BOT_H -#define BOT_H - - -//#define RELEASE_BUILD // Set when you want to make a release build - - -// Set for which mod the code is - -//#define VANILLA_CUBE -#define AC_CUBE -//#define CUBE_X - -#include "bot_util.h" -#include "bot_waypoint.h" - -#ifdef __linux__ -#include -#else // Windows doesn't have syslog -#define openlog (void)0 -#define syslog (void)0 -#define closelog (void)0 -#endif - -extern bool dedserv; -extern itemstat itemstats[]; - -#ifdef RELEASE_BUILD -inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) {}; -inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) {}; -inline void debugbeam(vec &s, vec &e) { }; -#else -inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; -inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; -inline void debugbeam(vec &s, vec &e) { /*if (!dedserv) particle_trail(1, 500, s, e);*/ }; -#endif - -#define JUMP_HEIGHT 4.0f // NOT accurate - -#define FORWARD (1<<1) -#define BACKWARD (1<<2) -#define LEFT (1<<3) -#define RIGHT (1<<4) -#define UP (1<<5) -#define DOWN (1<<6) -#define DIR_NONE 0 - -//fixmebot -#define m_sp 1000 -#define m_classicsp 1000 - -enum EBotCommands // For voting of bot commands -{ - COMMAND_ADDBOT=0, - COMMAND_KICKBOT, - COMMAND_BOTSKILL -}; - -struct bot_skill_s -{ - float flMinReactionDelay; // Minimal reaction time - float flMaxReactionDelay; // Maximal reaction time - float flMinAimXSpeed; // Minimal X aim speed in degrees(base value) - float flMaxAimXSpeed; // Maximal X aim speed in degrees(base value) - float flMinAimYSpeed; // Minimal Y aim speed in degrees(base value) - float flMaxAimYSpeed; // Maximal Y aim speed in degrees(base value) - float flMinAimXOffset; // Minimal X aim offset in degrees(base value) - float flMaxAimXOffset; // Maximal X aim offset in degrees(base value) - float flMinAimYOffset; // Minimal Y aim offset in degrees(base value) - float flMaxAimYOffset; // Maximal Y aim offset in degrees(base value) - float flMinAttackDelay; // Minimal delay for when a bot can attack again - float flMaxAttackDelay; // Maximal delay for when a bot can attack again - float flMinEnemySearchDelay; // Minimal delay for when a bot can search for an - // enemy again - float flMaxEnemySearchDelay; // Maximal delay for when a bot can search for an - // enemy again - short sShootAtFeetWithRLPercent; // Percent that a bot shoot with a rocket - // launcher at the enemy feet. - int iMaxHearVolume; // Max volume that bot can hear - bool bCanPredict; // Can this bot predict his enemy position? - bool bCircleStrafe; // Can this bot circle strafe? - bool bCanSearchItemsInCombat; -}; - -enum EBotWeaponTypes -{ - TYPE_MELEE, // Fist, knife etc - TYPE_NORMAL, // Normal weapon, distance doesn't really matter(ie pistol) - TYPE_SHOTGUN, - TYPE_ROCKET, - TYPE_SNIPER, // Rifle, sniper etc - TYPE_GRENADE, - TYPE_AUTO // Chain gun, machine gun etc -}; - -struct weaponinfo_s -{ - EBotWeaponTypes eWeaponType; - float flMinDesiredDistance; - float flMaxDesiredDistance; - float flMinFireDistance; - float flMaxFireDistance; - short sMinDesiredAmmo; -}; - -enum ECurrentBotState -{ - STATE_ENEMY, // Bot has an enemy - STATE_HUNT, // Bot is hunting an enemy - STATE_ENT, // Bot is heading to an entity - STATE_SP, // Bot is doing sp specific stuff - STATE_NORMAL // Bot is doing normal navigation -}; - -struct unreachable_ent_s -{ - entity *ent; - int time; - - unreachable_ent_s(entity *e, int t) : ent(e), time(t) { }; -}; - -class CBot -{ -public: - dynent *m_pMyEnt; - int m_iLastBotUpdate; - - // Combat variabels - dynent *m_pPrevEnemy; - int m_iShootDelay; - int m_iChangeWeaponDelay; - int m_iCombatNavTime; - int m_iEnemySearchDelay; - int m_iSawEnemyTime; - bool m_bCombatJump; - float m_iCombatJumpDelay; - bool m_bShootAtFeet; - - // Hunting variabeles - int m_iHuntDelay; - vec m_vHuntLocation, m_vPrevHuntLocation; - dynent *m_pHuntTarget; - float m_fPrevHuntDist; - int m_iHuntPauseTime, m_iHuntPlayerUpdateTime, m_iHuntLastTurnLessTime; - int m_iLookAroundDelay, m_iLookAroundTime, m_iLookAroundUpdateTime; - float m_fLookAroundYaw; - bool m_bLookLeft; - - // Navigation variabeles - int m_iCheckEnvDelay; - int m_iLastJumpPad; - vec m_vPrevOrigin; - float m_iStuckCheckDelay; - bool m_bStuck; - int m_iStuckTime; - int m_iStrafeTime; - int m_iStrafeCheckDelay; - int m_iMoveDir; - int m_iSPMoveTime; - bool m_bCalculatingAStarPath; - TLinkedList m_AStarNodeList; - TPriorList m_AStarOpenList[2]; - TLinkedList m_AStarClosedList[2]; - vec m_vWaterGoal; // Place to go to when underwater - - // Waypoint variabeles - TLinkedList m_WaypointList[MAX_MAP_GRIDS][MAX_MAP_GRIDS]; - float m_iWaypointTime; - waypoint_s *m_pCurrentWaypoint; - waypoint_s *m_pCurrentGoalWaypoint; - vec m_vGoal; - waypoint_s *m_pPrevWaypoints[5]; - int m_iRandomWaypointTime; - float m_fPrevWaypointDistance; - int m_iLookForWaypointTime; - int m_iWaypointHeadLastTurnLessTime; // Last time the didn't had to turn much wile - // heading to a WP - int m_iWaypointHeadPauseTime; // Pause time to give the bot time to aim. - bool m_bGoToDebugGoal; - - // Misc stuff - ECurrentBotState m_eCurrentBotState; - entity *m_pTargetEnt; - TLinkedList m_UnreachableEnts; - int m_iCheckTeleporterDelay; - int m_iCheckJumppadsDelay; - int m_iCheckEntsDelay; - int m_iCheckTriggersDelay; - int m_iAimDelay; - float m_fYawToTurn, m_fPitchToTurn; - bot_skill_s *m_pBotSkill; // Pointer to current bot skill - short m_sSkillNr; // Skill number, 0 == best 4 == worst - - void AimToVec(const vec &o); - void AimToIdeal(void); - float ChangeAngle(float speed, float ideal, float current); - bool SelectGun(int Gun); - virtual void CheckItemPickup(void) = 0; - virtual void PickUp(int n) = 0; - virtual void AddItem(int i, int &v, int spawnsec) = 0; - virtual void BotPain(int damage, dynent *d) = 0; - void SendBotInfo(void); - float GetDistance(const vec &o); - float GetDistance(const vec &v1, const vec &v2); - float GetDistance(entity *e); - float Get2DDistance(const vec &v) { return ::Get2DDistance(m_pMyEnt->o, v); }; - vec GetViewAngles(void) { vec temp = { m_pMyEnt->pitch, m_pMyEnt->yaw, - m_pMyEnt->roll }; return temp; }; - void ResetMoveSpeed(void) { m_pMyEnt->move = m_pMyEnt->strafe = 0; }; - void SetMoveDir(int iMoveDir, bool add); - void ResetCurrentTask(); - - // AI Functions - bool FindEnemy(void); - void ShootEnemy(void); - bool CheckHunt(void); - bool HuntEnemy(void); - void DoCombatNav(void); - void MainAI(void); - bool CheckStuck(void); - bool CheckJump(void); - bool CheckStrafe(void); - void CheckFOV(void); - bool IsVisible(const vec &o, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, o, - (CheckPlayers) ? m_pMyEnt : - NULL); }; - bool IsVisible(dynent *d, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, d->o, - (CheckPlayers) ? - m_pMyEnt : NULL); }; - bool IsVisible(entity *e, bool CheckPlayers = false); - bool IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist = NULL); - bool IsVisible(int Dir, float flDist, bool CheckPlayers) - { return IsVisible(m_pMyEnt->o, Dir, flDist, CheckPlayers); }; - bool IsInFOV(const vec &o); - bool IsInFOV(dynent *d) { return IsInFOV(d->o); }; - int GetShootDelay(void); - virtual bool ChoosePreferredWeapon(void); - virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, - float flMaxHeight=JUMP_HEIGHT) = NULL; - virtual bool HeadToTargetEnt(void) = 0; - bool CheckItems(void); - bool InUnreachableList(entity *e); - virtual bool DoSPStuff(void) = 0; - vec GetEnemyPos(dynent *d); - bool AStar(void); - float AStarCost(waypoint_s *pWP1, waypoint_s *pWP2); - void CleanAStarLists(bool bPathFailed); - bool IsReachable(vec to, float flMaxHeight=JUMP_HEIGHT); - bool WaterNav(void); - void HearSound(int n, vec *o); - - // Waypoint functions - bool HeadToWaypoint(void); - bool FindWaypoint(void); - void ResetWaypointVars(void); - void SetCurrentWaypoint(node_s *pWP); - void SetCurrentWaypoint(waypoint_s *pWP); - void SetCurrentGoalWaypoint(node_s *pNode); - void SetCurrentGoalWaypoint(waypoint_s *pWP); - bool CurrentWPIsValid(void); - bool ReachedGoalWP(void); - bool HeadToGoal(void); - waypoint_s *GetWPFromNode(node_s *pNode); - waypoint_s *GetNearestWaypoint(vec v_src, float flRange); - waypoint_s *GetNearestWaypoint(float flRange) { return GetNearestWaypoint(m_pMyEnt->o, flRange); }; - waypoint_s *GetNearestTriggerWaypoint(vec v_src, float flRange); -#ifdef WP_FLOOD - waypoint_s *GetNearestFloodWP(vec v_origin, float flRange); - waypoint_s *GetNearestFloodWP(float flRange) { return GetNearestFloodWP(m_pMyEnt->o, flRange); }; - waypoint_s *GetNearestTriggerFloodWP(vec v_origin, float flRange); -#endif - void SyncWaypoints(void); - - friend class CBotManager; - friend class CWaypointClass; - - bool m_bSendC2SInit; - - virtual ~CBot(void); - - virtual void Spawn(void); - virtual void Think(void); - void GoToDebugGoal(vec o); -}; - -class CStoredBot; - -extern dvector bots; - -class CBotManager -{ - char m_szBotNames[150][16]; // Max 150 bot names with a length of 16 characters - short m_sBotNameCount; - char m_szBotTeams[20][5]; // Max 100 bot teams co a length of 5 characters - short m_sBotTeamCount; - bool m_bInit; - bool m_bBotsShoot; - bool m_bIdleBots; - bot_skill_s m_BotSkills[5]; // 5 different bot skills, 0 = best 4 = worst - int m_iFrameTime; - int m_iPrevTime; - short m_sBotSkill; // Bad - Worse - Medium - Good - Best - short m_sMaxAStarBots; // Max bots that can use a* at the same time - short m_sUsingAStarBotsCount; // Number of bots that are using a* - short m_sCurrentTriggerNr; // Current waypoint trigger bots should use - TLinkedList m_StoredBots; // List of bots that should be re-added after map change - float m_fReAddBotDelay; - - void LoadBotNamesFile(void); - char *GetBotName(void); - void LoadBotTeamsFile(void); - char *GetBotTeam(void); - void CreateSkillData(void); - void InitSkillData(void); - void InitBotItems(void); - - friend class CBot; - friend class CCubeBot; - friend class CACBot; - friend class CWaypointClass; - -public: - dynent *m_pBotToView; - - // Construction - CBotManager(void) { m_bInit = true; m_fReAddBotDelay = -1.0f; }; - - // Destruction - ~CBotManager(void); - - void Init(void); - void Think(void); - void RenderBots(void); - void RenderBotScore(void) { loopv(bots) if(bots[i]) renderscore(bots[i]); } - void AddBotTeamScore(void) { loopv(bots) addteamscore(bots[i]); }; - void RespawnBots(void) { loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->Spawn(); }; - void EndMap(void); - void BeginMap(char *szMapName); - int GetBotIndex(dynent *m); - void LetBotsUpdateStats(void); - void LetBotsHear(int n, vec *loc); - void AddWaypoint(node_s *pNode); - void DelWaypoint(node_s *pNode); - bool BotsShoot(void) { return m_bBotsShoot; }; - bool IdleBots(void) { return m_bIdleBots; }; - void SetBotsShoot(bool bShoot) { m_bBotsShoot = bShoot; }; - void SetIdleBots(bool bIdle) { m_bIdleBots = bIdle; }; - void EnableBotView(dynent *bot) { m_pBotToView = bot; }; - void DisableBotView(void); - void ChangeBotSkill(short Skill, dynent *bot = NULL); - dynent *CreateBot(const char *team, const char *skill, const char *name); - void ViewBot(void); - void CalculateMaxAStarCount(void); - void PickNextTrigger(void); - - void MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, - char *szOutput); -}; - -#if defined VANILLA_CUBE -#include "cube_bot.h" -#elif defined AC_CUBE -#include "ac_bot.h" -#endif - -extern CBotManager BotManager; -extern const vec g_vecZero; - -#if defined AC_CUBE -extern CACWaypointClass WaypointClass; -#elif defined VANILLA_CUBE -extern CCubeWaypointClass WaypointClass; -#endif - -class CStoredBot // Used to store bots after mapchange, so that they can be readded -{ - char m_szName[32]; - char m_szTeam[32]; - short m_sSkillNr; - -public: - - CStoredBot(const char *name, const char *team, const short skill) : m_sSkillNr(skill) - { strcpy(m_szName, name); strcpy(m_szTeam, team); }; - void ReAddBot(void) { BotManager.CreateBot(m_szTeam, SkillNrToSkillName(m_sSkillNr), m_szName); }; -}; - -#endif +// +// C++ Interface: bot +// +// Description: +// +// Main bot header +// +// +// Author: Rick +// +// +// + + +/* + + TODO list + + -bots that "cover" each other in team matches ? + -If a bot gets ammo it should ignore that kind of ammo for the next 10 seconds. ? + -bots should ignore players that are too far away ? + -bots should dodge/jump when shot at + -If the bot's only weapon is the fist it should make looking for ammo its priority - Done ? + -bots should rocket jump to get quads and the 150 armor if normal jumping doesn't + work (only if health is x so they don't suicide). + -Make all the "UNDONE's" done :) + -Finish waypoint navigation + -Make bot personalities + -More advanced enemy scoring + -Bots should less bump into walls + -(Better) Coop support + -Check for reachable instead of visible in GetNearestWaypoint + -Waypoint experience file + -Bots should avoid team mates + -Handle failed goals better(ie remember them) + -Test multiplayer + -Fix bots getting stuck in each other + -Optimize hunting code + -Check/Fix msg code +*/ + +#ifndef BOT_H +#define BOT_H + + +//#define RELEASE_BUILD // Set when you want to make a release build + + +// Set for which mod the code is + +//#define VANILLA_CUBE +#define AC_CUBE +//#define CUBE_X + +#include "bot_util.h" +#include "bot_waypoint.h" + +#ifdef __linux__ +#include +#else // Windows doesn't have syslog +#define openlog (void)0 +#define syslog (void)0 +#define closelog (void)0 +#endif + +extern bool dedserv; +extern itemstat itemstats[]; + +#ifdef RELEASE_BUILD +inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) {}; +inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) {}; +inline void debugbeam(vec &s, vec &e) { }; +#else +inline void condebug(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; +inline void debugnav(const char *s, int a = 0, int b = 0, int c = 0) { /*conoutf(s, a, b, c);*/ }; +inline void debugbeam(vec &s, vec &e) { /*if (!dedserv) particle_trail(1, 500, s, e);*/ }; +#endif + +#define JUMP_HEIGHT 4.0f // NOT accurate + +#define FORWARD (1<<1) +#define BACKWARD (1<<2) +#define LEFT (1<<3) +#define RIGHT (1<<4) +#define UP (1<<5) +#define DOWN (1<<6) +#define DIR_NONE 0 + +//fixmebot +#define m_sp 1000 +#define m_classicsp 1000 + +enum EBotCommands // For voting of bot commands +{ + COMMAND_ADDBOT=0, + COMMAND_KICKBOT, + COMMAND_BOTSKILL +}; + +struct bot_skill_s +{ + float flMinReactionDelay; // Minimal reaction time + float flMaxReactionDelay; // Maximal reaction time + float flMinAimXSpeed; // Minimal X aim speed in degrees(base value) + float flMaxAimXSpeed; // Maximal X aim speed in degrees(base value) + float flMinAimYSpeed; // Minimal Y aim speed in degrees(base value) + float flMaxAimYSpeed; // Maximal Y aim speed in degrees(base value) + float flMinAimXOffset; // Minimal X aim offset in degrees(base value) + float flMaxAimXOffset; // Maximal X aim offset in degrees(base value) + float flMinAimYOffset; // Minimal Y aim offset in degrees(base value) + float flMaxAimYOffset; // Maximal Y aim offset in degrees(base value) + float flMinAttackDelay; // Minimal delay for when a bot can attack again + float flMaxAttackDelay; // Maximal delay for when a bot can attack again + float flMinEnemySearchDelay; // Minimal delay for when a bot can search for an + // enemy again + float flMaxEnemySearchDelay; // Maximal delay for when a bot can search for an + // enemy again + short sShootAtFeetWithRLPercent; // Percent that a bot shoot with a rocket + // launcher at the enemy feet. + int iMaxHearVolume; // Max volume that bot can hear + bool bCanPredict; // Can this bot predict his enemy position? + bool bCircleStrafe; // Can this bot circle strafe? + bool bCanSearchItemsInCombat; +}; + +enum EBotWeaponTypes +{ + TYPE_MELEE, // Fist, knife etc + TYPE_NORMAL, // Normal weapon, distance doesn't really matter(ie pistol) + TYPE_SHOTGUN, + TYPE_ROCKET, + TYPE_SNIPER, // Rifle, sniper etc + TYPE_GRENADE, + TYPE_AUTO // Chain gun, machine gun etc +}; + +struct weaponinfo_s +{ + EBotWeaponTypes eWeaponType; + float flMinDesiredDistance; + float flMaxDesiredDistance; + float flMinFireDistance; + float flMaxFireDistance; + short sMinDesiredAmmo; +}; + +enum ECurrentBotState +{ + STATE_ENEMY, // Bot has an enemy + STATE_HUNT, // Bot is hunting an enemy + STATE_ENT, // Bot is heading to an entity + STATE_SP, // Bot is doing sp specific stuff + STATE_NORMAL // Bot is doing normal navigation +}; + +struct unreachable_ent_s +{ + entity *ent; + int time; + + unreachable_ent_s(entity *e, int t) : ent(e), time(t) { }; +}; + +class CBot +{ +public: + dynent *m_pMyEnt; + int m_iLastBotUpdate; + + // Combat variabels + dynent *m_pPrevEnemy; + int m_iShootDelay; + int m_iChangeWeaponDelay; + int m_iCombatNavTime; + int m_iEnemySearchDelay; + int m_iSawEnemyTime; + bool m_bCombatJump; + float m_iCombatJumpDelay; + bool m_bShootAtFeet; + + // Hunting variabeles + int m_iHuntDelay; + vec m_vHuntLocation, m_vPrevHuntLocation; + dynent *m_pHuntTarget; + float m_fPrevHuntDist; + int m_iHuntPauseTime, m_iHuntPlayerUpdateTime, m_iHuntLastTurnLessTime; + int m_iLookAroundDelay, m_iLookAroundTime, m_iLookAroundUpdateTime; + float m_fLookAroundYaw; + bool m_bLookLeft; + + // Navigation variabeles + int m_iCheckEnvDelay; + int m_iLastJumpPad; + vec m_vPrevOrigin; + float m_iStuckCheckDelay; + bool m_bStuck; + int m_iStuckTime; + int m_iStrafeTime; + int m_iStrafeCheckDelay; + int m_iMoveDir; + int m_iSPMoveTime; + bool m_bCalculatingAStarPath; + TLinkedList m_AStarNodeList; + TPriorList m_AStarOpenList[2]; + TLinkedList m_AStarClosedList[2]; + vec m_vWaterGoal; // Place to go to when underwater + + // Waypoint variabeles + TLinkedList m_WaypointList[MAX_MAP_GRIDS][MAX_MAP_GRIDS]; + float m_iWaypointTime; + waypoint_s *m_pCurrentWaypoint; + waypoint_s *m_pCurrentGoalWaypoint; + vec m_vGoal; + waypoint_s *m_pPrevWaypoints[5]; + int m_iRandomWaypointTime; + float m_fPrevWaypointDistance; + int m_iLookForWaypointTime; + int m_iWaypointHeadLastTurnLessTime; // Last time the didn't had to turn much wile + // heading to a WP + int m_iWaypointHeadPauseTime; // Pause time to give the bot time to aim. + bool m_bGoToDebugGoal; + + // Misc stuff + ECurrentBotState m_eCurrentBotState; + entity *m_pTargetEnt; + TLinkedList m_UnreachableEnts; + int m_iCheckTeleporterDelay; + int m_iCheckJumppadsDelay; + int m_iCheckEntsDelay; + int m_iCheckTriggersDelay; + int m_iAimDelay; + float m_fYawToTurn, m_fPitchToTurn; + bot_skill_s *m_pBotSkill; // Pointer to current bot skill + short m_sSkillNr; // Skill number, 0 == best 4 == worst + + void AimToVec(const vec &o); + void AimToIdeal(void); + float ChangeAngle(float speed, float ideal, float current); + bool SelectGun(int Gun); + virtual void CheckItemPickup(void) = 0; + virtual void PickUp(int n) = 0; + virtual void AddItem(int i, int &v, int spawnsec) = 0; + virtual void BotPain(int damage, dynent *d) = 0; + void SendBotInfo(void); + float GetDistance(const vec &o); + float GetDistance(const vec &v1, const vec &v2); + float GetDistance(entity *e); + float Get2DDistance(const vec &v) { return ::Get2DDistance(m_pMyEnt->o, v); }; + vec GetViewAngles(void) { vec temp = { m_pMyEnt->pitch, m_pMyEnt->yaw, + m_pMyEnt->roll }; return temp; }; + void ResetMoveSpeed(void) { m_pMyEnt->move = m_pMyEnt->strafe = 0; }; + void SetMoveDir(int iMoveDir, bool add); + void ResetCurrentTask(); + + // AI Functions + bool FindEnemy(void); + void ShootEnemy(void); + bool CheckHunt(void); + bool HuntEnemy(void); + void DoCombatNav(void); + void MainAI(void); + bool CheckStuck(void); + bool CheckJump(void); + bool CheckStrafe(void); + void CheckFOV(void); + bool IsVisible(const vec &o, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, o, + (CheckPlayers) ? m_pMyEnt : + NULL); }; + bool IsVisible(dynent *d, bool CheckPlayers = false) { return ::IsVisible(m_pMyEnt->o, d->o, + (CheckPlayers) ? + m_pMyEnt : NULL); }; + bool IsVisible(entity *e, bool CheckPlayers = false); + bool IsVisible(vec o, int Dir, float flDist, bool CheckPlayers, float *pEndDist = NULL); + bool IsVisible(int Dir, float flDist, bool CheckPlayers) + { return IsVisible(m_pMyEnt->o, Dir, flDist, CheckPlayers); }; + bool IsInFOV(const vec &o); + bool IsInFOV(dynent *d) { return IsInFOV(d->o); }; + int GetShootDelay(void); + virtual bool ChoosePreferredWeapon(void); + virtual entity *SearchForEnts(bool bUseWPs, float flRange=9999.0f, + float flMaxHeight=JUMP_HEIGHT) = NULL; + virtual bool HeadToTargetEnt(void) = 0; + bool CheckItems(void); + bool InUnreachableList(entity *e); + virtual bool DoSPStuff(void) = 0; + vec GetEnemyPos(dynent *d); + bool AStar(void); + float AStarCost(waypoint_s *pWP1, waypoint_s *pWP2); + void CleanAStarLists(bool bPathFailed); + bool IsReachable(vec to, float flMaxHeight=JUMP_HEIGHT); + bool WaterNav(void); + void HearSound(int n, vec *o); + + // Waypoint functions + bool HeadToWaypoint(void); + bool FindWaypoint(void); + void ResetWaypointVars(void); + void SetCurrentWaypoint(node_s *pWP); + void SetCurrentWaypoint(waypoint_s *pWP); + void SetCurrentGoalWaypoint(node_s *pNode); + void SetCurrentGoalWaypoint(waypoint_s *pWP); + bool CurrentWPIsValid(void); + bool ReachedGoalWP(void); + bool HeadToGoal(void); + waypoint_s *GetWPFromNode(node_s *pNode); + waypoint_s *GetNearestWaypoint(vec v_src, float flRange); + waypoint_s *GetNearestWaypoint(float flRange) { return GetNearestWaypoint(m_pMyEnt->o, flRange); }; + waypoint_s *GetNearestTriggerWaypoint(vec v_src, float flRange); +#ifdef WP_FLOOD + waypoint_s *GetNearestFloodWP(vec v_origin, float flRange); + waypoint_s *GetNearestFloodWP(float flRange) { return GetNearestFloodWP(m_pMyEnt->o, flRange); }; + waypoint_s *GetNearestTriggerFloodWP(vec v_origin, float flRange); +#endif + void SyncWaypoints(void); + + friend class CBotManager; + friend class CWaypointClass; + + bool m_bSendC2SInit; + + virtual ~CBot(void); + + virtual void Spawn(void); + virtual void Think(void); + void GoToDebugGoal(vec o); +}; + +class CStoredBot; + +extern dvector bots; + +class CBotManager +{ + char m_szBotNames[150][16]; // Max 150 bot names with a length of 16 characters + short m_sBotNameCount; + char m_szBotTeams[20][5]; // Max 100 bot teams co a length of 5 characters + short m_sBotTeamCount; + bool m_bInit; + bool m_bBotsShoot; + bool m_bIdleBots; + bot_skill_s m_BotSkills[5]; // 5 different bot skills, 0 = best 4 = worst + int m_iFrameTime; + int m_iPrevTime; + short m_sBotSkill; // Bad - Worse - Medium - Good - Best + short m_sMaxAStarBots; // Max bots that can use a* at the same time + short m_sUsingAStarBotsCount; // Number of bots that are using a* + short m_sCurrentTriggerNr; // Current waypoint trigger bots should use + TLinkedList m_StoredBots; // List of bots that should be re-added after map change + float m_fReAddBotDelay; + + void LoadBotNamesFile(void); + char *GetBotName(void); + void LoadBotTeamsFile(void); + char *GetBotTeam(void); + void CreateSkillData(void); + void InitSkillData(void); + void InitBotItems(void); + + friend class CBot; + friend class CCubeBot; + friend class CACBot; + friend class CWaypointClass; + +public: + dynent *m_pBotToView; + + // Construction + CBotManager(void) { m_bInit = true; m_fReAddBotDelay = -1.0f; }; + + // Destruction + ~CBotManager(void); + + void Init(void); + void Think(void); + void RenderBots(void); + void RenderBotScore(void) { loopv(bots) if(bots[i]) renderscore(bots[i]); } + void AddBotTeamScore(void) { loopv(bots) addteamscore(bots[i]); }; + void RespawnBots(void) { loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->Spawn(); }; + void EndMap(void); + void BeginMap(char *szMapName); + int GetBotIndex(dynent *m); + void LetBotsUpdateStats(void); + void LetBotsHear(int n, vec *loc); + void AddWaypoint(node_s *pNode); + void DelWaypoint(node_s *pNode); + bool BotsShoot(void) { return m_bBotsShoot; }; + bool IdleBots(void) { return m_bIdleBots; }; + void SetBotsShoot(bool bShoot) { m_bBotsShoot = bShoot; }; + void SetIdleBots(bool bIdle) { m_bIdleBots = bIdle; }; + void EnableBotView(dynent *bot) { m_pBotToView = bot; }; + void DisableBotView(void); + void ChangeBotSkill(short Skill, dynent *bot = NULL); + dynent *CreateBot(const char *team, const char *skill, const char *name); + void ViewBot(void); + void CalculateMaxAStarCount(void); + void PickNextTrigger(void); + + void MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, + char *szOutput); +}; + +#if defined VANILLA_CUBE +#include "cube_bot.h" +#elif defined AC_CUBE +#include "ac_bot.h" +#endif + +extern CBotManager BotManager; +extern const vec g_vecZero; + +#if defined AC_CUBE +extern CACWaypointClass WaypointClass; +#elif defined VANILLA_CUBE +extern CCubeWaypointClass WaypointClass; +#endif + +class CStoredBot // Used to store bots after mapchange, so that they can be readded +{ + char m_szName[32]; + char m_szTeam[32]; + short m_sSkillNr; + +public: + + CStoredBot(const char *name, const char *team, const short skill) : m_sSkillNr(skill) + { strcpy(m_szName, name); strcpy(m_szTeam, team); }; + void ReAddBot(void) { BotManager.CreateBot(m_szTeam, SkillNrToSkillName(m_sSkillNr), m_szName); }; +}; + +#endif diff --git a/source/src/bot/bot_ai.cpp b/source/src/bot/bot_ai.cpp index c3fadb31..551990a5 100644 --- a/source/src/bot/bot_ai.cpp +++ b/source/src/bot/bot_ai.cpp @@ -1,2295 +1,2295 @@ -// -// C++ Implementation: bot_ai -// -// Description: The AI part comes here(navigation, shooting etc) -// -// -// Author: -// - - -// Code of CBot - Start - -#include "../cube.h" - -extern vector sents; -extern vector monsters; - -extern weaponinfo_s WeaponInfoTable[MAX_WEAPONS]; - -vec CBot::GetEnemyPos(dynent *d) -{ - // Aim offset idea by botman - vec o = d->o, offset; - float flDist = GetDistance(d->o), flScale; - - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_ROCKET) - { - // Bot is using a rocket launcher, aim at enemy feet? - if (m_bShootAtFeet && !OUTBORD(d->o.x, d->o.y)) - { - // Only do this when enemy is fairly close to the ground - vec end = o; - end.z -= 900.0f; - traceresult_s tr; - TraceLine(o, end, NULL, false, &tr); - if ((o.z - tr.end.z) < 8.0f) - { - end = o; - end.z = tr.end.z; - if (IsVisible(end)) - { - // Target at ground - o.z = tr.end.z; - } - } - } - - if (m_pBotSkill->bCanPredict) - { - // How higher the skill, how further the bot predicts - vec org = o; - float flPredictTime = RandomFloat(1.25f, 1.7f) / (m_sSkillNr+1); - o = PredictPos(o, d->vel, flPredictTime); - } - } - else - { - if (m_pBotSkill->bCanPredict) - { - // How higher the skill, how 'more' the bot predicts - vec org = o; - float flPredictTime = RandomFloat(0.8f, 1.2f) / (m_sSkillNr+1); - o = PredictPos(o, d->vel, flPredictTime); - } - } - - if (flDist > 60.0f) - flScale = 1.0; - else if (flDist > 6.0f) - flScale = flDist / 60.0; - else - flScale = 0.1; - - switch (m_sSkillNr) - { - case 0: - // no offset - offset.x = 0; - offset.y = 0; - offset.z = 0; - break; - case 1: - // GOOD, offset a little for x, y, and z - offset.x = RandomFloat(-3, 3) * flScale; - offset.y = RandomFloat(-3, 3) * flScale; - offset.z = RandomFloat(-6, 6) * flScale; - break; - case 2: - // FAIR, offset somewhat for x, y, and z - offset.x = RandomFloat(-8, 8) * flScale; - offset.y = RandomFloat(-8, 8) * flScale; - offset.z = RandomFloat(-12, 12) * flScale; - break; - case 3: - // POOR, offset for x, y, and z - offset.x = RandomFloat(-15, 15) * flScale; - offset.y = RandomFloat(-15, 15) * flScale; - offset.z = RandomFloat(-25, 25) * flScale; - break; - case 4: - // BAD, offset lots for x, y, and z - offset.x = RandomFloat(-20, 20) * flScale; - offset.y = RandomFloat(-20, 20) * flScale; - offset.z = RandomFloat(-35, 35) * flScale; - break; - } - - vadd(o, offset); - return o; -} - -bool CBot::FindEnemy(void) -{ - // UNDONE: Enemies are now only scored on their distance - - if (m_pMyEnt->enemy) // Bot already has an enemy - { - // Check if the enemy is still in game - bool found = IsInGame(m_pMyEnt->enemy); - - // Check if the enemy is still ingame, still alive, not joined my team and is visible - if (found && !isteam(m_pMyEnt->team, m_pMyEnt->enemy->team)) - { - if ((m_pMyEnt->enemy->state == CS_ALIVE) && (IsVisible(m_pMyEnt->enemy))) - return true; - else - m_pPrevEnemy = m_pMyEnt->enemy; - } - else - m_pMyEnt->enemy = NULL; - } - - if (m_iEnemySearchDelay > lastmillis) return (m_pMyEnt->enemy!=NULL); - - m_pMyEnt->enemy = NULL; - - // Add enemy searchy delay - float MinDelay = m_pBotSkill->flMinEnemySearchDelay; - float MaxDelay = m_pBotSkill->flMaxEnemySearchDelay; - m_iEnemySearchDelay = lastmillis + int(RandomFloat(MinDelay, MaxDelay) * 1000.0f); - - dynent *pNewEnemy = NULL, *d = NULL; - float flDist, flNearestDist = 99999.9f; - short EnemyVal, BestEnemyVal = -100; - - // search for only for monsters when SP mode - /* no more sp as such -argh (sorry bout messy commenting) - if (m_sp) - { - loopv(monsters) - { - d = monsters[i]; // Handy shortcut - - if (d->state != CS_ALIVE) - continue; - - // Check if the enemy is visible - if(!IsInFOV(d) || !IsVisible(d)) - continue; - - flDist = GetDistance(d->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - } - } - } */ - - //else - //{ - - // First loop through all players - loopv(players) - { - d = players[i]; // Handy shortcut - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - // Check if the enemy is visible - if(!IsInFOV(d) || !IsVisible(d)) - continue; - - flDist = GetDistance(d->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - } - } - - // Then loop through all bots - loopv(bots) - { - d = bots[i]; // Handy shortcut - - if (d == m_pMyEnt) - continue; - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - // Check if the enemy is visible - if(!IsInFOV(d) || !IsVisible(d)) - continue; - - flDist = GetDistance(d->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - } - } - - // Then examine the local player - if (player1 && !isteam(player1->team, m_pMyEnt->team) && - (player1->state == CS_ALIVE)) - { - // Check if the enemy is visible - if(IsInFOV(player1) && IsVisible(player1)) - { - flDist = GetDistance(player1->o); - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (EnemyVal > BestEnemyVal) - { - pNewEnemy = player1; - BestEnemyVal = EnemyVal; - } - } - } - //} - - if (pNewEnemy) - { - if (!m_pMyEnt->enemy) // Add shoot delay if new enemy is found - { - float flMinShootDelay = m_pBotSkill->flMinAttackDelay; - float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; - - m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, - flMaxShootDelay) * 1000.0f); - } - - if ((m_pMyEnt->enemy != pNewEnemy) && m_pMyEnt->enemy) - m_pPrevEnemy = m_pMyEnt->enemy; - - m_pMyEnt->enemy = pNewEnemy; - return true; - } - - return false; -} - -bool CBot::CheckHunt(void) -{ - if (!BotManager.BotsShoot()) return false; - - if (m_pHuntTarget) // Bot already has an enemy to hunt - { - // Check if the enemy is still in game - bool found = IsInGame(m_pHuntTarget); - - // Check if the enemy is still ingame, still alive, not joined my team and is visible - if (found && !isteam(m_pMyEnt->team, m_pHuntTarget->team)) - { - if ((m_pHuntTarget->state == CS_ALIVE) && IsReachable(m_vHuntLocation)) - return true; - } - else - m_pHuntTarget = NULL; - } - - if (m_iHuntDelay > lastmillis) return (m_pHuntTarget!=NULL); - - if (!vis(m_vHuntLocation, g_vecZero)) - m_vPrevHuntLocation = m_vHuntLocation; - - m_pHuntTarget = NULL; - m_vHuntLocation = g_vecZero; - - // Add enemy hunt search delay - m_iHuntDelay = lastmillis + 1500; - - dynent *pNewEnemy = NULL, *d = NULL; - float flDist, flNearestDist = 99999.9f, flNearestOldPosDistToEnemy = 99999.9f; - float flNearestOldPosDistToBot = 99999.9f; - short EnemyVal, BestEnemyVal = -100; - vec BestOldPos; - - // search for only for monsters when SP mode - //edit -argh - /*if (m_sp) - { - loopv(monsters) - { - d = monsters[i]; // Handy shortcut - - if (d->state != CS_ALIVE) - continue; - - flDist = GetDistance(d->o); - - if (flDist > 250.0f) continue; - - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal < BestEnemyVal) continue; - - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToBot) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - } - } */ - //else - //{ - // First loop through all players - loopv(players) - { - d = players[i]; // Handy shortcut - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - flDist = GetDistance(d->o); - - if (flDist > 250.0f) continue; - - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal < BestEnemyVal) continue; - - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - //} - - // Then loop through all bots - loopv(bots) - { - d = bots[i]; // Handy shortcut - - if (d == m_pMyEnt) - continue; - - if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) - continue; - - flDist = GetDistance(d->o); - - if (flDist > 250.0f) continue; - - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal < BestEnemyVal) continue; - - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - } - - // Then examine the local player - if (player1 && !isteam(player1->team, m_pMyEnt->team) && - (player1->state == CS_ALIVE) && ((flDist = GetDistance(player1->o)) <= 250.0f)) - { - d = player1; - EnemyVal = 1; - - if (flDist < flNearestDist) - { - EnemyVal+=2; - flNearestDist = flDist; - } - - if (d == m_pPrevEnemy) - EnemyVal+=2; - - if (EnemyVal >= BestEnemyVal) - { - BestEnemyVal = EnemyVal; - vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; - flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; - - // Check previous locations of enemy - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = d->PrevLocations.prevloc[j]; - - flDist = GetDistance(d->o, v); - - if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) - { - flNearestOldPosDistToEnemy = flDist; - bestfromenemy = v; - } - } - - // Check previous locations of bot hisself - for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; - if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; - - vec v = m_pMyEnt->PrevLocations.prevloc[j]; - - flDist = GetDistance(v); - - if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && - IsReachable(v)) - { - flNearestOldPosDistToBot = flDist; - bestfrombot = v; - } - } - - if (!vis(bestfromenemy, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfromenemy; - } - else if (!vis(bestfrombot, g_vecZero)) - { - pNewEnemy = d; - BestEnemyVal = EnemyVal; - BestOldPos = bestfrombot; - } - } - } - } - - if (pNewEnemy) - { - if (!m_pHuntTarget) // Add shoot delay if new enemy is found - { - float flMinShootDelay = m_pBotSkill->flMinAttackDelay; - float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; - - m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, - flMaxShootDelay) * 1000.0f); - } - - if (!vis(m_vHuntLocation, g_vecZero)) - m_vPrevHuntLocation = m_vHuntLocation; - - m_pHuntTarget = pNewEnemy; - m_vHuntLocation = BestOldPos; - m_fPrevHuntDist = 0.0f; - return true; - } - - return false; -} - -bool CBot::HuntEnemy(void) -{ - if (m_iCombatNavTime > lastmillis) - { - SetMoveDir(m_iMoveDir, false); - return true; - } - - m_iCombatNavTime = m_iMoveDir = 0; - - bool bDone = false, bNew = false; - float flDist = GetDistance(m_vHuntLocation); - - if (flDist <= 3.0f) - bDone = true; - - if ((m_fPrevHuntDist > 0.0) && (flDist > m_fPrevHuntDist)) - bDone = true; - - m_fPrevHuntDist = flDist; - - if ((m_iHuntPlayerUpdateTime < lastmillis) || bDone) - { - m_iHuntPlayerUpdateTime = lastmillis + 1250; - - short BestPosIndexFromEnemy = -1, BestPosIndexFromBot = -1, j; - float NearestDistToEnemy = 9999.9f, NearestDistToBot = 9999.9f; - - // Check previous locations of enemy - for (j=0;jPrevLocations.prevloc[j]; - - if (bDone && vis(m_vHuntLocation, OldPos)) - continue; - - if (vis(OldPos, g_vecZero)) - continue; - - if (GetDistance(OldPos) > 250.0f) - continue; - - flDist = GetDistance(m_pHuntTarget->o, OldPos); - - if ((flDist < NearestDistToEnemy) && (IsReachable(OldPos))) - { - NearestDistToEnemy = flDist; - BestPosIndexFromEnemy = j; - break; - } - } - - // Check previous locations of bot - for (j=0;jPrevLocations.prevloc[j]; - - if (bDone && vis(m_vHuntLocation, OldPos)) - continue; - - if (vis(OldPos, g_vecZero)) - continue; - - if (GetDistance(OldPos) > 25.0f) - continue; - - flDist = GetDistance(OldPos); - - if ((flDist < NearestDistToBot) && ::IsVisible(m_pHuntTarget->o, OldPos) && - (IsReachable(OldPos))) - { - NearestDistToBot = flDist; - BestPosIndexFromBot = j; - break; - } - } - - if (BestPosIndexFromEnemy > -1) - { - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = m_pHuntTarget->PrevLocations.prevloc[BestPosIndexFromEnemy]; - bNew = true; - m_fPrevHuntDist = 0.0f; - } - else if (BestPosIndexFromBot > -1) - { - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = m_pMyEnt->PrevLocations.prevloc[BestPosIndexFromBot]; - bNew = true; - m_fPrevHuntDist = 0.0f; - } - } - - if (!bNew) // Check if current location is still reachable - { - if (bDone || !IsReachable(m_vHuntLocation)) - { - m_pHuntTarget = NULL; - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = g_vecZero; - m_fPrevHuntDist = 0.0f; - m_iHuntDelay = lastmillis + 3500; - return false; - } - } - else - condebug("New hunt pos"); - - // Aim to position - //AimToVec(m_vHuntLocation); - - int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, m_vHuntLocation); - - if (iMoveDir != DIR_NONE) - { - m_iMoveDir = iMoveDir; - m_iCombatNavTime = lastmillis + 125; - } - - bool aimtopos = true; - - if ((lastmillis - m_iSawEnemyTime) > 1500) - { - if (m_iLookAroundDelay < lastmillis) - { - if (m_iLookAroundTime > lastmillis) - { - if (m_iLookAroundUpdateTime < lastmillis) - { - float flAddAngle; - if (m_bLookLeft) flAddAngle = RandomFloat(-110, -80); - else flAddAngle = RandomFloat(80, 110); - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->targetyaw + flAddAngle); - m_iLookAroundUpdateTime = lastmillis + RandomLong(400, 800); - } - aimtopos = false; - } - else if (m_iLookAroundTime > 0) - { - m_iLookAroundTime = 0; - m_iLookAroundDelay = lastmillis + RandomLong(750, 1000); - } - else - m_iLookAroundTime = lastmillis + RandomLong(2200, 3200); - } - } - - if (aimtopos) - AimToVec(m_vHuntLocation); - - debugbeam(m_pMyEnt->o, m_vHuntLocation); - - if (m_fYawToTurn <= 25.0f) - m_iHuntLastTurnLessTime = lastmillis; - - // Bot had to turn much for a while? - if ((m_iHuntLastTurnLessTime > 0) && (m_iHuntLastTurnLessTime < (lastmillis - 1000))) - { - m_iHuntPauseTime = lastmillis + 200; - } - - if (m_iHuntPauseTime >= lastmillis) - { - m_pMyEnt->move = 0; - m_fPrevHuntDist = 0.0f; - } - else - { - // Check if bot has to jump over a wall... - if (CheckJump()) - m_pMyEnt->jumpnext = true; - else // Check if bot has to jump to reach this location - { - float flHeightDiff = m_vHuntLocation.z - m_pMyEnt->o.z; - bool bToHigh = false; - if (Get2DDistance(m_vHuntLocation) <= 2.0f) - { - if (flHeightDiff >= 1.5f) - { - if (flHeightDiff <= JUMP_HEIGHT) - { -#ifndef RELEASE_BUILD - char sz[64]; - sprintf(sz, "OldPos z diff: %f", m_vHuntLocation.z-m_pMyEnt->o.z); - condebug(sz); -#endif - // Jump if close to pos and the pos is high - m_pMyEnt->jumpnext = true; - } - else - bToHigh = true; - } - } - - if (bToHigh) - { - m_pHuntTarget = NULL; - m_vPrevHuntLocation = m_vHuntLocation; - m_vHuntLocation = g_vecZero; - m_fPrevHuntDist = 0.0f; - m_iHuntDelay = lastmillis + 3500; - return false; - } - } - } - - return true; -} - -void CBot::ShootEnemy() -{ - if (!m_pMyEnt->enemy) - return; - - m_iSawEnemyTime = lastmillis; - - // Aim to enemy - vec enemypos = GetEnemyPos(m_pMyEnt->enemy); - AimToVec(enemypos); - - // Time to shoot? - if (m_iShootDelay < lastmillis) - //if ((lastmillis-m_pMyEnt->lastaction) >= m_pMyEnt->gunwait) - { - if (m_pMyEnt->ammo[m_pMyEnt->gunselect]) - { - // If the bot is using a sniper only shoot if crosshair is near the enemy - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_SNIPER) - { - float yawtoturn = fabs(WrapYZAngle(m_pMyEnt->yaw - m_pMyEnt->targetyaw)); - float pitchtoturn = fabs(WrapYZAngle(m_pMyEnt->pitch - m_pMyEnt->targetpitch)); - - if ((yawtoturn > 5) || (pitchtoturn > 15)) // UNDONE: Should be skill based - return; - } - - float flDist = GetDistance(enemypos); - - // Check if bot is in fire range - if ((flDist < WeaponInfoTable[m_pMyEnt->gunselect].flMinFireDistance) || - (flDist > WeaponInfoTable[m_pMyEnt->gunselect].flMaxFireDistance)) - return; - - // Now shoot! - m_pMyEnt->attacking = true; - - // Get the position the bot is aiming at - vec forward, right, up, dest; - traceresult_s tr; - - AnglesToVectors(GetViewAngles(), forward, right, up); - - dest = m_pMyEnt->o; - vmul(forward, 1000); - vadd(dest, forward); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); - debugbeam(m_pMyEnt->o, tr.end); - - // Shoot - shoot(m_pMyEnt, tr.end); - - // Add shoot delay - m_iShootDelay = lastmillis + GetShootDelay(); - } - ChoosePreferredWeapon(); - } -#ifndef RELEASE_BUILD - else - { - char sz[64]; - sprintf(sz, "shootdelay: %d\n", (m_iShootDelay-lastmillis)); - AddDebugText(sz); - } -#endif -} - -bool CBot::ChoosePreferredWeapon() -{ - TMultiChoice WeaponChoices; - short sWeaponScore; - float flDist = GetDistance(m_pMyEnt->enemy->o); - - if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) - { - if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) - return true; - } - - // Choose a weapon - for(int i=0;iammo[i] == 0) continue; - - sWeaponScore = 5; // Minimal score for a weapon - - if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && - (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) - { - // In desired range for this weapon - sWeaponScore += 5; // Increase score much - } - else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || - (flDist > WeaponInfoTable[i].flMaxFireDistance)) - continue; // Wrong distance for this weapon - - // The ideal distance would be between the Min and Max desired distance. - // Score on the difference of the avarage of the Min and Max desired distance. - float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + - WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; - float flIdealDiff = fabs(flDist - flAvarage); - - if (flIdealDiff < 0.5f) // Close to ideal distance - sWeaponScore += 4; - else if (flIdealDiff <= 1.0f) - sWeaponScore += 2; - - // Now rate the weapon on available ammo... - if (WeaponInfoTable[i].sMinDesiredAmmo > 0) - { - // Calculate how much percent of the min desired ammo the bot has - float flDesiredPercent = (float(m_pMyEnt->ammo[i]) / - float(WeaponInfoTable[i].sMinDesiredAmmo)) * - 100.0f; - - if (flDesiredPercent >= 400.0f) - sWeaponScore += 4; - else if (flDesiredPercent >= 200.0f) - sWeaponScore += 3; - else if (flDesiredPercent >= 100.0f) - sWeaponScore += 1; - } - - WeaponChoices.Insert(i, sWeaponScore); - } - - int WeaponSelect; - if (WeaponChoices.GetSelection(WeaponSelect)) - { - m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); - m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && - (RandomLong(1, 100) <= - m_pBotSkill->sShootAtFeetWithRLPercent)); - return SelectGun(WeaponSelect); - } - - return false; -} - -int CBot::GetShootDelay() -{ - // UNDONE - if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) || - (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_AUTO)) - return m_pMyEnt->gunwait; - - float flMinShootDelay = m_pBotSkill->flMinAttackDelay; - float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; - return max(m_pMyEnt->gunwait, int(RandomFloat(flMinShootDelay, flMaxShootDelay) * 1000.0f)); -} - -void CBot::MainAI() -{ - // Default bots will run forward - m_pMyEnt->move = 1; - - // Default bots won't strafe - m_pMyEnt->strafe = 0; - - if (!BotManager.BotsShoot() && m_pMyEnt->enemy) - m_pMyEnt->enemy = NULL; // Clear enemy when bots may not shoot - - if (m_bGoToDebugGoal) // For debugging the waypoint navigation - { - if (!HeadToGoal()) - { - ResetWaypointVars(); - m_vGoal = g_vecZero; - } - else - AddDebugText("Heading to debug goal..."); - } - if (BotManager.BotsShoot() && FindEnemy()) // Combat - { - AddDebugText("has enemy"); - - // Shoot at enemy - ShootEnemy(); - - if (m_eCurrentBotState != STATE_ENEMY) - { - m_vGoal = g_vecZero; - ResetWaypointVars(); - } - - m_eCurrentBotState = STATE_ENEMY; - if (!CheckJump()) - DoCombatNav(); - } - else if (CheckHunt() && HuntEnemy()) - { - AddDebugText("Hunting to %s", m_pHuntTarget->name); - m_eCurrentBotState = STATE_HUNT; - } - // Heading to an interesting entity(ammo, armour etc) - else if (CheckItems()) - { - AddDebugText("has ent"); - m_eCurrentBotState = STATE_ENT; - } - else if (m_classicsp && DoSPStuff()) // Home to goal, find/follow friends etc. - { - AddDebugText("SP stuff"); - m_eCurrentBotState = STATE_SP; - } - else // Normal navigation - { - if (m_eCurrentBotState != STATE_NORMAL) - { - m_vGoal = g_vecZero; - ResetWaypointVars(); - } - - m_eCurrentBotState = STATE_NORMAL; - bool bDoNormalNav = true; - - AddDebugText("normal nav"); - - // Make sure the bot looks straight forward and not up or down - m_pMyEnt->pitch = 0; - - // if it is time to look for a waypoint AND if there are waypoints in this - // level... - if (WaypointClass.m_iWaypointCount >= 1) - { - // check if we need to find a waypoint... - if (CurrentWPIsValid() == false) - { - if (m_iLookForWaypointTime <= lastmillis) - { - // find the nearest reachable waypoint - waypoint_s *pWP = GetNearestWaypoint(10.0f); - - if (pWP && (pWP != m_pCurrentWaypoint)) - { - SetCurrentWaypoint(pWP); - condebug("New nav wp"); - bDoNormalNav = !HeadToWaypoint(); - if (bDoNormalNav) - ResetWaypointVars(); - } - else - ResetWaypointVars(); - - m_iLookForWaypointTime = lastmillis + 250; - } - } - else - { - bDoNormalNav = !HeadToWaypoint(); - if (bDoNormalNav) - ResetWaypointVars(); - AddDebugText("Using wps for nav"); - } - } - - // If nothing special, do regular (waypointless) navigation - if(bDoNormalNav) - { - // Is the bot underwater? - if (UnderWater(m_pMyEnt->o) && WaterNav()) - { - // Bot is under water, navigation happens in WaterNav - } - // Time to check the environment? - else if (m_iCheckEnvDelay < lastmillis) - { - if (!vis(m_vWaterGoal, g_vecZero)) m_vWaterGoal = g_vecZero; - - // Check for stuck and strafe - if (UnderWater(m_pMyEnt->o) || !CheckStuck()) - { - // Only do this when the bot is underwater or when the bot isn't stuck - - // Check field of view (FOV) - CheckFOV(); - } - } - - // Check if the bot has to strafe - CheckStrafe(); - - m_pMyEnt->move = 1; - } - } -} - -void CBot::DoCombatNav() -{ - if (m_iCombatNavTime > lastmillis) - { - // If bot has a lower skill and has to turn much, wait - if ((m_sSkillNr > 2) && (m_fYawToTurn > 90.0f)) - { - ResetMoveSpeed(); - } - else - { - SetMoveDir(m_iMoveDir, false); - } - return; - } - - if (m_bCombatJump) - { - m_pMyEnt->jumpnext = true; - m_bCombatJump = false; - m_iCombatJumpDelay = lastmillis + RandomLong(1500, 2800); - return; - } - - m_iMoveDir = DIR_NONE; - - // Check if bot is on top of his enemy - float r = m_pMyEnt->radius+m_pMyEnt->enemy->radius; - if ((fabs(m_pMyEnt->enemy->o.x-m_pMyEnt->o.x)enemy->o.y-m_pMyEnt->o.y)enemy->o.z+m_pMyEnt->enemy->aboveeye) < (m_pMyEnt->o.z + m_pMyEnt->aboveeye))) - { - // Try to get off him! - condebug("On enemy!"); - TMultiChoice AwayDirChoices; - - if (IsVisible(LEFT, 4.0f, false)) - AwayDirChoices.Insert(LEFT); - if (IsVisible(RIGHT, 4.0f, false)) - AwayDirChoices.Insert(RIGHT); - if (IsVisible(FORWARD, 4.0f, false)) - AwayDirChoices.Insert(FORWARD); - if (IsVisible(BACKWARD, 4.0f, false)) - AwayDirChoices.Insert(BACKWARD); - - int iDir; - if (AwayDirChoices.GetSelection(iDir)) - { - m_iMoveDir = iDir; - m_iCombatNavTime = lastmillis + 500; - } - } - - float flDist = GetDistance(m_pMyEnt->enemy->o); - - // Check for nearby items? - if (((m_iCheckEntsDelay < lastmillis) || m_pTargetEnt) && - m_pBotSkill->bCanSearchItemsInCombat) - { - m_iCheckEntsDelay = lastmillis + 125; - bool bSearchItems = false; - - if (m_pTargetEnt) - { - // Bot has already found an entity, still valid? - vec v = { m_pTargetEnt->x, m_pTargetEnt->y, - S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; - if ((GetDistance(v) > 25.0f) || !IsVisible(m_pTargetEnt)) - m_pTargetEnt = NULL; - } - - if (!m_pTargetEnt && (m_iCheckEntsDelay <= lastmillis)) - { - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) - bSearchItems = (flDist >= 8.0f); - else - bSearchItems = (m_pMyEnt->ammo[m_pMyEnt->gunselect] <= - WeaponInfoTable[m_pMyEnt->gunselect].sMinDesiredAmmo); - - if (bSearchItems) - m_pTargetEnt = SearchForEnts(false, 25.0f, 1.0f); - } - - if (m_pTargetEnt) - { - condebug("Combat ent"); - vec v = { m_pTargetEnt->x, m_pTargetEnt->y, - S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; - - debugbeam(m_pMyEnt->o, v); - - float flHeightDiff = v.z - m_pMyEnt->o.z; - bool bToHigh = false; - - // Check he height for this ent - if (Get2DDistance(v) <= 2.0f) - { - if (flHeightDiff >= 1.5f) - { - if (flHeightDiff <= JUMP_HEIGHT) - { -#ifndef RELEASE_BUILD - char sz[64]; - sprintf(sz, "Ent z diff: %f", v.z-m_pMyEnt->o.z); - condebug(sz); -#endif - m_pMyEnt->jumpnext = true; // Jump if close to ent and the ent is high - } - else - bToHigh = true; - } - } - - if (!bToHigh) - { - int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, v); - - if (iMoveDir != DIR_NONE) - { - m_iMoveDir = iMoveDir; - m_iCombatNavTime = lastmillis + RandomLong(125, 250); - } - - // Check if bot needs to jump over something - vec from = m_pMyEnt->o; - from.z -= 1.0f; - if (!IsVisible(from, iMoveDir, 3.0f, false)) - m_pMyEnt->jumpnext = true; - - return; - } - } - } - - // High skill and enemy is close? - if ((m_sSkillNr <= 1) && (m_fYawToTurn < 80.0f) && (flDist <= 20.0f) && - (m_iCombatJumpDelay < lastmillis)) - { - // Randomly jump a bit, to avoid some basic firepower ;) - - // How lower the distance to the enemy, how higher the chance for a jump - short sJumpPercent = (100 - ((short)flDist * 8)); - if (RandomLong(1, 100) <= sJumpPercent) - { - // Choose a nice direction to jump to - - // Is the enemy close? - if ((GetDistance(m_pMyEnt->enemy->o) <= 4.0f) || - (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)) - { - m_iMoveDir = FORWARD; // Jump forward - SetMoveDir(FORWARD, false); - m_bCombatJump = true; - } - else if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE)// else jump to a random direction - { - /* - Directions to choose: - - Forward-right - - Right - - Backward-right - - Backward - - Backward-left - - Left - - Forward-left - - */ - - TMultiChoice JumpDirChoices; - short sForwardScore = ((flDist > 8.0f) || (flDist < 4.0f)) ? 20 : 10; - short sBackwardScore = (flDist <= 6.0f) ? 20 : 10; - short sStrafeScore = (flDist < 6.0f) ? 20 : 10; - - if (IsVisible((FORWARD | LEFT), 4.0f, false)) - JumpDirChoices.Insert((FORWARD | LEFT), sForwardScore); - if (IsVisible((FORWARD | RIGHT), 4.0f, false)) - JumpDirChoices.Insert((FORWARD | RIGHT), sForwardScore); - if (IsVisible(BACKWARD, 4.0f, false)) - JumpDirChoices.Insert(BACKWARD, sBackwardScore); - if (IsVisible((BACKWARD | LEFT), 4.0f, false)) - JumpDirChoices.Insert((BACKWARD | LEFT), sBackwardScore); - if (IsVisible((BACKWARD | RIGHT), 4.0f, false)) - JumpDirChoices.Insert((BACKWARD | RIGHT), sBackwardScore); - if (IsVisible(LEFT, 4.0f, false)) - JumpDirChoices.Insert(LEFT, sStrafeScore); - if (IsVisible(RIGHT, 4.0f, false)) - JumpDirChoices.Insert(RIGHT, sStrafeScore); - - int JumpDir; - if (JumpDirChoices.GetSelection(JumpDir)) - { - m_iMoveDir = JumpDir; - SetMoveDir(JumpDir, false); - m_bCombatJump = true; - } - } - - if (m_bCombatJump) - { - m_iCombatNavTime = lastmillis + RandomLong(125, 250); - return; - } - } - } - - if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) - return; // Simply walk towards enemy if using a melee type - - flDist = Get2DDistance(m_pMyEnt->enemy->o); - - // Out of desired range for current weapon? - if ((flDist <= WeaponInfoTable[m_pMyEnt->gunselect].flMinDesiredDistance) || - (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance)) - { - if (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance) - { - m_iMoveDir = FORWARD; - } - else - { - m_iMoveDir = BACKWARD; - } - - vec src, forward, right, up, dest, MyAngles = GetViewAngles(), o = m_pMyEnt->o; - traceresult_s tr; - - // Is it furthest or farthest? bleh - float flFurthestDist = 0; - int bestdir = -1, dir = 0; - bool moveback = (m_pMyEnt->move == -1); - - for(int j=-45;j<=45;j+=45) - { - src = MyAngles; - src.y = WrapYZAngle(src.y + j); - src.x = 0.0f; - - // If we're moving backwards, trace backwards - if (moveback) - src.y = WrapYZAngle(src.y + 180); - - AnglesToVectors(src, forward, right, up); - - dest = o; - vmul(forward, 40); - vadd(dest, forward); - - TraceLine(o, dest, m_pMyEnt, false, &tr); - - //debugbeam(origin, end); - flDist = GetDistance(tr.end); - - if (flFurthestDist < flDist) - { - flFurthestDist = flDist; - bestdir = dir; - } - dir++; - } - - switch(bestdir) - { - case 0: - if (moveback) - m_iMoveDir |= RIGHT; // Strafe right - else - m_iMoveDir |= LEFT; // Strafe left - break; - case 2: - if (moveback) - m_iMoveDir |= LEFT; // Strafe left - else - m_iMoveDir |= RIGHT; // Strafe right - break; - } - - if (m_iMoveDir != DIR_NONE) - { - SetMoveDir(m_iMoveDir, false); - m_iCombatNavTime = lastmillis + 500; - } - } - else if (m_pBotSkill->bCircleStrafe) // Circle strafe when in desired range... - { - traceresult_s tr; - vec angles, end, forward, right, up; - TMultiChoice StrafeDirChoices; - - // Check the left side... - angles = GetViewAngles(); - angles.y = WrapYZAngle(angles.y - 75.0f); // Not 90 degrees because the bot - // doesn't strafe in a straight line - // (aims still to enemy). - - AnglesToVectors(angles, forward, right, up); - end = m_pMyEnt->o; - vmul(forward, 15.0f); - vadd(end, forward); - - TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); - StrafeDirChoices.Insert(LEFT, (int)GetDistance(m_pMyEnt->o, tr.end)); - - // Check the right side... - angles = GetViewAngles(); - angles.y = WrapYZAngle(angles.y + 75.0f); // Not 90 degrees because the bot - // doesn't strafe in a straight line - // (aims still to enemy). - - AnglesToVectors(angles, forward, right, up); - end = m_pMyEnt->o; - vmul(forward, 15.0f); - vadd(end, forward); - - TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); - StrafeDirChoices.Insert(RIGHT, (int)GetDistance(m_pMyEnt->o, tr.end)); - - int StrafeDir; - if (StrafeDirChoices.GetSelection(StrafeDir)) - { - m_iMoveDir = StrafeDir; - SetMoveDir(StrafeDir, false); - m_iCombatNavTime = lastmillis + RandomLong(1500, 3000); - } - } - else // Bot can't circle strafe(low skill), just stand still - ResetMoveSpeed(); -} - -bool CBot::CheckStuck() -{ - if (m_iStuckCheckDelay >= lastmillis) - return false; - - if ((!vis(m_vGoal, g_vecZero)) && (GetDistance(m_vGoal) < 2.0f)) - return false; - - bool IsStuck = false; - - vec CurPos = m_pMyEnt->o, PrevPos = m_vPrevOrigin; - CurPos.z = PrevPos.z = 0; - // Did the bot hardly move the last frame? - if (GetDistance(CurPos, PrevPos) <= 0.1f) - { - if (m_bStuck) - { - if (m_iStuckTime < lastmillis) - IsStuck = true; - } - else - { - m_bStuck = true; - m_iStuckTime = lastmillis + 1000; - } - } - else - { - m_bStuck = false; - m_iStuckTime = 0; - } - - if (IsStuck) - { -#ifndef RELEASE_BUILD - char msg[64]; - sprintf(msg, "stuck (%f)", GetDistance(m_vPrevOrigin)); - condebug(msg); -#endif - - m_bStuck = false; - m_iStuckTime = 0; - - // Crap bot is stuck, lets just try some random things - - // Check if the bot can turn around - vec src = GetViewAngles(); - src.x = 0; - vec forward, right, up, dir, dest; - traceresult_s tr; - - AnglesToVectors(src, forward, right, up); - - // Check the left side... - dir = right; - dest = m_pMyEnt->o; - vmul(dir, 3); - vsub(dest, dir); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); - //debugbeam(m_pMyEnt->o, end); - - if (!tr.collided) - { - // Bot can turn left, do so - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw - 90); - m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; - return true; - } - - // Check the right side... - dir = right; - dest = m_pMyEnt->o; - vmul(dir, 3); - vadd(dest, dir); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); - //debugbeam(m_pMyEnt->o, end); - - if (!tr.collided) - { - // Bot can turn right, do so - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 90); - m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; - return true; - } - - // Check if bot can turn 180 degrees - dir = forward; - dest = m_pMyEnt->o; - vmul(dir, 3); - vsub(dest, dir); - - TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); - //debugbeam(m_pMyEnt->o, end); - - if (!tr.collided) - { - // Bot can turn around, do so - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); - m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; - return true; - } - - // Bleh bot couldn't turn, lets just randomly jump :| - - condebug("Randomly avoiding stuck..."); - if (RandomLong(0, 2) == 0) - m_pMyEnt->jumpnext = true; - else - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomLong(60, 160)); - return true; - } - - return false; -} - -// Check if a near wall is blocking and we can jump over it -bool CBot::CheckJump() -{ - bool bHasGoal = (!vis(m_vGoal, g_vecZero)); - float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f; - -// if ((bHasGoal) && (flGoalDist < 2.0f)) - // return false; UNDONE? - - vec start = m_pMyEnt->o; - float flTraceDist = 3.0f; - - if (bHasGoal && (flGoalDist < flTraceDist)) - flTraceDist = flGoalDist; - - // Something blocks at eye hight? - if (!IsVisible(start, FORWARD, flTraceDist, false)) - { - // Check if the bot can jump over it - start.z += (JUMP_HEIGHT - 1.0f); - if (IsVisible(start) && !IsVisible(start, FORWARD, flTraceDist, false)) - { - // Jump - debugnav("High wall"); - m_pMyEnt->jumpnext = true; - return true; - } - } - else - { - // Check if something is blocking at feet height, so the bot can jump over it - start.z -= 1.7f; - - // Trace was blocked? - if (!IsVisible(start, FORWARD, flTraceDist, false)) - { - //debugbeam(start, end); - - // Jump - debugnav("Low wall"); - m_pMyEnt->jumpnext = true; - return true; - } - } - - return false; // Bot didn't had to jump(or couldn't) -} - -bool CBot::CheckStrafe() -{ - if (m_iStrafeTime >= lastmillis) - { - SetMoveDir(m_iMoveDir, true); - return true; - } - - if (m_iStrafeCheckDelay >= lastmillis) - return false; - - // Check for near walls - traceresult_s tr; - vec from = m_pMyEnt->o, to, forward, right, up, dir; - float flLeftDist = -1.0f, flRightDist = -1.0f; - bool bStrafe = false; - int iStrafeDir = DIR_NONE; - - AnglesToVectors(GetViewAngles(), forward, right, up); - - // Check for a near left wall - to = from; - dir = right; - vmul(dir, 3.0f); - vsub(to, dir); - TraceLine(from, to, m_pMyEnt, false, &tr); - if (tr.collided) - flLeftDist = GetDistance(from, tr.end); - //debugbeam(m_pMyEnt->o, to); - - // Check for a near right wall - to = from; - dir = right; - vmul(dir, 3.0f); - vadd(to, dir); - TraceLine(from, to, m_pMyEnt, false, &tr); - if (tr.collided) - flRightDist = GetDistance(from, tr.end); - //debugbeam(m_pMyEnt->o, to); - - if ((flLeftDist == -1.0f) && (flRightDist == -1.0f)) - { - dir = right; - vmul(dir, m_pMyEnt->radius); - - // Check left - from = m_pMyEnt->o; - vsub(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) - flLeftDist = -1.0f; - - // Check right - from = m_pMyEnt->o; - vadd(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) - flRightDist = -1.0f; - } - - if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) - { - if (flLeftDist < flRightDist) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist < flLeftDist) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - else - { - // Randomly choose a strafe direction - bStrafe = true; - if (RandomLong(0, 1)) - iStrafeDir = LEFT; - else - iStrafeDir = RIGHT; - } - } - else if (flLeftDist != -1.0f) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist != -1.0f) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - - if (bStrafe) - { - SetMoveDir(iStrafeDir, true); - m_iMoveDir = iStrafeDir; - m_iStrafeTime = lastmillis + RandomLong(75, 150); - } - - return bStrafe; -} - -void CBot::CheckFOV() -{ - m_iCheckEnvDelay = lastmillis + RandomLong(125, 250); - vec MyAngles = GetViewAngles(); - vec src, forward, right, up, dest, best = {0,0,0}; - vec origin = m_pMyEnt->o; - float flDist, flFurthestDist = 0; - bool WallLeft = false; - traceresult_s tr; - - //origin.z -= 1.5; // Slightly under eye level - - // Scan 90 degrees FOV - for(int angle=-45;angle<=45;angle+=5) - { - src = MyAngles; - src.y = WrapYZAngle(src.y + angle); - - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 40); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - //debugbeam(origin, end); - flDist = GetDistance(tr.end); - - if (flFurthestDist < flDist) - { - flFurthestDist = flDist; - best = tr.end; - } - } - - if (best.x && best.y && best.z) - { - AimToVec(best); - // Update MyAngles, since their (going to be) change(d) - MyAngles.x = m_pMyEnt->targetpitch; - MyAngles.y = m_pMyEnt->targetyaw; - } - - float flNearestHitDist = GetDistance(best); - - if (!UnderWater(m_pMyEnt->o) && m_pMyEnt->onfloor) - { - // Check if a near wall is blocking and we can jump over it - if (flNearestHitDist < 4) - { - // Check if the bot can jump over it - src = MyAngles; - src.x = 0; - - AnglesToVectors(src, forward, right, up); - - vec start = origin; - start.z += 2.0f; - dest = start; - vmul(forward, 6); - vadd(dest, forward); - - TraceLine(start, dest, m_pMyEnt, false, &tr); - //debugbeam(start, end); - - if (!tr.collided) - { - // Jump - debugnav("High wall"); - m_pMyEnt->jumpnext = true; - m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); - return; - } - } - else - { - // Check if something is blocking below us, so the bot can jump over it - src = MyAngles; - src.x = 0; - - AnglesToVectors(src, forward, right, up); - - vec start = origin; - start.z -= 1.7f; - dest = start; - vmul(forward, 4); - vadd(dest, forward); - - TraceLine(start, dest, m_pMyEnt, false, &tr); - - // Trace was blocked? - if (tr.collided) - { - //debugbeam(start, end); - - // Jump - debugnav("Low wall"); - m_pMyEnt->jumpnext = true; - m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); - return; - } - } - - // Check if the bot is going to fall... - src = MyAngles; - src.x = 0.0f; - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 3.0f); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - int cx = fast_f2nat(tr.end.x), cy = fast_f2nat(tr.end.y); - short CubesInWater=0; - for(int x=cx-1;x<=(cx+1);x++) - { - for(int y=cy-1;y<=(cy+1);y++) - { - if (OUTBORD(x, y)) continue; - //sqr *s = S(fast_f2nat(x), fast_f2nat(y)); - //if (!SOLID(s)) - { - vec from = { x, y, m_pMyEnt->o.z }; - dest = from; - dest.z -= 6.0f; - TraceLine(from, dest, m_pMyEnt, false, &tr); - bool turn = false; - if (UnderWater(tr.end)) CubesInWater++; - if (CubesInWater > 2) turn = true; // Always avoid water - if (!tr.collided && RandomLong(0, 1)) - turn = true; // Randomly avoid a fall - - if (turn) - { - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); - m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); - debugnav("Water or a fall in front"); - return; - } - } - } - } - } - - // Is the bot about to head a corner? - if (flNearestHitDist <= 4.0f) - { - src = MyAngles; - src.y = WrapYZAngle(src.y - 45.0f); - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 4.0f); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - WallLeft = (tr.collided); - - src = MyAngles; - src.y += WrapYZAngle(src.y + 45.0f); - AnglesToVectors(src, forward, right, up); - - dest = origin; - vmul(forward, 4.0f); - vadd(dest, forward); - - TraceLine(origin, dest, m_pMyEnt, false, &tr); - - if (WallLeft && tr.collided) - { - // We're about to hit a corner, turn away - debugnav("Corner"); - m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomFloat(160.0f, 200.0f)); - m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); - return; - } - } -} - -// Called when bot is underwater -bool CBot::WaterNav() -{ - const int iSearchRange = 4; - - if (vis(m_vWaterGoal, g_vecZero)) - { - AddDebugText("WaterNav"); - // Find the nearest and reachable cube which isn't underwater - - int cx = fast_f2nat(m_pMyEnt->o.x); - int cy = fast_f2nat(m_pMyEnt->o.y); - float flNearestDist = 9999.0f, flDist; - - if (OUTBORD(cx, cy)) return false; - - // Check all cubes in range... - for (int x=(cx-iSearchRange);x<=(cx+iSearchRange);x++) - { - for (int y=(cy-iSearchRange);y<=(cy+iSearchRange);y++) - { - sqr *s = S(x, y); - - if (SOLID(s)) continue; - if ((x==cx) && (y==cy)) continue; - - vec v = { x, y, GetCubeFloor(x, y) }; - - if (UnderWater(v)) continue; // Skip, cube is underwater - - if (hdr.waterlevel < (v.z - 2.0f)) continue; // Cube is too high - - // Check if the bot 'can fit' on the cube(no near obstacles) - bool small = false; - for (int a=(x-2);a<=(x+2);a++) - { - if (small) break; - for (int b=(y-2);b<=(y+2);b++) - { - if ((x==a) && (y==b)) continue; - vec v2 = { a, b, GetCubeFloor(a, b) }; - if (v.z < (v2.z-1-JUMP_HEIGHT)) - { - small=true; - break; - } - - if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) - { - if ((v2.z) < (v.z-2.0f)) - { - small = true; - break; - } - } - - traceresult_s tr; - TraceLine(v, v2, NULL, false, &tr); - if (tr.collided) - { - small=true; - break; - } - } - if (small) break; - } - if (small) - { - debugbeam(m_pMyEnt->o, v); - continue; - } - - // Okay, cube is valid. - flDist = GetDistance(v); - if (flDist < flNearestDist) - { - flNearestDist = flDist; - m_vWaterGoal = v; - } - } - } - } - - if (!vis(m_vWaterGoal, g_vecZero)) - { - AddDebugText("WaterNav"); - //debugbeam(m_pMyEnt->o, m_vWaterGoal); - vec aim = m_vWaterGoal; - aim.z += 1.5f; // Aim a bit further up - AimToVec(aim); - if ((RandomLong(1, 100) <= 15) && (Get2DDistance(m_vWaterGoal) <= 7.0f)) - m_pMyEnt->jumpnext = true; - return true; - } - - return false; -} - -bool CBot::CheckItems() -{ - if (!m_pCurrentGoalWaypoint && !CheckJump() && CheckStuck()) - { - // Don't check for ents a while when stuck - m_iCheckEntsDelay = lastmillis + RandomLong(1000, 2000); - return false; - } - - if (vis(m_vGoal, g_vecZero)) - m_pTargetEnt = NULL; - - if (!m_pTargetEnt) - { - if (m_iCheckEntsDelay > lastmillis) - return false; - else - { - m_pTargetEnt = SearchForEnts(!m_classicsp); - m_iCheckEntsDelay = lastmillis + RandomLong(2500, 5000); - } - } - - if (m_pTargetEnt) - { - if (HeadToTargetEnt()) - return true; - } - - if (m_eCurrentBotState == STATE_ENT) - { - ResetWaypointVars(); - m_vGoal = g_vecZero; - m_pTargetEnt = NULL; - } - - return false; -} - -bool CBot::InUnreachableList(entity *e) -{ - TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(); - while(p) - { - if (p->Entry->ent == e) return true; - p = p->next; - } - return false; -} - -bool CBot::IsReachable(vec to, float flMaxHeight) -{ - vec from = m_pMyEnt->o; - traceresult_s tr; - float curr_height, last_height; - - float distance = GetDistance(from, to); - - // is the destination close enough? - //if (distance < REACHABLE_RANGE) - { - if (IsVisible(to)) - { - // Look if bot can 'fit trough' - vec src = from, forward, right, up; - AnglesToVectors(GetViewAngles(), forward, right, up); - - // Trace from 1 cube to the left - vec temp = right; - vmul(temp, 1.0f); - vsub(src, temp); - if (!::IsVisible(src, to)) return false; - - // Trace from 1 cube to the right - vadd(src, temp); - if (!::IsVisible(src, to)) return false; - - if (UnderWater(from) && UnderWater(to)) - { - // No need to worry about heights in water - return true; - } -/* - if (to.z > (from.z + JUMP_HEIGHT)) - { - vec v_new_src = to; - vec v_new_dest = to; - - v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); - - // check if we didn't hit anything, if so then it's in mid-air - if (::IsVisible(v_new_src, v_new_dest, NULL)) - { - condebug("to is in midair"); - debugbeam(from, to); - return false; // can't reach this one - } - } -*/ - - // check if distance to ground increases more than jump height - // at points between from and to... - - vec v_temp = to; vsub(v_temp, from); - vec v_direction = Normalize(v_temp); // 1 unit long - vec v_check = from; - vec v_down = from; - - v_down.z = v_down.z - 100.0f; // straight down - - TraceLine(v_check, v_down, NULL, false, &tr); - - // height from ground - last_height = GetDistance(v_check, tr.end); - - distance = GetDistance(to, v_check); // distance from goal - - while (distance > 2.0f) - { - // move 2 units closer to the goal - v_temp = v_direction; - vmul(v_temp, 2.0f); - vadd(v_check, v_temp); - - v_down = v_check; - v_down.z = v_down.z - 100.0f; - - TraceLine(v_check, v_down, NULL, false, &tr); - - curr_height = GetDistance(v_check, tr.end); - - // is the difference in the last height and the current height - // higher that the jump height? - if ((last_height - curr_height) >= flMaxHeight) - { - // can't get there from here... - //condebug("traces failed to to"); - debugbeam(from, to); - return false; - } - - last_height = curr_height; - - distance = GetDistance(to, v_check); // distance from goal - } - - return true; - } - } - - return false; -} - -void CBot::HearSound(int n, vec *o) -{ - // Has the bot already an enemy? - if (m_pMyEnt->enemy) return; - - // Is the sound not interesting? -//fixmebot -/* if ((n==S_DIE1) || (n==S_DIE2) || (n==S_DEATHR) || (n==S_DEATHE) || (n==S_DEATHS) || - (n==S_DEATHB) || (n==S_DEATHH) || (n==S_DEATHD) || (n==S_PIGGR2)) - return;*/ - - int soundvol = m_pBotSkill->iMaxHearVolume - - (int)(GetDistance(*o)*3*m_pBotSkill->iMaxHearVolume/255); - - if (soundvol == 0) return; - - // Look who made the sound(check for the nearest enemy) - float flDist, flNearestDist = 3.0f; // Range of 3 units - dynent *pNearest = NULL; - - /* edit -argh - if (m_sp) - { - // Check all monsters - loopv(monsters) - { - if (!monsters[i] || monsters[i]->state != CS_ALIVE) continue; - - flDist = GetDistance(*o, monsters[i]->o); - if ((flDist < flNearestDist) && IsVisible(monsters[i])) - { - pNearest = monsters[i]; - flNearestDist = flDist; - } - } - } */ - //else - //{ - // Check all players first - loopv(players) - { - if (!players[i] || (players[i]->state != CS_ALIVE) || - isteam(m_pMyEnt->team, players[i]->team)) - continue; - - flDist = GetDistance(*o, players[i]->o); - if ((flDist < flNearestDist) && IsVisible(players[i])) - { - pNearest = players[i]; - flNearestDist = flDist; - } - } - - // Check all bots - loopv(bots) - { - if (!bots[i] || (bots[i]->state != CS_ALIVE) || - isteam(m_pMyEnt->team, bots[i]->team) || (bots[i] == m_pMyEnt)) - continue; - - flDist = GetDistance(*o, bots[i]->o); - if ((flDist < flNearestDist) && IsVisible(bots[i])) - { - pNearest = bots[i]; - flNearestDist = flDist; - } - } - - // Check local player - if (player1 && (player1->state == CS_ALIVE) && - !isteam(m_pMyEnt->team, player1->team)) - { - flDist = GetDistance(*o, player1->o); - if ((flDist < flNearestDist) && IsVisible(player1)) - { - pNearest = player1; - flNearestDist = flDist; - } - } - //} - - if (pNearest) - { - if (m_pMyEnt->enemy != pNearest) - m_iShootDelay = lastmillis + GetShootDelay(); // Add shoot delay when new enemy found - - m_pMyEnt->enemy = pNearest; - } -} - -bool CBot::IsInFOV(const vec &o) -{ - vec v2, forward, right, up; - float flDot; - - AnglesToVectors(GetViewAngles(), forward, right, up); - - v2 = o; - vsub(v2, m_pMyEnt->o); - v2.z = 0.0f; // Make 2D - v2 = Normalize(v2); - forward.z = 0; // Make 2D - - flDot = dotprod(v2 , forward); - - return(flDot >= 0.5f); // sin2(0.5) == 60 degrees FOV -} - -// Code of CBot - End +// +// C++ Implementation: bot_ai +// +// Description: The AI part comes here(navigation, shooting etc) +// +// +// Author: +// + + +// Code of CBot - Start + +#include "../cube.h" + +extern vector sents; +extern vector monsters; + +extern weaponinfo_s WeaponInfoTable[MAX_WEAPONS]; + +vec CBot::GetEnemyPos(dynent *d) +{ + // Aim offset idea by botman + vec o = d->o, offset; + float flDist = GetDistance(d->o), flScale; + + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_ROCKET) + { + // Bot is using a rocket launcher, aim at enemy feet? + if (m_bShootAtFeet && !OUTBORD(d->o.x, d->o.y)) + { + // Only do this when enemy is fairly close to the ground + vec end = o; + end.z -= 900.0f; + traceresult_s tr; + TraceLine(o, end, NULL, false, &tr); + if ((o.z - tr.end.z) < 8.0f) + { + end = o; + end.z = tr.end.z; + if (IsVisible(end)) + { + // Target at ground + o.z = tr.end.z; + } + } + } + + if (m_pBotSkill->bCanPredict) + { + // How higher the skill, how further the bot predicts + vec org = o; + float flPredictTime = RandomFloat(1.25f, 1.7f) / (m_sSkillNr+1); + o = PredictPos(o, d->vel, flPredictTime); + } + } + else + { + if (m_pBotSkill->bCanPredict) + { + // How higher the skill, how 'more' the bot predicts + vec org = o; + float flPredictTime = RandomFloat(0.8f, 1.2f) / (m_sSkillNr+1); + o = PredictPos(o, d->vel, flPredictTime); + } + } + + if (flDist > 60.0f) + flScale = 1.0; + else if (flDist > 6.0f) + flScale = flDist / 60.0; + else + flScale = 0.1; + + switch (m_sSkillNr) + { + case 0: + // no offset + offset.x = 0; + offset.y = 0; + offset.z = 0; + break; + case 1: + // GOOD, offset a little for x, y, and z + offset.x = RandomFloat(-3, 3) * flScale; + offset.y = RandomFloat(-3, 3) * flScale; + offset.z = RandomFloat(-6, 6) * flScale; + break; + case 2: + // FAIR, offset somewhat for x, y, and z + offset.x = RandomFloat(-8, 8) * flScale; + offset.y = RandomFloat(-8, 8) * flScale; + offset.z = RandomFloat(-12, 12) * flScale; + break; + case 3: + // POOR, offset for x, y, and z + offset.x = RandomFloat(-15, 15) * flScale; + offset.y = RandomFloat(-15, 15) * flScale; + offset.z = RandomFloat(-25, 25) * flScale; + break; + case 4: + // BAD, offset lots for x, y, and z + offset.x = RandomFloat(-20, 20) * flScale; + offset.y = RandomFloat(-20, 20) * flScale; + offset.z = RandomFloat(-35, 35) * flScale; + break; + } + + vadd(o, offset); + return o; +} + +bool CBot::FindEnemy(void) +{ + // UNDONE: Enemies are now only scored on their distance + + if (m_pMyEnt->enemy) // Bot already has an enemy + { + // Check if the enemy is still in game + bool found = IsInGame(m_pMyEnt->enemy); + + // Check if the enemy is still ingame, still alive, not joined my team and is visible + if (found && !isteam(m_pMyEnt->team, m_pMyEnt->enemy->team)) + { + if ((m_pMyEnt->enemy->state == CS_ALIVE) && (IsVisible(m_pMyEnt->enemy))) + return true; + else + m_pPrevEnemy = m_pMyEnt->enemy; + } + else + m_pMyEnt->enemy = NULL; + } + + if (m_iEnemySearchDelay > lastmillis) return (m_pMyEnt->enemy!=NULL); + + m_pMyEnt->enemy = NULL; + + // Add enemy searchy delay + float MinDelay = m_pBotSkill->flMinEnemySearchDelay; + float MaxDelay = m_pBotSkill->flMaxEnemySearchDelay; + m_iEnemySearchDelay = lastmillis + int(RandomFloat(MinDelay, MaxDelay) * 1000.0f); + + dynent *pNewEnemy = NULL, *d = NULL; + float flDist, flNearestDist = 99999.9f; + short EnemyVal, BestEnemyVal = -100; + + // search for only for monsters when SP mode + /* no more sp as such -argh (sorry bout messy commenting) + if (m_sp) + { + loopv(monsters) + { + d = monsters[i]; // Handy shortcut + + if (d->state != CS_ALIVE) + continue; + + // Check if the enemy is visible + if(!IsInFOV(d) || !IsVisible(d)) + continue; + + flDist = GetDistance(d->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + } + } + } */ + + //else + //{ + + // First loop through all players + loopv(players) + { + d = players[i]; // Handy shortcut + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + // Check if the enemy is visible + if(!IsInFOV(d) || !IsVisible(d)) + continue; + + flDist = GetDistance(d->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + } + } + + // Then loop through all bots + loopv(bots) + { + d = bots[i]; // Handy shortcut + + if (d == m_pMyEnt) + continue; + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + // Check if the enemy is visible + if(!IsInFOV(d) || !IsVisible(d)) + continue; + + flDist = GetDistance(d->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + } + } + + // Then examine the local player + if (player1 && !isteam(player1->team, m_pMyEnt->team) && + (player1->state == CS_ALIVE)) + { + // Check if the enemy is visible + if(IsInFOV(player1) && IsVisible(player1)) + { + flDist = GetDistance(player1->o); + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (EnemyVal > BestEnemyVal) + { + pNewEnemy = player1; + BestEnemyVal = EnemyVal; + } + } + } + //} + + if (pNewEnemy) + { + if (!m_pMyEnt->enemy) // Add shoot delay if new enemy is found + { + float flMinShootDelay = m_pBotSkill->flMinAttackDelay; + float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; + + m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, + flMaxShootDelay) * 1000.0f); + } + + if ((m_pMyEnt->enemy != pNewEnemy) && m_pMyEnt->enemy) + m_pPrevEnemy = m_pMyEnt->enemy; + + m_pMyEnt->enemy = pNewEnemy; + return true; + } + + return false; +} + +bool CBot::CheckHunt(void) +{ + if (!BotManager.BotsShoot()) return false; + + if (m_pHuntTarget) // Bot already has an enemy to hunt + { + // Check if the enemy is still in game + bool found = IsInGame(m_pHuntTarget); + + // Check if the enemy is still ingame, still alive, not joined my team and is visible + if (found && !isteam(m_pMyEnt->team, m_pHuntTarget->team)) + { + if ((m_pHuntTarget->state == CS_ALIVE) && IsReachable(m_vHuntLocation)) + return true; + } + else + m_pHuntTarget = NULL; + } + + if (m_iHuntDelay > lastmillis) return (m_pHuntTarget!=NULL); + + if (!vis(m_vHuntLocation, g_vecZero)) + m_vPrevHuntLocation = m_vHuntLocation; + + m_pHuntTarget = NULL; + m_vHuntLocation = g_vecZero; + + // Add enemy hunt search delay + m_iHuntDelay = lastmillis + 1500; + + dynent *pNewEnemy = NULL, *d = NULL; + float flDist, flNearestDist = 99999.9f, flNearestOldPosDistToEnemy = 99999.9f; + float flNearestOldPosDistToBot = 99999.9f; + short EnemyVal, BestEnemyVal = -100; + vec BestOldPos; + + // search for only for monsters when SP mode + //edit -argh + /*if (m_sp) + { + loopv(monsters) + { + d = monsters[i]; // Handy shortcut + + if (d->state != CS_ALIVE) + continue; + + flDist = GetDistance(d->o); + + if (flDist > 250.0f) continue; + + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal < BestEnemyVal) continue; + + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToBot) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + } + } */ + //else + //{ + // First loop through all players + loopv(players) + { + d = players[i]; // Handy shortcut + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + flDist = GetDistance(d->o); + + if (flDist > 250.0f) continue; + + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal < BestEnemyVal) continue; + + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + //} + + // Then loop through all bots + loopv(bots) + { + d = bots[i]; // Handy shortcut + + if (d == m_pMyEnt) + continue; + + if (!d || isteam(d->team, m_pMyEnt->team) || (d->state != CS_ALIVE)) + continue; + + flDist = GetDistance(d->o); + + if (flDist > 250.0f) continue; + + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal < BestEnemyVal) continue; + + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + } + + // Then examine the local player + if (player1 && !isteam(player1->team, m_pMyEnt->team) && + (player1->state == CS_ALIVE) && ((flDist = GetDistance(player1->o)) <= 250.0f)) + { + d = player1; + EnemyVal = 1; + + if (flDist < flNearestDist) + { + EnemyVal+=2; + flNearestDist = flDist; + } + + if (d == m_pPrevEnemy) + EnemyVal+=2; + + if (EnemyVal >= BestEnemyVal) + { + BestEnemyVal = EnemyVal; + vec bestfromenemy = g_vecZero, bestfrombot = g_vecZero; + flNearestOldPosDistToEnemy = flNearestOldPosDistToBot = 9999.9f; + + // Check previous locations of enemy + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(d->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = d->PrevLocations.prevloc[j]; + + flDist = GetDistance(d->o, v); + + if ((flDist < flNearestOldPosDistToEnemy) && IsReachable(v)) + { + flNearestOldPosDistToEnemy = flDist; + bestfromenemy = v; + } + } + + // Check previous locations of bot hisself + for (int j=0;jPrevLocations.prevloc[j], m_vPrevHuntLocation)) continue; + if (vis(m_pMyEnt->PrevLocations.prevloc[j], g_vecZero)) continue; + + vec v = m_pMyEnt->PrevLocations.prevloc[j]; + + flDist = GetDistance(v); + + if ((flDist < flNearestOldPosDistToBot) && ::IsVisible(d->o, v) && + IsReachable(v)) + { + flNearestOldPosDistToBot = flDist; + bestfrombot = v; + } + } + + if (!vis(bestfromenemy, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfromenemy; + } + else if (!vis(bestfrombot, g_vecZero)) + { + pNewEnemy = d; + BestEnemyVal = EnemyVal; + BestOldPos = bestfrombot; + } + } + } + } + + if (pNewEnemy) + { + if (!m_pHuntTarget) // Add shoot delay if new enemy is found + { + float flMinShootDelay = m_pBotSkill->flMinAttackDelay; + float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; + + m_iShootDelay = lastmillis + int(RandomFloat(flMinShootDelay, + flMaxShootDelay) * 1000.0f); + } + + if (!vis(m_vHuntLocation, g_vecZero)) + m_vPrevHuntLocation = m_vHuntLocation; + + m_pHuntTarget = pNewEnemy; + m_vHuntLocation = BestOldPos; + m_fPrevHuntDist = 0.0f; + return true; + } + + return false; +} + +bool CBot::HuntEnemy(void) +{ + if (m_iCombatNavTime > lastmillis) + { + SetMoveDir(m_iMoveDir, false); + return true; + } + + m_iCombatNavTime = m_iMoveDir = 0; + + bool bDone = false, bNew = false; + float flDist = GetDistance(m_vHuntLocation); + + if (flDist <= 3.0f) + bDone = true; + + if ((m_fPrevHuntDist > 0.0) && (flDist > m_fPrevHuntDist)) + bDone = true; + + m_fPrevHuntDist = flDist; + + if ((m_iHuntPlayerUpdateTime < lastmillis) || bDone) + { + m_iHuntPlayerUpdateTime = lastmillis + 1250; + + short BestPosIndexFromEnemy = -1, BestPosIndexFromBot = -1, j; + float NearestDistToEnemy = 9999.9f, NearestDistToBot = 9999.9f; + + // Check previous locations of enemy + for (j=0;jPrevLocations.prevloc[j]; + + if (bDone && vis(m_vHuntLocation, OldPos)) + continue; + + if (vis(OldPos, g_vecZero)) + continue; + + if (GetDistance(OldPos) > 250.0f) + continue; + + flDist = GetDistance(m_pHuntTarget->o, OldPos); + + if ((flDist < NearestDistToEnemy) && (IsReachable(OldPos))) + { + NearestDistToEnemy = flDist; + BestPosIndexFromEnemy = j; + break; + } + } + + // Check previous locations of bot + for (j=0;jPrevLocations.prevloc[j]; + + if (bDone && vis(m_vHuntLocation, OldPos)) + continue; + + if (vis(OldPos, g_vecZero)) + continue; + + if (GetDistance(OldPos) > 25.0f) + continue; + + flDist = GetDistance(OldPos); + + if ((flDist < NearestDistToBot) && ::IsVisible(m_pHuntTarget->o, OldPos) && + (IsReachable(OldPos))) + { + NearestDistToBot = flDist; + BestPosIndexFromBot = j; + break; + } + } + + if (BestPosIndexFromEnemy > -1) + { + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = m_pHuntTarget->PrevLocations.prevloc[BestPosIndexFromEnemy]; + bNew = true; + m_fPrevHuntDist = 0.0f; + } + else if (BestPosIndexFromBot > -1) + { + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = m_pMyEnt->PrevLocations.prevloc[BestPosIndexFromBot]; + bNew = true; + m_fPrevHuntDist = 0.0f; + } + } + + if (!bNew) // Check if current location is still reachable + { + if (bDone || !IsReachable(m_vHuntLocation)) + { + m_pHuntTarget = NULL; + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = g_vecZero; + m_fPrevHuntDist = 0.0f; + m_iHuntDelay = lastmillis + 3500; + return false; + } + } + else + condebug("New hunt pos"); + + // Aim to position + //AimToVec(m_vHuntLocation); + + int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, m_vHuntLocation); + + if (iMoveDir != DIR_NONE) + { + m_iMoveDir = iMoveDir; + m_iCombatNavTime = lastmillis + 125; + } + + bool aimtopos = true; + + if ((lastmillis - m_iSawEnemyTime) > 1500) + { + if (m_iLookAroundDelay < lastmillis) + { + if (m_iLookAroundTime > lastmillis) + { + if (m_iLookAroundUpdateTime < lastmillis) + { + float flAddAngle; + if (m_bLookLeft) flAddAngle = RandomFloat(-110, -80); + else flAddAngle = RandomFloat(80, 110); + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->targetyaw + flAddAngle); + m_iLookAroundUpdateTime = lastmillis + RandomLong(400, 800); + } + aimtopos = false; + } + else if (m_iLookAroundTime > 0) + { + m_iLookAroundTime = 0; + m_iLookAroundDelay = lastmillis + RandomLong(750, 1000); + } + else + m_iLookAroundTime = lastmillis + RandomLong(2200, 3200); + } + } + + if (aimtopos) + AimToVec(m_vHuntLocation); + + debugbeam(m_pMyEnt->o, m_vHuntLocation); + + if (m_fYawToTurn <= 25.0f) + m_iHuntLastTurnLessTime = lastmillis; + + // Bot had to turn much for a while? + if ((m_iHuntLastTurnLessTime > 0) && (m_iHuntLastTurnLessTime < (lastmillis - 1000))) + { + m_iHuntPauseTime = lastmillis + 200; + } + + if (m_iHuntPauseTime >= lastmillis) + { + m_pMyEnt->move = 0; + m_fPrevHuntDist = 0.0f; + } + else + { + // Check if bot has to jump over a wall... + if (CheckJump()) + m_pMyEnt->jumpnext = true; + else // Check if bot has to jump to reach this location + { + float flHeightDiff = m_vHuntLocation.z - m_pMyEnt->o.z; + bool bToHigh = false; + if (Get2DDistance(m_vHuntLocation) <= 2.0f) + { + if (flHeightDiff >= 1.5f) + { + if (flHeightDiff <= JUMP_HEIGHT) + { +#ifndef RELEASE_BUILD + char sz[64]; + sprintf(sz, "OldPos z diff: %f", m_vHuntLocation.z-m_pMyEnt->o.z); + condebug(sz); +#endif + // Jump if close to pos and the pos is high + m_pMyEnt->jumpnext = true; + } + else + bToHigh = true; + } + } + + if (bToHigh) + { + m_pHuntTarget = NULL; + m_vPrevHuntLocation = m_vHuntLocation; + m_vHuntLocation = g_vecZero; + m_fPrevHuntDist = 0.0f; + m_iHuntDelay = lastmillis + 3500; + return false; + } + } + } + + return true; +} + +void CBot::ShootEnemy() +{ + if (!m_pMyEnt->enemy) + return; + + m_iSawEnemyTime = lastmillis; + + // Aim to enemy + vec enemypos = GetEnemyPos(m_pMyEnt->enemy); + AimToVec(enemypos); + + // Time to shoot? + if (m_iShootDelay < lastmillis) + //if ((lastmillis-m_pMyEnt->lastaction) >= m_pMyEnt->gunwait) + { + if (m_pMyEnt->ammo[m_pMyEnt->gunselect]) + { + // If the bot is using a sniper only shoot if crosshair is near the enemy + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_SNIPER) + { + float yawtoturn = fabs(WrapYZAngle(m_pMyEnt->yaw - m_pMyEnt->targetyaw)); + float pitchtoturn = fabs(WrapYZAngle(m_pMyEnt->pitch - m_pMyEnt->targetpitch)); + + if ((yawtoturn > 5) || (pitchtoturn > 15)) // UNDONE: Should be skill based + return; + } + + float flDist = GetDistance(enemypos); + + // Check if bot is in fire range + if ((flDist < WeaponInfoTable[m_pMyEnt->gunselect].flMinFireDistance) || + (flDist > WeaponInfoTable[m_pMyEnt->gunselect].flMaxFireDistance)) + return; + + // Now shoot! + m_pMyEnt->attacking = true; + + // Get the position the bot is aiming at + vec forward, right, up, dest; + traceresult_s tr; + + AnglesToVectors(GetViewAngles(), forward, right, up); + + dest = m_pMyEnt->o; + vmul(forward, 1000); + vadd(dest, forward); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); + debugbeam(m_pMyEnt->o, tr.end); + + // Shoot + shoot(m_pMyEnt, tr.end); + + // Add shoot delay + m_iShootDelay = lastmillis + GetShootDelay(); + } + ChoosePreferredWeapon(); + } +#ifndef RELEASE_BUILD + else + { + char sz[64]; + sprintf(sz, "shootdelay: %d\n", (m_iShootDelay-lastmillis)); + AddDebugText(sz); + } +#endif +} + +bool CBot::ChoosePreferredWeapon() +{ + TMultiChoice WeaponChoices; + short sWeaponScore; + float flDist = GetDistance(m_pMyEnt->enemy->o); + + if ((m_iChangeWeaponDelay > lastmillis) && (m_pMyEnt->ammo[m_pMyEnt->gunselect])) + { + if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE) || (flDist <= 3.5f)) + return true; + } + + // Choose a weapon + for(int i=0;iammo[i] == 0) continue; + + sWeaponScore = 5; // Minimal score for a weapon + + if ((flDist >= WeaponInfoTable[i].flMinDesiredDistance) && + (flDist <= WeaponInfoTable[i].flMaxDesiredDistance)) + { + // In desired range for this weapon + sWeaponScore += 5; // Increase score much + } + else if ((flDist < WeaponInfoTable[i].flMinFireDistance) || + (flDist > WeaponInfoTable[i].flMaxFireDistance)) + continue; // Wrong distance for this weapon + + // The ideal distance would be between the Min and Max desired distance. + // Score on the difference of the avarage of the Min and Max desired distance. + float flAvarage = (WeaponInfoTable[i].flMinDesiredDistance + + WeaponInfoTable[i].flMaxDesiredDistance) / 2.0f; + float flIdealDiff = fabs(flDist - flAvarage); + + if (flIdealDiff < 0.5f) // Close to ideal distance + sWeaponScore += 4; + else if (flIdealDiff <= 1.0f) + sWeaponScore += 2; + + // Now rate the weapon on available ammo... + if (WeaponInfoTable[i].sMinDesiredAmmo > 0) + { + // Calculate how much percent of the min desired ammo the bot has + float flDesiredPercent = (float(m_pMyEnt->ammo[i]) / + float(WeaponInfoTable[i].sMinDesiredAmmo)) * + 100.0f; + + if (flDesiredPercent >= 400.0f) + sWeaponScore += 4; + else if (flDesiredPercent >= 200.0f) + sWeaponScore += 3; + else if (flDesiredPercent >= 100.0f) + sWeaponScore += 1; + } + + WeaponChoices.Insert(i, sWeaponScore); + } + + int WeaponSelect; + if (WeaponChoices.GetSelection(WeaponSelect)) + { + m_iChangeWeaponDelay = lastmillis + RandomLong(2000, 8000); + m_bShootAtFeet = ((WeaponInfoTable[WeaponSelect].eWeaponType==TYPE_ROCKET) && + (RandomLong(1, 100) <= + m_pBotSkill->sShootAtFeetWithRLPercent)); + return SelectGun(WeaponSelect); + } + + return false; +} + +int CBot::GetShootDelay() +{ + // UNDONE + if ((WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) || + (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_AUTO)) + return m_pMyEnt->gunwait; + + float flMinShootDelay = m_pBotSkill->flMinAttackDelay; + float flMaxShootDelay = m_pBotSkill->flMaxAttackDelay; + return max(m_pMyEnt->gunwait, int(RandomFloat(flMinShootDelay, flMaxShootDelay) * 1000.0f)); +} + +void CBot::MainAI() +{ + // Default bots will run forward + m_pMyEnt->move = 1; + + // Default bots won't strafe + m_pMyEnt->strafe = 0; + + if (!BotManager.BotsShoot() && m_pMyEnt->enemy) + m_pMyEnt->enemy = NULL; // Clear enemy when bots may not shoot + + if (m_bGoToDebugGoal) // For debugging the waypoint navigation + { + if (!HeadToGoal()) + { + ResetWaypointVars(); + m_vGoal = g_vecZero; + } + else + AddDebugText("Heading to debug goal..."); + } + if (BotManager.BotsShoot() && FindEnemy()) // Combat + { + AddDebugText("has enemy"); + + // Shoot at enemy + ShootEnemy(); + + if (m_eCurrentBotState != STATE_ENEMY) + { + m_vGoal = g_vecZero; + ResetWaypointVars(); + } + + m_eCurrentBotState = STATE_ENEMY; + if (!CheckJump()) + DoCombatNav(); + } + else if (CheckHunt() && HuntEnemy()) + { + AddDebugText("Hunting to %s", m_pHuntTarget->name); + m_eCurrentBotState = STATE_HUNT; + } + // Heading to an interesting entity(ammo, armour etc) + else if (CheckItems()) + { + AddDebugText("has ent"); + m_eCurrentBotState = STATE_ENT; + } + else if (m_classicsp && DoSPStuff()) // Home to goal, find/follow friends etc. + { + AddDebugText("SP stuff"); + m_eCurrentBotState = STATE_SP; + } + else // Normal navigation + { + if (m_eCurrentBotState != STATE_NORMAL) + { + m_vGoal = g_vecZero; + ResetWaypointVars(); + } + + m_eCurrentBotState = STATE_NORMAL; + bool bDoNormalNav = true; + + AddDebugText("normal nav"); + + // Make sure the bot looks straight forward and not up or down + m_pMyEnt->pitch = 0; + + // if it is time to look for a waypoint AND if there are waypoints in this + // level... + if (WaypointClass.m_iWaypointCount >= 1) + { + // check if we need to find a waypoint... + if (CurrentWPIsValid() == false) + { + if (m_iLookForWaypointTime <= lastmillis) + { + // find the nearest reachable waypoint + waypoint_s *pWP = GetNearestWaypoint(10.0f); + + if (pWP && (pWP != m_pCurrentWaypoint)) + { + SetCurrentWaypoint(pWP); + condebug("New nav wp"); + bDoNormalNav = !HeadToWaypoint(); + if (bDoNormalNav) + ResetWaypointVars(); + } + else + ResetWaypointVars(); + + m_iLookForWaypointTime = lastmillis + 250; + } + } + else + { + bDoNormalNav = !HeadToWaypoint(); + if (bDoNormalNav) + ResetWaypointVars(); + AddDebugText("Using wps for nav"); + } + } + + // If nothing special, do regular (waypointless) navigation + if(bDoNormalNav) + { + // Is the bot underwater? + if (UnderWater(m_pMyEnt->o) && WaterNav()) + { + // Bot is under water, navigation happens in WaterNav + } + // Time to check the environment? + else if (m_iCheckEnvDelay < lastmillis) + { + if (!vis(m_vWaterGoal, g_vecZero)) m_vWaterGoal = g_vecZero; + + // Check for stuck and strafe + if (UnderWater(m_pMyEnt->o) || !CheckStuck()) + { + // Only do this when the bot is underwater or when the bot isn't stuck + + // Check field of view (FOV) + CheckFOV(); + } + } + + // Check if the bot has to strafe + CheckStrafe(); + + m_pMyEnt->move = 1; + } + } +} + +void CBot::DoCombatNav() +{ + if (m_iCombatNavTime > lastmillis) + { + // If bot has a lower skill and has to turn much, wait + if ((m_sSkillNr > 2) && (m_fYawToTurn > 90.0f)) + { + ResetMoveSpeed(); + } + else + { + SetMoveDir(m_iMoveDir, false); + } + return; + } + + if (m_bCombatJump) + { + m_pMyEnt->jumpnext = true; + m_bCombatJump = false; + m_iCombatJumpDelay = lastmillis + RandomLong(1500, 2800); + return; + } + + m_iMoveDir = DIR_NONE; + + // Check if bot is on top of his enemy + float r = m_pMyEnt->radius+m_pMyEnt->enemy->radius; + if ((fabs(m_pMyEnt->enemy->o.x-m_pMyEnt->o.x)enemy->o.y-m_pMyEnt->o.y)enemy->o.z+m_pMyEnt->enemy->aboveeye) < (m_pMyEnt->o.z + m_pMyEnt->aboveeye))) + { + // Try to get off him! + condebug("On enemy!"); + TMultiChoice AwayDirChoices; + + if (IsVisible(LEFT, 4.0f, false)) + AwayDirChoices.Insert(LEFT); + if (IsVisible(RIGHT, 4.0f, false)) + AwayDirChoices.Insert(RIGHT); + if (IsVisible(FORWARD, 4.0f, false)) + AwayDirChoices.Insert(FORWARD); + if (IsVisible(BACKWARD, 4.0f, false)) + AwayDirChoices.Insert(BACKWARD); + + int iDir; + if (AwayDirChoices.GetSelection(iDir)) + { + m_iMoveDir = iDir; + m_iCombatNavTime = lastmillis + 500; + } + } + + float flDist = GetDistance(m_pMyEnt->enemy->o); + + // Check for nearby items? + if (((m_iCheckEntsDelay < lastmillis) || m_pTargetEnt) && + m_pBotSkill->bCanSearchItemsInCombat) + { + m_iCheckEntsDelay = lastmillis + 125; + bool bSearchItems = false; + + if (m_pTargetEnt) + { + // Bot has already found an entity, still valid? + vec v = { m_pTargetEnt->x, m_pTargetEnt->y, + S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; + if ((GetDistance(v) > 25.0f) || !IsVisible(m_pTargetEnt)) + m_pTargetEnt = NULL; + } + + if (!m_pTargetEnt && (m_iCheckEntsDelay <= lastmillis)) + { + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) + bSearchItems = (flDist >= 8.0f); + else + bSearchItems = (m_pMyEnt->ammo[m_pMyEnt->gunselect] <= + WeaponInfoTable[m_pMyEnt->gunselect].sMinDesiredAmmo); + + if (bSearchItems) + m_pTargetEnt = SearchForEnts(false, 25.0f, 1.0f); + } + + if (m_pTargetEnt) + { + condebug("Combat ent"); + vec v = { m_pTargetEnt->x, m_pTargetEnt->y, + S(m_pTargetEnt->x, m_pTargetEnt->y)->floor+m_pMyEnt->eyeheight }; + + debugbeam(m_pMyEnt->o, v); + + float flHeightDiff = v.z - m_pMyEnt->o.z; + bool bToHigh = false; + + // Check he height for this ent + if (Get2DDistance(v) <= 2.0f) + { + if (flHeightDiff >= 1.5f) + { + if (flHeightDiff <= JUMP_HEIGHT) + { +#ifndef RELEASE_BUILD + char sz[64]; + sprintf(sz, "Ent z diff: %f", v.z-m_pMyEnt->o.z); + condebug(sz); +#endif + m_pMyEnt->jumpnext = true; // Jump if close to ent and the ent is high + } + else + bToHigh = true; + } + } + + if (!bToHigh) + { + int iMoveDir = GetDirection(GetViewAngles(), m_pMyEnt->o, v); + + if (iMoveDir != DIR_NONE) + { + m_iMoveDir = iMoveDir; + m_iCombatNavTime = lastmillis + RandomLong(125, 250); + } + + // Check if bot needs to jump over something + vec from = m_pMyEnt->o; + from.z -= 1.0f; + if (!IsVisible(from, iMoveDir, 3.0f, false)) + m_pMyEnt->jumpnext = true; + + return; + } + } + } + + // High skill and enemy is close? + if ((m_sSkillNr <= 1) && (m_fYawToTurn < 80.0f) && (flDist <= 20.0f) && + (m_iCombatJumpDelay < lastmillis)) + { + // Randomly jump a bit, to avoid some basic firepower ;) + + // How lower the distance to the enemy, how higher the chance for a jump + short sJumpPercent = (100 - ((short)flDist * 8)); + if (RandomLong(1, 100) <= sJumpPercent) + { + // Choose a nice direction to jump to + + // Is the enemy close? + if ((GetDistance(m_pMyEnt->enemy->o) <= 4.0f) || + (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE)) + { + m_iMoveDir = FORWARD; // Jump forward + SetMoveDir(FORWARD, false); + m_bCombatJump = true; + } + else if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType != TYPE_MELEE)// else jump to a random direction + { + /* + Directions to choose: + - Forward-right + - Right + - Backward-right + - Backward + - Backward-left + - Left + - Forward-left + + */ + + TMultiChoice JumpDirChoices; + short sForwardScore = ((flDist > 8.0f) || (flDist < 4.0f)) ? 20 : 10; + short sBackwardScore = (flDist <= 6.0f) ? 20 : 10; + short sStrafeScore = (flDist < 6.0f) ? 20 : 10; + + if (IsVisible((FORWARD | LEFT), 4.0f, false)) + JumpDirChoices.Insert((FORWARD | LEFT), sForwardScore); + if (IsVisible((FORWARD | RIGHT), 4.0f, false)) + JumpDirChoices.Insert((FORWARD | RIGHT), sForwardScore); + if (IsVisible(BACKWARD, 4.0f, false)) + JumpDirChoices.Insert(BACKWARD, sBackwardScore); + if (IsVisible((BACKWARD | LEFT), 4.0f, false)) + JumpDirChoices.Insert((BACKWARD | LEFT), sBackwardScore); + if (IsVisible((BACKWARD | RIGHT), 4.0f, false)) + JumpDirChoices.Insert((BACKWARD | RIGHT), sBackwardScore); + if (IsVisible(LEFT, 4.0f, false)) + JumpDirChoices.Insert(LEFT, sStrafeScore); + if (IsVisible(RIGHT, 4.0f, false)) + JumpDirChoices.Insert(RIGHT, sStrafeScore); + + int JumpDir; + if (JumpDirChoices.GetSelection(JumpDir)) + { + m_iMoveDir = JumpDir; + SetMoveDir(JumpDir, false); + m_bCombatJump = true; + } + } + + if (m_bCombatJump) + { + m_iCombatNavTime = lastmillis + RandomLong(125, 250); + return; + } + } + } + + if (WeaponInfoTable[m_pMyEnt->gunselect].eWeaponType == TYPE_MELEE) + return; // Simply walk towards enemy if using a melee type + + flDist = Get2DDistance(m_pMyEnt->enemy->o); + + // Out of desired range for current weapon? + if ((flDist <= WeaponInfoTable[m_pMyEnt->gunselect].flMinDesiredDistance) || + (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance)) + { + if (flDist >= WeaponInfoTable[m_pMyEnt->gunselect].flMaxDesiredDistance) + { + m_iMoveDir = FORWARD; + } + else + { + m_iMoveDir = BACKWARD; + } + + vec src, forward, right, up, dest, MyAngles = GetViewAngles(), o = m_pMyEnt->o; + traceresult_s tr; + + // Is it furthest or farthest? bleh + float flFurthestDist = 0; + int bestdir = -1, dir = 0; + bool moveback = (m_pMyEnt->move == -1); + + for(int j=-45;j<=45;j+=45) + { + src = MyAngles; + src.y = WrapYZAngle(src.y + j); + src.x = 0.0f; + + // If we're moving backwards, trace backwards + if (moveback) + src.y = WrapYZAngle(src.y + 180); + + AnglesToVectors(src, forward, right, up); + + dest = o; + vmul(forward, 40); + vadd(dest, forward); + + TraceLine(o, dest, m_pMyEnt, false, &tr); + + //debugbeam(origin, end); + flDist = GetDistance(tr.end); + + if (flFurthestDist < flDist) + { + flFurthestDist = flDist; + bestdir = dir; + } + dir++; + } + + switch(bestdir) + { + case 0: + if (moveback) + m_iMoveDir |= RIGHT; // Strafe right + else + m_iMoveDir |= LEFT; // Strafe left + break; + case 2: + if (moveback) + m_iMoveDir |= LEFT; // Strafe left + else + m_iMoveDir |= RIGHT; // Strafe right + break; + } + + if (m_iMoveDir != DIR_NONE) + { + SetMoveDir(m_iMoveDir, false); + m_iCombatNavTime = lastmillis + 500; + } + } + else if (m_pBotSkill->bCircleStrafe) // Circle strafe when in desired range... + { + traceresult_s tr; + vec angles, end, forward, right, up; + TMultiChoice StrafeDirChoices; + + // Check the left side... + angles = GetViewAngles(); + angles.y = WrapYZAngle(angles.y - 75.0f); // Not 90 degrees because the bot + // doesn't strafe in a straight line + // (aims still to enemy). + + AnglesToVectors(angles, forward, right, up); + end = m_pMyEnt->o; + vmul(forward, 15.0f); + vadd(end, forward); + + TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); + StrafeDirChoices.Insert(LEFT, (int)GetDistance(m_pMyEnt->o, tr.end)); + + // Check the right side... + angles = GetViewAngles(); + angles.y = WrapYZAngle(angles.y + 75.0f); // Not 90 degrees because the bot + // doesn't strafe in a straight line + // (aims still to enemy). + + AnglesToVectors(angles, forward, right, up); + end = m_pMyEnt->o; + vmul(forward, 15.0f); + vadd(end, forward); + + TraceLine(m_pMyEnt->o, end, m_pMyEnt, true, &tr); + StrafeDirChoices.Insert(RIGHT, (int)GetDistance(m_pMyEnt->o, tr.end)); + + int StrafeDir; + if (StrafeDirChoices.GetSelection(StrafeDir)) + { + m_iMoveDir = StrafeDir; + SetMoveDir(StrafeDir, false); + m_iCombatNavTime = lastmillis + RandomLong(1500, 3000); + } + } + else // Bot can't circle strafe(low skill), just stand still + ResetMoveSpeed(); +} + +bool CBot::CheckStuck() +{ + if (m_iStuckCheckDelay >= lastmillis) + return false; + + if ((!vis(m_vGoal, g_vecZero)) && (GetDistance(m_vGoal) < 2.0f)) + return false; + + bool IsStuck = false; + + vec CurPos = m_pMyEnt->o, PrevPos = m_vPrevOrigin; + CurPos.z = PrevPos.z = 0; + // Did the bot hardly move the last frame? + if (GetDistance(CurPos, PrevPos) <= 0.1f) + { + if (m_bStuck) + { + if (m_iStuckTime < lastmillis) + IsStuck = true; + } + else + { + m_bStuck = true; + m_iStuckTime = lastmillis + 1000; + } + } + else + { + m_bStuck = false; + m_iStuckTime = 0; + } + + if (IsStuck) + { +#ifndef RELEASE_BUILD + char msg[64]; + sprintf(msg, "stuck (%f)", GetDistance(m_vPrevOrigin)); + condebug(msg); +#endif + + m_bStuck = false; + m_iStuckTime = 0; + + // Crap bot is stuck, lets just try some random things + + // Check if the bot can turn around + vec src = GetViewAngles(); + src.x = 0; + vec forward, right, up, dir, dest; + traceresult_s tr; + + AnglesToVectors(src, forward, right, up); + + // Check the left side... + dir = right; + dest = m_pMyEnt->o; + vmul(dir, 3); + vsub(dest, dir); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, false, &tr); + //debugbeam(m_pMyEnt->o, end); + + if (!tr.collided) + { + // Bot can turn left, do so + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw - 90); + m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; + return true; + } + + // Check the right side... + dir = right; + dest = m_pMyEnt->o; + vmul(dir, 3); + vadd(dest, dir); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); + //debugbeam(m_pMyEnt->o, end); + + if (!tr.collided) + { + // Bot can turn right, do so + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 90); + m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; + return true; + } + + // Check if bot can turn 180 degrees + dir = forward; + dest = m_pMyEnt->o; + vmul(dir, 3); + vsub(dest, dir); + + TraceLine(m_pMyEnt->o, dest, m_pMyEnt, true, &tr); + //debugbeam(m_pMyEnt->o, end); + + if (!tr.collided) + { + // Bot can turn around, do so + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); + m_iStuckCheckDelay = m_iCheckEnvDelay = lastmillis + 500; + return true; + } + + // Bleh bot couldn't turn, lets just randomly jump :| + + condebug("Randomly avoiding stuck..."); + if (RandomLong(0, 2) == 0) + m_pMyEnt->jumpnext = true; + else + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomLong(60, 160)); + return true; + } + + return false; +} + +// Check if a near wall is blocking and we can jump over it +bool CBot::CheckJump() +{ + bool bHasGoal = (!vis(m_vGoal, g_vecZero)); + float flGoalDist = (bHasGoal) ? GetDistance(m_vGoal) : 0.0f; + +// if ((bHasGoal) && (flGoalDist < 2.0f)) + // return false; UNDONE? + + vec start = m_pMyEnt->o; + float flTraceDist = 3.0f; + + if (bHasGoal && (flGoalDist < flTraceDist)) + flTraceDist = flGoalDist; + + // Something blocks at eye hight? + if (!IsVisible(start, FORWARD, flTraceDist, false)) + { + // Check if the bot can jump over it + start.z += (JUMP_HEIGHT - 1.0f); + if (IsVisible(start) && !IsVisible(start, FORWARD, flTraceDist, false)) + { + // Jump + debugnav("High wall"); + m_pMyEnt->jumpnext = true; + return true; + } + } + else + { + // Check if something is blocking at feet height, so the bot can jump over it + start.z -= 1.7f; + + // Trace was blocked? + if (!IsVisible(start, FORWARD, flTraceDist, false)) + { + //debugbeam(start, end); + + // Jump + debugnav("Low wall"); + m_pMyEnt->jumpnext = true; + return true; + } + } + + return false; // Bot didn't had to jump(or couldn't) +} + +bool CBot::CheckStrafe() +{ + if (m_iStrafeTime >= lastmillis) + { + SetMoveDir(m_iMoveDir, true); + return true; + } + + if (m_iStrafeCheckDelay >= lastmillis) + return false; + + // Check for near walls + traceresult_s tr; + vec from = m_pMyEnt->o, to, forward, right, up, dir; + float flLeftDist = -1.0f, flRightDist = -1.0f; + bool bStrafe = false; + int iStrafeDir = DIR_NONE; + + AnglesToVectors(GetViewAngles(), forward, right, up); + + // Check for a near left wall + to = from; + dir = right; + vmul(dir, 3.0f); + vsub(to, dir); + TraceLine(from, to, m_pMyEnt, false, &tr); + if (tr.collided) + flLeftDist = GetDistance(from, tr.end); + //debugbeam(m_pMyEnt->o, to); + + // Check for a near right wall + to = from; + dir = right; + vmul(dir, 3.0f); + vadd(to, dir); + TraceLine(from, to, m_pMyEnt, false, &tr); + if (tr.collided) + flRightDist = GetDistance(from, tr.end); + //debugbeam(m_pMyEnt->o, to); + + if ((flLeftDist == -1.0f) && (flRightDist == -1.0f)) + { + dir = right; + vmul(dir, m_pMyEnt->radius); + + // Check left + from = m_pMyEnt->o; + vsub(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) + flLeftDist = -1.0f; + + // Check right + from = m_pMyEnt->o; + vadd(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) + flRightDist = -1.0f; + } + + if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) + { + if (flLeftDist < flRightDist) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist < flLeftDist) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + else + { + // Randomly choose a strafe direction + bStrafe = true; + if (RandomLong(0, 1)) + iStrafeDir = LEFT; + else + iStrafeDir = RIGHT; + } + } + else if (flLeftDist != -1.0f) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist != -1.0f) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + + if (bStrafe) + { + SetMoveDir(iStrafeDir, true); + m_iMoveDir = iStrafeDir; + m_iStrafeTime = lastmillis + RandomLong(75, 150); + } + + return bStrafe; +} + +void CBot::CheckFOV() +{ + m_iCheckEnvDelay = lastmillis + RandomLong(125, 250); + vec MyAngles = GetViewAngles(); + vec src, forward, right, up, dest, best = {0,0,0}; + vec origin = m_pMyEnt->o; + float flDist, flFurthestDist = 0; + bool WallLeft = false; + traceresult_s tr; + + //origin.z -= 1.5; // Slightly under eye level + + // Scan 90 degrees FOV + for(int angle=-45;angle<=45;angle+=5) + { + src = MyAngles; + src.y = WrapYZAngle(src.y + angle); + + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 40); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + //debugbeam(origin, end); + flDist = GetDistance(tr.end); + + if (flFurthestDist < flDist) + { + flFurthestDist = flDist; + best = tr.end; + } + } + + if (best.x && best.y && best.z) + { + AimToVec(best); + // Update MyAngles, since their (going to be) change(d) + MyAngles.x = m_pMyEnt->targetpitch; + MyAngles.y = m_pMyEnt->targetyaw; + } + + float flNearestHitDist = GetDistance(best); + + if (!UnderWater(m_pMyEnt->o) && m_pMyEnt->onfloor) + { + // Check if a near wall is blocking and we can jump over it + if (flNearestHitDist < 4) + { + // Check if the bot can jump over it + src = MyAngles; + src.x = 0; + + AnglesToVectors(src, forward, right, up); + + vec start = origin; + start.z += 2.0f; + dest = start; + vmul(forward, 6); + vadd(dest, forward); + + TraceLine(start, dest, m_pMyEnt, false, &tr); + //debugbeam(start, end); + + if (!tr.collided) + { + // Jump + debugnav("High wall"); + m_pMyEnt->jumpnext = true; + m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); + return; + } + } + else + { + // Check if something is blocking below us, so the bot can jump over it + src = MyAngles; + src.x = 0; + + AnglesToVectors(src, forward, right, up); + + vec start = origin; + start.z -= 1.7f; + dest = start; + vmul(forward, 4); + vadd(dest, forward); + + TraceLine(start, dest, m_pMyEnt, false, &tr); + + // Trace was blocked? + if (tr.collided) + { + //debugbeam(start, end); + + // Jump + debugnav("Low wall"); + m_pMyEnt->jumpnext = true; + m_iStrafeCheckDelay = lastmillis + RandomLong(250, 500); + return; + } + } + + // Check if the bot is going to fall... + src = MyAngles; + src.x = 0.0f; + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 3.0f); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + int cx = fast_f2nat(tr.end.x), cy = fast_f2nat(tr.end.y); + short CubesInWater=0; + for(int x=cx-1;x<=(cx+1);x++) + { + for(int y=cy-1;y<=(cy+1);y++) + { + if (OUTBORD(x, y)) continue; + //sqr *s = S(fast_f2nat(x), fast_f2nat(y)); + //if (!SOLID(s)) + { + vec from = { x, y, m_pMyEnt->o.z }; + dest = from; + dest.z -= 6.0f; + TraceLine(from, dest, m_pMyEnt, false, &tr); + bool turn = false; + if (UnderWater(tr.end)) CubesInWater++; + if (CubesInWater > 2) turn = true; // Always avoid water + if (!tr.collided && RandomLong(0, 1)) + turn = true; // Randomly avoid a fall + + if (turn) + { + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + 180); + m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); + debugnav("Water or a fall in front"); + return; + } + } + } + } + } + + // Is the bot about to head a corner? + if (flNearestHitDist <= 4.0f) + { + src = MyAngles; + src.y = WrapYZAngle(src.y - 45.0f); + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 4.0f); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + WallLeft = (tr.collided); + + src = MyAngles; + src.y += WrapYZAngle(src.y + 45.0f); + AnglesToVectors(src, forward, right, up); + + dest = origin; + vmul(forward, 4.0f); + vadd(dest, forward); + + TraceLine(origin, dest, m_pMyEnt, false, &tr); + + if (WallLeft && tr.collided) + { + // We're about to hit a corner, turn away + debugnav("Corner"); + m_pMyEnt->targetyaw = WrapYZAngle(m_pMyEnt->yaw + RandomFloat(160.0f, 200.0f)); + m_iCheckEnvDelay = m_iStrafeCheckDelay = lastmillis + RandomLong(750, 1500); + return; + } + } +} + +// Called when bot is underwater +bool CBot::WaterNav() +{ + const int iSearchRange = 4; + + if (vis(m_vWaterGoal, g_vecZero)) + { + AddDebugText("WaterNav"); + // Find the nearest and reachable cube which isn't underwater + + int cx = fast_f2nat(m_pMyEnt->o.x); + int cy = fast_f2nat(m_pMyEnt->o.y); + float flNearestDist = 9999.0f, flDist; + + if (OUTBORD(cx, cy)) return false; + + // Check all cubes in range... + for (int x=(cx-iSearchRange);x<=(cx+iSearchRange);x++) + { + for (int y=(cy-iSearchRange);y<=(cy+iSearchRange);y++) + { + sqr *s = S(x, y); + + if (SOLID(s)) continue; + if ((x==cx) && (y==cy)) continue; + + vec v = { x, y, GetCubeFloor(x, y) }; + + if (UnderWater(v)) continue; // Skip, cube is underwater + + if (hdr.waterlevel < (v.z - 2.0f)) continue; // Cube is too high + + // Check if the bot 'can fit' on the cube(no near obstacles) + bool small = false; + for (int a=(x-2);a<=(x+2);a++) + { + if (small) break; + for (int b=(y-2);b<=(y+2);b++) + { + if ((x==a) && (y==b)) continue; + vec v2 = { a, b, GetCubeFloor(a, b) }; + if (v.z < (v2.z-1-JUMP_HEIGHT)) + { + small=true; + break; + } + + if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) + { + if ((v2.z) < (v.z-2.0f)) + { + small = true; + break; + } + } + + traceresult_s tr; + TraceLine(v, v2, NULL, false, &tr); + if (tr.collided) + { + small=true; + break; + } + } + if (small) break; + } + if (small) + { + debugbeam(m_pMyEnt->o, v); + continue; + } + + // Okay, cube is valid. + flDist = GetDistance(v); + if (flDist < flNearestDist) + { + flNearestDist = flDist; + m_vWaterGoal = v; + } + } + } + } + + if (!vis(m_vWaterGoal, g_vecZero)) + { + AddDebugText("WaterNav"); + //debugbeam(m_pMyEnt->o, m_vWaterGoal); + vec aim = m_vWaterGoal; + aim.z += 1.5f; // Aim a bit further up + AimToVec(aim); + if ((RandomLong(1, 100) <= 15) && (Get2DDistance(m_vWaterGoal) <= 7.0f)) + m_pMyEnt->jumpnext = true; + return true; + } + + return false; +} + +bool CBot::CheckItems() +{ + if (!m_pCurrentGoalWaypoint && !CheckJump() && CheckStuck()) + { + // Don't check for ents a while when stuck + m_iCheckEntsDelay = lastmillis + RandomLong(1000, 2000); + return false; + } + + if (vis(m_vGoal, g_vecZero)) + m_pTargetEnt = NULL; + + if (!m_pTargetEnt) + { + if (m_iCheckEntsDelay > lastmillis) + return false; + else + { + m_pTargetEnt = SearchForEnts(!m_classicsp); + m_iCheckEntsDelay = lastmillis + RandomLong(2500, 5000); + } + } + + if (m_pTargetEnt) + { + if (HeadToTargetEnt()) + return true; + } + + if (m_eCurrentBotState == STATE_ENT) + { + ResetWaypointVars(); + m_vGoal = g_vecZero; + m_pTargetEnt = NULL; + } + + return false; +} + +bool CBot::InUnreachableList(entity *e) +{ + TLinkedList::node_s *p = m_UnreachableEnts.GetFirst(); + while(p) + { + if (p->Entry->ent == e) return true; + p = p->next; + } + return false; +} + +bool CBot::IsReachable(vec to, float flMaxHeight) +{ + vec from = m_pMyEnt->o; + traceresult_s tr; + float curr_height, last_height; + + float distance = GetDistance(from, to); + + // is the destination close enough? + //if (distance < REACHABLE_RANGE) + { + if (IsVisible(to)) + { + // Look if bot can 'fit trough' + vec src = from, forward, right, up; + AnglesToVectors(GetViewAngles(), forward, right, up); + + // Trace from 1 cube to the left + vec temp = right; + vmul(temp, 1.0f); + vsub(src, temp); + if (!::IsVisible(src, to)) return false; + + // Trace from 1 cube to the right + vadd(src, temp); + if (!::IsVisible(src, to)) return false; + + if (UnderWater(from) && UnderWater(to)) + { + // No need to worry about heights in water + return true; + } +/* + if (to.z > (from.z + JUMP_HEIGHT)) + { + vec v_new_src = to; + vec v_new_dest = to; + + v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); + + // check if we didn't hit anything, if so then it's in mid-air + if (::IsVisible(v_new_src, v_new_dest, NULL)) + { + condebug("to is in midair"); + debugbeam(from, to); + return false; // can't reach this one + } + } +*/ + + // check if distance to ground increases more than jump height + // at points between from and to... + + vec v_temp = to; vsub(v_temp, from); + vec v_direction = Normalize(v_temp); // 1 unit long + vec v_check = from; + vec v_down = from; + + v_down.z = v_down.z - 100.0f; // straight down + + TraceLine(v_check, v_down, NULL, false, &tr); + + // height from ground + last_height = GetDistance(v_check, tr.end); + + distance = GetDistance(to, v_check); // distance from goal + + while (distance > 2.0f) + { + // move 2 units closer to the goal + v_temp = v_direction; + vmul(v_temp, 2.0f); + vadd(v_check, v_temp); + + v_down = v_check; + v_down.z = v_down.z - 100.0f; + + TraceLine(v_check, v_down, NULL, false, &tr); + + curr_height = GetDistance(v_check, tr.end); + + // is the difference in the last height and the current height + // higher that the jump height? + if ((last_height - curr_height) >= flMaxHeight) + { + // can't get there from here... + //condebug("traces failed to to"); + debugbeam(from, to); + return false; + } + + last_height = curr_height; + + distance = GetDistance(to, v_check); // distance from goal + } + + return true; + } + } + + return false; +} + +void CBot::HearSound(int n, vec *o) +{ + // Has the bot already an enemy? + if (m_pMyEnt->enemy) return; + + // Is the sound not interesting? +//fixmebot +/* if ((n==S_DIE1) || (n==S_DIE2) || (n==S_DEATHR) || (n==S_DEATHE) || (n==S_DEATHS) || + (n==S_DEATHB) || (n==S_DEATHH) || (n==S_DEATHD) || (n==S_PIGGR2)) + return;*/ + + int soundvol = m_pBotSkill->iMaxHearVolume - + (int)(GetDistance(*o)*3*m_pBotSkill->iMaxHearVolume/255); + + if (soundvol == 0) return; + + // Look who made the sound(check for the nearest enemy) + float flDist, flNearestDist = 3.0f; // Range of 3 units + dynent *pNearest = NULL; + + /* edit -argh + if (m_sp) + { + // Check all monsters + loopv(monsters) + { + if (!monsters[i] || monsters[i]->state != CS_ALIVE) continue; + + flDist = GetDistance(*o, monsters[i]->o); + if ((flDist < flNearestDist) && IsVisible(monsters[i])) + { + pNearest = monsters[i]; + flNearestDist = flDist; + } + } + } */ + //else + //{ + // Check all players first + loopv(players) + { + if (!players[i] || (players[i]->state != CS_ALIVE) || + isteam(m_pMyEnt->team, players[i]->team)) + continue; + + flDist = GetDistance(*o, players[i]->o); + if ((flDist < flNearestDist) && IsVisible(players[i])) + { + pNearest = players[i]; + flNearestDist = flDist; + } + } + + // Check all bots + loopv(bots) + { + if (!bots[i] || (bots[i]->state != CS_ALIVE) || + isteam(m_pMyEnt->team, bots[i]->team) || (bots[i] == m_pMyEnt)) + continue; + + flDist = GetDistance(*o, bots[i]->o); + if ((flDist < flNearestDist) && IsVisible(bots[i])) + { + pNearest = bots[i]; + flNearestDist = flDist; + } + } + + // Check local player + if (player1 && (player1->state == CS_ALIVE) && + !isteam(m_pMyEnt->team, player1->team)) + { + flDist = GetDistance(*o, player1->o); + if ((flDist < flNearestDist) && IsVisible(player1)) + { + pNearest = player1; + flNearestDist = flDist; + } + } + //} + + if (pNearest) + { + if (m_pMyEnt->enemy != pNearest) + m_iShootDelay = lastmillis + GetShootDelay(); // Add shoot delay when new enemy found + + m_pMyEnt->enemy = pNearest; + } +} + +bool CBot::IsInFOV(const vec &o) +{ + vec v2, forward, right, up; + float flDot; + + AnglesToVectors(GetViewAngles(), forward, right, up); + + v2 = o; + vsub(v2, m_pMyEnt->o); + v2.z = 0.0f; // Make 2D + v2 = Normalize(v2); + forward.z = 0; // Make 2D + + flDot = dotprod(v2 , forward); + + return(flDot >= 0.5f); // sin2(0.5) == 60 degrees FOV +} + +// Code of CBot - End diff --git a/source/src/bot/bot_util.cpp b/source/src/bot/bot_util.cpp index 3b6058f4..ff802865 100644 --- a/source/src/bot/bot_util.cpp +++ b/source/src/bot/bot_util.cpp @@ -1,565 +1,565 @@ -// -// C++ Implementation: bot_util -// -// Description: Basic util functions, mostly bot related -// -// -// Author: -// -// -// -// - -#include "../cube.h" - -#ifndef __linux__ -#include -#endif -#include -#if (defined __linux__) -#include -#include -#else -#include -#endif - -#include - -extern dvector monsters; - -// Function code by PMB - Begin - - -// Random functions begin -// copied from -// http://forums.bots-united.com/showthread.php?t=244&page=1&pp=10&highlight=random -// So credits to PMB and the person(s) who actually made it :] - -// maximum value returned by our number generator (2^31 - 1 = 0x7FFFFFFF) -#define LRAND_MAX 2147483647L -long lseed; // our random number generator's seed - -long lrand (void) -{ - // this function is the equivalent of the rand() standard C library function, - // except that whereas rand() works only with short integers - // (i.e. not above 32767), this function is able to generate 32-bit random - // numbers. Isn't that nice ? - // credits go to Ray Gardner for his fast implementation of minimal random - // number generators - // http://c.snippets.org/snip_lister.php?fname=rg_rand.c - - // compose the two 16-bit parts of the long integer and assemble them - static unsigned long lrand_lo, lrand_hi; - lrand_lo = 16807 * (long) (lseed & 0xFFFF); // low part - lrand_hi = 16807 * (long) ((unsigned long) lseed >> 16); - // high part - lrand_lo += (lrand_hi & 0x7FFF) << 16; - // assemble both in lrand_lo - // is the resulting number greater than LRAND_MAX (half the capacity) ? - if (lrand_lo > LRAND_MAX) - { - lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit - lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) - } - - lrand_lo += lrand_hi >> 15; - // now do twisted maths to generate the next seed - // is the resulting number greater than LRAND_MAX (half the capacity) ? - if (lrand_lo > LRAND_MAX) - { - lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit - lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) - } - // now we've got our (pseudo-)random number. - lseed = (long) lrand_lo; // put it in the seed for next time - return (lseed); // and return it. Yeah, simple as that. -} - -void lsrand (unsigned long initial_seed) -{ - // this function initializes the random seed based on the initial seed value - // passed in the initial_seed parameter. Since random seeds are - // usually initialized with the time of day, and time is a value that - // changes slowly, we bump the seed twice to have it in a more - // random state for future calls of the random number generator. - - // fill in the initial seed of the random number generator - lseed = (long) initial_seed; - lseed = lrand (); // bump it once - lseed = lrand (); // bump it twice - return; // that's all folks -} - -long RandomLong (long from, long to) -{ - // this function returns a random integer number between (and including) - // the starting and ending values passed by parameters from and to. - if (to <= from) - return (from); - - return (from + lrand () / (LRAND_MAX / (to - from + 1))); -} - -float RandomFloat (float from, float to) -{ - // this function returns a random floating-point number between (and including) - // the starting and ending values passed by parameters from and to. - - if (to <= from) - return (from); - - return (from + (float) lrand () / (LRAND_MAX / (to - from))); -} - -// End random functions - -void AnglesToVectors(vec angles, vec &forward, vec &right, vec &up) -{ - static float degrees_to_radians = 2 * PI / 360; - float angle; - float sin_pitch; - float sin_yaw; - float sin_roll; - float cos_pitch; - float cos_yaw; - float cos_roll; - - // For some reason this has to be done :) - // Me = math n00b - angles.x = -angles.x; - angles.y -= 90; - - // compute the sin and cosine of the pitch component - angle = angles.x * degrees_to_radians; - sin_pitch = sinf (angle); - cos_pitch = cosf (angle); - - // compute the sin and cosine of the yaw component - angle = angles.y * degrees_to_radians; - sin_yaw = sinf (angle); - cos_yaw = cosf (angle); - - // compute the sin and cosine of the roll component - angle = angles.z * degrees_to_radians; - sin_roll = sinf (angle); - cos_roll = cosf (angle); - - // build the FORWARD vector - forward.x = cos_pitch * cos_yaw; - forward.y = cos_pitch * sin_yaw; - forward.z = -sin_pitch; - - // build the RIGHT vector - right.x = -(-(sin_roll * sin_pitch * cos_yaw) - (cos_roll * -sin_yaw)); - right.y = -(-(sin_roll * sin_pitch * sin_yaw) - (cos_roll * cos_yaw)); - right.z = -(sin_roll * cos_pitch); - - // build the UPWARDS vector - up.x = ((cos_roll * sin_pitch * cos_yaw) - (sin_roll * -sin_yaw)); - up.y = ((cos_roll * sin_pitch * sin_yaw) - (sin_roll * cos_yaw)); - up.z = cos_roll * cos_pitch; - - return; -} - -// Function code by PMB - End - -float WrapXAngle(float angle) -{ - if (angle > 90) angle = 90; - else if (angle < -90) angle = -90; - - return angle; -} - -float WrapYZAngle(float angle) -{ - while (angle >= 360.0f) angle -= 360.0f; - while (angle < 0.0f) angle += 360.0f; - - return angle; -} - -// Sees how far it can come from 'from' to 'to' and puts the end vector in 'end' -// (1337 description :-) -// UNDONE: Optimize this as much as possible -void TraceLine(vec from, vec to, dynent *pTracer, bool CheckPlayers, traceresult_s *tr, - bool SkipTags) -{ - tr->end = from; - tr->collided = false; - - static float flNearestDist, flDist; - static vec v; - static bool solid; - - flNearestDist = 9999.0f; - - if(OUTBORD((int)from.x, (int)from.y)) return; - - // Check if the 'line' collides with entities like mapmodels - loopv(ents) - { - entity &e = ents[i]; - if(e.type!=MAPMODEL) continue; // Only check map models for now - - mapmodelinfo &mmi = getmminfo(e.attr2); - if(!&mmi || !mmi.h) continue; - - float lo = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3); - float hi = lo+mmi.h; - float z = (fabs(from.z-lo) < fabs(from.z-hi)) ? lo : hi; - makevec(&v, e.x, e.y, z); - flDist = GetDistance(from, v); - - if ((flDist < flNearestDist) && (intersect(&e, from, to, &tr->end))) - { - flNearestDist = GetDistance(from, tr->end); - tr->collided = true; - } - } - - if (CheckPlayers) - { - // Check if the 'line' collides with players - loopv(players) - { - dynent *d = players[i]; - if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid players - - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - - // Check if the 'line' collides with bots - loopv(bots) - { - dynent *d = bots[i]; - if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid bots - - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - - // Check if the 'line' collides with monsters - /* we don't have monsters anymore, thanks for the bots :) -argh - dvector &mv = getmonsters(); - loopv(mv) - { - dynent *d = mv[i]; - if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid monsters - - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - */ - - - // Check if the 'line' collides with the local player(player1) - dynent *d = player1; // Shortcut - if (d && (d!=pTracer) && !BotManager.m_pBotToView && (d->state == CS_ALIVE)) - { - flDist = GetDistance(from, d->o); - - if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) - { - flNearestDist = flDist; - tr->collided = true; - } - } - } - - - float dx = to.x-from.x; - float dy = to.y-from.y; - int steps = (int)(sqrt(dx*dx+dy*dy)/0.9); - if(!steps) // If from and to are on the same cube... - { - if (GetDistance(from, to) < flNearestDist) - { - tr->end = to; - sqr *s = S(fast_f2nat(from.x), fast_f2nat(from.y)); - float flr = GetCubeFloor(fast_f2nat(from.x), fast_f2nat(from.y)); - solid = ((!SkipTags || !s->tag) && SOLID(s)); - if (solid || (to.z < flr) || (to.z > s->ceil)) - { - tr->collided = true; - tr->end.z = (fabs(to.z-s->ceil) < fabs(to.z-flr)) ? s->ceil : flr; - } - } - return; - } - - float x = from.x; - float y = from.y; - int i = 0; - vec endcube = from; - - // Now check if the 'line' is hit by a cube - while(i= flNearestDist) break; - sqr *s = S(fast_f2nat(x), fast_f2nat(y)); - solid = ((!SkipTags || !s->tag) && SOLID(s)); - if(solid) break; - float floor = s->floor; - if(s->type==FHF) floor -= s->vdelta/4.0f; - float ceil = s->ceil; - if(s->type==CHF) ceil += s->vdelta/4.0f; - float rz = from.z-((from.z-to.z)*(i/(float)steps)); - if(rzceil) break; - - endcube.x = x; - endcube.y = y; - endcube.z = rz; - x += dx/(float)steps; - y += dy/(float)steps; - i++; - } - - if ((i>=steps) && !tr->collided) - { - tr->end = to; - } - else - { - tr->collided = true; - if (GetDistance(from, endcube) < flNearestDist) - tr->end = endcube; - } - - return; -} - -float GetDistance(vec v1, vec v2) -{ - if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0) && (v1.z == 0) && - (v2.z == 0)) - return 0.0f; - - vdist(flDist, vecto, v1, v2); - return flDist; -} - -// As GetDistance(), but doesn take height in account. -float Get2DDistance(vec v1, vec v2) -{ - if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0)) - return 0.0f; - - v1.z = v2.z = 0.0f; - vdist(flDist, vecto, v1, v2); - return flDist; -} - -bool IsVisible(vec v1, vec v2, dynent *tracer, bool SkipTags) -{ - traceresult_s tr; - TraceLine(v1, v2, tracer, (tracer!=NULL), &tr, SkipTags); - return !tr.collided; -} - -// Prediction: -// - pos: Current position -// - vel: Current velocity -// - Time: In seconds, predict how far it is in Time seconds -vec PredictPos(vec pos, vec vel, float Time) -{ - float flVelLength = sqrt(vel.x*vel.x + vel.y*vel.y + vel.z*vel.z); - - if (flVelLength <= 1.0) - return pos; // don't bother with low velocities... - - float speed = flVelLength * Time; - float flTemp = 1/flVelLength; - vec vecNormalize; - - vecNormalize.x = vel.x * flTemp; - vecNormalize.y = vel.y * flTemp; - vecNormalize.z = vel.z * flTemp; - - vec v_src = pos; - vmul(vecNormalize, speed); - vec v_end = v_src; - vadd(v_end, vecNormalize); - - return v_end; -} - -// returns true if the given file is valid -bool IsValidFile(const char *szFileName) -{ - FILE *fp = fopen(szFileName, "r"); - - if (fp) - { - fclose(fp); - return true; - } - - return false; -} - -bool FileIsOlder(const char *szFileName1, const char *szFileName2) -{ - int file1, file2; - struct stat stat1, stat2; - - file1 = open(szFileName1, O_RDONLY); - file2 = open(szFileName2, O_RDONLY); - - fstat(file1, &stat1); - fstat(file2, &stat2); - - close(file1); - close(file2); - - return(stat1.st_mtime < stat2.st_mtime); -} - -vec Normalize(vec v) -{ - float flLen = sqrt(v.x*v.x + v.y*v.y + v.z*v.z); - static vec v_norm = g_vecZero; - if (flLen == 0) - { - v_norm.x=0;v_norm.y=0;v_norm.z=1; - } - else - { - flLen = 1 / flLen; - v_norm.x = v.x * flLen; v_norm.y = v.y * flLen; v_norm.z = v.z * flLen; - } - return v_norm; -} - -float GetYawDiff(float curyaw, vec v1, vec v2) -{ - float yaw = -(float)atan2(v2.x - v1.x, v2.y - v1.y)/PI*180+180; - return yaw-curyaw; -} - -vec CrossProduct(const vec &a, const vec &b) -{ - vec cross = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; - return cross; -} - -int GetDirection(const vec &angles, const vec &v1, const vec &v2) -{ - vec tmp, forward, right, up, cross; - float flDot; - - AnglesToVectors(angles, forward, right, up); - - tmp = v2; - vsub(tmp, v1); - tmp.z = 0.0f; // Make 2D - tmp = Normalize(tmp); - forward.z = 0; // Make 2D - - flDot = dotprod(tmp , forward); - cross = CrossProduct(tmp, forward); - - int Dir = DIR_NONE; - - if (cross.z > 0.1f) Dir |= LEFT; - else if (cross.z < -0.1f) Dir |= RIGHT; - if (flDot < -0.1f) Dir |= BACKWARD; - else if (flDot > 0.1f) Dir |= FORWARD; - - if (v1.z > v2.z) Dir |= DOWN; - if (v1.z < v2.z) Dir |= UP; - - return Dir; -} - -float GetCubeFloor(int x, int y) -{ - sqr *s = S(x, y); - float floor = s->floor; - if (s->type == FHF) - floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - return floor; -} - -float GetCubeHeight(int x, int y) -{ - sqr *s = S(x, y); - float ceil = s->ceil; - if(s->type == CHF) - ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; - return ceil; -} - -char *SkillNrToSkillName(short skillnr) -{ - if (skillnr == 0) return "best"; - else if (skillnr == 1) return "good"; - else if (skillnr == 2) return "medium"; - else if (skillnr == 3) return "worse"; - else if (skillnr == 4) return "bad"; - - return NULL; -} - -bool IsInGame(dynent *d) -{ - if (!d) return false; - - /*edit -argh - if (d->monsterstate) - { - loopv(monsters) - { - if (monsters[i] == d) - return true; - } - } - */ - //else - if (d->bIsBot) - { - loopv(bots) - { - if (bots[i] == d) - return true; - } - } - else - { - if (d == player1) - return true; - else - { - loopv(players) - { -#ifdef VANILLA_CUBE - if (!players[i] || (players[i]->state == CS_DEDHOST)) continue; -#elif defined AC_CUBE - if (!players[i]) continue; -#endif - if (players[i] == d) - return true; - } - } - } - return false; -} +// +// C++ Implementation: bot_util +// +// Description: Basic util functions, mostly bot related +// +// +// Author: +// +// +// +// + +#include "../cube.h" + +#ifndef __linux__ +#include +#endif +#include +#if (defined __linux__) +#include +#include +#else +#include +#endif + +#include + +extern dvector monsters; + +// Function code by PMB - Begin + + +// Random functions begin +// copied from +// http://forums.bots-united.com/showthread.php?t=244&page=1&pp=10&highlight=random +// So credits to PMB and the person(s) who actually made it :] + +// maximum value returned by our number generator (2^31 - 1 = 0x7FFFFFFF) +#define LRAND_MAX 2147483647L +long lseed; // our random number generator's seed + +long lrand (void) +{ + // this function is the equivalent of the rand() standard C library function, + // except that whereas rand() works only with short integers + // (i.e. not above 32767), this function is able to generate 32-bit random + // numbers. Isn't that nice ? + // credits go to Ray Gardner for his fast implementation of minimal random + // number generators + // http://c.snippets.org/snip_lister.php?fname=rg_rand.c + + // compose the two 16-bit parts of the long integer and assemble them + static unsigned long lrand_lo, lrand_hi; + lrand_lo = 16807 * (long) (lseed & 0xFFFF); // low part + lrand_hi = 16807 * (long) ((unsigned long) lseed >> 16); + // high part + lrand_lo += (lrand_hi & 0x7FFF) << 16; + // assemble both in lrand_lo + // is the resulting number greater than LRAND_MAX (half the capacity) ? + if (lrand_lo > LRAND_MAX) + { + lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit + lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) + } + + lrand_lo += lrand_hi >> 15; + // now do twisted maths to generate the next seed + // is the resulting number greater than LRAND_MAX (half the capacity) ? + if (lrand_lo > LRAND_MAX) + { + lrand_lo &= LRAND_MAX; // then get rid of the disturbing bit + lrand_lo++; // and increase it a bit (to avoid overflow problems, I suppose) + } + // now we've got our (pseudo-)random number. + lseed = (long) lrand_lo; // put it in the seed for next time + return (lseed); // and return it. Yeah, simple as that. +} + +void lsrand (unsigned long initial_seed) +{ + // this function initializes the random seed based on the initial seed value + // passed in the initial_seed parameter. Since random seeds are + // usually initialized with the time of day, and time is a value that + // changes slowly, we bump the seed twice to have it in a more + // random state for future calls of the random number generator. + + // fill in the initial seed of the random number generator + lseed = (long) initial_seed; + lseed = lrand (); // bump it once + lseed = lrand (); // bump it twice + return; // that's all folks +} + +long RandomLong (long from, long to) +{ + // this function returns a random integer number between (and including) + // the starting and ending values passed by parameters from and to. + if (to <= from) + return (from); + + return (from + lrand () / (LRAND_MAX / (to - from + 1))); +} + +float RandomFloat (float from, float to) +{ + // this function returns a random floating-point number between (and including) + // the starting and ending values passed by parameters from and to. + + if (to <= from) + return (from); + + return (from + (float) lrand () / (LRAND_MAX / (to - from))); +} + +// End random functions + +void AnglesToVectors(vec angles, vec &forward, vec &right, vec &up) +{ + static float degrees_to_radians = 2 * PI / 360; + float angle; + float sin_pitch; + float sin_yaw; + float sin_roll; + float cos_pitch; + float cos_yaw; + float cos_roll; + + // For some reason this has to be done :) + // Me = math n00b + angles.x = -angles.x; + angles.y -= 90; + + // compute the sin and cosine of the pitch component + angle = angles.x * degrees_to_radians; + sin_pitch = sinf (angle); + cos_pitch = cosf (angle); + + // compute the sin and cosine of the yaw component + angle = angles.y * degrees_to_radians; + sin_yaw = sinf (angle); + cos_yaw = cosf (angle); + + // compute the sin and cosine of the roll component + angle = angles.z * degrees_to_radians; + sin_roll = sinf (angle); + cos_roll = cosf (angle); + + // build the FORWARD vector + forward.x = cos_pitch * cos_yaw; + forward.y = cos_pitch * sin_yaw; + forward.z = -sin_pitch; + + // build the RIGHT vector + right.x = -(-(sin_roll * sin_pitch * cos_yaw) - (cos_roll * -sin_yaw)); + right.y = -(-(sin_roll * sin_pitch * sin_yaw) - (cos_roll * cos_yaw)); + right.z = -(sin_roll * cos_pitch); + + // build the UPWARDS vector + up.x = ((cos_roll * sin_pitch * cos_yaw) - (sin_roll * -sin_yaw)); + up.y = ((cos_roll * sin_pitch * sin_yaw) - (sin_roll * cos_yaw)); + up.z = cos_roll * cos_pitch; + + return; +} + +// Function code by PMB - End + +float WrapXAngle(float angle) +{ + if (angle > 90) angle = 90; + else if (angle < -90) angle = -90; + + return angle; +} + +float WrapYZAngle(float angle) +{ + while (angle >= 360.0f) angle -= 360.0f; + while (angle < 0.0f) angle += 360.0f; + + return angle; +} + +// Sees how far it can come from 'from' to 'to' and puts the end vector in 'end' +// (1337 description :-) +// UNDONE: Optimize this as much as possible +void TraceLine(vec from, vec to, dynent *pTracer, bool CheckPlayers, traceresult_s *tr, + bool SkipTags) +{ + tr->end = from; + tr->collided = false; + + static float flNearestDist, flDist; + static vec v; + static bool solid; + + flNearestDist = 9999.0f; + + if(OUTBORD((int)from.x, (int)from.y)) return; + + // Check if the 'line' collides with entities like mapmodels + loopv(ents) + { + entity &e = ents[i]; + if(e.type!=MAPMODEL) continue; // Only check map models for now + + mapmodelinfo &mmi = getmminfo(e.attr2); + if(!&mmi || !mmi.h) continue; + + float lo = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3); + float hi = lo+mmi.h; + float z = (fabs(from.z-lo) < fabs(from.z-hi)) ? lo : hi; + makevec(&v, e.x, e.y, z); + flDist = GetDistance(from, v); + + if ((flDist < flNearestDist) && (intersect(&e, from, to, &tr->end))) + { + flNearestDist = GetDistance(from, tr->end); + tr->collided = true; + } + } + + if (CheckPlayers) + { + // Check if the 'line' collides with players + loopv(players) + { + dynent *d = players[i]; + if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid players + + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + + // Check if the 'line' collides with bots + loopv(bots) + { + dynent *d = bots[i]; + if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid bots + + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + + // Check if the 'line' collides with monsters + /* we don't have monsters anymore, thanks for the bots :) -argh + dvector &mv = getmonsters(); + loopv(mv) + { + dynent *d = mv[i]; + if(!d || (d==pTracer) || (d->state != CS_ALIVE)) continue; // Only check valid monsters + + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + */ + + + // Check if the 'line' collides with the local player(player1) + dynent *d = player1; // Shortcut + if (d && (d!=pTracer) && !BotManager.m_pBotToView && (d->state == CS_ALIVE)) + { + flDist = GetDistance(from, d->o); + + if ((flDist < flNearestDist) && (intersect(d, from, to, &tr->end))) + { + flNearestDist = flDist; + tr->collided = true; + } + } + } + + + float dx = to.x-from.x; + float dy = to.y-from.y; + int steps = (int)(sqrt(dx*dx+dy*dy)/0.9); + if(!steps) // If from and to are on the same cube... + { + if (GetDistance(from, to) < flNearestDist) + { + tr->end = to; + sqr *s = S(fast_f2nat(from.x), fast_f2nat(from.y)); + float flr = GetCubeFloor(fast_f2nat(from.x), fast_f2nat(from.y)); + solid = ((!SkipTags || !s->tag) && SOLID(s)); + if (solid || (to.z < flr) || (to.z > s->ceil)) + { + tr->collided = true; + tr->end.z = (fabs(to.z-s->ceil) < fabs(to.z-flr)) ? s->ceil : flr; + } + } + return; + } + + float x = from.x; + float y = from.y; + int i = 0; + vec endcube = from; + + // Now check if the 'line' is hit by a cube + while(i= flNearestDist) break; + sqr *s = S(fast_f2nat(x), fast_f2nat(y)); + solid = ((!SkipTags || !s->tag) && SOLID(s)); + if(solid) break; + float floor = s->floor; + if(s->type==FHF) floor -= s->vdelta/4.0f; + float ceil = s->ceil; + if(s->type==CHF) ceil += s->vdelta/4.0f; + float rz = from.z-((from.z-to.z)*(i/(float)steps)); + if(rzceil) break; + + endcube.x = x; + endcube.y = y; + endcube.z = rz; + x += dx/(float)steps; + y += dy/(float)steps; + i++; + } + + if ((i>=steps) && !tr->collided) + { + tr->end = to; + } + else + { + tr->collided = true; + if (GetDistance(from, endcube) < flNearestDist) + tr->end = endcube; + } + + return; +} + +float GetDistance(vec v1, vec v2) +{ + if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0) && (v1.z == 0) && + (v2.z == 0)) + return 0.0f; + + vdist(flDist, vecto, v1, v2); + return flDist; +} + +// As GetDistance(), but doesn take height in account. +float Get2DDistance(vec v1, vec v2) +{ + if ((v1.x == 0) && (v2.x == 0) && (v1.y == 0) && (v2.y == 0)) + return 0.0f; + + v1.z = v2.z = 0.0f; + vdist(flDist, vecto, v1, v2); + return flDist; +} + +bool IsVisible(vec v1, vec v2, dynent *tracer, bool SkipTags) +{ + traceresult_s tr; + TraceLine(v1, v2, tracer, (tracer!=NULL), &tr, SkipTags); + return !tr.collided; +} + +// Prediction: +// - pos: Current position +// - vel: Current velocity +// - Time: In seconds, predict how far it is in Time seconds +vec PredictPos(vec pos, vec vel, float Time) +{ + float flVelLength = sqrt(vel.x*vel.x + vel.y*vel.y + vel.z*vel.z); + + if (flVelLength <= 1.0) + return pos; // don't bother with low velocities... + + float speed = flVelLength * Time; + float flTemp = 1/flVelLength; + vec vecNormalize; + + vecNormalize.x = vel.x * flTemp; + vecNormalize.y = vel.y * flTemp; + vecNormalize.z = vel.z * flTemp; + + vec v_src = pos; + vmul(vecNormalize, speed); + vec v_end = v_src; + vadd(v_end, vecNormalize); + + return v_end; +} + +// returns true if the given file is valid +bool IsValidFile(const char *szFileName) +{ + FILE *fp = fopen(szFileName, "r"); + + if (fp) + { + fclose(fp); + return true; + } + + return false; +} + +bool FileIsOlder(const char *szFileName1, const char *szFileName2) +{ + int file1, file2; + struct stat stat1, stat2; + + file1 = open(szFileName1, O_RDONLY); + file2 = open(szFileName2, O_RDONLY); + + fstat(file1, &stat1); + fstat(file2, &stat2); + + close(file1); + close(file2); + + return(stat1.st_mtime < stat2.st_mtime); +} + +vec Normalize(vec v) +{ + float flLen = sqrt(v.x*v.x + v.y*v.y + v.z*v.z); + static vec v_norm = g_vecZero; + if (flLen == 0) + { + v_norm.x=0;v_norm.y=0;v_norm.z=1; + } + else + { + flLen = 1 / flLen; + v_norm.x = v.x * flLen; v_norm.y = v.y * flLen; v_norm.z = v.z * flLen; + } + return v_norm; +} + +float GetYawDiff(float curyaw, vec v1, vec v2) +{ + float yaw = -(float)atan2(v2.x - v1.x, v2.y - v1.y)/PI*180+180; + return yaw-curyaw; +} + +vec CrossProduct(const vec &a, const vec &b) +{ + vec cross = { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; + return cross; +} + +int GetDirection(const vec &angles, const vec &v1, const vec &v2) +{ + vec tmp, forward, right, up, cross; + float flDot; + + AnglesToVectors(angles, forward, right, up); + + tmp = v2; + vsub(tmp, v1); + tmp.z = 0.0f; // Make 2D + tmp = Normalize(tmp); + forward.z = 0; // Make 2D + + flDot = dotprod(tmp , forward); + cross = CrossProduct(tmp, forward); + + int Dir = DIR_NONE; + + if (cross.z > 0.1f) Dir |= LEFT; + else if (cross.z < -0.1f) Dir |= RIGHT; + if (flDot < -0.1f) Dir |= BACKWARD; + else if (flDot > 0.1f) Dir |= FORWARD; + + if (v1.z > v2.z) Dir |= DOWN; + if (v1.z < v2.z) Dir |= UP; + + return Dir; +} + +float GetCubeFloor(int x, int y) +{ + sqr *s = S(x, y); + float floor = s->floor; + if (s->type == FHF) + floor -= (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + return floor; +} + +float GetCubeHeight(int x, int y) +{ + sqr *s = S(x, y); + float ceil = s->ceil; + if(s->type == CHF) + ceil += (s->vdelta+S(x+1,y)->vdelta+S(x,y+1)->vdelta+S(x+1,y+1)->vdelta)/16.0f; + return ceil; +} + +char *SkillNrToSkillName(short skillnr) +{ + if (skillnr == 0) return "best"; + else if (skillnr == 1) return "good"; + else if (skillnr == 2) return "medium"; + else if (skillnr == 3) return "worse"; + else if (skillnr == 4) return "bad"; + + return NULL; +} + +bool IsInGame(dynent *d) +{ + if (!d) return false; + + /*edit -argh + if (d->monsterstate) + { + loopv(monsters) + { + if (monsters[i] == d) + return true; + } + } + */ + //else + if (d->bIsBot) + { + loopv(bots) + { + if (bots[i] == d) + return true; + } + } + else + { + if (d == player1) + return true; + else + { + loopv(players) + { +#ifdef VANILLA_CUBE + if (!players[i] || (players[i]->state == CS_DEDHOST)) continue; +#elif defined AC_CUBE + if (!players[i]) continue; +#endif + if (players[i] == d) + return true; + } + } + } + return false; +} diff --git a/source/src/bot/bot_util.h b/source/src/bot/bot_util.h index 1b9d4dd5..7765dd55 100644 --- a/source/src/bot/bot_util.h +++ b/source/src/bot/bot_util.h @@ -1,565 +1,565 @@ -#ifndef BOT_UTIL_H -#define BOT_UTIL_H - -// Structure for storing traceresults -struct traceresult_s -{ - vec end; - bool collided; -}; - -long RandomLong(long from, long to); -float RandomFloat(float from, float to); -void lsrand(unsigned long initial_seed); - -void AnglesToVectors(vec angles, vec &forward, vec &right, vec &up); -float WrapXAngle(float angle); -float WrapYZAngle(float angle); -void TraceLine(vec from, vec to, dynent *pTracer, bool CheckPlayers, traceresult_s *tr, - bool SkipTags=false); -float GetDistance(vec v1, vec v2); -float Get2DDistance(vec v1, vec v2); -bool IsVisible(vec v1, vec v2, dynent *tracer = NULL, bool SkipTags=false); -bool IsValidFile(const char *szFileName); -bool FileIsOlder(const char *szFileName1, const char *szFileName2); -vec PredictPos(vec pos, vec vel, float Time); -vec Normalize(vec v); -inline void makevec(vec *v, float x, float y, float z) { v->x=x; v->y=y; v->z=z; }; -inline bool UnderWater(const vec &o) { return hdr.waterlevel>o.z-0.5f; }; -inline bool InWater(const vec &o) { return hdr.waterlevel>=o.z; }; -float GetYawDiff(float curyaw, vec v1, vec v2); -vec CrossProduct(const vec &a, const vec &b); -int GetDirection(const vec &angles, const vec &v1, const vec &v2); -float GetCubeFloor(int x, int y); -float GetCubeHeight(int x, int y); -char *SkillNrToSkillName(short skillnr); -bool IsInGame(dynent *d); - -// ================================================================== -// Code of TLinkedList - Start -// ================================================================== - -template class TLinkedList -{ -public: - struct node_s - { - C Entry; - node_s *next; - node_s *prev; - - node_s(void) : next(NULL), prev(NULL) - { - //memset(&Entry, 0, sizeof(Entry)); - }; - }; - - // member functions - - void AddNode(C entry) - { - if (!pNodeList) - { - pNodeList = new node_s; - pNodeList->Entry = entry; - pNodeList->next = NULL; - pNodeList->prev = NULL; - pLastNode = pNodeList; - iNodeCount = 1; - } - else - { - pLastNode->next = new node_s; - pLastNode->next->prev = pLastNode; - pLastNode = pLastNode->next; - pLastNode->Entry = entry; - pLastNode->next = NULL; - iNodeCount++; - } - } - - void PushNode(C Entry) - { - if (!pNodeList) - { - pNodeList = new node_s; - pNodeList->Entry = Entry; - pNodeList->next = NULL; - pNodeList->prev = NULL; - pLastNode = pNodeList; - iNodeCount = 1; - } - else - { - node_s *pNew = new node_s; - pNew->Entry = Entry; - pNew->prev = NULL; - pNew->next = pNodeList; - pNodeList->prev = pNew; - pNodeList = pNew; - iNodeCount++; - } - } - - void DeleteEntry(C Entry) - { - node_s *pNode = pNodeList; - if (!pNode) - return; - - if (pNode->Entry == Entry) // first node - { - if (pNodeList == pLastNode) - pLastNode = NULL; - - pNodeList = pNodeList->next; - if (pNodeList) - pNodeList->prev = NULL; - pNode->next = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - return; - } - - if (Entry == pLastNode->Entry) // last node - { - pNode = pLastNode; - pLastNode = pLastNode->prev; - pLastNode->next = NULL; - - pNode->next = NULL; - pNode->prev = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - return; - } - - // node is somewhere in the middle - pNode = SearchNode(Entry); - - if (!pNode) - return; - - node_s *pPrevNode = pNode->prev; - - if (!pPrevNode) - return; - - // unlink pNode - pNode->next->prev = pPrevNode; - pPrevNode->next = pNode->next; - - pNode->next = NULL; - pNode->prev = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - } - - void DeleteNode(node_s *pNode) - { - if (!pNode) - return; - - if (pNodeList == pNode) // first node - { - if (pNodeList == pLastNode) - pLastNode = pNodeList->next; - - pNodeList = pNodeList->next; - if (pNodeList) - pNodeList->prev = NULL; - pNode->next = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - return; - } - - if (pNode == pLastNode) // last node - { - pNode = pLastNode; - pLastNode = pLastNode->prev; - pLastNode->next = NULL; - - pNode->next = NULL; - pNode->prev = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - return; - } - - // node is somewhere in the middle - - node_s *pPrevNode = pNode->prev; - - if (!pPrevNode) - return; - - // unlink pNode - pNode->next->prev = pPrevNode; - pPrevNode->next = pNode->next; - - pNode->next = NULL; - pNode->prev = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - } - - void DeleteAllNodes(void) - { - node_s *pNode = pNodeList; - node_s *pTemp; - while (pNode != NULL) - { - pTemp = pNode; - pNode = pNode->next; - pTemp->next = NULL; - pTemp->prev = NULL; - delete pTemp; - } - pNodeList = pLastNode = NULL; - iNodeCount = 0; - } - - void Reset(void) // Special case, doesn't delete existing nodes - { - pNodeList = pLastNode = NULL; - iNodeCount = 0; - } - - node_s *SearchNode(C Entry) - { - node_s *pNode = pNodeList; - while(pNode) - { - if (pNode->Entry == Entry) - return pNode; - pNode = pNode->next; - } - return NULL; - } - - C Pop(void) - { - if (!pNodeList) - return static_cast(NULL); - - C Entry = pNodeList->Entry; - DeleteNode(pNodeList); - return Entry; - } - - node_s *GetFirst(void) { return pNodeList; }; - node_s *GetLast(void) { return pLastNode; }; - - void EditEntry(C OrigEntry, C NewVal) - { - node_s *pNode = SearchNode(OrigEntry); - - if (pNode) - pNode->Entry = NewVal; - } - - bool IsInList(C Entry) - { - return (SearchNode(Entry) != NULL); - } - - bool Empty(void) { return (pNodeList == NULL); }; - int NodeCount(void) { return iNodeCount; }; - - - // construction/destruction - TLinkedList(void) - { - pNodeList = NULL; - pLastNode = NULL; - iNodeCount = 0; - }; - - ~TLinkedList(void) - { - DeleteAllNodes(); - }; - -private: - node_s *pNodeList; - node_s *pLastNode; - int iNodeCount; -}; - -// ================================================================== -// Code of TLinkedList - End -// ================================================================== - -// ================================================================== -// Code of TPriorList - Begin -// ================================================================== - -template class TPriorList -{ -public: - struct node_s - { - C Entry; - D Priority; - node_s *next; - - node_s(C Ent, D Prior) : Entry(Ent), Priority(Prior), next(NULL) {}; - }; - - TPriorList(void) : pHeadNode(NULL), pLastNode(NULL) {}; - ~TPriorList(void) { Clear(); }; - - void AddEntry(C Entry, D Prior) - { - if (!pHeadNode) - { - pHeadNode = new node_s(Entry, Prior); - pLastNode = pHeadNode; - iNodeCount=1; - } - else - { - iNodeCount++; - node_s *pNew = new node_s(Entry, Prior); - node_s *pNode = pHeadNode; - node_s *pPrev = NULL; - - while(pNode) - { - if (Prior < pNode->Priority) - { - if (!pPrev) - { - pNew->next = pNode; - pHeadNode = pNew; - } - else - { - pPrev->next = pNew; - pNew->next = pNode; - } - break; - } - pPrev = pNode; - pNode = pNode->next; - } - - if (!pNode) - { - pLastNode = pNew; - if (pPrev) - pPrev->next = pNew; - } - } - } - - C Pop(void) - { - if (!pHeadNode) - return static_cast(NULL); - - C Entry = pHeadNode->Entry; - DeleteNode(pHeadNode); - return Entry; - } - - void Clear(void) - { - node_s *pTemp; - while(pHeadNode) - { - pTemp = pHeadNode; - pHeadNode = pHeadNode->next; - delete pTemp; - } - - pHeadNode = pLastNode = NULL; - } - - bool IsInList(C Entry, D Prior) - { - node_s *pNode = pHeadNode; - while(pNode) - { - if (pNode->Entry == Entry) - return true; - - if (Prior < pNode->Priority) - return false; - - pNode = pNode->next; - } - - return false; - } - - bool Empty(void) { return (pHeadNode == NULL); } - node_s *GetFirst(void) { return pHeadNode; } -private: - node_s *pHeadNode; - node_s *pLastNode; - int iNodeCount; - - node_s *GetPrevNode(node_s *pNode) - { - node_s *pTemp = pHeadNode; - while(pTemp) - { - if (pTemp->next == pNode) - return pTemp; - pTemp = pTemp->next; - } - - return NULL; - } - - void DeleteNode(node_s *pNode) - { - if (!pNode) - return; - - if (pHeadNode == pNode) // first node - { - if (pHeadNode == pLastNode) - pLastNode = pHeadNode->next; - - pHeadNode = pHeadNode->next; - pNode->next = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - return; - } - - if (pNode == pLastNode) // last node - { - pNode = pLastNode; - pLastNode->next = NULL; - - pNode->next = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - return; - } - - // node is somewhere in the middle - - node_s *pPrevNode = GetPrevNode(pNode); - if (!pPrevNode) - return; - - // unlink pNode - pPrevNode->next = pNode->next; - pNode->next = NULL; - delete pNode; - pNode = NULL; - iNodeCount--; - } -}; - -// ================================================================== -// Code of TPriorList - End -// ================================================================== - - -// ================================================================== -// Code of TMultiChoice - Begin -// ================================================================== - -template class TMultiChoice -{ - struct SMultiChoice - { - int MinVal; - int MaxVal; - C Choice; - SMultiChoice(void) : MinVal(0), MaxVal(0){}; - }; - - int TotalVal; - TLinkedList *pChoiceList; - -public: - TMultiChoice(void) : TotalVal(0) // Constructor - { - pChoiceList = new TLinkedList; - }; - - ~TMultiChoice(void) // Destructor - { - while(pChoiceList->Empty() == false) - { - SMultiChoice *pEntry = pChoiceList->Pop(); - if (pEntry) - delete pEntry; - else - break; - } - delete pChoiceList; - pChoiceList = NULL; - } - - void Insert(C Choice, short Percent = 50) - { - if (Percent == 0) - return; - - SMultiChoice *pChoiceEntry = new SMultiChoice; - - pChoiceEntry->MinVal = TotalVal; - pChoiceEntry->MaxVal = TotalVal + Percent; - pChoiceEntry->Choice = Choice; - - pChoiceList->AddNode(pChoiceEntry); - - TotalVal += Percent; - }; - - bool FindSelection(SMultiChoice *MS, int Choice) - { - if ((Choice >= MS->MinVal) && (Choice < MS->MaxVal)) - { - return true; - } - return false; - } - - void ClearChoices(void) - { - TotalVal = 0; - while(pChoiceList->Empty() == false) - delete pChoiceList->Pop(); - } - - bool GetSelection(C &Var) - { - int Choice = RandomLong(0, (TotalVal - 1)); - typename TLinkedList::node_s *pNode = pChoiceList->GetFirst(); - - while(pNode) - { - if ((Choice >= pNode->Entry->MinVal) && (Choice < pNode->Entry->MaxVal)) - { - Var = pNode->Entry->Choice; - return true; - } - pNode = pNode->next; - } - - return false; - } -}; - -// ================================================================== -// Code of TMutiChoice - End -// ================================================================== - -#endif +#ifndef BOT_UTIL_H +#define BOT_UTIL_H + +// Structure for storing traceresults +struct traceresult_s +{ + vec end; + bool collided; +}; + +long RandomLong(long from, long to); +float RandomFloat(float from, float to); +void lsrand(unsigned long initial_seed); + +void AnglesToVectors(vec angles, vec &forward, vec &right, vec &up); +float WrapXAngle(float angle); +float WrapYZAngle(float angle); +void TraceLine(vec from, vec to, dynent *pTracer, bool CheckPlayers, traceresult_s *tr, + bool SkipTags=false); +float GetDistance(vec v1, vec v2); +float Get2DDistance(vec v1, vec v2); +bool IsVisible(vec v1, vec v2, dynent *tracer = NULL, bool SkipTags=false); +bool IsValidFile(const char *szFileName); +bool FileIsOlder(const char *szFileName1, const char *szFileName2); +vec PredictPos(vec pos, vec vel, float Time); +vec Normalize(vec v); +inline void makevec(vec *v, float x, float y, float z) { v->x=x; v->y=y; v->z=z; }; +inline bool UnderWater(const vec &o) { return hdr.waterlevel>o.z-0.5f; }; +inline bool InWater(const vec &o) { return hdr.waterlevel>=o.z; }; +float GetYawDiff(float curyaw, vec v1, vec v2); +vec CrossProduct(const vec &a, const vec &b); +int GetDirection(const vec &angles, const vec &v1, const vec &v2); +float GetCubeFloor(int x, int y); +float GetCubeHeight(int x, int y); +char *SkillNrToSkillName(short skillnr); +bool IsInGame(dynent *d); + +// ================================================================== +// Code of TLinkedList - Start +// ================================================================== + +template class TLinkedList +{ +public: + struct node_s + { + C Entry; + node_s *next; + node_s *prev; + + node_s(void) : next(NULL), prev(NULL) + { + //memset(&Entry, 0, sizeof(Entry)); + }; + }; + + // member functions + + void AddNode(C entry) + { + if (!pNodeList) + { + pNodeList = new node_s; + pNodeList->Entry = entry; + pNodeList->next = NULL; + pNodeList->prev = NULL; + pLastNode = pNodeList; + iNodeCount = 1; + } + else + { + pLastNode->next = new node_s; + pLastNode->next->prev = pLastNode; + pLastNode = pLastNode->next; + pLastNode->Entry = entry; + pLastNode->next = NULL; + iNodeCount++; + } + } + + void PushNode(C Entry) + { + if (!pNodeList) + { + pNodeList = new node_s; + pNodeList->Entry = Entry; + pNodeList->next = NULL; + pNodeList->prev = NULL; + pLastNode = pNodeList; + iNodeCount = 1; + } + else + { + node_s *pNew = new node_s; + pNew->Entry = Entry; + pNew->prev = NULL; + pNew->next = pNodeList; + pNodeList->prev = pNew; + pNodeList = pNew; + iNodeCount++; + } + } + + void DeleteEntry(C Entry) + { + node_s *pNode = pNodeList; + if (!pNode) + return; + + if (pNode->Entry == Entry) // first node + { + if (pNodeList == pLastNode) + pLastNode = NULL; + + pNodeList = pNodeList->next; + if (pNodeList) + pNodeList->prev = NULL; + pNode->next = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + return; + } + + if (Entry == pLastNode->Entry) // last node + { + pNode = pLastNode; + pLastNode = pLastNode->prev; + pLastNode->next = NULL; + + pNode->next = NULL; + pNode->prev = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + return; + } + + // node is somewhere in the middle + pNode = SearchNode(Entry); + + if (!pNode) + return; + + node_s *pPrevNode = pNode->prev; + + if (!pPrevNode) + return; + + // unlink pNode + pNode->next->prev = pPrevNode; + pPrevNode->next = pNode->next; + + pNode->next = NULL; + pNode->prev = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + } + + void DeleteNode(node_s *pNode) + { + if (!pNode) + return; + + if (pNodeList == pNode) // first node + { + if (pNodeList == pLastNode) + pLastNode = pNodeList->next; + + pNodeList = pNodeList->next; + if (pNodeList) + pNodeList->prev = NULL; + pNode->next = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + return; + } + + if (pNode == pLastNode) // last node + { + pNode = pLastNode; + pLastNode = pLastNode->prev; + pLastNode->next = NULL; + + pNode->next = NULL; + pNode->prev = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + return; + } + + // node is somewhere in the middle + + node_s *pPrevNode = pNode->prev; + + if (!pPrevNode) + return; + + // unlink pNode + pNode->next->prev = pPrevNode; + pPrevNode->next = pNode->next; + + pNode->next = NULL; + pNode->prev = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + } + + void DeleteAllNodes(void) + { + node_s *pNode = pNodeList; + node_s *pTemp; + while (pNode != NULL) + { + pTemp = pNode; + pNode = pNode->next; + pTemp->next = NULL; + pTemp->prev = NULL; + delete pTemp; + } + pNodeList = pLastNode = NULL; + iNodeCount = 0; + } + + void Reset(void) // Special case, doesn't delete existing nodes + { + pNodeList = pLastNode = NULL; + iNodeCount = 0; + } + + node_s *SearchNode(C Entry) + { + node_s *pNode = pNodeList; + while(pNode) + { + if (pNode->Entry == Entry) + return pNode; + pNode = pNode->next; + } + return NULL; + } + + C Pop(void) + { + if (!pNodeList) + return static_cast(NULL); + + C Entry = pNodeList->Entry; + DeleteNode(pNodeList); + return Entry; + } + + node_s *GetFirst(void) { return pNodeList; }; + node_s *GetLast(void) { return pLastNode; }; + + void EditEntry(C OrigEntry, C NewVal) + { + node_s *pNode = SearchNode(OrigEntry); + + if (pNode) + pNode->Entry = NewVal; + } + + bool IsInList(C Entry) + { + return (SearchNode(Entry) != NULL); + } + + bool Empty(void) { return (pNodeList == NULL); }; + int NodeCount(void) { return iNodeCount; }; + + + // construction/destruction + TLinkedList(void) + { + pNodeList = NULL; + pLastNode = NULL; + iNodeCount = 0; + }; + + ~TLinkedList(void) + { + DeleteAllNodes(); + }; + +private: + node_s *pNodeList; + node_s *pLastNode; + int iNodeCount; +}; + +// ================================================================== +// Code of TLinkedList - End +// ================================================================== + +// ================================================================== +// Code of TPriorList - Begin +// ================================================================== + +template class TPriorList +{ +public: + struct node_s + { + C Entry; + D Priority; + node_s *next; + + node_s(C Ent, D Prior) : Entry(Ent), Priority(Prior), next(NULL) {}; + }; + + TPriorList(void) : pHeadNode(NULL), pLastNode(NULL) {}; + ~TPriorList(void) { Clear(); }; + + void AddEntry(C Entry, D Prior) + { + if (!pHeadNode) + { + pHeadNode = new node_s(Entry, Prior); + pLastNode = pHeadNode; + iNodeCount=1; + } + else + { + iNodeCount++; + node_s *pNew = new node_s(Entry, Prior); + node_s *pNode = pHeadNode; + node_s *pPrev = NULL; + + while(pNode) + { + if (Prior < pNode->Priority) + { + if (!pPrev) + { + pNew->next = pNode; + pHeadNode = pNew; + } + else + { + pPrev->next = pNew; + pNew->next = pNode; + } + break; + } + pPrev = pNode; + pNode = pNode->next; + } + + if (!pNode) + { + pLastNode = pNew; + if (pPrev) + pPrev->next = pNew; + } + } + } + + C Pop(void) + { + if (!pHeadNode) + return static_cast(NULL); + + C Entry = pHeadNode->Entry; + DeleteNode(pHeadNode); + return Entry; + } + + void Clear(void) + { + node_s *pTemp; + while(pHeadNode) + { + pTemp = pHeadNode; + pHeadNode = pHeadNode->next; + delete pTemp; + } + + pHeadNode = pLastNode = NULL; + } + + bool IsInList(C Entry, D Prior) + { + node_s *pNode = pHeadNode; + while(pNode) + { + if (pNode->Entry == Entry) + return true; + + if (Prior < pNode->Priority) + return false; + + pNode = pNode->next; + } + + return false; + } + + bool Empty(void) { return (pHeadNode == NULL); } + node_s *GetFirst(void) { return pHeadNode; } +private: + node_s *pHeadNode; + node_s *pLastNode; + int iNodeCount; + + node_s *GetPrevNode(node_s *pNode) + { + node_s *pTemp = pHeadNode; + while(pTemp) + { + if (pTemp->next == pNode) + return pTemp; + pTemp = pTemp->next; + } + + return NULL; + } + + void DeleteNode(node_s *pNode) + { + if (!pNode) + return; + + if (pHeadNode == pNode) // first node + { + if (pHeadNode == pLastNode) + pLastNode = pHeadNode->next; + + pHeadNode = pHeadNode->next; + pNode->next = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + return; + } + + if (pNode == pLastNode) // last node + { + pNode = pLastNode; + pLastNode->next = NULL; + + pNode->next = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + return; + } + + // node is somewhere in the middle + + node_s *pPrevNode = GetPrevNode(pNode); + if (!pPrevNode) + return; + + // unlink pNode + pPrevNode->next = pNode->next; + pNode->next = NULL; + delete pNode; + pNode = NULL; + iNodeCount--; + } +}; + +// ================================================================== +// Code of TPriorList - End +// ================================================================== + + +// ================================================================== +// Code of TMultiChoice - Begin +// ================================================================== + +template class TMultiChoice +{ + struct SMultiChoice + { + int MinVal; + int MaxVal; + C Choice; + SMultiChoice(void) : MinVal(0), MaxVal(0){}; + }; + + int TotalVal; + TLinkedList *pChoiceList; + +public: + TMultiChoice(void) : TotalVal(0) // Constructor + { + pChoiceList = new TLinkedList; + }; + + ~TMultiChoice(void) // Destructor + { + while(pChoiceList->Empty() == false) + { + SMultiChoice *pEntry = pChoiceList->Pop(); + if (pEntry) + delete pEntry; + else + break; + } + delete pChoiceList; + pChoiceList = NULL; + } + + void Insert(C Choice, short Percent = 50) + { + if (Percent == 0) + return; + + SMultiChoice *pChoiceEntry = new SMultiChoice; + + pChoiceEntry->MinVal = TotalVal; + pChoiceEntry->MaxVal = TotalVal + Percent; + pChoiceEntry->Choice = Choice; + + pChoiceList->AddNode(pChoiceEntry); + + TotalVal += Percent; + }; + + bool FindSelection(SMultiChoice *MS, int Choice) + { + if ((Choice >= MS->MinVal) && (Choice < MS->MaxVal)) + { + return true; + } + return false; + } + + void ClearChoices(void) + { + TotalVal = 0; + while(pChoiceList->Empty() == false) + delete pChoiceList->Pop(); + } + + bool GetSelection(C &Var) + { + int Choice = RandomLong(0, (TotalVal - 1)); + typename TLinkedList::node_s *pNode = pChoiceList->GetFirst(); + + while(pNode) + { + if ((Choice >= pNode->Entry->MinVal) && (Choice < pNode->Entry->MaxVal)) + { + Var = pNode->Entry->Choice; + return true; + } + pNode = pNode->next; + } + + return false; + } +}; + +// ================================================================== +// Code of TMutiChoice - End +// ================================================================== + +#endif diff --git a/source/src/bot/bot_waypoint.cpp b/source/src/bot/bot_waypoint.cpp index ff16e696..302c80de 100644 --- a/source/src/bot/bot_waypoint.cpp +++ b/source/src/bot/bot_waypoint.cpp @@ -1,3258 +1,3258 @@ -// -// C++ Implementation: bot_waypoint -// -// Description: Waypoint class for bots -// -// -// Author: Rick -// - -#include "../cube.h" - -vec v_debuggoal = g_vecZero; - -#if defined AC_CUBE -CACWaypointClass WaypointClass; -#elif defined VANILLA_CUBE -CCubeWaypointClass WaypointClass; -#endif - -// Waypoint class begin - -CWaypointClass::CWaypointClass(void) : m_bDrawWaypoints(false), m_bDrawWPPaths(true), - m_bAutoWaypoint(false), m_bAutoPlacePaths(true), - m_bDrawWPText(false), m_vLastCreatedWP(g_vecZero), - m_bFlooding(false), m_bFilteringNodes(false), - m_iFloodStartTime(0), m_iCurFloodX(0), - m_iCurFloodY(0), m_iFloodSize(0), - m_iFilteredNodes(0), m_iWaypointCount(0) -{ - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - while(!m_Waypoints[i][j].Empty()) - delete m_Waypoints[i][j].Pop(); - } - } - - m_szMapName[0] = 0; -} - -void CWaypointClass::Init() -{ - Clear(); // Free previously allocated path memory - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - while(!m_Waypoints[i][j].Empty()) - delete m_Waypoints[i][j].Pop(); - } - } - - m_fPathDrawTime = 0.0; - m_iWaypointCount = 0; - m_vLastCreatedWP = g_vecZero; -} - -void CWaypointClass::Clear() -{ - for(int i=0;iConnectedWPs.DeleteAllNodes(); - p->ConnectedWPsWithMe.DeleteAllNodes(); - BotManager.DelWaypoint(p); - delete p; - } - } - } -} - -// returns true if waypoints succesfull loaded -bool CWaypointClass::LoadWaypoints() -{ - FILE *bfp; - char szWPFileName[64]; - char filename[256]; - waypoint_header_s header; - short num, triggernr, yaw; - int i, j, flags; - vec from, to; - - strcpy(szWPFileName, m_szMapName); - strcat(szWPFileName, ".wpt"); - - BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); - - bfp = fopen(filename, "rb"); - - BotManager.m_sCurrentTriggerNr = -1; - - // if file exists, read the waypoint structure from it - if (bfp != NULL) - { - fread(&header, sizeof(header), 1, bfp); - - header.szFileType[10] = 0; - if (strcmp(header.szFileType, "cube_bot") == 0) - { - header.szMapName[31] = 0; - - if (strcmp(header.szMapName, m_szMapName) == 0) - { - Init(); // remove any existing waypoints - - // Check which waypoint file version this is, handle each of them different - if (header.iFileVersion == 1) - { - // First version, works with an big array and has a limit - - waypoint_version_1_s WPs[1024]; - int path_index; - - m_iWaypointCount = header.iWPCount; - for (i=0; i < header.iWPCount; i++) - { - fread(&WPs[i], sizeof(WPs[0]), 1, bfp); - WPs[i].ConnectedWPs.Reset(); // We get garbage when we read this from - // a file, so just clear it - - // Convert to new waypoint structure - node_s *pNode = new node_s(WPs[i].v_origin, WPs[i].iFlags, 0); - - short x, y; - GetNodeIndexes(pNode->v_origin, &x, &y); - - m_Waypoints[x][y].AddNode(pNode); - BotManager.AddWaypoint(pNode); - } - - // read and add waypoint paths... - for (i=0; i < m_iWaypointCount; i++) - { - // read the number of paths from this node... - fread(&num, sizeof(num), 1, bfp); - - // See which waypoint the current is - node_s *pCurrent = GetWaypointFromVec(WPs[i].v_origin); - - if (!pCurrent) - { - conoutf("Error: NULL path in waypoint file"); - continue; - } - - for (j=0; j < num; j++) - { - fread(&path_index, sizeof(path_index), 1, bfp); - - // See which waypoint this is - node_s *pTo = GetWaypointFromVec(WPs[path_index].v_origin); - - if (!pTo) - { - conoutf("Error: NULL path in waypoint file"); - continue; - } - - AddPath(pCurrent, pTo); - } - } - conoutf("Old waypoint version(%d) converted to new version(%d)", - header.iFileVersion, WAYPOINT_VERSION); - conoutf("Use the wpsave command to convert permently"); - - } - else if (header.iFileVersion == 2) - { - m_iWaypointCount = header.iWPCount; - - for (i=0; i < header.iWPCount; i++) - { - fread(&from, sizeof(from), 1, bfp); // Read origin - fread(&flags, sizeof(flags), 1, bfp); // Read waypoint flags - fread(&triggernr, sizeof(triggernr), 1, bfp); // Read trigger nr - fread(&yaw, sizeof(yaw), 1, bfp); // Read target yaw - - node_s *pNode = new node_s(from, flags, triggernr, yaw); - - short x, y; - GetNodeIndexes(pNode->v_origin, &x, &y); - - m_Waypoints[x][y].AddNode(pNode); - BotManager.AddWaypoint(pNode); - - if ((BotManager.m_sCurrentTriggerNr == -1) || - (triggernr < BotManager.m_sCurrentTriggerNr)) - BotManager.m_sCurrentTriggerNr = triggernr; - } - - for (i=0; i < header.iWPCount; i++) - { - // read the number of paths from this node... - fread(&num, sizeof(num), 1, bfp); - fread(&from, sizeof(from), 1, bfp); - - if (!num) - continue; - - node_s *pCurrent = GetWaypointFromVec(from); - - if (!pCurrent) - { - conoutf("Error: NULL path in waypoint file"); - - for(j=0;j WAYPOINT_VERSION) - { - conoutf("Error: Waypoint file is newer then current, upgrade cube bot."); - fclose(bfp); - return false; - } - else - { - conoutf("Error: Unknown waypoint version for cube bot"); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoints aren't for map %s but for map %s", int(m_szMapName), - int(header.szMapName)); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoint file isn't for cube bot"); - //conoutf("Header FileType: %s", int(header.szFileType)); - - fclose(bfp); - return false; - } - - fclose(bfp); - - //RouteInit(); - } - else - { - conoutf("Waypoint file %s does not exist", int(filename)); - return false; - } - - if (BotManager.m_sCurrentTriggerNr == -1) - BotManager.m_sCurrentTriggerNr = 0; - - ReCalcCosts(); - - conoutf("Waypoints for map %s loaded", int(m_szMapName)); - return true; -} - -void CWaypointClass::SaveWaypoints() -{ - char filename[256]; - char mapname[64]; - waypoint_header_s header; - short int num; - TLinkedList::node_s *pPath; - - strcpy(header.szFileType, "cube_bot"); - - header.iFileVersion = WAYPOINT_VERSION; - - header.iFileFlags = 0; // not currently used - - header.iWPCount = m_iWaypointCount; - - memset(header.szMapName, 0, sizeof(header.szMapName)); - strncpy(header.szMapName, m_szMapName, 31); - header.szMapName[31] = 0; - - strcpy(mapname, m_szMapName); - strcat(mapname, ".wpt"); - - BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); - - FILE *bfp = fopen(filename, "wb"); - - if (!bfp) - { - conoutf("Error writing waypoint file, check if the directory \"bot/waypoint\" exists and " - "the right permissions are set"); - return; - } - - // write the waypoint header to the file... - fwrite(&header, sizeof(header), 1, bfp); - - // write the waypoint data to the file... - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // Write origin - fwrite(&p->Entry->iFlags, sizeof(p->Entry->iFlags), 1, bfp); // Write waypoint flags - fwrite(&p->Entry->sTriggerNr, sizeof(p->Entry->sTriggerNr), 1, bfp); // Write trigger nr - fwrite(&p->Entry->sYaw, sizeof(p->Entry->sYaw), 1, bfp); // Write target yaw - - p = p->next; - } - } - } - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - // save the waypoint paths... - - // count the number of paths from this node... - pPath = p->Entry->ConnectedWPs.GetFirst(); - num = p->Entry->ConnectedWPs.NodeCount(); - - fwrite(&num, sizeof(num), 1, bfp); // write the count - fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // write the origin of this path - - // now write out each path... - while (pPath != NULL) - { - fwrite(&pPath->Entry->v_origin, sizeof(pPath->Entry->v_origin), 1, bfp); - pPath = pPath->next; // go to next node in linked list - } - p = p->next; - } - } - } - - fclose(bfp); -} - -bool CWaypointClass::LoadWPExpFile() -{ - FILE *bfp; - char szWPFileName[64]; - char filename[256]; - waypoint_header_s header; - short int num; - int i, j; - vec from, to; - - if (m_iWaypointCount == 0) - return false; - - strcpy(szWPFileName, m_szMapName); - strcat(szWPFileName, ".exp"); - - BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); - - bfp = fopen(filename, "rb"); - - // if file exists, read the waypoint structure from it - if (bfp != NULL) - { - fread(&header, sizeof(header), 1, bfp); - - header.szFileType[10] = 0; - if (strcmp(header.szFileType, "cube_bot") == 0) - { - header.szMapName[31] = 0; - - if (strcmp(header.szMapName, m_szMapName) == 0) - { - // Check which waypoint file version this is, handle each of them different - if (header.iFileVersion == 1) - { - for (i=0; i < header.iWPCount; i++) - { - // read the number of paths from this node... - fread(&num, sizeof(num), 1, bfp); - fread(&from, sizeof(vec), 1, bfp); - - if (!num) continue; - - node_s *pCurrent = GetWaypointFromVec(from); - - if (!pCurrent) - { - conoutf("Error: NULL node in waypoint experience file"); - continue; - } - - for (j=0; j < num; j++) - { - fread(&to, sizeof(vec), 1, bfp); - node_s *p = GetWaypointFromVec(to); - if (p) - { - pCurrent->FailedGoalList.AddNode(p); - } - } - } - } - else if (header.iFileVersion > EXP_WP_VERSION) - { - conoutf("Error: Waypoint experience file is newer then current, upgrade cube bot."); - fclose(bfp); - return false; - } - else - { - conoutf("Error: Unknown waypoint experience file version for cube bot"); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoint experience file isn't for map %s but for map %s", int(m_szMapName), - int(header.szMapName)); - fclose(bfp); - return false; - } - } - else - { - conoutf("Waypoint experience file isn't for cube bot"); - //conoutf("Header FileType: %s", int(header.szFileType)); - - fclose(bfp); - return false; - } - - fclose(bfp); - - //RouteInit(); - } - else - { - conoutf("Waypoint experience file %s does not exist", int(filename)); - return false; - } - - conoutf("Waypoint experience file for map %s loaded", int(m_szMapName)); - return true; -} - -void CWaypointClass::SaveWPExpFile() -{ - if (!m_iWaypointCount) - return; - - char filename[256]; - char mapname[64]; - waypoint_header_s header; - short int num; - - strcpy(header.szFileType, "cube_bot"); - - header.iFileVersion = EXP_WP_VERSION; - - header.iFileFlags = 0; // not currently used - - header.iWPCount = m_iWaypointCount; - - memset(header.szMapName, 0, sizeof(header.szMapName)); - strncpy(header.szMapName, m_szMapName, 31); - header.szMapName[31] = 0; - - strcpy(mapname, m_szMapName); - strcat(mapname, ".exp"); - - BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); - - FILE *bfp = fopen(filename, "wb"); - - if (!bfp) - { - conoutf("Error writing waypoint experience file, check if the directory \"bot/waypoint\" exists and " - "the right permissions are set"); - return; - } - - // write the waypoint header to the file... - fwrite(&header, sizeof(header), 1, bfp); - - // save the waypoint experience data... - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - // count the number of paths from this node... - TLinkedList::node_s *p2 = p->Entry->FailedGoalList.GetFirst(); - num = p->Entry->FailedGoalList.NodeCount(); - - if (!num || !p2) continue; - - fwrite(&num, sizeof(num), 1, bfp); // write the count - fwrite(&p->Entry->v_origin, sizeof(vec), 1, bfp); // write the origin of this node - - while (p2 != NULL) - { - // Write out the node which a bot can't reach with the current node - fwrite(&p2->Entry->v_origin, sizeof(vec), 1, bfp); - p2 = p2->next; // go to next node in linked list - } - p = p->next; - } - } - } - - fclose(bfp); -} - -void CWaypointClass::Think() -{ - if (dedserv) return; - -#ifdef WP_FLOOD - FloodThink(); -#endif - - if (m_bAutoWaypoint) // is auto waypoint on? - { - // find the distance from the last used waypoint - float flDistance = GetDistance(m_vLastCreatedWP, player1->o); - - bool NoLastCreatedWP = ((m_vLastCreatedWP.x == 0) && (m_vLastCreatedWP.y == 0) && - (m_vLastCreatedWP.z == 0)); - - if ((flDistance > 3.0f) || NoLastCreatedWP) - { - // check that no other reachable waypoints are nearby... - if (!GetNearestWaypoint(player1, 10.0f)) - { - AddWaypoint(player1->o, true); // place a waypoint here - } - } - } - - // Draw info for nearest waypoint? - if (m_bDrawWPText) - { - node_s *nearestwp = GetNearestWaypoint(player1, 20.0f); - -#ifdef WP_FLOOD - if (!nearestwp) - nearestwp = GetNearestFloodWP(player1, 10.0f); -#endif - - if (nearestwp) - { - char szWPInfo[256]; - sprintf(szWPInfo, "Distance nearest waypoint: %f", GetDistance(player1->o, nearestwp->v_origin)); - AddScreenText(szWPInfo); - - strcpy(szWPInfo, "Flags: "); - if (nearestwp->iFlags & W_FL_TELEPORT) - strcat(szWPInfo, "Teleport "); - if (nearestwp->iFlags & W_FL_TELEPORTDEST) - strcat(szWPInfo, "Teleport destination "); - if (nearestwp->iFlags & W_FL_JUMP) - strcat(szWPInfo, "Jump "); - if (nearestwp->iFlags & W_FL_TRIGGER) - { - char sz[32]; - sprintf(sz, "Trigger(nr %d) ", nearestwp->sTriggerNr); - strcat(szWPInfo, sz); - } - if (nearestwp->iFlags & W_FL_INTAG) - strcat(szWPInfo, "In tagged cube(s) "); - if (strlen(szWPInfo) == 7) - strcat(szWPInfo, "None"); - - AddScreenText(szWPInfo); - - if (nearestwp->sYaw != -1) - AddScreenText("yaw %d", nearestwp->sYaw); - - sprintf(szWPInfo, "Waypoint has %d connections", - nearestwp->ConnectedWPs.NodeCount()); - AddScreenText(szWPInfo); - AddScreenText("Base Cost: %d", nearestwp->sCost); - } - } - - if (m_bDrawWaypoints) // display the waypoints if turned on... - { - DrawNearWaypoints(); - } -} - -void CWaypointClass::DrawNearWaypoints() -{ - TLinkedList::node_s *pNode; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = ceil(15.0f / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(player1->o, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - node_s *nearestwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - -#ifdef WP_FLOOD - if (!nearestwp) - nearestwp = GetNearestFloodWP(player1, 20.0f); -#endif - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - flDist = GetDistance(player1->o, pNode->Entry->v_origin); - if (flDist <= 15) - { - vec o = pNode->Entry->v_origin; - vec e = o; - o.z -= 2; - e.z += 2; - - if (pNode->Entry->iFlags & W_FL_JUMP) - { - // draw a red waypoint - linestyle(2.5f, 0xFF, 0x40, 0x40); - } - else if (nearestwp == pNode->Entry) - { - // draw a green waypoint - linestyle(2.5f, 0x40, 0xFF, 0x40); - } - else - { - // draw a blue waypoint - linestyle(2.5f, 0x40, 0x40, 0xFF); - } - - line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); - - if (flNearestDist > flDist) - { - flNearestDist = flDist; - pNearest = pNode->Entry; - } - } - - pNode = pNode->next; - } - } - } - - if (pNearest) - { - pNode = pNearest->ConnectedWPs.GetFirst(); - - linestyle(2.0f, 0xFF, 0xFF, 0xFF); - - while(pNode) - { - vec o = pNode->Entry->v_origin; - vec e = pNearest->v_origin; - - line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); - - pNode = pNode->next; - } - - // Has this waypoint an target yaw? - if (pNearest->sYaw != -1) - { - vec angles = { 0, pNearest->sYaw, 0 }; - vec from = pNearest->v_origin, end = pNearest->v_origin; - vec forward, right, up; - - from.z = end.z = (pNearest->v_origin.z-1.0f); - - AnglesToVectors(angles, forward, right, up); - vmul(forward, 5.0f); - vadd(end, forward); - - linestyle(2.0f, 0xFF, 0x40, 0x40); - line(int(from.x), int(from.y), int(from.z), int(end.x), int(end.y), int(end.z)); - } - } - -#ifndef RELEASE_BUILD - // check if path waypointing is on... - if (m_bDrawWPPaths) - { - // Draw path from first bot - if (bots.length() && bots[0] && bots[0]->pBot && bots[0]->pBot->m_pCurrentWaypoint && - bots[0]->pBot->m_pCurrentGoalWaypoint) - { - CBot *pB = bots[0]->pBot; - if (!pB->m_bCalculatingAStarPath && !pB->m_AStarNodeList.Empty()) - { - TLinkedList::node_s *pNode = pB->m_AStarNodeList.GetFirst(), *pNext; - - linestyle(2.5f, 0xFF, 0x40, 0x40); - - line((int)pB->m_pCurrentWaypoint->pNode->v_origin.x, - (int)pB->m_pCurrentWaypoint->pNode->v_origin.y, - (int)pB->m_pCurrentWaypoint->pNode->v_origin.z, - (int)pNode->Entry->pNode->v_origin.x, - (int)pNode->Entry->pNode->v_origin.y, - (int)pNode->Entry->pNode->v_origin.z); - - while(pNode && pNode->next) - { - pNext = pNode->next; - vec &v1 = pNode->Entry->pNode->v_origin; - vec &v2 = pNext->Entry->pNode->v_origin; - - line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); - - pNode = pNode->next; - } - } - } - } - - extern bool intermission; - if (intermission) return; - - for(int i=0;iPrevLocations.prevloc[i], g_vecZero)) continue; - vec v1 = player1->PrevLocations.prevloc[i]; - v1.z -= 1.0f; - vec v2 = v1; - v2.z += 2.0f; - linestyle(2.5f, 0xFF, 0x40, 0x40); - line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); - } -#endif -} - -// Add waypoint at location o, returns pointer of created wp -node_s *CWaypointClass::AddWaypoint(vec o, bool connectwp) -{ - short x, y; - int flags = 0; - if (S((int)o.x, (int)o.y)->tag) flags |= W_FL_INTAG; - - node_s *pNode = new node_s(o, flags, 0); - m_vLastCreatedWP = o; - - GetNodeIndexes(o, &x, &y); - - m_Waypoints[x][y].AddNode(pNode); - BotManager.AddWaypoint(pNode); - - m_iWaypointCount++; - - if (connectwp && m_bAutoPlacePaths) - { - // Connect new waypoint with other near waypoints. - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - - while(p) - { - if (p->Entry == pNode) - { - p = p->next; - continue; // skip the waypoint that was just added - } - - // check if the waypoint is reachable from the new one (one-way) - if (WPIsReachable(o, p->Entry->v_origin)) - AddPath(pNode, p->Entry); // Add a path from a to b - if (WPIsReachable(p->Entry->v_origin, pNode->v_origin)) - AddPath(p->Entry, pNode); // Add a path from b to a - - p = p->next; - } - } - } - } - - return pNode; -} - -void CWaypointClass::DeleteWaypoint(dynent *d) -{ - node_s *pWP; - - pWP = GetNearestWaypoint(d, 7.0f); - - if (!pWP) - { - conoutf("Error: Couldn´t find near waypoint"); - return; - } - - BotManager.DelWaypoint(pWP); - - // delete any paths that lead to this index... - DeletePath(pWP); - - short x, y; - GetNodeIndexes(pWP->v_origin, &x, &y); - - m_Waypoints[x][y].DeleteEntry(pWP); - - pWP->ConnectedWPs.DeleteAllNodes(); - pWP->ConnectedWPsWithMe.DeleteAllNodes(); - - m_iWaypointCount--; - delete pWP; -} - -void CWaypointClass::AddPath(node_s *pWP1, node_s *pWP2) -{ - pWP1->ConnectedWPs.AddNode(pWP2); - pWP2->ConnectedWPsWithMe.AddNode(pWP1); -} - -// Deletes all paths connected to the given waypoint -void CWaypointClass::DeletePath(node_s *pWP) -{ - TLinkedList::node_s *p; - - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - p = m_Waypoints[i][j].GetFirst(); - while (p) - { - p->Entry->ConnectedWPs.DeleteEntry(pWP); - pWP->ConnectedWPsWithMe.DeleteEntry(p->Entry); - p = p->next; - } - } - } -} - -// Deletes path between 2 waypoints(1 way) -void CWaypointClass::DeletePath(node_s *pWP1, node_s *pWP2) -{ - pWP1->ConnectedWPs.DeleteEntry(pWP2); - pWP2->ConnectedWPsWithMe.DeleteEntry(pWP1); -} - -void CWaypointClass::ManuallyCreatePath(dynent *d, int iCmd, bool TwoWay) -{ - static node_s *waypoint1 = NULL; // initialized to unassigned - static node_s *waypoint2 = NULL; // initialized to unassigned - - if (iCmd == 1) // assign source of path - { - waypoint1 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint1) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - return; - } - - if (iCmd == 2) // assign dest of path and make path - { - if (!waypoint1) - { - conoutf("Error: First waypoint unset"); - return; - } - - waypoint2 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint2) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - AddPath(waypoint1, waypoint2); - - if (TwoWay) - AddPath(waypoint2, waypoint1); - } -} - -void CWaypointClass::ManuallyDeletePath(dynent *d, int iCmd, bool TwoWay) -{ - static node_s *waypoint1 = NULL; // initialized to unassigned - static node_s *waypoint2 = NULL; // initialized to unassigned - - if (iCmd == 1) // assign source of path - { - waypoint1 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint1) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - return; - } - - if (iCmd == 2) // assign dest of path and delete path - { - if (!waypoint1) - { - conoutf("Error: First waypoint unset"); - return; - } - - waypoint2 = GetNearestWaypoint(d, 7.0f); - - if (!waypoint2) - { - conoutf("Error: Couldn't find near waypoint"); - return; - } - - DeletePath(waypoint1, waypoint2); - - if (TwoWay) - DeletePath(waypoint2, waypoint1); - } -} - -bool CWaypointClass::WPIsReachable(vec from, vec to) -{ - traceresult_s tr; - float curr_height, last_height; - - float distance = GetDistance(from, to); - - // is the destination close enough? - if (distance < REACHABLE_RANGE) - { - if (IsVisible(from, to)) - { - if (UnderWater(from) && UnderWater(to)) - { - // No need to worry about heights in water - return true; - } -/* - if (to.z > (from.z + JUMP_HEIGHT)) - { - vec v_new_src = to; - vec v_new_dest = to; - - v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); - - // check if we didn't hit anything, if so then it's in mid-air - if (::IsVisible(v_new_src, v_new_dest, NULL)) - { - conoutf("to is in midair"); - debugbeam(from, to); - return false; // can't reach this one - } - } -*/ - - // check if distance to ground increases more than jump height - // at points between from and to... - - vec v_temp = to; vsub(v_temp, from); - vec v_direction = Normalize(v_temp); // 1 unit long - vec v_check = from; - vec v_down = from; - - v_down.z = v_down.z - 100.0f; // straight down - - TraceLine(v_check, v_down, NULL, false, &tr); - - // height from ground - last_height = GetDistance(v_check, tr.end); - - distance = GetDistance(to, v_check); // distance from goal - - while (distance > 2.0f) - { - // move 2 units closer to the goal - v_temp = v_direction; - vmul(v_temp, 2.0f); - vadd(v_check, v_temp); - - v_down = v_check; - v_down.z = v_down.z - 100.0f; - - TraceLine(v_check, v_down, NULL, false, &tr); - - curr_height = GetDistance(v_check, tr.end); - - // is the difference in the last height and the current height - // higher that the jump height? - if ((last_height - curr_height) >= JUMP_HEIGHT) - { - // can't get there from here... - //conoutf("traces failed to to"); - return false; - } - - last_height = curr_height; - - distance = GetDistance(to, v_check); // distance from goal - } - - return true; - } - } - - return false; -} - -node_s *CWaypointClass::GetNearestWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *pNode; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if (pNode->Entry->iFlags & W_FL_FLOOD) - { - pNode = pNode->next; - continue; - } - - flDist = GetDistance(v_src, pNode->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) - { - pNearest = pNode->Entry; - flNearestDist = flDist; - } - } - - pNode = pNode->next; - } - } - } - return pNearest; -} - -node_s *CWaypointClass::GetNearestTriggerWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *pNode; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if ((pNode->Entry->iFlags & W_FL_FLOOD) || !(pNode->Entry->iFlags & W_FL_TRIGGER)) - { - pNode = pNode->next; - continue; - } - - flDist = GetDistance(v_src, pNode->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) - { - pNearest = pNode->Entry; - flNearestDist = flDist; - } - } - - pNode = pNode->next; - } - } - } - return pNearest; -} - -node_s *CWaypointClass::GetWaypointFromVec(const vec &v_src) -{ - static TLinkedList::node_s *pNode; - static short x, y; - - GetNodeIndexes(v_src, &x, &y); - - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if (vis(pNode->Entry->v_origin, v_src)) - return pNode->Entry; - - pNode = pNode->next; - } - return NULL; -} - -void CWaypointClass::CalcCost(node_s *pNode) -{ - float flCost = 10.0f; - - // Check nearby cubes... - int x = int(pNode->v_origin.x); - int y = int(pNode->v_origin.y); - int a, b, row; - - for (row=0;row<=1;row++) - { - if (row==0) b = y - 6; - else b = y + 6; - - for (a=(x-6);a<=(x+6);a++) - { - if (OUTBORD(a, b)) continue; - - vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; - - // See if there is a obstacle(cube or mapmodel) nearby - traceresult_s tr; - TraceLine(pNode->v_origin, to, NULL, false, &tr); - if (tr.collided) - { - float flFraction = (GetDistance(pNode->v_origin, tr.end) / - GetDistance(pNode->v_origin, to)); - flCost += (1.0f-flFraction)*0.5f; - } - - if ((abs(a) > 4) || (abs(b) > 4)) continue; - - vec from = to; - to.z -= (JUMP_HEIGHT - 1.0f); - TraceLine(from, to, NULL, false, &tr); - if (!tr.collided) - flCost += 0.5f; - } - - if (row==0) a = x - 6; - else a = x + 6; - - for (b=(y-6);b<=(y+6);b++) - { - if (OUTBORD(a, b)) continue; - - vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; - - // See if there is a obstacle(cube or mapmodel) nearby - traceresult_s tr; - TraceLine(pNode->v_origin, to, NULL, false, &tr); - if (tr.collided) - { - float flFraction = (GetDistance(pNode->v_origin, tr.end) / - GetDistance(pNode->v_origin, to)); - flCost += (1.0f-flFraction)*0.5f; - } - - if ((abs(a) > 4) || (abs(b) > 4)) continue; - - vec from = to; - to.z -= (JUMP_HEIGHT - 1.0f); - TraceLine(from, to, NULL, false, &tr); - if (!tr.collided) - flCost += 0.5f; - } - } - - if (UnderWater(pNode->v_origin)) // Water is annoying - flCost += 5.0f; - - pNode->sCost = (short)flCost; -} - -void CWaypointClass::ReCalcCosts(void) -{ - loopi(MAX_MAP_GRIDS) - { - loopj(MAX_MAP_GRIDS) - { - TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); - while(p) - { - CalcCost(p->Entry); - p = p->next; - } - } - } -} - -#ifdef WP_FLOOD - -void CWaypointClass::StartFlood() -{ - if (m_bFlooding) return; - - conoutf("Starting flood, this may take a while on large maps...."); - m_bFlooding = true; - m_iFloodStartTime = SDL_GetTicks(); - m_iCurFloodX = m_iCurFloodY = MINBORD; - m_iFloodSize = 0; -} - -void CWaypointClass::FloodThink() -{ - if (!m_bFlooding) return; - - static int x, y, count; - count = 0; - - if (!m_bFilteringNodes) - { - // loop through ALL cubes and check if we should add a waypoint here - for (x=m_iCurFloodX; x<(ssize-MINBORD); x+=4) - { - if (count >= 3) - { - AddScreenText("Flooding map with waypoints... %.2f %%", - ((float)x / float(ssize-MINBORD)) * 100.0f); - m_iCurFloodX = x; - return; - } - - count++; - - for (y=MINBORD; y<(ssize-MINBORD); y+=4) - { - vec from = { x, y, GetCubeFloor(x, y)+2.0f }; - bool bFound = CanPlaceNodeHere(from); - - if (!bFound) - { - for (int a=x-2;a<=(x+2);a++) - { - for (int b=y-2;b<=(y+2);b++) - { - if (OUTBORD(a, b)) continue; - if ((a==x) && (b==y)) continue; - makevec(&from, a, b, GetCubeFloor(a, b) + 2.0f); - if (CanPlaceNodeHere(from)) - { - bFound = true; - break; - } - } - - if (bFound) break; - } - } - - if (!bFound) continue; - - // Add WP - int flags = W_FL_FLOOD; - if (S((int)from.x, (int)from.y)->tag) flags |= W_FL_INTAG; - - node_s *pWP = new node_s(from, flags, 0); - - short i, j; - GetNodeIndexes(from, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - } - } - - if (!m_bFilteringNodes) - { - m_bFilteringNodes = true; - m_iCurFloodX = m_iCurFloodY = 0; - m_iFilteredNodes = 0; - } - - count = 0; - - // Filter all nodes which aren't connected to any other nodes - for (x=m_iCurFloodX;x 3) - { - AddScreenText("Filtering useless waypoints and"); - AddScreenText("adding costs... %.2f %%", ((float)x / float(MAX_MAP_GRIDS)) * 100.0f); - m_iCurFloodX = x; - return; - } - - count++; - - for (y=0;y::node_s *pNode = m_Waypoints[x][y].GetFirst(), *pTemp; - while(pNode) - { - if (pNode->Entry->ConnectedWPs.Empty() || - pNode->Entry->ConnectedWPsWithMe.Empty()) - { - BotManager.DelWaypoint(pNode->Entry); - pTemp = pNode; - pNode = pNode->next; - delete pTemp->Entry; - m_Waypoints[x][y].DeleteNode(pTemp); - m_iFilteredNodes++; - m_iFloodSize -= sizeof(node_s); - m_iWaypointCount--; - continue; - } - else - CalcCost(pNode->Entry); - pNode = pNode->next; - } - } - } - - // Done with flooding - m_bFlooding = false; - m_bFilteringNodes = false; - - //ReCalcCosts(); - BotManager.PickNextTrigger(); - - m_iFloodSize += sizeof(m_Waypoints); - conoutf("Added %d wps in %d milliseconds", m_iWaypointCount, SDL_GetTicks()-m_iFloodStartTime); - conoutf("Filtered %d wps", m_iFilteredNodes); - - BotManager.CalculateMaxAStarCount(); - - char szSize[64]; - sprintf(szSize, "Total size: %.2f Kb", float(m_iFloodSize)/1024.0f); - conoutf(szSize); -} - -bool CWaypointClass::CanPlaceNodeHere(const vec &from) -{ - static short x, y, a, b; - static traceresult_s tr; - static vec to, v1, v2; - - x = fast_f2nat(from.x); - y = fast_f2nat(from.y); - - sqr *s = S(x, y); - if (SOLID(s)) - { - return false; - } - - if (fabs(s->ceil - s->floor) < player1->radius) - { - return false; - } - - if (GetNearestFloodWP(from, 2.0f, NULL)) return false; - - for (a=(x-1);a<=(x+1);a++) - { - for (b=(y-1);b<=(y+1);b++) - { - if ((x==a) && (y==b)) continue; - if (OUTBORD(a, b)) return false; - makevec(&v1, a, b, from.z); - v2 = v1; - v2.z -= 1000.0f; - - TraceLine(v1, v2, NULL, false, &tr, true); - to = tr.end; - - if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) - { - if (!tr.collided || (to.z < (from.z-4.0f))) - { - return false; - } - } - - to.z += 2.0f; - - if (from.z < (to.z-JUMP_HEIGHT)) - { - return false; - } - - TraceLine(from, to, NULL, false, &tr, true); - if (tr.collided) - return false; - } - } - - return true; -} - -void CWaypointClass::ConnectFloodWP(node_s *pWP) -{ - if (!pWP) return; - - static float flRange; - static TLinkedList::node_s *pNode; - static short i, j, MinI, MaxI, MinJ, MaxJ, x, y, Offset; - static float flDist; - static node_s *p; - - // Calculate range, based on distance to nearest node - p = GetNearestFloodWP(pWP->v_origin, 15.0f, pWP, true); - if (p) - { - flDist = GetDistance(pWP->v_origin, p->v_origin); - flRange = min(flDist+2.0f, 15.0f); - if (flRange < 5.0f) flRange = 5.0f; - } - else - return; - - Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - - GetNodeIndexes(pWP->v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (x=MinI;x<=MaxI;x++) - { - for(y=MinJ;y<=MaxJ;y++) - { - pNode = m_Waypoints[x][y].GetFirst(); - - while(pNode) - { - if (pNode->Entry == pWP) - { - pNode = pNode->next; - continue; - } - - flDist = GetDistance(pWP->v_origin, pNode->Entry->v_origin); - if (flDist <= flRange) - { - if (IsVisible(pWP->v_origin, pNode->Entry->v_origin, NULL, true)) - { - // Connect a with b - pWP->ConnectedWPs.AddNode(pNode->Entry); - pNode->Entry->ConnectedWPsWithMe.AddNode(pWP); - - // Connect b with a - pNode->Entry->ConnectedWPs.AddNode(pWP); - pWP->ConnectedWPsWithMe.AddNode(pNode->Entry); - - m_iFloodSize += (2 * sizeof(node_s *)); - } - } - - pNode = pNode->next; - } - } - } -} - -node_s *CWaypointClass::GetNearestFloodWP(vec v_origin, float flRange, node_s *pIgnore, - bool SkipTags) -{ - TLinkedList::node_s *p; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_Waypoints[x][y].GetFirst(); - - while(p) - { - if ((p->Entry == pIgnore) || (!(p->Entry->iFlags & W_FL_FLOOD))) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_origin, p->Entry->v_origin, NULL, SkipTags)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -node_s *CWaypointClass::GetNearestTriggerFloodWP(vec v_origin, float flRange) -{ - TLinkedList::node_s *p; - node_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_Waypoints[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (IsVisible(v_origin, p->Entry->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -void CWaypointClass::GetNodeIndexes(const vec &v_origin, short *i, short *j) -{ - // Function code by cheesy and PMB - //*i = abs((int)((int)(v_origin.x + (2*ssize)) / SECTOR_SIZE)); - //*j = abs((int)((int)(v_origin.y + (2*ssize)) / SECTOR_SIZE)); - //*i = (int)((v_origin.x) / ssize * MAX_MAP_GRIDS); - //*j = (int)((v_origin.y) / ssize * MAX_MAP_GRIDS); - *i = abs((int)((v_origin.x) / MAX_MAP_GRIDS)); - *j = abs((int)((v_origin.y) / MAX_MAP_GRIDS)); - - if (*i > MAX_MAP_GRIDS - 1) - *i = MAX_MAP_GRIDS - 1; - if (*j > MAX_MAP_GRIDS - 1) - *j = MAX_MAP_GRIDS - 1; -} - -#endif // WP_FLOOD -// Waypoint class end - -#if defined AC_CUBE - -// AC waypoint class begin -void CACWaypointClass::StartFlood() -{ - // UNDONE? - CWaypointClass::StartFlood(); -} - -// AC waypoint class end - -#elif defined VANILLA_CUBE - -// Cube waypoint class begin - -void CCubeWaypointClass::StartFlood() -{ - CWaypointClass::StartFlood(); - - // Add wps at triggers and teleporters and their destination - loopv(ents) - { - entity &e = ents[i]; - - if (OUTBORD(e.x, e.y)) continue; - - if (e.type == TELEPORT) - { - vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; - - // Find the teleport destination - int n = -1, tag = e.attr1, beenhere = -1; - for(;;) - { - n = findentity(TELEDEST, n+1); - if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); break; }; - if(beenhere<0) beenhere = n; - if(ents[n].attr2==tag) - { - teledestpos.x = ents[n].x; - teledestpos.y = ents[n].y; - teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; - break; - } - } - - if (vis(teledestpos, g_vecZero)) continue; - - int flags = (W_FL_FLOOD | W_FL_TELEPORT); - if (S((int)telepos.x, (int)telepos.y)->tag) flags |= W_FL_INTAG; - - // Add waypoint at teleporter and teleport destination - node_s *pWP = new node_s(telepos, flags, 0); - - short i, j; - GetNodeIndexes(telepos, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - flags = (W_FL_FLOOD | W_FL_TELEPORTDEST); - if (S((int)teledestpos.x, (int)teledestpos.y)->tag) flags |= W_FL_INTAG; - - node_s *pWP2 = new node_s(teledestpos, flags, 0); - - GetNodeIndexes(teledestpos, &i, &j); - m_Waypoints[i][j].PushNode(pWP2); - BotManager.AddWaypoint(pWP2); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) - AddPath(pWP, pWP2); - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - else if (e.type == CARROT) - { - vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - int flags = (W_FL_FLOOD | W_FL_TRIGGER); - if (S(e.x, e.y)->tag) flags |= W_FL_INTAG; - - node_s *pWP = new node_s(pos, flags, 0); - - short i, j; - GetNodeIndexes(pos, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - else if (e.type == MAPMODEL) - { - mapmodelinfo &mmi = getmminfo(e.attr2); - if(!&mmi || !mmi.h || !mmi.rad) continue; - - float floor = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3)+mmi.h; - - float x1 = e.x - mmi.rad; - float x2 = e.x + mmi.rad; - float y1 = e.y - mmi.rad; - float y2 = e.y + mmi.rad; - - // UNDONE? - for (float x=(x1+1.0f);x<=(x2-1.0f);x++) - { - for (float y=(y1+1.0f);y<=(y2-1.0f);y++) - { - vec from = { x, y, floor+2.0f }; - if (GetNearestFloodWP(from, 2.0f, NULL)) continue; - - // Add WP - int flags = W_FL_FLOOD; - if (S((int)x, (int)y)->tag) flags |= W_FL_INTAG; - - node_s *pWP = new node_s(from, flags, 0); - - short i, j; - GetNodeIndexes(from, &i, &j); - m_Waypoints[i][j].PushNode(pWP); - BotManager.AddWaypoint(pWP); - m_iFloodSize += sizeof(node_s); - m_iWaypointCount++; - - // Connect with other nearby nodes - ConnectFloodWP(pWP); - } - } - } - } - CWaypointClass::StartFlood(); -} - -void CCubeWaypointClass::CreateWPsAtTeleporters() -{ - loopv(ents) - { - entity &e = ents[i]; - - if (e.type != TELEPORT) continue; - if (OUTBORD(e.x, e.y)) continue; - - vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; - - // Find the teleport destination - int n = -1, tag = e.attr1, beenhere = -1; - for(;;) - { - n = findentity(TELEDEST, n+1); - if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); continue; }; - if(beenhere<0) beenhere = n; - if(ents[n].attr2==tag) - { - teledestpos.x = ents[n].x; - teledestpos.y = ents[n].y; - teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; - break; - } - } - - if (vis(teledestpos, g_vecZero)) continue; - - // Add waypoint at teleporter and teleport destination - node_s *telewp = AddWaypoint(telepos, false); - node_s *teledestwp = AddWaypoint(teledestpos, false); - - if (telewp && teledestwp) - { - // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) - AddPath(telewp, teledestwp); - - // Flag waypoints - telewp->iFlags = W_FL_TELEPORT; - teledestwp->iFlags = W_FL_TELEPORTDEST; - } - } -} - -void CCubeWaypointClass::CreateWPsAtTriggers() -{ - loopv(ents) - { - entity &e = ents[i]; - - if (e.type != CARROT) continue; - if (OUTBORD(e.x, e.y)) continue; - - vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - node_s *wp = AddWaypoint(pos, false); - - if (wp) - { - // Flag waypoints - wp->iFlags = W_FL_TRIGGER; - } - } -} - -#endif - -// Cube waypoint class end - -// Waypoint commands begin - -void addwp(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.AddWaypoint(player1->o, true); -} - -COMMAND(addwp, ARG_NONE); - -void delwp(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.DeleteWaypoint(player1); -} - -COMMAND(delwp, ARG_NONE); - -void wpvisible(int on) -{ - WaypointClass.SetWaypointsVisible(bool(on)); -} - -COMMAND(wpvisible, ARG_1INT); - -void wpsave(void) -{ - WaypointClass.SaveWaypoints(); -} - -COMMAND(wpsave, ARG_NONE); - -void wpload(void) -{ - WaypointClass.LoadWaypoints(); -} - -COMMAND(wpload, ARG_NONE); - -void autowp(int on) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.SetAutoWaypoint(bool(on)); -} - -COMMAND(autowp, ARG_1INT); - -void wpinfo(int on) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.SetWPInfo(bool(on)); -} - -COMMAND(wpinfo, ARG_1INT); - -void addpath1way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 1, false); -} - -COMMAND(addpath1way1, ARG_NONE); - -void addpath1way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 2, false); -} - -COMMAND(addpath1way2, ARG_NONE); - -void addpath2way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 1, true); -} - -COMMAND(addpath2way1, ARG_NONE); - -void addpath2way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyCreatePath(player1, 2, true); -} - -COMMAND(addpath2way2, ARG_NONE); - -void delpath1way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 1, false); -} - -COMMAND(delpath1way1, ARG_NONE); - -void delpath1way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 2, false); -} - -COMMAND(delpath1way2, ARG_NONE); - -void delpath2way1(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 1, true); -} - -COMMAND(delpath2way1, ARG_NONE); - -void delpath2way2(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.ManuallyDeletePath(player1, 2, true); -} - -COMMAND(delpath2way2, ARG_NONE); - -void setjumpwp(void) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.SetWPFlags(wp, W_FL_JUMP); - } -} - -COMMAND(setjumpwp, ARG_NONE); - -void unsetjumpwp(void) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.UnsetWPFlags(wp, W_FL_JUMP); - } -} - -COMMAND(unsetjumpwp, ARG_NONE); - -void setwptriggernr(int nr) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.SetWPTriggerNr(wp, nr); - } -} - -COMMAND(setwptriggernr, ARG_1INT); - -void setwpyaw(void) -{ - node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - if (wp) - { - WaypointClass.SetWPYaw(wp, short(player1->yaw)); - } -} - -COMMAND(setwpyaw, ARG_NONE); - -#ifdef WP_FLOOD -void startflood(void) -{ - WaypointClass.StartFlood(); -} - -COMMAND(startflood, ARG_NONE); -#endif - -#ifdef VANILLA_CUBE -// Commands specific for cube -void addtelewps(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.CreateWPsAtTeleporters(); -} - -COMMAND(addtelewps, ARG_NONE); - -void addtriggerwps(void) -{ - WaypointClass.SetWaypointsVisible(true); - WaypointClass.CreateWPsAtTriggers(); -} - -COMMAND(addtriggerwps, ARG_NONE); -#endif - -// Debug functions -#ifdef WP_FLOOD - -#ifndef RELEASE_BUILD -void botsheadtome(void) -{ - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - bots[i]->pBot->GoToDebugGoal(player1->o); - } -} - -COMMAND(botsheadtome, ARG_NONE); - -void setdebuggoal(void) { v_debuggoal = player1->o; }; -COMMAND(setdebuggoal, ARG_NONE); - -void botsheadtodebuggoal(void) -{ - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - bots[i]->pBot->GoToDebugGoal(v_debuggoal); - } -} - -COMMAND(botsheadtodebuggoal, ARG_NONE); - -#endif // RELEASE_BUILD - -#endif // WP_FLOOD - -// End debug functions -// Waypoint commands end - - -// Bot class begin - -bool CBot::FindWaypoint() -{ - waypoint_s *wp, *wpselect; - int index; - float distance, min_distance[3]; - waypoint_s *min_wp[3]; - - for (index=0; index < 3; index++) - { - min_distance[index] = 9999.0; - min_wp[index] = NULL; - } - - TLinkedList::node_s *pNode = m_pCurrentWaypoint->pNode->ConnectedWPs.GetFirst(); - - while (pNode) - { - if ((pNode->Entry->iFlags & W_FL_INTAG) && - SOLID(S((int)pNode->Entry->v_origin.x, (int)pNode->Entry->v_origin.y))) - { - pNode = pNode->next; - continue; - } - - wp = GetWPFromNode(pNode->Entry); - if (!wp) - { - pNode = pNode->next; - continue; - } - - // if index is not a current or recent previous waypoint... - if ((wp != m_pCurrentWaypoint) && - (wp != m_pPrevWaypoints[0]) && - (wp != m_pPrevWaypoints[1]) && - (wp != m_pPrevWaypoints[2]) && - (wp != m_pPrevWaypoints[3]) && - (wp != m_pPrevWaypoints[4])) - { - // find the distance from the bot to this waypoint - distance = GetDistance(wp->pNode->v_origin); - - if (distance < min_distance[0]) - { - min_distance[2] = min_distance[1]; - min_wp[2] = min_wp[1]; - - min_distance[1] = min_distance[0]; - min_wp[1] = min_wp[0]; - - min_distance[0] = distance; - min_wp[0] = wp; - } - else if (distance < min_distance [1]) - { - min_distance[2] = min_distance[1]; - min_wp[2] = min_wp[1]; - - min_distance[1] = distance; - min_wp[1] = wp; - } - else if (distance < min_distance[2]) - { - min_distance[2] = distance; - min_wp[2] = wp; - } - } - pNode = pNode->next; - } - - wpselect = NULL; - - // about 20% of the time choose a waypoint at random - // (don't do this any more often than every 10 seconds) - - if ((RandomLong(1, 100) <= 20) && (m_iRandomWaypointTime <= lastmillis)) - { - m_iRandomWaypointTime = lastmillis + 10000; - - if (min_wp[2]) - index = RandomLong(0, 2); - else if (min_wp[1]) - index = RandomLong(0, 1); - else if (min_wp[0]) - index = 0; - else - return false; // no waypoints found! - - wpselect = min_wp[index]; - } - else - { - // use the closest waypoint that has been recently used - wpselect = min_wp[0]; - } - - if (wpselect) // was a waypoint found? - { - m_pPrevWaypoints[4] = m_pPrevWaypoints[3]; - m_pPrevWaypoints[3] = m_pPrevWaypoints[2]; - m_pPrevWaypoints[2] = m_pPrevWaypoints[1]; - m_pPrevWaypoints[1] = m_pPrevWaypoints[0]; - m_pPrevWaypoints[0] = m_pCurrentWaypoint; - - SetCurrentWaypoint(wpselect); - return true; - } - - return false; // couldn't find a waypoint -} - -bool CBot::HeadToWaypoint() -{ - if (!m_pCurrentWaypoint) - return false; // Can't head to waypoint - - bool Touching = false; - float WPDist = GetDistance(m_pCurrentWaypoint->pNode->v_origin); - -#ifndef RELEASE_BUILD - if (m_pCurrentGoalWaypoint && vis(m_vGoal, g_vecZero)) - condebug("Warning: m_vGoal unset"); -#endif - - // did the bot run past the waypoint? (prevent the loop-the-loop problem) - if ((m_fPrevWaypointDistance > 1.0) && (WPDist > m_fPrevWaypointDistance) && - (WPDist <= 5.0f)) - Touching = true; - // bot needs to be close for jump and trigger waypoints - else if ((m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) || - (m_pCurrentWaypoint->pNode->iFlags & W_FL_TRIGGER)) - Touching = (WPDist <= 1.5f); - else if (m_pCurrentWaypoint->pNode->iFlags & W_FL_TELEPORT) - Touching = (WPDist <= 4.0f); - // are we close enough to a target waypoint... - else if (WPDist <= 3.0f) - { - if (!m_pCurrentGoalWaypoint || (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) || - IsVisible(m_vGoal) || (WPDist <= 1.5f)) - Touching = true; - // If the bot has a goal check if he can see his next wp - if (m_pCurrentGoalWaypoint && (m_pCurrentGoalWaypoint != m_pCurrentWaypoint) && - !m_AStarNodeList.Empty() && (WPDist >= 1.0f) && - !IsVisible(m_AStarNodeList.GetFirst()->Entry->pNode->v_origin)) - Touching = false; - } - - // save current distance as previous - m_fPrevWaypointDistance = WPDist; - - // Reached the waypoint? - if (Touching) - { - // Does this waypoint has a targetyaw? - if (m_pCurrentWaypoint->pNode->sYaw != -1) - { - // UNDONE: Inhuman - m_pMyEnt->yaw = m_pMyEnt->targetyaw = m_pCurrentWaypoint->pNode->sYaw; - } - - // Reached a jump waypoint? - if (m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) - m_pMyEnt->jumpnext = true; - - m_fPrevWaypointDistance = 0.0f; - - // Does the bot has a goal? - if (m_pCurrentGoalWaypoint) - { - if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) - { - if (m_AStarNodeList.Empty()) - { - if (!AStar()) - { - // Bot is calculating a new path, just stand still for now - ResetMoveSpeed(); - m_iWaypointTime += 200; - return true; - } - else - { - if (m_AStarNodeList.Empty()) - { - //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); - return false; // Couldn't get a new wp to go to - } - } - } - - m_pCurrentWaypoint = m_AStarNodeList.Pop(); - - if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) - //(!(m_pCurrentWaypoint->iFlags & W_FL_TELEPORT))) - { - //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); - condebug("Next WP not visible"); - return false; - } - - SetCurrentWaypoint(m_pCurrentWaypoint); - } - else - { - // Bot reached the goal waypoint but couldn't reach the goal itself - // (otherwise we wouldn't be in this function) - //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); - return false; - } - } - else - { - short index = 4; - bool status; - - // try to find the next waypoint - while (((status = FindWaypoint()) == false) && (index > 0)) - { - // if waypoint not found, clear oldest previous index and try again - - m_pPrevWaypoints[index] = NULL; - index--; - } - - if (status == false) - { - ResetWaypointVars(); - condebug("Couldn't find new random waypoint"); - return false; - } - } - m_iWaypointHeadPauseTime = lastmillis + 75; - m_iWaypointTime += 75; - } - - // keep turning towards the waypoint... - /*if (m_pCurrentWaypoint->pNode->iFlags & W_FL_FLOOD) - {UNDONE? - vec aim = m_pCurrentWaypoint->pNode->v_origin; - aim.x+=0.5f; - aim.y+=0.5f; - AimToVec(aim); - } - else*/ - AimToVec(m_pCurrentWaypoint->pNode->v_origin); - - if (m_fYawToTurn <= 25.0f) - m_iWaypointHeadLastTurnLessTime = lastmillis; - - // Bot had to turn much for a while? - if ((m_iWaypointHeadLastTurnLessTime > 0) && - (m_iWaypointHeadLastTurnLessTime < (lastmillis - 1000))) - { - m_iWaypointHeadPauseTime = lastmillis + 200; - m_iWaypointTime += 200; - } - - if (m_iWaypointHeadPauseTime >= lastmillis) - { - m_pMyEnt->move = 0; - //conoutf("Pause in HeadToWaypoint()"); - } - else - { - // Check if bot has to jump - vec from = m_pMyEnt->o; - from.z -= (m_pMyEnt->eyeheight - 1.25f); - float flEndDist; - if (!IsVisible(from, FORWARD, 3.0f, false, &flEndDist) && - (GetDistance(from, m_pCurrentWaypoint->pNode->v_origin) > flEndDist)) - { - m_pMyEnt->jumpnext = true; - condebug("Low wall in HeadToWaypoint()"); - } - - // Check if bot has to strafe - if (m_iStrafeTime > lastmillis) - SetMoveDir(m_iMoveDir, true); - else - { - m_iStrafeTime = 0; - m_iMoveDir = DIR_NONE; - - vec forward, up, right; - AnglesToVectors(GetViewAngles(), forward, right, up); - - float flLeftDist = -1.0f, flRightDist = -1.0f; - vec dir = right; - bool bStrafe = false; - int iStrafeDir = 0; - - vmul(dir, m_pMyEnt->radius); - - // Check left - from = m_pMyEnt->o; - vsub(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) - flLeftDist = -1.0f; - - // Check right - from = m_pMyEnt->o; - vadd(from, dir); - if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) - flRightDist = -1.0f; - - if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) - { - if (flLeftDist < flRightDist) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist < flLeftDist) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - else - { - // Randomly choose a strafe direction - bStrafe = true; - if (RandomLong(0, 1)) - iStrafeDir = LEFT; - else - iStrafeDir = RIGHT; - } - } - else if (flLeftDist != -1.0f) - { - // Strafe right - bStrafe = true; - iStrafeDir = RIGHT; - } - else if (flRightDist != -1.0f) - { - // Strafe left - bStrafe = true; - iStrafeDir = LEFT; - } - - if (bStrafe) - { - SetMoveDir(iStrafeDir, true); - m_iMoveDir = iStrafeDir; - m_iStrafeTime = lastmillis + RandomLong(30, 50); - } - } - } - - return true; -} - -// returns true when done or failure -bool CBot::HeadToGoal() -{ - // Does the bot has a goal(waypoint)? - if (m_pCurrentGoalWaypoint) - { - if (ReachedGoalWP()) - { - return false; - } - else - { - if (CurrentWPIsValid()) - { - if (m_bCalculatingAStarPath) - { - // Bot is calculating a new path, just stand still for now - ResetMoveSpeed(); - m_iWaypointTime += 200; - - // Done calculating the path? - if (AStar()) - { - if (m_AStarNodeList.Empty()) - { - return false; // Couldn't find a path - } - //else - // SetCurrentWaypoint(m_AStarNodeList.Pop()); - } - else - return true; // else just wait a little bit longer - } - //ResetMoveSpeed(); - //return true; - } - else - { - // Current waypoint isn't reachable, search new one - waypoint_s *pWP = NULL; - -#ifdef WP_FLOOD - if (m_pCurrentGoalWaypoint->pNode->iFlags & W_FL_FLOOD) - pWP = GetNearestFloodWP(8.0f); - else -#endif - pWP = GetNearestWaypoint(15.0f); - - if (!pWP || (pWP == m_pCurrentWaypoint)) - return false; - - SetCurrentWaypoint(pWP); - if (AStar()) - { - if (m_AStarNodeList.Empty()) return false; - } - else - { - m_iWaypointTime += 200; - ResetMoveSpeed(); - return true; - } - } - - //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); - } - } - else - return false; - - return HeadToWaypoint(); -} - -// return true when done calculating -bool CBot::AStar() -{ - if (!m_pCurrentGoalWaypoint || !m_pCurrentWaypoint) - { - if (m_bCalculatingAStarPath) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - } - return true; - } - - // Ideas by PMB : - // * Make locals static to speed up a bit - // * MaxCycles per frame and make it fps dependent - - static int iMaxCycles; - static int iCurrentCycles; - static short newg; - static waypoint_s *n, *n2; - static TLinkedList::node_s *pPath = NULL; - static bool bPathFailed; - - iMaxCycles = BotManager.m_iFrameTime / 10; - if (iMaxCycles < 10) iMaxCycles = 10; - //condebug("MaxCycles: %d", iMaxCycles); - iCurrentCycles = 0; - bPathFailed = false; - - if (!m_bCalculatingAStarPath) - { - if ((BotManager.m_sUsingAStarBotsCount+1) > BotManager.m_sMaxAStarBots) - { - return true; - } - - BotManager.m_sUsingAStarBotsCount++; - - CleanAStarLists(false); - - m_pCurrentWaypoint->g[0] = m_pCurrentWaypoint->g[1] = 0; - m_pCurrentWaypoint->pParent[0] = m_pCurrentWaypoint->pParent[1] = NULL; - m_pCurrentGoalWaypoint->g[0] = m_pCurrentGoalWaypoint->g[1] = 0; - m_pCurrentGoalWaypoint->pParent[0] = m_pCurrentGoalWaypoint->pParent[1] = NULL; - - m_AStarNodeList.DeleteAllNodes(); - - m_AStarOpenList[0].Clear(); - m_AStarOpenList[1].Clear(); - m_AStarClosedList[0].DeleteAllNodes(); - m_AStarClosedList[1].DeleteAllNodes(); - - m_AStarOpenList[0].AddEntry(m_pCurrentWaypoint, - GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); - m_AStarOpenList[1].AddEntry(m_pCurrentGoalWaypoint, - GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); - - m_pCurrentWaypoint->bIsOpen[0] = m_pCurrentGoalWaypoint->bIsOpen[1] = true; - m_pCurrentWaypoint->bIsOpen[1] = m_pCurrentGoalWaypoint->bIsOpen[0] = false; - m_pCurrentWaypoint->bIsClosed[0] = m_pCurrentGoalWaypoint->bIsClosed[1] = false; - m_pCurrentWaypoint->bIsClosed[1] = m_pCurrentGoalWaypoint->bIsClosed[0] = false; - } - - while(!m_AStarOpenList[0].Empty()) - { - if (iCurrentCycles >= (iMaxCycles/2)) - { - m_bCalculatingAStarPath = true; - break; - } - - n = m_AStarOpenList[0].Pop(); - n->bIsOpen[0] = false; - - if (n->pNode->FailedGoalList.IsInList(m_pCurrentGoalWaypoint->pNode)) - { - bPathFailed = true; - break; // Can't make path to goal - } - - // Done with calculating - if (n == m_pCurrentGoalWaypoint) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n) - { - m_AStarNodeList.PushNode(n); - n = n->pParent[0]; - } - - CleanAStarLists(false); - - return true; - } - - pPath = n->pNode->ConnectedWPs.GetFirst(); - while(pPath) - { - if ((pPath->Entry->iFlags & W_FL_INTAG) && - SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) - { - pPath = pPath->next; - continue; - } - - n2 = GetWPFromNode(pPath->Entry); - - if (!n2) - { - pPath = pPath->next; - continue; - } - - if (n2->bIsClosed[1]) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n2) - { - m_AStarNodeList.AddNode(n2); - n2 = n2->pParent[1]; - } - - while(n) - { - m_AStarNodeList.PushNode(n); - n = n->pParent[0]; - } - - CleanAStarLists(false); - - return true; - } - - newg = n->g[0] + (short)AStarCost(n, n2); - - if ((n2->g[0] <= newg) && (n2->bIsOpen[0] || n2->bIsClosed[0])) - { - pPath = pPath->next; - continue; - } - - n2->pParent[0] = n; - n2->g[0] = newg; - n2->bIsClosed[0] = false; - - if (!n2->bIsOpen[0]) - { - m_AStarOpenList[0].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, - m_pCurrentGoalWaypoint->pNode->v_origin)); - n2->bIsOpen[0] = true; - } - pPath = pPath->next; - } - - m_AStarClosedList[0].PushNode(n); - n->bIsClosed[0] = true; - iCurrentCycles++; - } - - if (!bPathFailed) - { - while(!m_AStarOpenList[1].Empty()) - { - if (iCurrentCycles >= iMaxCycles) - { - m_bCalculatingAStarPath = true; - return false; - } - - n = m_AStarOpenList[1].Pop(); - n->bIsOpen[1] = false; - - if (n->pNode->FailedGoalList.IsInList(m_pCurrentWaypoint->pNode)) - { - bPathFailed = true; - break; // Can't make path to goal - } - - // Done with calculating - if (n == m_pCurrentWaypoint) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n) - { - m_AStarNodeList.AddNode(n); - n = n->pParent[1]; - } - - CleanAStarLists(false); - - return true; - } - - pPath = n->pNode->ConnectedWPsWithMe.GetFirst(); - while(pPath) - { - if ((pPath->Entry->iFlags & W_FL_INTAG) && - SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) - { - pPath = pPath->next; - continue; - } - - n2 = GetWPFromNode(pPath->Entry); - - if (!n2) - { - pPath = pPath->next; - continue; - } - - if (n2->bIsClosed[0]) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - - while(n2) - { - m_AStarNodeList.PushNode(n2); - n2 = n2->pParent[0]; - } - - while(n) - { - m_AStarNodeList.AddNode(n); - n = n->pParent[1]; - } - - CleanAStarLists(false); - - return true; - } - - newg = n->g[1] + (short)AStarCost(n, n2); - - if ((n2->g[1] <= newg) && (n2->bIsOpen[1] || n2->bIsClosed[1])) - { - pPath = pPath->next; - continue; - } - - n2->pParent[1] = n; - n2->g[1] = newg; - n2->bIsClosed[1] = false; - - if (!n2->bIsOpen[1]) - { - m_AStarOpenList[1].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, - m_pCurrentWaypoint->pNode->v_origin)); - n2->bIsOpen[1] = true; - } - pPath = pPath->next; - } - - m_AStarClosedList[1].PushNode(n); - n->bIsClosed[1] = true; - iCurrentCycles++; - } - } - - // Failed making path - condebug("Path failed"); - - CleanAStarLists(true); - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - return true; -} - -float CBot::AStarCost(waypoint_s *pWP1, waypoint_s *pWP2) -{ - // UNDONE? - return (GetDistance(pWP1->pNode->v_origin, pWP2->pNode->v_origin) * pWP2->pNode->sCost); -} - -void CBot::CleanAStarLists(bool bPathFailed) -{ - while(m_AStarOpenList[0].Empty() == false) - { - waypoint_s *p = m_AStarOpenList[0].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - } - - while(m_AStarOpenList[1].Empty() == false) - { - waypoint_s *p = m_AStarOpenList[1].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - } - - while(m_AStarClosedList[0].Empty() == false) - { - waypoint_s *p = m_AStarClosedList[0].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - if (bPathFailed) - p->pNode->FailedGoalList.AddNode(m_pCurrentGoalWaypoint->pNode); - } - - while(m_AStarClosedList[1].Empty() == false) - { - waypoint_s *p = m_AStarClosedList[1].Pop(); - p->bIsOpen[0] = p->bIsOpen[1] = false; - p->bIsClosed[0] = p->bIsClosed[1] = false; - p->pParent[0] = p->pParent[1] = NULL; - p->g[0] = p->g[1] = 0; - if (bPathFailed) - p->pNode->FailedGoalList.AddNode(m_pCurrentWaypoint->pNode); - } -} - -void CBot::ResetWaypointVars() -{ - m_iWaypointTime = 0; - m_pCurrentWaypoint = NULL; - m_pCurrentGoalWaypoint = NULL; - m_pPrevWaypoints[0] = NULL; - m_pPrevWaypoints[1] = NULL; - m_pPrevWaypoints[2] = NULL; - m_pPrevWaypoints[3] = NULL; - m_pPrevWaypoints[4] = NULL; - m_fPrevWaypointDistance = 0; - m_iWaypointHeadLastTurnLessTime = 0; - m_iWaypointHeadPauseTime = 0; - if (m_bCalculatingAStarPath) - { - m_bCalculatingAStarPath = false; - BotManager.m_sUsingAStarBotsCount--; - } - m_AStarNodeList.DeleteAllNodes(); - CleanAStarLists(false); - m_bGoToDebugGoal = false; -} - -void CBot::SetCurrentWaypoint(node_s *pNode) -{ - waypoint_s *pWP = GetWPFromNode(pNode); -#ifndef RELEASE_BUILD - if (!pWP || !pNode) condebug("NULL WP In SetCurrentWP"); -#endif - - m_pCurrentWaypoint = pWP; - m_iWaypointTime = lastmillis; -} - -void CBot::SetCurrentWaypoint(waypoint_s *pWP) -{ -#ifndef RELEASE_BUILD - if (!pWP) condebug("NULL WP In SetCurrentWP(2)"); -#endif - - m_pCurrentWaypoint = pWP; - m_iWaypointTime = lastmillis; -} - -void CBot::SetCurrentGoalWaypoint(node_s *pNode) -{ - waypoint_s *pWP = GetWPFromNode(pNode); -#ifndef RELEASE_BUILD - if (!pWP || !pNode) condebug("NULL WP In SetCurrentGoalWP"); -#endif - - m_pCurrentGoalWaypoint = pWP; -} - -void CBot::SetCurrentGoalWaypoint(waypoint_s *pWP) -{ -#ifndef RELEASE_BUILD - if (!pWP) condebug("NULL WP In SetCurrentGoalWP(2)"); -#endif - - m_pCurrentGoalWaypoint = pWP; -} - -bool CBot::CurrentWPIsValid() -{ - if (!m_pCurrentWaypoint) - { - //condebug("Invalid WP: Is NULL"); - return false; - } - - // check if the bot has been trying to get to this waypoint for a while... - if ((m_iWaypointTime + 5000) < lastmillis) - { - condebug("Invalid WP: time over"); - return false; - } - -#ifndef RELEASE_BUILD - if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) - condebug("Invalid WP: Not visible"); -#endif - - return (IsVisible(m_pCurrentWaypoint->pNode->v_origin)); -} - -bool CBot::ReachedGoalWP() -{ - if ((!m_pCurrentWaypoint) || (!m_pCurrentGoalWaypoint)) - return false; - - if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) - return false; - - return ((GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin) <= 3.0f) && - (IsVisible(m_pCurrentGoalWaypoint->pNode->v_origin))); -} - -waypoint_s *CBot::GetWPFromNode(node_s *pNode) -{ - if (!pNode) return NULL; - - short x, y; - WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); - - TLinkedList::node_s *p = m_WaypointList[x][y].GetFirst(); - while(p) - { - if (p->Entry->pNode == pNode) - return p->Entry; - - p = p->next; - } - - return NULL; -} - -waypoint_s *CBot::GetNearestWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (p->Entry->pNode->iFlags & W_FL_FLOOD) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_src, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -waypoint_s *CBot::GetNearestTriggerWaypoint(vec v_src, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_src, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->pNode->iFlags & W_FL_TRIGGER)) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_src, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -// Makes a waypoint list for this bot based on the list from WaypointClass -void CBot::SyncWaypoints() -{ - short x, y; - TLinkedList::node_s *p; - - // Clean everything first - for (x=0;xpNode = p->Entry; - m_WaypointList[x][y].AddNode(pWP); - -#ifndef RELEASE_BUILD - if (!GetWPFromNode(p->Entry)) condebug("Error adding bot wp!"); -#endif - - p = p->next; - } - } - } -} - -#ifdef WP_FLOOD -waypoint_s *CBot::GetNearestFloodWP(vec v_origin, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->pNode->iFlags & W_FL_FLOOD)) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -waypoint_s *CBot::GetNearestTriggerFloodWP(vec v_origin, float flRange) -{ - TLinkedList::node_s *p; - waypoint_s *pNearest = NULL; - short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); - float flNearestDist = 9999.99, flDist; - - WaypointClass.GetNodeIndexes(v_origin, &i, &j); - MinI = i - Offset; - MaxI = i + Offset; - MinJ = j - Offset; - MaxJ = j + Offset; - - if (MinI < 0) - MinI = 0; - if (MaxI > MAX_MAP_GRIDS - 1) - MaxI = MAX_MAP_GRIDS - 1; - if (MinJ < 0) - MinJ = 0; - if (MaxJ > MAX_MAP_GRIDS - 1) - MaxJ = MAX_MAP_GRIDS - 1; - - for (int x=MinI;x<=MaxI;x++) - { - for(int y=MinJ;y<=MaxJ;y++) - { - p = m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (!(p->Entry->pNode->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) - { - p = p->next; - continue; - } - - flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); - if ((flDist < flNearestDist) && (flDist <= flRange)) - { - if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) - { - pNearest = p->Entry; - flNearestDist = flDist; - } - } - - p = p->next; - } - } - } - return pNearest; -} - -void CBot::GoToDebugGoal(vec o) -{ - ResetWaypointVars(); - - node_s *wp = WaypointClass.GetNearestWaypoint(m_pMyEnt, 20.0f); - node_s *goalwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); - - if (!wp || !goalwp) - { - wp = WaypointClass.GetNearestFloodWP(m_pMyEnt, 8.0f); - goalwp = WaypointClass.GetNearestFloodWP(player1, 5.0f); - } - if (!wp || !goalwp) { condebug("No near WP"); return; } - - SetCurrentWaypoint(wp); - SetCurrentGoalWaypoint(goalwp); - m_vGoal = o; - m_bGoToDebugGoal = true; -} -#endif - -// Bot class end +// +// C++ Implementation: bot_waypoint +// +// Description: Waypoint class for bots +// +// +// Author: Rick +// + +#include "../cube.h" + +vec v_debuggoal = g_vecZero; + +#if defined AC_CUBE +CACWaypointClass WaypointClass; +#elif defined VANILLA_CUBE +CCubeWaypointClass WaypointClass; +#endif + +// Waypoint class begin + +CWaypointClass::CWaypointClass(void) : m_bDrawWaypoints(false), m_bDrawWPPaths(true), + m_bAutoWaypoint(false), m_bAutoPlacePaths(true), + m_bDrawWPText(false), m_vLastCreatedWP(g_vecZero), + m_bFlooding(false), m_bFilteringNodes(false), + m_iFloodStartTime(0), m_iCurFloodX(0), + m_iCurFloodY(0), m_iFloodSize(0), + m_iFilteredNodes(0), m_iWaypointCount(0) +{ + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + while(!m_Waypoints[i][j].Empty()) + delete m_Waypoints[i][j].Pop(); + } + } + + m_szMapName[0] = 0; +} + +void CWaypointClass::Init() +{ + Clear(); // Free previously allocated path memory + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + while(!m_Waypoints[i][j].Empty()) + delete m_Waypoints[i][j].Pop(); + } + } + + m_fPathDrawTime = 0.0; + m_iWaypointCount = 0; + m_vLastCreatedWP = g_vecZero; +} + +void CWaypointClass::Clear() +{ + for(int i=0;iConnectedWPs.DeleteAllNodes(); + p->ConnectedWPsWithMe.DeleteAllNodes(); + BotManager.DelWaypoint(p); + delete p; + } + } + } +} + +// returns true if waypoints succesfull loaded +bool CWaypointClass::LoadWaypoints() +{ + FILE *bfp; + char szWPFileName[64]; + char filename[256]; + waypoint_header_s header; + short num, triggernr, yaw; + int i, j, flags; + vec from, to; + + strcpy(szWPFileName, m_szMapName); + strcat(szWPFileName, ".wpt"); + + BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); + + bfp = fopen(filename, "rb"); + + BotManager.m_sCurrentTriggerNr = -1; + + // if file exists, read the waypoint structure from it + if (bfp != NULL) + { + fread(&header, sizeof(header), 1, bfp); + + header.szFileType[10] = 0; + if (strcmp(header.szFileType, "cube_bot") == 0) + { + header.szMapName[31] = 0; + + if (strcmp(header.szMapName, m_szMapName) == 0) + { + Init(); // remove any existing waypoints + + // Check which waypoint file version this is, handle each of them different + if (header.iFileVersion == 1) + { + // First version, works with an big array and has a limit + + waypoint_version_1_s WPs[1024]; + int path_index; + + m_iWaypointCount = header.iWPCount; + for (i=0; i < header.iWPCount; i++) + { + fread(&WPs[i], sizeof(WPs[0]), 1, bfp); + WPs[i].ConnectedWPs.Reset(); // We get garbage when we read this from + // a file, so just clear it + + // Convert to new waypoint structure + node_s *pNode = new node_s(WPs[i].v_origin, WPs[i].iFlags, 0); + + short x, y; + GetNodeIndexes(pNode->v_origin, &x, &y); + + m_Waypoints[x][y].AddNode(pNode); + BotManager.AddWaypoint(pNode); + } + + // read and add waypoint paths... + for (i=0; i < m_iWaypointCount; i++) + { + // read the number of paths from this node... + fread(&num, sizeof(num), 1, bfp); + + // See which waypoint the current is + node_s *pCurrent = GetWaypointFromVec(WPs[i].v_origin); + + if (!pCurrent) + { + conoutf("Error: NULL path in waypoint file"); + continue; + } + + for (j=0; j < num; j++) + { + fread(&path_index, sizeof(path_index), 1, bfp); + + // See which waypoint this is + node_s *pTo = GetWaypointFromVec(WPs[path_index].v_origin); + + if (!pTo) + { + conoutf("Error: NULL path in waypoint file"); + continue; + } + + AddPath(pCurrent, pTo); + } + } + conoutf("Old waypoint version(%d) converted to new version(%d)", + header.iFileVersion, WAYPOINT_VERSION); + conoutf("Use the wpsave command to convert permently"); + + } + else if (header.iFileVersion == 2) + { + m_iWaypointCount = header.iWPCount; + + for (i=0; i < header.iWPCount; i++) + { + fread(&from, sizeof(from), 1, bfp); // Read origin + fread(&flags, sizeof(flags), 1, bfp); // Read waypoint flags + fread(&triggernr, sizeof(triggernr), 1, bfp); // Read trigger nr + fread(&yaw, sizeof(yaw), 1, bfp); // Read target yaw + + node_s *pNode = new node_s(from, flags, triggernr, yaw); + + short x, y; + GetNodeIndexes(pNode->v_origin, &x, &y); + + m_Waypoints[x][y].AddNode(pNode); + BotManager.AddWaypoint(pNode); + + if ((BotManager.m_sCurrentTriggerNr == -1) || + (triggernr < BotManager.m_sCurrentTriggerNr)) + BotManager.m_sCurrentTriggerNr = triggernr; + } + + for (i=0; i < header.iWPCount; i++) + { + // read the number of paths from this node... + fread(&num, sizeof(num), 1, bfp); + fread(&from, sizeof(from), 1, bfp); + + if (!num) + continue; + + node_s *pCurrent = GetWaypointFromVec(from); + + if (!pCurrent) + { + conoutf("Error: NULL path in waypoint file"); + + for(j=0;j WAYPOINT_VERSION) + { + conoutf("Error: Waypoint file is newer then current, upgrade cube bot."); + fclose(bfp); + return false; + } + else + { + conoutf("Error: Unknown waypoint version for cube bot"); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoints aren't for map %s but for map %s", int(m_szMapName), + int(header.szMapName)); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoint file isn't for cube bot"); + //conoutf("Header FileType: %s", int(header.szFileType)); + + fclose(bfp); + return false; + } + + fclose(bfp); + + //RouteInit(); + } + else + { + conoutf("Waypoint file %s does not exist", int(filename)); + return false; + } + + if (BotManager.m_sCurrentTriggerNr == -1) + BotManager.m_sCurrentTriggerNr = 0; + + ReCalcCosts(); + + conoutf("Waypoints for map %s loaded", int(m_szMapName)); + return true; +} + +void CWaypointClass::SaveWaypoints() +{ + char filename[256]; + char mapname[64]; + waypoint_header_s header; + short int num; + TLinkedList::node_s *pPath; + + strcpy(header.szFileType, "cube_bot"); + + header.iFileVersion = WAYPOINT_VERSION; + + header.iFileFlags = 0; // not currently used + + header.iWPCount = m_iWaypointCount; + + memset(header.szMapName, 0, sizeof(header.szMapName)); + strncpy(header.szMapName, m_szMapName, 31); + header.szMapName[31] = 0; + + strcpy(mapname, m_szMapName); + strcat(mapname, ".wpt"); + + BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); + + FILE *bfp = fopen(filename, "wb"); + + if (!bfp) + { + conoutf("Error writing waypoint file, check if the directory \"bot/waypoint\" exists and " + "the right permissions are set"); + return; + } + + // write the waypoint header to the file... + fwrite(&header, sizeof(header), 1, bfp); + + // write the waypoint data to the file... + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // Write origin + fwrite(&p->Entry->iFlags, sizeof(p->Entry->iFlags), 1, bfp); // Write waypoint flags + fwrite(&p->Entry->sTriggerNr, sizeof(p->Entry->sTriggerNr), 1, bfp); // Write trigger nr + fwrite(&p->Entry->sYaw, sizeof(p->Entry->sYaw), 1, bfp); // Write target yaw + + p = p->next; + } + } + } + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + // save the waypoint paths... + + // count the number of paths from this node... + pPath = p->Entry->ConnectedWPs.GetFirst(); + num = p->Entry->ConnectedWPs.NodeCount(); + + fwrite(&num, sizeof(num), 1, bfp); // write the count + fwrite(&p->Entry->v_origin, sizeof(p->Entry->v_origin), 1, bfp); // write the origin of this path + + // now write out each path... + while (pPath != NULL) + { + fwrite(&pPath->Entry->v_origin, sizeof(pPath->Entry->v_origin), 1, bfp); + pPath = pPath->next; // go to next node in linked list + } + p = p->next; + } + } + } + + fclose(bfp); +} + +bool CWaypointClass::LoadWPExpFile() +{ + FILE *bfp; + char szWPFileName[64]; + char filename[256]; + waypoint_header_s header; + short int num; + int i, j; + vec from, to; + + if (m_iWaypointCount == 0) + return false; + + strcpy(szWPFileName, m_szMapName); + strcat(szWPFileName, ".exp"); + + BotManager.MakeBotFileName(szWPFileName, "waypoints", NULL, filename); + + bfp = fopen(filename, "rb"); + + // if file exists, read the waypoint structure from it + if (bfp != NULL) + { + fread(&header, sizeof(header), 1, bfp); + + header.szFileType[10] = 0; + if (strcmp(header.szFileType, "cube_bot") == 0) + { + header.szMapName[31] = 0; + + if (strcmp(header.szMapName, m_szMapName) == 0) + { + // Check which waypoint file version this is, handle each of them different + if (header.iFileVersion == 1) + { + for (i=0; i < header.iWPCount; i++) + { + // read the number of paths from this node... + fread(&num, sizeof(num), 1, bfp); + fread(&from, sizeof(vec), 1, bfp); + + if (!num) continue; + + node_s *pCurrent = GetWaypointFromVec(from); + + if (!pCurrent) + { + conoutf("Error: NULL node in waypoint experience file"); + continue; + } + + for (j=0; j < num; j++) + { + fread(&to, sizeof(vec), 1, bfp); + node_s *p = GetWaypointFromVec(to); + if (p) + { + pCurrent->FailedGoalList.AddNode(p); + } + } + } + } + else if (header.iFileVersion > EXP_WP_VERSION) + { + conoutf("Error: Waypoint experience file is newer then current, upgrade cube bot."); + fclose(bfp); + return false; + } + else + { + conoutf("Error: Unknown waypoint experience file version for cube bot"); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoint experience file isn't for map %s but for map %s", int(m_szMapName), + int(header.szMapName)); + fclose(bfp); + return false; + } + } + else + { + conoutf("Waypoint experience file isn't for cube bot"); + //conoutf("Header FileType: %s", int(header.szFileType)); + + fclose(bfp); + return false; + } + + fclose(bfp); + + //RouteInit(); + } + else + { + conoutf("Waypoint experience file %s does not exist", int(filename)); + return false; + } + + conoutf("Waypoint experience file for map %s loaded", int(m_szMapName)); + return true; +} + +void CWaypointClass::SaveWPExpFile() +{ + if (!m_iWaypointCount) + return; + + char filename[256]; + char mapname[64]; + waypoint_header_s header; + short int num; + + strcpy(header.szFileType, "cube_bot"); + + header.iFileVersion = EXP_WP_VERSION; + + header.iFileFlags = 0; // not currently used + + header.iWPCount = m_iWaypointCount; + + memset(header.szMapName, 0, sizeof(header.szMapName)); + strncpy(header.szMapName, m_szMapName, 31); + header.szMapName[31] = 0; + + strcpy(mapname, m_szMapName); + strcat(mapname, ".exp"); + + BotManager.MakeBotFileName(mapname, "waypoints", NULL, filename); + + FILE *bfp = fopen(filename, "wb"); + + if (!bfp) + { + conoutf("Error writing waypoint experience file, check if the directory \"bot/waypoint\" exists and " + "the right permissions are set"); + return; + } + + // write the waypoint header to the file... + fwrite(&header, sizeof(header), 1, bfp); + + // save the waypoint experience data... + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + // count the number of paths from this node... + TLinkedList::node_s *p2 = p->Entry->FailedGoalList.GetFirst(); + num = p->Entry->FailedGoalList.NodeCount(); + + if (!num || !p2) continue; + + fwrite(&num, sizeof(num), 1, bfp); // write the count + fwrite(&p->Entry->v_origin, sizeof(vec), 1, bfp); // write the origin of this node + + while (p2 != NULL) + { + // Write out the node which a bot can't reach with the current node + fwrite(&p2->Entry->v_origin, sizeof(vec), 1, bfp); + p2 = p2->next; // go to next node in linked list + } + p = p->next; + } + } + } + + fclose(bfp); +} + +void CWaypointClass::Think() +{ + if (dedserv) return; + +#ifdef WP_FLOOD + FloodThink(); +#endif + + if (m_bAutoWaypoint) // is auto waypoint on? + { + // find the distance from the last used waypoint + float flDistance = GetDistance(m_vLastCreatedWP, player1->o); + + bool NoLastCreatedWP = ((m_vLastCreatedWP.x == 0) && (m_vLastCreatedWP.y == 0) && + (m_vLastCreatedWP.z == 0)); + + if ((flDistance > 3.0f) || NoLastCreatedWP) + { + // check that no other reachable waypoints are nearby... + if (!GetNearestWaypoint(player1, 10.0f)) + { + AddWaypoint(player1->o, true); // place a waypoint here + } + } + } + + // Draw info for nearest waypoint? + if (m_bDrawWPText) + { + node_s *nearestwp = GetNearestWaypoint(player1, 20.0f); + +#ifdef WP_FLOOD + if (!nearestwp) + nearestwp = GetNearestFloodWP(player1, 10.0f); +#endif + + if (nearestwp) + { + char szWPInfo[256]; + sprintf(szWPInfo, "Distance nearest waypoint: %f", GetDistance(player1->o, nearestwp->v_origin)); + AddScreenText(szWPInfo); + + strcpy(szWPInfo, "Flags: "); + if (nearestwp->iFlags & W_FL_TELEPORT) + strcat(szWPInfo, "Teleport "); + if (nearestwp->iFlags & W_FL_TELEPORTDEST) + strcat(szWPInfo, "Teleport destination "); + if (nearestwp->iFlags & W_FL_JUMP) + strcat(szWPInfo, "Jump "); + if (nearestwp->iFlags & W_FL_TRIGGER) + { + char sz[32]; + sprintf(sz, "Trigger(nr %d) ", nearestwp->sTriggerNr); + strcat(szWPInfo, sz); + } + if (nearestwp->iFlags & W_FL_INTAG) + strcat(szWPInfo, "In tagged cube(s) "); + if (strlen(szWPInfo) == 7) + strcat(szWPInfo, "None"); + + AddScreenText(szWPInfo); + + if (nearestwp->sYaw != -1) + AddScreenText("yaw %d", nearestwp->sYaw); + + sprintf(szWPInfo, "Waypoint has %d connections", + nearestwp->ConnectedWPs.NodeCount()); + AddScreenText(szWPInfo); + AddScreenText("Base Cost: %d", nearestwp->sCost); + } + } + + if (m_bDrawWaypoints) // display the waypoints if turned on... + { + DrawNearWaypoints(); + } +} + +void CWaypointClass::DrawNearWaypoints() +{ + TLinkedList::node_s *pNode; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = ceil(15.0f / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(player1->o, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + node_s *nearestwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + +#ifdef WP_FLOOD + if (!nearestwp) + nearestwp = GetNearestFloodWP(player1, 20.0f); +#endif + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + flDist = GetDistance(player1->o, pNode->Entry->v_origin); + if (flDist <= 15) + { + vec o = pNode->Entry->v_origin; + vec e = o; + o.z -= 2; + e.z += 2; + + if (pNode->Entry->iFlags & W_FL_JUMP) + { + // draw a red waypoint + linestyle(2.5f, 0xFF, 0x40, 0x40); + } + else if (nearestwp == pNode->Entry) + { + // draw a green waypoint + linestyle(2.5f, 0x40, 0xFF, 0x40); + } + else + { + // draw a blue waypoint + linestyle(2.5f, 0x40, 0x40, 0xFF); + } + + line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); + + if (flNearestDist > flDist) + { + flNearestDist = flDist; + pNearest = pNode->Entry; + } + } + + pNode = pNode->next; + } + } + } + + if (pNearest) + { + pNode = pNearest->ConnectedWPs.GetFirst(); + + linestyle(2.0f, 0xFF, 0xFF, 0xFF); + + while(pNode) + { + vec o = pNode->Entry->v_origin; + vec e = pNearest->v_origin; + + line(int(o.x), int(o.y), int(o.z), int(e.x), int(e.y), int(e.z)); + + pNode = pNode->next; + } + + // Has this waypoint an target yaw? + if (pNearest->sYaw != -1) + { + vec angles = { 0, pNearest->sYaw, 0 }; + vec from = pNearest->v_origin, end = pNearest->v_origin; + vec forward, right, up; + + from.z = end.z = (pNearest->v_origin.z-1.0f); + + AnglesToVectors(angles, forward, right, up); + vmul(forward, 5.0f); + vadd(end, forward); + + linestyle(2.0f, 0xFF, 0x40, 0x40); + line(int(from.x), int(from.y), int(from.z), int(end.x), int(end.y), int(end.z)); + } + } + +#ifndef RELEASE_BUILD + // check if path waypointing is on... + if (m_bDrawWPPaths) + { + // Draw path from first bot + if (bots.length() && bots[0] && bots[0]->pBot && bots[0]->pBot->m_pCurrentWaypoint && + bots[0]->pBot->m_pCurrentGoalWaypoint) + { + CBot *pB = bots[0]->pBot; + if (!pB->m_bCalculatingAStarPath && !pB->m_AStarNodeList.Empty()) + { + TLinkedList::node_s *pNode = pB->m_AStarNodeList.GetFirst(), *pNext; + + linestyle(2.5f, 0xFF, 0x40, 0x40); + + line((int)pB->m_pCurrentWaypoint->pNode->v_origin.x, + (int)pB->m_pCurrentWaypoint->pNode->v_origin.y, + (int)pB->m_pCurrentWaypoint->pNode->v_origin.z, + (int)pNode->Entry->pNode->v_origin.x, + (int)pNode->Entry->pNode->v_origin.y, + (int)pNode->Entry->pNode->v_origin.z); + + while(pNode && pNode->next) + { + pNext = pNode->next; + vec &v1 = pNode->Entry->pNode->v_origin; + vec &v2 = pNext->Entry->pNode->v_origin; + + line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); + + pNode = pNode->next; + } + } + } + } + + extern bool intermission; + if (intermission) return; + + for(int i=0;iPrevLocations.prevloc[i], g_vecZero)) continue; + vec v1 = player1->PrevLocations.prevloc[i]; + v1.z -= 1.0f; + vec v2 = v1; + v2.z += 2.0f; + linestyle(2.5f, 0xFF, 0x40, 0x40); + line(int(v1.x), int(v1.y), int(v1.z), int(v2.x), int(v2.y), int(v2.z)); + } +#endif +} + +// Add waypoint at location o, returns pointer of created wp +node_s *CWaypointClass::AddWaypoint(vec o, bool connectwp) +{ + short x, y; + int flags = 0; + if (S((int)o.x, (int)o.y)->tag) flags |= W_FL_INTAG; + + node_s *pNode = new node_s(o, flags, 0); + m_vLastCreatedWP = o; + + GetNodeIndexes(o, &x, &y); + + m_Waypoints[x][y].AddNode(pNode); + BotManager.AddWaypoint(pNode); + + m_iWaypointCount++; + + if (connectwp && m_bAutoPlacePaths) + { + // Connect new waypoint with other near waypoints. + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + + while(p) + { + if (p->Entry == pNode) + { + p = p->next; + continue; // skip the waypoint that was just added + } + + // check if the waypoint is reachable from the new one (one-way) + if (WPIsReachable(o, p->Entry->v_origin)) + AddPath(pNode, p->Entry); // Add a path from a to b + if (WPIsReachable(p->Entry->v_origin, pNode->v_origin)) + AddPath(p->Entry, pNode); // Add a path from b to a + + p = p->next; + } + } + } + } + + return pNode; +} + +void CWaypointClass::DeleteWaypoint(dynent *d) +{ + node_s *pWP; + + pWP = GetNearestWaypoint(d, 7.0f); + + if (!pWP) + { + conoutf("Error: Couldn´t find near waypoint"); + return; + } + + BotManager.DelWaypoint(pWP); + + // delete any paths that lead to this index... + DeletePath(pWP); + + short x, y; + GetNodeIndexes(pWP->v_origin, &x, &y); + + m_Waypoints[x][y].DeleteEntry(pWP); + + pWP->ConnectedWPs.DeleteAllNodes(); + pWP->ConnectedWPsWithMe.DeleteAllNodes(); + + m_iWaypointCount--; + delete pWP; +} + +void CWaypointClass::AddPath(node_s *pWP1, node_s *pWP2) +{ + pWP1->ConnectedWPs.AddNode(pWP2); + pWP2->ConnectedWPsWithMe.AddNode(pWP1); +} + +// Deletes all paths connected to the given waypoint +void CWaypointClass::DeletePath(node_s *pWP) +{ + TLinkedList::node_s *p; + + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + p = m_Waypoints[i][j].GetFirst(); + while (p) + { + p->Entry->ConnectedWPs.DeleteEntry(pWP); + pWP->ConnectedWPsWithMe.DeleteEntry(p->Entry); + p = p->next; + } + } + } +} + +// Deletes path between 2 waypoints(1 way) +void CWaypointClass::DeletePath(node_s *pWP1, node_s *pWP2) +{ + pWP1->ConnectedWPs.DeleteEntry(pWP2); + pWP2->ConnectedWPsWithMe.DeleteEntry(pWP1); +} + +void CWaypointClass::ManuallyCreatePath(dynent *d, int iCmd, bool TwoWay) +{ + static node_s *waypoint1 = NULL; // initialized to unassigned + static node_s *waypoint2 = NULL; // initialized to unassigned + + if (iCmd == 1) // assign source of path + { + waypoint1 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint1) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + return; + } + + if (iCmd == 2) // assign dest of path and make path + { + if (!waypoint1) + { + conoutf("Error: First waypoint unset"); + return; + } + + waypoint2 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint2) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + AddPath(waypoint1, waypoint2); + + if (TwoWay) + AddPath(waypoint2, waypoint1); + } +} + +void CWaypointClass::ManuallyDeletePath(dynent *d, int iCmd, bool TwoWay) +{ + static node_s *waypoint1 = NULL; // initialized to unassigned + static node_s *waypoint2 = NULL; // initialized to unassigned + + if (iCmd == 1) // assign source of path + { + waypoint1 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint1) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + return; + } + + if (iCmd == 2) // assign dest of path and delete path + { + if (!waypoint1) + { + conoutf("Error: First waypoint unset"); + return; + } + + waypoint2 = GetNearestWaypoint(d, 7.0f); + + if (!waypoint2) + { + conoutf("Error: Couldn't find near waypoint"); + return; + } + + DeletePath(waypoint1, waypoint2); + + if (TwoWay) + DeletePath(waypoint2, waypoint1); + } +} + +bool CWaypointClass::WPIsReachable(vec from, vec to) +{ + traceresult_s tr; + float curr_height, last_height; + + float distance = GetDistance(from, to); + + // is the destination close enough? + if (distance < REACHABLE_RANGE) + { + if (IsVisible(from, to)) + { + if (UnderWater(from) && UnderWater(to)) + { + // No need to worry about heights in water + return true; + } +/* + if (to.z > (from.z + JUMP_HEIGHT)) + { + vec v_new_src = to; + vec v_new_dest = to; + + v_new_dest.z = v_new_dest.z - (JUMP_HEIGHT + 1.0f); + + // check if we didn't hit anything, if so then it's in mid-air + if (::IsVisible(v_new_src, v_new_dest, NULL)) + { + conoutf("to is in midair"); + debugbeam(from, to); + return false; // can't reach this one + } + } +*/ + + // check if distance to ground increases more than jump height + // at points between from and to... + + vec v_temp = to; vsub(v_temp, from); + vec v_direction = Normalize(v_temp); // 1 unit long + vec v_check = from; + vec v_down = from; + + v_down.z = v_down.z - 100.0f; // straight down + + TraceLine(v_check, v_down, NULL, false, &tr); + + // height from ground + last_height = GetDistance(v_check, tr.end); + + distance = GetDistance(to, v_check); // distance from goal + + while (distance > 2.0f) + { + // move 2 units closer to the goal + v_temp = v_direction; + vmul(v_temp, 2.0f); + vadd(v_check, v_temp); + + v_down = v_check; + v_down.z = v_down.z - 100.0f; + + TraceLine(v_check, v_down, NULL, false, &tr); + + curr_height = GetDistance(v_check, tr.end); + + // is the difference in the last height and the current height + // higher that the jump height? + if ((last_height - curr_height) >= JUMP_HEIGHT) + { + // can't get there from here... + //conoutf("traces failed to to"); + return false; + } + + last_height = curr_height; + + distance = GetDistance(to, v_check); // distance from goal + } + + return true; + } + } + + return false; +} + +node_s *CWaypointClass::GetNearestWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *pNode; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if (pNode->Entry->iFlags & W_FL_FLOOD) + { + pNode = pNode->next; + continue; + } + + flDist = GetDistance(v_src, pNode->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) + { + pNearest = pNode->Entry; + flNearestDist = flDist; + } + } + + pNode = pNode->next; + } + } + } + return pNearest; +} + +node_s *CWaypointClass::GetNearestTriggerWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *pNode; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if ((pNode->Entry->iFlags & W_FL_FLOOD) || !(pNode->Entry->iFlags & W_FL_TRIGGER)) + { + pNode = pNode->next; + continue; + } + + flDist = GetDistance(v_src, pNode->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_src, pNode->Entry->v_origin, NULL)) + { + pNearest = pNode->Entry; + flNearestDist = flDist; + } + } + + pNode = pNode->next; + } + } + } + return pNearest; +} + +node_s *CWaypointClass::GetWaypointFromVec(const vec &v_src) +{ + static TLinkedList::node_s *pNode; + static short x, y; + + GetNodeIndexes(v_src, &x, &y); + + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if (vis(pNode->Entry->v_origin, v_src)) + return pNode->Entry; + + pNode = pNode->next; + } + return NULL; +} + +void CWaypointClass::CalcCost(node_s *pNode) +{ + float flCost = 10.0f; + + // Check nearby cubes... + int x = int(pNode->v_origin.x); + int y = int(pNode->v_origin.y); + int a, b, row; + + for (row=0;row<=1;row++) + { + if (row==0) b = y - 6; + else b = y + 6; + + for (a=(x-6);a<=(x+6);a++) + { + if (OUTBORD(a, b)) continue; + + vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; + + // See if there is a obstacle(cube or mapmodel) nearby + traceresult_s tr; + TraceLine(pNode->v_origin, to, NULL, false, &tr); + if (tr.collided) + { + float flFraction = (GetDistance(pNode->v_origin, tr.end) / + GetDistance(pNode->v_origin, to)); + flCost += (1.0f-flFraction)*0.5f; + } + + if ((abs(a) > 4) || (abs(b) > 4)) continue; + + vec from = to; + to.z -= (JUMP_HEIGHT - 1.0f); + TraceLine(from, to, NULL, false, &tr); + if (!tr.collided) + flCost += 0.5f; + } + + if (row==0) a = x - 6; + else a = x + 6; + + for (b=(y-6);b<=(y+6);b++) + { + if (OUTBORD(a, b)) continue; + + vec to = { a, b, GetCubeFloor(a, b) + 1.0f }; + + // See if there is a obstacle(cube or mapmodel) nearby + traceresult_s tr; + TraceLine(pNode->v_origin, to, NULL, false, &tr); + if (tr.collided) + { + float flFraction = (GetDistance(pNode->v_origin, tr.end) / + GetDistance(pNode->v_origin, to)); + flCost += (1.0f-flFraction)*0.5f; + } + + if ((abs(a) > 4) || (abs(b) > 4)) continue; + + vec from = to; + to.z -= (JUMP_HEIGHT - 1.0f); + TraceLine(from, to, NULL, false, &tr); + if (!tr.collided) + flCost += 0.5f; + } + } + + if (UnderWater(pNode->v_origin)) // Water is annoying + flCost += 5.0f; + + pNode->sCost = (short)flCost; +} + +void CWaypointClass::ReCalcCosts(void) +{ + loopi(MAX_MAP_GRIDS) + { + loopj(MAX_MAP_GRIDS) + { + TLinkedList::node_s *p = m_Waypoints[i][j].GetFirst(); + while(p) + { + CalcCost(p->Entry); + p = p->next; + } + } + } +} + +#ifdef WP_FLOOD + +void CWaypointClass::StartFlood() +{ + if (m_bFlooding) return; + + conoutf("Starting flood, this may take a while on large maps...."); + m_bFlooding = true; + m_iFloodStartTime = SDL_GetTicks(); + m_iCurFloodX = m_iCurFloodY = MINBORD; + m_iFloodSize = 0; +} + +void CWaypointClass::FloodThink() +{ + if (!m_bFlooding) return; + + static int x, y, count; + count = 0; + + if (!m_bFilteringNodes) + { + // loop through ALL cubes and check if we should add a waypoint here + for (x=m_iCurFloodX; x<(ssize-MINBORD); x+=4) + { + if (count >= 3) + { + AddScreenText("Flooding map with waypoints... %.2f %%", + ((float)x / float(ssize-MINBORD)) * 100.0f); + m_iCurFloodX = x; + return; + } + + count++; + + for (y=MINBORD; y<(ssize-MINBORD); y+=4) + { + vec from = { x, y, GetCubeFloor(x, y)+2.0f }; + bool bFound = CanPlaceNodeHere(from); + + if (!bFound) + { + for (int a=x-2;a<=(x+2);a++) + { + for (int b=y-2;b<=(y+2);b++) + { + if (OUTBORD(a, b)) continue; + if ((a==x) && (b==y)) continue; + makevec(&from, a, b, GetCubeFloor(a, b) + 2.0f); + if (CanPlaceNodeHere(from)) + { + bFound = true; + break; + } + } + + if (bFound) break; + } + } + + if (!bFound) continue; + + // Add WP + int flags = W_FL_FLOOD; + if (S((int)from.x, (int)from.y)->tag) flags |= W_FL_INTAG; + + node_s *pWP = new node_s(from, flags, 0); + + short i, j; + GetNodeIndexes(from, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + } + } + + if (!m_bFilteringNodes) + { + m_bFilteringNodes = true; + m_iCurFloodX = m_iCurFloodY = 0; + m_iFilteredNodes = 0; + } + + count = 0; + + // Filter all nodes which aren't connected to any other nodes + for (x=m_iCurFloodX;x 3) + { + AddScreenText("Filtering useless waypoints and"); + AddScreenText("adding costs... %.2f %%", ((float)x / float(MAX_MAP_GRIDS)) * 100.0f); + m_iCurFloodX = x; + return; + } + + count++; + + for (y=0;y::node_s *pNode = m_Waypoints[x][y].GetFirst(), *pTemp; + while(pNode) + { + if (pNode->Entry->ConnectedWPs.Empty() || + pNode->Entry->ConnectedWPsWithMe.Empty()) + { + BotManager.DelWaypoint(pNode->Entry); + pTemp = pNode; + pNode = pNode->next; + delete pTemp->Entry; + m_Waypoints[x][y].DeleteNode(pTemp); + m_iFilteredNodes++; + m_iFloodSize -= sizeof(node_s); + m_iWaypointCount--; + continue; + } + else + CalcCost(pNode->Entry); + pNode = pNode->next; + } + } + } + + // Done with flooding + m_bFlooding = false; + m_bFilteringNodes = false; + + //ReCalcCosts(); + BotManager.PickNextTrigger(); + + m_iFloodSize += sizeof(m_Waypoints); + conoutf("Added %d wps in %d milliseconds", m_iWaypointCount, SDL_GetTicks()-m_iFloodStartTime); + conoutf("Filtered %d wps", m_iFilteredNodes); + + BotManager.CalculateMaxAStarCount(); + + char szSize[64]; + sprintf(szSize, "Total size: %.2f Kb", float(m_iFloodSize)/1024.0f); + conoutf(szSize); +} + +bool CWaypointClass::CanPlaceNodeHere(const vec &from) +{ + static short x, y, a, b; + static traceresult_s tr; + static vec to, v1, v2; + + x = fast_f2nat(from.x); + y = fast_f2nat(from.y); + + sqr *s = S(x, y); + if (SOLID(s)) + { + return false; + } + + if (fabs(s->ceil - s->floor) < player1->radius) + { + return false; + } + + if (GetNearestFloodWP(from, 2.0f, NULL)) return false; + + for (a=(x-1);a<=(x+1);a++) + { + for (b=(y-1);b<=(y+1);b++) + { + if ((x==a) && (y==b)) continue; + if (OUTBORD(a, b)) return false; + makevec(&v1, a, b, from.z); + v2 = v1; + v2.z -= 1000.0f; + + TraceLine(v1, v2, NULL, false, &tr, true); + to = tr.end; + + if ((a >= (x-1)) && (a <= (x+1)) && (b >= (y-1)) && (b <= (y+1))) + { + if (!tr.collided || (to.z < (from.z-4.0f))) + { + return false; + } + } + + to.z += 2.0f; + + if (from.z < (to.z-JUMP_HEIGHT)) + { + return false; + } + + TraceLine(from, to, NULL, false, &tr, true); + if (tr.collided) + return false; + } + } + + return true; +} + +void CWaypointClass::ConnectFloodWP(node_s *pWP) +{ + if (!pWP) return; + + static float flRange; + static TLinkedList::node_s *pNode; + static short i, j, MinI, MaxI, MinJ, MaxJ, x, y, Offset; + static float flDist; + static node_s *p; + + // Calculate range, based on distance to nearest node + p = GetNearestFloodWP(pWP->v_origin, 15.0f, pWP, true); + if (p) + { + flDist = GetDistance(pWP->v_origin, p->v_origin); + flRange = min(flDist+2.0f, 15.0f); + if (flRange < 5.0f) flRange = 5.0f; + } + else + return; + + Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + + GetNodeIndexes(pWP->v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (x=MinI;x<=MaxI;x++) + { + for(y=MinJ;y<=MaxJ;y++) + { + pNode = m_Waypoints[x][y].GetFirst(); + + while(pNode) + { + if (pNode->Entry == pWP) + { + pNode = pNode->next; + continue; + } + + flDist = GetDistance(pWP->v_origin, pNode->Entry->v_origin); + if (flDist <= flRange) + { + if (IsVisible(pWP->v_origin, pNode->Entry->v_origin, NULL, true)) + { + // Connect a with b + pWP->ConnectedWPs.AddNode(pNode->Entry); + pNode->Entry->ConnectedWPsWithMe.AddNode(pWP); + + // Connect b with a + pNode->Entry->ConnectedWPs.AddNode(pWP); + pWP->ConnectedWPsWithMe.AddNode(pNode->Entry); + + m_iFloodSize += (2 * sizeof(node_s *)); + } + } + + pNode = pNode->next; + } + } + } +} + +node_s *CWaypointClass::GetNearestFloodWP(vec v_origin, float flRange, node_s *pIgnore, + bool SkipTags) +{ + TLinkedList::node_s *p; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_Waypoints[x][y].GetFirst(); + + while(p) + { + if ((p->Entry == pIgnore) || (!(p->Entry->iFlags & W_FL_FLOOD))) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_origin, p->Entry->v_origin, NULL, SkipTags)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +node_s *CWaypointClass::GetNearestTriggerFloodWP(vec v_origin, float flRange) +{ + TLinkedList::node_s *p; + node_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_Waypoints[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (IsVisible(v_origin, p->Entry->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +void CWaypointClass::GetNodeIndexes(const vec &v_origin, short *i, short *j) +{ + // Function code by cheesy and PMB + //*i = abs((int)((int)(v_origin.x + (2*ssize)) / SECTOR_SIZE)); + //*j = abs((int)((int)(v_origin.y + (2*ssize)) / SECTOR_SIZE)); + //*i = (int)((v_origin.x) / ssize * MAX_MAP_GRIDS); + //*j = (int)((v_origin.y) / ssize * MAX_MAP_GRIDS); + *i = abs((int)((v_origin.x) / MAX_MAP_GRIDS)); + *j = abs((int)((v_origin.y) / MAX_MAP_GRIDS)); + + if (*i > MAX_MAP_GRIDS - 1) + *i = MAX_MAP_GRIDS - 1; + if (*j > MAX_MAP_GRIDS - 1) + *j = MAX_MAP_GRIDS - 1; +} + +#endif // WP_FLOOD +// Waypoint class end + +#if defined AC_CUBE + +// AC waypoint class begin +void CACWaypointClass::StartFlood() +{ + // UNDONE? + CWaypointClass::StartFlood(); +} + +// AC waypoint class end + +#elif defined VANILLA_CUBE + +// Cube waypoint class begin + +void CCubeWaypointClass::StartFlood() +{ + CWaypointClass::StartFlood(); + + // Add wps at triggers and teleporters and their destination + loopv(ents) + { + entity &e = ents[i]; + + if (OUTBORD(e.x, e.y)) continue; + + if (e.type == TELEPORT) + { + vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; + + // Find the teleport destination + int n = -1, tag = e.attr1, beenhere = -1; + for(;;) + { + n = findentity(TELEDEST, n+1); + if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); break; }; + if(beenhere<0) beenhere = n; + if(ents[n].attr2==tag) + { + teledestpos.x = ents[n].x; + teledestpos.y = ents[n].y; + teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; + break; + } + } + + if (vis(teledestpos, g_vecZero)) continue; + + int flags = (W_FL_FLOOD | W_FL_TELEPORT); + if (S((int)telepos.x, (int)telepos.y)->tag) flags |= W_FL_INTAG; + + // Add waypoint at teleporter and teleport destination + node_s *pWP = new node_s(telepos, flags, 0); + + short i, j; + GetNodeIndexes(telepos, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + flags = (W_FL_FLOOD | W_FL_TELEPORTDEST); + if (S((int)teledestpos.x, (int)teledestpos.y)->tag) flags |= W_FL_INTAG; + + node_s *pWP2 = new node_s(teledestpos, flags, 0); + + GetNodeIndexes(teledestpos, &i, &j); + m_Waypoints[i][j].PushNode(pWP2); + BotManager.AddWaypoint(pWP2); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) + AddPath(pWP, pWP2); + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + else if (e.type == CARROT) + { + vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + int flags = (W_FL_FLOOD | W_FL_TRIGGER); + if (S(e.x, e.y)->tag) flags |= W_FL_INTAG; + + node_s *pWP = new node_s(pos, flags, 0); + + short i, j; + GetNodeIndexes(pos, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + else if (e.type == MAPMODEL) + { + mapmodelinfo &mmi = getmminfo(e.attr2); + if(!&mmi || !mmi.h || !mmi.rad) continue; + + float floor = (float)(S(e.x, e.y)->floor+mmi.zoff+e.attr3)+mmi.h; + + float x1 = e.x - mmi.rad; + float x2 = e.x + mmi.rad; + float y1 = e.y - mmi.rad; + float y2 = e.y + mmi.rad; + + // UNDONE? + for (float x=(x1+1.0f);x<=(x2-1.0f);x++) + { + for (float y=(y1+1.0f);y<=(y2-1.0f);y++) + { + vec from = { x, y, floor+2.0f }; + if (GetNearestFloodWP(from, 2.0f, NULL)) continue; + + // Add WP + int flags = W_FL_FLOOD; + if (S((int)x, (int)y)->tag) flags |= W_FL_INTAG; + + node_s *pWP = new node_s(from, flags, 0); + + short i, j; + GetNodeIndexes(from, &i, &j); + m_Waypoints[i][j].PushNode(pWP); + BotManager.AddWaypoint(pWP); + m_iFloodSize += sizeof(node_s); + m_iWaypointCount++; + + // Connect with other nearby nodes + ConnectFloodWP(pWP); + } + } + } + } + CWaypointClass::StartFlood(); +} + +void CCubeWaypointClass::CreateWPsAtTeleporters() +{ + loopv(ents) + { + entity &e = ents[i]; + + if (e.type != TELEPORT) continue; + if (OUTBORD(e.x, e.y)) continue; + + vec telepos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }, teledestpos = g_vecZero; + + // Find the teleport destination + int n = -1, tag = e.attr1, beenhere = -1; + for(;;) + { + n = findentity(TELEDEST, n+1); + if(n==beenhere || n<0) { conoutf("no teleport destination for tag %d", tag); continue; }; + if(beenhere<0) beenhere = n; + if(ents[n].attr2==tag) + { + teledestpos.x = ents[n].x; + teledestpos.y = ents[n].y; + teledestpos.z = S(ents[n].x, ents[n].y)->floor+player1->eyeheight; + break; + } + } + + if (vis(teledestpos, g_vecZero)) continue; + + // Add waypoint at teleporter and teleport destination + node_s *telewp = AddWaypoint(telepos, false); + node_s *teledestwp = AddWaypoint(teledestpos, false); + + if (telewp && teledestwp) + { + // Connect the teleporter waypoint with the teleport-destination waypoint(1 way) + AddPath(telewp, teledestwp); + + // Flag waypoints + telewp->iFlags = W_FL_TELEPORT; + teledestwp->iFlags = W_FL_TELEPORTDEST; + } + } +} + +void CCubeWaypointClass::CreateWPsAtTriggers() +{ + loopv(ents) + { + entity &e = ents[i]; + + if (e.type != CARROT) continue; + if (OUTBORD(e.x, e.y)) continue; + + vec pos = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + node_s *wp = AddWaypoint(pos, false); + + if (wp) + { + // Flag waypoints + wp->iFlags = W_FL_TRIGGER; + } + } +} + +#endif + +// Cube waypoint class end + +// Waypoint commands begin + +void addwp(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.AddWaypoint(player1->o, true); +} + +COMMAND(addwp, ARG_NONE); + +void delwp(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.DeleteWaypoint(player1); +} + +COMMAND(delwp, ARG_NONE); + +void wpvisible(int on) +{ + WaypointClass.SetWaypointsVisible(bool(on)); +} + +COMMAND(wpvisible, ARG_1INT); + +void wpsave(void) +{ + WaypointClass.SaveWaypoints(); +} + +COMMAND(wpsave, ARG_NONE); + +void wpload(void) +{ + WaypointClass.LoadWaypoints(); +} + +COMMAND(wpload, ARG_NONE); + +void autowp(int on) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.SetAutoWaypoint(bool(on)); +} + +COMMAND(autowp, ARG_1INT); + +void wpinfo(int on) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.SetWPInfo(bool(on)); +} + +COMMAND(wpinfo, ARG_1INT); + +void addpath1way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 1, false); +} + +COMMAND(addpath1way1, ARG_NONE); + +void addpath1way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 2, false); +} + +COMMAND(addpath1way2, ARG_NONE); + +void addpath2way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 1, true); +} + +COMMAND(addpath2way1, ARG_NONE); + +void addpath2way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyCreatePath(player1, 2, true); +} + +COMMAND(addpath2way2, ARG_NONE); + +void delpath1way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 1, false); +} + +COMMAND(delpath1way1, ARG_NONE); + +void delpath1way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 2, false); +} + +COMMAND(delpath1way2, ARG_NONE); + +void delpath2way1(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 1, true); +} + +COMMAND(delpath2way1, ARG_NONE); + +void delpath2way2(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.ManuallyDeletePath(player1, 2, true); +} + +COMMAND(delpath2way2, ARG_NONE); + +void setjumpwp(void) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.SetWPFlags(wp, W_FL_JUMP); + } +} + +COMMAND(setjumpwp, ARG_NONE); + +void unsetjumpwp(void) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.UnsetWPFlags(wp, W_FL_JUMP); + } +} + +COMMAND(unsetjumpwp, ARG_NONE); + +void setwptriggernr(int nr) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.SetWPTriggerNr(wp, nr); + } +} + +COMMAND(setwptriggernr, ARG_1INT); + +void setwpyaw(void) +{ + node_s *wp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + if (wp) + { + WaypointClass.SetWPYaw(wp, short(player1->yaw)); + } +} + +COMMAND(setwpyaw, ARG_NONE); + +#ifdef WP_FLOOD +void startflood(void) +{ + WaypointClass.StartFlood(); +} + +COMMAND(startflood, ARG_NONE); +#endif + +#ifdef VANILLA_CUBE +// Commands specific for cube +void addtelewps(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.CreateWPsAtTeleporters(); +} + +COMMAND(addtelewps, ARG_NONE); + +void addtriggerwps(void) +{ + WaypointClass.SetWaypointsVisible(true); + WaypointClass.CreateWPsAtTriggers(); +} + +COMMAND(addtriggerwps, ARG_NONE); +#endif + +// Debug functions +#ifdef WP_FLOOD + +#ifndef RELEASE_BUILD +void botsheadtome(void) +{ + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + bots[i]->pBot->GoToDebugGoal(player1->o); + } +} + +COMMAND(botsheadtome, ARG_NONE); + +void setdebuggoal(void) { v_debuggoal = player1->o; }; +COMMAND(setdebuggoal, ARG_NONE); + +void botsheadtodebuggoal(void) +{ + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + bots[i]->pBot->GoToDebugGoal(v_debuggoal); + } +} + +COMMAND(botsheadtodebuggoal, ARG_NONE); + +#endif // RELEASE_BUILD + +#endif // WP_FLOOD + +// End debug functions +// Waypoint commands end + + +// Bot class begin + +bool CBot::FindWaypoint() +{ + waypoint_s *wp, *wpselect; + int index; + float distance, min_distance[3]; + waypoint_s *min_wp[3]; + + for (index=0; index < 3; index++) + { + min_distance[index] = 9999.0; + min_wp[index] = NULL; + } + + TLinkedList::node_s *pNode = m_pCurrentWaypoint->pNode->ConnectedWPs.GetFirst(); + + while (pNode) + { + if ((pNode->Entry->iFlags & W_FL_INTAG) && + SOLID(S((int)pNode->Entry->v_origin.x, (int)pNode->Entry->v_origin.y))) + { + pNode = pNode->next; + continue; + } + + wp = GetWPFromNode(pNode->Entry); + if (!wp) + { + pNode = pNode->next; + continue; + } + + // if index is not a current or recent previous waypoint... + if ((wp != m_pCurrentWaypoint) && + (wp != m_pPrevWaypoints[0]) && + (wp != m_pPrevWaypoints[1]) && + (wp != m_pPrevWaypoints[2]) && + (wp != m_pPrevWaypoints[3]) && + (wp != m_pPrevWaypoints[4])) + { + // find the distance from the bot to this waypoint + distance = GetDistance(wp->pNode->v_origin); + + if (distance < min_distance[0]) + { + min_distance[2] = min_distance[1]; + min_wp[2] = min_wp[1]; + + min_distance[1] = min_distance[0]; + min_wp[1] = min_wp[0]; + + min_distance[0] = distance; + min_wp[0] = wp; + } + else if (distance < min_distance [1]) + { + min_distance[2] = min_distance[1]; + min_wp[2] = min_wp[1]; + + min_distance[1] = distance; + min_wp[1] = wp; + } + else if (distance < min_distance[2]) + { + min_distance[2] = distance; + min_wp[2] = wp; + } + } + pNode = pNode->next; + } + + wpselect = NULL; + + // about 20% of the time choose a waypoint at random + // (don't do this any more often than every 10 seconds) + + if ((RandomLong(1, 100) <= 20) && (m_iRandomWaypointTime <= lastmillis)) + { + m_iRandomWaypointTime = lastmillis + 10000; + + if (min_wp[2]) + index = RandomLong(0, 2); + else if (min_wp[1]) + index = RandomLong(0, 1); + else if (min_wp[0]) + index = 0; + else + return false; // no waypoints found! + + wpselect = min_wp[index]; + } + else + { + // use the closest waypoint that has been recently used + wpselect = min_wp[0]; + } + + if (wpselect) // was a waypoint found? + { + m_pPrevWaypoints[4] = m_pPrevWaypoints[3]; + m_pPrevWaypoints[3] = m_pPrevWaypoints[2]; + m_pPrevWaypoints[2] = m_pPrevWaypoints[1]; + m_pPrevWaypoints[1] = m_pPrevWaypoints[0]; + m_pPrevWaypoints[0] = m_pCurrentWaypoint; + + SetCurrentWaypoint(wpselect); + return true; + } + + return false; // couldn't find a waypoint +} + +bool CBot::HeadToWaypoint() +{ + if (!m_pCurrentWaypoint) + return false; // Can't head to waypoint + + bool Touching = false; + float WPDist = GetDistance(m_pCurrentWaypoint->pNode->v_origin); + +#ifndef RELEASE_BUILD + if (m_pCurrentGoalWaypoint && vis(m_vGoal, g_vecZero)) + condebug("Warning: m_vGoal unset"); +#endif + + // did the bot run past the waypoint? (prevent the loop-the-loop problem) + if ((m_fPrevWaypointDistance > 1.0) && (WPDist > m_fPrevWaypointDistance) && + (WPDist <= 5.0f)) + Touching = true; + // bot needs to be close for jump and trigger waypoints + else if ((m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) || + (m_pCurrentWaypoint->pNode->iFlags & W_FL_TRIGGER)) + Touching = (WPDist <= 1.5f); + else if (m_pCurrentWaypoint->pNode->iFlags & W_FL_TELEPORT) + Touching = (WPDist <= 4.0f); + // are we close enough to a target waypoint... + else if (WPDist <= 3.0f) + { + if (!m_pCurrentGoalWaypoint || (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) || + IsVisible(m_vGoal) || (WPDist <= 1.5f)) + Touching = true; + // If the bot has a goal check if he can see his next wp + if (m_pCurrentGoalWaypoint && (m_pCurrentGoalWaypoint != m_pCurrentWaypoint) && + !m_AStarNodeList.Empty() && (WPDist >= 1.0f) && + !IsVisible(m_AStarNodeList.GetFirst()->Entry->pNode->v_origin)) + Touching = false; + } + + // save current distance as previous + m_fPrevWaypointDistance = WPDist; + + // Reached the waypoint? + if (Touching) + { + // Does this waypoint has a targetyaw? + if (m_pCurrentWaypoint->pNode->sYaw != -1) + { + // UNDONE: Inhuman + m_pMyEnt->yaw = m_pMyEnt->targetyaw = m_pCurrentWaypoint->pNode->sYaw; + } + + // Reached a jump waypoint? + if (m_pCurrentWaypoint->pNode->iFlags & W_FL_JUMP) + m_pMyEnt->jumpnext = true; + + m_fPrevWaypointDistance = 0.0f; + + // Does the bot has a goal? + if (m_pCurrentGoalWaypoint) + { + if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) + { + if (m_AStarNodeList.Empty()) + { + if (!AStar()) + { + // Bot is calculating a new path, just stand still for now + ResetMoveSpeed(); + m_iWaypointTime += 200; + return true; + } + else + { + if (m_AStarNodeList.Empty()) + { + //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); + return false; // Couldn't get a new wp to go to + } + } + } + + m_pCurrentWaypoint = m_AStarNodeList.Pop(); + + if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) + //(!(m_pCurrentWaypoint->iFlags & W_FL_TELEPORT))) + { + //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); + condebug("Next WP not visible"); + return false; + } + + SetCurrentWaypoint(m_pCurrentWaypoint); + } + else + { + // Bot reached the goal waypoint but couldn't reach the goal itself + // (otherwise we wouldn't be in this function) + //m_UnreachableNodes.PushNode(unreachable_node_s(m_pCurrentGoalWaypoint, gpGlobals->time)); + return false; + } + } + else + { + short index = 4; + bool status; + + // try to find the next waypoint + while (((status = FindWaypoint()) == false) && (index > 0)) + { + // if waypoint not found, clear oldest previous index and try again + + m_pPrevWaypoints[index] = NULL; + index--; + } + + if (status == false) + { + ResetWaypointVars(); + condebug("Couldn't find new random waypoint"); + return false; + } + } + m_iWaypointHeadPauseTime = lastmillis + 75; + m_iWaypointTime += 75; + } + + // keep turning towards the waypoint... + /*if (m_pCurrentWaypoint->pNode->iFlags & W_FL_FLOOD) + {UNDONE? + vec aim = m_pCurrentWaypoint->pNode->v_origin; + aim.x+=0.5f; + aim.y+=0.5f; + AimToVec(aim); + } + else*/ + AimToVec(m_pCurrentWaypoint->pNode->v_origin); + + if (m_fYawToTurn <= 25.0f) + m_iWaypointHeadLastTurnLessTime = lastmillis; + + // Bot had to turn much for a while? + if ((m_iWaypointHeadLastTurnLessTime > 0) && + (m_iWaypointHeadLastTurnLessTime < (lastmillis - 1000))) + { + m_iWaypointHeadPauseTime = lastmillis + 200; + m_iWaypointTime += 200; + } + + if (m_iWaypointHeadPauseTime >= lastmillis) + { + m_pMyEnt->move = 0; + //conoutf("Pause in HeadToWaypoint()"); + } + else + { + // Check if bot has to jump + vec from = m_pMyEnt->o; + from.z -= (m_pMyEnt->eyeheight - 1.25f); + float flEndDist; + if (!IsVisible(from, FORWARD, 3.0f, false, &flEndDist) && + (GetDistance(from, m_pCurrentWaypoint->pNode->v_origin) > flEndDist)) + { + m_pMyEnt->jumpnext = true; + condebug("Low wall in HeadToWaypoint()"); + } + + // Check if bot has to strafe + if (m_iStrafeTime > lastmillis) + SetMoveDir(m_iMoveDir, true); + else + { + m_iStrafeTime = 0; + m_iMoveDir = DIR_NONE; + + vec forward, up, right; + AnglesToVectors(GetViewAngles(), forward, right, up); + + float flLeftDist = -1.0f, flRightDist = -1.0f; + vec dir = right; + bool bStrafe = false; + int iStrafeDir = 0; + + vmul(dir, m_pMyEnt->radius); + + // Check left + from = m_pMyEnt->o; + vsub(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flLeftDist)) + flLeftDist = -1.0f; + + // Check right + from = m_pMyEnt->o; + vadd(from, dir); + if (IsVisible(from, FORWARD, 3.0f, false, &flRightDist)) + flRightDist = -1.0f; + + if ((flLeftDist != -1.0f) && (flRightDist != -1.0f)) + { + if (flLeftDist < flRightDist) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist < flLeftDist) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + else + { + // Randomly choose a strafe direction + bStrafe = true; + if (RandomLong(0, 1)) + iStrafeDir = LEFT; + else + iStrafeDir = RIGHT; + } + } + else if (flLeftDist != -1.0f) + { + // Strafe right + bStrafe = true; + iStrafeDir = RIGHT; + } + else if (flRightDist != -1.0f) + { + // Strafe left + bStrafe = true; + iStrafeDir = LEFT; + } + + if (bStrafe) + { + SetMoveDir(iStrafeDir, true); + m_iMoveDir = iStrafeDir; + m_iStrafeTime = lastmillis + RandomLong(30, 50); + } + } + } + + return true; +} + +// returns true when done or failure +bool CBot::HeadToGoal() +{ + // Does the bot has a goal(waypoint)? + if (m_pCurrentGoalWaypoint) + { + if (ReachedGoalWP()) + { + return false; + } + else + { + if (CurrentWPIsValid()) + { + if (m_bCalculatingAStarPath) + { + // Bot is calculating a new path, just stand still for now + ResetMoveSpeed(); + m_iWaypointTime += 200; + + // Done calculating the path? + if (AStar()) + { + if (m_AStarNodeList.Empty()) + { + return false; // Couldn't find a path + } + //else + // SetCurrentWaypoint(m_AStarNodeList.Pop()); + } + else + return true; // else just wait a little bit longer + } + //ResetMoveSpeed(); + //return true; + } + else + { + // Current waypoint isn't reachable, search new one + waypoint_s *pWP = NULL; + +#ifdef WP_FLOOD + if (m_pCurrentGoalWaypoint->pNode->iFlags & W_FL_FLOOD) + pWP = GetNearestFloodWP(8.0f); + else +#endif + pWP = GetNearestWaypoint(15.0f); + + if (!pWP || (pWP == m_pCurrentWaypoint)) + return false; + + SetCurrentWaypoint(pWP); + if (AStar()) + { + if (m_AStarNodeList.Empty()) return false; + } + else + { + m_iWaypointTime += 200; + ResetMoveSpeed(); + return true; + } + } + + //debugbeam(m_pMyEnt->o, m_pCurrentWaypoint->pNode->v_origin); + } + } + else + return false; + + return HeadToWaypoint(); +} + +// return true when done calculating +bool CBot::AStar() +{ + if (!m_pCurrentGoalWaypoint || !m_pCurrentWaypoint) + { + if (m_bCalculatingAStarPath) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + } + return true; + } + + // Ideas by PMB : + // * Make locals static to speed up a bit + // * MaxCycles per frame and make it fps dependent + + static int iMaxCycles; + static int iCurrentCycles; + static short newg; + static waypoint_s *n, *n2; + static TLinkedList::node_s *pPath = NULL; + static bool bPathFailed; + + iMaxCycles = BotManager.m_iFrameTime / 10; + if (iMaxCycles < 10) iMaxCycles = 10; + //condebug("MaxCycles: %d", iMaxCycles); + iCurrentCycles = 0; + bPathFailed = false; + + if (!m_bCalculatingAStarPath) + { + if ((BotManager.m_sUsingAStarBotsCount+1) > BotManager.m_sMaxAStarBots) + { + return true; + } + + BotManager.m_sUsingAStarBotsCount++; + + CleanAStarLists(false); + + m_pCurrentWaypoint->g[0] = m_pCurrentWaypoint->g[1] = 0; + m_pCurrentWaypoint->pParent[0] = m_pCurrentWaypoint->pParent[1] = NULL; + m_pCurrentGoalWaypoint->g[0] = m_pCurrentGoalWaypoint->g[1] = 0; + m_pCurrentGoalWaypoint->pParent[0] = m_pCurrentGoalWaypoint->pParent[1] = NULL; + + m_AStarNodeList.DeleteAllNodes(); + + m_AStarOpenList[0].Clear(); + m_AStarOpenList[1].Clear(); + m_AStarClosedList[0].DeleteAllNodes(); + m_AStarClosedList[1].DeleteAllNodes(); + + m_AStarOpenList[0].AddEntry(m_pCurrentWaypoint, + GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); + m_AStarOpenList[1].AddEntry(m_pCurrentGoalWaypoint, + GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin)); + + m_pCurrentWaypoint->bIsOpen[0] = m_pCurrentGoalWaypoint->bIsOpen[1] = true; + m_pCurrentWaypoint->bIsOpen[1] = m_pCurrentGoalWaypoint->bIsOpen[0] = false; + m_pCurrentWaypoint->bIsClosed[0] = m_pCurrentGoalWaypoint->bIsClosed[1] = false; + m_pCurrentWaypoint->bIsClosed[1] = m_pCurrentGoalWaypoint->bIsClosed[0] = false; + } + + while(!m_AStarOpenList[0].Empty()) + { + if (iCurrentCycles >= (iMaxCycles/2)) + { + m_bCalculatingAStarPath = true; + break; + } + + n = m_AStarOpenList[0].Pop(); + n->bIsOpen[0] = false; + + if (n->pNode->FailedGoalList.IsInList(m_pCurrentGoalWaypoint->pNode)) + { + bPathFailed = true; + break; // Can't make path to goal + } + + // Done with calculating + if (n == m_pCurrentGoalWaypoint) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n) + { + m_AStarNodeList.PushNode(n); + n = n->pParent[0]; + } + + CleanAStarLists(false); + + return true; + } + + pPath = n->pNode->ConnectedWPs.GetFirst(); + while(pPath) + { + if ((pPath->Entry->iFlags & W_FL_INTAG) && + SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) + { + pPath = pPath->next; + continue; + } + + n2 = GetWPFromNode(pPath->Entry); + + if (!n2) + { + pPath = pPath->next; + continue; + } + + if (n2->bIsClosed[1]) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n2) + { + m_AStarNodeList.AddNode(n2); + n2 = n2->pParent[1]; + } + + while(n) + { + m_AStarNodeList.PushNode(n); + n = n->pParent[0]; + } + + CleanAStarLists(false); + + return true; + } + + newg = n->g[0] + (short)AStarCost(n, n2); + + if ((n2->g[0] <= newg) && (n2->bIsOpen[0] || n2->bIsClosed[0])) + { + pPath = pPath->next; + continue; + } + + n2->pParent[0] = n; + n2->g[0] = newg; + n2->bIsClosed[0] = false; + + if (!n2->bIsOpen[0]) + { + m_AStarOpenList[0].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, + m_pCurrentGoalWaypoint->pNode->v_origin)); + n2->bIsOpen[0] = true; + } + pPath = pPath->next; + } + + m_AStarClosedList[0].PushNode(n); + n->bIsClosed[0] = true; + iCurrentCycles++; + } + + if (!bPathFailed) + { + while(!m_AStarOpenList[1].Empty()) + { + if (iCurrentCycles >= iMaxCycles) + { + m_bCalculatingAStarPath = true; + return false; + } + + n = m_AStarOpenList[1].Pop(); + n->bIsOpen[1] = false; + + if (n->pNode->FailedGoalList.IsInList(m_pCurrentWaypoint->pNode)) + { + bPathFailed = true; + break; // Can't make path to goal + } + + // Done with calculating + if (n == m_pCurrentWaypoint) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n) + { + m_AStarNodeList.AddNode(n); + n = n->pParent[1]; + } + + CleanAStarLists(false); + + return true; + } + + pPath = n->pNode->ConnectedWPsWithMe.GetFirst(); + while(pPath) + { + if ((pPath->Entry->iFlags & W_FL_INTAG) && + SOLID(S((int)pPath->Entry->v_origin.x, (int)pPath->Entry->v_origin.y))) + { + pPath = pPath->next; + continue; + } + + n2 = GetWPFromNode(pPath->Entry); + + if (!n2) + { + pPath = pPath->next; + continue; + } + + if (n2->bIsClosed[0]) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + + while(n2) + { + m_AStarNodeList.PushNode(n2); + n2 = n2->pParent[0]; + } + + while(n) + { + m_AStarNodeList.AddNode(n); + n = n->pParent[1]; + } + + CleanAStarLists(false); + + return true; + } + + newg = n->g[1] + (short)AStarCost(n, n2); + + if ((n2->g[1] <= newg) && (n2->bIsOpen[1] || n2->bIsClosed[1])) + { + pPath = pPath->next; + continue; + } + + n2->pParent[1] = n; + n2->g[1] = newg; + n2->bIsClosed[1] = false; + + if (!n2->bIsOpen[1]) + { + m_AStarOpenList[1].AddEntry(n2, n2->g[1] + GetDistance(n2->pNode->v_origin, + m_pCurrentWaypoint->pNode->v_origin)); + n2->bIsOpen[1] = true; + } + pPath = pPath->next; + } + + m_AStarClosedList[1].PushNode(n); + n->bIsClosed[1] = true; + iCurrentCycles++; + } + } + + // Failed making path + condebug("Path failed"); + + CleanAStarLists(true); + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + return true; +} + +float CBot::AStarCost(waypoint_s *pWP1, waypoint_s *pWP2) +{ + // UNDONE? + return (GetDistance(pWP1->pNode->v_origin, pWP2->pNode->v_origin) * pWP2->pNode->sCost); +} + +void CBot::CleanAStarLists(bool bPathFailed) +{ + while(m_AStarOpenList[0].Empty() == false) + { + waypoint_s *p = m_AStarOpenList[0].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + } + + while(m_AStarOpenList[1].Empty() == false) + { + waypoint_s *p = m_AStarOpenList[1].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + } + + while(m_AStarClosedList[0].Empty() == false) + { + waypoint_s *p = m_AStarClosedList[0].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + if (bPathFailed) + p->pNode->FailedGoalList.AddNode(m_pCurrentGoalWaypoint->pNode); + } + + while(m_AStarClosedList[1].Empty() == false) + { + waypoint_s *p = m_AStarClosedList[1].Pop(); + p->bIsOpen[0] = p->bIsOpen[1] = false; + p->bIsClosed[0] = p->bIsClosed[1] = false; + p->pParent[0] = p->pParent[1] = NULL; + p->g[0] = p->g[1] = 0; + if (bPathFailed) + p->pNode->FailedGoalList.AddNode(m_pCurrentWaypoint->pNode); + } +} + +void CBot::ResetWaypointVars() +{ + m_iWaypointTime = 0; + m_pCurrentWaypoint = NULL; + m_pCurrentGoalWaypoint = NULL; + m_pPrevWaypoints[0] = NULL; + m_pPrevWaypoints[1] = NULL; + m_pPrevWaypoints[2] = NULL; + m_pPrevWaypoints[3] = NULL; + m_pPrevWaypoints[4] = NULL; + m_fPrevWaypointDistance = 0; + m_iWaypointHeadLastTurnLessTime = 0; + m_iWaypointHeadPauseTime = 0; + if (m_bCalculatingAStarPath) + { + m_bCalculatingAStarPath = false; + BotManager.m_sUsingAStarBotsCount--; + } + m_AStarNodeList.DeleteAllNodes(); + CleanAStarLists(false); + m_bGoToDebugGoal = false; +} + +void CBot::SetCurrentWaypoint(node_s *pNode) +{ + waypoint_s *pWP = GetWPFromNode(pNode); +#ifndef RELEASE_BUILD + if (!pWP || !pNode) condebug("NULL WP In SetCurrentWP"); +#endif + + m_pCurrentWaypoint = pWP; + m_iWaypointTime = lastmillis; +} + +void CBot::SetCurrentWaypoint(waypoint_s *pWP) +{ +#ifndef RELEASE_BUILD + if (!pWP) condebug("NULL WP In SetCurrentWP(2)"); +#endif + + m_pCurrentWaypoint = pWP; + m_iWaypointTime = lastmillis; +} + +void CBot::SetCurrentGoalWaypoint(node_s *pNode) +{ + waypoint_s *pWP = GetWPFromNode(pNode); +#ifndef RELEASE_BUILD + if (!pWP || !pNode) condebug("NULL WP In SetCurrentGoalWP"); +#endif + + m_pCurrentGoalWaypoint = pWP; +} + +void CBot::SetCurrentGoalWaypoint(waypoint_s *pWP) +{ +#ifndef RELEASE_BUILD + if (!pWP) condebug("NULL WP In SetCurrentGoalWP(2)"); +#endif + + m_pCurrentGoalWaypoint = pWP; +} + +bool CBot::CurrentWPIsValid() +{ + if (!m_pCurrentWaypoint) + { + //condebug("Invalid WP: Is NULL"); + return false; + } + + // check if the bot has been trying to get to this waypoint for a while... + if ((m_iWaypointTime + 5000) < lastmillis) + { + condebug("Invalid WP: time over"); + return false; + } + +#ifndef RELEASE_BUILD + if (!IsVisible(m_pCurrentWaypoint->pNode->v_origin)) + condebug("Invalid WP: Not visible"); +#endif + + return (IsVisible(m_pCurrentWaypoint->pNode->v_origin)); +} + +bool CBot::ReachedGoalWP() +{ + if ((!m_pCurrentWaypoint) || (!m_pCurrentGoalWaypoint)) + return false; + + if (m_pCurrentWaypoint != m_pCurrentGoalWaypoint) + return false; + + return ((GetDistance(m_pCurrentGoalWaypoint->pNode->v_origin) <= 3.0f) && + (IsVisible(m_pCurrentGoalWaypoint->pNode->v_origin))); +} + +waypoint_s *CBot::GetWPFromNode(node_s *pNode) +{ + if (!pNode) return NULL; + + short x, y; + WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); + + TLinkedList::node_s *p = m_WaypointList[x][y].GetFirst(); + while(p) + { + if (p->Entry->pNode == pNode) + return p->Entry; + + p = p->next; + } + + return NULL; +} + +waypoint_s *CBot::GetNearestWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (p->Entry->pNode->iFlags & W_FL_FLOOD) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_src, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +waypoint_s *CBot::GetNearestTriggerWaypoint(vec v_src, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_src, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->pNode->iFlags & W_FL_TRIGGER)) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_src, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_src, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +// Makes a waypoint list for this bot based on the list from WaypointClass +void CBot::SyncWaypoints() +{ + short x, y; + TLinkedList::node_s *p; + + // Clean everything first + for (x=0;xpNode = p->Entry; + m_WaypointList[x][y].AddNode(pWP); + +#ifndef RELEASE_BUILD + if (!GetWPFromNode(p->Entry)) condebug("Error adding bot wp!"); +#endif + + p = p->next; + } + } + } +} + +#ifdef WP_FLOOD +waypoint_s *CBot::GetNearestFloodWP(vec v_origin, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->pNode->iFlags & W_FL_FLOOD)) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +waypoint_s *CBot::GetNearestTriggerFloodWP(vec v_origin, float flRange) +{ + TLinkedList::node_s *p; + waypoint_s *pNearest = NULL; + short i, j, MinI, MaxI, MinJ, MaxJ, Offset = (short)ceil(flRange / MAX_MAP_GRIDS); + float flNearestDist = 9999.99, flDist; + + WaypointClass.GetNodeIndexes(v_origin, &i, &j); + MinI = i - Offset; + MaxI = i + Offset; + MinJ = j - Offset; + MaxJ = j + Offset; + + if (MinI < 0) + MinI = 0; + if (MaxI > MAX_MAP_GRIDS - 1) + MaxI = MAX_MAP_GRIDS - 1; + if (MinJ < 0) + MinJ = 0; + if (MaxJ > MAX_MAP_GRIDS - 1) + MaxJ = MAX_MAP_GRIDS - 1; + + for (int x=MinI;x<=MaxI;x++) + { + for(int y=MinJ;y<=MaxJ;y++) + { + p = m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (!(p->Entry->pNode->iFlags & (W_FL_FLOOD | W_FL_TRIGGER))) + { + p = p->next; + continue; + } + + flDist = GetDistance(v_origin, p->Entry->pNode->v_origin); + if ((flDist < flNearestDist) && (flDist <= flRange)) + { + if (::IsVisible(v_origin, p->Entry->pNode->v_origin, NULL)) + { + pNearest = p->Entry; + flNearestDist = flDist; + } + } + + p = p->next; + } + } + } + return pNearest; +} + +void CBot::GoToDebugGoal(vec o) +{ + ResetWaypointVars(); + + node_s *wp = WaypointClass.GetNearestWaypoint(m_pMyEnt, 20.0f); + node_s *goalwp = WaypointClass.GetNearestWaypoint(player1, 20.0f); + + if (!wp || !goalwp) + { + wp = WaypointClass.GetNearestFloodWP(m_pMyEnt, 8.0f); + goalwp = WaypointClass.GetNearestFloodWP(player1, 5.0f); + } + if (!wp || !goalwp) { condebug("No near WP"); return; } + + SetCurrentWaypoint(wp); + SetCurrentGoalWaypoint(goalwp); + m_vGoal = o; + m_bGoToDebugGoal = true; +} +#endif + +// Bot class end diff --git a/source/src/bot/bot_waypoint.h b/source/src/bot/bot_waypoint.h index 934e9d55..565bf593 100644 --- a/source/src/bot/bot_waypoint.h +++ b/source/src/bot/bot_waypoint.h @@ -1,191 +1,191 @@ -// -// C++ Implementation: bot_waypoint.h -// -// Description: Containts all defintions for bot waypoint code -// -// -// Author: Rick Helmus -// - -// Enable this for flood support -#define WP_FLOOD - -#ifndef BOT_WAYPOINT_H -#define BOT_WAYPOINT_H - -#define WAYPOINT_VERSION 2 -#define EXP_WP_VERSION 1 -#define REACHABLE_RANGE 15.0f -#define MAX_MAP_GRIDS 64 -#define SECTOR_SIZE 18 - -#define W_FL_TELEPORT (1<<1) // used if waypoint is at a teleporter -#define W_FL_TELEPORTDEST (1<<2) // used if waypoint is at a teleporter destination -#define W_FL_FLOOD (1<<3) // used if this waypoint is made by flooding the map -#define W_FL_JUMP (1<<4) // if set, bot jumps when reached this waypoint -#define W_FL_TRIGGER (1<<5) // used if this waypoint is at a trigger -#define W_FL_INTAG (1<<6) // used if this waypoint is in a tagged cube - -#define MAX_FLOODWPCONNECTDIST 2 -#define MAX_FLOODWPDIST 4.0f - -struct waypoint_header_s -{ - char szFileType[10]; - int iFileVersion; - int iFileFlags; // not currently used - int iWPCount; - char szMapName[32]; // name of map for these waypoints -}; - -// Old waypoint structure, used for waypoint version 1 -struct waypoint_version_1_s -{ - int iFlags; - vec v_origin; - TLinkedList ConnectedWPs; - - // A* stuff - float f, g; - waypoint_version_1_s *pParent; - - // Construction - waypoint_version_1_s(void) : iFlags(0), f(0.0f), g(0.0f), pParent(NULL) { }; -}; - -struct node_s -{ - vec v_origin; - int iFlags; - short sTriggerNr; - short sYaw; - short sCost; // Base and static cost - TLinkedList ConnectedWPs; - TLinkedList ConnectedWPsWithMe; - - TLinkedList FailedGoalList; - - // Construction - node_s(void) : iFlags(0), sTriggerNr(0), sYaw(-1), sCost(10) { }; - node_s(const vec &o, const int &f, const short t=0, const short y=-1) : v_origin(o), - iFlags(f), - sTriggerNr(t), - sYaw(y), - sCost(0) { }; -}; - -struct waypoint_s -{ - node_s *pNode; - short g[2]; - waypoint_s *pParent[2]; - bool bIsOpen[2], bIsClosed[2]; - - // Construction - waypoint_s(void) : pNode(NULL) { bIsOpen[0] = bIsOpen[1] = bIsClosed[0] = - bIsClosed[1] = false; pParent[0] = pParent[1] = NULL; - g[0] = g[1] = 0; }; -}; - -class CWaypointClass -{ -protected: - bool m_bDrawWaypoints; - bool m_bDrawWPPaths; - bool m_bAutoWaypoint; - bool m_bAutoPlacePaths; - bool m_bDrawWPText; - vec m_vLastCreatedWP; - float m_fPathDrawTime; - char m_szMapName[32]; - -#ifdef WP_FLOOD - bool m_bFlooding; // True if flooding map with waypoints - bool m_bFilteringNodes; - int m_iFloodStartTime; - int m_iCurFloodX, m_iCurFloodY; - int m_iFloodSize; - int m_iFilteredNodes; -#endif - friend class CBot; - -public: - TLinkedList m_Waypoints[MAX_MAP_GRIDS][MAX_MAP_GRIDS]; - int m_iWaypointCount; // number of waypoints currently in use - - CWaypointClass(void); - virtual ~CWaypointClass(void) { Clear(); }; - - void Think(void); - void Init(void); - void Clear(void); - bool LoadWaypoints(void); - void SaveWaypoints(void); - bool LoadWPExpFile(void); - void SaveWPExpFile(void); - void SetMapName(char *map) { strcpy(m_szMapName, map); }; - - void SetWaypointsVisible(bool Visible) { m_bDrawWaypoints = Visible; }; - void SetPathsVisible(bool Visible) { m_bDrawWPPaths = Visible; }; - void SetAutoWaypoint(bool On) { m_bAutoWaypoint = On; }; - void SetWPInfo(bool on) { m_bDrawWPText = on; }; - void SetWPFlags(node_s *wp, int iFlags) { wp->iFlags |= iFlags; }; - void UnsetWPFlags(node_s *wp, int iFlags) { wp->iFlags &= ~iFlags; }; - void SetWPTriggerNr(node_s *wp, short sTriggerNr) { wp->sTriggerNr = sTriggerNr; }; - void SetWPYaw(node_s *wp, short sYaw) { wp->sYaw = sYaw; }; - bool WaypointsAreVisible(void) { return m_bDrawWaypoints; }; - node_s *AddWaypoint(vec o, bool connectwp); - void DeleteWaypoint(dynent *d); - void AddPath(node_s *pWP1, node_s *pWP2); - void DeletePath(node_s *pWP); - void DeletePath(node_s *pWP1, node_s *pWP2); - void ManuallyCreatePath(dynent *d, int iCmd, bool TwoWay); - void ManuallyDeletePath(dynent *d, int iCmd, bool TwoWay); - - bool WPIsReachable(vec from, vec to); - //node_s *GetNearestWaypoint(vec v_src, float flRange, bool CheckVisible = true, bool SkipFloodWPs = true); - node_s *GetNearestWaypoint(vec v_src, float flRange); - node_s *GetNearestWaypoint(dynent *d, float flRange) { return GetNearestWaypoint(d->o, flRange); }; - node_s *GetNearestTriggerWaypoint(vec v_src, float flRange); - node_s *GetWaypointFromVec(const vec &v_src); - void DrawNearWaypoints(); - void GetNodeIndexes(const vec &v_origin, short *i, short *j); - void CalcCost(node_s *pNode); - void ReCalcCosts(void); - - -#ifdef WP_FLOOD - // Flood functions - virtual void StartFlood(void); - void StopFlood(void); - void FloodThink(void); - bool CanPlaceNodeHere(const vec &from); - void ConnectFloodWP(node_s *pWP); - node_s *GetNearestFloodWP(vec v_origin, float flRange, node_s *pIgnore, bool SkipTags=false); - node_s *GetNearestFloodWP(dynent *d, float flRange) { return GetNearestFloodWP(d->o, flRange, NULL); }; - node_s *GetNearestTriggerFloodWP(vec v_origin, float flRange); -#endif -}; - -class CACWaypointClass: public CWaypointClass -{ -public: -#ifdef WP_FLOOD - // Flood functions - virtual void StartFlood(void); -#endif -}; - -class CCubeWaypointClass: public CWaypointClass -{ -public: - void CreateWPsAtTeleporters(void); - void CreateWPsAtTriggers(void); - -#ifdef WP_FLOOD - // Flood functions - virtual void StartFlood(void); -#endif -}; - -#endif // BOT_WAYPOINT_H +// +// C++ Implementation: bot_waypoint.h +// +// Description: Containts all defintions for bot waypoint code +// +// +// Author: Rick Helmus +// + +// Enable this for flood support +#define WP_FLOOD + +#ifndef BOT_WAYPOINT_H +#define BOT_WAYPOINT_H + +#define WAYPOINT_VERSION 2 +#define EXP_WP_VERSION 1 +#define REACHABLE_RANGE 15.0f +#define MAX_MAP_GRIDS 64 +#define SECTOR_SIZE 18 + +#define W_FL_TELEPORT (1<<1) // used if waypoint is at a teleporter +#define W_FL_TELEPORTDEST (1<<2) // used if waypoint is at a teleporter destination +#define W_FL_FLOOD (1<<3) // used if this waypoint is made by flooding the map +#define W_FL_JUMP (1<<4) // if set, bot jumps when reached this waypoint +#define W_FL_TRIGGER (1<<5) // used if this waypoint is at a trigger +#define W_FL_INTAG (1<<6) // used if this waypoint is in a tagged cube + +#define MAX_FLOODWPCONNECTDIST 2 +#define MAX_FLOODWPDIST 4.0f + +struct waypoint_header_s +{ + char szFileType[10]; + int iFileVersion; + int iFileFlags; // not currently used + int iWPCount; + char szMapName[32]; // name of map for these waypoints +}; + +// Old waypoint structure, used for waypoint version 1 +struct waypoint_version_1_s +{ + int iFlags; + vec v_origin; + TLinkedList ConnectedWPs; + + // A* stuff + float f, g; + waypoint_version_1_s *pParent; + + // Construction + waypoint_version_1_s(void) : iFlags(0), f(0.0f), g(0.0f), pParent(NULL) { }; +}; + +struct node_s +{ + vec v_origin; + int iFlags; + short sTriggerNr; + short sYaw; + short sCost; // Base and static cost + TLinkedList ConnectedWPs; + TLinkedList ConnectedWPsWithMe; + + TLinkedList FailedGoalList; + + // Construction + node_s(void) : iFlags(0), sTriggerNr(0), sYaw(-1), sCost(10) { }; + node_s(const vec &o, const int &f, const short t=0, const short y=-1) : v_origin(o), + iFlags(f), + sTriggerNr(t), + sYaw(y), + sCost(0) { }; +}; + +struct waypoint_s +{ + node_s *pNode; + short g[2]; + waypoint_s *pParent[2]; + bool bIsOpen[2], bIsClosed[2]; + + // Construction + waypoint_s(void) : pNode(NULL) { bIsOpen[0] = bIsOpen[1] = bIsClosed[0] = + bIsClosed[1] = false; pParent[0] = pParent[1] = NULL; + g[0] = g[1] = 0; }; +}; + +class CWaypointClass +{ +protected: + bool m_bDrawWaypoints; + bool m_bDrawWPPaths; + bool m_bAutoWaypoint; + bool m_bAutoPlacePaths; + bool m_bDrawWPText; + vec m_vLastCreatedWP; + float m_fPathDrawTime; + char m_szMapName[32]; + +#ifdef WP_FLOOD + bool m_bFlooding; // True if flooding map with waypoints + bool m_bFilteringNodes; + int m_iFloodStartTime; + int m_iCurFloodX, m_iCurFloodY; + int m_iFloodSize; + int m_iFilteredNodes; +#endif + friend class CBot; + +public: + TLinkedList m_Waypoints[MAX_MAP_GRIDS][MAX_MAP_GRIDS]; + int m_iWaypointCount; // number of waypoints currently in use + + CWaypointClass(void); + virtual ~CWaypointClass(void) { Clear(); }; + + void Think(void); + void Init(void); + void Clear(void); + bool LoadWaypoints(void); + void SaveWaypoints(void); + bool LoadWPExpFile(void); + void SaveWPExpFile(void); + void SetMapName(char *map) { strcpy(m_szMapName, map); }; + + void SetWaypointsVisible(bool Visible) { m_bDrawWaypoints = Visible; }; + void SetPathsVisible(bool Visible) { m_bDrawWPPaths = Visible; }; + void SetAutoWaypoint(bool On) { m_bAutoWaypoint = On; }; + void SetWPInfo(bool on) { m_bDrawWPText = on; }; + void SetWPFlags(node_s *wp, int iFlags) { wp->iFlags |= iFlags; }; + void UnsetWPFlags(node_s *wp, int iFlags) { wp->iFlags &= ~iFlags; }; + void SetWPTriggerNr(node_s *wp, short sTriggerNr) { wp->sTriggerNr = sTriggerNr; }; + void SetWPYaw(node_s *wp, short sYaw) { wp->sYaw = sYaw; }; + bool WaypointsAreVisible(void) { return m_bDrawWaypoints; }; + node_s *AddWaypoint(vec o, bool connectwp); + void DeleteWaypoint(dynent *d); + void AddPath(node_s *pWP1, node_s *pWP2); + void DeletePath(node_s *pWP); + void DeletePath(node_s *pWP1, node_s *pWP2); + void ManuallyCreatePath(dynent *d, int iCmd, bool TwoWay); + void ManuallyDeletePath(dynent *d, int iCmd, bool TwoWay); + + bool WPIsReachable(vec from, vec to); + //node_s *GetNearestWaypoint(vec v_src, float flRange, bool CheckVisible = true, bool SkipFloodWPs = true); + node_s *GetNearestWaypoint(vec v_src, float flRange); + node_s *GetNearestWaypoint(dynent *d, float flRange) { return GetNearestWaypoint(d->o, flRange); }; + node_s *GetNearestTriggerWaypoint(vec v_src, float flRange); + node_s *GetWaypointFromVec(const vec &v_src); + void DrawNearWaypoints(); + void GetNodeIndexes(const vec &v_origin, short *i, short *j); + void CalcCost(node_s *pNode); + void ReCalcCosts(void); + + +#ifdef WP_FLOOD + // Flood functions + virtual void StartFlood(void); + void StopFlood(void); + void FloodThink(void); + bool CanPlaceNodeHere(const vec &from); + void ConnectFloodWP(node_s *pWP); + node_s *GetNearestFloodWP(vec v_origin, float flRange, node_s *pIgnore, bool SkipTags=false); + node_s *GetNearestFloodWP(dynent *d, float flRange) { return GetNearestFloodWP(d->o, flRange, NULL); }; + node_s *GetNearestTriggerFloodWP(vec v_origin, float flRange); +#endif +}; + +class CACWaypointClass: public CWaypointClass +{ +public: +#ifdef WP_FLOOD + // Flood functions + virtual void StartFlood(void); +#endif +}; + +class CCubeWaypointClass: public CWaypointClass +{ +public: + void CreateWPsAtTeleporters(void); + void CreateWPsAtTriggers(void); + +#ifdef WP_FLOOD + // Flood functions + virtual void StartFlood(void); +#endif +}; + +#endif // BOT_WAYPOINT_H diff --git a/source/src/bot/botmanager.cpp b/source/src/bot/botmanager.cpp index b8920eee..99889033 100644 --- a/source/src/bot/botmanager.cpp +++ b/source/src/bot/botmanager.cpp @@ -1,1435 +1,1435 @@ -// -// C++ Implementation: bot -// -// Description: Code for botmanager -// -// Main bot file -// -// Author: Rick -// -// -// - -#include "../cube.h" - -#ifndef VANILLA_CUBE // UNDONE -bool dedserv = false; -#define CS_DEDHOST -1 -#endif - -extern ENetHost *clienthost; - -extern void respawnself(); - -CBotManager BotManager; - -// Bot manager class begin - -CBotManager::~CBotManager(void) -{ - EndMap(); - - while(m_StoredBots.Empty() == false) - delete m_StoredBots.Pop(); -} - -void CBotManager::Init() -{ - m_pBotToView = NULL; - - if (!ishost()) - return; - - m_bBotsShoot = true; - m_bIdleBots = false; - m_iFrameTime = 0; - m_iPrevTime = lastmillis; - m_sBotSkill = 1; // Default all bots have the skill 'Good' - - CreateSkillData(); - LoadBotNamesFile(); - LoadBotTeamsFile(); - WaypointClass.Init(); - lsrand(time(NULL)); -} - -void CBotManager::Think() -{ - if (m_bInit) - { - Init(); - m_bInit = false; - } - - if (m_pBotToView) - ViewBot(); - - if (!ishost()) - return; - - AddDebugText("m_sMaxAStarBots: %d", m_sMaxAStarBots); - AddDebugText("m_sCurrentTriggerNr: %d", m_sCurrentTriggerNr); - short x, y; - WaypointClass.GetNodeIndexes(player1->o, &x, &y); - AddDebugText("x: %d y: %d", x, y); - - m_iFrameTime = lastmillis - m_iPrevTime; - if (m_iFrameTime > 250) m_iFrameTime = 250; - m_iPrevTime = lastmillis; - - // Is it time to re-add bots? - if ((m_fReAddBotDelay < lastmillis) && (m_fReAddBotDelay != -1.0f)) - { - while(m_StoredBots.Empty() == false) - { - CStoredBot *pStoredBot = m_StoredBots.Pop(); - pStoredBot->ReAddBot(); - delete pStoredBot; - } - - m_fReAddBotDelay = -1.0f; - } - - // If this is a ded server check if there are any players, if not bots should be idle - if (dedserv) - { - bool botsbeidle = true; - loopv(players) - { - if (players[i] && (players[i]->state != CS_DEDHOST)) { botsbeidle = false; break; } - } - - if (botsbeidle) return; - } - - // Let all bots 'think' - loopv(bots) - { - if (!bots[i]) - continue; - - if (bots[i]->pBot) - bots[i]->pBot->Think(); - else - condebug("Error: pBot == NULL in bot ent\n"); - } -} - -void CBotManager::LoadBotNamesFile() -{ - if (!ishost()) - return; - - // Init bot names array first - for (int i=0;i<100;i++) - strcpy(m_szBotNames[i], "Bot"); - - m_sBotNameCount = 0; - - // Load bot file - char szNameFileName[256]; - MakeBotFileName("bot_names.txt", NULL, NULL, szNameFileName); - FILE *fp = fopen(szNameFileName, "r"); - char szNameBuffer[256]; - int iIndex, iStrIndex; - - if (!fp) - { - conoutf("Warning: Couldn't load bot names file"); - return; - } - - while (fgets(szNameBuffer, 80, fp) != NULL) - { - if (m_sBotNameCount >= 150) - { - conoutf("Warning: Max bot names reached(150), ignoring the rest of the" - "names"); - break; - } - - short length = strlen(szNameBuffer); - - if (szNameBuffer[length-1] == '\n') - { - szNameBuffer[length-1] = 0; // remove '\n' - length--; - } - - iStrIndex = 0; - while (iStrIndex < length) - { - if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || - (szNameBuffer[iStrIndex] == '"')) - { - for (iIndex=iStrIndex; iIndex < length; iIndex++) - szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; - } - - iStrIndex++; - } - - if (szNameBuffer[0] != 0) - { - if (strlen(szNameBuffer) >= 16) - { conoutf("Warning: bot name \"%s\" has to many characters(16 is max)", - (int)szNameBuffer); - } - strn0cpy(m_szBotNames[m_sBotNameCount], szNameBuffer, 16); - m_sBotNameCount++; - } - } - fclose(fp); -} - -char *CBotManager::GetBotName() -{ - char *szOutput = NULL; - TMultiChoice BotNameChoices; - short ChoiceVal; - - for(int j=0;jstate != CS_DEDHOST) && - !strcasecmp(players[i]->name, m_szBotNames[j])) - ChoiceVal -= 10; - } - - loopv(bots) - { - if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) - ChoiceVal -= 10; - } - - if ((player1->state != CS_DEDHOST) && !strcasecmp(player1->name, m_szBotNames[j])) - ChoiceVal -= 10; - - if (ChoiceVal <= 0) - ChoiceVal = 1; - - BotNameChoices.Insert(m_szBotNames[j], ChoiceVal); - } - - // Couldn't find a selection? - if (!BotNameChoices.GetSelection(szOutput)) - szOutput = "Bot"; - - return szOutput; -} - -void CBotManager::LoadBotTeamsFile() -{ - if (!ishost()) - return; - - // Init bot teams array first - for (int i=0;i<20;i++) - strcpy(m_szBotTeams[i], "b0ts"); - - m_sBotTeamCount = 0; - - // Load bot file - char szNameFileName[256]; - MakeBotFileName("bot_teams.txt", NULL, NULL, szNameFileName); - FILE *fp = fopen(szNameFileName, "r"); - char szNameBuffer[256]; - int iIndex, iStrIndex; - - if (!fp) - { - conoutf("Warning: Couldn't load bot teams file"); - return; - } - - while ((m_sBotTeamCount < 20) && (fgets(szNameBuffer, 80, fp) != NULL)) - { - short length = strlen(szNameBuffer); - - if (szNameBuffer[length-1] == '\n') - { - szNameBuffer[length-1] = 0; // remove '\n' - length--; - } - - iStrIndex = 0; - while (iStrIndex < length) - { - if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || - (szNameBuffer[iStrIndex] == '"')) - { - for (iIndex=iStrIndex; iIndex < length; iIndex++) - szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; - } - - iStrIndex++; - } - - if (szNameBuffer[0] != 0) - { - strn0cpy(m_szBotTeams[m_sBotTeamCount], szNameBuffer, 5); - m_sBotTeamCount++; - } - } - fclose(fp); -} - -char *CBotManager::GetBotTeam() -{ - char *szOutput = NULL; - TMultiChoice BotTeamChoices; - short ChoiceVal; - - for(int j=0;jname, m_szBotNames[j]))) - ChoiceVal -= 10; - } - - loopv(bots) - { - if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) - ChoiceVal -= 10; - } - - if (!strcasecmp(player1->name, m_szBotNames[j])) - ChoiceVal -= 10; - - if (ChoiceVal <= 0) - ChoiceVmonsterclearal = 1;*/ - - BotTeamChoices.Insert(m_szBotTeams[j], ChoiceVal); - } - - // Couldn't find a selection? - if (!BotTeamChoices.GetSelection(szOutput)) - szOutput = "b0t"; - - return szOutput; -} - -void CBotManager::RenderBots() -{ - static bool drawblue; - - loopv(bots) - { - if (bots[i] && (bots[i] != m_pBotToView)) - { - drawblue = (m_sp || isteam(player1->team, bots[i]->team)); - renderclient(bots[i], drawblue, "playermodels/counterterrorist", false, 1.0f); - } - } -} - -void CBotManager::EndMap() -{ - // Remove all bots - loopv(bots) - { - if (!bots[i]) - continue; - - if (ishost()) // Store bots so they can be re-added after map change - { - if (bots[i]->pBot && bots[i]->name[0] && bots[i]->team[0]) - { - CStoredBot *pStoredBot = new CStoredBot(bots[i]->name, bots[i]->team, - bots[i]->pBot->m_sSkillNr); - m_StoredBots.AddNode(pStoredBot); - } - delete bots[i]->pBot; - } - - bots[i]->pBot = NULL; - zapdynent(bots[i]); - } - bots.setsize(0); - condebug("Cleared all bots"); - m_fReAddBotDelay = lastmillis + 7500; - //if(ishost()) WaypointClass.SaveWPExpFile(); //UNDONE -} - -void CBotManager::BeginMap(char *szMapName) -{ - EndMap(); // End previous map - - if (!ishost()) return; - - WaypointClass.Init(); - WaypointClass.SetMapName(szMapName); - if (!WaypointClass.LoadWaypoints()) - WaypointClass.StartFlood(); - //WaypointClass.LoadWPExpFile(); // UNDONE - - CalculateMaxAStarCount(); - m_sUsingAStarBotsCount = 0; - PickNextTrigger(); -} - -int CBotManager::GetBotIndex(dynent *m) -{ - loopv(bots) - { - if (!bots[i]) - continue; - - if (bots[i] == m) - return i; - } - - return -1; -} - -void CBotManager::LetBotsUpdateStats() -{ - loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->m_bSendC2SInit = false; -} - -void CBotManager::LetBotsHear(int n, vec *loc) -{ - if (bots.length() == 0) return; - - if (!loc) return; - - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot || (bots[i]->state == CS_DEAD)) continue; - bots[i]->pBot->HearSound(n, loc); - } -} - -// Notify all bots of a new waypoint -void CBotManager::AddWaypoint(node_s *pNode) -{ - if (bots.length()) - { - short x, y; - waypoint_s *pWP; - - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - pWP = new waypoint_s; - pWP->pNode = pNode; - WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); - bots[i]->pBot->m_WaypointList[x][y].AddNode(pWP); - -#ifndef RELEASE_BUILD - if (!bots[i]->pBot->GetWPFromNode(pNode)) condebug("Error adding bot wp!"); -#endif - } - } - - CalculateMaxAStarCount(); -} - -// Notify all bots of a deleted waypoint -void CBotManager::DelWaypoint(node_s *pNode) -{ - if (bots.length()) - { - short x, y; - TLinkedList::node_s *p; - - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); - p = bots[i]->pBot->m_WaypointList[x][y].GetFirst(); - - while(p) - { - if (p->Entry->pNode == pNode) - { - delete p->Entry; - bots[i]->pBot->m_WaypointList[x][y].DeleteNode(p); - break; - } - p = p->next; - } - } - } - - CalculateMaxAStarCount(); -} - -void CBotManager::MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, char *szOutput) -{ - char *DirSeperator; - -#ifndef __linux__ - DirSeperator = "\\"; - strcpy(szOutput, "bot\\"); -#else - DirSeperator = "/"; - strcpy(szOutput, "bot/"); -#endif - - if (szDir1) - { - strcat(szOutput, szDir1); - strcat(szOutput, DirSeperator); - } - - if (szDir2) - { - strcat(szOutput, szDir2); - strcat(szOutput, DirSeperator); - } - - strcat(szOutput, szFileName); -} - -void CBotManager::CreateSkillData() -{ - // First give the bot skill structure some default data - InitSkillData(); - - // Now see if we can load the skill.cfg file - char SkillFileName[256] = ""; - FILE *pSkillFile = NULL; - int SkillNr = -1; - float value = 0; - - MakeBotFileName("bot_skill.cfg", NULL, NULL, SkillFileName); - - pSkillFile = fopen(SkillFileName, "r"); - - conoutf("Reading bot_skill.cfg file... "); - - if (pSkillFile == NULL) // file doesn't exist - { - conoutf("skill file not found, default settings will be used\n"); - return; - } - - int ch; - char cmd_line[256]; - int cmd_index; - char *cmd, *arg1; - - while (pSkillFile) - { - cmd_index = 0; - cmd_line[cmd_index] = 0; - - ch = fgetc(pSkillFile); - - // skip any leading blanks - while (ch == ' ') - ch = fgetc(pSkillFile); - - while ((ch != EOF) && (ch != '\r') && (ch != '\n')) - { - if (ch == '\t') // convert tabs to spaces - ch = ' '; - - cmd_line[cmd_index] = ch; - - ch = fgetc(pSkillFile); - - // skip multiple spaces in input file - while ((cmd_line[cmd_index] == ' ') && (ch == ' ')) - ch = fgetc(pSkillFile); - - cmd_index++; - } - - if (ch == '\r') // is it a carriage return? - { - ch = fgetc(pSkillFile); // skip the linefeed - } - - // if reached end of file, then close it - if (ch == EOF) - { - fclose(pSkillFile); - pSkillFile = NULL; - } - - cmd_line[cmd_index] = 0; // terminate the command line - - cmd_index = 0; - cmd = cmd_line; - arg1 = NULL; - - // skip to blank or end of string... - while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) - cmd_index++; - - if (cmd_line[cmd_index] == ' ') - { - cmd_line[cmd_index++] = 0; - arg1 = &cmd_line[cmd_index]; - } - - if ((cmd_line[0] == '#') || (cmd_line[0] == 0)) - continue; // skip if comment or blank line - - - if (strcasecmp(cmd, "[SKILL1]") == 0) - SkillNr = 0; - else if (strcasecmp(cmd, "[SKILL2]") == 0) - SkillNr = 1; - else if (strcasecmp(cmd, "[SKILL3]") == 0) - SkillNr = 2; - else if (strcasecmp(cmd, "[SKILL4]") == 0) - SkillNr = 3; - else if (strcasecmp(cmd, "[SKILL5]") == 0) - SkillNr = 4; - - if ((arg1 == NULL) || (*arg1 == 0)) - continue; - - if (SkillNr == -1) // Not in a skill block yet? - continue; - - value = atof(arg1); - - if (strcasecmp(cmd, "min_x_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMinAimXSpeed = value; - } - else if (strcasecmp(cmd, "max_x_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMaxAimXSpeed = value; - } - else if (strcasecmp(cmd, "min_y_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMinAimYSpeed = value; - } - else if (strcasecmp(cmd, "max_y_aim_speed") == 0) - { - m_BotSkills[SkillNr].flMaxAimYSpeed = value; - } - else if (strcasecmp(cmd, "min_x_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMinAimXOffset = value; - } - else if (strcasecmp(cmd, "max_x_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMaxAimXOffset = value; - } - else if (strcasecmp(cmd, "min_y_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMinAimYOffset = value; - } - else if (strcasecmp(cmd, "max_y_aim_offset") == 0) - { - m_BotSkills[SkillNr].flMaxAimYOffset = value; - } - else if (strcasecmp(cmd, "min_attack_delay") == 0) - { - m_BotSkills[SkillNr].flMinAttackDelay = value; - } - else if (strcasecmp(cmd, "max_attack_delay") == 0) - { - m_BotSkills[SkillNr].flMaxAttackDelay = value; - } - else if (strcasecmp(cmd, "min_enemy_search_delay") == 0) - { - m_BotSkills[SkillNr].flMinEnemySearchDelay = value; - } - else if (strcasecmp(cmd, "max_enemy_search_delay") == 0) - { - m_BotSkills[SkillNr].flMaxEnemySearchDelay = value; - } - else if (strcasecmp(cmd, "shoot_at_feet_percent") == 0) - { - if (value < 0) value = 0; - else if (value > 100) value = 100; - m_BotSkills[SkillNr].sShootAtFeetWithRLPercent = (short)value; - } - else if (strcasecmp(cmd, "can_predict_position") == 0) - { - m_BotSkills[SkillNr].bCanPredict = value; - } - else if (strcasecmp(cmd, "max_hear_volume") == 0) - { - if (value < 0) value = 0; - else if (value > 255) value = 100; - m_BotSkills[SkillNr].iMaxHearVolume = (int)value; - } - else if (strcasecmp(cmd, "can_circle_strafe") == 0) - { - m_BotSkills[SkillNr].bCircleStrafe = value; - } - else if (strcasecmp(cmd, "can_search_items_in_combat") == 0) - { - m_BotSkills[SkillNr].bCanSearchItemsInCombat = value; - } - } - - conoutf("done\n"); -} - -void CBotManager::InitSkillData() -{ - // Best skill - m_BotSkills[0].flMinReactionDelay = 0.015f; - m_BotSkills[0].flMaxReactionDelay = 0.035f; - m_BotSkills[0].flMinAimXOffset = 15.0f; - m_BotSkills[0].flMaxAimXOffset = 20.0f; - m_BotSkills[0].flMinAimYOffset = 10.0f; - m_BotSkills[0].flMaxAimYOffset = 15.0f; - m_BotSkills[0].flMinAimXSpeed = 330.0f; - m_BotSkills[0].flMaxAimXSpeed = 355.0f; - m_BotSkills[0].flMinAimYSpeed = 400.0f; - m_BotSkills[0].flMaxAimYSpeed = 450.0f; - m_BotSkills[0].flMinAttackDelay = 0.1f; - m_BotSkills[0].flMaxAttackDelay = 0.4f; - m_BotSkills[0].flMinEnemySearchDelay = 0.09f; - m_BotSkills[0].flMaxEnemySearchDelay = 0.12f; - m_BotSkills[0].sShootAtFeetWithRLPercent = 85; - m_BotSkills[0].bCanPredict = true; - m_BotSkills[0].iMaxHearVolume = 75; - m_BotSkills[0].bCircleStrafe = true; - m_BotSkills[0].bCanSearchItemsInCombat = true; - - // Good skill - m_BotSkills[1].flMinReactionDelay = 0.035f; - m_BotSkills[1].flMaxReactionDelay = 0.045f; - m_BotSkills[1].flMinAimXOffset = 20.0f; - m_BotSkills[1].flMaxAimXOffset = 25.0f; - m_BotSkills[1].flMinAimYOffset = 15.0f; - m_BotSkills[1].flMaxAimYOffset = 20.0f; - m_BotSkills[1].flMinAimXSpeed = 250.0f; - m_BotSkills[1].flMaxAimXSpeed = 265.0f; - m_BotSkills[1].flMinAimYSpeed = 260.0f; - m_BotSkills[1].flMaxAimYSpeed = 285.0f; - m_BotSkills[1].flMinAttackDelay = 0.3f; - m_BotSkills[1].flMaxAttackDelay = 0.6f; - m_BotSkills[1].flMinEnemySearchDelay = 0.12f; - m_BotSkills[1].flMaxEnemySearchDelay = 0.17f; - m_BotSkills[1].sShootAtFeetWithRLPercent = 60; - m_BotSkills[1].bCanPredict = true; - m_BotSkills[1].iMaxHearVolume = 60; - m_BotSkills[1].bCircleStrafe = true; - m_BotSkills[1].bCanSearchItemsInCombat = true; - - // Medium skill - m_BotSkills[2].flMinReactionDelay = 0.075f; - m_BotSkills[2].flMaxReactionDelay = 0.010f; - m_BotSkills[2].flMinAimXOffset = 25.0f; - m_BotSkills[2].flMaxAimXOffset = 30.0f; - m_BotSkills[2].flMinAimYOffset = 20.0f; - m_BotSkills[2].flMaxAimYOffset = 25.0f; - m_BotSkills[2].flMinAimXSpeed = 190.0f; - m_BotSkills[2].flMaxAimXSpeed = 125.0f; - m_BotSkills[2].flMinAimYSpeed = 210.0f; - m_BotSkills[2].flMaxAimYSpeed = 240.0f; - m_BotSkills[2].flMinAttackDelay = 0.75f; - m_BotSkills[2].flMaxAttackDelay = 1.0f; - m_BotSkills[2].flMinEnemySearchDelay = 0.18f; - m_BotSkills[2].flMaxEnemySearchDelay = 0.22f; - m_BotSkills[2].sShootAtFeetWithRLPercent = 25; - m_BotSkills[2].bCanPredict = false; - m_BotSkills[2].iMaxHearVolume = 45; - m_BotSkills[2].bCircleStrafe = true; - m_BotSkills[2].bCanSearchItemsInCombat = false; - - // Worse skill - m_BotSkills[3].flMinReactionDelay = 0.15f; - m_BotSkills[3].flMaxReactionDelay = 0.20f; - m_BotSkills[3].flMinAimXOffset = 30.0f; - m_BotSkills[3].flMaxAimXOffset = 35.0f; - m_BotSkills[3].flMinAimYOffset = 25.0f; - m_BotSkills[3].flMaxAimYOffset = 30.0f; - m_BotSkills[3].flMinAimXSpeed = 155.0f; - m_BotSkills[3].flMaxAimXSpeed = 170.0f; - m_BotSkills[3].flMinAimYSpeed = 160.0f; - m_BotSkills[3].flMaxAimYSpeed = 210.0f; - m_BotSkills[3].flMinAttackDelay = 1.2f; - m_BotSkills[3].flMaxAttackDelay = 1.6f; - m_BotSkills[3].flMinEnemySearchDelay = 0.25f; - m_BotSkills[3].flMaxEnemySearchDelay = 0.30f; - m_BotSkills[3].sShootAtFeetWithRLPercent = 10; - m_BotSkills[3].bCanPredict = false; - m_BotSkills[3].iMaxHearVolume = 30; - m_BotSkills[3].bCircleStrafe = false; - m_BotSkills[3].bCanSearchItemsInCombat = false; - - // Bad skill - m_BotSkills[4].flMinReactionDelay = 0.30f; - m_BotSkills[4].flMaxReactionDelay = 0.50f; - m_BotSkills[4].flMinAimXOffset = 35.0f; - m_BotSkills[4].flMaxAimXOffset = 40.0f; - m_BotSkills[4].flMinAimYOffset = 30.0f; - m_BotSkills[4].flMaxAimYOffset = 35.0f; - m_BotSkills[4].flMinAimXSpeed = 45.0f; - m_BotSkills[4].flMaxAimXSpeed = 60.0f; - m_BotSkills[4].flMinAimYSpeed = 125.0f; - m_BotSkills[4].flMaxAimYSpeed = 180.0f; - m_BotSkills[4].flMinAttackDelay = 1.5f; - m_BotSkills[4].flMaxAttackDelay = 2.0f; - m_BotSkills[4].flMinEnemySearchDelay = 0.30f; - m_BotSkills[4].flMaxEnemySearchDelay = 0.36f; - m_BotSkills[4].sShootAtFeetWithRLPercent = 0; - m_BotSkills[4].bCanPredict = false; - m_BotSkills[4].iMaxHearVolume = 15; - m_BotSkills[4].bCircleStrafe = false; - m_BotSkills[4].bCanSearchItemsInCombat = false; -} - -void CBotManager::ChangeBotSkill(short Skill, dynent *bot) -{ - static char *SkillNames[5] = { "best", "good", "medium", "worse", "bad" }; - - if (bot && bot->pBot) - { - // Only change skill of a single bot - bot->pBot->m_pBotSkill = &m_BotSkills[Skill]; - bot->pBot->m_sSkillNr = Skill; - conoutf("Skill of %s is now %s", (int)bot->name, (int)SkillNames[Skill]); - return; - } - - // Change skill of all bots - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - - bots[i]->pBot->m_pBotSkill = &m_BotSkills[Skill]; - bots[i]->pBot->m_sSkillNr = Skill; - } - - // Change default bot skill - m_sBotSkill = Skill; - - conoutf("Skill of all bots is now %s", (int)SkillNames[Skill]); -} - -void CBotManager::ViewBot() -{ - // Check if this bot is still in game - bool bFound = false; - loopv(bots) - { - if (bots[i] == m_pBotToView) - { - bFound = true; - break; - } - } - - if (!bFound) - { - DisableBotView(); - return; - } - - player1->state = CS_DEAD; // Fake dead - - player1->o = m_pBotToView->o; - player1->o.z += 1.0f; - player1->yaw = m_pBotToView->yaw; - player1->pitch = m_pBotToView->pitch; - player1->roll = m_pBotToView->roll; - player1->radius = 0; // Don't collide -} - -void CBotManager::DisableBotView() -{ - m_pBotToView = NULL; - respawnself(); - player1->radius = 1.1f; -} - -void CBotManager::CalculateMaxAStarCount() -{ - if (WaypointClass.m_iWaypointCount > 0) // Are there any waypoints? - { - m_sMaxAStarBots = 8 - short(ceil((float)WaypointClass.m_iWaypointCount / - 1000.0f)); - if (m_sMaxAStarBots < 1) - m_sMaxAStarBots = 1; - } - else - m_sMaxAStarBots = 1; -} - -void CBotManager::PickNextTrigger() -{ - if (!ishost()) return; - - short lowest = -1; - bool found0 = false; // True if found a trigger with nr 0 - - loopv(ents) - { - entity &e = ents[i]; - -#if defined AC_BUBE - if ((e.type != TRIGGER) || !e.spawned) - continue; -#elif defined VANILLA_CUBE - if ((e.type != CARROT) || !e.spawned) - continue; -#endif - if (OUTBORD(e.x, e.y)) continue; - - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - - node_s *pWptNearEnt = NULL; - - pWptNearEnt = WaypointClass.GetNearestTriggerWaypoint(o, 2.0f); - - if (pWptNearEnt) - { - if ((pWptNearEnt->sTriggerNr > 0) && - ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) - lowest = pWptNearEnt->sTriggerNr; - if (pWptNearEnt->sTriggerNr == 0) found0 = true; - } - -#ifdef WP_FLOOD - pWptNearEnt = WaypointClass.GetNearestTriggerFloodWP(o, 2.0f); - - if (pWptNearEnt) - { - if ((pWptNearEnt->sTriggerNr > 0) && - ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) - lowest = pWptNearEnt->sTriggerNr; - if (pWptNearEnt->sTriggerNr == 0) found0 = true; - } - -#endif - } - - if ((lowest == -1) && found0) lowest = 0; - - if (lowest != -1) - m_sCurrentTriggerNr = lowest; -} - -dynent *CBotManager::CreateBot(const char *team, const char *skill, const char *name) -{ - dynent *m = newdynent(); - if (!m) return NULL; - - // Create new bot class, dependand on the current mod -#if defined VANILLA_CUBE - m->pBot = new CCubeBot; -#elif defined AC_CUBE - m->pBot = new CACBot; -#else - #error "Unsupported mod!" -#endif - m->bIsBot = true; - m->pBot->m_pMyEnt = m; - m->pBot->m_iLastBotUpdate = 0; - m->pBot->m_bSendC2SInit = false; - - if (name && *name) - strn0cpy(m->name, name, 16); - else - strn0cpy(m->name, BotManager.GetBotName(), 16); - - if (team && *team && strcmp(team, "random")) - strn0cpy(m->team, team, 5); - else - strn0cpy(m->team, BotManager.GetBotTeam(), 5); - - if (skill && *skill && strcmp(skill, "random")) - { - if (!strcasecmp(skill, "best")) - m->pBot->m_sSkillNr = 0; - else if (!strcasecmp(skill, "good")) - m->pBot->m_sSkillNr = 1; - else if (!strcasecmp(skill, "medium")) - m->pBot->m_sSkillNr = 2; - else if (!strcasecmp(skill, "worse")) - m->pBot->m_sSkillNr = 3; - else if (!strcasecmp(skill, "bad")) - m->pBot->m_sSkillNr = 4; - else - { - conoutf("Wrong skill specified. Should be best, good, medium, " - "worse or bad"); - conoutf("Using default skill instead..."); - m->pBot->m_sSkillNr = BotManager.m_sBotSkill; - } - } - else // No skill specified, use default - m->pBot->m_sSkillNr = BotManager.m_sBotSkill; - - m->pBot->m_pBotSkill = &BotManager.m_BotSkills[m->pBot->m_sSkillNr]; - - // Sync waypoints - m->pBot->SyncWaypoints(); - - m->pBot->Spawn(); - - bots.add(m); - - return m; -} - -// Bot manager class end - -void addbot(char *arg1, char *arg2, char *arg3) -{ - if (ishost()) - { - conoutf("Creating bot...\n"); - dynent *b = BotManager.CreateBot(arg1, arg2, arg3); - if (b) - conoutf("connected: %s", (int)b->name); - else - { - conoutf("Error: Couldn't create bot!"); - return; - } - } - else if (clienthost) - { - char team[32], name[32]; - int skill = -1; - - team[0] = name[0] = 0; - - if (arg1 && arg1[0]) strcpy(team, arg1); - if (arg3 && arg3[0]) strcpy(name, arg3); - if (arg2 && arg2[0]) - { - if (!strcasecmp(arg2, "best")) skill = 0; - else if (!strcasecmp(arg2, "good")) skill = 1; - else if (!strcasecmp(arg2, "medium")) skill = 2; - else if (!strcasecmp(arg2, "worse")) skill = 3; - else if (!strcasecmp(arg2, "bad")) skill = 4; - } - - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_ADDBOT)); // Bot command type - putint(p, 1); // Bot count - putint(p, skill); // Bot skill - sendstring(team, p); // Bot team - sendstring(name, p); // Bot name - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } -} - -COMMAND(addbot, ARG_3STR); - -void addnbot(char *arg1, char *arg2, char *arg3) -{ - if (!arg1 || !arg1[0]) return; - - int i = atoi(arg1); - - if (ishost()) - { - while(i > 0) - { - addbot(arg2, arg3, NULL); - i--; - } - } - else if (clienthost) - { - char team[32], name[32]; - int skill = -1; - - team[0] = name[0] = 0; - - if (arg2 && arg2[0]) strcpy(team, arg2); -// if (arg4 && arg4[0]) strcpy(name, arg4); - if (arg3 && arg3[0]) - { - if (!strcasecmp(arg2, "best")) skill = 0; - else if (!strcasecmp(arg2, "good")) skill = 1; - else if (!strcasecmp(arg2, "medium")) skill = 2; - else if (!strcasecmp(arg2, "worse")) skill = 3; - else if (!strcasecmp(arg2, "bad")) skill = 4; - } - - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_ADDBOT)); // Bot command type - putint(p, i); // Bot count - putint(p, skill); // Bot skill - sendstring(team, p); // Bot team - sendstring(name, p); // Bot name - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } -} - -COMMAND(addnbot, ARG_3STR); - -void botsshoot(int Shoot) -{ - if (!ishost()) return; - if (Shoot) - { - BotManager.SetBotsShoot(true); - conoutf("Bots will shoot"); - } - else - { - BotManager.SetBotsShoot(false); - conoutf("Bots won't shoot"); - } -} - -COMMAND(botsshoot, ARG_1INT); - -void idlebots(int Idle) -{ - if (!ishost()) return; - if (Idle) - { - BotManager.SetIdleBots(true); - conoutf("Bots are idle"); - } - else - { - BotManager.SetIdleBots(false); - conoutf("Bots aren't idle"); - } -} - -COMMAND(idlebots, ARG_1INT); - -void drawbeamtobots() -{ - loopv(bots) - { - if (bots[i]) - particle_trail(1, 500, player1->o, bots[i]->o); - } -} - -COMMAND(drawbeamtobots, ARG_NONE); - -void kickbot(const char *szName) -{ - if (!szName || !(*szName)) - return; - - int iBotInd = -1; - loopv(bots) - { - if (!bots[i]) continue; - - if (!strcmp(bots[i]->name, szName)) - { - iBotInd = i; - break; - } - } - - if (iBotInd != -1) - { - if (ishost()) - { - addmsg(1, 2, SV_BOTDIS, iBotInd); - - conoutf("bot %s disconnected", (int)(bots[iBotInd]->name[0] ? bots[iBotInd]->name : - "[incompatible client]")); - delete bots[iBotInd]->pBot; - zapdynent(bots[iBotInd]); - bots.remove(iBotInd); - } - else if (clienthost) // Ask server to destroy bot(with voting) - { - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_KICKBOT)); // Bot command type - putint(p, 1); // 0==kick all bots, 1==kick one specific bot - char tmp[20]; - strcpy(tmp, szName); - sendstring(tmp, p); // Bot name - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } - } -} - -COMMAND(kickbot, ARG_1STR); - -void kickallbots(void) -{ - loopv(bots) - { - if (bots[i]) - { - if (ishost()) - { - addmsg(1, 2, SV_BOTDIS, i); - - conoutf("bot %s disconnected",(int)(bots[i]->name[0] ? - bots[i]->name : "[incompatible client]")); - delete bots[i]->pBot; - zapdynent(bots[i]); - } - else if (clienthost) // Ask the server to destroy all bots - { - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, - ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_KICKBOT)); // Bot command type - putint(p, 0); // 0==kick all bots, 1==kick one specific bot - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } - } - } - - bots.setsize(0); -} - -COMMAND(kickallbots, ARG_NONE); - -void togglegrap() -{ - if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GrabMode(0)) - SDL_WM_GrabInput(SDL_GRAB_ON); - else - SDL_WM_GrabInput(SDL_GrabMode(0)); -} - -COMMAND(togglegrap, ARG_NONE); - -void togglebotview(char *bot) -{ - if (BotManager.m_pBotToView) - BotManager.DisableBotView(); - else if (bot && *bot) - { - loopv(bots) - { - if (!bots[i]) continue; - - if (!strcmp(bots[i]->name, bot)) - { - BotManager.EnableBotView(bots[i]); - break; - } - } - } -} - -COMMAND(togglebotview, ARG_1STR); - -void botskill(char *bot, char *skill) -{ - if (!skill || !(*skill)) - return; - - short SkillNr; - - if (!strcasecmp(skill, "best")) - SkillNr = 0; - else if (!strcasecmp(skill, "good")) - SkillNr = 1; - else if (!strcasecmp(skill, "medium")) - SkillNr = 2; - else if (!strcasecmp(skill, "worse")) - SkillNr = 3; - else if (!strcasecmp(skill, "bad")) - SkillNr = 4; - else - { - conoutf("Wrong skill specified. Should be best, good, medium, worse or bad"); - return; - } - - if (ishost()) - { - if (bot) - { - loopv(bots) - { - if (bots[i] && !strcmp(bots[i]->name, bot)) - { - BotManager.ChangeBotSkill(SkillNr, bots[i]); - break; - } - } - } - else - BotManager.ChangeBotSkill(SkillNr); - } - else if (clienthost) // ask server to change botskill - { - ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); - uchar *start = packet->data; - uchar *p = start+2; - putint(p, SV_BOTCOMMAND); - putint(p, int(COMMAND_BOTSKILL)); // Bot command type - putint(p, SkillNr); - *(ushort *)start = ENET_HOST_TO_NET_16(p-start); - enet_packet_resize(packet, p-start); - sendpackettoserv(packet); - } -} - -COMMAND(botskill, ARG_2STR); - -void botskillall(char *skill) -{ - botskill(NULL, skill); -} - -COMMAND(botskillall, ARG_1STR); - -#ifndef RELEASE_BUILD - -#ifdef VANILLA_CUBE -void drawbeamtocarrots() -{ - loopv(ents) - { - entity &e = ents[i]; - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - if ((e.type != CARROT) || !e.spawned) continue; - particle_trail(1, 500, player1->o, o); - } -} - -COMMAND(drawbeamtocarrots, ARG_NONE); - -void drawbeamtoteleporters() -{ - loopv(ents) - { - entity &e = ents[i]; - vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; - if (e.type != TELEPORT) continue; - particle_trail(1, 500, player1->o, o); - } -} - -COMMAND(drawbeamtoteleporters, ARG_NONE); -#endif - -void telebot(void) -{ - vec dest = player1->o, forward, right, up; - vec angles = { player1->pitch, player1->yaw, player1->roll }; - traceresult_s tr; - - AnglesToVectors(angles, forward, right, up); - vmul(forward, 4.0f); - vadd(dest, forward); - - TraceLine(player1->o, dest, player1, true, &tr); - - if (!tr.collided) - { - // Get the first bot - loopv(bots) - { - if (!bots[i] || !bots[i]->pBot) continue; - bots[i]->o = tr.end; - break; - } - } -} - -COMMAND(telebot, ARG_NONE); - -void testvisible(int iDir) -{ - vec angles, end, forward, right, up; - traceresult_s tr; - int Dir; - - switch(iDir) - { - case 0: default: Dir = FORWARD; break; - case 1: Dir = BACKWARD; break; - case 2: Dir = LEFT; break; - case 3: Dir = RIGHT; break; - case 4: Dir = UP; break; - case 5: Dir = DOWN; break; - } - - vec from = player1->o; - from.z -= (player1->eyeheight - 1.25f); - end = from; - makevec(&angles, player1->pitch, player1->yaw, player1->roll); - angles.x=0; - - if (Dir & UP) - angles.x = WrapXAngle(angles.x + 45.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 45.0f); - - if ((Dir & FORWARD) || (Dir & BACKWARD)) - { - if (Dir & BACKWARD) - angles.y = WrapYZAngle(angles.y + 180.0f); - - if (Dir & LEFT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y - 45.0f); - else - angles.y = WrapYZAngle(angles.y + 45.0f); - } - else if (Dir & RIGHT) - { - if (Dir & FORWARD) - angles.y = WrapYZAngle(angles.y + 45.0f); - else - angles.y = WrapYZAngle(angles.y - 45.0f); - } - } - else if (Dir & LEFT) - angles.y = WrapYZAngle(angles.y - 90.0f); - else if (Dir & RIGHT) - angles.y = WrapYZAngle(angles.y + 90.0f); - else if (Dir & UP) - angles.x = WrapXAngle(angles.x + 90.0f); - else if (Dir & DOWN) - angles.x = WrapXAngle(angles.x - 90.0f); - - AnglesToVectors(angles, forward, right, up); - - vmul(forward, 20.0f); - vadd(end, forward); - - TraceLine(from, end, player1, false, &tr); - - //debugbeam(from, tr.end); - char sz[250]; - sprintf(sz, "dist: %f; hit: %d", GetDistance(from, tr.end), tr.collided); - condebug(sz); -} - -COMMAND(testvisible, ARG_1INT); - -void mapsize(void) -{ - conoutf("ssize: %d", ssize); -} - -COMMAND(mapsize, ARG_NONE); - -#endif +// +// C++ Implementation: bot +// +// Description: Code for botmanager +// +// Main bot file +// +// Author: Rick +// +// +// + +#include "../cube.h" + +#ifndef VANILLA_CUBE // UNDONE +bool dedserv = false; +#define CS_DEDHOST -1 +#endif + +extern ENetHost *clienthost; + +extern void respawnself(); + +CBotManager BotManager; + +// Bot manager class begin + +CBotManager::~CBotManager(void) +{ + EndMap(); + + while(m_StoredBots.Empty() == false) + delete m_StoredBots.Pop(); +} + +void CBotManager::Init() +{ + m_pBotToView = NULL; + + if (!ishost()) + return; + + m_bBotsShoot = true; + m_bIdleBots = false; + m_iFrameTime = 0; + m_iPrevTime = lastmillis; + m_sBotSkill = 1; // Default all bots have the skill 'Good' + + CreateSkillData(); + LoadBotNamesFile(); + LoadBotTeamsFile(); + WaypointClass.Init(); + lsrand(time(NULL)); +} + +void CBotManager::Think() +{ + if (m_bInit) + { + Init(); + m_bInit = false; + } + + if (m_pBotToView) + ViewBot(); + + if (!ishost()) + return; + + AddDebugText("m_sMaxAStarBots: %d", m_sMaxAStarBots); + AddDebugText("m_sCurrentTriggerNr: %d", m_sCurrentTriggerNr); + short x, y; + WaypointClass.GetNodeIndexes(player1->o, &x, &y); + AddDebugText("x: %d y: %d", x, y); + + m_iFrameTime = lastmillis - m_iPrevTime; + if (m_iFrameTime > 250) m_iFrameTime = 250; + m_iPrevTime = lastmillis; + + // Is it time to re-add bots? + if ((m_fReAddBotDelay < lastmillis) && (m_fReAddBotDelay != -1.0f)) + { + while(m_StoredBots.Empty() == false) + { + CStoredBot *pStoredBot = m_StoredBots.Pop(); + pStoredBot->ReAddBot(); + delete pStoredBot; + } + + m_fReAddBotDelay = -1.0f; + } + + // If this is a ded server check if there are any players, if not bots should be idle + if (dedserv) + { + bool botsbeidle = true; + loopv(players) + { + if (players[i] && (players[i]->state != CS_DEDHOST)) { botsbeidle = false; break; } + } + + if (botsbeidle) return; + } + + // Let all bots 'think' + loopv(bots) + { + if (!bots[i]) + continue; + + if (bots[i]->pBot) + bots[i]->pBot->Think(); + else + condebug("Error: pBot == NULL in bot ent\n"); + } +} + +void CBotManager::LoadBotNamesFile() +{ + if (!ishost()) + return; + + // Init bot names array first + for (int i=0;i<100;i++) + strcpy(m_szBotNames[i], "Bot"); + + m_sBotNameCount = 0; + + // Load bot file + char szNameFileName[256]; + MakeBotFileName("bot_names.txt", NULL, NULL, szNameFileName); + FILE *fp = fopen(szNameFileName, "r"); + char szNameBuffer[256]; + int iIndex, iStrIndex; + + if (!fp) + { + conoutf("Warning: Couldn't load bot names file"); + return; + } + + while (fgets(szNameBuffer, 80, fp) != NULL) + { + if (m_sBotNameCount >= 150) + { + conoutf("Warning: Max bot names reached(150), ignoring the rest of the" + "names"); + break; + } + + short length = strlen(szNameBuffer); + + if (szNameBuffer[length-1] == '\n') + { + szNameBuffer[length-1] = 0; // remove '\n' + length--; + } + + iStrIndex = 0; + while (iStrIndex < length) + { + if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || + (szNameBuffer[iStrIndex] == '"')) + { + for (iIndex=iStrIndex; iIndex < length; iIndex++) + szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; + } + + iStrIndex++; + } + + if (szNameBuffer[0] != 0) + { + if (strlen(szNameBuffer) >= 16) + { conoutf("Warning: bot name \"%s\" has to many characters(16 is max)", + (int)szNameBuffer); + } + strn0cpy(m_szBotNames[m_sBotNameCount], szNameBuffer, 16); + m_sBotNameCount++; + } + } + fclose(fp); +} + +char *CBotManager::GetBotName() +{ + char *szOutput = NULL; + TMultiChoice BotNameChoices; + short ChoiceVal; + + for(int j=0;jstate != CS_DEDHOST) && + !strcasecmp(players[i]->name, m_szBotNames[j])) + ChoiceVal -= 10; + } + + loopv(bots) + { + if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) + ChoiceVal -= 10; + } + + if ((player1->state != CS_DEDHOST) && !strcasecmp(player1->name, m_szBotNames[j])) + ChoiceVal -= 10; + + if (ChoiceVal <= 0) + ChoiceVal = 1; + + BotNameChoices.Insert(m_szBotNames[j], ChoiceVal); + } + + // Couldn't find a selection? + if (!BotNameChoices.GetSelection(szOutput)) + szOutput = "Bot"; + + return szOutput; +} + +void CBotManager::LoadBotTeamsFile() +{ + if (!ishost()) + return; + + // Init bot teams array first + for (int i=0;i<20;i++) + strcpy(m_szBotTeams[i], "b0ts"); + + m_sBotTeamCount = 0; + + // Load bot file + char szNameFileName[256]; + MakeBotFileName("bot_teams.txt", NULL, NULL, szNameFileName); + FILE *fp = fopen(szNameFileName, "r"); + char szNameBuffer[256]; + int iIndex, iStrIndex; + + if (!fp) + { + conoutf("Warning: Couldn't load bot teams file"); + return; + } + + while ((m_sBotTeamCount < 20) && (fgets(szNameBuffer, 80, fp) != NULL)) + { + short length = strlen(szNameBuffer); + + if (szNameBuffer[length-1] == '\n') + { + szNameBuffer[length-1] = 0; // remove '\n' + length--; + } + + iStrIndex = 0; + while (iStrIndex < length) + { + if ((szNameBuffer[iStrIndex] < ' ') || (szNameBuffer[iStrIndex] > '~') || + (szNameBuffer[iStrIndex] == '"')) + { + for (iIndex=iStrIndex; iIndex < length; iIndex++) + szNameBuffer[iIndex] = szNameBuffer[iIndex+1]; + } + + iStrIndex++; + } + + if (szNameBuffer[0] != 0) + { + strn0cpy(m_szBotTeams[m_sBotTeamCount], szNameBuffer, 5); + m_sBotTeamCount++; + } + } + fclose(fp); +} + +char *CBotManager::GetBotTeam() +{ + char *szOutput = NULL; + TMultiChoice BotTeamChoices; + short ChoiceVal; + + for(int j=0;jname, m_szBotNames[j]))) + ChoiceVal -= 10; + } + + loopv(bots) + { + if (bots[i] && (!strcasecmp(bots[i]->name, m_szBotNames[j]))) + ChoiceVal -= 10; + } + + if (!strcasecmp(player1->name, m_szBotNames[j])) + ChoiceVal -= 10; + + if (ChoiceVal <= 0) + ChoiceVmonsterclearal = 1;*/ + + BotTeamChoices.Insert(m_szBotTeams[j], ChoiceVal); + } + + // Couldn't find a selection? + if (!BotTeamChoices.GetSelection(szOutput)) + szOutput = "b0t"; + + return szOutput; +} + +void CBotManager::RenderBots() +{ + static bool drawblue; + + loopv(bots) + { + if (bots[i] && (bots[i] != m_pBotToView)) + { + drawblue = (m_sp || isteam(player1->team, bots[i]->team)); + renderclient(bots[i], drawblue, "playermodels/counterterrorist", false, 1.0f); + } + } +} + +void CBotManager::EndMap() +{ + // Remove all bots + loopv(bots) + { + if (!bots[i]) + continue; + + if (ishost()) // Store bots so they can be re-added after map change + { + if (bots[i]->pBot && bots[i]->name[0] && bots[i]->team[0]) + { + CStoredBot *pStoredBot = new CStoredBot(bots[i]->name, bots[i]->team, + bots[i]->pBot->m_sSkillNr); + m_StoredBots.AddNode(pStoredBot); + } + delete bots[i]->pBot; + } + + bots[i]->pBot = NULL; + zapdynent(bots[i]); + } + bots.setsize(0); + condebug("Cleared all bots"); + m_fReAddBotDelay = lastmillis + 7500; + //if(ishost()) WaypointClass.SaveWPExpFile(); //UNDONE +} + +void CBotManager::BeginMap(char *szMapName) +{ + EndMap(); // End previous map + + if (!ishost()) return; + + WaypointClass.Init(); + WaypointClass.SetMapName(szMapName); + if (!WaypointClass.LoadWaypoints()) + WaypointClass.StartFlood(); + //WaypointClass.LoadWPExpFile(); // UNDONE + + CalculateMaxAStarCount(); + m_sUsingAStarBotsCount = 0; + PickNextTrigger(); +} + +int CBotManager::GetBotIndex(dynent *m) +{ + loopv(bots) + { + if (!bots[i]) + continue; + + if (bots[i] == m) + return i; + } + + return -1; +} + +void CBotManager::LetBotsUpdateStats() +{ + loopv(bots) if (bots[i] && bots[i]->pBot) bots[i]->pBot->m_bSendC2SInit = false; +} + +void CBotManager::LetBotsHear(int n, vec *loc) +{ + if (bots.length() == 0) return; + + if (!loc) return; + + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot || (bots[i]->state == CS_DEAD)) continue; + bots[i]->pBot->HearSound(n, loc); + } +} + +// Notify all bots of a new waypoint +void CBotManager::AddWaypoint(node_s *pNode) +{ + if (bots.length()) + { + short x, y; + waypoint_s *pWP; + + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + pWP = new waypoint_s; + pWP->pNode = pNode; + WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); + bots[i]->pBot->m_WaypointList[x][y].AddNode(pWP); + +#ifndef RELEASE_BUILD + if (!bots[i]->pBot->GetWPFromNode(pNode)) condebug("Error adding bot wp!"); +#endif + } + } + + CalculateMaxAStarCount(); +} + +// Notify all bots of a deleted waypoint +void CBotManager::DelWaypoint(node_s *pNode) +{ + if (bots.length()) + { + short x, y; + TLinkedList::node_s *p; + + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + WaypointClass.GetNodeIndexes(pNode->v_origin, &x, &y); + p = bots[i]->pBot->m_WaypointList[x][y].GetFirst(); + + while(p) + { + if (p->Entry->pNode == pNode) + { + delete p->Entry; + bots[i]->pBot->m_WaypointList[x][y].DeleteNode(p); + break; + } + p = p->next; + } + } + } + + CalculateMaxAStarCount(); +} + +void CBotManager::MakeBotFileName(const char *szFileName, const char *szDir1, const char *szDir2, char *szOutput) +{ + char *DirSeperator; + +#ifndef __linux__ + DirSeperator = "\\"; + strcpy(szOutput, "bot\\"); +#else + DirSeperator = "/"; + strcpy(szOutput, "bot/"); +#endif + + if (szDir1) + { + strcat(szOutput, szDir1); + strcat(szOutput, DirSeperator); + } + + if (szDir2) + { + strcat(szOutput, szDir2); + strcat(szOutput, DirSeperator); + } + + strcat(szOutput, szFileName); +} + +void CBotManager::CreateSkillData() +{ + // First give the bot skill structure some default data + InitSkillData(); + + // Now see if we can load the skill.cfg file + char SkillFileName[256] = ""; + FILE *pSkillFile = NULL; + int SkillNr = -1; + float value = 0; + + MakeBotFileName("bot_skill.cfg", NULL, NULL, SkillFileName); + + pSkillFile = fopen(SkillFileName, "r"); + + conoutf("Reading bot_skill.cfg file... "); + + if (pSkillFile == NULL) // file doesn't exist + { + conoutf("skill file not found, default settings will be used\n"); + return; + } + + int ch; + char cmd_line[256]; + int cmd_index; + char *cmd, *arg1; + + while (pSkillFile) + { + cmd_index = 0; + cmd_line[cmd_index] = 0; + + ch = fgetc(pSkillFile); + + // skip any leading blanks + while (ch == ' ') + ch = fgetc(pSkillFile); + + while ((ch != EOF) && (ch != '\r') && (ch != '\n')) + { + if (ch == '\t') // convert tabs to spaces + ch = ' '; + + cmd_line[cmd_index] = ch; + + ch = fgetc(pSkillFile); + + // skip multiple spaces in input file + while ((cmd_line[cmd_index] == ' ') && (ch == ' ')) + ch = fgetc(pSkillFile); + + cmd_index++; + } + + if (ch == '\r') // is it a carriage return? + { + ch = fgetc(pSkillFile); // skip the linefeed + } + + // if reached end of file, then close it + if (ch == EOF) + { + fclose(pSkillFile); + pSkillFile = NULL; + } + + cmd_line[cmd_index] = 0; // terminate the command line + + cmd_index = 0; + cmd = cmd_line; + arg1 = NULL; + + // skip to blank or end of string... + while ((cmd_line[cmd_index] != ' ') && (cmd_line[cmd_index] != 0)) + cmd_index++; + + if (cmd_line[cmd_index] == ' ') + { + cmd_line[cmd_index++] = 0; + arg1 = &cmd_line[cmd_index]; + } + + if ((cmd_line[0] == '#') || (cmd_line[0] == 0)) + continue; // skip if comment or blank line + + + if (strcasecmp(cmd, "[SKILL1]") == 0) + SkillNr = 0; + else if (strcasecmp(cmd, "[SKILL2]") == 0) + SkillNr = 1; + else if (strcasecmp(cmd, "[SKILL3]") == 0) + SkillNr = 2; + else if (strcasecmp(cmd, "[SKILL4]") == 0) + SkillNr = 3; + else if (strcasecmp(cmd, "[SKILL5]") == 0) + SkillNr = 4; + + if ((arg1 == NULL) || (*arg1 == 0)) + continue; + + if (SkillNr == -1) // Not in a skill block yet? + continue; + + value = atof(arg1); + + if (strcasecmp(cmd, "min_x_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMinAimXSpeed = value; + } + else if (strcasecmp(cmd, "max_x_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMaxAimXSpeed = value; + } + else if (strcasecmp(cmd, "min_y_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMinAimYSpeed = value; + } + else if (strcasecmp(cmd, "max_y_aim_speed") == 0) + { + m_BotSkills[SkillNr].flMaxAimYSpeed = value; + } + else if (strcasecmp(cmd, "min_x_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMinAimXOffset = value; + } + else if (strcasecmp(cmd, "max_x_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMaxAimXOffset = value; + } + else if (strcasecmp(cmd, "min_y_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMinAimYOffset = value; + } + else if (strcasecmp(cmd, "max_y_aim_offset") == 0) + { + m_BotSkills[SkillNr].flMaxAimYOffset = value; + } + else if (strcasecmp(cmd, "min_attack_delay") == 0) + { + m_BotSkills[SkillNr].flMinAttackDelay = value; + } + else if (strcasecmp(cmd, "max_attack_delay") == 0) + { + m_BotSkills[SkillNr].flMaxAttackDelay = value; + } + else if (strcasecmp(cmd, "min_enemy_search_delay") == 0) + { + m_BotSkills[SkillNr].flMinEnemySearchDelay = value; + } + else if (strcasecmp(cmd, "max_enemy_search_delay") == 0) + { + m_BotSkills[SkillNr].flMaxEnemySearchDelay = value; + } + else if (strcasecmp(cmd, "shoot_at_feet_percent") == 0) + { + if (value < 0) value = 0; + else if (value > 100) value = 100; + m_BotSkills[SkillNr].sShootAtFeetWithRLPercent = (short)value; + } + else if (strcasecmp(cmd, "can_predict_position") == 0) + { + m_BotSkills[SkillNr].bCanPredict = value; + } + else if (strcasecmp(cmd, "max_hear_volume") == 0) + { + if (value < 0) value = 0; + else if (value > 255) value = 100; + m_BotSkills[SkillNr].iMaxHearVolume = (int)value; + } + else if (strcasecmp(cmd, "can_circle_strafe") == 0) + { + m_BotSkills[SkillNr].bCircleStrafe = value; + } + else if (strcasecmp(cmd, "can_search_items_in_combat") == 0) + { + m_BotSkills[SkillNr].bCanSearchItemsInCombat = value; + } + } + + conoutf("done\n"); +} + +void CBotManager::InitSkillData() +{ + // Best skill + m_BotSkills[0].flMinReactionDelay = 0.015f; + m_BotSkills[0].flMaxReactionDelay = 0.035f; + m_BotSkills[0].flMinAimXOffset = 15.0f; + m_BotSkills[0].flMaxAimXOffset = 20.0f; + m_BotSkills[0].flMinAimYOffset = 10.0f; + m_BotSkills[0].flMaxAimYOffset = 15.0f; + m_BotSkills[0].flMinAimXSpeed = 330.0f; + m_BotSkills[0].flMaxAimXSpeed = 355.0f; + m_BotSkills[0].flMinAimYSpeed = 400.0f; + m_BotSkills[0].flMaxAimYSpeed = 450.0f; + m_BotSkills[0].flMinAttackDelay = 0.1f; + m_BotSkills[0].flMaxAttackDelay = 0.4f; + m_BotSkills[0].flMinEnemySearchDelay = 0.09f; + m_BotSkills[0].flMaxEnemySearchDelay = 0.12f; + m_BotSkills[0].sShootAtFeetWithRLPercent = 85; + m_BotSkills[0].bCanPredict = true; + m_BotSkills[0].iMaxHearVolume = 75; + m_BotSkills[0].bCircleStrafe = true; + m_BotSkills[0].bCanSearchItemsInCombat = true; + + // Good skill + m_BotSkills[1].flMinReactionDelay = 0.035f; + m_BotSkills[1].flMaxReactionDelay = 0.045f; + m_BotSkills[1].flMinAimXOffset = 20.0f; + m_BotSkills[1].flMaxAimXOffset = 25.0f; + m_BotSkills[1].flMinAimYOffset = 15.0f; + m_BotSkills[1].flMaxAimYOffset = 20.0f; + m_BotSkills[1].flMinAimXSpeed = 250.0f; + m_BotSkills[1].flMaxAimXSpeed = 265.0f; + m_BotSkills[1].flMinAimYSpeed = 260.0f; + m_BotSkills[1].flMaxAimYSpeed = 285.0f; + m_BotSkills[1].flMinAttackDelay = 0.3f; + m_BotSkills[1].flMaxAttackDelay = 0.6f; + m_BotSkills[1].flMinEnemySearchDelay = 0.12f; + m_BotSkills[1].flMaxEnemySearchDelay = 0.17f; + m_BotSkills[1].sShootAtFeetWithRLPercent = 60; + m_BotSkills[1].bCanPredict = true; + m_BotSkills[1].iMaxHearVolume = 60; + m_BotSkills[1].bCircleStrafe = true; + m_BotSkills[1].bCanSearchItemsInCombat = true; + + // Medium skill + m_BotSkills[2].flMinReactionDelay = 0.075f; + m_BotSkills[2].flMaxReactionDelay = 0.010f; + m_BotSkills[2].flMinAimXOffset = 25.0f; + m_BotSkills[2].flMaxAimXOffset = 30.0f; + m_BotSkills[2].flMinAimYOffset = 20.0f; + m_BotSkills[2].flMaxAimYOffset = 25.0f; + m_BotSkills[2].flMinAimXSpeed = 190.0f; + m_BotSkills[2].flMaxAimXSpeed = 125.0f; + m_BotSkills[2].flMinAimYSpeed = 210.0f; + m_BotSkills[2].flMaxAimYSpeed = 240.0f; + m_BotSkills[2].flMinAttackDelay = 0.75f; + m_BotSkills[2].flMaxAttackDelay = 1.0f; + m_BotSkills[2].flMinEnemySearchDelay = 0.18f; + m_BotSkills[2].flMaxEnemySearchDelay = 0.22f; + m_BotSkills[2].sShootAtFeetWithRLPercent = 25; + m_BotSkills[2].bCanPredict = false; + m_BotSkills[2].iMaxHearVolume = 45; + m_BotSkills[2].bCircleStrafe = true; + m_BotSkills[2].bCanSearchItemsInCombat = false; + + // Worse skill + m_BotSkills[3].flMinReactionDelay = 0.15f; + m_BotSkills[3].flMaxReactionDelay = 0.20f; + m_BotSkills[3].flMinAimXOffset = 30.0f; + m_BotSkills[3].flMaxAimXOffset = 35.0f; + m_BotSkills[3].flMinAimYOffset = 25.0f; + m_BotSkills[3].flMaxAimYOffset = 30.0f; + m_BotSkills[3].flMinAimXSpeed = 155.0f; + m_BotSkills[3].flMaxAimXSpeed = 170.0f; + m_BotSkills[3].flMinAimYSpeed = 160.0f; + m_BotSkills[3].flMaxAimYSpeed = 210.0f; + m_BotSkills[3].flMinAttackDelay = 1.2f; + m_BotSkills[3].flMaxAttackDelay = 1.6f; + m_BotSkills[3].flMinEnemySearchDelay = 0.25f; + m_BotSkills[3].flMaxEnemySearchDelay = 0.30f; + m_BotSkills[3].sShootAtFeetWithRLPercent = 10; + m_BotSkills[3].bCanPredict = false; + m_BotSkills[3].iMaxHearVolume = 30; + m_BotSkills[3].bCircleStrafe = false; + m_BotSkills[3].bCanSearchItemsInCombat = false; + + // Bad skill + m_BotSkills[4].flMinReactionDelay = 0.30f; + m_BotSkills[4].flMaxReactionDelay = 0.50f; + m_BotSkills[4].flMinAimXOffset = 35.0f; + m_BotSkills[4].flMaxAimXOffset = 40.0f; + m_BotSkills[4].flMinAimYOffset = 30.0f; + m_BotSkills[4].flMaxAimYOffset = 35.0f; + m_BotSkills[4].flMinAimXSpeed = 45.0f; + m_BotSkills[4].flMaxAimXSpeed = 60.0f; + m_BotSkills[4].flMinAimYSpeed = 125.0f; + m_BotSkills[4].flMaxAimYSpeed = 180.0f; + m_BotSkills[4].flMinAttackDelay = 1.5f; + m_BotSkills[4].flMaxAttackDelay = 2.0f; + m_BotSkills[4].flMinEnemySearchDelay = 0.30f; + m_BotSkills[4].flMaxEnemySearchDelay = 0.36f; + m_BotSkills[4].sShootAtFeetWithRLPercent = 0; + m_BotSkills[4].bCanPredict = false; + m_BotSkills[4].iMaxHearVolume = 15; + m_BotSkills[4].bCircleStrafe = false; + m_BotSkills[4].bCanSearchItemsInCombat = false; +} + +void CBotManager::ChangeBotSkill(short Skill, dynent *bot) +{ + static char *SkillNames[5] = { "best", "good", "medium", "worse", "bad" }; + + if (bot && bot->pBot) + { + // Only change skill of a single bot + bot->pBot->m_pBotSkill = &m_BotSkills[Skill]; + bot->pBot->m_sSkillNr = Skill; + conoutf("Skill of %s is now %s", (int)bot->name, (int)SkillNames[Skill]); + return; + } + + // Change skill of all bots + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + + bots[i]->pBot->m_pBotSkill = &m_BotSkills[Skill]; + bots[i]->pBot->m_sSkillNr = Skill; + } + + // Change default bot skill + m_sBotSkill = Skill; + + conoutf("Skill of all bots is now %s", (int)SkillNames[Skill]); +} + +void CBotManager::ViewBot() +{ + // Check if this bot is still in game + bool bFound = false; + loopv(bots) + { + if (bots[i] == m_pBotToView) + { + bFound = true; + break; + } + } + + if (!bFound) + { + DisableBotView(); + return; + } + + player1->state = CS_DEAD; // Fake dead + + player1->o = m_pBotToView->o; + player1->o.z += 1.0f; + player1->yaw = m_pBotToView->yaw; + player1->pitch = m_pBotToView->pitch; + player1->roll = m_pBotToView->roll; + player1->radius = 0; // Don't collide +} + +void CBotManager::DisableBotView() +{ + m_pBotToView = NULL; + respawnself(); + player1->radius = 1.1f; +} + +void CBotManager::CalculateMaxAStarCount() +{ + if (WaypointClass.m_iWaypointCount > 0) // Are there any waypoints? + { + m_sMaxAStarBots = 8 - short(ceil((float)WaypointClass.m_iWaypointCount / + 1000.0f)); + if (m_sMaxAStarBots < 1) + m_sMaxAStarBots = 1; + } + else + m_sMaxAStarBots = 1; +} + +void CBotManager::PickNextTrigger() +{ + if (!ishost()) return; + + short lowest = -1; + bool found0 = false; // True if found a trigger with nr 0 + + loopv(ents) + { + entity &e = ents[i]; + +#if defined AC_BUBE + if ((e.type != TRIGGER) || !e.spawned) + continue; +#elif defined VANILLA_CUBE + if ((e.type != CARROT) || !e.spawned) + continue; +#endif + if (OUTBORD(e.x, e.y)) continue; + + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + + node_s *pWptNearEnt = NULL; + + pWptNearEnt = WaypointClass.GetNearestTriggerWaypoint(o, 2.0f); + + if (pWptNearEnt) + { + if ((pWptNearEnt->sTriggerNr > 0) && + ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) + lowest = pWptNearEnt->sTriggerNr; + if (pWptNearEnt->sTriggerNr == 0) found0 = true; + } + +#ifdef WP_FLOOD + pWptNearEnt = WaypointClass.GetNearestTriggerFloodWP(o, 2.0f); + + if (pWptNearEnt) + { + if ((pWptNearEnt->sTriggerNr > 0) && + ((pWptNearEnt->sTriggerNr < lowest) || (lowest == -1))) + lowest = pWptNearEnt->sTriggerNr; + if (pWptNearEnt->sTriggerNr == 0) found0 = true; + } + +#endif + } + + if ((lowest == -1) && found0) lowest = 0; + + if (lowest != -1) + m_sCurrentTriggerNr = lowest; +} + +dynent *CBotManager::CreateBot(const char *team, const char *skill, const char *name) +{ + dynent *m = newdynent(); + if (!m) return NULL; + + // Create new bot class, dependand on the current mod +#if defined VANILLA_CUBE + m->pBot = new CCubeBot; +#elif defined AC_CUBE + m->pBot = new CACBot; +#else + #error "Unsupported mod!" +#endif + m->bIsBot = true; + m->pBot->m_pMyEnt = m; + m->pBot->m_iLastBotUpdate = 0; + m->pBot->m_bSendC2SInit = false; + + if (name && *name) + strn0cpy(m->name, name, 16); + else + strn0cpy(m->name, BotManager.GetBotName(), 16); + + if (team && *team && strcmp(team, "random")) + strn0cpy(m->team, team, 5); + else + strn0cpy(m->team, BotManager.GetBotTeam(), 5); + + if (skill && *skill && strcmp(skill, "random")) + { + if (!strcasecmp(skill, "best")) + m->pBot->m_sSkillNr = 0; + else if (!strcasecmp(skill, "good")) + m->pBot->m_sSkillNr = 1; + else if (!strcasecmp(skill, "medium")) + m->pBot->m_sSkillNr = 2; + else if (!strcasecmp(skill, "worse")) + m->pBot->m_sSkillNr = 3; + else if (!strcasecmp(skill, "bad")) + m->pBot->m_sSkillNr = 4; + else + { + conoutf("Wrong skill specified. Should be best, good, medium, " + "worse or bad"); + conoutf("Using default skill instead..."); + m->pBot->m_sSkillNr = BotManager.m_sBotSkill; + } + } + else // No skill specified, use default + m->pBot->m_sSkillNr = BotManager.m_sBotSkill; + + m->pBot->m_pBotSkill = &BotManager.m_BotSkills[m->pBot->m_sSkillNr]; + + // Sync waypoints + m->pBot->SyncWaypoints(); + + m->pBot->Spawn(); + + bots.add(m); + + return m; +} + +// Bot manager class end + +void addbot(char *arg1, char *arg2, char *arg3) +{ + if (ishost()) + { + conoutf("Creating bot...\n"); + dynent *b = BotManager.CreateBot(arg1, arg2, arg3); + if (b) + conoutf("connected: %s", (int)b->name); + else + { + conoutf("Error: Couldn't create bot!"); + return; + } + } + else if (clienthost) + { + char team[32], name[32]; + int skill = -1; + + team[0] = name[0] = 0; + + if (arg1 && arg1[0]) strcpy(team, arg1); + if (arg3 && arg3[0]) strcpy(name, arg3); + if (arg2 && arg2[0]) + { + if (!strcasecmp(arg2, "best")) skill = 0; + else if (!strcasecmp(arg2, "good")) skill = 1; + else if (!strcasecmp(arg2, "medium")) skill = 2; + else if (!strcasecmp(arg2, "worse")) skill = 3; + else if (!strcasecmp(arg2, "bad")) skill = 4; + } + + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_ADDBOT)); // Bot command type + putint(p, 1); // Bot count + putint(p, skill); // Bot skill + sendstring(team, p); // Bot team + sendstring(name, p); // Bot name + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } +} + +COMMAND(addbot, ARG_3STR); + +void addnbot(char *arg1, char *arg2, char *arg3) +{ + if (!arg1 || !arg1[0]) return; + + int i = atoi(arg1); + + if (ishost()) + { + while(i > 0) + { + addbot(arg2, arg3, NULL); + i--; + } + } + else if (clienthost) + { + char team[32], name[32]; + int skill = -1; + + team[0] = name[0] = 0; + + if (arg2 && arg2[0]) strcpy(team, arg2); +// if (arg4 && arg4[0]) strcpy(name, arg4); + if (arg3 && arg3[0]) + { + if (!strcasecmp(arg2, "best")) skill = 0; + else if (!strcasecmp(arg2, "good")) skill = 1; + else if (!strcasecmp(arg2, "medium")) skill = 2; + else if (!strcasecmp(arg2, "worse")) skill = 3; + else if (!strcasecmp(arg2, "bad")) skill = 4; + } + + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_ADDBOT)); // Bot command type + putint(p, i); // Bot count + putint(p, skill); // Bot skill + sendstring(team, p); // Bot team + sendstring(name, p); // Bot name + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } +} + +COMMAND(addnbot, ARG_3STR); + +void botsshoot(int Shoot) +{ + if (!ishost()) return; + if (Shoot) + { + BotManager.SetBotsShoot(true); + conoutf("Bots will shoot"); + } + else + { + BotManager.SetBotsShoot(false); + conoutf("Bots won't shoot"); + } +} + +COMMAND(botsshoot, ARG_1INT); + +void idlebots(int Idle) +{ + if (!ishost()) return; + if (Idle) + { + BotManager.SetIdleBots(true); + conoutf("Bots are idle"); + } + else + { + BotManager.SetIdleBots(false); + conoutf("Bots aren't idle"); + } +} + +COMMAND(idlebots, ARG_1INT); + +void drawbeamtobots() +{ + loopv(bots) + { + if (bots[i]) + particle_trail(1, 500, player1->o, bots[i]->o); + } +} + +COMMAND(drawbeamtobots, ARG_NONE); + +void kickbot(const char *szName) +{ + if (!szName || !(*szName)) + return; + + int iBotInd = -1; + loopv(bots) + { + if (!bots[i]) continue; + + if (!strcmp(bots[i]->name, szName)) + { + iBotInd = i; + break; + } + } + + if (iBotInd != -1) + { + if (ishost()) + { + addmsg(1, 2, SV_BOTDIS, iBotInd); + + conoutf("bot %s disconnected", (int)(bots[iBotInd]->name[0] ? bots[iBotInd]->name : + "[incompatible client]")); + delete bots[iBotInd]->pBot; + zapdynent(bots[iBotInd]); + bots.remove(iBotInd); + } + else if (clienthost) // Ask server to destroy bot(with voting) + { + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_KICKBOT)); // Bot command type + putint(p, 1); // 0==kick all bots, 1==kick one specific bot + char tmp[20]; + strcpy(tmp, szName); + sendstring(tmp, p); // Bot name + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } + } +} + +COMMAND(kickbot, ARG_1STR); + +void kickallbots(void) +{ + loopv(bots) + { + if (bots[i]) + { + if (ishost()) + { + addmsg(1, 2, SV_BOTDIS, i); + + conoutf("bot %s disconnected",(int)(bots[i]->name[0] ? + bots[i]->name : "[incompatible client]")); + delete bots[i]->pBot; + zapdynent(bots[i]); + } + else if (clienthost) // Ask the server to destroy all bots + { + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, + ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_KICKBOT)); // Bot command type + putint(p, 0); // 0==kick all bots, 1==kick one specific bot + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } + } + } + + bots.setsize(0); +} + +COMMAND(kickallbots, ARG_NONE); + +void togglegrap() +{ + if (SDL_WM_GrabInput(SDL_GRAB_QUERY) == SDL_GrabMode(0)) + SDL_WM_GrabInput(SDL_GRAB_ON); + else + SDL_WM_GrabInput(SDL_GrabMode(0)); +} + +COMMAND(togglegrap, ARG_NONE); + +void togglebotview(char *bot) +{ + if (BotManager.m_pBotToView) + BotManager.DisableBotView(); + else if (bot && *bot) + { + loopv(bots) + { + if (!bots[i]) continue; + + if (!strcmp(bots[i]->name, bot)) + { + BotManager.EnableBotView(bots[i]); + break; + } + } + } +} + +COMMAND(togglebotview, ARG_1STR); + +void botskill(char *bot, char *skill) +{ + if (!skill || !(*skill)) + return; + + short SkillNr; + + if (!strcasecmp(skill, "best")) + SkillNr = 0; + else if (!strcasecmp(skill, "good")) + SkillNr = 1; + else if (!strcasecmp(skill, "medium")) + SkillNr = 2; + else if (!strcasecmp(skill, "worse")) + SkillNr = 3; + else if (!strcasecmp(skill, "bad")) + SkillNr = 4; + else + { + conoutf("Wrong skill specified. Should be best, good, medium, worse or bad"); + return; + } + + if (ishost()) + { + if (bot) + { + loopv(bots) + { + if (bots[i] && !strcmp(bots[i]->name, bot)) + { + BotManager.ChangeBotSkill(SkillNr, bots[i]); + break; + } + } + } + else + BotManager.ChangeBotSkill(SkillNr); + } + else if (clienthost) // ask server to change botskill + { + ENetPacket *packet = enet_packet_create(NULL, MAXTRANS, ENET_PACKET_FLAG_RELIABLE); + uchar *start = packet->data; + uchar *p = start+2; + putint(p, SV_BOTCOMMAND); + putint(p, int(COMMAND_BOTSKILL)); // Bot command type + putint(p, SkillNr); + *(ushort *)start = ENET_HOST_TO_NET_16(p-start); + enet_packet_resize(packet, p-start); + sendpackettoserv(packet); + } +} + +COMMAND(botskill, ARG_2STR); + +void botskillall(char *skill) +{ + botskill(NULL, skill); +} + +COMMAND(botskillall, ARG_1STR); + +#ifndef RELEASE_BUILD + +#ifdef VANILLA_CUBE +void drawbeamtocarrots() +{ + loopv(ents) + { + entity &e = ents[i]; + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + if ((e.type != CARROT) || !e.spawned) continue; + particle_trail(1, 500, player1->o, o); + } +} + +COMMAND(drawbeamtocarrots, ARG_NONE); + +void drawbeamtoteleporters() +{ + loopv(ents) + { + entity &e = ents[i]; + vec o = { e.x, e.y, S(e.x, e.y)->floor+player1->eyeheight }; + if (e.type != TELEPORT) continue; + particle_trail(1, 500, player1->o, o); + } +} + +COMMAND(drawbeamtoteleporters, ARG_NONE); +#endif + +void telebot(void) +{ + vec dest = player1->o, forward, right, up; + vec angles = { player1->pitch, player1->yaw, player1->roll }; + traceresult_s tr; + + AnglesToVectors(angles, forward, right, up); + vmul(forward, 4.0f); + vadd(dest, forward); + + TraceLine(player1->o, dest, player1, true, &tr); + + if (!tr.collided) + { + // Get the first bot + loopv(bots) + { + if (!bots[i] || !bots[i]->pBot) continue; + bots[i]->o = tr.end; + break; + } + } +} + +COMMAND(telebot, ARG_NONE); + +void testvisible(int iDir) +{ + vec angles, end, forward, right, up; + traceresult_s tr; + int Dir; + + switch(iDir) + { + case 0: default: Dir = FORWARD; break; + case 1: Dir = BACKWARD; break; + case 2: Dir = LEFT; break; + case 3: Dir = RIGHT; break; + case 4: Dir = UP; break; + case 5: Dir = DOWN; break; + } + + vec from = player1->o; + from.z -= (player1->eyeheight - 1.25f); + end = from; + makevec(&angles, player1->pitch, player1->yaw, player1->roll); + angles.x=0; + + if (Dir & UP) + angles.x = WrapXAngle(angles.x + 45.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 45.0f); + + if ((Dir & FORWARD) || (Dir & BACKWARD)) + { + if (Dir & BACKWARD) + angles.y = WrapYZAngle(angles.y + 180.0f); + + if (Dir & LEFT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y - 45.0f); + else + angles.y = WrapYZAngle(angles.y + 45.0f); + } + else if (Dir & RIGHT) + { + if (Dir & FORWARD) + angles.y = WrapYZAngle(angles.y + 45.0f); + else + angles.y = WrapYZAngle(angles.y - 45.0f); + } + } + else if (Dir & LEFT) + angles.y = WrapYZAngle(angles.y - 90.0f); + else if (Dir & RIGHT) + angles.y = WrapYZAngle(angles.y + 90.0f); + else if (Dir & UP) + angles.x = WrapXAngle(angles.x + 90.0f); + else if (Dir & DOWN) + angles.x = WrapXAngle(angles.x - 90.0f); + + AnglesToVectors(angles, forward, right, up); + + vmul(forward, 20.0f); + vadd(end, forward); + + TraceLine(from, end, player1, false, &tr); + + //debugbeam(from, tr.end); + char sz[250]; + sprintf(sz, "dist: %f; hit: %d", GetDistance(from, tr.end), tr.collided); + condebug(sz); +} + +COMMAND(testvisible, ARG_1INT); + +void mapsize(void) +{ + conoutf("ssize: %d", ssize); +} + +COMMAND(mapsize, ARG_NONE); + +#endif diff --git a/source/src/clients2c.cpp b/source/src/clients2c.cpp index 1be9a668..85fd7b43 100644 --- a/source/src/clients2c.cpp +++ b/source/src/clients2c.cpp @@ -234,27 +234,27 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th if(target==clientnum) { if(ls==player1->lifesequence) selfdamage(damage, cn, d); else printf("wrong lifesequence\n"); } else playsound(S_PAIN1+rnd(5), &getclient(target)->o); break; - }; - - case SV_BOT2CLIENTDMG: - { - - int target = getint(p); - int damage = getint(p); - int ls = getint(p); - int damager = getint(p); - - if(target==clientnum) - { - dynent *a = getbot(damager); - if(ls==player1->lifesequence) - selfdamage(damage, cn, a); - } - else playsound(S_PAIN1+rnd(5), &getclient(target)->o); - break; + }; + + case SV_BOT2CLIENTDMG: + { + + int target = getint(p); + int damage = getint(p); + int ls = getint(p); + int damager = getint(p); + + if(target==clientnum) + { + dynent *a = getbot(damager); + if(ls==player1->lifesequence) + selfdamage(damage, cn, a); + } + else playsound(S_PAIN1+rnd(5), &getclient(target)->o); + break; } - - case SV_DIEDBYBOT: + + case SV_DIEDBYBOT: killedbybot = true; case SV_DIED: { @@ -294,18 +294,18 @@ void localservertoclient(uchar *buf, int len) // processes any updates from th }; }; playsound(S_DIE1+rnd(2), &d->o); - if(!c2si) d->lifesequence++; + if(!c2si) d->lifesequence++; killedbybot = false; break; - }; - + }; + case SV_FRAGS: players[cn]->frags = getint(p); break; - case SV_ITEMPICKUP: + case SV_ITEMPICKUP: case SV_BOTITEMPICKUP: setspawn(getint(p), false); getint(p); diff --git a/source/src/cube.h b/source/src/cube.h index b2a85820..f2ce2704 100644 --- a/source/src/cube.h +++ b/source/src/cube.h @@ -92,7 +92,7 @@ struct block { int x, y, xs, ys; }; struct mapmodelinfo { int rad, h, zoff, snap; char *name; }; enum { GUN_KNIFE = 0, GUN_PISTOL, GUN_SHOTGUN, GUN_SUBGUN, GUN_SNIPER, GUN_ASSULT, GUN_GRENADE, NUMGUNS }; -enum { G_PRIMARY=0, G_SECONDARY, G_MELEE, G_GRENADE, G_NUM }; +enum { G_PRIMARY=0, G_SECONDARY, G_MELEE, G_GRENADE, G_NUM }; @@ -278,7 +278,7 @@ enum // Added by Rick: Bot specific messages SV_BOTSOUND, SV_BOTDIS, SV_BOTDIED, SV_CLIENT2BOTDMG, SV_BOT2BOTDMG, SV_BOTFRAGS, SV_ADDBOT, SV_BOTUPDATE, SV_BOTCOMMAND, - // End add + // End add SV_BOTITEMPICKUP, SV_BOT2CLIENTDMG, SV_DIEDBYBOT, SV_EXT, }; diff --git a/source/src/entities.cpp b/source/src/entities.cpp index 1b69f717..c5bc52d8 100644 --- a/source/src/entities.cpp +++ b/source/src/entities.cpp @@ -129,13 +129,13 @@ void realpickup(int n, dynent *d) case I_AMMO: radditem(n, d->ammo[d->primary], d->primary); break; - case I_GRENADE: - radditem(n, d->mag[6], 6); + case I_GRENADE: + radditem(n, d->mag[6], 6); break; - case I_HEALTH: - radditem(n, d->health, 7); + case I_HEALTH: + radditem(n, d->health, 7); break; - case I_ARMOUR: + case I_ARMOUR: radditem(n, d->armour, 8); //d->hasarmour = true; break; @@ -291,7 +291,7 @@ void radd(dynent *d) else if (d->primary>GUN_PISTOL && d->primaryammo[d->primary] = itemstats[d->primary-1].max; - d->mag[d->primary] = itemstats[d->primary-1].start; + d->mag[d->primary] = itemstats[d->primary-1].start; printf("player is you: %i\n", d==player1?1:0); }; diff --git a/source/src/protos.h b/source/src/protos.h index 03f3287c..9722b8d1 100644 --- a/source/src/protos.h +++ b/source/src/protos.h @@ -243,7 +243,7 @@ extern void createrays(vec &from, vec &to); extern void moveprojectiles(float time); extern void projreset(); extern dynent *playerincrosshair(); -extern int reloadtime(int gun); +extern int reloadtime(int gun); extern void reload(dynent *d); extern int attackdelay(int gun); extern int magsize(int gun); diff --git a/source/src/server.cpp b/source/src/server.cpp index c6d0cd94..06794e18 100644 --- a/source/src/server.cpp +++ b/source/src/server.cpp @@ -294,7 +294,7 @@ bool vote(char *map, int reqmode, int sender) return true; }; -// Added by Rick: Vote for bot commands +// Added by Rick: Vote for bot commands void resetbotvotes() { @@ -587,12 +587,12 @@ void process(ENetPacket * packet, int sender) // sender may be -1 int n = getint(p); pickup(n, getint(p), sender); break; - }; - - case SV_BOTITEMPICKUP: - { - loopi(2) getint(p); - break; + }; + + case SV_BOTITEMPICKUP: + { + loopi(2) getint(p); + break; }; case SV_PING: diff --git a/source/src/serverutil.cpp b/source/src/serverutil.cpp index 381daf20..ddce17d6 100644 --- a/source/src/serverutil.cpp +++ b/source/src/serverutil.cpp @@ -64,7 +64,7 @@ char msgsizesl[] = // size inclusive message token, 0 for variable // Added by Rick: Bot specific messages SV_BOTSOUND, 3, SV_BOTDIS, 2, SV_BOTDIED, 4, SV_CLIENT2BOTDMG, 4, SV_BOT2BOTDMG, 2, SV_BOTFRAGS, 3, SV_ADDBOT, 5, SV_BOTUPDATE, 12, SV_BOTCOMMAND, 0, - // End add + // End add SV_BOTITEMPICKUP, 3, SV_BOT2CLIENTDMG, 5, SV_DIEDBYBOT, 2, SV_EXT, 0, -1 diff --git a/source/src/weapon.cpp b/source/src/weapon.cpp index 2f332bdd..b7003766 100644 --- a/source/src/weapon.cpp +++ b/source/src/weapon.cpp @@ -55,8 +55,8 @@ COMMAND(weapon, ARG_1INT); COMMAND(shiftweapon, ARG_1INT); void reload(dynent *d) -{ - if(!d) return; +{ + if(!d) return; bool akimbo = d->gunselect==GUN_PISTOL && d->akimbo!=0; @@ -97,7 +97,7 @@ void reload(dynent *d) if(akimbo) playsoundc(S_RAKIMBO); else playsoundc(guns[d->gunselect].reload); }; - + void selfreload() { reload(player1); }; COMMANDN(reload, selfreload, ARG_NONE); @@ -249,7 +249,7 @@ void hit(int target, int damage, dynent *d, dynent *at) } } } - if(at->bIsBot) addmsg(1, 5, SV_BOT2CLIENTDMG, target, damage, d->lifesequence, PlayerIndex); + if(at->bIsBot) addmsg(1, 5, SV_BOT2CLIENTDMG, target, damage, d->lifesequence, PlayerIndex); else addmsg(1, 4, SV_DAMAGE, target, damage, d->lifesequence); playsound(S_PAIN1+rnd(5), &d->o); }; @@ -658,7 +658,7 @@ void shoot(dynent *d, vec &targ) d->lastattackgun = -1; return; }; - // End mod + // End mod if(d->gunselect) d->mag[d->gunselect]--; @@ -699,16 +699,16 @@ void shoot(dynent *d, vec &targ) dynent *o = players[i]; if(!o) continue; raydamage(o, from, to, d, i); - }; - - // Added by Rick: raydamage on bots too - loopv(bots) - { - dynent *o = bots[i]; - if(!o || (o == d)) continue; - raydamage(o, from, to, d, i); - }; + }; + + // Added by Rick: raydamage on bots too + loopv(bots) + { + dynent *o = bots[i]; + if(!o || (o == d)) continue; + raydamage(o, from, to, d, i); + }; // End add by Rick - + if(d->bIsBot) raydamage(player1, from, to, d, -1); };