From c03d8ac79023ee7256412de905d65ba7934b54f1 Mon Sep 17 00:00:00 2001 From: jeefo Date: Tue, 19 Mar 2024 23:38:48 +0300 Subject: [PATCH] fix: bots can't plant bomb on multi-scenario maps (fix #537) --- .gitattributes | 14 +- src/storage.cpp | 898 ++++++++++++++++++++++++------------------------ src/tasks.cpp | 10 +- 3 files changed, 462 insertions(+), 460 deletions(-) diff --git a/.gitattributes b/.gitattributes index 3ae18b9..02b39b3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,7 @@ -# Auto detect text files and perform LF normalization -* text=auto - -# Custom for Visual Studio -*.sln merge=union -*.vcxproj merge=union - +# Auto detect text files and perform LF normalization +* text=lf + +# Custom for Visual Studio +*.sln merge=union +*.vcxproj merge=union + diff --git a/src/storage.cpp b/src/storage.cpp index de0b422..3776235 100644 --- a/src/storage.cpp +++ b/src/storage.cpp @@ -1,449 +1,449 @@ -// -// YaPB, based on PODBot by Markus Klinge ("CountFloyd"). -// Copyright © YaPB Project Developers . -// -// SPDX-License-Identifier: MIT -// - -#include - -#if defined (BOT_STORAGE_EXPLICIT_INSTANTIATIONS) - -template bool BotStorage::load (SmallArray &data, ExtenHeader *exten, int32_t *outOptions) { - auto type = guessType (); - String filename = buildPath (storageToBotFile (type.option), true); - - extern ConVar cv_debug, cv_graph_url; - - // graphs can be downloaded... - const auto isGraph = !!(type.option & StorageOption::Graph); - const auto isDebug = cv_debug.bool_ (); - - MemFile file (filename); // open the file - data.clear (); - - // resize data to fit the stuff - auto resizeData = [&] (const size_t length) { - data.resize (length); // for non-graph data the graph should be already loaded - data.shrink (); // free up memory to minimum - - // ensure we're have enough memory to decompress the data - data.ensure (length + ULZ::Excess); - }; - - // if graph & attempted to load multiple times, bail out, we're failed - if (isGraph && ++m_retries > 2) { - resetRetries (); - - return error (isGraph, isDebug, file, "Unable to load %s (filename: '%s'). Download process has failed as well. No nodes has been found.", type.name, filename); - } - - // downloader for graph - auto download = [&] () -> bool { - if (!graph.canDownload ()) { - return false; - } - String lowercaseMapName = game.getMapName (); - lowercaseMapName = lowercaseMapName.lowercase (); - - auto downloadAddress = cv_graph_url.str (); - - auto toDownload = buildPath (storageToBotFile (type.option), false); - auto fromDownload = strings.format ("%s://%s/graph/%s.graph", product.httpScheme, downloadAddress, lowercaseMapName); - - // try to download - if (http.downloadFile (fromDownload, toDownload)) { - ctrl.msg ("%s file '%s' successfully downloaded. Processing...", type.name, filename); - return true; - } - else { - ctrl.msg ("Can't download '%s' from '%s' to '%s'... (%d).", filename, fromDownload, toDownload, http.getLastStatusCode ()); - } - return false; - }; - - // tries to reload or open pwf file - auto tryReload = [&] () -> bool { - file.close (); - - if (!isGraph) { - return false; - } - - if (download ()) { - return load (data, exten, outOptions); - } - - if (graph.convertOldFormat ()) { - return load (data, exten, outOptions); - } - return false; - }; - - // no open no fun - if (!file) { - if (tryReload ()) { - return true; - } - return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename); - } - - // read the header - StorageHeader hdr {}; - file.read (&hdr, sizeof (StorageHeader)); - - // check the magic - if (hdr.magic != kStorageMagic && hdr.magic != kStorageMagicUB) { - if (tryReload ()) { - return true; - } - return error (isGraph, isDebug, file, "Unable to read magic of %s (filename: '%s').", type.name, filename); - } - - // check the path-numbers - if (!isGraph && hdr.length != graph.length ()) { - return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Mismatch number of nodes (got: '%d', need: '%d').", type.name, filename, hdr.length, graph.length ()); - } - - // check the count - if (hdr.length == 0 || hdr.length > kMaxNodes || hdr.length < kMaxNodeLinks) { - if (tryReload ()) { - return true; - } - return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Paths length is overflowed (got: '%d').", type.name, filename, hdr.length); - } - - // check the version - if (hdr.version > type.version && isGraph) { - ctrl.msg ("Graph version mismatch %s (filename: '%s'). Version number differs (got: '%d', need: '%d') Please, upgrade %s.", type.name, filename, hdr.version, type.version, product.name); - } - else if (hdr.version != type.version && !isGraph) { - return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Version number differs (got: '%d', need: '%d').", type.name, filename, hdr.version, type.version); - } - - // save graph version - if (isGraph) { - graph.setGraphHeader (&hdr); - } - - // check the storage type - if ((hdr.options & type.option) != type.option) { - return error (isGraph, isDebug, file, "Incorrect storage format for %s (filename: '%s').", type.name, filename); - } - const auto compressedSize = static_cast (hdr.compressed); - const auto numberNodes = static_cast (hdr.length); - - SmallArray compressed (compressedSize + sizeof (uint8_t) * ULZ::Excess); - - // graph is not resized upon load - if (isGraph) { - resizeData (numberNodes); - } - else { - resizeData (hdr.uncompressed / sizeof (U)); - } - - // read compressed data - if (file.read (compressed.data (), sizeof (uint8_t), compressedSize) == compressedSize) { - - // try to uncompress - if (ulz.uncompress (compressed.data (), hdr.compressed, reinterpret_cast (data.data ()), hdr.uncompressed) == ULZ::UncompressFailure) { - return error (isGraph, isDebug, file, "Unable to decompress ULZ data for %s (filename: '%s').", type.name, filename); - } - else { - - if (outOptions) { - outOptions = &hdr.options; - } - - // author of graph.. save - if ((hdr.options & StorageOption::Exten) && exten != nullptr) { - const auto extenSize = sizeof (ExtenHeader); - const auto actuallyRead = file.read (exten, extenSize) * extenSize; - - if (isGraph) { - resetRetries (); - - ExtenHeader extenHeader; - strings.copy (extenHeader.author, exten->author, cr::bufsize (exten->author)); - - if (extenSize <= actuallyRead) { - // write modified by, only if the name is different - if (!strings.isEmpty (extenHeader.author) - && strncmp (extenHeader.author, exten->modified, cr::bufsize (extenHeader.author)) != 0) { - - strings.copy (extenHeader.modified, exten->modified, cr::bufsize (exten->modified)); - } - } - else { - strings.copy (extenHeader.modified, "(none)", cr::bufsize (exten->modified)); - } - extenHeader.mapSize = exten->mapSize; - - // tell graph about exten header - graph.setExtenHeader (&extenHeader); - } - } - ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f); - file.close (); - - return true; - } - } - else { - return error (isGraph, isDebug, file, "Unable to read ULZ data for %s (filename: '%s').", type.name, filename); - } - return false; -} - -template bool BotStorage::save (const SmallArray &data, ExtenHeader *exten, int32_t passOptions) { - auto type = guessType (); - - // append additional options - if (passOptions != 0) { - type.option |= passOptions; - } - const auto isGraph = !!(type.option & StorageOption::Graph); - - // do not allow to save graph with less than 8 nodes - if (isGraph && graph.length () < kMaxNodeLinks) { - ctrl.msg ("Can't save graph data with less than %d nodes. Please add some more before saving.", kMaxNodeLinks); - return false; - } - String filename = buildPath (storageToBotFile (type.option)); - - if (data.empty ()) { - logger.error ("Unable to save %s file. Empty data. (filename: '%s').", type.name, filename); - return false; - } - else if (isGraph) { - for (auto &path : graph) { - path.display = 0.0f; - path.light = illum.getLightLevel (path.origin); - } - } - - // open the file - File file (filename, "wb"); - - // no open no fun - if (!file) { - logger.error ("Unable to open %s file for writing (filename: '%s').", type.name, filename); - return false; - } - const auto rawLength = data.length () * sizeof (U); - SmallArray compressed (rawLength + sizeof (uint8_t) * ULZ::Excess); - - // try to compress - const auto compressedLength = static_cast (ulz.compress (reinterpret_cast (data.data ()), static_cast (rawLength), reinterpret_cast (compressed.data ()))); - - if (compressedLength > 0) { - StorageHeader hdr {}; - - hdr.magic = kStorageMagic; - hdr.version = type.version; - hdr.options = type.option; - hdr.length = graph.length (); - hdr.compressed = static_cast (compressedLength); - hdr.uncompressed = static_cast (rawLength); - - file.write (&hdr, sizeof (StorageHeader)); - file.write (compressed.data (), sizeof (uint8_t), compressedLength); - - // add extension - if ((type.option & StorageOption::Exten) && exten != nullptr) { - file.write (exten, sizeof (ExtenHeader)); - } - extern ConVar cv_debug; - - // notify only about graph - if (isGraph || cv_debug.bool_ ()) { - ctrl.msg ("Successfully saved Bots %s data.", type.name); - } - } - else { - logger.error ("Unable to compress %s data (filename: '%s').", type.name, filename); - return false; - } - return true; -} - -template bool BotStorage::error (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args) { - auto result = strings.format (fmt, cr::forward (args)...); - - // display error only for graph file - if (isGraph || isDebug) { - logger.error (result); - } - - // if graph reset paths - if (isGraph) { - bots.kickEveryone (true); - graph.reset (); - } - file.close (); - - return false; -} - -template BotStorage::SaveLoadData BotStorage::guessType () { - if constexpr (cr::is_same ::value) { - return { "Pathmatrix", StorageOption::Matrix, StorageVersion::Matrix }; - } - else if constexpr (cr::is_same ::value) { - return { "Practice", StorageOption::Practice, StorageVersion::Practice }; - } - else if constexpr (cr::is_same ::value) { - return { "Vistable", StorageOption::Vistable, StorageVersion::Vistable }; - } - else if constexpr (cr::is_same ::value) { - return { "Graph", StorageOption::Graph, StorageVersion::Graph }; - } -} - -#else - -String BotStorage::buildPath (int32_t file, bool isMemoryLoad, bool withoutMapName) { - using FilePath = Twin ; - - static HashMap paths = { - { BotFile::Vistable, FilePath (folders.train, "vis")}, - { BotFile::Practice, FilePath (folders.train, "prc")}, - { BotFile::Pathmatrix, FilePath (folders.train, "pmx")}, - { BotFile::LogFile, FilePath (folders.logs, "txt")}, - { BotFile::Graph, FilePath (folders.graph, "graph")}, - { BotFile::PodbotPWF, FilePath (folders.podbot, "pwf")}, - { BotFile::EbotEWP, FilePath (folders.ebot, "ewp")}, - }; - - static StringArray path; - path.clear (); - - // if not memory file we're don't need game dir - if (isMemoryLoad) { - path.emplace (getRunningPathVFS ()); - } - else { - path.emplace (getRunningPath ()); - } - - // the datadir - path.emplace (folders.data); - - // append real filepath - path.emplace (paths[file].first); - - // if file is logfile use correct logfile name with date - if (file == BotFile::LogFile) { - time_t ticks = time (&ticks); - tm timeinfo {}; - - plat.loctime (&timeinfo, &ticks); - auto timebuf = strings.chars (); - - strftime (timebuf, StringBuffer::StaticBufferSize, "L%d%m%Y", &timeinfo); - path.emplace (strings.format ("%s_%s.%s", product.nameLower, timebuf, paths[file].second)); - } - else if (!withoutMapName) { - String mapName = game.getMapName (); - path.emplace (strings.format ("%s.%s", mapName.lowercase (), paths[file].second)); - } - - // finally use correct path separators for us - return String::join (path, kPathSeparator); -} - -int32_t BotStorage::storageToBotFile (int32_t options) { - // converts storage option to storage filename - - if (options & StorageOption::Graph) { - return BotFile::Graph; - } - else if (options & StorageOption::Matrix) { - return BotFile::Pathmatrix; - } - else if (options & StorageOption::Vistable) { - return BotFile::Vistable; - } - else if (options & StorageOption::Practice) { - return BotFile::Practice; - } - return BotFile::Graph; -} - -void BotStorage::unlinkFromDisk () { - // this function removes graph file from the hard disk - - StringArray unlinkable; - bots.kickEveryone (true); - - // if we're delete graph, delete all corresponding to it files - unlinkable.emplace (buildPath (BotFile::Graph)); // graph itself - unlinkable.emplace (buildPath (BotFile::Practice)); // corresponding to practice - unlinkable.emplace (buildPath (BotFile::Vistable)); // corresponding to vistable - unlinkable.emplace (buildPath (BotFile::Pathmatrix)); // corresponding to matrix - - for (const auto &item : unlinkable) { - if (plat.fileExists (item.chars ())) { - plat.removeFile (item.chars ()); - ctrl.msg ("File %s, has been deleted from the hard disk", item); - } - else { - logger.error ("Unable to open %s", item); - } - } - graph.reset (); // re-initialize points -} - -StringRef BotStorage::getRunningPath () { - // this function get's relative path against bot library (bot library should reside in bin dir) - - static String path; - - // we're do not do relative (against bot's library) paths on android - if (plat.android) { - if (path.empty ()) { - path = strings.joinPath (game.getRunningModName (), folders.addons, folders.bot); - } - return path; - } - - // compute the full path to the our folder - if (path.empty ()) { - path = SharedLibrary::path (&bstor); - - if (path.startsWith (". +// +// SPDX-License-Identifier: MIT +// + +#include + +#if defined (BOT_STORAGE_EXPLICIT_INSTANTIATIONS) + +template bool BotStorage::load (SmallArray &data, ExtenHeader *exten, int32_t *outOptions) { + auto type = guessType (); + String filename = buildPath (storageToBotFile (type.option), true); + + extern ConVar cv_debug, cv_graph_url; + + // graphs can be downloaded... + const auto isGraph = !!(type.option & StorageOption::Graph); + const auto isDebug = cv_debug.bool_ (); + + MemFile file (filename); // open the file + data.clear (); + + // resize data to fit the stuff + auto resizeData = [&] (const size_t length) { + data.resize (length); // for non-graph data the graph should be already loaded + data.shrink (); // free up memory to minimum + + // ensure we're have enough memory to decompress the data + data.ensure (length + ULZ::Excess); + }; + + // if graph & attempted to load multiple times, bail out, we're failed + if (isGraph && ++m_retries > 2) { + resetRetries (); + + return error (isGraph, isDebug, file, "Unable to load %s (filename: '%s'). Download process has failed as well. No nodes has been found.", type.name, filename); + } + + // downloader for graph + auto download = [&] () -> bool { + if (!graph.canDownload ()) { + return false; + } + String lowercaseMapName = game.getMapName (); + lowercaseMapName = lowercaseMapName.lowercase (); + + auto downloadAddress = cv_graph_url.str (); + + auto toDownload = buildPath (storageToBotFile (type.option), false); + auto fromDownload = strings.format ("%s://%s/graph/%s.graph", product.httpScheme, downloadAddress, lowercaseMapName); + + // try to download + if (http.downloadFile (fromDownload, toDownload)) { + ctrl.msg ("%s file '%s' successfully downloaded. Processing...", type.name, filename); + return true; + } + else { + ctrl.msg ("Can't download '%s' from '%s' to '%s'... (%d).", filename, fromDownload, toDownload, http.getLastStatusCode ()); + } + return false; + }; + + // tries to reload or open pwf file + auto tryReload = [&] () -> bool { + file.close (); + + if (!isGraph) { + return false; + } + + if (download ()) { + return load (data, exten, outOptions); + } + + if (graph.convertOldFormat ()) { + return load (data, exten, outOptions); + } + return false; + }; + + // no open no fun + if (!file) { + if (tryReload ()) { + return true; + } + return error (isGraph, isDebug, file, "Unable to open %s file for reading (filename: '%s').", type.name, filename); + } + + // read the header + StorageHeader hdr {}; + file.read (&hdr, sizeof (StorageHeader)); + + // check the magic + if (hdr.magic != kStorageMagic && hdr.magic != kStorageMagicUB) { + if (tryReload ()) { + return true; + } + return error (isGraph, isDebug, file, "Unable to read magic of %s (filename: '%s').", type.name, filename); + } + + // check the path-numbers + if (!isGraph && hdr.length != graph.length ()) { + return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Mismatch number of nodes (got: '%d', need: '%d').", type.name, filename, hdr.length, graph.length ()); + } + + // check the count + if (hdr.length == 0 || hdr.length > kMaxNodes || hdr.length < kMaxNodeLinks) { + if (tryReload ()) { + return true; + } + return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Paths length is overflowed (got: '%d').", type.name, filename, hdr.length); + } + + // check the version + if (hdr.version > type.version && isGraph) { + ctrl.msg ("Graph version mismatch %s (filename: '%s'). Version number differs (got: '%d', need: '%d') Please, upgrade %s.", type.name, filename, hdr.version, type.version, product.name); + } + else if (hdr.version != type.version && !isGraph) { + return error (isGraph, isDebug, file, "Damaged %s (filename: '%s'). Version number differs (got: '%d', need: '%d').", type.name, filename, hdr.version, type.version); + } + + // save graph version + if (isGraph) { + graph.setGraphHeader (&hdr); + } + + // check the storage type + if ((hdr.options & type.option) != type.option) { + return error (isGraph, isDebug, file, "Incorrect storage format for %s (filename: '%s').", type.name, filename); + } + const auto compressedSize = static_cast (hdr.compressed); + const auto numberNodes = static_cast (hdr.length); + + SmallArray compressed (compressedSize + sizeof (uint8_t) * ULZ::Excess); + + // graph is not resized upon load + if (isGraph) { + resizeData (numberNodes); + } + else { + resizeData (hdr.uncompressed / sizeof (U)); + } + + // read compressed data + if (file.read (compressed.data (), sizeof (uint8_t), compressedSize) == compressedSize) { + + // try to uncompress + if (ulz.uncompress (compressed.data (), hdr.compressed, reinterpret_cast (data.data ()), hdr.uncompressed) == ULZ::UncompressFailure) { + return error (isGraph, isDebug, file, "Unable to decompress ULZ data for %s (filename: '%s').", type.name, filename); + } + else { + + if (outOptions) { + outOptions = &hdr.options; + } + + // author of graph.. save + if ((hdr.options & StorageOption::Exten) && exten != nullptr) { + const auto extenSize = sizeof (ExtenHeader); + const auto actuallyRead = file.read (exten, extenSize) * extenSize; + + if (isGraph) { + resetRetries (); + + ExtenHeader extenHeader; + strings.copy (extenHeader.author, exten->author, cr::bufsize (exten->author)); + + if (extenSize <= actuallyRead) { + // write modified by, only if the name is different + if (!strings.isEmpty (extenHeader.author) + && strncmp (extenHeader.author, exten->modified, cr::bufsize (extenHeader.author)) != 0) { + + strings.copy (extenHeader.modified, exten->modified, cr::bufsize (exten->modified)); + } + } + else { + strings.copy (extenHeader.modified, "(none)", cr::bufsize (exten->modified)); + } + extenHeader.mapSize = exten->mapSize; + + // tell graph about exten header + graph.setExtenHeader (&extenHeader); + } + } + ctrl.msg ("Loaded Bots %s data v%d (Memory: %.2fMB).", type.name, hdr.version, static_cast (data.capacity () * sizeof (U)) / 1024.0f / 1024.0f); + file.close (); + + return true; + } + } + else { + return error (isGraph, isDebug, file, "Unable to read ULZ data for %s (filename: '%s').", type.name, filename); + } + return false; +} + +template bool BotStorage::save (const SmallArray &data, ExtenHeader *exten, int32_t passOptions) { + auto type = guessType (); + + // append additional options + if (passOptions != 0) { + type.option |= passOptions; + } + const auto isGraph = !!(type.option & StorageOption::Graph); + + // do not allow to save graph with less than 8 nodes + if (isGraph && graph.length () < kMaxNodeLinks) { + ctrl.msg ("Can't save graph data with less than %d nodes. Please add some more before saving.", kMaxNodeLinks); + return false; + } + String filename = buildPath (storageToBotFile (type.option)); + + if (data.empty ()) { + logger.error ("Unable to save %s file. Empty data. (filename: '%s').", type.name, filename); + return false; + } + else if (isGraph) { + for (auto &path : graph) { + path.display = 0.0f; + path.light = illum.getLightLevel (path.origin); + } + } + + // open the file + File file (filename, "wb"); + + // no open no fun + if (!file) { + logger.error ("Unable to open %s file for writing (filename: '%s').", type.name, filename); + return false; + } + const auto rawLength = data.length () * sizeof (U); + SmallArray compressed (rawLength + sizeof (uint8_t) * ULZ::Excess); + + // try to compress + const auto compressedLength = static_cast (ulz.compress (reinterpret_cast (data.data ()), static_cast (rawLength), reinterpret_cast (compressed.data ()))); + + if (compressedLength > 0) { + StorageHeader hdr {}; + + hdr.magic = kStorageMagic; + hdr.version = type.version; + hdr.options = type.option; + hdr.length = graph.length (); + hdr.compressed = static_cast (compressedLength); + hdr.uncompressed = static_cast (rawLength); + + file.write (&hdr, sizeof (StorageHeader)); + file.write (compressed.data (), sizeof (uint8_t), compressedLength); + + // add extension + if ((type.option & StorageOption::Exten) && exten != nullptr) { + file.write (exten, sizeof (ExtenHeader)); + } + extern ConVar cv_debug; + + // notify only about graph + if (isGraph || cv_debug.bool_ ()) { + ctrl.msg ("Successfully saved Bots %s data.", type.name); + } + } + else { + logger.error ("Unable to compress %s data (filename: '%s').", type.name, filename); + return false; + } + return true; +} + +template bool BotStorage::error (bool isGraph, bool isDebug, MemFile &file, const char *fmt, Args &&...args) { + auto result = strings.format (fmt, cr::forward (args)...); + + // display error only for graph file + if (isGraph || isDebug) { + logger.error (result); + } + + // if graph reset paths + if (isGraph) { + bots.kickEveryone (true); + graph.reset (); + } + file.close (); + + return false; +} + +template BotStorage::SaveLoadData BotStorage::guessType () { + if constexpr (cr::is_same ::value) { + return { "Pathmatrix", StorageOption::Matrix, StorageVersion::Matrix }; + } + else if constexpr (cr::is_same ::value) { + return { "Practice", StorageOption::Practice, StorageVersion::Practice }; + } + else if constexpr (cr::is_same ::value) { + return { "Vistable", StorageOption::Vistable, StorageVersion::Vistable }; + } + else if constexpr (cr::is_same ::value) { + return { "Graph", StorageOption::Graph, StorageVersion::Graph }; + } +} + +#else + +String BotStorage::buildPath (int32_t file, bool isMemoryLoad, bool withoutMapName) { + using FilePath = Twin ; + + static HashMap paths = { + { BotFile::Vistable, FilePath (folders.train, "vis")}, + { BotFile::Practice, FilePath (folders.train, "prc")}, + { BotFile::Pathmatrix, FilePath (folders.train, "pmx")}, + { BotFile::LogFile, FilePath (folders.logs, "txt")}, + { BotFile::Graph, FilePath (folders.graph, "graph")}, + { BotFile::PodbotPWF, FilePath (folders.podbot, "pwf")}, + { BotFile::EbotEWP, FilePath (folders.ebot, "ewp")}, + }; + + static StringArray path; + path.clear (); + + // if not memory file we're don't need game dir + if (isMemoryLoad) { + path.emplace (getRunningPathVFS ()); + } + else { + path.emplace (getRunningPath ()); + } + + // the datadir + path.emplace (folders.data); + + // append real filepath + path.emplace (paths[file].first); + + // if file is logfile use correct logfile name with date + if (file == BotFile::LogFile) { + time_t ticks = time (&ticks); + tm timeinfo {}; + + plat.loctime (&timeinfo, &ticks); + auto timebuf = strings.chars (); + + strftime (timebuf, StringBuffer::StaticBufferSize, "L%d%m%Y", &timeinfo); + path.emplace (strings.format ("%s_%s.%s", product.nameLower, timebuf, paths[file].second)); + } + else if (!withoutMapName) { + String mapName = game.getMapName (); + path.emplace (strings.format ("%s.%s", mapName.lowercase (), paths[file].second)); + } + + // finally use correct path separators for us + return String::join (path, kPathSeparator); +} + +int32_t BotStorage::storageToBotFile (int32_t options) { + // converts storage option to storage filename + + if (options & StorageOption::Graph) { + return BotFile::Graph; + } + else if (options & StorageOption::Matrix) { + return BotFile::Pathmatrix; + } + else if (options & StorageOption::Vistable) { + return BotFile::Vistable; + } + else if (options & StorageOption::Practice) { + return BotFile::Practice; + } + return BotFile::Graph; +} + +void BotStorage::unlinkFromDisk () { + // this function removes graph file from the hard disk + + StringArray unlinkable; + bots.kickEveryone (true); + + // if we're delete graph, delete all corresponding to it files + unlinkable.emplace (buildPath (BotFile::Graph)); // graph itself + unlinkable.emplace (buildPath (BotFile::Practice)); // corresponding to practice + unlinkable.emplace (buildPath (BotFile::Vistable)); // corresponding to vistable + unlinkable.emplace (buildPath (BotFile::Pathmatrix)); // corresponding to matrix + + for (const auto &item : unlinkable) { + if (plat.fileExists (item.chars ())) { + plat.removeFile (item.chars ()); + ctrl.msg ("File %s, has been deleted from the hard disk", item); + } + else { + logger.error ("Unable to open %s", item); + } + } + graph.reset (); // re-initialize points +} + +StringRef BotStorage::getRunningPath () { + // this function get's relative path against bot library (bot library should reside in bin dir) + + static String path; + + // we're do not do relative (against bot's library) paths on android + if (plat.android) { + if (path.empty ()) { + path = strings.joinPath (game.getRunningModName (), folders.addons, folders.bot); + } + return path; + } + + // compute the full path to the our folder + if (path.empty ()) { + path = SharedLibrary::path (&bstor); + + if (path.startsWith ("origin); startTask (Task::Camp, TaskPri::Camp, kInvalidNodeIndex, game.time () + rg.get (60.0f, 120.0f), true); // push camp task on to stack @@ -193,7 +193,9 @@ void Bot::normal_ () { pushChatterMessage (Chatter::GoingToGuardVIPSafety); // play info about that } } - else if (game.mapIs (MapFlags::Demolition) && ((m_pathFlags & NodeFlag::Goal) && m_inBombZone)) { + + // was elseif here but brokes csde_ scenario + if (game.mapIs (MapFlags::Demolition) && (m_pathFlags & NodeFlag::Goal) && m_inBombZone) { // is it a terrorist carrying the bomb? if (m_hasC4) { if ((m_states & Sense::SeeingEnemy) && numFriendsNear (pev->origin, 768.0f) == 0) {