diff --git a/source/src/Makefile b/source/src/Makefile index d9403503..fcc8bb07 100644 --- a/source/src/Makefile +++ b/source/src/Makefile @@ -19,7 +19,6 @@ CLIENT_OBJS= \ renderextras.o \ rendergl.o \ rendermd2.o \ - rendermd3_2.o \ renderparticles.o \ rendertext.o \ rndmap.o \ diff --git a/source/src/bot/ac_bot.cpp b/source/src/bot/ac_bot.cpp new file mode 100644 index 00000000..9831cdb1 --- /dev/null +++ b/source/src/bot/ac_bot.cpp @@ -0,0 +1,318 @@ +// +// 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; + + 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 = 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] = 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_SEMIPISTOL] = 5; + m_pMyEnt->mag[GUN_SEMIPISTOL] = 5; + SelectGun(GUN_SEMIPISTOL); + + 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; + 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_SEMIPISTOL: AddItem(n, m_pMyEnt->ammo[1], ammo); break; + case I_AUTOPISTOL: AddItem(n, m_pMyEnt->ammo[2], ammo); break; + case I_SHOTGUN: AddItem(n, m_pMyEnt->ammo[3], ammo); break; + case I_SNIPER: AddItem(n, m_pMyEnt->ammo[4], ammo); break; + case I_SUBGUN: AddItem(n, m_pMyEnt->ammo[5], ammo); break; + case I_CARBINE: AddItem(n, m_pMyEnt->ammo[6], ammo); break; + case I_SEMIRIFLE: AddItem(n, m_pMyEnt->ammo[7], ammo); break; + case I_AUTORIFLE: AddItem(n, m_pMyEnt->ammo[8], ammo); break; + case I_GRENADE: AddItem(n, m_pMyEnt->ammo[9], 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; + + if(v>=itemstats[ents[i].type-I_SEMIPISTOL].max) // don't pick up if not needed + return; + + itemstat &is = itemstats[ents[i].type-I_SEMIPISTOL]; + 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, 4, SV_ITEMPICKUP, i, m_classicsp ? 100000 : spawnsec, false); + + // 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 new file mode 100644 index 00000000..e81e3896 --- /dev/null +++ b/source/src/bot/ac_bot.h @@ -0,0 +1,46 @@ +// +// 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); + 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 new file mode 100644 index 00000000..6fe3f244 --- /dev/null +++ b/source/src/bot/ac_bot_ai.cpp @@ -0,0 +1,298 @@ +// +// 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 }, + // SEMI PISTOL + { TYPE_NORMAL, 5.0f, 12.0f, 0.0f, 50.0f, 4 }, + // AUTO PISTOL + { TYPE_AUTO, 10.0f, 20.0f, 0.0f, 100.0f, 10 }, + // SHOTGUN + { TYPE_SHOTGUN, 15.0f, 25.0f, 10.0f, 70.0f, 3 }, + // SNIPER + { TYPE_SNIPER, 12.0f, 25.0f, 0.0f, 100.0f, 4 }, + // SUBGUN + { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 4 }, + + /* EDIT: Adrian Henke - those are not used yet + // CARBINE + { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 4 }, + // SEMI RIFLE + { TYPE_SNIPER, 12.0f, 25.0f, 0.0f, 100.0f, 4 }, + // AUTO RIFLE + { TYPE_AUTO, 12.0f, 25.0f, 0.0f, 100.0f, 4 }, + */ + + // GRENADE + { TYPE_GRENADE, 12.0f, 25.0f, 0.0f, 100.0f, 4 } +}; + +// Code of CACBot - Start + +bool CACBot::ChoosePreferredWeapon() +{ + if (CBot::ChoosePreferredWeapon()) + return true; + else + return SelectGun(GUN_KNIFE); +} + +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 = itemstats[e.type-I_SEMIPISTOL].max; + + switch(e.type) + { + case I_SEMIPISTOL: + bInteresting = (m_pMyEnt->ammo[1]ammo[1]; + break; + case I_AUTOPISTOL: + bInteresting = (m_pMyEnt->ammo[2]ammo[2]; + break; + case I_SHOTGUN: + bInteresting = (m_pMyEnt->ammo[3]ammo[3]; + break; + case I_SNIPER: + bInteresting = (m_pMyEnt->ammo[4]ammo[4]; + break; + case I_SUBGUN: + bInteresting = (m_pMyEnt->ammo[5]ammo[4]; + break; + case I_CARBINE: + bInteresting = (m_pMyEnt->ammo[6]ammo[4]; + break; + case I_SEMIRIFLE: + bInteresting = (m_pMyEnt->ammo[7]ammo[4]; + break; + case I_AUTORIFLE: + bInteresting = (m_pMyEnt->ammo[8]ammo[4]; + break; + case I_GRENADE: + bInteresting = (m_pMyEnt->ammo[9]ammo[4]; + break; + case I_HELMET: + bInteresting = ((m_pMyEnt->armourarmourtype!=A_YELLOW || m_pMyEnt->armour<=66)); + sAmmo = m_pMyEnt->armour; + break; + case I_ARMOUR: + bInteresting = (m_pMyEnt->armourarmour; + 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 new file mode 100644 index 00000000..59f72a6b --- /dev/null +++ b/source/src/bot/bot.cpp @@ -0,0 +1,497 @@ +// +// 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 new file mode 100644 index 00000000..522ebf1c --- /dev/null +++ b/source/src/bot/bot.h @@ -0,0 +1,426 @@ +// +// 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 + +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 navaigation +}; + +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) = NULL; + virtual void PickUp(int n) = NULL; + virtual void AddItem(int i, int &v, int spawnsec) = NULL; + virtual void BotPain(int damage, dynent *d) = NULL; + 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) = NULL; + bool CheckItems(void); + bool InUnreachableList(entity *e); + virtual bool DoSPStuff(void) = NULL; + 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 new file mode 100644 index 00000000..e75b7016 --- /dev/null +++ b/source/src/bot/bot_ai.cpp @@ -0,0 +1,2294 @@ +// +// 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? + 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 new file mode 100644 index 00000000..3b6058f4 --- /dev/null +++ b/source/src/bot/bot_util.cpp @@ -0,0 +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; +} diff --git a/source/src/bot/bot_util.h b/source/src/bot/bot_util.h new file mode 100644 index 00000000..f3327a02 --- /dev/null +++ b/source/src/bot/bot_util.h @@ -0,0 +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 TMutiChoice - 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 new file mode 100644 index 00000000..ff16e696 --- /dev/null +++ b/source/src/bot/bot_waypoint.cpp @@ -0,0 +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 diff --git a/source/src/bot/bot_waypoint.h b/source/src/bot/bot_waypoint.h new file mode 100644 index 00000000..934e9d55 --- /dev/null +++ b/source/src/bot/bot_waypoint.h @@ -0,0 +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 diff --git a/source/src/bot/botmanager.cpp b/source/src/bot/botmanager.cpp new file mode 100644 index 00000000..0e4b0789 --- /dev/null +++ b/source/src/bot/botmanager.cpp @@ -0,0 +1,1444 @@ +// +// 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(); + + // edit: driAn + loopi(3) + { + m->mdl[i] = i; + m->animstate[i].frm = 0; + m->animstate[i].anim = 0; + m->animstate[i].lastTime = 0; + }; + + 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