diff --git a/cfg/addons/yapb/conf/maps/.gitkeep b/cfg/addons/yapb/conf/maps/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/cfg/addons/yapb/conf/yapb.cfg b/cfg/addons/yapb/conf/yapb.cfg index 8801d3e..544087b 100644 --- a/cfg/addons/yapb/conf/yapb.cfg +++ b/cfg/addons/yapb/conf/yapb.cfg @@ -201,9 +201,9 @@ yb_graph_fixcamp "1" // // Specifies the URL from bots will be able to download graph in case of missing local one. // --- -// Default: "http://graph.yapb.ru/" +// Default: "yapb.ru" // -yb_graph_url "http://graph.yapb.ru/" +yb_graph_url "yapb.ru" // // Kick bots to automatically make room for human players. diff --git a/inc/config.h b/inc/config.h index cb566c2..162ace8 100644 --- a/inc/config.h +++ b/inc/config.h @@ -119,6 +119,9 @@ public: // load bots difficulty config void loadDifficultyConfig (); + // loads bots map-specific config + void loadMapSpecificConfig (); + // sets memfile to use engine functions void setupMemoryFiles (); diff --git a/inc/engine.h b/inc/engine.h index 7f7b894..eaaa8f0 100644 --- a/inc/engine.h +++ b/inc/engine.h @@ -171,7 +171,7 @@ public: bool isDedicated (); // get stripped down mod name - const char *getModName (); + const char *getRunningModName (); // get the valid mapname const char *getMapName (); diff --git a/inc/yapb.h b/inc/yapb.h index 29f16c6..ea0acda 100644 --- a/inc/yapb.h +++ b/inc/yapb.h @@ -1141,6 +1141,8 @@ extern ConVar cv_show_latency; extern ConVar cv_enable_query_hook; extern ConVar cv_whose_your_daddy; extern ConVar cv_chatter_path; +extern ConVar cv_quota; +extern ConVar cv_difficulty; // execute client command helper template void Bot::issueCommand (const char *fmt, Args &&...args) { diff --git a/src/botlib.cpp b/src/botlib.cpp index bff7e78..7803ae0 100644 --- a/src/botlib.cpp +++ b/src/botlib.cpp @@ -840,7 +840,7 @@ void Bot::updatePickups () { const float highOffset = (m_pickupType == Pickup::Hostage || m_pickupType == Pickup::PlantedC4) ? 50.0f : 20.0f; // check if item is too high to reach, check if getting the item would hurt bot - if (!game.isNullEntity (m_pickupItem) && pickupPos.z > getEyesPos ().z + highOffset || isDeadlyMove (pickupPos)) { + if (pickupPos.z > getEyesPos ().z + highOffset || isDeadlyMove (pickupPos)) { m_itemIgnore = m_pickupItem; m_pickupItem = nullptr; m_pickupType = Pickup::None; diff --git a/src/config.cpp b/src/config.cpp new file mode 100644 index 0000000..37270c7 --- /dev/null +++ b/src/config.cpp @@ -0,0 +1,766 @@ +// +// YaPB - Counter-Strike Bot based on PODBot by Markus Klinge. +// Copyright © 2004-2020 YaPB Development Team . +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// + +#include + +ConVar cv_bind_menu_key ("yb_bind_menu_key", "=", "Bind's specified key for opening bots menu.", false); +ConVar cv_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate", "Specifies comma separated list of bot cvars, that will not be overriten by config on changelevel.", false); + +BotConfig::BotConfig () { + m_chat.resize (Chat::Count); + m_chatter.resize (Chatter::Count); + + m_weaponProps.resize (kMaxWeapons); +} + +void BotConfig::loadConfigs () { + setupMemoryFiles (); + + loadNamesConfig (); + loadChatConfig (); + loadChatterConfig (); + loadWeaponsConfig (); + loadLanguageConfig (); + loadLogosConfig (); + loadAvatarsConfig (); + loadDifficultyConfig (); +} + +void BotConfig::loadMainConfig () { + if (game.is (GameFlags::Legacy) && !game.is (GameFlags::Xash3D)) { + util.setNeedForWelcome (true); + } + setupMemoryFiles (); + + static bool firstLoad = true; + + auto needsToIgnoreVar = [](StringArray &list, const char *needle) { + for (const auto &var : list) { + if (var == needle) { + return true; + } + } + return false; + }; + String line; + MemFile file; + + // this is does the same as exec of engine, but not overwriting values of cvars spcified in cv_ignore_cvars_on_changelevel + if (util.openConfig ("yapb.cfg", "YaPB main config file is not found.", &file, false)) { + while (file.getLine (line)) { + line.trim (); + + if (isCommentLine (line)) { + continue; + } + + if (firstLoad) { + game.serverCommand (line.chars ()); + continue; + } + auto keyval = line.split (" "); + + if (keyval.length () > 1) { + auto ignore = String (cv_ignore_cvars_on_changelevel.str ()).split (","); + + auto key = keyval[0].trim ().chars (); + auto cvar = engfuncs.pfnCVarGetPointer (key); + + if (cvar != nullptr) { + auto value = const_cast (keyval[1].trim ().trim ("\"").trim ().chars ()); + + if (needsToIgnoreVar (ignore, key) && !strings.matches (value, cvar->string)) { + + // preserve quota number if it's zero + if (strings.matches (cvar->name, "yb_quota") && cv_quota.int_ () <= 0) { + engfuncs.pfnCvar_DirectSet (cvar, value); + continue; + } + game.print ("Bot CVAR '%s' differs from the stored in the config (%s/%s). Ignoring.", cvar->name, cvar->string, value); + + // ensure cvar will have old value + engfuncs.pfnCvar_DirectSet (cvar, cvar->string); + } + else { + engfuncs.pfnCvar_DirectSet (cvar, value); + } + } + else { + game.serverCommand (line.chars ()); + } + } + } + file.close (); + } + firstLoad = false; + + // android is abit hard to play, lower the difficulty by default + if (plat.android && cv_difficulty.int_ () > 3) { + cv_difficulty.set (3); + } + + // bind the correct menu key for bot menu... + if (!game.isDedicated () && !strings.isEmpty (cv_bind_menu_key.str ())) { + game.serverCommand ("bind \"%s\" \"yb menu\"", cv_bind_menu_key.str ()); + } +} + +void BotConfig::loadNamesConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + + // naming initialization + if (util.openConfig ("names.cfg", "Name configuration file not found.", &file, true)) { + m_botNames.clear (); + + while (file.getLine (line)) { + line.trim (); + + if (isCommentLine (line)) { + continue; + } + // max botname is 32 characters + if (line.length () > 32) { + line[32] = kNullChar; + } + m_botNames.emplace (line, -1); + } + file.close (); + } +} + +void BotConfig::loadWeaponsConfig () { + setupMemoryFiles (); + + auto addWeaponEntries = [](SmallArray &weapons, bool as, StringRef name, const StringArray &data) { + + // we're have null terminator element in weapons array... + if (data.length () + 1 != weapons.length ()) { + logger.error ("%s entry in weapons config is not valid or malformed (%d/%d).", name, data.length (), weapons.length ()); + + return; + } + + for (size_t i = 0; i < data.length (); ++i) { + if (as) { + weapons[i].teamAS = data[i].int_ (); + } + else { + weapons[i].teamStandard = data[i].int_ (); + } + } + }; + + auto addIntEntries = [](SmallArray &to, StringRef name, const StringArray &data) { + if (data.length () != to.length ()) { + logger.error ("%s entry in weapons config is not valid or malformed (%d/%d).", name, data.length (), to.length ()); + return; + } + + for (size_t i = 0; i < to.length (); ++i) { + to[i] = data[i].int_ (); + } + }; + String line; + MemFile file; + + // weapon data initialization + if (util.openConfig ("weapon.cfg", "Weapon configuration file not found. Loading defaults", &file)) { + while (file.getLine (line)) { + line.trim (); + + if (isCommentLine (line)) { + continue; + } + auto pair = line.split ("="); + + if (pair.length () != 2) { + continue; + } + + for (auto &trim : pair) { + trim.trim (); + } + auto splitted = pair[1].split (","); + + if (pair[0].startsWith ("MapStandard")) { + addWeaponEntries (m_weapons, false, pair[0], splitted); + } + else if (pair[0].startsWith ("MapAS")) { + addWeaponEntries (m_weapons, true, pair[0], splitted); + } + + else if (pair[0].startsWith ("GrenadePercent")) { + addIntEntries (m_grenadeBuyPrecent, pair[0], splitted); + } + else if (pair[0].startsWith ("Economics")) { + addIntEntries (m_botBuyEconomyTable, pair[0], splitted); + } + else if (pair[0].startsWith ("PersonalityNormal")) { + addIntEntries (m_normalWeaponPrefs, pair[0], splitted); + } + else if (pair[0].startsWith ("PersonalityRusher")) { + addIntEntries (m_rusherWeaponPrefs, pair[0], splitted); + } + else if (pair[0].startsWith ("PersonalityCareful")) { + addIntEntries (m_carefulWeaponPrefs, pair[0], splitted); + } + } + file.close (); + } +} + +void BotConfig::loadChatterConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + + // chatter initialization + if (game.is (GameFlags::HasBotVoice) && cv_radio_mode.int_ () == 2 && util.openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &file)) { + m_chatter.clear (); + + struct EventMap { + String str; + int code; + float repeat; + } chatterEventMap[] = { + { "Radio_CoverMe", Radio::CoverMe, kMaxChatterRepeatInteval }, + { "Radio_YouTakePoint", Radio::YouTakeThePoint, kMaxChatterRepeatInteval }, + { "Radio_HoldPosition", Radio::HoldThisPosition, 10.0f }, + { "Radio_RegroupTeam", Radio::RegroupTeam, 10.0f }, + { "Radio_FollowMe", Radio::FollowMe, 15.0f }, + { "Radio_TakingFire", Radio::TakingFireNeedAssistance, 5.0f }, + { "Radio_GoGoGo", Radio::GoGoGo, kMaxChatterRepeatInteval }, + { "Radio_Fallback", Radio::TeamFallback, kMaxChatterRepeatInteval }, + { "Radio_StickTogether", Radio::StickTogetherTeam, kMaxChatterRepeatInteval }, + { "Radio_GetInPosition", Radio::GetInPositionAndWaitForGo, kMaxChatterRepeatInteval }, + { "Radio_StormTheFront", Radio::StormTheFront, kMaxChatterRepeatInteval }, + { "Radio_ReportTeam", Radio::ReportInTeam, kMaxChatterRepeatInteval }, + { "Radio_Affirmative", Radio::RogerThat, kMaxChatterRepeatInteval }, + { "Radio_EnemySpotted", Radio::EnemySpotted, 4.0f }, + { "Radio_NeedBackup", Radio::NeedBackup, 5.0f }, + { "Radio_SectorClear", Radio::SectorClear, 10.0f }, + { "Radio_InPosition", Radio::ImInPosition, 10.0f }, + { "Radio_ReportingIn", Radio::ReportingIn, 3.0f }, + { "Radio_ShesGonnaBlow", Radio::ShesGonnaBlow, kMaxChatterRepeatInteval }, + { "Radio_Negative", Radio::Negative, kMaxChatterRepeatInteval }, + { "Radio_EnemyDown", Radio::EnemyDown, 10.0f }, + { "Chatter_DiePain", Chatter::DiePain, kMaxChatterRepeatInteval }, + { "Chatter_GoingToPlantBomb", Chatter::GoingToPlantBomb, 5.0f }, + { "Chatter_GoingToGuardVIPSafety", Chatter::GoingToGuardVIPSafety, kMaxChatterRepeatInteval }, + { "Chatter_RescuingHostages", Chatter::RescuingHostages, kMaxChatterRepeatInteval }, + { "Chatter_TeamKill", Chatter::FriendlyFire, kMaxChatterRepeatInteval }, + { "Chatter_GuardingVipSafety", Chatter::GuardingVIPSafety, kMaxChatterRepeatInteval }, + { "Chatter_PlantingC4", Chatter::PlantingBomb, 10.0f }, + { "Chatter_InCombat", Chatter::InCombat, kMaxChatterRepeatInteval }, + { "Chatter_SeeksEnemy", Chatter::SeekingEnemies, kMaxChatterRepeatInteval }, + { "Chatter_Nothing", Chatter::Nothing, kMaxChatterRepeatInteval }, + { "Chatter_EnemyDown", Chatter::EnemyDown, 10.0f }, + { "Chatter_UseHostage", Chatter::UsingHostages, kMaxChatterRepeatInteval }, + { "Chatter_WonTheRound", Chatter::WonTheRound, kMaxChatterRepeatInteval }, + { "Chatter_QuicklyWonTheRound", Chatter::QuickWonRound, kMaxChatterRepeatInteval }, + { "Chatter_NoEnemiesLeft", Chatter::NoEnemiesLeft, kMaxChatterRepeatInteval }, + { "Chatter_FoundBombPlace", Chatter::FoundC4Plant, 15.0f }, + { "Chatter_WhereIsTheBomb", Chatter::WhereIsTheC4, kMaxChatterRepeatInteval }, + { "Chatter_DefendingBombSite", Chatter::DefendingBombsite, kMaxChatterRepeatInteval }, + { "Chatter_BarelyDefused", Chatter::BarelyDefused, kMaxChatterRepeatInteval }, + { "Chatter_NiceshotCommander", Chatter::NiceShotCommander, 10.0f }, + { "Chatter_ReportingIn", Chatter::ReportingIn, 10.0f }, + { "Chatter_SpotTheBomber", Chatter::SpotTheBomber, 4.3f }, + { "Chatter_VIPSpotted", Chatter::VIPSpotted, 5.3f }, + { "Chatter_FriendlyFire", Chatter::FriendlyFire, 2.1f }, + { "Chatter_GotBlinded", Chatter::Blind, 12.0f }, + { "Chatter_GuardDroppedC4", Chatter::GuardingDroppedC4, 3.0f }, + { "Chatter_DefusingC4", Chatter::DefusingBomb, 3.0f }, + { "Chatter_FoundC4", Chatter::FoundC4, 5.5f }, + { "Chatter_ScaredEmotion", Chatter::ScaredEmotion, 6.1f }, + { "Chatter_HeardEnemy", Chatter::ScaredEmotion, 12.8f }, + { "Chatter_SniperWarning", Chatter::SniperWarning, 14.3f }, + { "Chatter_SniperKilled", Chatter::SniperKilled, 12.1f }, + { "Chatter_OneEnemyLeft", Chatter::OneEnemyLeft, 12.5f }, + { "Chatter_TwoEnemiesLeft", Chatter::TwoEnemiesLeft, 12.5f }, + { "Chatter_ThreeEnemiesLeft", Chatter::ThreeEnemiesLeft, 12.5f }, + { "Chatter_NiceshotPall", Chatter::NiceShotPall, 2.0f }, + { "Chatter_GoingToGuardHostages", Chatter::GoingToGuardHostages, 3.0f }, + { "Chatter_GoingToGuardDoppedBomb", Chatter::GoingToGuardDroppedC4, 6.0f }, + { "Chatter_OnMyWay", Chatter::OnMyWay, 1.5f }, + { "Chatter_LeadOnSir", Chatter::LeadOnSir, 5.0f }, + { "Chatter_Pinned_Down", Chatter::PinnedDown, 5.0f }, + { "Chatter_GottaFindTheBomb", Chatter::GottaFindC4, 3.0f }, + { "Chatter_You_Heard_The_Man", Chatter::YouHeardTheMan, 3.0f }, + { "Chatter_Lost_The_Commander", Chatter::LostCommander, 4.5f }, + { "Chatter_NewRound", Chatter::NewRound, 3.5f }, + { "Chatter_CoverMe", Chatter::CoverMe, 3.5f }, + { "Chatter_BehindSmoke", Chatter::BehindSmoke, 3.5f }, + { "Chatter_BombSiteSecured", Chatter::BombsiteSecured, 3.5f }, + { "Chatter_GoingToCamp", Chatter::GoingToCamp, 30.0f }, + { "Chatter_Camp", Chatter::Camping, 10.0f }, + }; + + while (file.getLine (line)) { + line.trim (); + + if (isCommentLine (line)) { + continue; + } + + StringRef rewriteKey = "RewritePath"; + StringRef eventKey = "Event"; + + if (line.startsWith (rewriteKey)) { + cv_chatter_path.set (line.substr (rewriteKey.length ()).trim ().chars ()); + } + else if (line.startsWith (eventKey)) { + auto items = line.substr (eventKey.length ()).split ("="); + + if (items.length () != 2) { + logger.error ("Error in chatter config file syntax... Please correct all errors."); + continue; + } + + for (auto &item : items) { + item.trim (); + } + items[1].trim ("(;)"); + + for (const auto &event : chatterEventMap) { + if (event.str == items[0]) { + // this does common work of parsing comma-separated chatter line + auto sounds = items[1].split (","); + + for (auto &sound : sounds) { + sound.trim ().trim ("\""); + float duration = game.getWaveLen (sound.chars ()); + + if (duration > 0.0f) { + m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration); + } + } + sounds.clear (); + } + } + } + } + file.close (); + } + else { + cv_radio_mode.set (1); + logger.message ("Bots chatter communication disabled."); + } +} + +void BotConfig::loadChatConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + + // chat config initialization + if (util.openConfig ("chat.cfg", "Chat file not found.", &file, true)) { + StringArray *chat = nullptr; + + StringArray keywords {}; + StringArray replies {}; + + // clear all the stuff before loading new one + for (auto &item : m_chat) { + item.clear (); + } + m_replies.clear (); + + while (file.getLine (line)) { + line.trim (); + + if (isCommentLine (line)) { + continue; + } + + if (line.startsWith ("[KILLED]")) { + chat = &m_chat[Chat::Kill]; + continue; + } + else if (line.startsWith ("[BOMBPLANT]")) { + chat = &m_chat[Chat::Kill]; + continue; + } + else if (line.startsWith ("[DEADCHAT]")) { + chat = &m_chat[Chat::Dead]; + continue; + } + else if (line.startsWith ("[REPLIES]")) { + chat = nullptr; + continue; + } + else if (line.startsWith ("[UNKNOWN]")) { + chat = &m_chat[Chat::NoKeyword]; + continue; + } + else if (line.startsWith ("[TEAMATTACK]")) { + chat = &m_chat[Chat::TeamAttack]; + continue; + } + else if (line.startsWith ("[WELCOME]")) { + chat = &m_chat[Chat::Hello]; + continue; + } + else if (line.startsWith ("[TEAMKILL]")) { + chat = &m_chat[Chat::TeamKill]; + continue; + } + + if (chat != nullptr) { + chat->push (line); + + } + else { + if (line.startsWith ("@KEY")) { + if (!keywords.empty () && !replies.empty ()) { + m_replies.emplace (keywords, replies); + + keywords.clear (); + replies.clear (); + } + keywords.clear (); + + for (const auto &key : line.substr (4).split (",")) { + keywords.emplace (utf8tools.strToUpper (key)); + } + + for (auto &keyword : keywords) { + keyword.trim ().trim ("\""); + } + } + else if (!keywords.empty () && !line.empty ()) { + replies.push (line); + } + } + } + + // shuffle chat a bit + for (auto &item : m_chat) { + item.shuffle (); + item.shuffle (); + } + file.close (); + } + else { + cv_chat.set (0); + } +} + +void BotConfig::loadLanguageConfig () { + setupMemoryFiles (); + + if (game.isDedicated () || game.is (GameFlags::Legacy)) { + + if (game.is (GameFlags::Legacy)) { + logger.message ("Bots multilingual system disabled, due to your Counter-Strike version!"); + } + return; // dedicated server will use only english translation + } + String line; + MemFile file; + + // localizer inititalization + if (util.openConfig ("lang.cfg", "Specified language not found.", &file, true)) { + String temp; + Twin lang; + + // clear all the translations before new load + m_language.clear (); + + while (file.getLine (line)) { + if (isCommentLine (line)) { + continue; + } + + if (line.startsWith ("[ORIGINAL]")) { + if (!temp.empty ()) { + lang.second = cr::move (temp); + } + + if (!lang.second.empty () && !lang.first.empty ()) { + m_language.push (lang.first.trim (), lang.second.trim ()); + } + } + else if (line.startsWith ("[TRANSLATED]") && !temp.empty ()) { + lang.first = cr::move (temp); + } + else { + temp += line; + } + } + file.close (); + } + else if (strcmp (cv_language.str (), "en") != 0) { + logger.error ("Couldn't load language configuration"); + } +} + +void BotConfig::loadAvatarsConfig () { + setupMemoryFiles (); + + if (game.is (GameFlags::Legacy)) { + return; + } + + String line; + MemFile file; + + // avatars inititalization + if (util.openConfig ("avatars.cfg", "Avatars config file not found. Avatars will not be displayed.", &file)) { + m_avatars.clear (); + + while (file.getLine (line)) { + if (isCommentLine (line)) { + continue; + } + m_avatars.push (cr::move (line.trim ())); + } + } +} + +void BotConfig::loadDifficultyConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + + // initialize defaults + m_difficulty[Difficulty::Noob] = { + { 0.8f, 1.0f }, 5, 0, 0 + }; + + m_difficulty[Difficulty::Easy] = { + { 0.6f, 0.8f }, 30, 10, 10 + }; + + m_difficulty[Difficulty::Normal] = { + { 0.4f, 0.6f }, 50, 30, 40 + }; + + m_difficulty[Difficulty::Hard] = { + { 0.2f, 0.4f }, 75, 60, 70 + }; + + m_difficulty[Difficulty::Expert] = { + { 0.1f, 0.2f }, 100, 90, 90 + }; + + // currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob + constexpr uint32 kMaxDifficultyValues = 5; + + // helper for parsing each level + auto parseLevel = [&] (int32 level, StringRef data) { + auto values = data.split (","); + + if (values.length () != kMaxDifficultyValues) { + logger.error ("Bad value for difficulty level #%d.", level); + return; + } + auto diff = &m_difficulty[level]; + + diff->reaction[0] = values[0].float_ (); + diff->reaction[1] = values[1].float_ (); + diff->headshotPct = values[2].int_ (); + diff->seenThruPct = values[3].int_ (); + diff->hearThruPct = values[4].int_ (); + }; + + // avatars inititalization + if (util.openConfig ("difficulty.cfg", "Difficulty config file not found. Defaults loaded.", &file)) { + + while (file.getLine (line)) { + if (isCommentLine (line)) { + continue; + } + auto items = line.split ("="); + + if (items.length () != 2) { + logger.error ("Error in difficulty config file syntax... Please correct all errors."); + continue; + } + auto key = items[0].trim (); + + // get our keys + if (key == "Noob") { + parseLevel (Difficulty::Noob, items[1]); + } + else if (key == "Easy") { + parseLevel (Difficulty::Easy, items[1]); + } + else if (key == "Normal") { + parseLevel (Difficulty::Normal, items[1]); + } + else if (key == "Hard") { + parseLevel (Difficulty::Hard, items[1]); + } + else if (key == "Expert") { + parseLevel (Difficulty::Expert, items[1]); + } + } + } +} + +void BotConfig::loadMapSpecificConfig () { + auto mapSpecificConfig = strings.format ("addons/%s/conf/maps/%s.cfg", product.folder, game.getMapName ()); + + // check existence of file + if (File::exists (strings.format ("%s/%s", game.getRunningModName (), mapSpecificConfig))) { + game.serverCommand ("exec %s", mapSpecificConfig); + + game.print ("Executed map-specific config: %s", mapSpecificConfig); + } +} + +void BotConfig::loadLogosConfig () { + setupMemoryFiles (); + + String line; + MemFile file; + + // logos inititalization + if (util.openConfig ("logos.cfg", "Logos config file not found. Loading defaults.", &file)) { + m_logos.clear (); + + while (file.getLine (line)) { + if (isCommentLine (line)) { + continue; + } + m_logos.push (cr::move (line.trim ())); + } + } + else { + m_logos = cr::move (String { "{biohaz;{graf003;{graf004;{graf005;{lambda06;{target;{hand1;{spit2;{bloodhand6;{foot_l;{foot_r" }.split (";")); + } +} + +void BotConfig::setupMemoryFiles () { + static bool setMemoryPointers = true; + + auto wrapLoadFile = [] (const char *filename, int *length) { + return engfuncs.pfnLoadFileForMe (filename, length); + }; + + auto wrapFreeFile = [] (void *buffer) { + engfuncs.pfnFreeFile (buffer); + }; + + if (setMemoryPointers) { + MemFileStorage::instance ().initizalize (wrapLoadFile, wrapFreeFile); + setMemoryPointers = false; + } +} + +BotName *BotConfig::pickBotName () { + if (m_botNames.empty ()) { + return nullptr; + } + + for (size_t i = 0; i < m_botNames.length () * 2; ++i) { + auto botName = &m_botNames.random (); + + if (botName->name.length () < 3 || botName->usedBy != -1) { + continue; + } + return botName; + } + return nullptr; +} + +void BotConfig::clearUsedName (Bot *bot) { + for (auto &name : m_botNames) { + if (name.usedBy == bot->index ()) { + name.usedBy = -1; + break; + } + } +} + +void BotConfig::initWeapons () { + m_weapons.clear (); + + // fill array with available weapons + m_weapons.emplace (Weapon::Knife, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, true); + m_weapons.emplace (Weapon::USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, 12, false); + m_weapons.emplace (Weapon::Glock18, "weapon_glock18", "glock18.mdl", 400, 1, -1, -1, 1, 2, 1, 1, 0, 20, false); + m_weapons.emplace (Weapon::Deagle, "weapon_deagle", "deagle.mdl", 650, 1, 2, 2, 1, 3, 4, 4, 2, 7, false); + m_weapons.emplace (Weapon::P228, "weapon_p228", "p228.mdl", 600, 1, 2, 2, 1, 4, 3, 3, 0, 13, false); + m_weapons.emplace (Weapon::Elite, "weapon_elite", "elite.mdl", 800, 1, 0, 0, 1, 5, 5, 5, 0, 30, false); + m_weapons.emplace (Weapon::FiveSeven, "weapon_fiveseven", "fiveseven.mdl", 750, 1, 1, 1, 1, 6, 5, 5, 0, 20, false); + m_weapons.emplace (Weapon::M3, "weapon_m3", "m3.mdl", 1700, 1, 2, -1, 2, 1, 1, 1, 0, 8, false); + m_weapons.emplace (Weapon::XM1014, "weapon_xm1014", "xm1014.mdl", 3000, 1, 2, -1, 2, 2, 2, 2, 0, 7, false); + m_weapons.emplace (Weapon::MP5, "weapon_mp5navy", "mp5.mdl", 1500, 1, 2, 1, 3, 1, 2, 2, 0, 30, true); + m_weapons.emplace (Weapon::TMP, "weapon_tmp", "tmp.mdl", 1250, 1, 1, 1, 3, 2, 1, 1, 0, 30, true); + m_weapons.emplace (Weapon::P90, "weapon_p90", "p90.mdl", 2350, 1, 2, 1, 3, 3, 4, 4, 0, 50, true); + m_weapons.emplace (Weapon::MAC10, "weapon_mac10", "mac10.mdl", 1400, 1, 0, 0, 3, 4, 1, 1, 0, 30, true); + m_weapons.emplace (Weapon::UMP45, "weapon_ump45", "ump45.mdl", 1700, 1, 2, 2, 3, 5, 3, 3, 0, 25, true); + m_weapons.emplace (Weapon::AK47, "weapon_ak47", "ak47.mdl", 2500, 1, 0, 0, 4, 1, 2, 2, 2, 30, true); + m_weapons.emplace (Weapon::SG552, "weapon_sg552", "sg552.mdl", 3500, 1, 0, -1, 4, 2, 4, 4, 2, 30, true); + m_weapons.emplace (Weapon::M4A1, "weapon_m4a1", "m4a1.mdl", 3100, 1, 1, 1, 4, 3, 3, 3, 2, 30, true); + m_weapons.emplace (Weapon::Galil, "weapon_galil", "galil.mdl", 2000, 1, 0, 0, 4, -1, 1, 1, 2, 35, true); + m_weapons.emplace (Weapon::Famas, "weapon_famas", "famas.mdl", 2250, 1, 1, 1, 4, -1, 1, 1, 2, 25, true); + m_weapons.emplace (Weapon::AUG, "weapon_aug", "aug.mdl", 3500, 1, 1, 1, 4, 4, 4, 4, 2, 30, true); + m_weapons.emplace (Weapon::Scout, "weapon_scout", "scout.mdl", 2750, 1, 2, 0, 4, 5, 3, 2, 3, 10, false); + m_weapons.emplace (Weapon::AWP, "weapon_awp", "awp.mdl", 4750, 1, 2, 0, 4, 6, 5, 6, 3, 10, false); + m_weapons.emplace (Weapon::G3SG1, "weapon_g3sg1", "g3sg1.mdl", 5000, 1, 0, 2, 4, 7, 6, 6, 3, 20, false); + m_weapons.emplace (Weapon::SG550, "weapon_sg550", "sg550.mdl", 4200, 1, 1, 1, 4, 8, 5, 5, 3, 30, false); + m_weapons.emplace (Weapon::M249, "weapon_m249", "m249.mdl", 5750, 1, 2, 1, 5, 1, 1, 1, 2, 100, true); + m_weapons.emplace (Weapon::Shield, "weapon_shield", "shield.mdl", 2200, 0, 1, 1, 8, -1, 8, 8, 0, 0, false); + + // not needed actually, but cause too much refactoring for now. todo + m_weapons.emplace (0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false); +} + +void BotConfig::adjustWeaponPrices () { + // elite price is 1000$ on older versions of cs... + if (!(game.is (GameFlags::Legacy))) { + return; + } + + for (auto &weapon : m_weapons) { + if (weapon.id == Weapon::Elite) { + weapon.price = 1000; + break; + } + } +} + +WeaponInfo &BotConfig::findWeaponById (const int id) { + for (auto &weapon : m_weapons) { + if (weapon.id == id) { + return weapon; + } + } + return m_weapons.at (0); +} + +const char *BotConfig::translate (StringRef input) { + // this function translate input string into needed language + + if (game.isDedicated ()) { + return input.chars (); + } + static String result; + result.clear (); + + if (m_language.find (input, result)) { + return result.chars (); + } + return input.chars (); // nothing found +} \ No newline at end of file diff --git a/src/control.cpp b/src/control.cpp index 034854d..fe2178d 100644 --- a/src/control.cpp +++ b/src/control.cpp @@ -204,7 +204,7 @@ int BotControl::cmdCvars () { // if save requested, dump cvars to yapb.cfg if (isSave) { - cfg.open (strings.format ("%s/addons/%s/conf/%s.cfg", game.getModName (), product.folder, product.folder), "wt"); + cfg.open (strings.format ("%s/addons/%s/conf/%s.cfg", game.getRunningModName (), product.folder, product.folder), "wt"); cfg.puts ("// Configuration file for %s\n\n", product.name); } @@ -1550,6 +1550,12 @@ bool BotControl::executeCommands () { if (m_args.empty ()) { return false; } + const auto &prefix = m_args[0]; + + // no handling if not for us + if (prefix != "yb" && prefix != "yapb") { + return false; + } Client &client = util.getClient (game.indexOfPlayer (m_ent)); // do not allow to execute stuff for non admins @@ -1557,7 +1563,6 @@ bool BotControl::executeCommands () { msg ("Access to %s commands is restricted.", product.name); return true; } - const auto &prefix = m_args[0]; auto aliasMatch = [] (String &test, const String &cmd, String &aliasName) -> bool { for (auto &alias : test.split ("/")) { diff --git a/src/engine.cpp b/src/engine.cpp index 46fc356..4c58c53 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -257,7 +257,7 @@ void Game::testHull (const Vector &start, const Vector &end, int ignoreFlags, in } float Game::getWaveLen (const char *fileName) { - auto filePath = strings.format ("%s/%s/%s.wav", getModName (), cv_chatter_path.str (), fileName); + auto filePath = strings.format ("%s/%s/%s.wav", getRunningModName (), cv_chatter_path.str (), fileName); File fp (filePath, "rb"); @@ -316,7 +316,7 @@ bool Game::isDedicated () { return dedicated; } -const char *Game::getModName () { +const char *Game::getRunningModName () { // this function returns mod name without path static String name; @@ -631,7 +631,7 @@ void Game::registerCvars (bool gameVars) { } bool Game::loadCSBinary () { - auto modname = getModName (); + auto modname = getRunningModName (); if (!modname) { return false; @@ -735,7 +735,7 @@ bool Game::postload () { // ensure we're have all needed directories for (const auto &dir : StringArray { "conf/lang", "data/train", "data/graph", "data/logs" }) { - File::createPath (strings.format ("%s/addons/%s/%s", getModName (), product.folder, dir)); + File::createPath (strings.format ("%s/addons/%s/%s", getRunningModName (), product.folder, dir)); } // set out user agent for http stuff @@ -815,7 +815,7 @@ bool Game::postload () { auto gamedll = strings.format ("%s/%s", plat.env ("XASH3D_GAMELIBDIR"), plat.hfp ? "libserver_hardfp.so" : "libserver.so"); if (!m_gameLib.load (gamedll)) { - logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getModName ()); + logger.fatal ("Unable to load gamedll \"%s\". Exiting... (gamedir: %s)", gamedll, getRunningModName ()); } displayCSVersion (); } @@ -823,7 +823,7 @@ bool Game::postload () { bool binaryLoaded = loadCSBinary (); if (!binaryLoaded && !is (GameFlags::Metamod)) { - logger.fatal ("Mod that you has started, not supported by this bot (gamedir: %s)", getModName ()); + logger.fatal ("Mod that you has started, not supported by this bot (gamedir: %s)", getRunningModName ()); } displayCSVersion (); diff --git a/src/graph.cpp b/src/graph.cpp index f569d0a..5b28b24 100644 --- a/src/graph.cpp +++ b/src/graph.cpp @@ -2719,7 +2719,7 @@ const char *BotGraph::getDataDirectory (bool isMemoryFile) { buffer.assignf ("addons/%s/data/", product.folder); } else { - buffer.assignf ("%s/addons/%s/data/", game.getModName (), product.folder); + buffer.assignf ("%s/addons/%s/data/", game.getRunningModName (), product.folder); } return buffer.chars (); } diff --git a/src/linkage.cpp b/src/linkage.cpp index 0df1662..558c796 100644 --- a/src/linkage.cpp +++ b/src/linkage.cpp @@ -309,10 +309,10 @@ CR_EXPORT int GetEntityAPI (gamefuncs_t *table, int) { // execute main config conf.loadMainConfig (); - if (File::exists (strings.format ("%s/maps/%s_%s.cfg", game.getModName (), game.getMapName (), product.folder))) { - game.serverCommand ("exec maps/%s_%s.cfg", game.getMapName (), product.folder); - game.print ("Executing Map-Specific config file"); - } + // load map-specific config + conf.loadMapSpecificConfig (); + + // initialize quota management bots.initQuota (); if (game.is (GameFlags::Metamod)) { diff --git a/src/manager.cpp b/src/manager.cpp index 12079ba..3d9abcf 100644 --- a/src/manager.cpp +++ b/src/manager.cpp @@ -16,7 +16,6 @@ #include ConVar cv_autovacate ("yb_autovacate", "1", "Kick bots to automatically make room for human players."); -ConVar cv_bind_menu_key ("yb_bind_menu_key", "=", "Bind's specified key for opening bots menu.", false); ConVar cv_quota ("yb_quota", "0", "Specifies the number bots to be added to the game.", true, 0.0f, static_cast (kGameMaxPlayers)); ConVar cv_quota_mode ("yb_quota_mode", "normal", "Specifies the type of quota.\nAllowed values: 'normal', 'fill', and 'match'.\nIf 'fill', the server will adjust bots to keep N players in the game, where N is yb_quota.\nIf 'match', the server will maintain a 1:N ratio of humans to bots, where N is yb_quota_match.", false); @@ -35,7 +34,6 @@ ConVar cv_show_avatars ("yb_show_avatars", "1", "Enables or disabels displaying ConVar cv_show_latency ("yb_show_latency", "2", "Enables latency display in scoreboard.\nAllowed values: '0', '1', '2'.\nIf '0', there is nothing displayed.\nIf '1', there is a 'BOT' is displayed.\nIf '2' fake ping is displayed.", true, 0.0f, 2.0f); ConVar cv_language ("yb_language", "en", "Specifies the language for bot messages and menus.", false); -ConVar cv_ignore_cvars_on_changelevel ("yb_ignore_cvars_on_changelevel", "yb_quota,yb_autovacate", "Specifies comma separated list of bot cvars, that will not be overriten by config on changelevel.", false); ConVar mp_limitteams ("mp_limitteams", nullptr, Var::GameRef); ConVar mp_autoteambalance ("mp_autoteambalance", nullptr, Var::GameRef); @@ -1696,739 +1694,3 @@ void BotManager::setBombPlanted (bool isPlanted) { } m_bombPlanted = isPlanted; } - -BotConfig::BotConfig () { - m_chat.resize (Chat::Count); - m_chatter.resize (Chatter::Count); - - m_weaponProps.resize (kMaxWeapons); -} - -void BotConfig::loadConfigs () { - setupMemoryFiles (); - - loadNamesConfig (); - loadChatConfig (); - loadChatterConfig (); - loadWeaponsConfig (); - loadLanguageConfig (); - loadLogosConfig (); - loadAvatarsConfig (); - loadDifficultyConfig (); -} - -void BotConfig::loadMainConfig () { - if (game.is (GameFlags::Legacy) && !game.is (GameFlags::Xash3D)) { - util.setNeedForWelcome (true); - } - setupMemoryFiles (); - - static bool firstLoad = true; - - auto needsToIgnoreVar = [] (StringArray &list, const char *needle) { - for (const auto &var : list) { - if (var == needle) { - return true; - } - } - return false; - }; - String line; - MemFile file; - - // this is does the same as exec of engine, but not overwriting values of cvars spcified in cv_ignore_cvars_on_changelevel - if (util.openConfig ("yapb.cfg", "YaPB main config file is not found.", &file, false)) { - while (file.getLine (line)) { - line.trim (); - - if (isCommentLine (line)) { - continue; - } - - if (firstLoad) { - game.serverCommand (line.chars ()); - continue; - } - auto keyval = line.split (" "); - - if (keyval.length () > 1) { - auto ignore = String (cv_ignore_cvars_on_changelevel.str ()).split (","); - - auto key = keyval[0].trim ().chars (); - auto cvar = engfuncs.pfnCVarGetPointer (key); - - if (cvar != nullptr) { - auto value = const_cast (keyval[1].trim ().trim ("\"").trim ().chars ()); - - if (needsToIgnoreVar (ignore, key) && !strings.matches (value, cvar->string)) { - - // preserve quota number if it's zero - if (strings.matches (cvar->name, "yb_quota") && cv_quota.int_ () <= 0) { - engfuncs.pfnCvar_DirectSet (cvar, value); - continue; - } - game.print ("Bot CVAR '%s' differs from the stored in the config (%s/%s). Ignoring.", cvar->name, cvar->string, value); - - // ensure cvar will have old value - engfuncs.pfnCvar_DirectSet (cvar, cvar->string); - } - else { - engfuncs.pfnCvar_DirectSet (cvar, value); - } - } - else { - game.serverCommand (line.chars ()); - } - } - } - file.close (); - } - firstLoad = false; - - // android is abit hard to play, lower the difficulty by default - if (plat.android && cv_difficulty.int_ () > 3) { - cv_difficulty.set (3); - } - - // bind the correct menu key for bot menu... - if (!game.isDedicated () && !strings.isEmpty (cv_bind_menu_key.str ())) { - game.serverCommand ("bind \"%s\" \"yb menu\"", cv_bind_menu_key.str ()); - } -} - -void BotConfig::loadNamesConfig () { - setupMemoryFiles (); - - String line; - MemFile file; - - // naming initialization - if (util.openConfig ("names.cfg", "Name configuration file not found.", &file, true)) { - m_botNames.clear (); - - while (file.getLine (line)) { - line.trim (); - - if (isCommentLine (line)) { - continue; - } - // max botname is 32 characters - if (line.length () > 32) { - line[32] = kNullChar; - } - m_botNames.emplace (line, -1); - } - file.close (); - } -} - -void BotConfig::loadWeaponsConfig () { - setupMemoryFiles (); - - auto addWeaponEntries = [] (SmallArray &weapons, bool as, StringRef name, const StringArray &data) { - - // we're have null terminator element in weapons array... - if (data.length () + 1 != weapons.length ()) { - logger.error ("%s entry in weapons config is not valid or malformed (%d/%d).", name, data.length (), weapons.length ()); - - return; - } - - for (size_t i = 0; i < data.length (); ++i) { - if (as) { - weapons[i].teamAS = data[i].int_ (); - } - else { - weapons[i].teamStandard = data[i].int_ (); - } - } - }; - - auto addIntEntries = [] (SmallArray &to, StringRef name, const StringArray &data) { - if (data.length () != to.length ()) { - logger.error ("%s entry in weapons config is not valid or malformed (%d/%d).", name, data.length (), to.length ()); - return; - } - - for (size_t i = 0; i < to.length (); ++i) { - to[i] = data[i].int_ (); - } - }; - String line; - MemFile file; - - // weapon data initialization - if (util.openConfig ("weapon.cfg", "Weapon configuration file not found. Loading defaults", &file)) { - while (file.getLine (line)) { - line.trim (); - - if (isCommentLine (line)) { - continue; - } - auto pair = line.split ("="); - - if (pair.length () != 2) { - continue; - } - - for (auto &trim : pair) { - trim.trim (); - } - auto splitted = pair[1].split (","); - - if (pair[0].startsWith ("MapStandard")) { - addWeaponEntries (m_weapons, false, pair[0], splitted); - } - else if (pair[0].startsWith ("MapAS")) { - addWeaponEntries (m_weapons, true, pair[0], splitted); - } - - else if (pair[0].startsWith ("GrenadePercent")) { - addIntEntries (m_grenadeBuyPrecent, pair[0], splitted); - } - else if (pair[0].startsWith ("Economics")) { - addIntEntries (m_botBuyEconomyTable, pair[0], splitted); - } - else if (pair[0].startsWith ("PersonalityNormal")) { - addIntEntries (m_normalWeaponPrefs, pair[0], splitted); - } - else if (pair[0].startsWith ("PersonalityRusher")) { - addIntEntries (m_rusherWeaponPrefs, pair[0], splitted); - } - else if (pair[0].startsWith ("PersonalityCareful")) { - addIntEntries (m_carefulWeaponPrefs, pair[0], splitted); - } - } - file.close (); - } -} - -void BotConfig::loadChatterConfig () { - setupMemoryFiles (); - - String line; - MemFile file; - - // chatter initialization - if (game.is (GameFlags::HasBotVoice) && cv_radio_mode.int_ () == 2 && util.openConfig ("chatter.cfg", "Couldn't open chatter system configuration", &file)) { - m_chatter.clear (); - - struct EventMap { - String str; - int code; - float repeat; - } chatterEventMap[] = { - { "Radio_CoverMe", Radio::CoverMe, kMaxChatterRepeatInteval }, - { "Radio_YouTakePoint", Radio::YouTakeThePoint, kMaxChatterRepeatInteval }, - { "Radio_HoldPosition", Radio::HoldThisPosition, 10.0f }, - { "Radio_RegroupTeam", Radio::RegroupTeam, 10.0f }, - { "Radio_FollowMe", Radio::FollowMe, 15.0f }, - { "Radio_TakingFire", Radio::TakingFireNeedAssistance, 5.0f }, - { "Radio_GoGoGo", Radio::GoGoGo, kMaxChatterRepeatInteval }, - { "Radio_Fallback", Radio::TeamFallback, kMaxChatterRepeatInteval }, - { "Radio_StickTogether", Radio::StickTogetherTeam, kMaxChatterRepeatInteval }, - { "Radio_GetInPosition", Radio::GetInPositionAndWaitForGo, kMaxChatterRepeatInteval }, - { "Radio_StormTheFront", Radio::StormTheFront, kMaxChatterRepeatInteval }, - { "Radio_ReportTeam", Radio::ReportInTeam, kMaxChatterRepeatInteval }, - { "Radio_Affirmative", Radio::RogerThat, kMaxChatterRepeatInteval }, - { "Radio_EnemySpotted", Radio::EnemySpotted, 4.0f }, - { "Radio_NeedBackup", Radio::NeedBackup, 5.0f }, - { "Radio_SectorClear", Radio::SectorClear, 10.0f }, - { "Radio_InPosition", Radio::ImInPosition, 10.0f }, - { "Radio_ReportingIn", Radio::ReportingIn, 3.0f }, - { "Radio_ShesGonnaBlow", Radio::ShesGonnaBlow, kMaxChatterRepeatInteval }, - { "Radio_Negative", Radio::Negative, kMaxChatterRepeatInteval }, - { "Radio_EnemyDown", Radio::EnemyDown, 10.0f }, - { "Chatter_DiePain", Chatter::DiePain, kMaxChatterRepeatInteval }, - { "Chatter_GoingToPlantBomb", Chatter::GoingToPlantBomb, 5.0f }, - { "Chatter_GoingToGuardVIPSafety", Chatter::GoingToGuardVIPSafety, kMaxChatterRepeatInteval }, - { "Chatter_RescuingHostages", Chatter::RescuingHostages, kMaxChatterRepeatInteval }, - { "Chatter_TeamKill", Chatter::FriendlyFire, kMaxChatterRepeatInteval }, - { "Chatter_GuardingVipSafety", Chatter::GuardingVIPSafety, kMaxChatterRepeatInteval }, - { "Chatter_PlantingC4", Chatter::PlantingBomb, 10.0f }, - { "Chatter_InCombat", Chatter::InCombat, kMaxChatterRepeatInteval }, - { "Chatter_SeeksEnemy", Chatter::SeekingEnemies, kMaxChatterRepeatInteval }, - { "Chatter_Nothing", Chatter::Nothing, kMaxChatterRepeatInteval }, - { "Chatter_EnemyDown", Chatter::EnemyDown, 10.0f }, - { "Chatter_UseHostage", Chatter::UsingHostages, kMaxChatterRepeatInteval }, - { "Chatter_WonTheRound", Chatter::WonTheRound, kMaxChatterRepeatInteval }, - { "Chatter_QuicklyWonTheRound", Chatter::QuickWonRound, kMaxChatterRepeatInteval }, - { "Chatter_NoEnemiesLeft", Chatter::NoEnemiesLeft, kMaxChatterRepeatInteval }, - { "Chatter_FoundBombPlace", Chatter::FoundC4Plant, 15.0f }, - { "Chatter_WhereIsTheBomb", Chatter::WhereIsTheC4, kMaxChatterRepeatInteval }, - { "Chatter_DefendingBombSite", Chatter::DefendingBombsite, kMaxChatterRepeatInteval }, - { "Chatter_BarelyDefused", Chatter::BarelyDefused, kMaxChatterRepeatInteval }, - { "Chatter_NiceshotCommander", Chatter::NiceShotCommander, 10.0f }, - { "Chatter_ReportingIn", Chatter::ReportingIn, 10.0f }, - { "Chatter_SpotTheBomber", Chatter::SpotTheBomber, 4.3f }, - { "Chatter_VIPSpotted", Chatter::VIPSpotted, 5.3f }, - { "Chatter_FriendlyFire", Chatter::FriendlyFire, 2.1f }, - { "Chatter_GotBlinded", Chatter::Blind, 12.0f }, - { "Chatter_GuardDroppedC4", Chatter::GuardingDroppedC4, 3.0f }, - { "Chatter_DefusingC4", Chatter::DefusingBomb, 3.0f }, - { "Chatter_FoundC4", Chatter::FoundC4, 5.5f }, - { "Chatter_ScaredEmotion", Chatter::ScaredEmotion, 6.1f }, - { "Chatter_HeardEnemy", Chatter::ScaredEmotion, 12.8f }, - { "Chatter_SniperWarning", Chatter::SniperWarning, 14.3f }, - { "Chatter_SniperKilled", Chatter::SniperKilled, 12.1f }, - { "Chatter_OneEnemyLeft", Chatter::OneEnemyLeft, 12.5f }, - { "Chatter_TwoEnemiesLeft", Chatter::TwoEnemiesLeft, 12.5f }, - { "Chatter_ThreeEnemiesLeft", Chatter::ThreeEnemiesLeft, 12.5f }, - { "Chatter_NiceshotPall", Chatter::NiceShotPall, 2.0f }, - { "Chatter_GoingToGuardHostages", Chatter::GoingToGuardHostages, 3.0f }, - { "Chatter_GoingToGuardDoppedBomb", Chatter::GoingToGuardDroppedC4, 6.0f }, - { "Chatter_OnMyWay", Chatter::OnMyWay, 1.5f }, - { "Chatter_LeadOnSir", Chatter::LeadOnSir, 5.0f }, - { "Chatter_Pinned_Down", Chatter::PinnedDown, 5.0f }, - { "Chatter_GottaFindTheBomb", Chatter::GottaFindC4, 3.0f }, - { "Chatter_You_Heard_The_Man", Chatter::YouHeardTheMan, 3.0f }, - { "Chatter_Lost_The_Commander", Chatter::LostCommander, 4.5f }, - { "Chatter_NewRound", Chatter::NewRound, 3.5f }, - { "Chatter_CoverMe", Chatter::CoverMe, 3.5f }, - { "Chatter_BehindSmoke", Chatter::BehindSmoke, 3.5f }, - { "Chatter_BombSiteSecured", Chatter::BombsiteSecured, 3.5f }, - { "Chatter_GoingToCamp", Chatter::GoingToCamp, 30.0f }, - { "Chatter_Camp", Chatter::Camping, 10.0f }, - }; - - while (file.getLine (line)) { - line.trim (); - - if (isCommentLine (line)) { - continue; - } - - StringRef rewriteKey = "RewritePath"; - StringRef eventKey = "Event"; - - if (line.startsWith (rewriteKey)) { - cv_chatter_path.set (line.substr (rewriteKey.length ()).trim ().chars ()); - } - else if (line.startsWith (eventKey)) { - auto items = line.substr (eventKey.length ()).split ("="); - - if (items.length () != 2) { - logger.error ("Error in chatter config file syntax... Please correct all errors."); - continue; - } - - for (auto &item : items) { - item.trim (); - } - items[1].trim ("(;)"); - - for (const auto &event : chatterEventMap) { - if (event.str == items[0]) { - // this does common work of parsing comma-separated chatter line - auto sounds = items[1].split (","); - - for (auto &sound : sounds) { - sound.trim ().trim ("\""); - float duration = game.getWaveLen (sound.chars ()); - - if (duration > 0.0f) { - m_chatter[event.code].emplace (cr::move (sound), event.repeat, duration); - } - } - sounds.clear (); - } - } - } - } - file.close (); - } - else { - cv_radio_mode.set (1); - logger.message ("Bots chatter communication disabled."); - } -} - -void BotConfig::loadChatConfig () { - setupMemoryFiles (); - - String line; - MemFile file; - - // chat config initialization - if (util.openConfig ("chat.cfg", "Chat file not found.", &file, true)) { - StringArray *chat = nullptr; - - StringArray keywords {}; - StringArray replies {}; - - // clear all the stuff before loading new one - for (auto &item : m_chat) { - item.clear (); - } - m_replies.clear (); - - while (file.getLine (line)) { - line.trim (); - - if (isCommentLine (line)) { - continue; - } - - if (line.startsWith ("[KILLED]")) { - chat = &m_chat[Chat::Kill]; - continue; - } - else if (line.startsWith ("[BOMBPLANT]")) { - chat = &m_chat[Chat::Kill]; - continue; - } - else if (line.startsWith ("[DEADCHAT]")) { - chat = &m_chat[Chat::Dead]; - continue; - } - else if (line.startsWith ("[REPLIES]")) { - chat = nullptr; - continue; - } - else if (line.startsWith ("[UNKNOWN]")) { - chat = &m_chat[Chat::NoKeyword]; - continue; - } - else if (line.startsWith ("[TEAMATTACK]")) { - chat = &m_chat[Chat::TeamAttack]; - continue; - } - else if (line.startsWith ("[WELCOME]")) { - chat = &m_chat[Chat::Hello]; - continue; - } - else if (line.startsWith ("[TEAMKILL]")) { - chat = &m_chat[Chat::TeamKill]; - continue; - } - - if (chat != nullptr) { - chat->push (line); - - } - else { - if (line.startsWith ("@KEY")) { - if (!keywords.empty () && !replies.empty ()) { - m_replies.emplace (keywords, replies); - - keywords.clear (); - replies.clear (); - } - keywords.clear (); - - for (const auto &key : line.substr (4).split (",")) { - keywords.emplace (utf8tools.strToUpper (key)); - } - - for (auto &keyword : keywords) { - keyword.trim ().trim ("\""); - } - } - else if (!keywords.empty () && !line.empty ()) { - replies.push (line); - } - } - } - - // shuffle chat a bit - for (auto &item : m_chat) { - item.shuffle (); - item.shuffle (); - } - file.close (); - } - else { - cv_chat.set (0); - } -} - -void BotConfig::loadLanguageConfig () { - setupMemoryFiles (); - - if (game.isDedicated () || game.is (GameFlags::Legacy)) { - - if (game.is (GameFlags::Legacy)) { - logger.message ("Bots multilingual system disabled, due to your Counter-Strike version!"); - } - return; // dedicated server will use only english translation - } - String line; - MemFile file; - - // localizer inititalization - if (util.openConfig ("lang.cfg", "Specified language not found.", &file, true)) { - String temp; - Twin lang; - - // clear all the translations before new load - m_language.clear (); - - while (file.getLine (line)) { - if (isCommentLine (line)) { - continue; - } - - if (line.startsWith ("[ORIGINAL]")) { - if (!temp.empty ()) { - lang.second = cr::move (temp); - } - - if (!lang.second.empty () && !lang.first.empty ()) { - m_language.push (lang.first.trim (), lang.second.trim ()); - } - } - else if (line.startsWith ("[TRANSLATED]") && !temp.empty ()) { - lang.first = cr::move (temp); - } - else { - temp += line; - } - } - file.close (); - } - else if (strcmp (cv_language.str (), "en") != 0) { - logger.error ("Couldn't load language configuration"); - } -} - -void BotConfig::loadAvatarsConfig () { - setupMemoryFiles (); - - if (game.is (GameFlags::Legacy)) { - return; - } - - String line; - MemFile file; - - // avatars inititalization - if (util.openConfig ("avatars.cfg", "Avatars config file not found. Avatars will not be displayed.", &file)) { - m_avatars.clear (); - - while (file.getLine (line)) { - if (isCommentLine (line)) { - continue; - } - m_avatars.push (cr::move (line.trim ())); - } - } -} - -void BotConfig::loadDifficultyConfig () { - setupMemoryFiles (); - - String line; - MemFile file; - - // initialize defaults - m_difficulty[Difficulty::Noob] = { - { 0.8f, 1.0f }, 5, 0, 0 - }; - - m_difficulty[Difficulty::Easy] = { - { 0.6f, 0.8f }, 30, 10, 10 - }; - - m_difficulty[Difficulty::Normal] = { - { 0.4f, 0.6f }, 50, 30, 40 - }; - - m_difficulty[Difficulty::Hard] = { - { 0.2f, 0.4f }, 75, 60, 70 - }; - - m_difficulty[Difficulty::Expert] = { - { 0.1f, 0.2f }, 100, 90, 90 - }; - - // currently, mindelay, maxdelay, headprob, seenthruprob, heardthruprob - constexpr uint32 kMaxDifficultyValues = 5; - - // helper for parsing each level - auto parseLevel = [&] (int32 level, StringRef data) { - auto values = data.split (","); - - if (values.length () != kMaxDifficultyValues) { - logger.error ("Bad value for difficulty level #%d.", level); - return; - } - auto diff = &m_difficulty[level]; - - diff->reaction[0] = values[0].float_ (); - diff->reaction[1] = values[1].float_ (); - diff->headshotPct = values[2].int_ (); - diff->seenThruPct = values[3].int_ (); - diff->hearThruPct = values[4].int_ (); - }; - - // avatars inititalization - if (util.openConfig ("difficulty.cfg", "Difficulty config file not found. Defaults loaded.", &file)) { - - while (file.getLine (line)) { - if (isCommentLine (line)) { - continue; - } - auto items = line.split ("="); - - if (items.length () != 2) { - logger.error ("Error in difficulty config file syntax... Please correct all errors."); - continue; - } - auto key = items[0].trim (); - - // get our keys - if (key == "Noob") { - parseLevel (Difficulty::Noob, items[1]); - } - else if (key == "Easy") { - parseLevel (Difficulty::Easy, items[1]); - } - else if (key == "Normal") { - parseLevel (Difficulty::Normal, items[1]); - } - else if (key == "Hard") { - parseLevel (Difficulty::Hard, items[1]); - } - else if (key == "Expert") { - parseLevel (Difficulty::Expert, items[1]); - } - } - } -} - -void BotConfig::loadLogosConfig () { - setupMemoryFiles (); - - String line; - MemFile file; - - // logos inititalization - if (util.openConfig ("logos.cfg", "Logos config file not found. Loading defaults.", &file)) { - m_logos.clear (); - - while (file.getLine (line)) { - if (isCommentLine (line)) { - continue; - } - m_logos.push (cr::move (line.trim ())); - } - } - else { - m_logos = cr::move (String { "{biohaz;{graf003;{graf004;{graf005;{lambda06;{target;{hand1;{spit2;{bloodhand6;{foot_l;{foot_r" }.split (";")); - } -} - -void BotConfig::setupMemoryFiles () { - static bool setMemoryPointers = true; - - auto wrapLoadFile= [] (const char *filename, int *length) { - return engfuncs.pfnLoadFileForMe (filename, length); - }; - - auto wrapFreeFile = [] (void *buffer) { - engfuncs.pfnFreeFile (buffer); - }; - - if (setMemoryPointers) { - MemFileStorage::instance ().initizalize (wrapLoadFile, wrapFreeFile); - setMemoryPointers = false; - } -} - -BotName *BotConfig::pickBotName () { - if (m_botNames.empty ()) { - return nullptr; - } - - for (size_t i = 0; i < m_botNames.length () * 2; ++i) { - auto botName = &m_botNames.random (); - - if (botName->name.length () < 3 || botName->usedBy != -1) { - continue; - } - return botName; - } - return nullptr; -} - -void BotConfig::clearUsedName (Bot *bot) { - for (auto &name : m_botNames) { - if (name.usedBy == bot->index ()) { - name.usedBy = -1; - break; - } - } -} - -void BotConfig::initWeapons () { - m_weapons.clear (); - - // fill array with available weapons - m_weapons.emplace (Weapon::Knife, "weapon_knife", "knife.mdl", 0, 0, -1, -1, 0, 0, 0, 0, 0, 0, true ); - m_weapons.emplace (Weapon::USP, "weapon_usp", "usp.mdl", 500, 1, -1, -1, 1, 1, 2, 2, 0, 12, false ); - m_weapons.emplace (Weapon::Glock18, "weapon_glock18", "glock18.mdl", 400, 1, -1, -1, 1, 2, 1, 1, 0, 20, false ); - m_weapons.emplace (Weapon::Deagle, "weapon_deagle", "deagle.mdl", 650, 1, 2, 2, 1, 3, 4, 4, 2, 7, false ); - m_weapons.emplace (Weapon::P228, "weapon_p228", "p228.mdl", 600, 1, 2, 2, 1, 4, 3, 3, 0, 13, false ); - m_weapons.emplace (Weapon::Elite, "weapon_elite", "elite.mdl", 800, 1, 0, 0, 1, 5, 5, 5, 0, 30, false ); - m_weapons.emplace (Weapon::FiveSeven, "weapon_fiveseven", "fiveseven.mdl", 750, 1, 1, 1, 1, 6, 5, 5, 0, 20, false ); - m_weapons.emplace (Weapon::M3, "weapon_m3", "m3.mdl", 1700, 1, 2, -1, 2, 1, 1, 1, 0, 8, false ); - m_weapons.emplace (Weapon::XM1014, "weapon_xm1014", "xm1014.mdl", 3000, 1, 2, -1, 2, 2, 2, 2, 0, 7, false ); - m_weapons.emplace (Weapon::MP5, "weapon_mp5navy", "mp5.mdl", 1500, 1, 2, 1, 3, 1, 2, 2, 0, 30, true ); - m_weapons.emplace (Weapon::TMP, "weapon_tmp", "tmp.mdl", 1250, 1, 1, 1, 3, 2, 1, 1, 0, 30, true ); - m_weapons.emplace (Weapon::P90, "weapon_p90", "p90.mdl", 2350, 1, 2, 1, 3, 3, 4, 4, 0, 50, true ); - m_weapons.emplace (Weapon::MAC10, "weapon_mac10", "mac10.mdl", 1400, 1, 0, 0, 3, 4, 1, 1, 0, 30, true ); - m_weapons.emplace (Weapon::UMP45, "weapon_ump45", "ump45.mdl", 1700, 1, 2, 2, 3, 5, 3, 3, 0, 25, true ); - m_weapons.emplace (Weapon::AK47, "weapon_ak47", "ak47.mdl", 2500, 1, 0, 0, 4, 1, 2, 2, 2, 30, true ); - m_weapons.emplace (Weapon::SG552, "weapon_sg552", "sg552.mdl", 3500, 1, 0, -1, 4, 2, 4, 4, 2, 30, true ); - m_weapons.emplace (Weapon::M4A1, "weapon_m4a1", "m4a1.mdl", 3100, 1, 1, 1, 4, 3, 3, 3, 2, 30, true ); - m_weapons.emplace (Weapon::Galil, "weapon_galil", "galil.mdl", 2000, 1, 0, 0, 4, -1, 1, 1, 2, 35, true ); - m_weapons.emplace (Weapon::Famas, "weapon_famas", "famas.mdl", 2250, 1, 1, 1, 4, -1, 1, 1, 2, 25, true ); - m_weapons.emplace (Weapon::AUG, "weapon_aug", "aug.mdl", 3500, 1, 1, 1, 4, 4, 4, 4, 2, 30, true ); - m_weapons.emplace (Weapon::Scout, "weapon_scout", "scout.mdl", 2750, 1, 2, 0, 4, 5, 3, 2, 3, 10, false ); - m_weapons.emplace (Weapon::AWP, "weapon_awp", "awp.mdl", 4750, 1, 2, 0, 4, 6, 5, 6, 3, 10, false ); - m_weapons.emplace (Weapon::G3SG1, "weapon_g3sg1", "g3sg1.mdl", 5000, 1, 0, 2, 4, 7, 6, 6, 3, 20, false ); - m_weapons.emplace (Weapon::SG550, "weapon_sg550", "sg550.mdl", 4200, 1, 1, 1, 4, 8, 5, 5, 3, 30, false ); - m_weapons.emplace (Weapon::M249, "weapon_m249", "m249.mdl", 5750, 1, 2, 1, 5, 1, 1, 1, 2, 100, true ); - m_weapons.emplace (Weapon::Shield, "weapon_shield", "shield.mdl", 2200, 0, 1, 1, 8, -1, 8, 8, 0, 0, false ); - - // not needed actually, but cause too much refactoring for now. todo - m_weapons.emplace (0, "", "", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, false ); -} - -void BotConfig::adjustWeaponPrices () { - // elite price is 1000$ on older versions of cs... - if (!(game.is (GameFlags::Legacy))) { - return; - } - - for (auto &weapon : m_weapons) { - if (weapon.id == Weapon::Elite) { - weapon.price = 1000; - break; - } - } -} - -WeaponInfo &BotConfig::findWeaponById (const int id) { - for (auto &weapon : m_weapons) { - if (weapon.id == id) { - return weapon; - } - } - return m_weapons.at (0); -} - -const char *BotConfig::translate (StringRef input) { - // this function translate input string into needed language - - if (game.isDedicated ()) { - return input.chars (); - } - static String result; - result.clear (); - - if (m_language.find (input, result)) { - return result.chars (); - } - return input.chars (); // nothing found -} \ No newline at end of file diff --git a/vc/yapb.vcxproj b/vc/yapb.vcxproj index 2aed5ad..85a3daf 100644 --- a/vc/yapb.vcxproj +++ b/vc/yapb.vcxproj @@ -57,6 +57,7 @@ + diff --git a/vc/yapb.vcxproj.filters b/vc/yapb.vcxproj.filters index 1edea0e..096cbba 100644 --- a/vc/yapb.vcxproj.filters +++ b/vc/yapb.vcxproj.filters @@ -179,6 +179,9 @@ src + + src +