fix: breakable headache (ref #660)
fix: bots firing rates on short distances (ref #658)
This commit is contained in:
parent
e717710bd1
commit
c07d02c14e
9 changed files with 157 additions and 53 deletions
10
inc/engine.h
10
inc/engine.h
|
|
@ -164,6 +164,8 @@ private:
|
|||
edict_t *m_localEntity {};
|
||||
|
||||
Array <edict_t *> m_breakables {};
|
||||
HashMap <int32_t, bool> m_checkedBreakables {};
|
||||
|
||||
SmallArray <ConVarReg> m_cvars {};
|
||||
SharedLibrary m_gameLib {};
|
||||
SharedLibrary m_engineLib {};
|
||||
|
|
@ -269,6 +271,9 @@ public:
|
|||
// creates a fake client's a nd resets all the entvars
|
||||
edict_t *createFakeClient (StringRef name);
|
||||
|
||||
// mark breakable entity as invalid
|
||||
void markBreakableAsInvalid (edict_t *ent);
|
||||
|
||||
// public inlines
|
||||
public:
|
||||
// get the current time on server
|
||||
|
|
@ -424,6 +429,11 @@ public:
|
|||
return !m_breakables.empty ();
|
||||
}
|
||||
|
||||
// is breakable entity is valid ?
|
||||
bool isBreakableValid (edict_t *ent) {
|
||||
return m_checkedBreakables[indexOfEntity (ent)];
|
||||
}
|
||||
|
||||
// find variable value by variable name
|
||||
StringRef findCvar (StringRef name) {
|
||||
return engfuncs.pfnCVarGetString (name.chars ());
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ public:
|
|||
bool isDoorEntity (edict_t *ent);
|
||||
|
||||
// this function is checking that pointed by ent pointer obstacle, can be destroyed
|
||||
bool isShootableBreakable (edict_t *ent);
|
||||
bool isBreakableEntity (edict_t *ent, bool initialSeed = false);
|
||||
|
||||
// nearest player search helper
|
||||
bool findNearestPlayer (void **holder, edict_t *to, float searchDistance = 4096.0, bool sameTeam = false, bool needBot = false, bool needAlive = false, bool needDrawn = false, bool needBotWithC4 = false);
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ void GraphAnalyze::flood (const Vector &pos, const Vector &next, float range) {
|
|||
game.testHull (pos, { next.x, next.y, next.z + 19.0f }, TraceIgnore::Monsters, head_hull, nullptr, &tr);
|
||||
|
||||
// we're can't reach next point
|
||||
if (!cr::fequal (tr.flFraction, 1.0f) && !util.isShootableBreakable (tr.pHit)) {
|
||||
if (!cr::fequal (tr.flFraction, 1.0f) && !util.isBreakableEntity (tr.pHit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -153,6 +153,10 @@ void Bot::avoidGrenades () {
|
|||
}
|
||||
|
||||
void Bot::checkBreakable (edict_t *touch) {
|
||||
if (!game.hasBreakables ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.isNullEntity (touch)) {
|
||||
m_breakableEntity = lookupBreakable ();
|
||||
}
|
||||
|
|
@ -199,7 +203,7 @@ void Bot::checkBreakablesAround () {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!util.isShootableBreakable (breakable)) {
|
||||
if (!util.isBreakableEntity (breakable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -246,26 +250,69 @@ void Bot::checkBreakablesAround () {
|
|||
edict_t *Bot::lookupBreakable () {
|
||||
// this function checks if bot is blocked by a shoot able breakable in his moving direction
|
||||
|
||||
TraceResult tr {};
|
||||
game.testLine (pev->origin, pev->origin + (m_destOrigin - pev->origin).normalize_apx () * 72.0f, TraceIgnore::None, ent (), &tr);
|
||||
auto doLookup = [&] (const Vector &start, const Vector &end, const float dist) -> edict_t * {
|
||||
TraceResult tr {};
|
||||
game.testLine (start, start + (end - start).normalize_apx () * dist, TraceIgnore::None, ent (), &tr);
|
||||
|
||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||
auto ent = tr.pHit;
|
||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||
auto hit = tr.pHit;
|
||||
|
||||
// check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap!
|
||||
if (util.isShootableBreakable (ent)) {
|
||||
m_breakableOrigin = game.getEntityOrigin (ent);
|
||||
return ent;
|
||||
// check if this isn't a triggered (bomb) breakable and if it takes damage. if true, shoot the crap!
|
||||
if (util.isBreakableEntity (hit)) {
|
||||
m_breakableOrigin = tr.vecEndPos;
|
||||
m_breakableEntity = hit;
|
||||
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
}
|
||||
game.testLine (getEyesPos (), getEyesPos () + (m_destOrigin - getEyesPos ()).normalize_apx () * 72.0f, TraceIgnore::None, ent (), &tr);
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
if (!cr::fequal (tr.flFraction, 1.0f)) {
|
||||
auto ent = tr.pHit;
|
||||
// got recipe from KWo
|
||||
for (auto i = 0; i < 5; ++i) {
|
||||
if (i == 1 && game.isNullEntity (m_breakableEntity)) {
|
||||
continue;
|
||||
}
|
||||
Vector end = m_pathOrigin;
|
||||
Vector start = getEyesPos ();
|
||||
|
||||
if (util.isShootableBreakable (ent)) {
|
||||
m_breakableOrigin = game.getEntityOrigin (ent);
|
||||
return ent;
|
||||
if (graph.exists (m_currentNodeIndex)) {
|
||||
end = graph[m_currentNodeIndex].origin;
|
||||
}
|
||||
|
||||
switch (i) {
|
||||
case 0:
|
||||
if (graph.exists (m_previousNodes[0])) {
|
||||
start = graph[m_previousNodes[0]].origin;
|
||||
}
|
||||
else {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
start = getEyesPos ();
|
||||
end = game.getEntityOrigin (m_breakableEntity);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
start = pev->origin;
|
||||
end = m_destOrigin;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
start = getEyesPos ();
|
||||
end = m_destOrigin;
|
||||
break;
|
||||
|
||||
case 4:
|
||||
start = getEyesPos ();
|
||||
break;
|
||||
}
|
||||
auto hit = doLookup (start, end, usesKnife () ? 32.0f : rg (72.0f, 256.0f));
|
||||
|
||||
if (!game.isNullEntity (hit)) {
|
||||
return hit;
|
||||
}
|
||||
}
|
||||
m_breakableEntity = nullptr;
|
||||
|
|
|
|||
|
|
@ -1147,6 +1147,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
}
|
||||
}
|
||||
}
|
||||
const float timeDelta = game.time () - m_frameInterval;
|
||||
|
||||
// need to care for burst fire?
|
||||
if (distance < kSprayDistance || m_blindTime > game.time () || usesKnife ()) {
|
||||
|
|
@ -1164,7 +1165,7 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
}
|
||||
else {
|
||||
// if automatic weapon press attack
|
||||
if (tab[choosen].primaryFireHold && getAmmo (tab[index].id) > tab[index].minPrimaryAmmo) {
|
||||
if (tab[choosen].primaryFireHold && getAmmoInClip () > tab[index].minPrimaryAmmo) {
|
||||
pev->button |= IN_ATTACK;
|
||||
}
|
||||
|
||||
|
|
@ -1177,13 +1178,13 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
}
|
||||
|
||||
if (pev->button & IN_ATTACK) {
|
||||
m_shootTime = game.time ();
|
||||
m_shootTime = timeDelta;
|
||||
}
|
||||
}
|
||||
else {
|
||||
// don't attack with knife over long distance
|
||||
if (id == Weapon::Knife) {
|
||||
m_shootTime = game.time ();
|
||||
m_shootTime = timeDelta;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1192,8 +1193,8 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
}
|
||||
|
||||
if (tab[choosen].primaryFireHold) {
|
||||
m_shootTime = game.time ();
|
||||
m_zoomCheckTime = game.time ();
|
||||
m_shootTime = timeDelta;
|
||||
m_zoomCheckTime = timeDelta;
|
||||
|
||||
pev->button |= IN_ATTACK; // use primary attack
|
||||
}
|
||||
|
|
@ -1207,8 +1208,8 @@ void Bot::selectWeapons (float distance, int index, int id, int choosen) {
|
|||
|
||||
const int offset = cr::abs <int> (m_difficulty * 25 / 20 - 5);
|
||||
|
||||
m_shootTime = game.time () + 0.1f + rg (kMinFireDelay[offset], kMaxFireDelay[offset]);
|
||||
m_zoomCheckTime = game.time ();
|
||||
m_shootTime = timeDelta + 0.1f + rg (kMinFireDelay[offset], kMaxFireDelay[offset]);
|
||||
m_zoomCheckTime = timeDelta;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
|||
|
||||
// clear all breakables before initialization
|
||||
m_breakables.clear ();
|
||||
m_checkedBreakables.clear ();
|
||||
|
||||
// initialize all config files
|
||||
conf.loadConfigs ();
|
||||
|
|
@ -154,7 +155,10 @@ void Game::levelInitialize (edict_t *entities, int max) {
|
|||
else if (classname.startsWith ("func_button")) {
|
||||
m_mapFlags |= MapFlags::HasButtons;
|
||||
}
|
||||
else if (util.isShootableBreakable (ent)) {
|
||||
else if (util.isBreakableEntity (ent, true)) {
|
||||
|
||||
// add breakable for material check
|
||||
m_checkedBreakables[indexOfEntity (ent)] = ent->v.impulse <= 0;
|
||||
m_breakables.push (ent);
|
||||
}
|
||||
}
|
||||
|
|
@ -1229,6 +1233,10 @@ edict_t *Game::createFakeClient (StringRef name) {
|
|||
return ent;
|
||||
}
|
||||
|
||||
void Game::markBreakableAsInvalid (edict_t *ent) {
|
||||
m_checkedBreakables[indexOfEntity (ent)] = false;
|
||||
}
|
||||
|
||||
void LightMeasure::initializeLightstyles () {
|
||||
// this function initializes lighting information...
|
||||
|
||||
|
|
|
|||
|
|
@ -153,10 +153,13 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
|
|||
// the two entities both have velocities, for example two players colliding, this function
|
||||
// is called twice, once for each entity moving.
|
||||
|
||||
if (!game.isNullEntity (pentTouched) && pentOther != game.getStartEntity ()) {
|
||||
if (game.hasBreakables ()
|
||||
&& !game.isNullEntity (pentTouched)
|
||||
&& pentOther != game.getStartEntity ()) {
|
||||
|
||||
auto bot = bots[pentTouched];
|
||||
|
||||
if (bot && util.isShootableBreakable (pentOther)) {
|
||||
if (bot && util.isBreakableEntity (pentOther)) {
|
||||
bot->checkBreakable (pentOther);
|
||||
}
|
||||
}
|
||||
|
|
@ -413,6 +416,14 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
|
|||
|
||||
if (game.is (GameFlags::HasFakePings) && !game.is (GameFlags::Metamod)) {
|
||||
table->pfnUpdateClientData = [] (const struct edict_s *player, int sendweapons, struct clientdata_s *cd) {
|
||||
// this function is a synchronization tool that is used periodically by the engine to tell
|
||||
// the game DLL to send player info over the network to one of its clients when it suspects
|
||||
// that this client is desynchronizing. Early bots were using it to ask the game DLL for the
|
||||
// weapon list of players (by setting sendweapons to TRUE), but most of the time having a
|
||||
// look around the ent->v.weapons bitmask is enough, since that's the place commonly used for
|
||||
// MODs to store weapon information. If it can't be read from there, catching a few network
|
||||
// messages (like in DMC) do the job better than this function anyway.
|
||||
|
||||
dllapi.pfnUpdateClientData (player, sendweapons, cd);
|
||||
|
||||
// do a post-processing with non-metamod
|
||||
|
|
@ -439,6 +450,28 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int interfaceVersion) {
|
|||
}
|
||||
dllapi.pfnPM_Move (pm, server);
|
||||
};
|
||||
|
||||
table->pfnKeyValue = [] (edict_t *ent, KeyValueData *kvd) {
|
||||
// this function is called when the game requests a pointer to some entity's keyvalue data.
|
||||
// The keyvalue data is held in each entity's infobuffer (basically a char buffer where each
|
||||
// game DLL can put the stuff it wants) under - as it says - the form of a key/value pair. A
|
||||
// common example of key/value pair is the "model", "(name of player model here)" one which
|
||||
// is often used for client DLLs to display player characters with the right model (else they
|
||||
// would all have the dull "models/player.mdl" one). The entity for which the keyvalue data
|
||||
// pointer is requested is pentKeyvalue, the pointer to the keyvalue data structure pkvd.
|
||||
|
||||
if (game.isNullEntity (ent) && strcmp (ent->v.classname.chars (), "func_breakable") == 0) {
|
||||
if (kvd && kvd->szKeyName && strcmp (kvd->szKeyName, "material") == 0) {
|
||||
if (atoi (kvd->szValue) == 7) {
|
||||
game.markBreakableAsInvalid (ent);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (game.is (GameFlags::Metamod)) {
|
||||
RETURN_META (MRES_IGNORED);
|
||||
}
|
||||
dllapi.pfnKeyValue (ent, kvd);
|
||||
};
|
||||
return HLTrue;
|
||||
}
|
||||
|
||||
|
|
@ -499,6 +532,14 @@ CR_LINKAGE_C int GetEntityAPI_Post (gamefuncs_t *table, int) {
|
|||
|
||||
if (game.is (GameFlags::HasFakePings)) {
|
||||
table->pfnUpdateClientData = [] (const struct edict_s *player, int, struct clientdata_s *) {
|
||||
// this function is a synchronization tool that is used periodically by the engine to tell
|
||||
// the game DLL to send player info over the network to one of its clients when it suspects
|
||||
// that this client is desynchronizing. Early bots were using it to ask the game DLL for the
|
||||
// weapon list of players (by setting sendweapons to TRUE), but most of the time having a
|
||||
// look around the ent->v.weapons bitmask is enough, since that's the place commonly used for
|
||||
// MODs to store weapon information. If it can't be read from there, catching a few network
|
||||
// messages (like in DMC) do the job better than this function anyway.
|
||||
//
|
||||
// do a post-processing with non-metamod
|
||||
auto ent = const_cast <edict_t *> (reinterpret_cast <const edict_t *> (player));
|
||||
|
||||
|
|
|
|||
|
|
@ -242,21 +242,19 @@ bool BotSupport::isHostageEntity (edict_t *ent) {
|
|||
return classHash == kHostageEntity || classHash == kMonsterScientist;
|
||||
}
|
||||
|
||||
bool BotSupport::isShootableBreakable (edict_t *ent) {
|
||||
if (game.isNullEntity (ent) || ent == game.getStartEntity ()) {
|
||||
return false;
|
||||
bool BotSupport::isBreakableEntity (edict_t *ent, bool initialSeed) {
|
||||
if (!initialSeed) {
|
||||
if (!game.hasBreakables ()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// todo: move the breakables list into own array, and refresh them every round, since next thing is very expensive
|
||||
#if 0
|
||||
StringRef material = engfuncs.pfnInfoKeyValue (engfuncs.pfnGetInfoKeyBuffer (ent), "material");
|
||||
|
||||
if (material == "7") {
|
||||
if (game.isNullEntity (ent) || ent == game.getStartEntity () || (!initialSeed && !game.isBreakableValid (ent))) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
const auto limit = cv_breakable_health_limit.as <float> ();
|
||||
|
||||
// not shootable
|
||||
// not shoot-able
|
||||
if (ent->v.health >= limit) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -271,14 +269,12 @@ bool BotSupport::isShootableBreakable (edict_t *ent) {
|
|||
return ent->v.movetype == MOVETYPE_PUSH || ent->v.movetype == MOVETYPE_PUSHSTEP;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BotSupport::isFakeClient (edict_t *ent) {
|
||||
if (bots[ent] != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return bots[ent] != nullptr || (!game.isNullEntity (ent) && (ent->v.flags & FL_FAKECLIENT));
|
||||
}
|
||||
|
||||
void BotSupport::checkWelcome () {
|
||||
|
|
|
|||
|
|
@ -1460,8 +1460,9 @@ void Bot::escapeFromBomb_ () {
|
|||
}
|
||||
|
||||
void Bot::shootBreakable_ () {
|
||||
|
||||
// breakable destroyed?
|
||||
if (!util.isShootableBreakable (m_breakableEntity)) {
|
||||
if (!util.isBreakableEntity (m_breakableEntity)) {
|
||||
completeTask ();
|
||||
return;
|
||||
}
|
||||
|
|
@ -1469,30 +1470,30 @@ void Bot::shootBreakable_ () {
|
|||
|
||||
m_checkTerrain = false;
|
||||
m_moveToGoal = false;
|
||||
|
||||
m_navTimeset = game.time ();
|
||||
m_lookAtSafe = m_breakableOrigin;
|
||||
|
||||
// is bot facing the breakable?
|
||||
if (util.getConeDeviation (ent (), m_breakableOrigin) >= 0.90f) {
|
||||
if (util.getConeDeviation (ent (), m_lookAtSafe) >= 0.95f) {
|
||||
m_aimFlags |= AimFlags::Override;
|
||||
|
||||
m_moveSpeed = 0.0f;
|
||||
m_strafeSpeed = 0.0f;
|
||||
|
||||
m_aimFlags |= AimFlags::Override;
|
||||
|
||||
if (usesKnife ()) {
|
||||
selectBestWeapon ();
|
||||
}
|
||||
m_wantsToFire = true;
|
||||
m_shootTime = game.time ();
|
||||
|
||||
if (!hasAnyAmmoInClip ()
|
||||
&& usesKnife ()
|
||||
&& pev->origin.distanceSq (m_lookAtSafe) > cr::sqrf (72.0f)) {
|
||||
|
||||
completeTask ();
|
||||
}
|
||||
}
|
||||
else {
|
||||
m_checkTerrain = true;
|
||||
m_moveToGoal = true;
|
||||
|
||||
m_breakableOrigin.clear ();
|
||||
m_breakableEntity = nullptr;
|
||||
|
||||
completeTask ();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue