bot: add basic support to play as zombie bot

aim: check that we're actually hit the target in body checks
This commit is contained in:
jeefo 2023-05-08 00:44:14 +03:00
commit 3009b4f50a
No known key found for this signature in database
GPG key ID: 927BCA0779BEA8ED
8 changed files with 101 additions and 26 deletions

View file

@ -307,6 +307,11 @@ public:
return m_paths.length <int32_t> ();
}
// get the random node on map
int32_t random () const {
return rg.get (0, length () - 1);
}
// check if has editor
bool hasEditor () const {
return !!m_editor;

View file

@ -151,6 +151,7 @@ public:
void erase (Bot *bot);
void handleDeath (edict_t *killer, edict_t *victim);
void setLastWinner (int winner);
void checkBotModel (edict_t *ent, char *infobuffer);
bool isTeamStacked (int team);
bool kickRandom (bool decQuota = true, Team fromTeam = Team::Unassigned);

View file

@ -771,6 +771,7 @@ private:
bool m_grenadeRequested {}; // bot requested change to grenade
bool m_needToSendWelcomeChat {}; // bot needs to greet people on server?
bool m_switchedToKnifeDuringJump {}; // bot needs to revert weapon after jump?
bool m_isCreature {}; // bot is not a player, but something else ? zombie ?
Pickup m_pickupType {}; // type of entity which needs to be used/picked up
PathWalk m_pathWalk {}; // pointer to current node from path
@ -779,6 +780,7 @@ private:
CollisionState m_collisionState {}; // collision State
FindPath m_pathType {}; // which pathfinder to use
uint8_t m_enemyParts {}; // visibility flags
uint16_t m_modelMask {}; // model mask bits
TraceResult m_lastTrace[TraceChannel::Num] {}; // last trace result
UniquePtr <class AStarAlgo> m_planner;
@ -894,6 +896,7 @@ private:
bool canRunHeavyWeight ();
bool isEnemyInSight (Vector &endPos);
bool isEnemyNoticeable (float range);
bool isCreature ();
void doPlayerAvoidance (const Vector &normal);
void selectCampButtons (int index);
@ -953,6 +956,7 @@ private:
void refreshEnemyPredict ();
void syncUpdatePredictedIndex ();
void updatePredictedIndex ();
void refreshModelName (char *infobuffer);
void completeTask ();
void executeTasks ();

View file

@ -325,7 +325,7 @@ void Bot::updatePickups () {
// this function finds Items to collect or use in the near of a bot
// don't try to pickup anything while on ladder or trying to escape from bomb...
if ((m_states & Sense::SeeingEnemy) || isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || !bots.hasInterestingEntities ()) {
if (m_isCreature || (m_states & Sense::SeeingEnemy) || isOnLadder () || getCurrentTaskId () == Task::EscapeFromBomb || !cv_pickup_best.bool_ () || cv_jasonmode.bool_ () || !bots.hasInterestingEntities ()) {
m_pickupItem = nullptr;
m_pickupType = Pickup::None;
@ -903,7 +903,7 @@ void Bot::checkMsgQueue () {
return;
}
if (!m_inBuyZone || game.is (GameFlags::CSDM)) {
if (!m_inBuyZone || game.is (GameFlags::CSDM) || m_isCreature) {
m_buyPending = true;
m_buyingFinished = true;
@ -1767,7 +1767,7 @@ void Bot::setConditions () {
refreshEnemyPredict ();
// check for grenades depending on difficulty
if (rg.chance (cr::max (25, m_difficulty * 25))) {
if (rg.chance (cr::max (25, m_difficulty * 25)) && !m_isCreature) {
checkGrenadesThrow ();
}
@ -1841,7 +1841,6 @@ void Bot::filterTasks () {
float retreatLevel = (100.0f - (m_healthValue > 70.0f ? 100.0f : m_healthValue)) * tempFear; // retreat level depends on bot health
if (m_numEnemiesLeft > m_numFriendsLeft / 2 && m_retreatTime < game.time () && m_seeEnemyTime - rg.get (2.0f, 4.0f) < game.time ()) {
float timeSeen = m_seeEnemyTime - game.time ();
float timeHeard = m_heardSoundTime - game.time ();
float ratio = 0.0f;
@ -1865,6 +1864,9 @@ void Bot::filterTasks () {
else if (m_isVIP || m_isReloading || (sniping && usesSniper ())) {
ratio *= 3.0f; // triple the seek cover desire if bot is VIP or reloading
}
else if (m_isCreature) {
ratio = 0.0f;
}
else {
ratio /= 2.0f; // reduce seek cover otherwise
}
@ -2553,7 +2555,7 @@ void Bot::checkRadioQueue () {
break;
case Radio::GetInPositionAndWaitForGo:
if ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f) {
if (!m_isCreature && ((game.isNullEntity (m_enemy) && seesEntity (m_radioEntity->v.origin)) || distance < 1024.0f)) {
pushRadioMessage (Radio::RogerThat);
if (getCurrentTaskId () == Task::Camp) {
@ -2927,6 +2929,7 @@ void Bot::update () {
else if (m_team == Team::CT && game.mapIs (MapFlags::HostageRescue)) {
m_hasHostage = hasHostage ();
}
m_isCreature = isCreature ();
// is bot movement enabled
bool botMovement = false;
@ -3537,7 +3540,7 @@ void Bot::blind_ () {
}
void Bot::camp_ () {
if (!cv_camping_allowed.bool_ ()) {
if (!cv_camping_allowed.bool_ () || m_isCreature) {
completeTask ();
return;
}
@ -3627,6 +3630,11 @@ void Bot::camp_ () {
}
void Bot::hide_ () {
if (m_isCreature) {
completeTask ();
return;
};
m_aimFlags |= AimFlags::Camp;
m_checkTerrain = false;
m_moveToGoal = false;
@ -3747,7 +3755,7 @@ void Bot::plantBomb_ () {
m_aimFlags |= AimFlags::Camp;
// we're still got the C4?
if (m_hasC4) {
if (m_hasC4 && !isKnifeMode ()) {
if (m_currentWeapon != Weapon::C4) {
selectWeaponById (Weapon::C4);
}
@ -4821,7 +4829,7 @@ void Bot::logic () {
m_wantsToFire = false;
// avoid flyings grenades, if needed
if (cv_avoid_grenades.bool_ ()) {
if (cv_avoid_grenades.bool_ () && !m_isCreature) {
avoidGrenades ();
}
m_isUsingGrenade = false;
@ -5660,6 +5668,9 @@ void Bot::updateHearing () {
void Bot::enteredBuyZone (int buyState) {
// this function is gets called when bot enters a buyzone, to allow bot to buy some stuff
if (m_isCreature) {
return; // creatures can't buy anything
}
const int *econLimit = conf.getEconLimit ();
// if bot is in buy zone, try to buy ammo for this weapon...
@ -5795,3 +5806,32 @@ bool Bot::isEnemyInFrustum (edict_t *enemy) {
}
return true;
}
void Bot::refreshModelName (char *infobuffer) {
if (infobuffer == nullptr) {
infobuffer = engfuncs.pfnGetInfoKeyBuffer (ent ());
}
String modelName = engfuncs.pfnInfoKeyValue (infobuffer, "model");
// need at least two characters to test model mask
if (modelName.length () < 2) {
m_modelMask = 0;
return;
}
union ModelTest {
char model[2];
uint16_t mask;
ModelTest (StringRef m) : model { m[0], m[1] } {};
} modelTest { modelName };
// assign our model mask (tests against model done every bot update)
m_modelMask = modelTest.mask;
}
bool Bot::isCreature () {
// current creature models are: zombie, chicken
constexpr auto modelMaskZombie = (('o' << 8) + 'z');
constexpr auto modelMaskChicken = (('h' << 8) + 'c');
return m_modelMask == modelMaskZombie || m_modelMask == modelMaskChicken;
}

View file

@ -132,19 +132,22 @@ bool Bot::checkBodyParts (edict_t *target) {
auto spot = target->v.origin;
auto self = pev->pContainingEntity;
m_enemyParts = Visibility::None;
game.testLine (eyes, spot, TraceIgnore::Everything, self, &result);
// creatures can't hurt behind anything
auto ignoreFlags = m_isCreature ? TraceIgnore::None : TraceIgnore::Everything;
if (result.flFraction >= 1.0f) {
m_enemyParts = Visibility::None;
game.testLine (eyes, spot, ignoreFlags, self, &result);
if (result.flFraction >= 1.0f && result.pHit == target) {
m_enemyParts |= Visibility::Body;
m_enemyOrigin = result.vecEndPos;
}
// check top of head
spot.z += 25.0f;
game.testLine (eyes, spot, TraceIgnore::Everything, self, &result);
game.testLine (eyes, spot, ignoreFlags, self, &result);
if (result.flFraction >= 1.0f) {
if (result.flFraction >= 1.0f && result.pHit == target) {
m_enemyParts |= Visibility::Head;
m_enemyOrigin = result.vecEndPos;
}
@ -162,9 +165,9 @@ bool Bot::checkBodyParts (edict_t *target) {
else {
spot.z = target->v.origin.z - standFeet;
}
game.testLineChannel (TraceChannel::Enemy, eyes, spot, TraceIgnore::Everything, self, result);
game.testLineChannel (TraceChannel::Enemy, eyes, spot, ignoreFlags, self, result);
if (result.flFraction >= 1.0f) {
if (result.flFraction >= 1.0f && result.pHit == target) {
m_enemyParts |= Visibility::Other;
m_enemyOrigin = result.vecEndPos;
@ -177,9 +180,9 @@ bool Bot::checkBodyParts (edict_t *target) {
Vector perp (-dir.y, dir.x, 0.0f);
spot = target->v.origin + Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0);
game.testLineChannel (TraceChannel::Enemy, eyes, spot, TraceIgnore::Everything, self, result);
game.testLineChannel (TraceChannel::Enemy, eyes, spot, ignoreFlags, self, result);
if (result.flFraction >= 1.0f) {
if (result.flFraction >= 1.0f && result.pHit == target) {
m_enemyParts |= Visibility::Other;
m_enemyOrigin = result.vecEndPos;
@ -187,9 +190,9 @@ bool Bot::checkBodyParts (edict_t *target) {
}
spot = target->v.origin - Vector (perp.x * edgeOffset, perp.y * edgeOffset, 0);
game.testLineChannel (TraceChannel::Enemy, eyes, spot, TraceIgnore::Everything, self, result);
game.testLineChannel (TraceChannel::Enemy, eyes, spot, ignoreFlags, self, result);
if (result.flFraction >= 1.0f) {
if (result.flFraction >= 1.0f && result.pHit == target) {
m_enemyParts |= Visibility::Other;
m_enemyOrigin = result.vecEndPos;
@ -1494,7 +1497,7 @@ bool Bot::hasAnyWeapons () {
}
bool Bot::isKnifeMode () {
return cv_jasonmode.bool_ () || (usesKnife () && !hasAnyWeapons ());
return cv_jasonmode.bool_ () || (usesKnife () && !hasAnyWeapons ()) || m_isCreature;
}
void Bot::selectBestWeapon () {

View file

@ -260,6 +260,7 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) {
// team changes, recounting the teams population, etc...
ctrl.assignAdminRights (ent, infobuffer);
bots.checkBotModel (ent, infobuffer);
if (game.is (GameFlags::Metamod)) {
RETURN_META (MRES_IGNORED);

View file

@ -740,6 +740,15 @@ void BotManager::setLastWinner (int winner) {
}
}
void BotManager::checkBotModel (edict_t *ent, char *infobuffer) {
for (const auto &bot : bots) {
if (bot->ent () == ent) {
bot->refreshModelName (infobuffer);
break;
}
}
}
void BotManager::setWeaponMode (int selection) {
// this function sets bots weapon mode
@ -1367,6 +1376,7 @@ void Bot::newRound () {
for (auto &timer : m_chatterTimes) {
timer = kMaxChatterRepeatInterval;
}
refreshModelName (nullptr);
m_isReloading = false;
m_reloadState = Reload::None;

View file

@ -13,6 +13,17 @@ int Bot::findBestGoal () {
return goal;
};
if (m_isCreature) {
if (!graph.m_terrorPoints.empty ()) {
return graph.m_terrorPoints.random ();
}
if (!graph.m_goalPoints.empty ()) {
return graph.m_goalPoints.random ();
}
return graph.random ();
}
// chooses a destination (goal) node for a bot
if (m_team == Team::Terrorist && game.mapIs (MapFlags::Demolition)) {
auto result = findBestGoalWhenBombAction ();
@ -282,7 +293,7 @@ int Bot::findGoalPost (int tactic, IntArray *defensive, IntArray *offsensive) {
}
if (goalChoices[0] == kInvalidNodeIndex) {
return m_chosenGoalIndex = rg.get (0, graph.length () - 1);
return m_chosenGoalIndex = graph.random ();
}
bool sorting = false;
@ -1785,7 +1796,7 @@ int Bot::findDefendNode (const Vector &origin) {
// some of points not found, return random one
if (srcIndex == kInvalidNodeIndex || posIndex == kInvalidNodeIndex) {
return rg.get (0, graph.length () - 1);
return graph.random ();
}
// find the best node now
@ -1885,7 +1896,7 @@ int Bot::findDefendNode (const Vector &origin) {
}
if (found.empty ()) {
return rg.get (0, graph.length () - 1); // most worst case, since there a evil error in nodes
return graph.random (); // most worst case, since there a evil error in nodes
}
return found.random ();
}
@ -2129,7 +2140,7 @@ bool Bot::advanceMovement () {
Task taskID = getCurrentTaskId ();
// only if we in normal task and bomb is not planted
if (taskID == Task::Normal && bots.getRoundMidTime () + 5.0f < game.time () && m_timeCamping + 5.0f < game.time () && !bots.isBombPlanted () && m_personality != Personality::Rusher && !m_hasC4 && !m_isVIP && m_loosedBombNodeIndex == kInvalidNodeIndex && !m_hasHostage) {
if (taskID == Task::Normal && bots.getRoundMidTime () + 5.0f < game.time () && m_timeCamping + 5.0f < game.time () && !bots.isBombPlanted () && m_personality != Personality::Rusher && !m_hasC4 && !m_isVIP && m_loosedBombNodeIndex == kInvalidNodeIndex && !m_hasHostage && !m_isCreature) {
m_campButtons = 0;
const int nextIndex = m_pathWalk.next ();
@ -2861,7 +2872,7 @@ int Bot::getRandomCampDir () {
if (count >= 0) {
return indices[rg.get (0, count)];
}
return rg.get (0, graph.length () - 1);
return graph.random ();
}
void Bot::updateBodyAngles () {
@ -3215,7 +3226,7 @@ void Bot::syncFindPath (int srcIndex, int destIndex, FindPath pathType) {
destIndex = graph.getNearestNoBuckets (pev->origin, kInfiniteDistance, NodeFlag::Goal);
if (!graph.exists (destIndex) || srcIndex == destIndex) {
destIndex = rg.get (0, graph.length () - 1);
destIndex = graph.random ();
if (!graph.exists (destIndex)) {
printf ("%s dest path index not valid (%d).", __func__, destIndex);