diff --git a/Readme_release.txt b/Readme_release.txt
index a136aeba..46561b3f 100644
--- a/Readme_release.txt
+++ b/Readme_release.txt
@@ -69,8 +69,8 @@ If you want to set custom ips (or domains) which the emulator will send broadcas
If the custom ips/domains are specific for one game only you can put the custom_broadcasts.txt in the steam_settings\ folder.
An example is provided in steam_settings.EXAMPLE\custom_broadcasts.EXAMPLE.txt
-Items or Inventory:
-Create a folder named steam_settings right beside steam_api.dll if there isn't one already. In that folder, create a file named items.json which will contain every item you want to have in your game.
+Achievements, Items or Inventory:
+Create a folder named steam_settings right beside steam_api.dll if there isn't one already. In that folder, create a file named items.json and/or achievements.json which will contain every item/achievement you want to have in your game.
An example can be found in steam_settings.EXAMPLE that works with Killing Floor 2.
The items.json syntax is simple, you SHOULD validate your .json file before trying to run your game or you won't have any item in your inventory. Just look for "online json validator" on your web brower to valide your file.
You can use https://steamdb.info/ to list items and attributes they have and put them into your .json.
diff --git a/build_env_x64.bat b/build_env_x64.bat
old mode 100644
new mode 100755
diff --git a/build_env_x86.bat b/build_env_x86.bat
old mode 100644
new mode 100755
diff --git a/build_linux.sh b/build_linux.sh
old mode 100644
new mode 100755
diff --git a/build_set_protobuf_directories.bat b/build_set_protobuf_directories.bat
old mode 100644
new mode 100755
diff --git a/build_steamos.sh b/build_steamos.sh
old mode 100644
new mode 100755
diff --git a/build_win_debug_experimental.bat b/build_win_debug_experimental.bat
old mode 100644
new mode 100755
diff --git a/build_win_find_interfaces.bat b/build_win_find_interfaces.bat
old mode 100644
new mode 100755
diff --git a/build_win_lobby_connect.bat b/build_win_lobby_connect.bat
old mode 100644
new mode 100755
diff --git a/build_win_release.bat b/build_win_release.bat
old mode 100644
new mode 100755
diff --git a/build_win_release_experimental.bat b/build_win_release_experimental.bat
old mode 100644
new mode 100755
diff --git a/dll/item_db_loader.cpp b/dll/item_db_loader.cpp
deleted file mode 100644
index ee591059..00000000
--- a/dll/item_db_loader.cpp
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Copyright (C) 2019 Nemirtingas (Maxime P)
- This file is part of the Goldberg Emulator
-
- The Goldberg Emulator is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 3 of the License, or (at your option) any later version.
-
- The Goldberg Emulator 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the Goldberg Emulator; if not, see
- . */
-#include "item_db_loader.h"
-
-#include
-#include "../json/json.hpp"
-
-void read_items_db(std::string items_db, std::map> *items, std::atomic_bool *is_loadedb)
-{
- std::ifstream items_file(items_db);
- // If there is a file and we opened it
- if( items_file )
- {
- items_file.seekg(0, std::ios::end);
- size_t size = items_file.tellg();
- std::string buffer(size, '\0');
- items_file.seekg(0);
- // Read it entirely, if the .json file gets too big,
- // I should look into this and split reads into smaller parts.
- items_file.read(&buffer[0], size);
- items_file.close();
-
- try
- {
- std::map> tmp;
- nlohmann::json json = nlohmann::json::parse(buffer);
-
- for (auto& i : json.items())
- {
- SteamItemDef_t key = std::stoi((*i).key());
- nlohmann::json& value = (*i).value();
- for (auto& j : value.items())
- {
- tmp[key][(*j).key()] = (*j).value();
- }
- }
-
- items->swap(tmp);
- }
- catch (std::exception& e)
- {
- PRINT_DEBUG("Error while parsing json: %s\n", e.what());
- }
- }
-
- PRINT_DEBUG("Loaded json. Loaded %u items.\n", items->size());
- *is_loadedb = true;
-}
\ No newline at end of file
diff --git a/dll/item_db_loader.h b/dll/item_db_loader.h
deleted file mode 100644
index 89c09129..00000000
--- a/dll/item_db_loader.h
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Copyright (C) 2019 Nemirtingas (Maxime P)
- This file is part of the Goldberg Emulator
-
- The Goldberg Emulator is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 3 of the License, or (at your option) any later version.
-
- The Goldberg Emulator 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
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with the Goldberg Emulator;*/
-#ifndef __ITEM_DB_LOADER_INCLUDED__
-#define __ITEM_DB_LOADER_INCLUDED__
-
-#include "base.h" // For SteamItemDef_t
-
-#include
-void read_items_db(std::string items_db, std::map> *items, std::atomic_bool *is_loaded);
-
-#endif//__ITEM_DB_LOADER_INCLUDED__
\ No newline at end of file
diff --git a/dll/local_storage.cpp b/dll/local_storage.cpp
index d5d6d46c..33ead80b 100644
--- a/dll/local_storage.cpp
+++ b/dll/local_storage.cpp
@@ -20,6 +20,7 @@
#include
#include
#include
+#include
struct File_Data {
std::string name;
@@ -127,6 +128,16 @@ bool Local_Storage::update_save_filenames(std::string folder)
return true;
}
+bool Local_Storage::load_json_file(std::string folder, std::string const&file, nlohmann::json& json)
+{
+ return true;
+}
+
+bool Local_Storage::write_json_file(std::string folder, std::string const&file, nlohmann::json const& json)
+{
+ return true;
+}
+
std::vector Local_Storage::get_filenames_path(std::string path)
{
return std::vector();
@@ -378,7 +389,7 @@ std::string Local_Storage::get_program_path()
std::string Local_Storage::get_game_settings_path()
{
- return get_program_path().append(GAME_SETTINGS_FOLDER).append(PATH_SEPARATOR);
+ return get_program_path().append(game_settings_folder).append(PATH_SEPARATOR);
}
#if defined(STEAM_WIN32)
@@ -508,7 +519,7 @@ std::string Local_Storage::get_path(std::string folder)
std::string Local_Storage::get_global_settings_path()
{
- return save_directory + SETTINGS_STORAGE_FOLDER + PATH_SEPARATOR;
+ return save_directory + settings_storage_folder + PATH_SEPARATOR;
}
std::vector Local_Storage::get_filenames_path(std::string path)
@@ -680,4 +691,65 @@ bool Local_Storage::update_save_filenames(std::string folder)
return true;
}
+bool Local_Storage::load_json_file(std::string folder, std::string const&file, nlohmann::json& json)
+{
+ if (!folder.empty() && folder.back() != *PATH_SEPARATOR) {
+ folder.append(PATH_SEPARATOR);
+ }
+ std::string inv_path = std::move(save_directory + appid + folder);
+ std::string full_path = inv_path + file;
+
+ create_directory(inv_path);
+
+ std::ifstream inventory_file(full_path);
+ // If there is a file and we opened it
+ if (inventory_file)
+ {
+ inventory_file.seekg(0, std::ios::end);
+ size_t size = inventory_file.tellg();
+ std::string buffer(size, '\0');
+ inventory_file.seekg(0);
+ // Read it entirely, if the .json file gets too big,
+ // I should look into this and split reads into smaller parts.
+ inventory_file.read(&buffer[0], size);
+ inventory_file.close();
+
+ try {
+ json = std::move(nlohmann::json::parse(buffer));
+ PRINT_DEBUG("Loaded json \"%s\". Loaded %u items.\n", full_path.c_str(), json.size());
+ return true;
+ } catch (std::exception& e) {
+ PRINT_DEBUG("Error while parsing \"%s\" json: %s\n", full_path.c_str(), e.what());
+ }
+ }
+ else
+ {
+ PRINT_DEBUG("Couldn't open file \"%s\" to read json\n", full_path.c_str());
+ }
+
+ return false;
+}
+
+bool Local_Storage::write_json_file(std::string folder, std::string const&file, nlohmann::json const& json)
+{
+ if (!folder.empty() && folder.back() != *PATH_SEPARATOR) {
+ folder.append(PATH_SEPARATOR);
+ }
+ std::string inv_path = std::move(save_directory + appid + folder);
+ std::string full_path = inv_path + file;
+
+ create_directory(inv_path);
+
+ std::ofstream inventory_file(full_path, std::ios::trunc | std::ios::out);
+ if (inventory_file)
+ {
+ inventory_file << std::setw(2) << json;
+ return true;
+ }
+
+ PRINT_DEBUG("Couldn't open file \"%s\" to write json\n", full_path.c_str());
+
+ return false;
+}
+
#endif
diff --git a/dll/local_storage.h b/dll/local_storage.h
index fb22db73..3c550bdf 100644
--- a/dll/local_storage.h
+++ b/dll/local_storage.h
@@ -21,18 +21,21 @@
#ifndef LOCAL_STORAGE_INCLUDE
#define LOCAL_STORAGE_INCLUDE
-#define SETTINGS_STORAGE_FOLDER "settings"
-#define REMOTE_STORAGE_FOLDER "remote"
-#define STATS_STORAGE_FOLDER "stats"
-#define USER_DATA_FOLDER "local"
-
-#define GAME_SETTINGS_FOLDER "steam_settings"
-
#include
+#include "../json/json.hpp"
#define MAX_FILENAME_LENGTH 300
class Local_Storage {
+public:
+ static constexpr auto inventory_storage_folder = "inventory";
+ static constexpr auto settings_storage_folder = "settings";
+ static constexpr auto remote_storage_folder = "remote";
+ static constexpr auto stats_storage_folder = "stats";
+ static constexpr auto user_data_storage = "local";
+ static constexpr auto game_settings_folder = "steam_settings";
+
+private:
std::string save_directory;
std::string appid;
public:
@@ -59,6 +62,9 @@ public:
std::string get_path(std::string folder);
bool update_save_filenames(std::string folder);
+
+ bool load_json_file(std::string folder, std::string const& file, nlohmann::json& json);
+ bool write_json_file(std::string folder, std::string const& file, nlohmann::json const& json);
};
#endif
diff --git a/dll/settings_parser.cpp b/dll/settings_parser.cpp
index ca12e854..dc46f59d 100644
--- a/dll/settings_parser.cpp
+++ b/dll/settings_parser.cpp
@@ -217,7 +217,7 @@ uint32 create_localstorage_settings(Settings **settings_client_out, Settings **s
uint64 steam_id = 0;
bool generate_new = false;
//try to load steam id from game specific settings folder first
- if (local_storage->get_data(SETTINGS_STORAGE_FOLDER, "user_steam_id.txt", array_steam_id, sizeof(array_steam_id) - 1) > 0) {
+ if (local_storage->get_data(Local_Storage::settings_storage_folder, "user_steam_id.txt", array_steam_id, sizeof(array_steam_id) - 1) > 0) {
user_id = CSteamID((uint64)std::atoll(array_steam_id));
if (!user_id.IsValid()) {
generate_new = true;
diff --git a/dll/steam_client.cpp b/dll/steam_client.cpp
index f7b5fe77..9d82223f 100644
--- a/dll/steam_client.cpp
+++ b/dll/steam_client.cpp
@@ -43,7 +43,6 @@ Steam_Client::Steam_Client()
{
uint32 appid = create_localstorage_settings(&settings_client, &settings_server, &local_storage);
- std::string items_db_file_path = (Local_Storage::get_game_settings_path() + "items.json");
network = new Networking(settings_server->get_local_steam_id(), appid, settings_server->get_port(), &(settings_server->custom_broadcasts), settings_server->disable_networking);
@@ -72,7 +71,7 @@ Steam_Client::Steam_Client()
steam_music = new Steam_Music(callbacks_client);
steam_musicremote = new Steam_MusicRemote();
steam_HTMLsurface = new Steam_HTMLsurface(settings_client, network, callback_results_client, callbacks_client);
- steam_inventory = new Steam_Inventory(settings_client, callback_results_client, callbacks_client, run_every_runcb, items_db_file_path);
+ steam_inventory = new Steam_Inventory(settings_client, callback_results_client, callbacks_client, run_every_runcb, local_storage);
steam_video = new Steam_Video();
steam_parental = new Steam_Parental();
steam_networking_sockets = new Steam_Networking_Sockets(settings_client, network, callback_results_client, callbacks_client, run_every_runcb);
@@ -90,7 +89,7 @@ Steam_Client::Steam_Client()
steam_gameserverstats = new Steam_GameServerStats(settings_server, network, callback_results_server, callbacks_server);
steam_gameserver_networking = new Steam_Networking(settings_server, network, callbacks_server, run_every_runcb);
steam_gameserver_http = new Steam_HTTP(settings_server, network, callback_results_server, callbacks_server);
- steam_gameserver_inventory = new Steam_Inventory(settings_server, callback_results_server, callbacks_server, run_every_runcb, items_db_file_path);
+ steam_gameserver_inventory = new Steam_Inventory(settings_server, callback_results_server, callbacks_server, run_every_runcb, local_storage);
steam_gameserver_ugc = new Steam_UGC(settings_server, callback_results_server, callbacks_server);
steam_gameserver_apps = new Steam_Apps(settings_server, callback_results_server);
steam_gameserver_networking_sockets = new Steam_Networking_Sockets(settings_server, network, callback_results_server, callbacks_server, run_every_runcb);
diff --git a/dll/steam_inventory.h b/dll/steam_inventory.h
index 2eec7fba..f92585bb 100644
--- a/dll/steam_inventory.h
+++ b/dll/steam_inventory.h
@@ -15,8 +15,8 @@
License along with the Goldberg Emulator; if not, see
. */
-#include "item_db_loader.h"
-#include
+#include "base.h" // For SteamItemDef_t
+#include "../json/json.hpp"
struct Steam_Inventory_Requests {
double timeout = 0.1;
@@ -42,22 +42,25 @@ class Steam_Inventory :
public ISteamInventory002,
public ISteamInventory
{
+public:
+ static constexpr auto items_user_file = "items.json";
+ static constexpr auto items_default_file = "default_items.json";
+
+private:
class Settings *settings;
class SteamCallResults *callback_results;
class SteamCallBacks *callbacks;
class RunEveryRunCB *run_every_runcb;
+ class Local_Storage* local_storage;
std::vector inventory_requests;
- std::map> items;
- // Like typedefs
- using item_iterator = std::map>::iterator;
- using attr_iterator = std::map::iterator;
+ nlohmann::json defined_items;
+ nlohmann::json user_items;
- std::atomic_bool items_loaded;
- std::string items_db_file;
- std::once_flag load_items_flag;
+ bool inventory_loaded;
bool call_definition_update;
+ bool call_inventory_update;
bool definition_update_called;
bool full_update_called;
@@ -89,6 +92,69 @@ struct Steam_Inventory_Requests *get_inventory_result(SteamInventoryResult_t res
return &(*request);
}
+void read_items_db()
+{
+ std::string items_db_path = Local_Storage::get_game_settings_path() + items_user_file;
+ PRINT_DEBUG("Items file path: %s\n", items_db_path.c_str());
+ std::ifstream inventory_file(items_db_path);
+ // If there is a file and we opened it
+ if (inventory_file)
+ {
+ inventory_file.seekg(0, std::ios::end);
+ size_t size = inventory_file.tellg();
+ std::string buffer(size, '\0');
+ inventory_file.seekg(0);
+ // Read it entirely, if the .json file gets too big,
+ // I should look into this and split reads into smaller parts.
+ inventory_file.read(&buffer[0], size);
+ inventory_file.close();
+
+ try
+ {
+ defined_items = std::move(nlohmann::json::parse(buffer));
+ PRINT_DEBUG("Loaded inventory. Loaded %u items.\n", defined_items.size());
+ }
+ catch (std::exception& e)
+ {
+ PRINT_DEBUG("Error while parsing inventory json: %s\n", e.what());
+ }
+ }
+}
+
+void read_inventory_db()
+{
+ // If we havn't got any inventory
+ if (!local_storage->load_json_file("", items_user_file, user_items))
+ {
+ // Try to load a default one
+ std::string items_db_path = Local_Storage::get_game_settings_path() + items_default_file;
+ PRINT_DEBUG("Default items file path: %s\n", items_db_path.c_str());
+ std::ifstream inventory_file(items_db_path);
+ // If there is a file and we opened it
+ if (inventory_file)
+ {
+ inventory_file.seekg(0, std::ios::end);
+ size_t size = inventory_file.tellg();
+ std::string buffer(size, '\0');
+ inventory_file.seekg(0);
+ // Read it entirely, if the .json file gets too big,
+ // I should look into this and split reads into smaller parts.
+ inventory_file.read(&buffer[0], size);
+ inventory_file.close();
+
+ try
+ {
+ user_items = std::move(nlohmann::json::parse(buffer));
+ PRINT_DEBUG("Loaded default inventory. Loaded %u items.\n", user_items.size());
+ }
+ catch (std::exception& e)
+ {
+ PRINT_DEBUG("Error while parsing inventory json: %s\n", e.what());
+ }
+ }
+ }
+}
+
public:
static void run_every_runcb_cb(void *object)
@@ -99,21 +165,21 @@ static void run_every_runcb_cb(void *object)
obj->RunCallbacks();
}
-Steam_Inventory(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, std::string items_db_file_path)
+Steam_Inventory(class Settings *settings, class SteamCallResults *callback_results, class SteamCallBacks *callbacks, class RunEveryRunCB *run_every_runcb, class Local_Storage *local_storage):
+ settings(settings),
+ callback_results(callback_results),
+ callbacks(callbacks),
+ run_every_runcb(run_every_runcb),
+ local_storage(local_storage),
+ defined_items(nlohmann::json::object()),
+ user_items(nlohmann::json::object()),
+ inventory_loaded(false),
+ call_definition_update(false),
+ call_inventory_update(false),
+ definition_update_called(false),
+ full_update_called(false)
{
- items_db_file = items_db_file_path;
- PRINT_DEBUG("Items file path: %s\n", items_db_file.c_str());
- items_loaded = false;
-
- this->settings = settings;
- this->callbacks = callbacks;
- this->callback_results = callback_results;
- this->run_every_runcb = run_every_runcb;
this->run_every_runcb->add(&Steam_Inventory::run_every_runcb_cb, this);
-
- call_definition_update = false;
- definition_update_called = false;
- full_update_called = false;
}
~Steam_Inventory()
@@ -160,6 +226,7 @@ bool GetResultItems( SteamInventoryResult_t resultHandle,
struct Steam_Inventory_Requests *request = get_inventory_result(resultHandle);
if (!request) return false;
if (!request->result_done()) return false;
+ if (!inventory_loaded) return false;
if (pOutItemsArray != nullptr)
{
@@ -167,21 +234,35 @@ bool GetResultItems( SteamInventoryResult_t resultHandle,
if (request->full_query) {
// We end if we reached the end of items or the end of buffer
- for( auto i = items.begin(); i != items.end() && max_items; ++i, --max_items )
+ for( auto i = user_items.begin(); i != user_items.end() && max_items; ++i, --max_items )
{
- pOutItemsArray->m_iDefinition = i->first;
- pOutItemsArray->m_itemId = i->first;
- pOutItemsArray->m_unQuantity = 1;
+ pOutItemsArray->m_iDefinition = std::stoi(i.key());
+ pOutItemsArray->m_itemId = pOutItemsArray->m_iDefinition;
+ try
+ {
+ pOutItemsArray->m_unQuantity = i.value().get();
+ }
+ catch (...)
+ {
+ pOutItemsArray->m_unQuantity = 0;
+ }
pOutItemsArray->m_unFlags = k_ESteamItemNoTrade;
++pOutItemsArray;
}
- *punOutItemsArraySize = std::min(*punOutItemsArraySize, static_cast(items.size()));
+ *punOutItemsArraySize = std::min(*punOutItemsArraySize, static_cast(user_items.size()));
} else {
for (auto &itemid : request->instance_ids) {
if (!max_items) break;
pOutItemsArray->m_iDefinition = itemid;
pOutItemsArray->m_itemId = itemid;
- pOutItemsArray->m_unQuantity = 1;
+ try
+ {
+ pOutItemsArray->m_unQuantity = user_items[itemid].get();
+ }
+ catch (...)
+ {
+ pOutItemsArray->m_unQuantity = 0;
+ }
pOutItemsArray->m_unFlags = k_ESteamItemNoTrade;
++pOutItemsArray;
--max_items;
@@ -190,7 +271,7 @@ bool GetResultItems( SteamInventoryResult_t resultHandle,
}
else if (punOutItemsArraySize != nullptr)
{
- *punOutItemsArraySize = items.size();
+ *punOutItemsArraySize = user_items.size();
}
PRINT_DEBUG("GetResultItems good\n");
@@ -278,7 +359,7 @@ bool GetAllItems( SteamInventoryResult_t *pResultHandle )
std::lock_guard lock(global_mutex);
struct Steam_Inventory_Requests* request = new_inventory_result();
- if (!definition_update_called) call_definition_update = true;
+ call_inventory_update = true;
if (pResultHandle != nullptr)
*pResultHandle = request->inventory_result;
@@ -302,6 +383,7 @@ bool GetItemsByID( SteamInventoryResult_t *pResultHandle, STEAM_ARRAY_COUNT( unC
std::lock_guard lock(global_mutex);
if (pResultHandle) {
struct Steam_Inventory_Requests *request = new_inventory_result(false, pInstanceIDs, unCountInstanceIDs);
+ //call_inventory_update = true;
*pResultHandle = request->inventory_result;
return true;
}
@@ -559,15 +641,15 @@ bool GetItemDefinitionIDs(
if (pItemDefIDs == nullptr)
{
- *punItemDefIDsArraySize = items.size();
+ *punItemDefIDsArraySize = defined_items.size();
return true;
}
- if (*punItemDefIDsArraySize < items.size())
+ if (*punItemDefIDsArraySize < defined_items.size())
return false;
- for (auto& i : items)
- *pItemDefIDs++ = i.first;
+ for (auto i = defined_items.begin(); i != defined_items.end(); ++i)
+ *pItemDefIDs++ = std::stoi(i.key());
return true;
}
@@ -588,25 +670,39 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope
PRINT_DEBUG("GetItemDefinitionProperty %i %s\n", iDefinition, pchPropertyName);
std::lock_guard lock(global_mutex);
- item_iterator item;
- if ((item = items.find(iDefinition)) != items.end())
+ auto item = defined_items.find(std::to_string(iDefinition));
+ if (item != defined_items.end())
{
- attr_iterator attr;
if (pchPropertyName != nullptr)
{
// Should I check for punValueBufferSizeOut == nullptr ?
// Try to get the property
- if ((attr = item->second.find(pchPropertyName)) != items[iDefinition].end())
+ auto attr = item.value().find(pchPropertyName);
+ if (attr != item.value().end())
{
- std::string const& val = attr->second;
+ std::string val;
+ try
+ {
+ val = attr.value().get();
+ }
+ catch (...)
+ {
+ pchPropertyName = "";
+ *punValueBufferSizeOut = 0;
+ PRINT_DEBUG("Error, item: %d, attr: %s is not a string!", iDefinition, pchPropertyName);
+ return true;
+ }
if (pchValueBuffer != nullptr)
{
// copy what we can
strncpy(pchValueBuffer, val.c_str(), *punValueBufferSizeOut);
+ *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut);
+ }
+ else
+ {
+ // Set punValueBufferSizeOut to the property size
+ *punValueBufferSizeOut = val.length() + 1;
}
-
- // Set punValueBufferSizeOut to the property size
- *punValueBufferSizeOut = std::min(static_cast(val.length() + 1), *punValueBufferSizeOut);
if (pchValueBuffer != nullptr)
{
@@ -628,8 +724,8 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope
{
// Should I check for punValueBufferSizeOut == nullptr ?
*punValueBufferSizeOut = 0;
- for (auto& i : item->second)
- *punValueBufferSizeOut += i.first.length() + 1; // Size of key + comma, and the last is not a comma but null char
+ for (auto i = item.value().begin(); i != item.value().end(); ++i)
+ *punValueBufferSizeOut += i.key().length() + 1; // Size of key + comma, and the last is not a comma but null char
}
else
{
@@ -637,16 +733,16 @@ bool GetItemDefinitionProperty( SteamItemDef_t iDefinition, const char *pchPrope
uint32_t len = *punValueBufferSizeOut-1;
*punValueBufferSizeOut = 0;
memset(pchValueBuffer, 0, len);
- for( auto i = item->second.begin(); i != item->second.end() && len > 0; ++i )
+ for( auto i = item.value().begin(); i != item.value().end() && len > 0; ++i )
{
- strncat(pchValueBuffer, i->first.c_str(), len);
+ strncat(pchValueBuffer, i.key().c_str(), len);
// Count how many chars we copied
// Either the string length or the buffer size if its too small
- uint32 x = std::min(len, static_cast(i->first.length()));
+ uint32 x = std::min(len, static_cast(i.key().length()));
*punValueBufferSizeOut += x;
len -= x;
- if (len && std::distance(i, item->second.end()) != 1) // If this is not the last item, add a comma
+ if (len && std::distance(i, item.value().end()) != 1) // If this is not the last item, add a comma
strncat(pchValueBuffer, ",", len--);
// Always add 1, its a comma or the null terminator
@@ -781,24 +877,27 @@ bool SubmitUpdateProperties( SteamInventoryUpdateHandle_t handle, SteamInventory
void RunCallbacks()
{
- if (call_definition_update || inventory_requests.size()) {
- std::call_once(load_items_flag, [&]() {
- std::thread items_load_thread(read_items_db, items_db_file, &items, &items_loaded);
- items_load_thread.detach();
- });
+ if (call_definition_update && !definition_update_called) {
+ definition_update_called = true;
+ read_items_db();
+
+ SteamInventoryDefinitionUpdate_t data = {};
+ callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
+ call_definition_update = false;
}
- if (items_loaded) {
- if (call_definition_update) {
- SteamInventoryDefinitionUpdate_t data = {};
- callbacks->addCBResult(data.k_iCallback, &data, sizeof(data));
- call_definition_update = false;
- definition_update_called = true;
- }
+ if (call_inventory_update) {
+ read_inventory_db();
+ inventory_loaded = true;
+ call_definition_update = true;
+ call_inventory_update = false;
+ }
+
+ if (definition_update_called && inventory_loaded)
+ {
std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
-
- for (auto & r : inventory_requests) {
+ for (auto& r : inventory_requests) {
if (!r.done && std::chrono::duration_cast>(now - r.time_created).count() > r.timeout) {
if (r.full_query) {
if (!full_update_called) {
diff --git a/dll/steam_remote_storage.h b/dll/steam_remote_storage.h
index f6a0c518..7709b93c 100644
--- a/dll/steam_remote_storage.h
+++ b/dll/steam_remote_storage.h
@@ -69,7 +69,7 @@ Steam_Remote_Storage(class Settings *settings, Local_Storage *local_storage, cla
this->local_storage = local_storage;
this->callback_results = callback_results;
steam_cloud_enabled = true;
- local_storage->update_save_filenames(REMOTE_STORAGE_FOLDER);
+ local_storage->update_save_filenames(Local_Storage::remote_storage_folder);
}
// NOTE
@@ -88,7 +88,7 @@ bool FileWrite( const char *pchFile, const void *pvData, int32 cubData )
}
std::lock_guard lock(global_mutex);
- int data_stored = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData);
+ int data_stored = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData);
PRINT_DEBUG("Steam_Remote_Storage::Stored %i, %u\n", data_stored, data_stored == cubData);
return data_stored == cubData;
}
@@ -97,7 +97,7 @@ int32 FileRead( const char *pchFile, void *pvData, int32 cubDataToRead )
{
PRINT_DEBUG("Steam_Remote_Storage::FileRead %s %i\n", pchFile, cubDataToRead);
std::lock_guard lock(global_mutex);
- int read_data = local_storage->get_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubDataToRead);
+ int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubDataToRead);
if (read_data < 0) read_data = 0;
PRINT_DEBUG("Read %i\n", read_data);
return read_data;
@@ -112,8 +112,7 @@ SteamAPICall_t FileWriteAsync( const char *pchFile, const void *pvData, uint32 c
}
std::lock_guard lock(global_mutex);
- bool success = local_storage->store_data(REMOTE_STORAGE_FOLDER, pchFile, (char* )pvData, cubData) == cubData;
-
+ bool success = local_storage->store_data(Local_Storage::remote_storage_folder, pchFile, (char* )pvData, cubData) == cubData;
RemoteStorageFileWriteAsyncComplete_t data;
data.m_eResult = success ? k_EResultOK : k_EResultFail;
@@ -127,7 +126,7 @@ SteamAPICall_t FileReadAsync( const char *pchFile, uint32 nOffset, uint32 cubToR
PRINT_DEBUG("Steam_Remote_Storage::FileReadAsync\n");
std::lock_guard lock(global_mutex);
- unsigned int size = local_storage->file_size(REMOTE_STORAGE_FOLDER, pchFile);
+ unsigned int size = local_storage->file_size(Local_Storage::remote_storage_folder, pchFile);
RemoteStorageFileReadAsyncComplete_t data;
if (size <= nOffset) {
@@ -162,7 +161,7 @@ bool FileReadAsyncComplete( SteamAPICall_t hReadCall, void *pvBuffer, uint32 cub
return false;
char *temp = new char[a_read->size];
- int read_data = local_storage->get_data(REMOTE_STORAGE_FOLDER, a_read->file_name, (char* )temp, a_read->size);
+ int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, a_read->file_name, (char* )temp, a_read->size);
if (read_data < a_read->to_read + a_read->offset) {
delete[] temp;
return false;
@@ -184,7 +183,7 @@ bool FileForget( const char *pchFile )
bool FileDelete( const char *pchFile )
{
PRINT_DEBUG("Steam_Remote_Storage::FileDelete\n");
- return local_storage->file_delete(REMOTE_STORAGE_FOLDER, pchFile);
+ return local_storage->file_delete(Local_Storage::remote_storage_folder, pchFile);
}
STEAM_CALL_RESULT( RemoteStorageFileShareResult_t )
@@ -193,7 +192,7 @@ SteamAPICall_t FileShare( const char *pchFile )
PRINT_DEBUG("Steam_Remote_Storage::FileShare\n");
std::lock_guard lock(global_mutex);
RemoteStorageFileShareResult_t data = {};
- if (local_storage->file_exists(REMOTE_STORAGE_FOLDER, pchFile)) {
+ if (local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile)) {
data.m_eResult = k_EResultOK;
data.m_hFile = generate_steam_api_call_id();
strncpy(data.m_rgchFilename, pchFile, sizeof(data.m_rgchFilename) - 1);
@@ -246,7 +245,7 @@ bool FileWriteStreamClose( UGCFileWriteStreamHandle_t writeHandle )
if (stream_writes.end() == request)
return false;
- local_storage->store_data(REMOTE_STORAGE_FOLDER, request->file_name, request->file_data.data(), request->file_data.size());
+ local_storage->store_data(Local_Storage::remote_storage_folder, request->file_name, request->file_data.data(), request->file_data.size());
stream_writes.erase(request);
return true;
}
@@ -267,25 +266,25 @@ bool FileWriteStreamCancel( UGCFileWriteStreamHandle_t writeHandle )
bool FileExists( const char *pchFile )
{
PRINT_DEBUG("Steam_Remote_Storage::FileExists %s\n", pchFile);
- return local_storage->file_exists(REMOTE_STORAGE_FOLDER, pchFile);
+ return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile);
}
bool FilePersisted( const char *pchFile )
{
PRINT_DEBUG("Steam_Remote_Storage::FilePersisted\n");
- return local_storage->file_exists(REMOTE_STORAGE_FOLDER, pchFile);
+ return local_storage->file_exists(Local_Storage::remote_storage_folder, pchFile);
}
int32 GetFileSize( const char *pchFile )
{
PRINT_DEBUG("Steam_Remote_Storage::GetFileSize %s\n", pchFile);
- return local_storage->file_size(REMOTE_STORAGE_FOLDER, pchFile);
+ return local_storage->file_size(Local_Storage::remote_storage_folder, pchFile);
}
int64 GetFileTimestamp( const char *pchFile )
{
PRINT_DEBUG("Steam_Remote_Storage::GetFileTimestamp\n");
- return local_storage->file_timestamp(REMOTE_STORAGE_FOLDER, pchFile);
+ return local_storage->file_timestamp(Local_Storage::remote_storage_folder, pchFile);
}
ERemoteStoragePlatform GetSyncPlatforms( const char *pchFile )
@@ -299,7 +298,7 @@ ERemoteStoragePlatform GetSyncPlatforms( const char *pchFile )
int32 GetFileCount()
{
PRINT_DEBUG("Steam_Remote_Storage::GetFileCount\n");
- int32 num = local_storage->count_files(REMOTE_STORAGE_FOLDER);
+ int32 num = local_storage->count_files(Local_Storage::remote_storage_folder);
PRINT_DEBUG("Steam_Remote_Storage::File count: %i\n", num);
return num;
}
@@ -308,7 +307,7 @@ const char *GetFileNameAndSize( int iFile, int32 *pnFileSizeInBytes )
{
PRINT_DEBUG("Steam_Remote_Storage::GetFileNameAndSize %i\n", iFile);
static char output_filename[MAX_FILENAME_LENGTH];
- if (local_storage->iterate_file(REMOTE_STORAGE_FOLDER, iFile, output_filename, pnFileSizeInBytes)) {
+ if (local_storage->iterate_file(Local_Storage::remote_storage_folder, iFile, output_filename, pnFileSizeInBytes)) {
PRINT_DEBUG("Steam_Remote_Storage::Name: |%s|, size: %i\n", output_filename, pnFileSizeInBytes ? *pnFileSizeInBytes : 0);
return output_filename;
} else {
@@ -381,7 +380,7 @@ SteamAPICall_t UGCDownload( UGCHandle_t hContent, uint32 unPriority )
data.m_eResult = k_EResultOK;
data.m_hFile = hContent;
data.m_nAppID = settings->get_local_game_id().AppID();
- data.m_nSizeInBytes = local_storage->file_size(REMOTE_STORAGE_FOLDER, shared_files[hContent]);
+ data.m_nSizeInBytes = local_storage->file_size(Local_Storage::remote_storage_folder, shared_files[hContent]);
shared_files[hContent].copy(data.m_pchFileName, sizeof(data.m_pchFileName) - 1);
data.m_ulSteamIDOwner = settings->get_local_steam_id().ConvertToUint64();
downloaded_files[hContent].file = shared_files[hContent];
@@ -435,7 +434,7 @@ int32 UGCRead( UGCHandle_t hContent, void *pvData, int32 cubDataToRead, uint32 c
}
Downloaded_File f = downloaded_files[hContent];
- int read_data = local_storage->get_data(REMOTE_STORAGE_FOLDER, f.file, (char* )pvData, cubDataToRead, cOffset);
+ int read_data = local_storage->get_data(Local_Storage::remote_storage_folder, f.file, (char* )pvData, cubDataToRead, cOffset);
if (eAction == k_EUGCRead_Close || (eAction == k_EUGCRead_ContinueReadingUntilFinished && (read_data < cubDataToRead || (cOffset + cubDataToRead) >= f.total_size))) {
downloaded_files.erase(hContent);
diff --git a/dll/steam_user.h b/dll/steam_user.h
index 93cc4285..35875eca 100644
--- a/dll/steam_user.h
+++ b/dll/steam_user.h
@@ -149,7 +149,7 @@ bool GetUserDataFolder( char *pchBuffer, int cubBuffer )
PRINT_DEBUG("GetUserDataFolder\n");
if (!cubBuffer) return false;
- std::string user_data = local_storage->get_path(USER_DATA_FOLDER);
+ std::string user_data = local_storage->get_path(Local_Storage::user_data_storage);
strncpy(pchBuffer, user_data.c_str(), cubBuffer - 1);
pchBuffer[cubBuffer - 1] = 0;
return true;
diff --git a/dll/steam_user_stats.h b/dll/steam_user_stats.h
index 7bc9e9e8..98337c37 100644
--- a/dll/steam_user_stats.h
+++ b/dll/steam_user_stats.h
@@ -17,13 +17,16 @@
#include "base.h"
+#include
+#include
+#include "../json/json.hpp"
+
struct Steam_Leaderboard {
std::string name;
ELeaderboardSortMethod sort_method;
ELeaderboardDisplayType display_type;
};
-
class Steam_User_Stats :
public ISteamUserStats003,
public ISteamUserStats004,
@@ -35,12 +38,20 @@ public ISteamUserStats009,
public ISteamUserStats010,
public ISteamUserStats
{
+public:
+ static constexpr auto achievements_user_file = "achievements.json";
+
+private:
+
Local_Storage *local_storage;
Settings *settings;
SteamCallResults *callback_results;
class SteamCallBacks *callbacks;
std::vector leaderboards;
+ nlohmann::json defined_achievements;
+ nlohmann::json user_achievements;
+
unsigned int find_leaderboard(std::string name)
{
unsigned index = 1;
@@ -52,13 +63,50 @@ unsigned int find_leaderboard(std::string name)
return 0;
}
-public:
-Steam_User_Stats(Settings *settings, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks)
+void load_achievements_db()
{
- this->local_storage = local_storage;
- this->settings = settings;
- this->callback_results = callback_results;
- this->callbacks = callbacks;
+ std::string file_path = Local_Storage::get_game_settings_path() + achievements_user_file;
+ std::ifstream achs_file(file_path);
+
+ if (achs_file)
+ {
+ achs_file.seekg(0, std::ios::end);
+ size_t size = achs_file.tellg();
+ std::string buffer(size, '\0');
+ achs_file.seekg(0);
+ // Read it entirely, if the .json file gets too big,
+ // I should look into this and split reads into smaller parts.
+ achs_file.read(&buffer[0], size);
+ achs_file.close();
+
+ try {
+ defined_achievements = std::move(nlohmann::json::parse(buffer));
+ } catch (std::exception &e) {
+ PRINT_DEBUG("Error while parsing json: \"%s\" : %s\n", file_path.c_str(), e.what());
+ }
+ }
+ else
+ {
+ PRINT_DEBUG("Couldn't open file \"%s\" to read achievements definition\n", file_path.c_str());
+ }
+}
+
+void load_achievements()
+{
+ local_storage->load_json_file("", achievements_user_file, user_achievements);
+}
+
+public:
+Steam_User_Stats(Settings *settings, Local_Storage *local_storage, class SteamCallResults *callback_results, class SteamCallBacks *callbacks):
+ settings(settings),
+ local_storage(local_storage),
+ callback_results(callback_results),
+ callbacks(callbacks),
+ defined_achievements(nlohmann::json::object()),
+ user_achievements(nlohmann::json::object())
+{
+ load_achievements_db(); // achievements db
+ load_achievements(); // achievements per user
}
// Ask the server to send down this user's data and achievements for this game
@@ -89,7 +137,7 @@ bool GetStat( const char *pchName, int32 *pData )
if (stats_data->second.type != Stat_Type::STAT_TYPE_INT) return false;
}
- int read_data = local_storage->get_data(STATS_STORAGE_FOLDER, pchName, (char* )pData, sizeof(*pData));
+ int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, pchName, (char* )pData, sizeof(*pData));
if (read_data == sizeof(int32))
return true;
@@ -112,7 +160,7 @@ bool GetStat( const char *pchName, float *pData )
if (stats_data->second.type == Stat_Type::STAT_TYPE_INT) return false;
}
- int read_data = local_storage->get_data(STATS_STORAGE_FOLDER, pchName, (char* )pData, sizeof(*pData));
+ int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, pchName, (char* )pData, sizeof(*pData));
if (read_data == sizeof(float))
return true;
@@ -132,7 +180,7 @@ bool SetStat( const char *pchName, int32 nData )
if (!pchName) return false;
std::lock_guard lock(global_mutex);
- return local_storage->store_data(STATS_STORAGE_FOLDER, pchName, (char* )&nData, sizeof(nData)) == sizeof(nData);
+ return local_storage->store_data(Local_Storage::stats_storage_folder, pchName, (char* )&nData, sizeof(nData)) == sizeof(nData);
}
bool SetStat( const char *pchName, float fData )
@@ -141,7 +189,7 @@ bool SetStat( const char *pchName, float fData )
if (!pchName) return false;
std::lock_guard lock(global_mutex);
- return local_storage->store_data(STATS_STORAGE_FOLDER, pchName, (char* )&fData, sizeof(fData)) == sizeof(fData);
+ return local_storage->store_data(Local_Storage::stats_storage_folder, pchName, (char* )&fData, sizeof(fData)) == sizeof(fData);
}
bool UpdateAvgRateStat( const char *pchName, float flCountThisSession, double dSessionLength )
@@ -150,7 +198,7 @@ bool UpdateAvgRateStat( const char *pchName, float flCountThisSession, double dS
std::lock_guard lock(global_mutex);
char data[sizeof(float) + sizeof(float) + sizeof(double)];
- int read_data = local_storage->get_data(STATS_STORAGE_FOLDER, pchName, (char* )data, sizeof(*data));
+ int read_data = local_storage->get_data(Local_Storage::stats_storage_folder, pchName, (char* )data, sizeof(*data));
float oldcount = 0;
double oldsessionlength = 0;
if (read_data == sizeof(data)) {
@@ -166,28 +214,69 @@ bool UpdateAvgRateStat( const char *pchName, float flCountThisSession, double dS
memcpy(data + sizeof(float), &oldcount, sizeof(oldcount));
memcpy(data + sizeof(float) * 2, &oldsessionlength, sizeof(oldsessionlength));
- return local_storage->store_data(STATS_STORAGE_FOLDER, pchName, data, sizeof(data)) == sizeof(data);
+ return local_storage->store_data(Local_Storage::stats_storage_folder, pchName, data, sizeof(data)) == sizeof(data);
}
// Achievement flag accessors
bool GetAchievement( const char *pchName, bool *pbAchieved )
{
- //TODO: these achievement functions need to load a list of achievements from somewhere, return false so that kf2 doesn't loop endlessly
PRINT_DEBUG("GetAchievement %s\n", pchName);
- *pbAchieved = false;
+ if (pchName == nullptr) return false;
+
+ try {
+ auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName]( nlohmann::json &item ) {
+ return item["name"].get() == pchName;
+ });
+ auto ach = user_achievements.find(pchName);
+ if (it != defined_achievements.end() && ach != user_achievements.end()) {
+ if(pbAchieved != nullptr) *pbAchieved = (*ach)["earned"];
+ return true;
+ }
+ } catch (...) {}
+
+ if (pbAchieved != nullptr)* pbAchieved = false;
+
return false;
}
bool SetAchievement( const char *pchName )
{
PRINT_DEBUG("SetAchievement %s\n", pchName);
+ if (pchName == nullptr) return false;
+
+ try {
+ auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
+ return item["name"].get() == pchName;
+ });
+ if (it != defined_achievements.end()) {
+ if (user_achievements[pchName]["earned"] == false) {
+ user_achievements[pchName]["earned"] = true;
+ user_achievements[pchName]["earned_time"] = static_cast(std::time(nullptr));
+ }
+ return true;
+ }
+ } catch (...) {}
+
return false;
}
bool ClearAchievement( const char *pchName )
{
PRINT_DEBUG("ClearAchievement %s\n", pchName);
+ if (pchName == nullptr) return false;
+
+ try {
+ auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
+ return static_cast(item["name"]) == pchName;
+ });
+ if (it != defined_achievements.end()) {
+ user_achievements[pchName]["earned"] = false;
+ user_achievements[pchName]["earned_time"] = static_cast(0);
+ return true;
+ }
+ } catch (...) {}
+
return false;
}
@@ -198,7 +287,22 @@ bool ClearAchievement( const char *pchName )
bool GetAchievementAndUnlockTime( const char *pchName, bool *pbAchieved, uint32 *punUnlockTime )
{
PRINT_DEBUG("GetAchievementAndUnlockTime\n");
- *pbAchieved = false;
+ if (pchName == nullptr) return false;
+
+ try {
+ auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
+ return static_cast(item["name"]) == pchName;
+ });
+ auto ach = user_achievements.find(pchName);
+ if (it != defined_achievements.end() && ach != user_achievements.end()) {
+ if(pbAchieved != nullptr) *pbAchieved = (*ach)["earned"];
+ if(punUnlockTime != nullptr) *punUnlockTime = (*ach)["earned_time"];
+ return true;
+ }
+ } catch (...) {}
+
+ if(pbAchieved != nullptr) *pbAchieved = false;
+ if(punUnlockTime != nullptr) *punUnlockTime = 0;
return true;
}
@@ -215,6 +319,8 @@ bool StoreStats()
PRINT_DEBUG("StoreStats\n");
std::lock_guard lock(global_mutex);
+ local_storage->write_json_file("", achievements_user_file, user_achievements);
+
UserStatsStored_t data;
data.m_nGameID = settings->get_local_game_id().ToUint64();
data.m_eResult = k_EResultOK;
@@ -232,6 +338,8 @@ bool StoreStats()
int GetAchievementIcon( const char *pchName )
{
PRINT_DEBUG("GetAchievementIcon\n");
+ if (pchName == nullptr) return 0;
+
return 0;
}
@@ -242,18 +350,40 @@ int GetAchievementIcon( const char *pchName )
const char * GetAchievementDisplayAttribute( const char *pchName, const char *pchKey )
{
PRINT_DEBUG("GetAchievementDisplayAttribute %s %s\n", pchName, pchKey);
- return ""; //TODO
+ if (pchName == nullptr) return "";
+ if (pchKey == nullptr) return "";
if (strcmp (pchKey, "name") == 0) {
- return "Achievement Name";
+ try {
+ auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
+ return static_cast(item["name"]) == pchName;
+ });
+ if (it != defined_achievements.end()) {
+ return it.value()["displayName"].get().c_str();
+ }
+ } catch (...) {}
}
if (strcmp (pchKey, "desc") == 0) {
- return "Achievement Description";
+ try {
+ auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
+ return static_cast(item["name"]) == pchName;
+ });
+ if (it != defined_achievements.end()) {
+ return it.value()["description"].get().c_str();
+ }
+ } catch (...) {}
}
if (strcmp (pchKey, "hidden") == 0) {
- return "0";
+ try {
+ auto it = std::find_if(defined_achievements.begin(), defined_achievements.end(), [pchName](nlohmann::json& item) {
+ return static_cast(item["name"]) == pchName;
+ });
+ if (it != defined_achievements.end()) {
+ return it.value()["description"].get().c_str();
+ }
+ } catch (...) {}
}
return "";
@@ -265,6 +395,8 @@ const char * GetAchievementDisplayAttribute( const char *pchName, const char *pc
bool IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint32 nMaxProgress )
{
PRINT_DEBUG("IndicateAchievementProgress\n");
+ if (pchName == nullptr) return false;
+
}
@@ -273,13 +405,16 @@ bool IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint
uint32 GetNumAchievements()
{
PRINT_DEBUG("GetNumAchievements\n");
- return 0;
+ return defined_achievements.size();
}
// Get achievement name iAchievement in [0,GetNumAchievements)
const char * GetAchievementName( uint32 iAchievement )
{
PRINT_DEBUG("GetAchievementName\n");
+ try {
+ return defined_achievements[iAchievement]["name"].get().c_str();
+ } catch (...) {}
return "";
}
@@ -296,6 +431,12 @@ SteamAPICall_t RequestUserStats( CSteamID steamIDUser )
PRINT_DEBUG("Steam_User_Stats::RequestUserStats %llu\n", steamIDUser.ConvertToUint64());
std::lock_guard lock(global_mutex);
+ // Enable this to allow hot reload achievements status
+ //if (steamIDUser == settings->get_local_steam_id()) {
+ // load_achievements();
+ //}
+
+
UserStatsReceived_t data;
data.m_nGameID = settings->get_local_game_id().ToUint64();
data.m_eResult = k_EResultOK;
@@ -308,6 +449,8 @@ SteamAPICall_t RequestUserStats( CSteamID steamIDUser )
bool GetUserStat( CSteamID steamIDUser, const char *pchName, int32 *pData )
{
PRINT_DEBUG("GetUserStat %s %llu\n", pchName, steamIDUser.ConvertToUint64());
+ if (pchName == nullptr) return false;
+
std::lock_guard lock(global_mutex);
if (steamIDUser == settings->get_local_steam_id()) {
@@ -322,6 +465,8 @@ bool GetUserStat( CSteamID steamIDUser, const char *pchName, int32 *pData )
bool GetUserStat( CSteamID steamIDUser, const char *pchName, float *pData )
{
PRINT_DEBUG("GetUserStat %s %llu\n", pchName, steamIDUser.ConvertToUint64());
+ if (pchName == nullptr) return false;
+
std::lock_guard lock(global_mutex);
if (steamIDUser == settings->get_local_steam_id()) {
@@ -336,6 +481,12 @@ bool GetUserStat( CSteamID steamIDUser, const char *pchName, float *pData )
bool GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchieved )
{
PRINT_DEBUG("GetUserAchievement %s\n", pchName);
+ if (pchName == nullptr) return false;
+
+ if (steamIDUser == settings->get_local_steam_id()) {
+ return GetAchievement(pchName, pbAchieved);
+ }
+
return false;
}
@@ -343,6 +494,11 @@ bool GetUserAchievement( CSteamID steamIDUser, const char *pchName, bool *pbAchi
bool GetUserAchievementAndUnlockTime( CSteamID steamIDUser, const char *pchName, bool *pbAchieved, uint32 *punUnlockTime )
{
PRINT_DEBUG("GetUserAchievementAndUnlockTime %s\n", pchName);
+ if (pchName == nullptr) return false;
+
+ if (steamIDUser == settings->get_local_steam_id()) {
+ return GetAchievementAndUnlockTime(pchName, pbAchieved, punUnlockTime);
+ }
return false;
}
@@ -352,6 +508,13 @@ bool ResetAllStats( bool bAchievementsToo )
{
PRINT_DEBUG("ResetAllStats\n");
//TODO
+ if (bAchievementsToo) {
+ std::for_each(user_achievements.begin(), user_achievements.end(), [](nlohmann::json& v) {
+ v["earned"] = false;
+ v["earned_time"] = 0;
+ });
+ }
+
return true;
}
diff --git a/files_example/steam_settings.EXAMPLE/achievements_EXAMPLE.json b/files_example/steam_settings.EXAMPLE/achievements_EXAMPLE.json
new file mode 100644
index 00000000..b5788f64
--- /dev/null
+++ b/files_example/steam_settings.EXAMPLE/achievements_EXAMPLE.json
@@ -0,0 +1,2098 @@
+[
+ {
+ "description": "Complete Burning Paris on Survival Normal difficulty",
+ "displayName": "Tower Tussle",
+ "hidden": "0",
+ "icon": "images/Achievement_0.jpg",
+ "icongray": "images/Achievement_0_gray.jpg",
+ "name": "Achievement_0"
+ },
+ {
+ "description": "Complete Burning Paris on Survival Hard difficulty",
+ "displayName": "Seine Skirmish",
+ "hidden": "0",
+ "icon": "images/Achievement_1.jpg",
+ "icongray": "images/Achievement_1_gray.jpg",
+ "name": "Achievement_1"
+ },
+ {
+ "description": "Beat Burning Paris on Suicidal",
+ "displayName": "Bastille Brawl",
+ "hidden": "0",
+ "icon": "images/Achievement_2.jpg",
+ "icongray": "images/Achievement_2_gray.jpg",
+ "name": "Achievement_2"
+ },
+ {
+ "description": "Beat Burning Paris on Hell on Earth",
+ "displayName": "Arc Action",
+ "hidden": "0",
+ "icon": "images/Achievement_3.jpg",
+ "icongray": "images/Achievement_3_gray.jpg",
+ "name": "Achievement_3"
+ },
+ {
+ "description": "Beat Outpost on Normal",
+ "displayName": "You Can't Fight In Here, This Is The Control Room",
+ "hidden": "0",
+ "icon": "images/Achievement_4.jpg",
+ "icongray": "images/Achievement_4_gray.jpg",
+ "name": "Achievement_4"
+ },
+ {
+ "description": "Beat Outpost on Hard",
+ "displayName": "This Is What Happens When You Meet A Zed In The Alps",
+ "hidden": "0",
+ "icon": "images/Achievement_5.jpg",
+ "icongray": "images/Achievement_5_gray.jpg",
+ "name": "Achievement_5"
+ },
+ {
+ "description": "Beat Outpost on Suicidal",
+ "displayName": "The Shield Doors Must Be Closed",
+ "hidden": "0",
+ "icon": "images/Achievement_6.jpg",
+ "icongray": "images/Achievement_6_gray.jpg",
+ "name": "Achievement_6"
+ },
+ {
+ "description": "Beat Outpost on Hell on Earth",
+ "displayName": "Fear Is For The Zeds, My Little Lord",
+ "hidden": "0",
+ "icon": "images/Achievement_7.jpg",
+ "icongray": "images/Achievement_7_gray.jpg",
+ "name": "Achievement_7"
+ },
+ {
+ "description": "Beat Biotics Lab on Normal",
+ "displayName": "Open For Testing",
+ "hidden": "0",
+ "icon": "images/Achievement_8.jpg",
+ "icongray": "images/Achievement_8_gray.jpg",
+ "name": "Achievement_8"
+ },
+ {
+ "description": "Beat Biotics Lab on Hard",
+ "displayName": "Limited Contact",
+ "hidden": "0",
+ "icon": "images/Achievement_9.jpg",
+ "icongray": "images/Achievement_9_gray.jpg",
+ "name": "Achievement_9"
+ },
+ {
+ "description": "Beat Biotics Lab on Suicidal",
+ "displayName": "Restricted Access",
+ "hidden": "0",
+ "icon": "images/Achievement_10.jpg",
+ "icongray": "images/Achievement_10_gray.jpg",
+ "name": "Achievement_10"
+ },
+ {
+ "description": "Beat Biotics Lab on Hell on Earth",
+ "displayName": "Controlled Environment",
+ "hidden": "0",
+ "icon": "images/Achievement_11.jpg",
+ "icongray": "images/Achievement_11_gray.jpg",
+ "name": "Achievement_11"
+ },
+ {
+ "description": "Beat Volter Manor on Normal",
+ "displayName": "Just Visiting",
+ "hidden": "0",
+ "icon": "images/Achievement_12.jpg",
+ "icongray": "images/Achievement_12_gray.jpg",
+ "name": "Achievement_12"
+ },
+ {
+ "description": "Beat Volter Manor on Hard",
+ "displayName": "Mind Your Manor",
+ "hidden": "0",
+ "icon": "images/Achievement_13.jpg",
+ "icongray": "images/Achievement_13_gray.jpg",
+ "name": "Achievement_13"
+ },
+ {
+ "description": "Beat Volter Manor on Suicidal",
+ "displayName": "Settling In",
+ "hidden": "0",
+ "icon": "images/Achievement_14.jpg",
+ "icongray": "images/Achievement_14_gray.jpg",
+ "name": "Achievement_14"
+ },
+ {
+ "description": "Complete Volter Manor on Survival Hell On Earth difficulty",
+ "displayName": "Lord of the Manor",
+ "hidden": "0",
+ "icon": "images/Achievement_15.jpg",
+ "icongray": "images/Achievement_15_gray.jpg",
+ "name": "Achievement_15"
+ },
+ {
+ "description": "Collect all the items on Burning Paris",
+ "displayName": "Paris Plunder",
+ "hidden": "0",
+ "icon": "images/Achievement_16.jpg",
+ "icongray": "images/Achievement_16_gray.jpg",
+ "name": "Achievement_16"
+ },
+ {
+ "description": "Collect all the items on Outpost",
+ "displayName": "Outpost Offerings",
+ "hidden": "0",
+ "icon": "images/Achievement_17.jpg",
+ "icongray": "images/Achievement_17_gray.jpg",
+ "name": "Achievement_17"
+ },
+ {
+ "description": "Collect all the items on Biotics Lab",
+ "displayName": "Biotics Bling",
+ "hidden": "0",
+ "icon": "images/Achievement_18.jpg",
+ "icongray": "images/Achievement_18_gray.jpg",
+ "name": "Achievement_18"
+ },
+ {
+ "description": "Collect all the items on Volter Manor",
+ "displayName": "Manor Money",
+ "hidden": "0",
+ "icon": "images/Achievement_19.jpg",
+ "icongray": "images/Achievement_19_gray.jpg",
+ "name": "Achievement_19"
+ },
+ {
+ "description": "Complete Evacuation Point on Survival Normal difficulty",
+ "displayName": "The Suite Life",
+ "hidden": "0",
+ "icon": "images/Achievement_20.jpg",
+ "icongray": "images/Achievement_20_gray.jpg",
+ "name": "Achievement_20"
+ },
+ {
+ "description": "Complete Evacuation Point on Survival Hard difficulty",
+ "displayName": "Unsinkable II",
+ "hidden": "0",
+ "icon": "images/Achievement_21.jpg",
+ "icongray": "images/Achievement_21_gray.jpg",
+ "name": "Achievement_21"
+ },
+ {
+ "description": "Complete Evacuation Point on Survival Suicidal difficulty",
+ "displayName": "Bow Movement",
+ "hidden": "0",
+ "icon": "images/Achievement_22.jpg",
+ "icongray": "images/Achievement_22_gray.jpg",
+ "name": "Achievement_22"
+ },
+ {
+ "description": "Complete Evacuation Point on Survival Hell On Earth difficulty",
+ "displayName": "Seas The Day",
+ "hidden": "0",
+ "icon": "images/Achievement_23.jpg",
+ "icongray": "images/Achievement_23_gray.jpg",
+ "name": "Achievement_23"
+ },
+ {
+ "description": "Complete Catacombs on Survival Normal difficulty",
+ "displayName": "A Light In The Darkness",
+ "hidden": "0",
+ "icon": "images/Achievement_24.jpg",
+ "icongray": "images/Achievement_24_gray.jpg",
+ "name": "Achievement_24"
+ },
+ {
+ "description": "Complete Catacombs on Survival Hard difficulty",
+ "displayName": "This Is No Mine",
+ "hidden": "0",
+ "icon": "images/Achievement_25.jpg",
+ "icongray": "images/Achievement_25_gray.jpg",
+ "name": "Achievement_25"
+ },
+ {
+ "description": "Complete Catacombs on Survival Suicidal difficulty",
+ "displayName": "This Is A Tomb, Theirs",
+ "hidden": "0",
+ "icon": "images/Achievement_26.jpg",
+ "icongray": "images/Achievement_26_gray.jpg",
+ "name": "Achievement_26"
+ },
+ {
+ "description": "Complete Catacombs on Survival Hell On Earth difficulty",
+ "displayName": "They Shall Not Pass",
+ "hidden": "0",
+ "icon": "images/Achievement_27.jpg",
+ "icongray": "images/Achievement_27_gray.jpg",
+ "name": "Achievement_27"
+ },
+ {
+ "description": "Collect all the items on Evacuation Point",
+ "displayName": "Point Paper",
+ "hidden": "0",
+ "icon": "images/Achievement_28.jpg",
+ "icongray": "images/Achievement_28_gray.jpg",
+ "name": "Achievement_28"
+ },
+ {
+ "description": "Collect all the items on Catacombs",
+ "displayName": "Catacombs Cash",
+ "hidden": "0",
+ "icon": "images/Achievement_29.jpg",
+ "icongray": "images/Achievement_29_gray.jpg",
+ "name": "Achievement_29"
+ },
+ {
+ "description": "Reach Level 5 Berserker",
+ "displayName": "Reach Level 5 Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_30.jpg",
+ "icongray": "images/Achievement_30_gray.jpg",
+ "name": "Achievement_30"
+ },
+ {
+ "description": "Reach Level 10 Berserker",
+ "displayName": "Reach Level 10 Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_31.jpg",
+ "icongray": "images/Achievement_31_gray.jpg",
+ "name": "Achievement_31"
+ },
+ {
+ "description": "Reach Level 15 Berserker",
+ "displayName": "Reach Level 15 Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_32.jpg",
+ "icongray": "images/Achievement_32_gray.jpg",
+ "name": "Achievement_32"
+ },
+ {
+ "description": "Reach Level 20 Berserker",
+ "displayName": "Reach Level 20 Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_33.jpg",
+ "icongray": "images/Achievement_33_gray.jpg",
+ "name": "Achievement_33"
+ },
+ {
+ "description": "Reach Level 25 Berserker",
+ "displayName": "Reach Level 25 Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_34.jpg",
+ "icongray": "images/Achievement_34_gray.jpg",
+ "name": "Achievement_34"
+ },
+ {
+ "description": "Reach Level 5 Medic",
+ "displayName": "Reach Level 5 Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_35.jpg",
+ "icongray": "images/Achievement_35_gray.jpg",
+ "name": "Achievement_35"
+ },
+ {
+ "description": "Reach Level 10 Medic",
+ "displayName": "Reach Level 10 Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_36.jpg",
+ "icongray": "images/Achievement_36_gray.jpg",
+ "name": "Achievement_36"
+ },
+ {
+ "description": "Reach Level 15 Medic",
+ "displayName": "Reach Level 15 Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_37.jpg",
+ "icongray": "images/Achievement_37_gray.jpg",
+ "name": "Achievement_37"
+ },
+ {
+ "description": "Reach Level 20 Medic",
+ "displayName": "Reach Level 20 Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_38.jpg",
+ "icongray": "images/Achievement_38_gray.jpg",
+ "name": "Achievement_38"
+ },
+ {
+ "description": "Reach Level 25 Medic",
+ "displayName": "Reach Level 25 Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_39.jpg",
+ "icongray": "images/Achievement_39_gray.jpg",
+ "name": "Achievement_39"
+ },
+ {
+ "description": "Reach Level 5 Commando",
+ "displayName": "Reach Level 5 Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_40.jpg",
+ "icongray": "images/Achievement_40_gray.jpg",
+ "name": "Achievement_40"
+ },
+ {
+ "description": "Reach Level 10 Commando",
+ "displayName": "Reach Level 10 Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_41.jpg",
+ "icongray": "images/Achievement_41_gray.jpg",
+ "name": "Achievement_41"
+ },
+ {
+ "description": "Reach Level 15 Commando",
+ "displayName": "Reach Level 15 Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_42.jpg",
+ "icongray": "images/Achievement_42_gray.jpg",
+ "name": "Achievement_42"
+ },
+ {
+ "description": "Reach Level 20 Commando",
+ "displayName": "Reach Level 20 Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_43.jpg",
+ "icongray": "images/Achievement_43_gray.jpg",
+ "name": "Achievement_43"
+ },
+ {
+ "description": "Reach Level 25 Commando",
+ "displayName": "Reach Level 25 Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_44.jpg",
+ "icongray": "images/Achievement_44_gray.jpg",
+ "name": "Achievement_44"
+ },
+ {
+ "description": "Reach Level 5 Support",
+ "displayName": "Reach Level 5 Support",
+ "hidden": "0",
+ "icon": "images/Achievement_45.jpg",
+ "icongray": "images/Achievement_45_gray.jpg",
+ "name": "Achievement_45"
+ },
+ {
+ "description": "Reach Level 10 Support",
+ "displayName": "Reach Level 10 Support",
+ "hidden": "0",
+ "icon": "images/Achievement_46.jpg",
+ "icongray": "images/Achievement_46_gray.jpg",
+ "name": "Achievement_46"
+ },
+ {
+ "description": "Reach Level 15 Support",
+ "displayName": "Reach Level 15 Support",
+ "hidden": "0",
+ "icon": "images/Achievement_47.jpg",
+ "icongray": "images/Achievement_47_gray.jpg",
+ "name": "Achievement_47"
+ },
+ {
+ "description": "Reach Level 20 Support",
+ "displayName": "Reach Level 20 Support",
+ "hidden": "0",
+ "icon": "images/Achievement_48.jpg",
+ "icongray": "images/Achievement_48_gray.jpg",
+ "name": "Achievement_48"
+ },
+ {
+ "description": "Reach Level 25 Support",
+ "displayName": "Reach Level 25 Support",
+ "hidden": "0",
+ "icon": "images/Achievement_49.jpg",
+ "icongray": "images/Achievement_49_gray.jpg",
+ "name": "Achievement_49"
+ },
+ {
+ "description": "Reach Level 5 Firebug",
+ "displayName": "Reach Level 5 Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_50.jpg",
+ "icongray": "images/Achievement_50_gray.jpg",
+ "name": "Achievement_50"
+ },
+ {
+ "description": "Reach Level 10 Firebug",
+ "displayName": "Reach Level 10 Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_51.jpg",
+ "icongray": "images/Achievement_51_gray.jpg",
+ "name": "Achievement_51"
+ },
+ {
+ "description": "Reach Level 15 Firebug",
+ "displayName": "Reach Level 15 Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_52.jpg",
+ "icongray": "images/Achievement_52_gray.jpg",
+ "name": "Achievement_52"
+ },
+ {
+ "description": "Reach Level 20 Firebug",
+ "displayName": "Reach Level 20 Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_53.jpg",
+ "icongray": "images/Achievement_53_gray.jpg",
+ "name": "Achievement_53"
+ },
+ {
+ "description": "Reach Level 25 Firebug",
+ "displayName": "Reach Level 25 Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_54.jpg",
+ "icongray": "images/Achievement_54_gray.jpg",
+ "name": "Achievement_54"
+ },
+ {
+ "description": "Reach Level 5 Demolitions",
+ "displayName": "Reach Level 5 Demolitions",
+ "hidden": "0",
+ "icon": "images/Achievement_55.jpg",
+ "icongray": "images/Achievement_55_gray.jpg",
+ "name": "Achievement_55"
+ },
+ {
+ "description": "Reach Level 10 Demolitions",
+ "displayName": "Reach Level 10 Demolitions",
+ "hidden": "0",
+ "icon": "images/Achievement_56.jpg",
+ "icongray": "images/Achievement_56_gray.jpg",
+ "name": "Achievement_56"
+ },
+ {
+ "description": "Reach Level 15 Demolitions",
+ "displayName": "Reach Level 15 Demolitions",
+ "hidden": "0",
+ "icon": "images/Achievement_57.jpg",
+ "icongray": "images/Achievement_57_gray.jpg",
+ "name": "Achievement_57"
+ },
+ {
+ "description": "Reach Level 20 Demolitions",
+ "displayName": "Reach Level 20 Demolitions",
+ "hidden": "0",
+ "icon": "images/Achievement_58.jpg",
+ "icongray": "images/Achievement_58_gray.jpg",
+ "name": "Achievement_58"
+ },
+ {
+ "description": "Reach Level 25 Demolitions",
+ "displayName": "Reach Level 25 Demolitions",
+ "hidden": "0",
+ "icon": "images/Achievement_59.jpg",
+ "icongray": "images/Achievement_59_gray.jpg",
+ "name": "Achievement_59"
+ },
+ {
+ "description": "Reach Level 5 Gunslinger",
+ "displayName": "Reach Level 5 Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_60.jpg",
+ "icongray": "images/Achievement_60_gray.jpg",
+ "name": "Achievement_60"
+ },
+ {
+ "description": "Reach Level 10 Gunslinger",
+ "displayName": "Reach Level 10 Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_61.jpg",
+ "icongray": "images/Achievement_61_gray.jpg",
+ "name": "Achievement_61"
+ },
+ {
+ "description": "Reach Level 15 Gunslinger",
+ "displayName": "Reach Level 15 Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_62.jpg",
+ "icongray": "images/Achievement_62_gray.jpg",
+ "name": "Achievement_62"
+ },
+ {
+ "description": "Reach Level 20 Gunslinger",
+ "displayName": "Reach Level 20 Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_63.jpg",
+ "icongray": "images/Achievement_63_gray.jpg",
+ "name": "Achievement_63"
+ },
+ {
+ "description": "Reach Level 25 Gunslinger",
+ "displayName": "Reach Level 25 Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_64.jpg",
+ "icongray": "images/Achievement_64_gray.jpg",
+ "name": "Achievement_64"
+ },
+ {
+ "description": "Beat Any One Map as Berserker on Normal Difficulty",
+ "displayName": "Normal Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_65.jpg",
+ "icongray": "images/Achievement_65_gray.jpg",
+ "name": "Achievement_65"
+ },
+ {
+ "description": "Beat Any One Map as Berserker on Hard Difficulty",
+ "displayName": "Hard Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_66.jpg",
+ "icongray": "images/Achievement_66_gray.jpg",
+ "name": "Achievement_66"
+ },
+ {
+ "description": "Beat Any One Map as Berserker on Suicidal Difficulty",
+ "displayName": "Suicidal Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_67.jpg",
+ "icongray": "images/Achievement_67_gray.jpg",
+ "name": "Achievement_67"
+ },
+ {
+ "description": "Beat Any One Map as Berserker on Hell on Earth Difficulty",
+ "displayName": "Hellish Berserker",
+ "hidden": "0",
+ "icon": "images/Achievement_68.jpg",
+ "icongray": "images/Achievement_68_gray.jpg",
+ "name": "Achievement_68"
+ },
+ {
+ "description": "Beat Any One Map as Medic on Normal Difficulty",
+ "displayName": "Normal Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_69.jpg",
+ "icongray": "images/Achievement_69_gray.jpg",
+ "name": "Achievement_69"
+ },
+ {
+ "description": "Beat Any One Map as Medic on Hard Difficulty",
+ "displayName": "Hard Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_70.jpg",
+ "icongray": "images/Achievement_70_gray.jpg",
+ "name": "Achievement_70"
+ },
+ {
+ "description": "Beat Any One Map as Medic on Suicidal Difficulty",
+ "displayName": "Suicidal Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_71.jpg",
+ "icongray": "images/Achievement_71_gray.jpg",
+ "name": "Achievement_71"
+ },
+ {
+ "description": "Beat Any One Map as Medic on Hell on Earth Difficulty",
+ "displayName": "Hellish Medic",
+ "hidden": "0",
+ "icon": "images/Achievement_72.jpg",
+ "icongray": "images/Achievement_72_gray.jpg",
+ "name": "Achievement_72"
+ },
+ {
+ "description": "Complete any map as a Commando on Survival Normal difficulty",
+ "displayName": "Normal Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_73.jpg",
+ "icongray": "images/Achievement_73_gray.jpg",
+ "name": "Achievement_73"
+ },
+ {
+ "description": "Complete any map as a Commando on Survival Hard difficulty",
+ "displayName": "Hard Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_74.jpg",
+ "icongray": "images/Achievement_74_gray.jpg",
+ "name": "Achievement_74"
+ },
+ {
+ "description": "Complete any map as a Commando on Survival Suicidal difficulty",
+ "displayName": "Suicidal Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_75.jpg",
+ "icongray": "images/Achievement_75_gray.jpg",
+ "name": "Achievement_75"
+ },
+ {
+ "description": "Complete any map as a Commando on Survival Hell On Earth difficulty",
+ "displayName": "Hellish Commando",
+ "hidden": "0",
+ "icon": "images/Achievement_76.jpg",
+ "icongray": "images/Achievement_76_gray.jpg",
+ "name": "Achievement_76"
+ },
+ {
+ "description": "Complete any map as a Support on Survival Normal difficulty",
+ "displayName": "Normal Support",
+ "hidden": "0",
+ "icon": "images/Achievement_77.jpg",
+ "icongray": "images/Achievement_77_gray.jpg",
+ "name": "Achievement_77"
+ },
+ {
+ "description": "Complete any map as a Support on Survival Hard difficulty",
+ "displayName": "Hard Support",
+ "hidden": "0",
+ "icon": "images/Achievement_78.jpg",
+ "icongray": "images/Achievement_78_gray.jpg",
+ "name": "Achievement_78"
+ },
+ {
+ "description": "Complete any map as a Support on Survival Suicidal difficulty",
+ "displayName": "Suicidal Support",
+ "hidden": "0",
+ "icon": "images/Achievement_79.jpg",
+ "icongray": "images/Achievement_79_gray.jpg",
+ "name": "Achievement_79"
+ },
+ {
+ "description": "Complete any map as a Support on Survival Hell On Earth difficulty",
+ "displayName": "Hellish Support",
+ "hidden": "0",
+ "icon": "images/Achievement_80.jpg",
+ "icongray": "images/Achievement_80_gray.jpg",
+ "name": "Achievement_80"
+ },
+ {
+ "description": "Complete any map as a Firebug on Survival Normal difficulty",
+ "displayName": "Normal Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_81.jpg",
+ "icongray": "images/Achievement_81_gray.jpg",
+ "name": "Achievement_81"
+ },
+ {
+ "description": "Complete any map as a Firebug on Survival Hard difficulty",
+ "displayName": "Hard Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_82.jpg",
+ "icongray": "images/Achievement_82_gray.jpg",
+ "name": "Achievement_82"
+ },
+ {
+ "description": "Complete any map as a Firebug on Survival Suicidal difficulty",
+ "displayName": "Suicidal Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_83.jpg",
+ "icongray": "images/Achievement_83_gray.jpg",
+ "name": "Achievement_83"
+ },
+ {
+ "description": "Complete any map as a Firebug on Survival Hell On Earth difficulty",
+ "displayName": "Hellish Firebug",
+ "hidden": "0",
+ "icon": "images/Achievement_84.jpg",
+ "icongray": "images/Achievement_84_gray.jpg",
+ "name": "Achievement_84"
+ },
+ {
+ "description": "Complete any map as a Demolitionist on Survival Normal difficulty",
+ "displayName": "Normal Demolition",
+ "hidden": "0",
+ "icon": "images/Achievement_85.jpg",
+ "icongray": "images/Achievement_85_gray.jpg",
+ "name": "Achievement_85"
+ },
+ {
+ "description": "Complete any map as a Demolitionist on Survival Hard difficulty",
+ "displayName": "Hard Demolition",
+ "hidden": "0",
+ "icon": "images/Achievement_86.jpg",
+ "icongray": "images/Achievement_86_gray.jpg",
+ "name": "Achievement_86"
+ },
+ {
+ "description": "Complete any map as a Demolitionist on Survival Suicidal difficulty",
+ "displayName": "Suicidal Demolition",
+ "hidden": "0",
+ "icon": "images/Achievement_87.jpg",
+ "icongray": "images/Achievement_87_gray.jpg",
+ "name": "Achievement_87"
+ },
+ {
+ "description": "Complete any map as a Demolitionist on Survival Hell On Earth difficulty",
+ "displayName": "Hellish Demolition",
+ "hidden": "0",
+ "icon": "images/Achievement_88.jpg",
+ "icongray": "images/Achievement_88_gray.jpg",
+ "name": "Achievement_88"
+ },
+ {
+ "description": "Complete any map as a Gunslinger on Survival Normal difficulty",
+ "displayName": "Normal Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_89.jpg",
+ "icongray": "images/Achievement_89_gray.jpg",
+ "name": "Achievement_89"
+ },
+ {
+ "description": "Complete any map as a Gunslinger on Survival Hard difficulty",
+ "displayName": "Hard Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_90.jpg",
+ "icongray": "images/Achievement_90_gray.jpg",
+ "name": "Achievement_90"
+ },
+ {
+ "description": "Complete any map as a Gunslinger on Survival Suicidal difficulty",
+ "displayName": "Suicidal Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_91.jpg",
+ "icongray": "images/Achievement_91_gray.jpg",
+ "name": "Achievement_91"
+ },
+ {
+ "description": "Complete any map as a Gunslinger on Survival Hell On Earth difficulty",
+ "displayName": "Hellish Gunslinger",
+ "hidden": "0",
+ "icon": "images/Achievement_92.jpg",
+ "icongray": "images/Achievement_92_gray.jpg",
+ "name": "Achievement_92"
+ },
+ {
+ "description": "Reach Level 25 in all Perks",
+ "displayName": "Perked Up",
+ "hidden": "0",
+ "icon": "images/Achievement_93.jpg",
+ "icongray": "images/Achievement_93_gray.jpg",
+ "name": "Achievement_93"
+ },
+ {
+ "description": "Complete Black Forest on Survival Normal difficulty",
+ "displayName": "Killer Korn",
+ "hidden": "0",
+ "icon": "images/Achievement_94.jpg",
+ "icongray": "images/Achievement_94_gray.jpg",
+ "name": "Achievement_94"
+ },
+ {
+ "description": "Complete Black Forest on Survival Hard difficulty",
+ "displayName": "Lager Me Up",
+ "hidden": "0",
+ "icon": "images/Achievement_95.jpg",
+ "icongray": "images/Achievement_95_gray.jpg",
+ "name": "Achievement_95"
+ },
+ {
+ "description": "Complete Black Forest on Survival Suicidal difficulty",
+ "displayName": "I'll Dopple Bock",
+ "hidden": "0",
+ "icon": "images/Achievement_96.jpg",
+ "icongray": "images/Achievement_96_gray.jpg",
+ "name": "Achievement_96"
+ },
+ {
+ "description": "Complete Black Forest on Survival Hell On Earth difficulty",
+ "displayName": "Kein Bier Vor Vier",
+ "hidden": "0",
+ "icon": "images/Achievement_97.jpg",
+ "icongray": "images/Achievement_97_gray.jpg",
+ "name": "Achievement_97"
+ },
+ {
+ "description": "Collect all the items on Black Forest",
+ "displayName": "Black Forest Babies",
+ "hidden": "0",
+ "icon": "images/Achievement_98.jpg",
+ "icongray": "images/Achievement_98_gray.jpg",
+ "name": "Achievement_98"
+ },
+ {
+ "description": "Beat Farmhouse on Normal Difficulty",
+ "displayName": "Plow the Field",
+ "hidden": "0",
+ "icon": "images/Achievement_99.jpg",
+ "icongray": "images/Achievement_99_gray.jpg",
+ "name": "Achievement_99"
+ },
+ {
+ "description": "Beat Farmhouse on Hard Difficulty",
+ "displayName": "Sow the Seed",
+ "hidden": "0",
+ "icon": "images/Achievement_100.jpg",
+ "icongray": "images/Achievement_100_gray.jpg",
+ "name": "Achievement_100"
+ },
+ {
+ "description": "Beat Farmhouse on Suicidal Difficulty",
+ "displayName": "Water the Crops",
+ "hidden": "0",
+ "icon": "images/Achievement_101.jpg",
+ "icongray": "images/Achievement_101_gray.jpg",
+ "name": "Achievement_101"
+ },
+ {
+ "description": "Beat Farmhouse on Hell on Earth Difficulty",
+ "displayName": "Reap what you Sow",
+ "hidden": "0",
+ "icon": "images/Achievement_102.jpg",
+ "icongray": "images/Achievement_102_gray.jpg",
+ "name": "Achievement_102"
+ },
+ {
+ "description": "Collect all the items on Farmhouse",
+ "displayName": "Darkness Dolls",
+ "hidden": "0",
+ "icon": "images/Achievement_103.jpg",
+ "icongray": "images/Achievement_103_gray.jpg",
+ "name": "Achievement_103"
+ },
+ {
+ "description": "Beat Prison on Normal",
+ "displayName": "Walked into the wrong room",
+ "hidden": "0",
+ "icon": "images/Achievement_104.jpg",
+ "icongray": "images/Achievement_104_gray.jpg",
+ "name": "Achievement_104"
+ },
+ {
+ "description": "Beat Prison on Hard",
+ "displayName": "Mercenaries get paid",
+ "hidden": "0",
+ "icon": "images/Achievement_105.jpg",
+ "icongray": "images/Achievement_105_gray.jpg",
+ "name": "Achievement_105"
+ },
+ {
+ "description": "Beat Prison on Suicidal",
+ "displayName": "Like what, kill him again?",
+ "hidden": "0",
+ "icon": "images/Achievement_106.jpg",
+ "icongray": "images/Achievement_106_gray.jpg",
+ "name": "Achievement_106"
+ },
+ {
+ "description": "Beat Prison on Hell on Earth",
+ "displayName": "I was trained by the best. British Intel.",
+ "hidden": "0",
+ "icon": "images/Achievement_107.jpg",
+ "icongray": "images/Achievement_107_gray.jpg",
+ "name": "Achievement_107"
+ },
+ {
+ "description": "Collect all the items on Prison",
+ "displayName": "Perilous Prison",
+ "hidden": "0",
+ "icon": "images/Achievement_108.jpg",
+ "icongray": "images/Achievement_108_gray.jpg",
+ "name": "Achievement_108"
+ },
+ {
+ "description": "Complete the Training Floor",
+ "displayName": "School's Out Forever",
+ "hidden": "0",
+ "icon": "images/Achievement_109.jpg",
+ "icongray": "images/Achievement_109_gray.jpg",
+ "name": "Achievement_109"
+ },
+ {
+ "description": "Reach Level 5 Sharpshooter",
+ "displayName": "Reach Level 5 Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_110.jpg",
+ "icongray": "images/Achievement_110_gray.jpg",
+ "name": "Achievement_110"
+ },
+ {
+ "description": "Reach Level 10 Sharpshooter",
+ "displayName": "Reach Level 10 Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_111.jpg",
+ "icongray": "images/Achievement_111_gray.jpg",
+ "name": "Achievement_111"
+ },
+ {
+ "description": "Reach Level 15 Sharpshooter",
+ "displayName": "Reach Level 15 Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_112.jpg",
+ "icongray": "images/Achievement_112_gray.jpg",
+ "name": "Achievement_112"
+ },
+ {
+ "description": "Reach Level 20 Sharpshooter",
+ "displayName": "Reach Level 20 Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_113.jpg",
+ "icongray": "images/Achievement_113_gray.jpg",
+ "name": "Achievement_113"
+ },
+ {
+ "description": "Reach Level 25 Sharpshooter",
+ "displayName": "Reach Level 25 Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_114.jpg",
+ "icongray": "images/Achievement_114_gray.jpg",
+ "name": "Achievement_114"
+ },
+ {
+ "description": "Beat Any One Map as Sharpshooter on Normal Difficulty",
+ "displayName": "Normal Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_115.jpg",
+ "icongray": "images/Achievement_115_gray.jpg",
+ "name": "Achievement_115"
+ },
+ {
+ "description": "Beat Any One Map as Sharpshooter on Hard Difficulty",
+ "displayName": "Hard Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_116.jpg",
+ "icongray": "images/Achievement_116_gray.jpg",
+ "name": "Achievement_116"
+ },
+ {
+ "description": "Complete any map as a Sharpshooter on Survival Suicidal difficulty",
+ "displayName": "Suicidal Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_117.jpg",
+ "icongray": "images/Achievement_117_gray.jpg",
+ "name": "Achievement_117"
+ },
+ {
+ "description": "Complete any map as a Sharpshooter on Survival Hell On Earth difficulty",
+ "displayName": "Hellish Sharpshooter",
+ "hidden": "0",
+ "icon": "images/Achievement_118.jpg",
+ "icongray": "images/Achievement_118_gray.jpg",
+ "name": "Achievement_118"
+ },
+ {
+ "description": "Complete Containment Station on Survival Normal difficulty",
+ "displayName": "Never Got the Hang of Thursdays",
+ "hidden": "0",
+ "icon": "images/Achievement_119.jpg",
+ "icongray": "images/Achievement_119_gray.jpg",
+ "name": "Achievement_119"
+ },
+ {
+ "description": "Complete Containment Station on Survival Hard difficulty",
+ "displayName": "Don't Panic",
+ "hidden": "0",
+ "icon": "images/Achievement_120.jpg",
+ "icongray": "images/Achievement_120_gray.jpg",
+ "name": "Achievement_120"
+ },
+ {
+ "description": "Complete Containment Station on Survival Suicidal difficulty",
+ "displayName": "Give Up and Go Mad Now",
+ "hidden": "0",
+ "icon": "images/Achievement_121.jpg",
+ "icongray": "images/Achievement_121_gray.jpg",
+ "name": "Achievement_121"
+ },
+ {
+ "description": "Complete Containment Station on Survival Hell On Earth difficulty",
+ "displayName": "So Long and Thanks for All the Zeds",
+ "hidden": "0",
+ "icon": "images/Achievement_122.jpg",
+ "icongray": "images/Achievement_122_gray.jpg",
+ "name": "Achievement_122"
+ },
+ {
+ "description": "Collect All the Items on Containment Station",
+ "displayName": "Can't Be Contained",
+ "hidden": "0",
+ "icon": "images/Achievement_123.jpg",
+ "icongray": "images/Achievement_123_gray.jpg",
+ "name": "Achievement_123"
+ },
+ {
+ "description": "Complete Hostile Grounds on Survival Normal difficulty",
+ "displayName": "Mind the Gap",
+ "hidden": "0",
+ "icon": "images/Achievement_124.jpg",
+ "icongray": "images/Achievement_124_gray.jpg",
+ "name": "Achievement_124"
+ },
+ {
+ "description": "Complete Hostile Grounds on Survival Hard difficulty",
+ "displayName": "Can't Make an Omelette Without Killing a Few People",
+ "hidden": "0",
+ "icon": "images/Achievement_125.jpg",
+ "icongray": "images/Achievement_125_gray.jpg",
+ "name": "Achievement_125"
+ },
+ {
+ "description": "Complete Hostile Grounds on Survival Suicidal difficulty",
+ "displayName": "There Is No I in Team, But There Is an I in Pie",
+ "hidden": "0",
+ "icon": "images/Achievement_126.jpg",
+ "icongray": "images/Achievement_126_gray.jpg",
+ "name": "Achievement_126"
+ },
+ {
+ "description": "Complete Hostile Grounds on Survival Hell On Earth difficulty",
+ "displayName": "Who Died and Made You #*%$&@ King of the Zombies?",
+ "hidden": "0",
+ "icon": "images/Achievement_127.jpg",
+ "icongray": "images/Achievement_127_gray.jpg",
+ "name": "Achievement_127"
+ },
+ {
+ "description": "Collect All the Items on Hostile Grounds",
+ "displayName": "You've Got Red on You",
+ "hidden": "0",
+ "icon": "images/Achievement_128.jpg",
+ "icongray": "images/Achievement_128_gray.jpg",
+ "name": "Achievement_128"
+ },
+ {
+ "description": "Kill a Siren Before She Screams",
+ "displayName": "Dead Silence",
+ "hidden": "0",
+ "icon": "images/Achievement_129.jpg",
+ "icongray": "images/Achievement_129_gray.jpg",
+ "name": "Achievement_129"
+ },
+ {
+ "description": "Kill the Patriarch Before He Has a Chance to Heal",
+ "displayName": "Quick on the Trigger",
+ "hidden": "0",
+ "icon": "images/Achievement_130.jpg",
+ "icongray": "images/Achievement_130_gray.jpg",
+ "name": "Achievement_130"
+ },
+ {
+ "description": "Kill Your First Fleshpound",
+ "displayName": "It's Only a Flesh Wound",
+ "hidden": "0",
+ "icon": "images/Achievement_131.jpg",
+ "icongray": "images/Achievement_131_gray.jpg",
+ "name": "Achievement_131"
+ },
+ {
+ "description": "Kill Your First Scrake",
+ "displayName": "Hack and Slash",
+ "hidden": "0",
+ "icon": "images/Achievement_132.jpg",
+ "icongray": "images/Achievement_132_gray.jpg",
+ "name": "Achievement_132"
+ },
+ {
+ "description": "Kill Dr. Hans Volter For the First Time",
+ "displayName": "Die Volter",
+ "hidden": "0",
+ "icon": "images/Achievement_133.jpg",
+ "icongray": "images/Achievement_133_gray.jpg",
+ "name": "Achievement_133"
+ },
+ {
+ "description": "Win Any Match on Hard Difficulty",
+ "displayName": "Win Hard",
+ "hidden": "0",
+ "icon": "images/Achievement_134.jpg",
+ "icongray": "images/Achievement_134_gray.jpg",
+ "name": "Achievement_134"
+ },
+ {
+ "description": "Win Any Match on Suicidal Difficulty",
+ "displayName": "Win Suicidal",
+ "hidden": "0",
+ "icon": "images/Achievement_135.jpg",
+ "icongray": "images/Achievement_135_gray.jpg",
+ "name": "Achievement_135"
+ },
+ {
+ "description": "Win Any Match on Hell on Earth Difficulty",
+ "displayName": "Win Hell on Earth",
+ "hidden": "0",
+ "icon": "images/Achievement_136.jpg",
+ "icongray": "images/Achievement_136_gray.jpg",
+ "name": "Achievement_136"
+ },
+ {
+ "description": "Reach Level 5 on Any Perk",
+ "displayName": "Mr. Perky 5",
+ "hidden": "0",
+ "icon": "images/Achievement_137.jpg",
+ "icongray": "images/Achievement_137_gray.jpg",
+ "name": "Achievement_137"
+ },
+ {
+ "description": "Reach Level 10 on Any Perk",
+ "displayName": "Mr. Perky 10",
+ "hidden": "0",
+ "icon": "images/Achievement_138.jpg",
+ "icongray": "images/Achievement_138_gray.jpg",
+ "name": "Achievement_138"
+ },
+ {
+ "description": "Reach Level 15 on Any Perk",
+ "displayName": "Mr. Perky 15",
+ "hidden": "0",
+ "icon": "images/Achievement_139.jpg",
+ "icongray": "images/Achievement_139_gray.jpg",
+ "name": "Achievement_139"
+ },
+ {
+ "description": "Reach Level 20 on Any Perk",
+ "displayName": "Mr. Perky 20",
+ "hidden": "0",
+ "icon": "images/Achievement_140.jpg",
+ "icongray": "images/Achievement_140_gray.jpg",
+ "name": "Achievement_140"
+ },
+ {
+ "description": "Reach Level 25 on Any Perk",
+ "displayName": "Mr. Perky 25",
+ "hidden": "0",
+ "icon": "images/Achievement_141.jpg",
+ "icongray": "images/Achievement_141_gray.jpg",
+ "name": "Achievement_141"
+ },
+ {
+ "description": "Win 1 Multiplayer Match",
+ "displayName": "Win 1",
+ "hidden": "0",
+ "icon": "images/Achievement_142.jpg",
+ "icongray": "images/Achievement_142_gray.jpg",
+ "name": "Achievement_142"
+ },
+ {
+ "description": "Win 10 Multiplayer Matches",
+ "displayName": "Win 10",
+ "hidden": "0",
+ "icon": "images/Achievement_143.jpg",
+ "icongray": "images/Achievement_143_gray.jpg",
+ "name": "Achievement_143"
+ },
+ {
+ "description": "Win 25 Multiplayer Matches",
+ "displayName": "Win 25",
+ "hidden": "0",
+ "icon": "images/Achievement_144.jpg",
+ "icongray": "images/Achievement_144_gray.jpg",
+ "name": "Achievement_144"
+ },
+ {
+ "description": "Win a VS Survival Round Playing as the Zeds",
+ "displayName": "VS Zed Win",
+ "hidden": "0",
+ "icon": "images/Achievement_145.jpg",
+ "icongray": "images/Achievement_145_gray.jpg",
+ "name": "Achievement_145"
+ },
+ {
+ "description": "Win a VS Survival Round Playing as the Humans",
+ "displayName": "VS Human Win",
+ "hidden": "0",
+ "icon": "images/Achievement_146.jpg",
+ "icongray": "images/Achievement_146_gray.jpg",
+ "name": "Achievement_146"
+ },
+ {
+ "description": "Weld a Door to 100%",
+ "displayName": "Hold Out",
+ "hidden": "0",
+ "icon": "images/Achievement_147.jpg",
+ "icongray": "images/Achievement_147_gray.jpg",
+ "name": "Achievement_147"
+ },
+ {
+ "description": "Heal a Teammate With the Syringe",
+ "displayName": "I Got Your Back",
+ "hidden": "0",
+ "icon": "images/Achievement_148.jpg",
+ "icongray": "images/Achievement_148_gray.jpg",
+ "name": "Achievement_148"
+ },
+ {
+ "description": "Give 1000 Dosh to Another Player During a Multiplayer Match",
+ "displayName": "Benefactor",
+ "hidden": "0",
+ "icon": "images/Achievement_149.jpg",
+ "icongray": "images/Achievement_149_gray.jpg",
+ "name": "Achievement_149"
+ },
+ {
+ "description": "Beat Infernal Realms on Normal",
+ "displayName": "Hell is Other People",
+ "hidden": "0",
+ "icon": "images/Achievement_150.jpg",
+ "icongray": "images/Achievement_150_gray.jpg",
+ "name": "Achievement_150"
+ },
+ {
+ "description": "Complete Infernal Realms on Survival Hard difficulty",
+ "displayName": "If You Are Going Through Hell, Keep Going",
+ "hidden": "0",
+ "icon": "images/Achievement_151.jpg",
+ "icongray": "images/Achievement_151_gray.jpg",
+ "name": "Achievement_151"
+ },
+ {
+ "description": "Complete Infernal Realms on Survival Suicidal difficulty",
+ "displayName": "Hell is Just a Frame of Mind",
+ "hidden": "0",
+ "icon": "images/Achievement_152.jpg",
+ "icongray": "images/Achievement_152_gray.jpg",
+ "name": "Achievement_152"
+ },
+ {
+ "description": "Complete Infernal Realms on Survival Hell On Earth difficulty",
+ "displayName": "All Hope abandon, Ye Who Enter Here",
+ "hidden": "0",
+ "icon": "images/Achievement_153.jpg",
+ "icongray": "images/Achievement_153_gray.jpg",
+ "name": "Achievement_153"
+ },
+ {
+ "description": "Collect all the Items in Infernal Realms",
+ "displayName": "Infernal Relics",
+ "hidden": "0",
+ "icon": "images/Achievement_154.jpg",
+ "icongray": "images/Achievement_154_gray.jpg",
+ "name": "Achievement_154"
+ },
+ {
+ "description": "Reach Level 5 SWAT",
+ "displayName": "Reach Level 5 SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_155.jpg",
+ "icongray": "images/Achievement_155_gray.jpg",
+ "name": "Achievement_155"
+ },
+ {
+ "description": "Reach Level 10 SWAT",
+ "displayName": "Reach Level 10 SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_156.jpg",
+ "icongray": "images/Achievement_156_gray.jpg",
+ "name": "Achievement_156"
+ },
+ {
+ "description": "Reach Level 15 SWAT",
+ "displayName": "Reach Level 15 SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_157.jpg",
+ "icongray": "images/Achievement_157_gray.jpg",
+ "name": "Achievement_157"
+ },
+ {
+ "description": "Reach Level 20 SWAT",
+ "displayName": "Reach Level 20 SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_158.jpg",
+ "icongray": "images/Achievement_158_gray.jpg",
+ "name": "Achievement_158"
+ },
+ {
+ "description": "Reach Level 25 SWAT",
+ "displayName": "Reach Level 25 SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_159.jpg",
+ "icongray": "images/Achievement_159_gray.jpg",
+ "name": "Achievement_159"
+ },
+ {
+ "description": "Complete any map as a SWAT on Survival Normal difficulty",
+ "displayName": "Normal SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_160.jpg",
+ "icongray": "images/Achievement_160_gray.jpg",
+ "name": "Achievement_160"
+ },
+ {
+ "description": "Beat Any One Map as SWAT on Hard Difficulty",
+ "displayName": "Hard SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_161.jpg",
+ "icongray": "images/Achievement_161_gray.jpg",
+ "name": "Achievement_161"
+ },
+ {
+ "description": "Beat Any One Map as SWAT on Suicidal Difficulty",
+ "displayName": "Suicidal SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_162.jpg",
+ "icongray": "images/Achievement_162_gray.jpg",
+ "name": "Achievement_162"
+ },
+ {
+ "description": "Beat Any One Map as SWAT on Hell On Earth Difficulty",
+ "displayName": "Hellish SWAT",
+ "hidden": "0",
+ "icon": "images/Achievement_163.jpg",
+ "icongray": "images/Achievement_163_gray.jpg",
+ "name": "Achievement_163"
+ },
+ {
+ "description": "Reach Level 5 Survivalist",
+ "displayName": "Reach Level 5 Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_164.jpg",
+ "icongray": "images/Achievement_164_gray.jpg",
+ "name": "Achievement_164"
+ },
+ {
+ "description": "Reach Level 10 Survivalist",
+ "displayName": "Reach Level 10 Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_165.jpg",
+ "icongray": "images/Achievement_165_gray.jpg",
+ "name": "Achievement_165"
+ },
+ {
+ "description": "Reach Level 15 Survivalist",
+ "displayName": "Reach Level 15 Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_166.jpg",
+ "icongray": "images/Achievement_166_gray.jpg",
+ "name": "Achievement_166"
+ },
+ {
+ "description": "Reach Level 20 Survivalist",
+ "displayName": "Reach Level 20 Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_167.jpg",
+ "icongray": "images/Achievement_167_gray.jpg",
+ "name": "Achievement_167"
+ },
+ {
+ "description": "Reach Level 25 Survivalist",
+ "displayName": "Reach Level 25 Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_168.jpg",
+ "icongray": "images/Achievement_168_gray.jpg",
+ "name": "Achievement_168"
+ },
+ {
+ "description": "Beat Any One Map as Survivalist on Normal Difficulty",
+ "displayName": "Normal Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_169.jpg",
+ "icongray": "images/Achievement_169_gray.jpg",
+ "name": "Achievement_169"
+ },
+ {
+ "description": "Complete any map as a Survivalist on Survival Hard difficulty",
+ "displayName": "Hard Survivalist ",
+ "hidden": "0",
+ "icon": "images/Achievement_170.jpg",
+ "icongray": "images/Achievement_170_gray.jpg",
+ "name": "Achievement_170"
+ },
+ {
+ "description": "Complete any map as a Survivalist on Survival Suicidal difficulty",
+ "displayName": "Suicidal Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_171.jpg",
+ "icongray": "images/Achievement_171_gray.jpg",
+ "name": "Achievement_171"
+ },
+ {
+ "description": "Complete any map as a Survivalist on Survival Hell On Earth difficulty",
+ "displayName": "Hellish Survivalist",
+ "hidden": "0",
+ "icon": "images/Achievement_172.jpg",
+ "icongray": "images/Achievement_172_gray.jpg",
+ "name": "Achievement_172"
+ },
+ {
+ "description": "Complete Zed Landing on Survival Normal difficulty",
+ "displayName": "Surfs Up",
+ "hidden": "0",
+ "icon": "images/Achievement_173.jpg",
+ "icongray": "images/Achievement_173_gray.jpg",
+ "name": "Achievement_173"
+ },
+ {
+ "description": "Complete Zed Landing on Survival Hard difficulty",
+ "displayName": "Gnarly",
+ "hidden": "0",
+ "icon": "images/Achievement_174.jpg",
+ "icongray": "images/Achievement_174_gray.jpg",
+ "name": "Achievement_174"
+ },
+ {
+ "description": "Complete Zed Landing on Survival Suicidal difficulty",
+ "displayName": "Close-out",
+ "hidden": "0",
+ "icon": "images/Achievement_175.jpg",
+ "icongray": "images/Achievement_175_gray.jpg",
+ "name": "Achievement_175"
+ },
+ {
+ "description": "Complete Zed Landing on Survival Hell On Earth difficulty",
+ "displayName": "Blown Out",
+ "hidden": "0",
+ "icon": "images/Achievement_176.jpg",
+ "icongray": "images/Achievement_176_gray.jpg",
+ "name": "Achievement_176"
+ },
+ {
+ "description": "Collect all the volley balls on Zed Landing",
+ "displayName": "ALAN!!!!!!",
+ "hidden": "0",
+ "icon": "images/Achievement_177.jpg",
+ "icongray": "images/Achievement_177_gray.jpg",
+ "name": "Achievement_177"
+ },
+ {
+ "description": "Complete The Descent on Survival Normal difficulty",
+ "displayName": "How Bout Some Gas?",
+ "hidden": "0",
+ "icon": "images/Achievement_178.jpg",
+ "icongray": "images/Achievement_178_gray.jpg",
+ "name": "Achievement_178"
+ },
+ {
+ "description": "Complete The Descent on Survival Hard difficulty",
+ "displayName": "But what if we added Gas?",
+ "hidden": "0",
+ "icon": "images/Achievement_179.jpg",
+ "icongray": "images/Achievement_179_gray.jpg",
+ "name": "Achievement_179"
+ },
+ {
+ "description": "Complete The Descent on Survival Suicidal difficulty",
+ "displayName": "Let's Try Some Gas",
+ "hidden": "0",
+ "icon": "images/Achievement_180.jpg",
+ "icongray": "images/Achievement_180_gray.jpg",
+ "name": "Achievement_180"
+ },
+ {
+ "description": "Complete The Descent on Survival Hell On Earth difficulty",
+ "displayName": "That's enough gas....",
+ "hidden": "0",
+ "icon": "images/Achievement_181.jpg",
+ "icongray": "images/Achievement_181_gray.jpg",
+ "name": "Achievement_181"
+ },
+ {
+ "description": "Collect all the items on The Descent",
+ "displayName": "Hans Off the Merchandise",
+ "hidden": "0",
+ "icon": "images/Achievement_182.jpg",
+ "icongray": "images/Achievement_182_gray.jpg",
+ "name": "Achievement_182"
+ },
+ {
+ "description": "Complete Nuked on Survival Normal difficulty",
+ "displayName": "The War Room",
+ "hidden": "0",
+ "icon": "images/Achievement_183.jpg",
+ "icongray": "images/Achievement_183_gray.jpg",
+ "name": "Achievement_183"
+ },
+ {
+ "description": "Complete Nuked on Survival Hard difficulty",
+ "displayName": "The Mineshaft Gap",
+ "hidden": "0",
+ "icon": "images/Achievement_184.jpg",
+ "icongray": "images/Achievement_184_gray.jpg",
+ "name": "Achievement_184"
+ },
+ {
+ "description": "Complete Nuked on Survival Suicidal difficulty",
+ "displayName": "Peace is Our Profession",
+ "hidden": "0",
+ "icon": "images/Achievement_185.jpg",
+ "icongray": "images/Achievement_185_gray.jpg",
+ "name": "Achievement_185"
+ },
+ {
+ "description": "Complete Nuked on Survival Hell On Earth difficulty",
+ "displayName": "How I Learned to Love the Bomb",
+ "hidden": "0",
+ "icon": "images/Achievement_186.jpg",
+ "icongray": "images/Achievement_186_gray.jpg",
+ "name": "Achievement_186"
+ },
+ {
+ "description": "Collect all the items on Nuked",
+ "displayName": "Davy Crockett",
+ "hidden": "0",
+ "icon": "images/Achievement_187.jpg",
+ "icongray": "images/Achievement_187_gray.jpg",
+ "name": "Achievement_187"
+ },
+ {
+ "description": "Complete Tragic Kingdom on Survival Normal difficulty",
+ "displayName": "It's a Bloody World After All",
+ "hidden": "0",
+ "icon": "images/Achievement_188.jpg",
+ "icongray": "images/Achievement_188_gray.jpg",
+ "name": "Achievement_188"
+ },
+ {
+ "description": "Complete Tragic Kingdom on Survival Hard difficulty",
+ "displayName": "The Goriest Place on Earth",
+ "hidden": "0",
+ "icon": "images/Achievement_189.jpg",
+ "icongray": "images/Achievement_189_gray.jpg",
+ "name": "Achievement_189"
+ },
+ {
+ "description": "Complete Tragic Kingdom on Survival Suicidal difficulty",
+ "displayName": "The House the Zed Built",
+ "hidden": "0",
+ "icon": "images/Achievement_190.jpg",
+ "icongray": "images/Achievement_190_gray.jpg",
+ "name": "Achievement_190"
+ },
+ {
+ "description": "Complete Tragic Kingdom on Survival Hell On Earth difficulty",
+ "displayName": "Where Nightmares Come True",
+ "hidden": "0",
+ "icon": "images/Achievement_191.jpg",
+ "icongray": "images/Achievement_191_gray.jpg",
+ "name": "Achievement_191"
+ },
+ {
+ "description": "Collect all the items on The Tragic Kingdom",
+ "displayName": "The Wonderful World of Merchandising",
+ "hidden": "0",
+ "icon": "images/Achievement_192.jpg",
+ "icongray": "images/Achievement_192_gray.jpg",
+ "name": "Achievement_192"
+ },
+ {
+ "description": "Beat Nightmare on Normal",
+ "displayName": "I Got a Rock",
+ "hidden": "0",
+ "icon": "images/Achievement_193.jpg",
+ "icongray": "images/Achievement_193_gray.jpg",
+ "name": "Achievement_193"
+ },
+ {
+ "description": "Beat Nightmare on Hard",
+ "displayName": "Yuck, Candy Corn",
+ "hidden": "0",
+ "icon": "images/Achievement_194.jpg",
+ "icongray": "images/Achievement_194_gray.jpg",
+ "name": "Achievement_194"
+ },
+ {
+ "description": "Beat Nightmare on Suicidal",
+ "displayName": "Fun Size? What is Fun Size?",
+ "hidden": "0",
+ "icon": "images/Achievement_195.jpg",
+ "icongray": "images/Achievement_195_gray.jpg",
+ "name": "Achievement_195"
+ },
+ {
+ "description": "Beat Nightmare on Hell on Earth",
+ "displayName": "Victory! Full Size Candy Bar!",
+ "hidden": "0",
+ "icon": "images/Achievement_196.jpg",
+ "icongray": "images/Achievement_196_gray.jpg",
+ "name": "Achievement_196"
+ },
+ {
+ "description": "Collect all the items (skulls) on Nightmare",
+ "displayName": "Letting the Demons Out",
+ "hidden": "0",
+ "icon": "images/Achievement_197.jpg",
+ "icongray": "images/Achievement_197_gray.jpg",
+ "name": "Achievement_197"
+ },
+ {
+ "description": "Beat Krampus Lair on Normal",
+ "displayName": "Stocking Full of Coal",
+ "hidden": "0",
+ "icon": "images/Achievement_198.jpg",
+ "icongray": "images/Achievement_198_gray.jpg",
+ "name": "Achievement_198"
+ },
+ {
+ "description": "Beat Krampus Lair on Hard",
+ "displayName": "Bundle of Switches",
+ "hidden": "0",
+ "icon": "images/Achievement_199.jpg",
+ "icongray": "images/Achievement_199_gray.jpg",
+ "name": "Achievement_199"
+ },
+ {
+ "description": "Beat Krampus Lair on Suicidal",
+ "displayName": "A Whupping",
+ "hidden": "0",
+ "icon": "images/Achievement_200.jpg",
+ "icongray": "images/Achievement_200_gray.jpg",
+ "name": "Achievement_200"
+ },
+ {
+ "description": "Complete Krampus Lair on Survival Hell On Earth difficulty",
+ "displayName": "Carried off to the Underworld",
+ "hidden": "0",
+ "icon": "images/Achievement_201.jpg",
+ "icongray": "images/Achievement_201_gray.jpg",
+ "name": "Achievement_201"
+ },
+ {
+ "description": "Collect all the items (Snowglobes) on Krampus Lair",
+ "displayName": "A World Under Glass",
+ "hidden": "0",
+ "icon": "images/Achievement_202.jpg",
+ "icongray": "images/Achievement_202_gray.jpg",
+ "name": "Achievement_202"
+ },
+ {
+ "description": "Complete DieSector wave 25 on Endless Normal difficulty",
+ "displayName": "Training Simulation",
+ "hidden": "0",
+ "icon": "images/Achievement_203.jpg",
+ "icongray": "images/Achievement_203_gray.jpg",
+ "name": "Achievement_203"
+ },
+ {
+ "description": "Complete DieSector wave 25 on Endless Hard difficulty",
+ "displayName": "Test Trials",
+ "hidden": "0",
+ "icon": "images/Achievement_204.jpg",
+ "icongray": "images/Achievement_204_gray.jpg",
+ "name": "Achievement_204"
+ },
+ {
+ "description": "Complete DieSector wave 25 on Endless Suicidal difficulty",
+ "displayName": "Code Dead",
+ "hidden": "0",
+ "icon": "images/Achievement_205.jpg",
+ "icongray": "images/Achievement_205_gray.jpg",
+ "name": "Achievement_205"
+ },
+ {
+ "description": "Complete DieSector wave 25 on Endless Hell On Earth difficulty",
+ "displayName": "Fatal Exception",
+ "hidden": "0",
+ "icon": "images/Achievement_206.jpg",
+ "icongray": "images/Achievement_206_gray.jpg",
+ "name": "Achievement_206"
+ },
+ {
+ "description": "Collect all the items (D.A.R. Bobbleheads) on DieSector",
+ "displayName": "It's aD.A.R.able",
+ "hidden": "0",
+ "icon": "images/Achievement_207.jpg",
+ "icongray": "images/Achievement_207_gray.jpg",
+ "name": "Achievement_207"
+ },
+ {
+ "description": "Complete Power Core on Survival Normal difficulty",
+ "displayName": "A Spark to Light the Way",
+ "hidden": "0",
+ "icon": "images/Achievement_208.jpg",
+ "icongray": "images/Achievement_208_gray.jpg",
+ "name": "Achievement_208"
+ },
+ {
+ "description": "Complete Power Core on Survival Hard difficulty",
+ "displayName": "Bolting through the Core",
+ "hidden": "0",
+ "icon": "images/Achievement_209.jpg",
+ "icongray": "images/Achievement_209_gray.jpg",
+ "name": "Achievement_209"
+ },
+ {
+ "description": "Complete Power Core on Survival Suicidal difficulty",
+ "displayName": "High Voltage",
+ "hidden": "0",
+ "icon": "images/Achievement_210.jpg",
+ "icongray": "images/Achievement_210_gray.jpg",
+ "name": "Achievement_210"
+ },
+ {
+ "description": "Complete Power Core on Survival Hell On Earth difficulty",
+ "displayName": "I Have the Power!",
+ "hidden": "0",
+ "icon": "images/Achievement_211.jpg",
+ "icongray": "images/Achievement_211_gray.jpg",
+ "name": "Achievement_211"
+ },
+ {
+ "description": "Collect all the items (Batteries) on Power Core",
+ "displayName": "Surge Breaker",
+ "hidden": "0",
+ "icon": "images/Achievement_212.jpg",
+ "icongray": "images/Achievement_212_gray.jpg",
+ "name": "Achievement_212"
+ },
+ {
+ "description": "Complete Airship on Survival Normal difficulty",
+ "displayName": "A Little Turbulence",
+ "hidden": "0",
+ "icon": "images/Achievement_213.jpg",
+ "icongray": "images/Achievement_213_gray.jpg",
+ "name": "Achievement_213"
+ },
+ {
+ "description": "Complete Airship on Survival Hard difficulty",
+ "displayName": "Flying Unfriendly Skies",
+ "hidden": "0",
+ "icon": "images/Achievement_214.jpg",
+ "icongray": "images/Achievement_214_gray.jpg",
+ "name": "Achievement_214"
+ },
+ {
+ "description": "Complete Airship on Survival Suicidal difficulty",
+ "displayName": "Soar, Gore, and More",
+ "hidden": "0",
+ "icon": "images/Achievement_215.jpg",
+ "icongray": "images/Achievement_215_gray.jpg",
+ "name": "Achievement_215"
+ },
+ {
+ "description": "Complete Airship on Survival Hell On Earth difficulty",
+ "displayName": "Mile High Dead Club",
+ "hidden": "0",
+ "icon": "images/Achievement_216.jpg",
+ "icongray": "images/Achievement_216_gray.jpg",
+ "name": "Achievement_216"
+ },
+ {
+ "description": "Collect all the items (Steam Cells) on Airship",
+ "displayName": "Powered by Steam",
+ "hidden": "0",
+ "icon": "images/Achievement_217.jpg",
+ "icongray": "images/Achievement_217_gray.jpg",
+ "name": "Achievement_217"
+ },
+ {
+ "description": "Complete Lockdown on Survival Normal difficulty",
+ "displayName": "Station Stabilization",
+ "hidden": "0",
+ "icon": "images/Achievement_218.jpg",
+ "icongray": "images/Achievement_218_gray.jpg",
+ "name": "Achievement_218"
+ },
+ {
+ "description": "Complete Lockdown on Survival Hard difficulty",
+ "displayName": "Fun Near the Sun",
+ "hidden": "0",
+ "icon": "images/Achievement_219.jpg",
+ "icongray": "images/Achievement_219_gray.jpg",
+ "name": "Achievement_219"
+ },
+ {
+ "description": "Complete Lockdown on Survival Suicidal difficulty",
+ "displayName": "Space Race",
+ "hidden": "0",
+ "icon": "images/Achievement_220.jpg",
+ "icongray": "images/Achievement_220_gray.jpg",
+ "name": "Achievement_220"
+ },
+ {
+ "description": "Complete Lockdown on Survival Hell On Earth difficulty",
+ "displayName": "Houston, We Don't Have a Problem",
+ "hidden": "0",
+ "icon": "images/Achievement_221.jpg",
+ "icongray": "images/Achievement_221_gray.jpg",
+ "name": "Achievement_221"
+ },
+ {
+ "description": "Collect all the items (Batteries) on Lockdown",
+ "displayName": "Shocking Discovery!",
+ "hidden": "0",
+ "icon": "images/Achievement_222.jpg",
+ "icongray": "images/Achievement_222_gray.jpg",
+ "name": "Achievement_222"
+ },
+ {
+ "description": "Complete Monster Ball on Survival Normal difficulty",
+ "displayName": "Castle Crashers",
+ "hidden": "0",
+ "icon": "images/Achievement_223.jpg",
+ "icongray": "images/Achievement_223_gray.jpg",
+ "name": "Achievement_223"
+ },
+ {
+ "description": "Complete Monster Ball on Survival Hard difficulty",
+ "displayName": "Party Hard!",
+ "hidden": "0",
+ "icon": "images/Achievement_224.jpg",
+ "icongray": "images/Achievement_224_gray.jpg",
+ "name": "Achievement_224"
+ },
+ {
+ "description": "Complete Monster Ball on Survival Suicidal difficulty",
+ "displayName": "Dance on the Gore Floor",
+ "hidden": "0",
+ "icon": "images/Achievement_225.jpg",
+ "icongray": "images/Achievement_225_gray.jpg",
+ "name": "Achievement_225"
+ },
+ {
+ "description": "Complete Monster Ball on Survival Hell on Earth difficulty",
+ "displayName": "Rest In Pieces",
+ "hidden": "0",
+ "icon": "images/Achievement_226.jpg",
+ "icongray": "images/Achievement_226_gray.jpg",
+ "name": "Achievement_226"
+ },
+ {
+ "description": "Destroy 10 Glowing Skulls in Monster Ball",
+ "displayName": "Spooky Scary Skeletons",
+ "hidden": "0",
+ "icon": "images/Achievement_227.jpg",
+ "icongray": "images/Achievement_227_gray.jpg",
+ "name": "Achievement_227"
+ },
+ {
+ "description": "Unlock the Alchemist Room in Monster Ball",
+ "displayName": "Death's Door",
+ "hidden": "0",
+ "icon": "images/Achievement_228.jpg",
+ "icongray": "images/Achievement_228_gray.jpg",
+ "name": "Achievement_228"
+ },
+ {
+ "description": "Complete Santa's Workshop on Survival Normal Difficulty",
+ "displayName": "Cookies and Milk",
+ "hidden": "0",
+ "icon": "images/Achievement_229.jpg",
+ "icongray": "images/Achievement_229_gray.jpg",
+ "name": "Achievement_229"
+ },
+ {
+ "description": "Complete Santa's Workshop on Survival Hard Difficulty",
+ "displayName": "Slaying with Santa",
+ "hidden": "0",
+ "icon": "images/Achievement_230.jpg",
+ "icongray": "images/Achievement_230_gray.jpg",
+ "name": "Achievement_230"
+ },
+ {
+ "description": "Complete Santa's Workshop on Survival Suicidal Difficulty",
+ "displayName": "A Deadly Carol",
+ "hidden": "0",
+ "icon": "images/Achievement_231.jpg",
+ "icongray": "images/Achievement_231_gray.jpg",
+ "name": "Achievement_231"
+ },
+ {
+ "description": "Complete Santa's Workshop on Survival Hell On Earth Difficulty",
+ "displayName": "You're On The Badass List",
+ "hidden": "0",
+ "icon": "images/Achievement_232.jpg",
+ "icongray": "images/Achievement_232_gray.jpg",
+ "name": "Achievement_232"
+ },
+ {
+ "description": "Destroy 10 Snow Globes in Santa's Workshop",
+ "displayName": "Yule Shoot Your Eye Out",
+ "hidden": "0",
+ "icon": "images/Achievement_233.jpg",
+ "icongray": "images/Achievement_233_gray.jpg",
+ "name": "Achievement_233"
+ },
+ {
+ "description": "Complete Shopping Spree on Survival Normal Difficulty",
+ "displayName": "Cleanup On Aisle 3",
+ "hidden": "0",
+ "icon": "images/Achievement_234.jpg",
+ "icongray": "images/Achievement_234_gray.jpg",
+ "name": "Achievement_234"
+ },
+ {
+ "description": "Complete Shopping Spree on Survival Hard Difficulty",
+ "displayName": "Shoot One Get Two Free",
+ "hidden": "0",
+ "icon": "images/Achievement_235.jpg",
+ "icongray": "images/Achievement_235_gray.jpg",
+ "name": "Achievement_235"
+ },
+ {
+ "description": "Complete Shopping Spree on Survival Suicidal Difficulty",
+ "displayName": "Savings to Die For",
+ "hidden": "0",
+ "icon": "images/Achievement_236.jpg",
+ "icongray": "images/Achievement_236_gray.jpg",
+ "name": "Achievement_236"
+ },
+ {
+ "description": "Complete Shopping Spree on Survival Hell On Earth Difficulty",
+ "displayName": "Red Friday",
+ "hidden": "0",
+ "icon": "images/Achievement_237.jpg",
+ "icongray": "images/Achievement_237_gray.jpg",
+ "name": "Achievement_237"
+ },
+ {
+ "description": "Destroy 10 Dosh Necklaces in Shopping Spree",
+ "displayName": "A Special Deal",
+ "hidden": "0",
+ "icon": "images/Achievement_238.jpg",
+ "icongray": "images/Achievement_238_gray.jpg",
+ "name": "Achievement_238"
+ },
+ {
+ "description": "Complete Spillway on Survival Normal Difficulty",
+ "displayName": "It's All Downstream From Here",
+ "hidden": "0",
+ "icon": "images/Achievement_239.jpg",
+ "icongray": "images/Achievement_239_gray.jpg",
+ "name": "Achievement_239"
+ },
+ {
+ "description": "Complete Spillway on Survival Hard Difficulty",
+ "displayName": "Zeds Be Dam",
+ "hidden": "0",
+ "icon": "images/Achievement_240.jpg",
+ "icongray": "images/Achievement_240_gray.jpg",
+ "name": "Achievement_240"
+ },
+ {
+ "description": "Complete Spillway on Survival Suicidal Difficulty",
+ "displayName": "Overflow Controlled",
+ "hidden": "0",
+ "icon": "images/Achievement_241.jpg",
+ "icongray": "images/Achievement_241_gray.jpg",
+ "name": "Achievement_241"
+ },
+ {
+ "description": "Complete Spillway on Survival Hell On Earth Difficulty",
+ "displayName": "Dam You're Good!",
+ "hidden": "0",
+ "icon": "images/Achievement_242.jpg",
+ "icongray": "images/Achievement_242_gray.jpg",
+ "name": "Achievement_242"
+ },
+ {
+ "description": "Destroy 10 Dosh Necklaces in Spillway",
+ "displayName": "Money Down the Drain",
+ "hidden": "0",
+ "icon": "images/Achievement_243.jpg",
+ "icongray": "images/Achievement_243_gray.jpg",
+ "name": "Achievement_243"
+ },
+ {
+ "description": "Complete Steam Fortress on Objective Normal Difficulty",
+ "displayName": "Burning Out the Fuse",
+ "hidden": "0",
+ "icon": "images/Achievement_244.jpg",
+ "icongray": "images/Achievement_244_gray.jpg",
+ "name": "Achievement_244"
+ },
+ {
+ "description": "Complete Steam Fortress on Objective Hard Difficulty",
+ "displayName": "Touchdown Brings Me Down",
+ "hidden": "0",
+ "icon": "images/Achievement_245.jpg",
+ "icongray": "images/Achievement_245_gray.jpg",
+ "name": "Achievement_245"
+ },
+ {
+ "description": "Complete Steam Fortress on Objective Suicidal Difficulty",
+ "displayName": "High as a Kite",
+ "hidden": "0",
+ "icon": "images/Achievement_246.jpg",
+ "icongray": "images/Achievement_246_gray.jpg",
+ "name": "Achievement_246"
+ },
+ {
+ "description": "Complete Steam Fortress on Objective Hell On Earth Difficulty",
+ "displayName": "It's Cold as Hell",
+ "hidden": "0",
+ "icon": "images/Achievement_247.jpg",
+ "icongray": "images/Achievement_247_gray.jpg",
+ "name": "Achievement_247"
+ },
+ {
+ "description": "Destroy 10 Steam Batteries on Steam Fortress",
+ "displayName": "All This Science I Don't Understand",
+ "hidden": "0",
+ "icon": "images/Achievement_248.jpg",
+ "icongray": "images/Achievement_248_gray.jpg",
+ "name": "Achievement_248"
+ },
+ {
+ "description": "Complete Zed Landing on Objective Normal Difficulty",
+ "displayName": "Droning On",
+ "hidden": "0",
+ "icon": "images/Achievement_249.jpg",
+ "icongray": "images/Achievement_249_gray.jpg",
+ "name": "Achievement_249"
+ },
+ {
+ "description": "Complete Zed Landing on Objective Hard Difficulty",
+ "displayName": "Data Deliverier ",
+ "hidden": "0",
+ "icon": "images/Achievement_250.jpg",
+ "icongray": "images/Achievement_250_gray.jpg",
+ "name": "Achievement_250"
+ },
+ {
+ "description": "Complete Zed Landing on Objective Suicidal Difficulty",
+ "displayName": "Island Isolation",
+ "hidden": "0",
+ "icon": "images/Achievement_251.jpg",
+ "icongray": "images/Achievement_251_gray.jpg",
+ "name": "Achievement_251"
+ },
+ {
+ "description": "Complete Zed Landing on Objective Hell On Earth Difficulty",
+ "displayName": "Someone Call a Chopper?",
+ "hidden": "0",
+ "icon": "images/Achievement_252.jpg",
+ "icongray": "images/Achievement_252_gray.jpg",
+ "name": "Achievement_252"
+ },
+ {
+ "description": "Complete Outpost on Objective Normal Difficulty",
+ "displayName": "It's Snow Good. ",
+ "hidden": "0",
+ "icon": "images/Achievement_253.jpg",
+ "icongray": "images/Achievement_253_gray.jpg",
+ "name": "Achievement_253"
+ },
+ {
+ "description": "Complete Outpost on Objective Hard Difficulty",
+ "displayName": "Giving the Cold Shoulder",
+ "hidden": "0",
+ "icon": "images/Achievement_254.jpg",
+ "icongray": "images/Achievement_254_gray.jpg",
+ "name": "Achievement_254"
+ },
+ {
+ "description": "Complete Outpost on Objective Suicidal Difficulty",
+ "displayName": "Frozen Assets",
+ "hidden": "0",
+ "icon": "images/Achievement_255.jpg",
+ "icongray": "images/Achievement_255_gray.jpg",
+ "name": "Achievement_255"
+ },
+ {
+ "description": "Complete Outpost on Objective Hell On Earth Difficulty",
+ "displayName": "The Snow Must Go On",
+ "hidden": "0",
+ "icon": "images/Achievement_256.jpg",
+ "icongray": "images/Achievement_256_gray.jpg",
+ "name": "Achievement_256"
+ },
+ {
+ "description": "Complete Ashwood Asylum on Survival Hard or higher Difficulty",
+ "displayName": "Cackling Crazy",
+ "hidden": "0",
+ "icon": "images/Achievement_257.jpg",
+ "icongray": "images/Achievement_257_gray.jpg",
+ "name": "Achievement_257"
+ },
+ {
+ "description": "Complete Ashwood Asylum on Survival Hell On Earth Difficulty",
+ "displayName": "Non Compos Mentis",
+ "hidden": "0",
+ "icon": "images/Achievement_258.jpg",
+ "icongray": "images/Achievement_258_gray.jpg",
+ "name": "Achievement_258"
+ },
+ {
+ "description": "Destroy 10 red skulls on Ashwood Asylum",
+ "displayName": "Money Mania",
+ "hidden": "0",
+ "icon": "images/Achievement_259.jpg",
+ "icongray": "images/Achievement_259_gray.jpg",
+ "name": "Achievement_259"
+ },
+ {
+ "description": "Complete Nuked on Objective Hard or higher Difficulty",
+ "displayName": "An Explosive Ending",
+ "hidden": "0",
+ "icon": "images/Achievement_260.jpg",
+ "icongray": "images/Achievement_260_gray.jpg",
+ "name": "Achievement_260"
+ },
+ {
+ "description": "Complete Nuked on Objective Hell On Earth Difficulty",
+ "displayName": "The World On Fire",
+ "hidden": "0",
+ "icon": "images/Achievement_261.jpg",
+ "icongray": "images/Achievement_261_gray.jpg",
+ "name": "Achievement_261"
+ }
+]
\ No newline at end of file
diff --git a/files_example/steam_settings.EXAMPLE/default_items.EXAMPLE.json b/files_example/steam_settings.EXAMPLE/default_items.EXAMPLE.json
new file mode 100644
index 00000000..34db3278
--- /dev/null
+++ b/files_example/steam_settings.EXAMPLE/default_items.EXAMPLE.json
@@ -0,0 +1,4 @@
+{
+ "2001": 1,
+ "2002": 1
+}
\ No newline at end of file
diff --git a/generate_game_infos/generate_game_infos.cpp b/generate_game_infos/generate_game_infos.cpp
new file mode 100644
index 00000000..0ff8f505
--- /dev/null
+++ b/generate_game_infos/generate_game_infos.cpp
@@ -0,0 +1,456 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+class CurlGlobal
+{
+ bool _init;
+
+ CurlGlobal() :_init(false) {}
+
+ ~CurlGlobal() { cleanup(); }
+
+public:
+ static CurlGlobal& Inst()
+ {
+ static CurlGlobal _this;
+ return _this;
+ }
+
+ CURLcode init(long flags = CURL_GLOBAL_DEFAULT) { return curl_global_init(flags); }
+ void cleanup()
+ {
+ if (_init)
+ {
+ curl_global_cleanup();
+ _init = false;
+ }
+ }
+};
+
+class CurlEasy
+{
+ CURL* _me;
+ bool _init;
+ std::string _buffer;
+
+ static int writer(char* data, size_t size, size_t nmemb,
+ CurlEasy *_this)
+ {
+ if (_this == nullptr)
+ return 0;
+
+ _this->_buffer.append(data, size * nmemb);
+
+ return size * nmemb;
+ }
+
+public:
+ CurlEasy() :_me(nullptr), _init(false) {}
+ ~CurlEasy() { cleanup(); }
+
+ bool init()
+ {
+ _init = (_me = curl_easy_init()) != nullptr;
+ if (_init)
+ {
+ if (curl_easy_setopt(_me, CURLOPT_WRITEFUNCTION, writer) != CURLE_OK)
+ {
+ cleanup();
+ return false;
+ }
+
+ if (curl_easy_setopt(_me, CURLOPT_WRITEDATA, this) != CURLE_OK)
+ {
+ cleanup();
+ return false;
+ }
+ }
+ return _init;
+ }
+
+ void cleanup()
+ {
+ if (_init)
+ {
+ curl_easy_cleanup(_me);
+ }
+ }
+
+ CURLcode set_url(const std::string& url)
+ {
+ return curl_easy_setopt(_me, CURLOPT_URL, url.c_str());
+ }
+
+ CURLcode skip_verifypeer(bool skip = true)
+ {
+ return curl_easy_setopt(_me, CURLOPT_SSL_VERIFYPEER, skip ? 0L : 1L);
+ }
+
+ CURLcode skip_verifhost(bool skip = true)
+ {
+ return curl_easy_setopt(_me, CURLOPT_SSL_VERIFYHOST, skip ? 0L : 1L);
+ }
+
+ CURLcode connect_only(bool connect = true)
+ {
+ return curl_easy_setopt(_me, CURLOPT_CONNECT_ONLY, connect ? 1L : 0L);
+ }
+
+ CURLcode perform()
+ {
+ _buffer.clear();
+ return curl_easy_perform(_me);
+ }
+
+ CURLcode recv(void *buffer, size_t buflen, size_t* read_len)
+ {
+ return curl_easy_recv(_me, buffer, buflen, read_len);
+ }
+
+ CURLcode get_html_code(long &code)
+ {
+ return curl_easy_getinfo(_me, CURLINFO_RESPONSE_CODE, &code);
+ }
+
+ std::string const& get_answer() const { return _buffer; }
+};
+
+// Get all steam appid with their name: http://api.steampowered.com/ISteamApps/GetAppList/v2/
+// Steam storefront webapi: https://wiki.teamfortress.com/wiki/User:RJackson/StorefrontAPI
+// http://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/?key=&appid=
+/*
+{
+ "game" : {
+ "gameName" : "",
+ "availableGameStats" : {
+ "achievements" : {
+ ("" : {
+ "name" : "achievement_name",
+ "displayName" : "achievement name on screen",
+ "hidden" : (0|1),
+ ["description" : "",]
+ "icon" : "",
+ "icongray" : ""
+ },
+ ...)
+ }
+ }
+ }
+}
+*/
+// Get appid infos: http://store.steampowered.com/api/appdetails/?appids=218620
+/*
+"appid" : {
+ "success" : (true|false),
+ (success == true "data" : {
+ ...
+ "name" : "",
+ "steam_appid" : ,
+ (OPT "dlc" : [, ]),
+ "header_image" : "" <-- Use this in the overlay ?
+ (OPT "achievements" : {
+ "total" :
+ }),
+ "background" : "" <-- Use this as the overlay background ?
+ (OPT "packages" : [, ])
+ })
+}
+*/
+// ---------------------------------
+// -- Special thanks to psychonic --
+// ---------------------------------
+// Get game items definition digest (Phase1): https://api.steampowered.com/IInventoryService/GetItemDefMeta/v1?key=&appid=218620
+/*
+{
+ "response": {
+ "modified": 1566848385,
+ "digest": "3CDFC1CC1AC2B0D55D12C1C130F4294BDD6DF8D0"
+ }
+}
+*/
+
+// Get game items definition: https://api.steampowered.com/IGameInventory/GetItemDefArchive/v0001?appid=218620&digest=
+/*
+[
+ {
+ "appid":"218620",
+ "itemdefid":"0",
+ "Timestamp":"2016-04-08T18:00:21.3643085Z",
+ "modified":"20160408T180021Z",
+ "date_created":"20160408T180021Z",
+ "type":"",
+ "display_type":"",
+ "name":"",
+ "quantity":0,
+ "description":"",
+ "tradable":false,
+ "marketable":false,
+ "commodity":false,
+ "drop_interval":0,
+ "drop_max_per_window":0,
+ "workshopid":"0"
+ },
+ {
+ "appid":"218620",
+ "itemdefid":"50002",
+ "Timestamp":"2015-11-13T16:01:18.0338618Z",
+ "modified":"20151113T160117Z",
+ "date_created":"20151113T160117Z",
+ "type":"item",
+ "display_type":"",
+ "name":"Sputnik Safe",
+ "quantity":0,
+ "description":"[color=#2360D8]THE JUDGE SHOTGUN | Pixel [/color]\n[color=#2360D8]KOBUS 90 SUBMACHINE GUN | Red Stars[/color]\n[color=#2360D8]PLAINSRIDER BOW | Arctic Plains[/color]\n[color=#2360D8]GRUBER KURZ PISTOL | Little Leopard[/color]\n[color=#2360D8]HRL-7 ROCKET LAUNCHER | Headline[/color]\n[color=#2360D8]LOCOMOTIVE 12G SHOTGUN | Cosmonaut[/color]\n[color=#9900FF]FLAMETHROWER | St. Basil[/color]\n[color=#9900FF]JP36 RIFLE | Ice Leopard [/color]\n[color=#9900FF]CAR-4 RIFLE | Stripe On[/color]\n[color=#9900FF]BRONCO .44 REVOLVER | Black Bull[/color]\n[color=#FF00FF]BERNETTI 9 PISTOL | Angry Bear[/color]\n[color=#FF00FF]THANATOS .50 CAL SNIPER RIFLE | Matrjoschka[/color]\n[color=#FF00FF]M308 RIFLE | Helmet Space Program[/color]\n[color=#FF0000]CLARION RIFLE | Breaching Owl[/color]\n[color=#FF0000]MOSCONI 12G SHOTGUN | Bullet Bear Gun[/color]\n[color=#FFAA00]or an exceedingly rare special item![/color]",
+ "icon_url":"http://media.overkillsoftware.com/economy42gF2Y/safes_weapon_01.png",
+ "icon_url_large":"http://media.overkillsoftware.com/economy42gF2Y/safes_weapon_01.png",
+ "store_tags":"safe;sputnik safe;",
+ "tradable":true,
+ "marketable":true,
+ "commodity":false,
+ "drop_interval":0,
+ "drop_max_per_window":0,
+ "workshopid":"0",
+ "dsl_bonus":"false",
+ "item_name":"weapon_01",
+ "item_slot":"safes"
+ }
+*/
+
+#ifdef max
+#undef max
+#endif
+
+std::string steam_apikey;
+std::string app_id;
+
+#if defined(WIN32) || defined(_WIN32)
+#include
+
+static bool create_directory(std::string const& strPath)
+{
+ DWORD dwAttrib = GetFileAttributesA(strPath.c_str());
+
+ if (dwAttrib != INVALID_FILE_ATTRIBUTES && dwAttrib & FILE_ATTRIBUTE_DIRECTORY)
+ return true;
+
+ return CreateDirectoryA(strPath.c_str(), NULL);
+}
+#elif defined(__linux__)
+#include
+#include
+#include
+
+static bool create_directory(std::string const& strPath)
+{
+ struct stat sb;
+
+ if (stat(strPath.c_str(), &sb) != 0)
+ {
+ return mkdir(strPath.c_str(), 0755) == 0;
+ }
+ if (S_ISDIR(sb.st_mode))
+ return true;
+
+ return false;
+}
+
+#endif
+
+static void generate_achievements(CurlEasy &easy)
+{
+ std::string url = "http://api.steampowered.com/ISteamUserStats/GetSchemaForGame/v2/?key=";
+ url += steam_apikey;
+ url += "&appid=";
+ url += app_id;
+ easy.set_url(url);
+ easy.perform();
+ try
+ {
+ std::ofstream ach_file("achievements.json", std::ios::trunc | std::ios::out);
+ nlohmann::json json = nlohmann::json::parse(easy.get_answer());
+ nlohmann::json output_json = nlohmann::json::array();
+
+ bool first = true;
+ int i = 0;
+ for (auto& item : json["game"]["availableGameStats"]["achievements"].items())
+ {
+ output_json[i]["name"] = item.value()["name"];
+ output_json[i]["displayName"] = item.value()["displayName"];
+ output_json[i]["hidden"] = std::to_string(item.value()["hidden"].get());
+ try
+ {
+ if( !item.value()["description"].is_null() )
+ output_json[i]["description"] = item.value()["description"];
+ else
+ output_json[i]["description"] = "";
+ }
+ catch (...)
+ {
+ output_json[i]["description"] = "";
+ }
+
+ {
+ std::string icon_path = "images/" + item.value()["name"].get() + ".jpg";
+ std::ofstream achievement_icon(icon_path, std::ios::out | std::ios::trunc | std::ios::binary);
+ if (!achievement_icon)
+ {
+ std::cerr << "Cannot create achievement icon \"" << icon_path << "\"" << std::endl;
+ return;
+ }
+ easy.set_url(item.value()["icon"]);
+ easy.perform();
+
+ std::string picture = easy.get_answer();
+ achievement_icon.write(picture.c_str(), picture.length());
+
+ output_json[i]["icon"] = icon_path;
+
+ }
+ {
+ std::string icon_path = "images/" + item.value()["name"].get() + "_gray.jpg";
+ std::ofstream achievement_icon(icon_path, std::ios::out | std::ios::trunc | std::ios::binary);
+ if (!achievement_icon)
+ {
+ std::cerr << "Cannot create achievement icon \"" << icon_path << "\"" << std::endl;
+ return;
+ }
+ easy.set_url(item.value()["icongray"]);
+ easy.perform();
+
+ std::string picture = easy.get_answer();
+ achievement_icon.write(picture.c_str(), picture.length());
+
+ output_json[i]["icongray"] = icon_path;
+ }
+ ++i;
+ }
+ ach_file << std::setw(2) << output_json;
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Failed to get infos: ";
+ long code;
+ if (easy.get_html_code(code) == CURLE_OK && code == 403)
+ {
+ std::cerr << "Error in webapi key";
+ }
+ else
+ {
+ std::cerr << "Error while parsing json. Try to go at " << url << " and see what you can do to build your achivements.json";
+ }
+ std::cerr << std::endl;
+ }
+}
+
+static void generate_items(CurlEasy& easy)
+{
+ std::string url = "https://api.steampowered.com/IInventoryService/GetItemDefMeta/v1?key=";
+ url += steam_apikey;
+ url += "&appid=";
+ url += app_id;
+
+ easy.set_url(url);
+ easy.perform();
+
+ try
+ {
+ nlohmann::json json = nlohmann::json::parse(easy.get_answer());
+ std::string digest = json["response"]["digest"];
+
+ url = "https://api.steampowered.com/IGameInventory/GetItemDefArchive/v0001?appid=";
+ url += app_id;
+ url += "&digest=";
+ url += digest;
+
+ easy.set_url(url);
+ easy.perform();
+
+ nlohmann::json item_json = nlohmann::json::object();
+ json = nlohmann::json::parse(easy.get_answer());
+ std::ofstream items_file("items.json", std::ios::trunc | std::ios::out);
+ for (auto &i : json)
+ {
+ for (auto j = i.begin(); j != i.end(); ++j)
+ {
+ //if (j.key() == "itemdefid")
+ //{
+ // j.value() = std::stoll(j.value().get());
+ //}
+ //else
+ {
+ nlohmann::json& v = j.value();
+ switch (v.type())
+ {
+ case nlohmann::json::value_t::boolean:
+ v = (v.get() ? "true" : "false");
+ break;
+
+ case nlohmann::json::value_t::number_float:
+ v = std::to_string(v.get());
+ break;
+
+ case nlohmann::json::value_t::number_integer:
+ v = std::to_string(v.get());
+ break;
+
+ case nlohmann::json::value_t::number_unsigned:
+ v = std::to_string(v.get());
+ break;
+ }
+ }
+ }
+ item_json[i["itemdefid"].get()] = i;
+ }
+
+ items_file << std::setw(2) << item_json;
+ }
+ catch (std::exception& e)
+ {
+ std::cerr << "Failed to get infos: ";
+ long code;
+ if (easy.get_html_code(code) == CURLE_OK && code == 403)
+ {
+ std::cerr << "Error in webapi key";
+ }
+ else
+ {
+ std::cerr << "Error while parsing json. Try to go at " << url << " and see what you can do to build your items.json";
+ }
+ std::cerr << std::endl;
+ }
+}
+
+int main()
+{
+ if (!create_directory("images"))
+ {
+ std::cerr << "Cannot create directory \"images\"" << std::endl;
+ return -1;
+ }
+
+ CurlGlobal& cglobal = CurlGlobal::Inst();
+ cglobal.init();
+
+ CurlEasy easy;
+ if (easy.init())
+ {
+ std::cout << "Enter the game appid: ";
+ std::cin >> app_id;
+ std::cout << "Enter your webapi key: ";
+ std::cin.clear();
+ std::cin.ignore(std::numeric_limits::max(), '\n');
+ std::cin >> steam_apikey;
+
+ generate_achievements(easy);
+ generate_items(easy);
+ }
+}
+
\ No newline at end of file