From d0bdc387b5eb045cda4918e545deab590729d6ca Mon Sep 17 00:00:00 2001 From: JAJames Date: Sat, 30 Jan 2016 23:22:15 -0500 Subject: [PATCH] Moved 'RenX_LadderDatabase' from 'RenX.Ladder' to 'RenX::LadderDatabase' in 'RenX.Core' Added HTTPServer plugin Added RenX.Ladder.Web plugin Adder tag parsing for Ladder entries Updated Jupiter --- HTTPServer/HTTPServer.cpp | 51 ++ HTTPServer/HTTPServer.h | 75 +++ HTTPServer/HTTPServer.vcxproj | 84 ++++ HTTPServer/HTTPServer.vcxproj.filters | 35 ++ Jupiter | 2 +- Release/Bot.lib | Bin 24762 -> 24762 bytes Release/Plugins/RenX.Core.lib | Bin 172438 -> 187196 bytes RenX.Core/RenX.Core.vcxproj | 2 + RenX.Core/RenX.Core.vcxproj.filters | 6 + RenX.Core/RenX_LadderDatabase.cpp | 469 ++++++++++++++++++ RenX.Core/RenX_LadderDatabase.h | 201 ++++++++ RenX.Core/RenX_Server.cpp | 4 +- RenX.Core/RenX_Tags.cpp | 396 ++++++++++----- RenX.Core/RenX_Tags.h | 82 ++- RenX.Ladder.Web/RenX.Ladder.Web.vcxproj | 86 ++++ .../RenX.Ladder.Web.vcxproj.filters | 41 ++ RenX.Ladder.Web/RenX_Ladder_Web.cpp | 339 +++++++++++++ RenX.Ladder.Web/RenX_Ladder_Web.h | 66 +++ RenX.Ladder/RenX.Ladder.vcxproj | 4 +- RenX.Ladder/RenX.Ladder.vcxproj.filters | 6 - RenX.Ladder/RenX_Ladder.cpp | 113 +---- RenX.Ladder/RenX_Ladder.h | 15 +- RenX.Ladder/RenX_LadderDatabase.cpp | 347 ------------- RenX.Ladder/RenX_LadderDatabase.h | 158 ------ RenX.Listen/RenX_Listen.cpp | 4 +- 25 files changed, 1837 insertions(+), 749 deletions(-) create mode 100644 HTTPServer/HTTPServer.cpp create mode 100644 HTTPServer/HTTPServer.h create mode 100644 HTTPServer/HTTPServer.vcxproj create mode 100644 HTTPServer/HTTPServer.vcxproj.filters create mode 100644 RenX.Core/RenX_LadderDatabase.cpp create mode 100644 RenX.Core/RenX_LadderDatabase.h create mode 100644 RenX.Ladder.Web/RenX.Ladder.Web.vcxproj create mode 100644 RenX.Ladder.Web/RenX.Ladder.Web.vcxproj.filters create mode 100644 RenX.Ladder.Web/RenX_Ladder_Web.cpp create mode 100644 RenX.Ladder.Web/RenX_Ladder_Web.h delete mode 100644 RenX.Ladder/RenX_LadderDatabase.cpp delete mode 100644 RenX.Ladder/RenX_LadderDatabase.h diff --git a/HTTPServer/HTTPServer.cpp b/HTTPServer/HTTPServer.cpp new file mode 100644 index 0000000..96c0b56 --- /dev/null +++ b/HTTPServer/HTTPServer.cpp @@ -0,0 +1,51 @@ +/** + * Copyright (C) 2015-2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "Jupiter/INIFile.h" +#include "Jupiter/IRC_Client.h" +#include "HTTPServer.h" + +using namespace Jupiter::literals; + +HTTPServerPlugin::HTTPServerPlugin() +{ + HTTPServerPlugin::server.bind(Jupiter::IRC::Client::Config->get(HTTPServerPlugin::name, "BindAddress"_jrs, "0.0.0.0"_jrs), Jupiter::IRC::Client::Config->getInt(HTTPServerPlugin::name, "BindPort"_jrs, 80)); +} + +int HTTPServerPlugin::think() +{ + return HTTPServerPlugin::server.think(); +} + +// Plugin instantiation and entry point. +HTTPServerPlugin pluginInstance; + +HTTPServerPlugin &getHTTPServerPlugin() +{ + return pluginInstance; +} + +Jupiter::HTTP::Server &getHTTPServer() +{ + return pluginInstance.server; +} + +extern "C" __declspec(dllexport) Jupiter::Plugin *getPlugin() +{ + return &pluginInstance; +} diff --git a/HTTPServer/HTTPServer.h b/HTTPServer/HTTPServer.h new file mode 100644 index 0000000..0dabdee --- /dev/null +++ b/HTTPServer/HTTPServer.h @@ -0,0 +1,75 @@ +/** + * Copyright (C) 2015 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#if !defined _HTTPSERVER_H_HEADER +#define _HTTPSERVER_H_HEADER + + /** + * @file HTTPServer.h + * @brief Provides an interface to push HTTP data to HTTP clients. + */ + +#if defined _WIN32 + +#include "Jupiter/Plugin.h" +#include "Jupiter/Reference_String.h" +#include "Jupiter/String.h" +#include "Jupiter/HTTP_Server.h" + +#if defined HTTPSERVER_EXPORTS +#define HTTPSERVER_API __declspec(dllexport) +#else // HTTPSERVER_EXPORTS +#define HTTPSERVER_API __declspec(dllimport) +#endif // HTTPSERVER_EXPORTS + +#else // _WIN32 +#define HTTPSERVER_API +#endif // _WIN32 + +/** DLL Linkage Nagging */ +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + +/** +* @brief Instantiates an instance of Jupiter::HTTP::Server to permit declaration of HTTP pages. +*/ +class HTTPSERVER_API HTTPServerPlugin : public Jupiter::Plugin +{ +public: + HTTPServerPlugin(); + Jupiter::HTTP::Server server; + +public: // Jupiter::Plugin + const Jupiter::ReadableString &getName() override { return name; } + int think() override; + +private: + STRING_LITERAL_AS_NAMED_REFERENCE(name, "HTTPServer"); +}; + +HTTPSERVER_API HTTPServerPlugin &getHTTPServerPlugin(); +HTTPSERVER_API Jupiter::HTTP::Server &getHTTPServer(); + +/** Re-enable warnings */ +#if defined _MSC_VER +#pragma warning(pop) +#endif + +#endif // _HTTPSERVER_H_HEADER \ No newline at end of file diff --git a/HTTPServer/HTTPServer.vcxproj b/HTTPServer/HTTPServer.vcxproj new file mode 100644 index 0000000..52d83e0 --- /dev/null +++ b/HTTPServer/HTTPServer.vcxproj @@ -0,0 +1,84 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {BB048D6F-F001-4E9B-95F4-886081E0807A} + HTTPServer + + + + Application + true + v140 + MultiByte + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + $(SolutionDir)$(Configuration)\Plugins\ + AllRules.ruleset + + + + Level3 + Disabled + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + ../Bot;../Jupiter + _CRT_SECURE_NO_WARNINGS;HTTPSERVER_EXPORTS;%(PreprocessorDefinitions) + + + true + true + true + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/HTTPServer/HTTPServer.vcxproj.filters b/HTTPServer/HTTPServer.vcxproj.filters new file mode 100644 index 0000000..2db592d --- /dev/null +++ b/HTTPServer/HTTPServer.vcxproj.filters @@ -0,0 +1,35 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Resource Files + + + Resource Files + + + + + Header Files + + + + + Source Files + + + \ No newline at end of file diff --git a/Jupiter b/Jupiter index 993a313..0fba343 160000 --- a/Jupiter +++ b/Jupiter @@ -1 +1 @@ -Subproject commit 993a313b81c7fa061d0101bdc2ac101b8b5e2114 +Subproject commit 0fba343102afa3d397e613c85ca451d0085522f8 diff --git a/Release/Bot.lib b/Release/Bot.lib index e6a1efb37a7afa6de2ef0d6fd0c9159b4570d9cb..8016e2f30f9c6a163e2f99347094c8e515b42e2a 100644 GIT binary patch delta 1981 zcmZuxJ4{ni7;Xgwg-E5)i=t2j1;t1)JS?RzS_*yBLMyeU1%qw|7D7yP&;%DBnEY`v z#4!W-Cv-ZUURMm?kJrLIcR6GoMh#Dm z-|j1jmvv)yhlc;cZmgiXO+>et{6Jr6gk5{HhV;mTqtuvAc&zci$G(LB=BvT%LgJ*Xq&6lIFdx7DK zXL*wCq?uJD*WUhC(#vCvL;b5hO9pr@8oM)qx#k9j7+=9<%{HQ0Lo{ESo1wwSHO)Z@ z+R_+1?c+1Zli7UoA^yvkWSZ22I?MS%VU_~^LxYSmq!GhJ#raNDCrEmItyM)Hc-? zLOd4YhQwxXg|yA?3L`%lj&S|3Qh)}Nz#2hrG%_r3C3qDv8tB*}3pN{q<79S^Souv5J%|TK;-xgZTq;ghY zl3bI1hOfv$+K&32b(KHNlgoG#ZUoQx4xxHo^l}HDm_#Dqb;u zr@tg#){WW+RQy+VeGS!ZBf90}2ijUa?AhB@q^CnjY9^!#BpG1ARaKIGVF2B~80hB$ zK?(jSc!Oh{;kwv4+0l|C&M@Z$VrQK(hMC7C*quP#Rp%{+lPN*ojd<1-V0ghR!Ilg0 zJ=dTLcMKw)7*uyb?CufL-S=fpkRB2TE>A%AYaLR@lIFt+0y`x3(y z&+;Tqq?vUj*Z#py(#sQ!L;V+hRt)o8G+hYB}9XcX(cHY(PO^ZYpKW7;zr#KK;V z6}*Uj@bX+bA7Xajv=A%7J0C7%C#kiCF5^+15q|slu#A2T4Ei%HkQPLSEDvPQuWqU> zfOs^(4T;U(4yc>m6GXm07~=Xtr2rKsfi;BOa3~^hC3q9k>*(28Nf8~S2*=d)G(=N9 za64Adh~_YgPKK{DOy`qeHHM-;!@M&TzZpS%A#zp4uZ`l{;U~$u$aZWR+|WExrOHJM zUW{Y>W|S9Pyi$9NU#a3jem6#l<^}R7!1GE6I3j%I%wvlAv3viTIYcX`X3VgiXai^5 z!5E4SnQ@*lF3OBg@fOA?HpP_SO%bQHmEh*-(_ufFzn{sIwj BWrqL& diff --git a/Release/Plugins/RenX.Core.lib b/Release/Plugins/RenX.Core.lib index bb052b49269cb19609334adcf2d3178257a1d205..8d90ffcb6649fdf279f911f2d76d40d0271c2984 100644 GIT binary patch literal 187196 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 delta 21512 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 diff --git a/RenX.Core/RenX.Core.vcxproj b/RenX.Core/RenX.Core.vcxproj index d9de636..896baa5 100644 --- a/RenX.Core/RenX.Core.vcxproj +++ b/RenX.Core/RenX.Core.vcxproj @@ -77,6 +77,7 @@ + @@ -89,6 +90,7 @@ + diff --git a/RenX.Core/RenX.Core.vcxproj.filters b/RenX.Core/RenX.Core.vcxproj.filters index 28e0f83..b1c8e83 100644 --- a/RenX.Core/RenX.Core.vcxproj.filters +++ b/RenX.Core/RenX.Core.vcxproj.filters @@ -59,6 +59,9 @@ Header Files + + Header Files + @@ -88,5 +91,8 @@ Source Files + + Source Files + \ No newline at end of file diff --git a/RenX.Core/RenX_LadderDatabase.cpp b/RenX.Core/RenX_LadderDatabase.cpp new file mode 100644 index 0000000..ba0e9d8 --- /dev/null +++ b/RenX.Core/RenX_LadderDatabase.cpp @@ -0,0 +1,469 @@ +/** +* Copyright (C) 2015-2016 Jessica James. +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +* +* Written by Jessica James +*/ + +#include "RenX_LadderDatabase.h" +#include "RenX_Server.h" +#include "RenX_PlayerInfo.h" +#include "RenX_BanDatabase.h" + +Jupiter::ArrayList _ladder_databases; +Jupiter::ArrayList &RenX::ladder_databases = _ladder_databases; + +RenX::LadderDatabase::LadderDatabase() +{ + _ladder_databases.add(this); +} + +RenX::LadderDatabase::~LadderDatabase() +{ + while (RenX::LadderDatabase::head != nullptr) + { + RenX::LadderDatabase::end = RenX::LadderDatabase::head; + RenX::LadderDatabase::head = RenX::LadderDatabase::head->next; + delete RenX::LadderDatabase::end; + } +} + +void RenX::LadderDatabase::process_data(Jupiter::DataBuffer &buffer, FILE *file, fpos_t pos) +{ + RenX::LadderDatabase::Entry *entry = new RenX::LadderDatabase::Entry(); + + // read data from buffer to entry + entry->steam_id = buffer.pop(); + entry->total_score = buffer.pop(); + + entry->total_kills = buffer.pop(); + entry->total_deaths = buffer.pop(); + entry->total_headshot_kills = buffer.pop(); + entry->total_vehicle_kills = buffer.pop(); + entry->total_building_kills = buffer.pop(); + entry->total_defence_kills = buffer.pop(); + entry->total_captures = buffer.pop(); + entry->total_game_time = buffer.pop(); + entry->total_games = buffer.pop(); + entry->total_gdi_games = buffer.pop(); + entry->total_nod_games = buffer.pop(); + entry->total_wins = buffer.pop(); + entry->total_gdi_wins = buffer.pop(); + entry->total_nod_wins = buffer.pop(); + entry->total_beacon_placements = buffer.pop(); + entry->total_beacon_disarms = buffer.pop(); + entry->total_proxy_placements = buffer.pop(); + entry->total_proxy_disarms = buffer.pop(); + + entry->top_score = buffer.pop(); + entry->top_kills = buffer.pop(); + entry->most_deaths = buffer.pop(); + entry->top_headshot_kills = buffer.pop(); + entry->top_vehicle_kills = buffer.pop(); + entry->top_building_kills = buffer.pop(); + entry->top_defence_kills = buffer.pop(); + entry->top_captures = buffer.pop(); + entry->top_game_time = buffer.pop(); + entry->top_beacon_placements = buffer.pop(); + entry->top_beacon_disarms = buffer.pop(); + entry->top_proxy_placements = buffer.pop(); + entry->top_proxy_disarms = buffer.pop(); + + entry->most_recent_ip = buffer.pop(); + entry->last_game = buffer.pop(); + entry->most_recent_name = buffer.pop(); + + // push data to list + if (RenX::LadderDatabase::head == nullptr) + { + RenX::LadderDatabase::head = entry; + RenX::LadderDatabase::end = RenX::LadderDatabase::head; + } + else + { + RenX::LadderDatabase::end->next = entry; + entry->prev = end; + end = entry; + } + + entry->rank = ++RenX::LadderDatabase::entries; +} + +void RenX::LadderDatabase::process_header(FILE *file) +{ + int chr = fgetc(file); + if (chr != EOF) + RenX::LadderDatabase::read_version = chr; +} + +void RenX::LadderDatabase::create_header(FILE *file) +{ + fputc(RenX::LadderDatabase::write_version, file); +} + +RenX::LadderDatabase::Entry *RenX::LadderDatabase::getHead() const +{ + return RenX::LadderDatabase::head; +} + +RenX::LadderDatabase::Entry *RenX::LadderDatabase::getPlayerEntry(uint64_t steamid) const +{ + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next) + if (itr->steam_id == steamid) + return itr; + return nullptr; +} + +std::pair RenX::LadderDatabase::getPlayerEntryAndIndex(uint64_t steamid) const +{ + size_t index = 0; + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) + if (itr->steam_id == steamid) + return std::pair(itr, index); + return std::pair(nullptr, Jupiter::INVALID_INDEX); +} + +RenX::LadderDatabase::Entry *RenX::LadderDatabase::getPlayerEntryByName(const Jupiter::ReadableString &name) const +{ + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next) + if (itr->most_recent_name.equalsi(name)) + return itr; + return nullptr; +} + +std::pair RenX::LadderDatabase::getPlayerEntryAndIndexByName(const Jupiter::ReadableString &name) const +{ + size_t index = 0; + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) + if (itr->most_recent_name.equalsi(name)) + return std::pair(itr, index); + return std::pair(nullptr, Jupiter::INVALID_INDEX); +} + +RenX::LadderDatabase::Entry *RenX::LadderDatabase::getPlayerEntryByPartName(const Jupiter::ReadableString &name) const +{ + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next) + if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) + return itr; + return nullptr; +} + +std::pair RenX::LadderDatabase::getPlayerEntryAndIndexByPartName(const Jupiter::ReadableString &name) const +{ + size_t index = 0; + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) + if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) + return std::pair(itr, index); + return std::pair(nullptr, Jupiter::INVALID_INDEX); +} + +Jupiter::SLList RenX::LadderDatabase::getPlayerEntriesByPartName(const Jupiter::ReadableString &name, size_t max) const +{ + Jupiter::SLList list; + if (max == 0) + { + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next) + if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) + list.add(new RenX::LadderDatabase::Entry(*itr)); + } + else + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next) + if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) + { + list.add(new RenX::LadderDatabase::Entry(*itr)); + if (--max == 0) + return list; + } + return list; +} + +Jupiter::SLList> RenX::LadderDatabase::getPlayerEntriesAndIndexByPartName(const Jupiter::ReadableString &name, size_t max) const +{ + Jupiter::SLList> list; + size_t index = 0; + if (max == 0) + { + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) + if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) + list.add(new std::pair(*itr, index)); + } + else + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) + if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) + { + list.add(new std::pair(*itr, index)); + if (--max) + return list; + } + return list; +} + +RenX::LadderDatabase::Entry *RenX::LadderDatabase::getPlayerEntryByIndex(size_t index) const +{ + for (RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; itr != nullptr; itr = itr->next, --index) + if (index == 0) + return itr; + return nullptr; +} + +size_t RenX::LadderDatabase::getEntries() const +{ + return RenX::LadderDatabase::entries; +} + +std::chrono::steady_clock::time_point RenX::LadderDatabase::getLastSortTime() const +{ + return RenX::LadderDatabase::last_sort; +} + +void RenX::LadderDatabase::append(RenX::LadderDatabase::Entry *entry) +{ + ++RenX::LadderDatabase::entries; + if (RenX::LadderDatabase::head == nullptr) + { + RenX::LadderDatabase::head = entry; + RenX::LadderDatabase::end = RenX::LadderDatabase::head; + return; + } + RenX::LadderDatabase::end->next = entry; + entry->prev = RenX::LadderDatabase::end; + RenX::LadderDatabase::end = entry; +} + +void RenX::LadderDatabase::write(const Jupiter::CStringType &filename) +{ + return RenX::LadderDatabase::write(filename.c_str()); +} + +void RenX::LadderDatabase::write(const char *filename) +{ + if (RenX::LadderDatabase::entries != 0) + { + FILE *file = fopen(filename, "wb"); + if (file != nullptr) + { + size_t rank = 0; + Jupiter::DataBuffer buffer; + RenX::LadderDatabase::create_header(file); + RenX::LadderDatabase::Entry *entry = RenX::LadderDatabase::head; + while (entry != nullptr) + { + // update rank + entry->rank = ++rank; + + // push data from entry to buffer + buffer.push(entry->steam_id); + buffer.push(entry->total_score); + + buffer.push(entry->total_kills); + buffer.push(entry->total_deaths); + buffer.push(entry->total_headshot_kills); + buffer.push(entry->total_vehicle_kills); + buffer.push(entry->total_building_kills); + buffer.push(entry->total_defence_kills); + buffer.push(entry->total_captures); + buffer.push(entry->total_game_time); + buffer.push(entry->total_games); + buffer.push(entry->total_gdi_games); + buffer.push(entry->total_nod_games); + buffer.push(entry->total_wins); + buffer.push(entry->total_gdi_wins); + buffer.push(entry->total_nod_wins); + buffer.push(entry->total_beacon_placements); + buffer.push(entry->total_beacon_disarms); + buffer.push(entry->total_proxy_placements); + buffer.push(entry->total_proxy_disarms); + + buffer.push(entry->top_score); + buffer.push(entry->top_kills); + buffer.push(entry->most_deaths); + buffer.push(entry->top_headshot_kills); + buffer.push(entry->top_vehicle_kills); + buffer.push(entry->top_building_kills); + buffer.push(entry->top_defence_kills); + buffer.push(entry->top_captures); + buffer.push(entry->top_game_time); + buffer.push(entry->top_beacon_placements); + buffer.push(entry->top_beacon_disarms); + buffer.push(entry->top_proxy_placements); + buffer.push(entry->top_proxy_disarms); + + buffer.push(entry->most_recent_ip); + buffer.push(entry->last_game); + buffer.push(entry->most_recent_name); + + // push buffer to file + buffer.push_to(file); + + // iterate + entry = entry->next; + } + fclose(file); + } + } +} + +void RenX::LadderDatabase::sort_entries() +{ + if (RenX::LadderDatabase::entries <= 1) + return; + + RenX::LadderDatabase::Entry *itr = RenX::LadderDatabase::head; + RenX::LadderDatabase::Entry *itr2, *ptr; + + // iterate forward (search for out-of-order content) + while (itr->next != nullptr) + { + // out-of-order content found + if (itr->next->total_score > itr->total_score) + { + // pull content out + ptr = itr->next; + itr->next = ptr->next; + if (itr->next != nullptr) + itr->next->prev = itr; + + // iterate backwards from our iterator, and insert + itr2 = itr; + while (true) + { + if (itr2->prev == nullptr) + { + // push ptr to head + ptr->next = itr2; + ptr->prev = nullptr; + itr2->prev = ptr; + RenX::LadderDatabase::head = ptr; + break; + } + itr2 = itr2->prev; + if (itr2->total_score > ptr->total_score) + { + // insert ptr after itr2 + ptr->next = itr2->next; + ptr->next->prev = ptr; + ptr->prev = itr2; + itr2->next = ptr; + break; + } + } + } + else // continue iterating + itr = itr->next; + } + + RenX::LadderDatabase::end = itr; + RenX::LadderDatabase::last_sort = std::chrono::steady_clock::now(); +} + +void RenX::LadderDatabase::updateLadder(RenX::Server *server, const RenX::TeamType &team, bool output_times) +{ + if (server->players.size() != 0) + { + // update player stats in memory + RenX::PlayerInfo *player; + RenX::LadderDatabase::Entry *entry; + for (Jupiter::DLList::Node *node = server->players.getNode(0); node != nullptr; node = node->next) + { + player = node->data; + if (player->steamid != 0 && (player->ban_flags & RenX::BanDatabase::Entry::FLAG_TYPE_LADDER) == 0) + { + entry = RenX::LadderDatabase::getPlayerEntry(player->steamid); + if (entry == nullptr) + { + entry = new RenX::LadderDatabase::Entry(); + RenX::LadderDatabase::append(entry); + entry->steam_id = player->steamid; + } + + entry->total_score += static_cast(player->score); + + entry->total_kills += player->kills; + entry->total_deaths += player->deaths; + entry->total_headshot_kills += player->headshots; + entry->total_vehicle_kills += player->vehicleKills; + entry->total_building_kills += player->buildingKills; + entry->total_defence_kills += player->defenceKills; + entry->total_captures += player->captures; + entry->total_game_time += static_cast(std::chrono::duration_cast(server->getGameTime(player)).count()); + ++entry->total_games; + switch (player->team) + { + case RenX::TeamType::GDI: + ++entry->total_gdi_games; + if (player->team == team) + ++entry->total_wins, ++entry->total_gdi_wins; + break; + case RenX::TeamType::Nod: + ++entry->total_nod_games; + if (player->team == team) + ++entry->total_wins, ++entry->total_nod_wins; + break; + default: + if (player->team == team) + ++entry->total_wins; + break; + } + entry->total_beacon_placements += player->beaconPlacements; + entry->total_beacon_disarms += player->beaconDisarms; + entry->total_proxy_placements += player->proxy_placements; + entry->total_proxy_disarms += player->proxy_disarms; + + auto set_if_greater = [](uint32_t &src, const uint32_t &cmp) + { + if (cmp > src) + src = cmp; + }; + + set_if_greater(entry->top_score, static_cast(player->score)); + set_if_greater(entry->top_kills, player->kills); + set_if_greater(entry->most_deaths, player->deaths); + set_if_greater(entry->top_headshot_kills, player->headshots); + set_if_greater(entry->top_vehicle_kills, player->vehicleKills); + set_if_greater(entry->top_building_kills, player->buildingKills); + set_if_greater(entry->top_defence_kills, player->defenceKills); + set_if_greater(entry->top_captures, player->captures); + set_if_greater(entry->top_game_time, static_cast(std::chrono::duration_cast(server->getGameTime(player)).count())); + set_if_greater(entry->top_beacon_placements, player->beaconPlacements); + set_if_greater(entry->top_beacon_disarms, player->beaconDisarms); + set_if_greater(entry->top_proxy_placements, player->proxy_placements); + set_if_greater(entry->top_proxy_disarms, player->proxy_disarms); + + entry->most_recent_ip = player->ip32; + entry->last_game = time(nullptr); + entry->most_recent_name = player->name; + } + } + + // sort new stats + std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); + RenX::LadderDatabase::sort_entries(); + std::chrono::steady_clock::duration sort_duration = std::chrono::steady_clock::now() - start_time; + + // write new stats + start_time = std::chrono::steady_clock::now(); + RenX::LadderDatabase::write(this->getFilename()); + std::chrono::steady_clock::duration write_duration = std::chrono::steady_clock::now() - start_time; + + if (output_times) + { + Jupiter::StringS str = Jupiter::StringS::Format("Ladder: %u entries sorted in %f seconds; Database written in %f seconds." ENDL, + RenX::LadderDatabase::getEntries(), + static_cast(sort_duration.count()) * (static_cast(std::chrono::steady_clock::duration::period::num / static_cast(std::chrono::steady_clock::duration::period::den) * static_cast(std::chrono::seconds::duration::period::den / std::chrono::seconds::duration::period::num))), + static_cast(write_duration.count()) * (static_cast(std::chrono::steady_clock::duration::period::num) / static_cast(std::chrono::steady_clock::duration::period::den) * static_cast(std::chrono::seconds::duration::period::den / std::chrono::seconds::duration::period::num))); + str.println(stdout); + server->sendLogChan(str); + } + } +} diff --git a/RenX.Core/RenX_LadderDatabase.h b/RenX.Core/RenX_LadderDatabase.h new file mode 100644 index 0000000..422158d --- /dev/null +++ b/RenX.Core/RenX_LadderDatabase.h @@ -0,0 +1,201 @@ +/** +* Copyright (C) 2015-2016 Jessica James. +* +* Permission to use, copy, modify, and/or distribute this software for any +* purpose with or without fee is hereby granted, provided that the above +* copyright notice and this permission notice appear in all copies. +* +* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY +* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN +* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +* +* Written by Jessica James +*/ + +#if !defined _RENX_LADDERDATABASE_H_HEADER +#define _RENX_LADDERDATABASE_H_HEADER + +#include +#include "Jupiter/Database.h" +#include "Jupiter/String.h" +#include "Jupiter/SLList.h" +#include "Jupiter/ArrayList.h" +#include "RenX.h" + +/** DLL Linkage Nagging */ +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + +namespace RenX +{ + class Server; + + class RENX_API LadderDatabase : public Jupiter::Database + { + public: // Jupiter::Database + /** + * @brief Processes a chunk of data in a database. + * + * @param buffer Buffer to process + * @param file File being processed + * @param pos position that the buffer starts at in the file + */ + void process_data(Jupiter::DataBuffer &buffer, FILE *file, fpos_t pos) override; + + /** + * @brief Processes the header for a database. + * + * @param file File being processed + */ + void process_header(FILE *file) override; + + /** + * @brief Generates a header for a database. + * + * @param file File being created + */ + void create_header(FILE *file) override; + + public: // LadderDatabase + struct RENX_API Entry + { + size_t rank; + + uint64_t steam_id, total_score; + uint32_t total_kills, total_deaths, total_headshot_kills, total_vehicle_kills, total_building_kills, total_defence_kills, total_captures, total_game_time, total_games, total_gdi_games, total_nod_games, total_wins, total_gdi_wins, total_nod_wins, total_beacon_placements, total_beacon_disarms, total_proxy_placements, total_proxy_disarms, // totals (15) + top_score, top_kills, most_deaths, top_headshot_kills, top_vehicle_kills, top_building_kills, top_defence_kills, top_captures, top_game_time, top_beacon_placements, top_beacon_disarms, top_proxy_placements, top_proxy_disarms, // tops (12) + most_recent_ip; // other (1) + time_t last_game; + Jupiter::StringS most_recent_name; + Entry *next = nullptr; + Entry *prev = nullptr; + }; + + /** + * @brief Fetches the head of the entry list. + * + * @return Head of the ladder entry list. + */ + Entry *getHead() const; + + /** + * @brief Fetches a ladder entry by Steam ID. + * + * @param steamid Steam ID to search ladder for + * @return Ladder entry with a matching steamid if one exists, nullptr otherwise. + */ + Entry *getPlayerEntry(uint64_t steamid) const; + std::pair getPlayerEntryAndIndex(uint64_t steamid) const; + + /** + * @brief Searches for a ladder entry by name + * + * @param name Name to search ladder for + * @return Ladder entry with a matching name if one exists, nullptr otherwise. + */ + Entry *getPlayerEntryByName(const Jupiter::ReadableString &name) const; + std::pair getPlayerEntryAndIndexByName(const Jupiter::ReadableString &name) const; + + /** + * @brief Searches for a ladder entry by part name + * + * @param name Part of name to search ladder for + * @return Ladder entry with a matching name if one exists, nullptr otherwise. + */ + Entry *getPlayerEntryByPartName(const Jupiter::ReadableString &name) const; + std::pair getPlayerEntryAndIndexByPartName(const Jupiter::ReadableString &name) const; + + /** + * @brief Fetches all entries matching a part name. + * + * @param name Part of name to search for + * @param max Maximum number of entries to return + * @return List containing entries with matching names. + */ + Jupiter::SLList getPlayerEntriesByPartName(const Jupiter::ReadableString &name, size_t max) const; + Jupiter::SLList> getPlayerEntriesAndIndexByPartName(const Jupiter::ReadableString &name, size_t max) const; + + /** + * @brief Fetches a ladder entry at a specified index + * + * @param index Index of the element to fetch + * @return Ladder entry with a matching name if one exists, nullptr otherwise. + */ + Entry *getPlayerEntryByIndex(size_t index) const; + + /** + * @brief Fetches the total number of ladder entries in the list. + * + * @return Total number of entries. + */ + size_t getEntries() const; + + /** + * @brief Returns the last time that the contents of this database object were modified (i.e: initialized or written). + */ + std::chrono::steady_clock::time_point getLastSortTime() const; + + /** + * @brief Places a ladder entry at the end of the list, regardless of order + * Note: This does not copy data from the pointer -- the pointer is added to the list. + * + * @param entry Ladder entry to add + */ + void append(Entry *entry); + + /** + * @brief Writes the current ladder data to the disk. + */ + void write(const Jupiter::CStringType &filename); + void write(const char *filename); + + /** + * @brief Sorts the ladder data in memory. + */ + void sort_entries(); + + /** + * @brief Pushes the player data from the server into the ladder, sorts the data, and writes it to file storage. + * + * @param server Renegade-X server to pull player data from + * @param team Team which just won + * @param output_times True if the sort/write times should be output, false otherwise. + */ + void updateLadder(RenX::Server *server, const RenX::TeamType &team, bool output_times); + + /** + * @brief Constructor for the LadderDatabase class + */ + LadderDatabase(); + + /** + * @brief Deconstructor for the LadderDatabase class + */ + ~LadderDatabase(); + private: + + /** Database version */ + const uint8_t write_version = 0; + uint8_t read_version = write_version; + + std::chrono::steady_clock::time_point last_sort = std::chrono::steady_clock::now(); + size_t entries = 0; + Entry *head = nullptr; + Entry *end; + }; + + RENX_API extern Jupiter::ArrayList &ladder_databases; +} + +/** Re-enable warnings */ +#if defined _MSC_VER +#pragma warning(pop) +#endif + +#endif //_RENX_LADDERDATABASE_H_HEADER \ No newline at end of file diff --git a/RenX.Core/RenX_Server.cpp b/RenX.Core/RenX_Server.cpp index 679f404..d92057a 100644 --- a/RenX.Core/RenX_Server.cpp +++ b/RenX.Core/RenX_Server.cpp @@ -2762,7 +2762,7 @@ void RenX::Server::disconnect(RenX::DisconnectReason reason) for (size_t i = 0; i < xPlugins.size(); i++) xPlugins.get(i)->RenX_OnServerDisconnect(this, reason); - RenX::Server::sock.closeSocket(); + RenX::Server::sock.close(); RenX::Server::wipeData(); RenX::Server::connected = false; } @@ -2916,7 +2916,7 @@ void RenX::Server::init() RenX::Server::~Server() { - sock.closeSocket(); + sock.close(); RenX::Server::wipeData(); RenX::Server::commands.emptyAndDelete(); } diff --git a/RenX.Core/RenX_Tags.cpp b/RenX.Core/RenX_Tags.cpp index d0a3e88..257b4c6 100644 --- a/RenX.Core/RenX_Tags.cpp +++ b/RenX.Core/RenX_Tags.cpp @@ -1,5 +1,5 @@ /** - * Copyright (C) 2015 Jessica James. + * Copyright (C) 2015-2016 Jessica James. * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -27,10 +27,13 @@ #include "RenX_Plugin.h" #include "RenX_Tags.h" +using namespace Jupiter::literals; + struct TagsImp : RenX::Tags { TagsImp(); void processTags(Jupiter::StringType &msg, const RenX::Server *server, const RenX::PlayerInfo *player, const RenX::PlayerInfo *victim, const RenX::BuildingInfo *building); + void processTags(Jupiter::StringType &msg, const RenX::LadderDatabase::Entry &entry); void sanitizeTags(Jupiter::StringType &fmt); const Jupiter::ReadableString &getUniqueInternalTag(); Jupiter::StringS get_building_health_bar(const RenX::BuildingInfo *building); @@ -54,14 +57,14 @@ RenX::Tags *RenX::tags = &_tags; TagsImp::TagsImp() { this->tagItr = 0; - this->uniqueTag = STRING_LITERAL_AS_REFERENCE("\0\0\0\0\0\0"); + this->uniqueTag = "\0\0\0\0\0\0"_jrs; - const Jupiter::ReadableString &configSection = Jupiter::IRC::Client::Config->get(STRING_LITERAL_AS_REFERENCE("RenX"), STRING_LITERAL_AS_REFERENCE("TagDefinitions"), STRING_LITERAL_AS_REFERENCE("RenX.Tags")); - TagsImp::bar_width = Jupiter::IRC::Client::Config->getInt(configSection, STRING_LITERAL_AS_REFERENCE("BarWidth"), 19); + const Jupiter::ReadableString &configSection = Jupiter::IRC::Client::Config->get("RenX"_jrs, "TagDefinitions"_jrs, "RenX.Tags"_jrs); + TagsImp::bar_width = Jupiter::IRC::Client::Config->getInt(configSection, "BarWidth"_jrs, 19); /** Global formats */ - this->dateFmt = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("DateFormat"), STRING_LITERAL_AS_REFERENCE("%A, %B %d, %Y")); - this->timeFmt = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("TimeFormat"), STRING_LITERAL_AS_REFERENCE("%H:%M:%S"));; + this->dateFmt = Jupiter::IRC::Client::Config->get(configSection, "DateFormat"_jrs, "%A, %B %d, %Y"_jrs); + this->timeFmt = Jupiter::IRC::Client::Config->get(configSection, "TimeFormat"_jrs, "%H:%M:%S"_jrs);; /** Internal message tags */ @@ -101,6 +104,7 @@ TagsImp::TagsImp() this->INTERNAL_TEAM_LONG_TAG = this->getUniqueInternalTag(); this->INTERNAL_PING_TAG = this->getUniqueInternalTag(); this->INTERNAL_SCORE_TAG = this->getUniqueInternalTag(); + this->INTERNAL_SCORE_PER_MINUTE_TAG = this->getUniqueInternalTag(); this->INTERNAL_CREDITS_TAG = this->getUniqueInternalTag(); this->INTERNAL_KILLS_TAG = this->getUniqueInternalTag(); this->INTERNAL_DEATHS_TAG = this->getUniqueInternalTag(); @@ -110,10 +114,24 @@ TagsImp::TagsImp() this->INTERNAL_VEHICLE_KILLS_TAG = this->getUniqueInternalTag(); this->INTERNAL_BUILDING_KILLS_TAG = this->getUniqueInternalTag(); this->INTERNAL_DEFENCE_KILLS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_GAME_TIME_TAG = this->getUniqueInternalTag(); + this->INTERNAL_GAMES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_GDI_GAMES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_NOD_GAMES_TAG = this->getUniqueInternalTag(); this->INTERNAL_WINS_TAG = this->getUniqueInternalTag(); - this->INTERNAL_LOSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_GDI_WINS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_NOD_WINS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_TIES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_LOSSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_GDI_LOSSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_NOD_LOSSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_WIN_LOSS_RATIO_TAG = this->getUniqueInternalTag(); + this->INTERNAL_GDI_WIN_LOSS_RATIO_TAG = this->getUniqueInternalTag(); + this->INTERNAL_NOD_WIN_LOSS_RATIO_TAG = this->getUniqueInternalTag(); this->INTERNAL_BEACON_PLACEMENTS_TAG = this->getUniqueInternalTag(); this->INTERNAL_BEACON_DISARMS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_PROXY_PLACEMENTS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_PROXY_DISARMS_TAG = this->getUniqueInternalTag(); this->INTERNAL_CAPTURES_TAG = this->getUniqueInternalTag(); this->INTERNAL_STEALS_TAG = this->getUniqueInternalTag(); this->INTERNAL_STOLEN_TAG = this->getUniqueInternalTag(); @@ -146,10 +164,24 @@ TagsImp::TagsImp() this->INTERNAL_VICTIM_VEHICLE_KILLS_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_BUILDING_KILLS_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_DEFENCE_KILLS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_GAME_TIME_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_GAMES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_GDI_GAMES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_NOD_GAMES_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_WINS_TAG = this->getUniqueInternalTag(); - this->INTERNAL_VICTIM_LOSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_GDI_WINS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_NOD_WINS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_TIES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_LOSSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_GDI_LOSSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_NOD_LOSSES_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_WIN_LOSS_RATIO_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_GDI_WIN_LOSS_RATIO_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_NOD_WIN_LOSS_RATIO_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_BEACON_PLACEMENTS_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_BEACON_DISARMS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_PROXY_PLACEMENTS_TAG = this->getUniqueInternalTag(); + this->INTERNAL_VICTIM_PROXY_DISARMS_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_CAPTURES_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_STEALS_TAG = this->getUniqueInternalTag(); this->INTERNAL_VICTIM_STOLEN_TAG = this->getUniqueInternalTag(); @@ -173,117 +205,151 @@ TagsImp::TagsImp() this->INTERNAL_NEW_NAME_TAG = this->getUniqueInternalTag(); this->INTERNAL_WIN_SCORE_TAG = this->getUniqueInternalTag(); this->INTERNAL_LOSE_SCORE_TAG = this->getUniqueInternalTag(); + this->INTERNAL_LAST_GAME_TAG = this->getUniqueInternalTag(); + this->INTERNAL_RANK_TAG = this->getUniqueInternalTag(); /** External (config) tags */ /** Global tags */ - this->dateTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("DateTag"), STRING_LITERAL_AS_REFERENCE("{DATE}")); - this->timeTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("TimeTag"), STRING_LITERAL_AS_REFERENCE("{TIME}")); + this->dateTag = Jupiter::IRC::Client::Config->get(configSection, "DateTag"_jrs, "{DATE}"_jrs); + this->timeTag = Jupiter::IRC::Client::Config->get(configSection, "TimeTag"_jrs, "{TIME}"_jrs); /** Server tags */ - this->rconVersionTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("RCONVersionTag"), STRING_LITERAL_AS_REFERENCE("{RVER}")); - this->gameVersionTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("GameVersionTag"), STRING_LITERAL_AS_REFERENCE("{GVER}")); - this->rulesTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("RulesTag"), STRING_LITERAL_AS_REFERENCE("{RULES}")); - this->userTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("UserTag"), STRING_LITERAL_AS_REFERENCE("{USER}")); - this->serverNameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("ServerNameTag"), STRING_LITERAL_AS_REFERENCE("{SERVERNAME}")); - this->mapTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("MapTag"), STRING_LITERAL_AS_REFERENCE("{MAP}")); - this->mapGUIDTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("MapGUIDTag"), STRING_LITERAL_AS_REFERENCE("{MGUID}")); - this->serverHostnameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("ServerHostnameTag"), STRING_LITERAL_AS_REFERENCE("{SERVERHOST}")); - this->serverPortTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("ServerPortTag"), STRING_LITERAL_AS_REFERENCE("{SERVERPORT}")); - this->socketHostnameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("SocketHostnameTag"), STRING_LITERAL_AS_REFERENCE("{SOCKHOST}")); - this->socketPortTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("SocketPortTag"), STRING_LITERAL_AS_REFERENCE("{SOCKPORT}")); - this->serverPrefixTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("ServerPrefixTag"), STRING_LITERAL_AS_REFERENCE("{SERVERPREFIX}")); + this->rconVersionTag = Jupiter::IRC::Client::Config->get(configSection, "RCONVersionTag"_jrs, "{RVER}"_jrs); + this->gameVersionTag = Jupiter::IRC::Client::Config->get(configSection, "GameVersionTag"_jrs, "{GVER}"_jrs); + this->rulesTag = Jupiter::IRC::Client::Config->get(configSection, "RulesTag"_jrs, "{RULES}"_jrs); + this->userTag = Jupiter::IRC::Client::Config->get(configSection, "UserTag"_jrs, "{USER}"_jrs); + this->serverNameTag = Jupiter::IRC::Client::Config->get(configSection, "ServerNameTag"_jrs, "{SERVERNAME}"_jrs); + this->mapTag = Jupiter::IRC::Client::Config->get(configSection, "MapTag"_jrs, "{MAP}"_jrs); + this->mapGUIDTag = Jupiter::IRC::Client::Config->get(configSection, "MapGUIDTag"_jrs, "{MGUID}"_jrs); + this->serverHostnameTag = Jupiter::IRC::Client::Config->get(configSection, "ServerHostnameTag"_jrs, "{SERVERHOST}"_jrs); + this->serverPortTag = Jupiter::IRC::Client::Config->get(configSection, "ServerPortTag"_jrs, "{SERVERPORT}"_jrs); + this->socketHostnameTag = Jupiter::IRC::Client::Config->get(configSection, "SocketHostnameTag"_jrs, "{SOCKHOST}"_jrs); + this->socketPortTag = Jupiter::IRC::Client::Config->get(configSection, "SocketPortTag"_jrs, "{SOCKPORT}"_jrs); + this->serverPrefixTag = Jupiter::IRC::Client::Config->get(configSection, "ServerPrefixTag"_jrs, "{SERVERPREFIX}"_jrs); /** Player tags */ - this->nameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("NameTag"), STRING_LITERAL_AS_REFERENCE("{NAME}")); - this->rawNameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("RawNameTag"), STRING_LITERAL_AS_REFERENCE("{RNAME}")); - this->ipTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("IPTag"), STRING_LITERAL_AS_REFERENCE("{IP}")); - this->rdnsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("RDNSTag"), STRING_LITERAL_AS_REFERENCE("{RDNS}")); - this->steamTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("SteamTag"), STRING_LITERAL_AS_REFERENCE("{STEAM}")); - this->uuidTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("UUIDTag"), STRING_LITERAL_AS_REFERENCE("{UUID}")); - this->idTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("IDTag"), STRING_LITERAL_AS_REFERENCE("{ID}")); - this->characterTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("CharacterTag"), STRING_LITERAL_AS_REFERENCE("{CHAR}")); - this->vehicleTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VehicleTag"), STRING_LITERAL_AS_REFERENCE("{VEH}")); - this->adminTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("AdminTag"), STRING_LITERAL_AS_REFERENCE("{ADMIN}")); - this->prefixTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("PrefixTag"), STRING_LITERAL_AS_REFERENCE("{PREFIX}")); - this->gamePrefixTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("GamePrefixTag"), STRING_LITERAL_AS_REFERENCE("{GPREFIX}")); - this->teamColorTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("TeamColorTag"), STRING_LITERAL_AS_REFERENCE("{TCOLOR}")); - this->teamShortTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("ShortTeamTag"), STRING_LITERAL_AS_REFERENCE("{TEAMS}")); - this->teamLongTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("LongTeamTag"), STRING_LITERAL_AS_REFERENCE("{TEAML}")); - this->pingTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("PingTag"), STRING_LITERAL_AS_REFERENCE("{PING}")); - this->scoreTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("ScoreTag"), STRING_LITERAL_AS_REFERENCE("{SCORE}")); - this->creditsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("CreditsTag"), STRING_LITERAL_AS_REFERENCE("{CREDITS}")); - this->killsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("KillsTag"), STRING_LITERAL_AS_REFERENCE("{KILLS}")); - this->deathsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("DeathsTag"), STRING_LITERAL_AS_REFERENCE("{DEATHS}")); - this->kdrTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("KDRTag"), STRING_LITERAL_AS_REFERENCE("{KDR}")); - this->suicidesTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("SuicidesTag"), STRING_LITERAL_AS_REFERENCE("{SUICIDES}")); - this->headshotsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("HeadshotsTag"), STRING_LITERAL_AS_REFERENCE("{HEADSHOTS}")); - this->vehicleKillsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VehicleKillsTag"), STRING_LITERAL_AS_REFERENCE("{VEHICLEKILLS}")); - this->buildingKillsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingKillsTag"), STRING_LITERAL_AS_REFERENCE("{BUILDINGKILLS}")); - this->defenceKillsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("DefenceKillsTag"), STRING_LITERAL_AS_REFERENCE("{DEFENCEKILLS}")); - this->winsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("WinsTag"), STRING_LITERAL_AS_REFERENCE("{WINS}")); - this->losesTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("LosesTag"), STRING_LITERAL_AS_REFERENCE("{LOSES}")); - this->beaconPlacementsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BeaconPlacementsTag"), STRING_LITERAL_AS_REFERENCE("{BEACONPLACEMENTS}")); - this->beaconDisarmsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BeaconDisarmsTag"), STRING_LITERAL_AS_REFERENCE("{BEACONDISARMS}")); - this->capturesTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("CapturesTag"), STRING_LITERAL_AS_REFERENCE("{CAPTURES}")); - this->stealsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("StealsTag"), STRING_LITERAL_AS_REFERENCE("{STEALS}")); - this->stolenTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("StolenTag"), STRING_LITERAL_AS_REFERENCE("{STOLEN}")); - this->accessTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("AccessTag"), STRING_LITERAL_AS_REFERENCE("{ACCESS}")); + this->nameTag = Jupiter::IRC::Client::Config->get(configSection, "NameTag"_jrs, "{NAME}"_jrs); + this->rawNameTag = Jupiter::IRC::Client::Config->get(configSection, "RawNameTag"_jrs, "{RNAME}"_jrs); + this->ipTag = Jupiter::IRC::Client::Config->get(configSection, "IPTag"_jrs, "{IP}"_jrs); + this->rdnsTag = Jupiter::IRC::Client::Config->get(configSection, "RDNSTag"_jrs, "{RDNS}"_jrs); + this->steamTag = Jupiter::IRC::Client::Config->get(configSection, "SteamTag"_jrs, "{STEAM}"_jrs); + this->uuidTag = Jupiter::IRC::Client::Config->get(configSection, "UUIDTag"_jrs, "{UUID}"_jrs); + this->idTag = Jupiter::IRC::Client::Config->get(configSection, "IDTag"_jrs, "{ID}"_jrs); + this->characterTag = Jupiter::IRC::Client::Config->get(configSection, "CharacterTag"_jrs, "{CHAR}"_jrs); + this->vehicleTag = Jupiter::IRC::Client::Config->get(configSection, "VehicleTag"_jrs, "{VEH}"_jrs); + this->adminTag = Jupiter::IRC::Client::Config->get(configSection, "AdminTag"_jrs, "{ADMIN}"_jrs); + this->prefixTag = Jupiter::IRC::Client::Config->get(configSection, "PrefixTag"_jrs, "{PREFIX}"_jrs); + this->gamePrefixTag = Jupiter::IRC::Client::Config->get(configSection, "GamePrefixTag"_jrs, "{GPREFIX}"_jrs); + this->teamColorTag = Jupiter::IRC::Client::Config->get(configSection, "TeamColorTag"_jrs, "{TCOLOR}"_jrs); + this->teamShortTag = Jupiter::IRC::Client::Config->get(configSection, "ShortTeamTag"_jrs, "{TEAMS}"_jrs); + this->teamLongTag = Jupiter::IRC::Client::Config->get(configSection, "LongTeamTag"_jrs, "{TEAML}"_jrs); + this->pingTag = Jupiter::IRC::Client::Config->get(configSection, "PingTag"_jrs, "{PING}"_jrs); + this->scoreTag = Jupiter::IRC::Client::Config->get(configSection, "ScoreTag"_jrs, "{SCORE}"_jrs); + this->scorePerMinuteTag = Jupiter::IRC::Client::Config->get(configSection, "ScorePerMinuteTag"_jrs, "{SPM}"_jrs); + this->creditsTag = Jupiter::IRC::Client::Config->get(configSection, "CreditsTag"_jrs, "{CREDITS}"_jrs); + this->killsTag = Jupiter::IRC::Client::Config->get(configSection, "KillsTag"_jrs, "{KILLS}"_jrs); + this->deathsTag = Jupiter::IRC::Client::Config->get(configSection, "DeathsTag"_jrs, "{DEATHS}"_jrs); + this->kdrTag = Jupiter::IRC::Client::Config->get(configSection, "KDRTag"_jrs, "{KDR}"_jrs); + this->suicidesTag = Jupiter::IRC::Client::Config->get(configSection, "SuicidesTag"_jrs, "{SUICIDES}"_jrs); + this->headshotsTag = Jupiter::IRC::Client::Config->get(configSection, "HeadshotsTag"_jrs, "{HEADSHOTS}"_jrs); + this->vehicleKillsTag = Jupiter::IRC::Client::Config->get(configSection, "VehicleKillsTag"_jrs, "{VEHICLEKILLS}"_jrs); + this->buildingKillsTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingKillsTag"_jrs, "{BUILDINGKILLS}"_jrs); + this->defenceKillsTag = Jupiter::IRC::Client::Config->get(configSection, "DefenceKillsTag"_jrs, "{DEFENCEKILLS}"_jrs); + this->gameTimeTag = Jupiter::IRC::Client::Config->get(configSection, "GameTimeTag"_jrs, "{GAMETIME}"_jrs); + this->gamesTag = Jupiter::IRC::Client::Config->get(configSection, "GamesTag"_jrs, "{GAMES}"_jrs); + this->GDIGamesTag = Jupiter::IRC::Client::Config->get(configSection, "GDIGamesTag"_jrs, "{GDIGAMES}"_jrs); + this->NodGamesTag = Jupiter::IRC::Client::Config->get(configSection, "NodGamesTag"_jrs, "{NODGAMES}"_jrs); + this->winsTag = Jupiter::IRC::Client::Config->get(configSection, "WinsTag"_jrs, "{WINS}"_jrs); + this->GDIWinsTag = Jupiter::IRC::Client::Config->get(configSection, "GDIWinsTag"_jrs, "{GDIWINS}"_jrs); + this->NodWinsTag = Jupiter::IRC::Client::Config->get(configSection, "NodWinsTag"_jrs, "{NODWINS}"_jrs); + this->tiesTag = Jupiter::IRC::Client::Config->get(configSection, "TiesTag"_jrs, "{TIES}"_jrs); + this->lossesTag = Jupiter::IRC::Client::Config->get(configSection, "LossesTag"_jrs, "{LOSSES}"_jrs); + this->GDILossesTag = Jupiter::IRC::Client::Config->get(configSection, "GDILossesTag"_jrs, "{GDILOSSES}"_jrs); + this->NodLossesTag = Jupiter::IRC::Client::Config->get(configSection, "NodLossesTag"_jrs, "{NODLOSSES}"_jrs); + this->winLossRatioTag = Jupiter::IRC::Client::Config->get(configSection, "WinLossRatioTag"_jrs, "{WLR}"_jrs); + this->GDIWinLossRatioTag = Jupiter::IRC::Client::Config->get(configSection, "GDIWinLossRatioTag"_jrs, "{GWLR}"_jrs); + this->NodWinLossRatioTag = Jupiter::IRC::Client::Config->get(configSection, "NodWinLossRatioTag"_jrs, "{NWLR}"_jrs); + this->beaconPlacementsTag = Jupiter::IRC::Client::Config->get(configSection, "BeaconPlacementsTag"_jrs, "{BEACONPLACEMENTS}"_jrs); + this->beaconDisarmsTag = Jupiter::IRC::Client::Config->get(configSection, "BeaconDisarmsTag"_jrs, "{BEACONDISARMS}"_jrs); + this->proxyPlacementsTag = Jupiter::IRC::Client::Config->get(configSection, "ProxyPlacementsTag"_jrs, "{PROXYPLACEMENTS}"_jrs); + this->proxyDisarmsTag = Jupiter::IRC::Client::Config->get(configSection, "ProxyDisarmsTag"_jrs, "{PROXYDISARMS}"_jrs); + this->capturesTag = Jupiter::IRC::Client::Config->get(configSection, "CapturesTag"_jrs, "{CAPTURES}"_jrs); + this->stealsTag = Jupiter::IRC::Client::Config->get(configSection, "StealsTag"_jrs, "{STEALS}"_jrs); + this->stolenTag = Jupiter::IRC::Client::Config->get(configSection, "StolenTag"_jrs, "{STOLEN}"_jrs); + this->accessTag = Jupiter::IRC::Client::Config->get(configSection, "AccessTag"_jrs, "{ACCESS}"_jrs); /** Victim player tags */ - this->victimNameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimNameTag"), STRING_LITERAL_AS_REFERENCE("{VNAME}")); - this->victimRawNameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimRawNameTag"), STRING_LITERAL_AS_REFERENCE("{VRNAME}")); - this->victimIPTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimIPTag"), STRING_LITERAL_AS_REFERENCE("{VIP}")); - this->victimRDNSTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimRDNSTag"), STRING_LITERAL_AS_REFERENCE("{VRDNS}")); - this->victimSteamTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimSteamTag"), STRING_LITERAL_AS_REFERENCE("{VSTEAM}")); - this->victimUUIDTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimUUIDTag"), STRING_LITERAL_AS_REFERENCE("{VUUID}")); - this->victimIDTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimIDTag"), STRING_LITERAL_AS_REFERENCE("{VID}")); - this->victimCharacterTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimCharacterTag"), STRING_LITERAL_AS_REFERENCE("{VCHAR}")); - this->victimVehicleTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimVehicleTag"), STRING_LITERAL_AS_REFERENCE("{VVEH}")); - this->victimAdminTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimAdminTag"), STRING_LITERAL_AS_REFERENCE("{VADMIN}")); - this->victimPrefixTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimPrefixTag"), STRING_LITERAL_AS_REFERENCE("{VPREFIX}")); - this->victimGamePrefixTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimGamePrefixTag"), STRING_LITERAL_AS_REFERENCE("{VGPREFIX}")); - this->victimTeamColorTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimTeamColorTag"), STRING_LITERAL_AS_REFERENCE("{VTCOLOR}")); - this->victimTeamShortTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimShortTeamTag"), STRING_LITERAL_AS_REFERENCE("{VTEAMS}")); - this->victimTeamLongTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimLongTeamTag"), STRING_LITERAL_AS_REFERENCE("{VTEAML}")); - this->victimPingTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimPingTag"), STRING_LITERAL_AS_REFERENCE("{VPING}")); - this->victimScoreTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimScoreTag"), STRING_LITERAL_AS_REFERENCE("{VSCORE}")); - this->victimCreditsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimCreditsTag"), STRING_LITERAL_AS_REFERENCE("{VCREDITS}")); - this->victimKillsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimKillsTag"), STRING_LITERAL_AS_REFERENCE("{VKILLS}")); - this->victimDeathsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimDeathsTag"), STRING_LITERAL_AS_REFERENCE("{VDEATHS}")); - this->victimKDRTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimKDRTag"), STRING_LITERAL_AS_REFERENCE("{VKDR}")); - this->victimSuicidesTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimSuicidesTag"), STRING_LITERAL_AS_REFERENCE("{VSUICIDES}")); - this->victimHeadshotsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimHeadshotsTag"), STRING_LITERAL_AS_REFERENCE("{VHEADSHOTS}")); - this->victimVehicleKillsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimVehicleKillsTag"), STRING_LITERAL_AS_REFERENCE("{VVEHICLEKILLS}")); - this->victimBuildingKillsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimBuildingKillsTag"), STRING_LITERAL_AS_REFERENCE("{VBUILDINGKILLS}")); - this->victimDefenceKillsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimDefenceKillsTag"), STRING_LITERAL_AS_REFERENCE("{VDEFENCEKILLS}")); - this->victimWinsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimWinsTag"), STRING_LITERAL_AS_REFERENCE("{VWINS}")); - this->victimLosesTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimLosesTag"), STRING_LITERAL_AS_REFERENCE("{VLOSES}")); - this->victimBeaconPlacementsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimBeaconPlacementsTag"), STRING_LITERAL_AS_REFERENCE("{VBEACONPLACEMENTS}")); - this->victimBeaconDisarmsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimBeaconDisarmsTag"), STRING_LITERAL_AS_REFERENCE("{VBEACONDISARMS}")); - this->victimCapturesTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimCapturesTag"), STRING_LITERAL_AS_REFERENCE("{VCAPTURES}")); - this->victimStealsTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimStealsTag"), STRING_LITERAL_AS_REFERENCE("{VSTEALS}")); - this->victimStolenTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimStolenTag"), STRING_LITERAL_AS_REFERENCE("{VSTOLEN}")); - this->victimAccessTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("VictimAccessTag"), STRING_LITERAL_AS_REFERENCE("{VACCESS}")); + this->victimNameTag = Jupiter::IRC::Client::Config->get(configSection, "VictimNameTag"_jrs, "{VNAME}"_jrs); + this->victimRawNameTag = Jupiter::IRC::Client::Config->get(configSection, "VictimRawNameTag"_jrs, "{VRNAME}"_jrs); + this->victimIPTag = Jupiter::IRC::Client::Config->get(configSection, "VictimIPTag"_jrs, "{VIP}"_jrs); + this->victimRDNSTag = Jupiter::IRC::Client::Config->get(configSection, "VictimRDNSTag"_jrs, "{VRDNS}"_jrs); + this->victimSteamTag = Jupiter::IRC::Client::Config->get(configSection, "VictimSteamTag"_jrs, "{VSTEAM}"_jrs); + this->victimUUIDTag = Jupiter::IRC::Client::Config->get(configSection, "VictimUUIDTag"_jrs, "{VUUID}"_jrs); + this->victimIDTag = Jupiter::IRC::Client::Config->get(configSection, "VictimIDTag"_jrs, "{VID}"_jrs); + this->victimCharacterTag = Jupiter::IRC::Client::Config->get(configSection, "VictimCharacterTag"_jrs, "{VCHAR}"_jrs); + this->victimVehicleTag = Jupiter::IRC::Client::Config->get(configSection, "VictimVehicleTag"_jrs, "{VVEH}"_jrs); + this->victimAdminTag = Jupiter::IRC::Client::Config->get(configSection, "VictimAdminTag"_jrs, "{VADMIN}"_jrs); + this->victimPrefixTag = Jupiter::IRC::Client::Config->get(configSection, "VictimPrefixTag"_jrs, "{VPREFIX}"_jrs); + this->victimGamePrefixTag = Jupiter::IRC::Client::Config->get(configSection, "VictimGamePrefixTag"_jrs, "{VGPREFIX}"_jrs); + this->victimTeamColorTag = Jupiter::IRC::Client::Config->get(configSection, "VictimTeamColorTag"_jrs, "{VTCOLOR}"_jrs); + this->victimTeamShortTag = Jupiter::IRC::Client::Config->get(configSection, "VictimShortTeamTag"_jrs, "{VTEAMS}"_jrs); + this->victimTeamLongTag = Jupiter::IRC::Client::Config->get(configSection, "VictimLongTeamTag"_jrs, "{VTEAML}"_jrs); + this->victimPingTag = Jupiter::IRC::Client::Config->get(configSection, "VictimPingTag"_jrs, "{VPING}"_jrs); + this->victimScoreTag = Jupiter::IRC::Client::Config->get(configSection, "VictimScoreTag"_jrs, "{VSCORE}"_jrs); + this->victimScorePerMinuteTag = Jupiter::IRC::Client::Config->get(configSection, "VictimScorePerMinuteTag"_jrs, "{VSPM}"_jrs); + this->victimCreditsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimCreditsTag"_jrs, "{VCREDITS}"_jrs); + this->victimKillsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimKillsTag"_jrs, "{VKILLS}"_jrs); + this->victimDeathsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimDeathsTag"_jrs, "{VDEATHS}"_jrs); + this->victimKDRTag = Jupiter::IRC::Client::Config->get(configSection, "VictimKDRTag"_jrs, "{VKDR}"_jrs); + this->victimSuicidesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimSuicidesTag"_jrs, "{VSUICIDES}"_jrs); + this->victimHeadshotsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimHeadshotsTag"_jrs, "{VHEADSHOTS}"_jrs); + this->victimVehicleKillsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimVehicleKillsTag"_jrs, "{VVEHICLEKILLS}"_jrs); + this->victimBuildingKillsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimBuildingKillsTag"_jrs, "{VBUILDINGKILLS}"_jrs); + this->victimDefenceKillsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimDefenceKillsTag"_jrs, "{VDEFENCEKILLS}"_jrs); + this->victimGameTimeTag = Jupiter::IRC::Client::Config->get(configSection, "VictimGameTimeTag"_jrs, "{VGAMETIME}"_jrs); + this->victimGamesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimGamesTag"_jrs, "{VGAMES}"_jrs); + this->victimGDIGamesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimGDIGamesTag"_jrs, "{VGDIGAMES}"_jrs); + this->victimNodGamesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimNodGamesTag"_jrs, "{VNODGAMES}"_jrs); + this->victimWinsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimWinsTag"_jrs, "{VWINS}"_jrs); + this->victimGDIWinsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimGDIWinsTag"_jrs, "{VGDIWINS}"_jrs); + this->victimNodWinsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimNodWinsTag"_jrs, "{VNODWINS}"_jrs); + this->victimTiesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimTiesTag"_jrs, "{VTIES}"_jrs); + this->victimLossesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimLossesTag"_jrs, "{VLOSSES}"_jrs); + this->victimGDILossesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimGDILossesTag"_jrs, "{VGDILOSSES}"_jrs); + this->victimNodLossesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimNodLossesTag"_jrs, "{VNODLOSSES}"_jrs); + this->victimWinLossRatioTag = Jupiter::IRC::Client::Config->get(configSection, "WinLossRatioTag"_jrs, "{WLR}"_jrs); + this->victimGDIWinLossRatioTag = Jupiter::IRC::Client::Config->get(configSection, "GDIWinLossRatioTag"_jrs, "{VGWLR}"_jrs); + this->victimNodWinLossRatioTag = Jupiter::IRC::Client::Config->get(configSection, "NodWinLossRatioTag"_jrs, "{VNWLR}"_jrs); + this->victimBeaconPlacementsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimBeaconPlacementsTag"_jrs, "{VBEACONPLACEMENTS}"_jrs); + this->victimBeaconDisarmsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimBeaconDisarmsTag"_jrs, "{VBEACONDISARMS}"_jrs); + this->victimProxyPlacementsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimProxyPlacementsTag"_jrs, "{VPROXYPLACEMENTS}"_jrs); + this->victimProxyDisarmsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimProxyDisarmsTag"_jrs, "{VPROXYDISARMS}"_jrs); + this->victimCapturesTag = Jupiter::IRC::Client::Config->get(configSection, "VictimCapturesTag"_jrs, "{VCAPTURES}"_jrs); + this->victimStealsTag = Jupiter::IRC::Client::Config->get(configSection, "VictimStealsTag"_jrs, "{VSTEALS}"_jrs); + this->victimStolenTag = Jupiter::IRC::Client::Config->get(configSection, "VictimStolenTag"_jrs, "{VSTOLEN}"_jrs); + this->victimAccessTag = Jupiter::IRC::Client::Config->get(configSection, "VictimAccessTag"_jrs, "{VACCESS}"_jrs); /** Building tags */ - this->buildingNameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingNameTag"), STRING_LITERAL_AS_REFERENCE("{BNAME}")); - this->buildingRawNameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingRawNameTag"), STRING_LITERAL_AS_REFERENCE("{BRNAME}")); - this->buildingHealthTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingHealthTag"), STRING_LITERAL_AS_REFERENCE("{BHEALTH}")); - this->buildingMaxHealthTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingMaxHealthTag"), STRING_LITERAL_AS_REFERENCE("{BMHEALTH}")); - this->buildingHealthPercentageTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingHealthPercentageTag"), STRING_LITERAL_AS_REFERENCE("{BHP}")); - this->buildingHealthBarTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingHealthBarTag"), STRING_LITERAL_AS_REFERENCE("{BHBAR}")); - this->buildingTeamColorTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingTeamColorTag"), STRING_LITERAL_AS_REFERENCE("{BCOLOR}")); - this->buildingTeamShortTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingShortTeamTag"), STRING_LITERAL_AS_REFERENCE("{BTEAMS}")); - this->buildingTeamLongTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("BuildingLongTeamTag"), STRING_LITERAL_AS_REFERENCE("{BTEAML}")); + this->buildingNameTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingNameTag"_jrs, "{BNAME}"_jrs); + this->buildingRawNameTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingRawNameTag"_jrs, "{BRNAME}"_jrs); + this->buildingHealthTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingHealthTag"_jrs, "{BHEALTH}"_jrs); + this->buildingMaxHealthTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingMaxHealthTag"_jrs, "{BMHEALTH}"_jrs); + this->buildingHealthPercentageTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingHealthPercentageTag"_jrs, "{BHP}"_jrs); + this->buildingHealthBarTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingHealthBarTag"_jrs, "{BHBAR}"_jrs); + this->buildingTeamColorTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingTeamColorTag"_jrs, "{BCOLOR}"_jrs); + this->buildingTeamShortTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingShortTeamTag"_jrs, "{BTEAMS}"_jrs); + this->buildingTeamLongTag = Jupiter::IRC::Client::Config->get(configSection, "BuildingLongTeamTag"_jrs, "{BTEAML}"_jrs); /** Other tags */ - this->weaponTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("WeaponTag"), STRING_LITERAL_AS_REFERENCE("{WEAPON}")); - this->objectTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("ObjectTag"), STRING_LITERAL_AS_REFERENCE("{OBJECT}")); - this->messageTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("MessageTag"), STRING_LITERAL_AS_REFERENCE("{MESSAGE}")); - this->newNameTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("NewNameTag"), STRING_LITERAL_AS_REFERENCE("{NNAME}")); - this->winScoreTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("WinScoreTag"), STRING_LITERAL_AS_REFERENCE("{WINSCORE}")); - this->loseScoreTag = Jupiter::IRC::Client::Config->get(configSection, STRING_LITERAL_AS_REFERENCE("LoseScoreTag"), STRING_LITERAL_AS_REFERENCE("{LOSESCORE}")); + this->weaponTag = Jupiter::IRC::Client::Config->get(configSection, "WeaponTag"_jrs, "{WEAPON}"_jrs); + this->objectTag = Jupiter::IRC::Client::Config->get(configSection, "ObjectTag"_jrs, "{OBJECT}"_jrs); + this->messageTag = Jupiter::IRC::Client::Config->get(configSection, "MessageTag"_jrs, "{MESSAGE}"_jrs); + this->newNameTag = Jupiter::IRC::Client::Config->get(configSection, "NewNameTag"_jrs, "{NNAME}"_jrs); + this->winScoreTag = Jupiter::IRC::Client::Config->get(configSection, "WinScoreTag"_jrs, "{WINSCORE}"_jrs); + this->loseScoreTag = Jupiter::IRC::Client::Config->get(configSection, "LoseScoreTag"_jrs, "{LOSESCORE}"_jrs); + this->lastGameTag = Jupiter::IRC::Client::Config->get(configSection, "LastScoreTag"_jrs, "{LOSESCORE}"_jrs); + this->rankTag = Jupiter::IRC::Client::Config->get(configSection, "RankTag"_jrs, "{RANK}"_jrs); } Jupiter::StringS TagsImp::get_building_health_bar(const RenX::BuildingInfo *building) @@ -320,11 +386,12 @@ Jupiter::StringS TagsImp::get_building_health_bar(const RenX::BuildingInfo *buil return r += IRCNORMAL; } -#define PROCESS_TAG(tag, value) \ -while(true) { \ -index = msg.find(tag); \ -if (index == Jupiter::INVALID_INDEX) break; \ -msg.replace(index, tag.size(), value); } +double get_ratio(double num, double denom) +{ + if (denom == 0.0f) + return num; + return num / denom; +} void TagsImp::processTags(Jupiter::StringType &msg, const RenX::Server *server, const RenX::PlayerInfo *player, const RenX::PlayerInfo *victim, const RenX::BuildingInfo *building) { @@ -372,6 +439,7 @@ void TagsImp::processTags(Jupiter::StringType &msg, const RenX::Server *server, PROCESS_TAG(this->INTERNAL_TEAM_LONG_TAG, RenX::getFullTeamName(player->team)); PROCESS_TAG(this->INTERNAL_PING_TAG, Jupiter::StringS::Format("%hu", player->ping)); PROCESS_TAG(this->INTERNAL_SCORE_TAG, Jupiter::StringS::Format("%.0f", player->score)); + PROCESS_TAG(this->INTERNAL_SCORE_PER_MINUTE_TAG, Jupiter::StringS::Format("%.2f", get_ratio(static_cast(player->score), static_cast((std::chrono::steady_clock::now() - player->joinTime).count()) / 60.0))); PROCESS_TAG(this->INTERNAL_CREDITS_TAG, Jupiter::StringS::Format("%.0f", player->credits)); PROCESS_TAG(this->INTERNAL_KILLS_TAG, Jupiter::StringS::Format("%u", player->kills)); PROCESS_TAG(this->INTERNAL_DEATHS_TAG, Jupiter::StringS::Format("%u", player->deaths)); @@ -382,7 +450,7 @@ void TagsImp::processTags(Jupiter::StringType &msg, const RenX::Server *server, PROCESS_TAG(this->INTERNAL_BUILDING_KILLS_TAG, Jupiter::StringS::Format("%u", player->buildingKills)); PROCESS_TAG(this->INTERNAL_DEFENCE_KILLS_TAG, Jupiter::StringS::Format("%u", player->defenceKills)); PROCESS_TAG(this->INTERNAL_WINS_TAG, Jupiter::StringS::Format("%u", player->wins)); - PROCESS_TAG(this->INTERNAL_LOSES_TAG, Jupiter::StringS::Format("%u", player->loses)); + PROCESS_TAG(this->INTERNAL_LOSSES_TAG, Jupiter::StringS::Format("%u", player->loses)); PROCESS_TAG(this->INTERNAL_BEACON_PLACEMENTS_TAG, Jupiter::StringS::Format("%u", player->beaconPlacements)); PROCESS_TAG(this->INTERNAL_BEACON_DISARMS_TAG, Jupiter::StringS::Format("%u", player->beaconDisarms)); PROCESS_TAG(this->INTERNAL_CAPTURES_TAG, Jupiter::StringS::Format("%u", player->captures)); @@ -408,6 +476,7 @@ void TagsImp::processTags(Jupiter::StringType &msg, const RenX::Server *server, PROCESS_TAG(this->INTERNAL_VICTIM_TEAM_LONG_TAG, RenX::getFullTeamName(victim->team)); PROCESS_TAG(this->INTERNAL_VICTIM_PING_TAG, Jupiter::StringS::Format("%hu", victim->ping)); PROCESS_TAG(this->INTERNAL_VICTIM_SCORE_TAG, Jupiter::StringS::Format("%.0f", victim->score)); + PROCESS_TAG(this->INTERNAL_VICTIM_SCORE_PER_MINUTE_TAG, Jupiter::StringS::Format("%.2f", get_ratio(static_cast(victim->score), static_cast((std::chrono::steady_clock::now() - victim->joinTime).count()) / 60.0))); PROCESS_TAG(this->INTERNAL_VICTIM_CREDITS_TAG, Jupiter::StringS::Format("%.0f", victim->credits)); PROCESS_TAG(this->INTERNAL_VICTIM_KILLS_TAG, Jupiter::StringS::Format("%u", victim->kills)); PROCESS_TAG(this->INTERNAL_VICTIM_DEATHS_TAG, Jupiter::StringS::Format("%u", victim->deaths)); @@ -418,7 +487,7 @@ void TagsImp::processTags(Jupiter::StringType &msg, const RenX::Server *server, PROCESS_TAG(this->INTERNAL_VICTIM_BUILDING_KILLS_TAG, Jupiter::StringS::Format("%u", victim->buildingKills)); PROCESS_TAG(this->INTERNAL_VICTIM_DEFENCE_KILLS_TAG, Jupiter::StringS::Format("%u", victim->defenceKills)); PROCESS_TAG(this->INTERNAL_VICTIM_WINS_TAG, Jupiter::StringS::Format("%u", victim->wins)); - PROCESS_TAG(this->INTERNAL_VICTIM_LOSES_TAG, Jupiter::StringS::Format("%u", victim->loses)); + PROCESS_TAG(this->INTERNAL_VICTIM_LOSSES_TAG, Jupiter::StringS::Format("%u", victim->loses)); PROCESS_TAG(this->INTERNAL_VICTIM_BEACON_PLACEMENTS_TAG, Jupiter::StringS::Format("%u", victim->beaconPlacements)); PROCESS_TAG(this->INTERNAL_VICTIM_BEACON_DISARMS_TAG, Jupiter::StringS::Format("%u", victim->beaconDisarms)); PROCESS_TAG(this->INTERNAL_VICTIM_CAPTURES_TAG, Jupiter::StringS::Format("%u", victim->captures)); @@ -440,8 +509,66 @@ void TagsImp::processTags(Jupiter::StringType &msg, const RenX::Server *server, } Jupiter::ArrayList &xPlugins = *RenX::getCore()->getPlugins(); - for (size_t i = 0; i < xPlugins.size(); i++) - xPlugins.get(i)->RenX_ProcessTags(msg, server, player, victim, building); + for (index = 0; index < xPlugins.size(); ++index) + xPlugins.get(index)->RenX_ProcessTags(msg, server, player, victim, building); +} + +void TagsImp::processTags(Jupiter::StringType &msg, const RenX::LadderDatabase::Entry &entry) +{ + size_t index; + uint32_t total_tied_games = entry.total_wins - entry.total_gdi_wins - entry.total_nod_wins; + uint32_t total_nontied_games = entry.total_games - total_tied_games; + + PROCESS_TAG(this->INTERNAL_NAME_TAG, entry.most_recent_name); + PROCESS_TAG(this->INTERNAL_STEAM_TAG, Jupiter::StringS::Format("%llu", entry.steam_id)); + PROCESS_TAG(this->INTERNAL_LAST_GAME_TAG, Jupiter::StringS::Format("XX Xuary 20XX at 00:00:00")); // TODO: format this! + PROCESS_TAG(this->INTERNAL_RANK_TAG, Jupiter::StringS::Format("%u", entry.rank)); + + /** Totals */ + PROCESS_TAG(this->INTERNAL_SCORE_TAG, Jupiter::StringS::Format("%llu", entry.total_score)); + PROCESS_TAG(this->INTERNAL_KILLS_TAG, Jupiter::StringS::Format("%u", entry.total_kills)); + PROCESS_TAG(this->INTERNAL_DEATHS_TAG, Jupiter::StringS::Format("%u", entry.total_deaths)); + PROCESS_TAG(this->INTERNAL_KDR_TAG, Jupiter::StringS::Format("%.2f", get_ratio(static_cast(entry.total_kills), static_cast(entry.total_deaths)))); + PROCESS_TAG(this->INTERNAL_SCORE_PER_MINUTE_TAG, Jupiter::StringS::Format("%.2f", get_ratio(static_cast(entry.total_score), static_cast(entry.total_game_time) / 60.0))); + PROCESS_TAG(this->INTERNAL_HEADSHOTS_TAG, Jupiter::StringS::Format("%u", entry.total_headshot_kills)); + PROCESS_TAG(this->INTERNAL_VEHICLE_KILLS_TAG, Jupiter::StringS::Format("%u", entry.total_vehicle_kills)); + PROCESS_TAG(this->INTERNAL_BUILDING_KILLS_TAG, Jupiter::StringS::Format("%u", entry.total_building_kills)); + PROCESS_TAG(this->INTERNAL_DEFENCE_KILLS_TAG, Jupiter::StringS::Format("%u", entry.total_defence_kills)); + PROCESS_TAG(this->INTERNAL_CAPTURES_TAG, Jupiter::StringS::Format("%u", entry.total_captures)); + PROCESS_TAG(this->INTERNAL_GAME_TIME_TAG, Jupiter::StringS::Format("%u", entry.total_game_time)); + PROCESS_TAG(this->INTERNAL_GAMES_TAG, Jupiter::StringS::Format("%u", entry.total_games)); + PROCESS_TAG(this->INTERNAL_GDI_GAMES_TAG, Jupiter::StringS::Format("%u", entry.total_gdi_games)); + PROCESS_TAG(this->INTERNAL_NOD_GAMES_TAG, Jupiter::StringS::Format("%u", entry.total_nod_games)); + PROCESS_TAG(this->INTERNAL_WINS_TAG, Jupiter::StringS::Format("%u", entry.total_wins)); + PROCESS_TAG(this->INTERNAL_GDI_WINS_TAG, Jupiter::StringS::Format("%u", entry.total_gdi_wins)); + PROCESS_TAG(this->INTERNAL_NOD_WINS_TAG, Jupiter::StringS::Format("%u", entry.total_nod_wins)); + PROCESS_TAG(this->INTERNAL_TIES_TAG, Jupiter::StringS::Format("%u", total_tied_games)); + PROCESS_TAG(this->INTERNAL_LOSSES_TAG, Jupiter::StringS::Format("%u", total_nontied_games - entry.total_wins)); + PROCESS_TAG(this->INTERNAL_GDI_LOSSES_TAG, Jupiter::StringS::Format("%u", total_nontied_games - entry.total_gdi_wins)); + PROCESS_TAG(this->INTERNAL_NOD_LOSSES_TAG, Jupiter::StringS::Format("%u", total_nontied_games - entry.total_nod_wins)); + + PROCESS_TAG(this->INTERNAL_WIN_LOSS_RATIO_TAG, Jupiter::StringS::Format("%.2f", get_ratio(static_cast(entry.total_wins), static_cast(total_nontied_games - entry.total_wins)))); + PROCESS_TAG(this->INTERNAL_GDI_WIN_LOSS_RATIO_TAG, Jupiter::StringS::Format("%.2f", get_ratio(static_cast(entry.total_gdi_wins), static_cast(total_nontied_games - entry.total_gdi_wins)))); + PROCESS_TAG(this->INTERNAL_NOD_WIN_LOSS_RATIO_TAG, Jupiter::StringS::Format("%.2f", get_ratio(static_cast(entry.total_nod_wins), static_cast(total_nontied_games - entry.total_nod_wins)))); + PROCESS_TAG(this->INTERNAL_BEACON_PLACEMENTS_TAG, Jupiter::StringS::Format("%u", entry.total_beacon_placements)); + PROCESS_TAG(this->INTERNAL_BEACON_DISARMS_TAG, Jupiter::StringS::Format("%u", entry.total_beacon_disarms)); + PROCESS_TAG(this->INTERNAL_PROXY_PLACEMENTS_TAG, Jupiter::StringS::Format("%u", entry.total_proxy_placements)); + PROCESS_TAG(this->INTERNAL_PROXY_DISARMS_TAG, Jupiter::StringS::Format("%u", entry.total_proxy_disarms)); + + /** Tops */ + PROCESS_TAG(this->INTERNAL_VICTIM_SCORE_TAG, Jupiter::StringS::Format("%llu", entry.top_score)); + PROCESS_TAG(this->INTERNAL_VICTIM_KILLS_TAG, Jupiter::StringS::Format("%u", entry.top_kills)); + PROCESS_TAG(this->INTERNAL_VICTIM_DEATHS_TAG, Jupiter::StringS::Format("%u", entry.most_deaths)); + PROCESS_TAG(this->INTERNAL_VICTIM_HEADSHOTS_TAG, Jupiter::StringS::Format("%u", entry.top_headshot_kills)); + PROCESS_TAG(this->INTERNAL_VICTIM_VEHICLE_KILLS_TAG, Jupiter::StringS::Format("%u", entry.top_vehicle_kills)); + PROCESS_TAG(this->INTERNAL_VICTIM_BUILDING_KILLS_TAG, Jupiter::StringS::Format("%u", entry.top_building_kills)); + PROCESS_TAG(this->INTERNAL_VICTIM_DEFENCE_KILLS_TAG, Jupiter::StringS::Format("%u", entry.top_defence_kills)); + PROCESS_TAG(this->INTERNAL_VICTIM_CAPTURES_TAG, Jupiter::StringS::Format("%u", entry.top_captures)); + PROCESS_TAG(this->INTERNAL_VICTIM_GAME_TIME_TAG, Jupiter::StringS::Format("%u", entry.top_game_time)); + PROCESS_TAG(this->INTERNAL_VICTIM_BEACON_PLACEMENTS_TAG, Jupiter::StringS::Format("%u", entry.top_beacon_placements)); + PROCESS_TAG(this->INTERNAL_VICTIM_BEACON_DISARMS_TAG, Jupiter::StringS::Format("%u", entry.top_beacon_disarms)); + PROCESS_TAG(this->INTERNAL_VICTIM_PROXY_PLACEMENTS_TAG, Jupiter::StringS::Format("%u", entry.top_proxy_placements)); + PROCESS_TAG(this->INTERNAL_VICTIM_PROXY_DISARMS_TAG, Jupiter::StringS::Format("%u", entry.top_proxy_disarms)); } void TagsImp::sanitizeTags(Jupiter::StringType &fmt) @@ -482,6 +609,7 @@ void TagsImp::sanitizeTags(Jupiter::StringType &fmt) fmt.replace(this->teamLongTag, this->INTERNAL_TEAM_LONG_TAG); fmt.replace(this->pingTag, this->INTERNAL_PING_TAG); fmt.replace(this->scoreTag, this->INTERNAL_SCORE_TAG); + fmt.replace(this->scorePerMinuteTag, this->INTERNAL_SCORE_PER_MINUTE_TAG); fmt.replace(this->creditsTag, this->INTERNAL_CREDITS_TAG); fmt.replace(this->killsTag, this->INTERNAL_KILLS_TAG); fmt.replace(this->deathsTag, this->INTERNAL_DEATHS_TAG); @@ -491,10 +619,24 @@ void TagsImp::sanitizeTags(Jupiter::StringType &fmt) fmt.replace(this->vehicleKillsTag, this->INTERNAL_VEHICLE_KILLS_TAG); fmt.replace(this->buildingKillsTag, this->INTERNAL_BUILDING_KILLS_TAG); fmt.replace(this->defenceKillsTag, this->INTERNAL_DEFENCE_KILLS_TAG); + fmt.replace(this->gameTimeTag, this->INTERNAL_GAME_TIME_TAG); + fmt.replace(this->gamesTag, this->INTERNAL_GAMES_TAG); + fmt.replace(this->GDIGamesTag, this->INTERNAL_GDI_GAMES_TAG); + fmt.replace(this->NodGamesTag, this->INTERNAL_NOD_GAMES_TAG); fmt.replace(this->winsTag, this->INTERNAL_WINS_TAG); - fmt.replace(this->losesTag, this->INTERNAL_LOSES_TAG); + fmt.replace(this->GDIWinsTag, this->INTERNAL_GDI_WINS_TAG); + fmt.replace(this->NodWinsTag, this->INTERNAL_NOD_WINS_TAG); + fmt.replace(this->tiesTag, this->INTERNAL_TIES_TAG); + fmt.replace(this->lossesTag, this->INTERNAL_LOSSES_TAG); + fmt.replace(this->GDILossesTag, this->INTERNAL_GDI_LOSSES_TAG); + fmt.replace(this->NodLossesTag, this->INTERNAL_NOD_LOSSES_TAG); + fmt.replace(this->winLossRatioTag, this->INTERNAL_WIN_LOSS_RATIO_TAG); + fmt.replace(this->GDIWinLossRatioTag, this->INTERNAL_GDI_WIN_LOSS_RATIO_TAG); + fmt.replace(this->NodWinLossRatioTag, this->INTERNAL_NOD_WIN_LOSS_RATIO_TAG); fmt.replace(this->beaconPlacementsTag, this->INTERNAL_BEACON_PLACEMENTS_TAG); fmt.replace(this->beaconDisarmsTag, this->INTERNAL_BEACON_DISARMS_TAG); + fmt.replace(this->proxyPlacementsTag, this->INTERNAL_PROXY_PLACEMENTS_TAG); + fmt.replace(this->proxyDisarmsTag, this->INTERNAL_PROXY_DISARMS_TAG); fmt.replace(this->capturesTag, this->INTERNAL_CAPTURES_TAG); fmt.replace(this->stealsTag, this->INTERNAL_STEALS_TAG); fmt.replace(this->stolenTag, this->INTERNAL_STOLEN_TAG); @@ -518,6 +660,7 @@ void TagsImp::sanitizeTags(Jupiter::StringType &fmt) fmt.replace(this->victimTeamLongTag, this->INTERNAL_VICTIM_TEAM_LONG_TAG); fmt.replace(this->victimPingTag, this->INTERNAL_VICTIM_PING_TAG); fmt.replace(this->victimScoreTag, this->INTERNAL_VICTIM_SCORE_TAG); + fmt.replace(this->victimScorePerMinuteTag, this->INTERNAL_SCORE_PER_MINUTE_TAG); fmt.replace(this->victimCreditsTag, this->INTERNAL_VICTIM_CREDITS_TAG); fmt.replace(this->victimKillsTag, this->INTERNAL_VICTIM_KILLS_TAG); fmt.replace(this->victimDeathsTag, this->INTERNAL_VICTIM_DEATHS_TAG); @@ -527,10 +670,24 @@ void TagsImp::sanitizeTags(Jupiter::StringType &fmt) fmt.replace(this->victimVehicleKillsTag, this->INTERNAL_VICTIM_VEHICLE_KILLS_TAG); fmt.replace(this->victimBuildingKillsTag, this->INTERNAL_VICTIM_BUILDING_KILLS_TAG); fmt.replace(this->victimDefenceKillsTag, this->INTERNAL_VICTIM_DEFENCE_KILLS_TAG); + fmt.replace(this->victimGameTimeTag, this->INTERNAL_VICTIM_GAME_TIME_TAG); + fmt.replace(this->victimGamesTag, this->INTERNAL_VICTIM_GAMES_TAG); + fmt.replace(this->victimGDIGamesTag, this->INTERNAL_VICTIM_GDI_GAMES_TAG); + fmt.replace(this->victimNodGamesTag, this->INTERNAL_VICTIM_NOD_GAMES_TAG); fmt.replace(this->victimWinsTag, this->INTERNAL_VICTIM_WINS_TAG); - fmt.replace(this->victimLosesTag, this->INTERNAL_VICTIM_LOSES_TAG); + fmt.replace(this->victimGDIWinsTag, this->INTERNAL_VICTIM_GDI_WINS_TAG); + fmt.replace(this->victimNodWinsTag, this->INTERNAL_VICTIM_NOD_WINS_TAG); + fmt.replace(this->victimTiesTag, this->INTERNAL_VICTIM_TIES_TAG); + fmt.replace(this->victimLossesTag, this->INTERNAL_VICTIM_LOSSES_TAG); + fmt.replace(this->victimGDILossesTag, this->INTERNAL_VICTIM_GDI_LOSSES_TAG); + fmt.replace(this->victimNodLossesTag, this->INTERNAL_VICTIM_NOD_LOSSES_TAG); + fmt.replace(this->victimWinLossRatioTag, this->INTERNAL_VICTIM_WIN_LOSS_RATIO_TAG); + fmt.replace(this->victimGDIWinLossRatioTag, this->INTERNAL_VICTIM_GDI_WIN_LOSS_RATIO_TAG); + fmt.replace(this->victimNodWinLossRatioTag, this->INTERNAL_VICTIM_NOD_WIN_LOSS_RATIO_TAG); fmt.replace(this->victimBeaconPlacementsTag, this->INTERNAL_VICTIM_BEACON_PLACEMENTS_TAG); fmt.replace(this->victimBeaconDisarmsTag, this->INTERNAL_VICTIM_BEACON_DISARMS_TAG); + fmt.replace(this->victimProxyPlacementsTag, this->INTERNAL_VICTIM_PROXY_PLACEMENTS_TAG); + fmt.replace(this->victimProxyDisarmsTag, this->INTERNAL_VICTIM_PROXY_DISARMS_TAG); fmt.replace(this->victimCapturesTag, this->INTERNAL_VICTIM_CAPTURES_TAG); fmt.replace(this->victimStealsTag, this->INTERNAL_VICTIM_STEALS_TAG); fmt.replace(this->victimStolenTag, this->INTERNAL_VICTIM_STOLEN_TAG); @@ -554,6 +711,8 @@ void TagsImp::sanitizeTags(Jupiter::StringType &fmt) fmt.replace(this->newNameTag, this->INTERNAL_NEW_NAME_TAG); fmt.replace(this->winScoreTag, this->INTERNAL_WIN_SCORE_TAG); fmt.replace(this->loseScoreTag, this->INTERNAL_LOSE_SCORE_TAG); + fmt.replace(this->lastGameTag, this->INTERNAL_LAST_GAME_TAG); + fmt.replace(this->rankTag, this->INTERNAL_RANK_TAG); Jupiter::ArrayList &xPlugins = *RenX::getCore()->getPlugins(); for (size_t i = 0; i < xPlugins.size(); i++) @@ -582,6 +741,11 @@ void RenX::processTags(Jupiter::StringType &msg, const RenX::Server *server, con _tags.processTags(msg, server, player, victim, building); } +void RenX::processTags(Jupiter::StringType &msg, const RenX::LadderDatabase::Entry &entry) +{ + _tags.processTags(msg, entry); +} + void RenX::sanitizeTags(Jupiter::StringType &fmt) { _tags.sanitizeTags(fmt); diff --git a/RenX.Core/RenX_Tags.h b/RenX.Core/RenX_Tags.h index 37bcded..a0eb0b7 100644 --- a/RenX.Core/RenX_Tags.h +++ b/RenX.Core/RenX_Tags.h @@ -27,6 +27,7 @@ #include "Jupiter/String.h" #include "Jupiter/CString.h" #include "RenX.h" +#include "RenX_LadderDatabase.h" /** DLL Linkage Nagging */ #if defined _MSC_VER @@ -39,8 +40,10 @@ namespace RenX /** Forward declarations */ struct PlayerInfo; class Server; + struct BuildingInfo; RENX_API void processTags(Jupiter::StringType &msg, const RenX::Server *server = nullptr, const RenX::PlayerInfo *player = nullptr, const RenX::PlayerInfo *victim = nullptr, const RenX::BuildingInfo *building = nullptr); + RENX_API void processTags(Jupiter::StringType &msg, const RenX::LadderDatabase::Entry &entry); RENX_API void sanitizeTags(Jupiter::StringType &fmt); RENX_API const Jupiter::ReadableString &getUniqueInternalTag(); @@ -86,6 +89,7 @@ namespace RenX Jupiter::StringS INTERNAL_TEAM_LONG_TAG; Jupiter::StringS INTERNAL_PING_TAG; Jupiter::StringS INTERNAL_SCORE_TAG; + Jupiter::StringS INTERNAL_SCORE_PER_MINUTE_TAG; Jupiter::StringS INTERNAL_CREDITS_TAG; Jupiter::StringS INTERNAL_KILLS_TAG; Jupiter::StringS INTERNAL_DEATHS_TAG; @@ -95,10 +99,24 @@ namespace RenX Jupiter::StringS INTERNAL_VEHICLE_KILLS_TAG; Jupiter::StringS INTERNAL_BUILDING_KILLS_TAG; Jupiter::StringS INTERNAL_DEFENCE_KILLS_TAG; + Jupiter::StringS INTERNAL_GAME_TIME_TAG; + Jupiter::StringS INTERNAL_GAMES_TAG; + Jupiter::StringS INTERNAL_GDI_GAMES_TAG; + Jupiter::StringS INTERNAL_NOD_GAMES_TAG; Jupiter::StringS INTERNAL_WINS_TAG; - Jupiter::StringS INTERNAL_LOSES_TAG; + Jupiter::StringS INTERNAL_GDI_WINS_TAG; + Jupiter::StringS INTERNAL_NOD_WINS_TAG; + Jupiter::StringS INTERNAL_TIES_TAG; + Jupiter::StringS INTERNAL_LOSSES_TAG; + Jupiter::StringS INTERNAL_GDI_LOSSES_TAG; + Jupiter::StringS INTERNAL_NOD_LOSSES_TAG; + Jupiter::StringS INTERNAL_WIN_LOSS_RATIO_TAG; + Jupiter::StringS INTERNAL_GDI_WIN_LOSS_RATIO_TAG; + Jupiter::StringS INTERNAL_NOD_WIN_LOSS_RATIO_TAG; Jupiter::StringS INTERNAL_BEACON_PLACEMENTS_TAG; Jupiter::StringS INTERNAL_BEACON_DISARMS_TAG; + Jupiter::StringS INTERNAL_PROXY_PLACEMENTS_TAG; + Jupiter::StringS INTERNAL_PROXY_DISARMS_TAG; Jupiter::StringS INTERNAL_CAPTURES_TAG; Jupiter::StringS INTERNAL_STEALS_TAG; Jupiter::StringS INTERNAL_STOLEN_TAG; @@ -122,6 +140,7 @@ namespace RenX Jupiter::StringS INTERNAL_VICTIM_TEAM_LONG_TAG; Jupiter::StringS INTERNAL_VICTIM_PING_TAG; Jupiter::StringS INTERNAL_VICTIM_SCORE_TAG; + Jupiter::StringS INTERNAL_VICTIM_SCORE_PER_MINUTE_TAG; Jupiter::StringS INTERNAL_VICTIM_CREDITS_TAG; Jupiter::StringS INTERNAL_VICTIM_KILLS_TAG; Jupiter::StringS INTERNAL_VICTIM_DEATHS_TAG; @@ -131,10 +150,24 @@ namespace RenX Jupiter::StringS INTERNAL_VICTIM_VEHICLE_KILLS_TAG; Jupiter::StringS INTERNAL_VICTIM_BUILDING_KILLS_TAG; Jupiter::StringS INTERNAL_VICTIM_DEFENCE_KILLS_TAG; + Jupiter::StringS INTERNAL_VICTIM_GAME_TIME_TAG; + Jupiter::StringS INTERNAL_VICTIM_GAMES_TAG; + Jupiter::StringS INTERNAL_VICTIM_GDI_GAMES_TAG; + Jupiter::StringS INTERNAL_VICTIM_NOD_GAMES_TAG; Jupiter::StringS INTERNAL_VICTIM_WINS_TAG; - Jupiter::StringS INTERNAL_VICTIM_LOSES_TAG; + Jupiter::StringS INTERNAL_VICTIM_GDI_WINS_TAG; + Jupiter::StringS INTERNAL_VICTIM_NOD_WINS_TAG; + Jupiter::StringS INTERNAL_VICTIM_TIES_TAG; + Jupiter::StringS INTERNAL_VICTIM_LOSSES_TAG; + Jupiter::StringS INTERNAL_VICTIM_GDI_LOSSES_TAG; + Jupiter::StringS INTERNAL_VICTIM_NOD_LOSSES_TAG; + Jupiter::StringS INTERNAL_VICTIM_WIN_LOSS_RATIO_TAG; + Jupiter::StringS INTERNAL_VICTIM_GDI_WIN_LOSS_RATIO_TAG; + Jupiter::StringS INTERNAL_VICTIM_NOD_WIN_LOSS_RATIO_TAG; Jupiter::StringS INTERNAL_VICTIM_BEACON_PLACEMENTS_TAG; Jupiter::StringS INTERNAL_VICTIM_BEACON_DISARMS_TAG; + Jupiter::StringS INTERNAL_VICTIM_PROXY_PLACEMENTS_TAG; + Jupiter::StringS INTERNAL_VICTIM_PROXY_DISARMS_TAG; Jupiter::StringS INTERNAL_VICTIM_CAPTURES_TAG; Jupiter::StringS INTERNAL_VICTIM_STEALS_TAG; Jupiter::StringS INTERNAL_VICTIM_STOLEN_TAG; @@ -158,6 +191,8 @@ namespace RenX Jupiter::StringS INTERNAL_NEW_NAME_TAG; Jupiter::StringS INTERNAL_WIN_SCORE_TAG; Jupiter::StringS INTERNAL_LOSE_SCORE_TAG; + Jupiter::StringS INTERNAL_LAST_GAME_TAG; + Jupiter::StringS INTERNAL_RANK_TAG; /** External message tags */ @@ -197,6 +232,7 @@ namespace RenX Jupiter::StringS teamLongTag; Jupiter::StringS pingTag; Jupiter::StringS scoreTag; + Jupiter::StringS scorePerMinuteTag; Jupiter::StringS creditsTag; Jupiter::StringS killsTag; Jupiter::StringS deathsTag; @@ -206,10 +242,24 @@ namespace RenX Jupiter::StringS vehicleKillsTag; Jupiter::StringS buildingKillsTag; Jupiter::StringS defenceKillsTag; + Jupiter::StringS gameTimeTag; + Jupiter::StringS gamesTag; + Jupiter::StringS GDIGamesTag; + Jupiter::StringS NodGamesTag; Jupiter::StringS winsTag; - Jupiter::StringS losesTag; + Jupiter::StringS GDIWinsTag; + Jupiter::StringS NodWinsTag; + Jupiter::StringS tiesTag; + Jupiter::StringS lossesTag; + Jupiter::StringS GDILossesTag; + Jupiter::StringS NodLossesTag; + Jupiter::StringS winLossRatioTag; + Jupiter::StringS GDIWinLossRatioTag; + Jupiter::StringS NodWinLossRatioTag; Jupiter::StringS beaconPlacementsTag; Jupiter::StringS beaconDisarmsTag; + Jupiter::StringS proxyPlacementsTag; + Jupiter::StringS proxyDisarmsTag; Jupiter::StringS capturesTag; Jupiter::StringS stealsTag; Jupiter::StringS stolenTag; @@ -233,6 +283,7 @@ namespace RenX Jupiter::StringS victimTeamLongTag; Jupiter::StringS victimPingTag; Jupiter::StringS victimScoreTag; + Jupiter::StringS victimScorePerMinuteTag; Jupiter::StringS victimCreditsTag; Jupiter::StringS victimKillsTag; Jupiter::StringS victimDeathsTag; @@ -242,10 +293,24 @@ namespace RenX Jupiter::StringS victimVehicleKillsTag; Jupiter::StringS victimBuildingKillsTag; Jupiter::StringS victimDefenceKillsTag; + Jupiter::StringS victimGameTimeTag; + Jupiter::StringS victimGamesTag; + Jupiter::StringS victimGDIGamesTag; + Jupiter::StringS victimNodGamesTag; Jupiter::StringS victimWinsTag; - Jupiter::StringS victimLosesTag; + Jupiter::StringS victimGDIWinsTag; + Jupiter::StringS victimNodWinsTag; + Jupiter::StringS victimTiesTag; + Jupiter::StringS victimLossesTag; + Jupiter::StringS victimGDILossesTag; + Jupiter::StringS victimNodLossesTag; + Jupiter::StringS victimWinLossRatioTag; + Jupiter::StringS victimGDIWinLossRatioTag; + Jupiter::StringS victimNodWinLossRatioTag; Jupiter::StringS victimBeaconPlacementsTag; Jupiter::StringS victimBeaconDisarmsTag; + Jupiter::StringS victimProxyPlacementsTag; + Jupiter::StringS victimProxyDisarmsTag; Jupiter::StringS victimCapturesTag; Jupiter::StringS victimStealsTag; Jupiter::StringS victimStolenTag; @@ -269,11 +334,20 @@ namespace RenX Jupiter::StringS newNameTag; Jupiter::StringS winScoreTag; Jupiter::StringS loseScoreTag; + Jupiter::StringS lastGameTag; + Jupiter::StringS rankTag; }; RENX_API extern Tags *tags; } +/** Helper macro for processing tags */ +#define PROCESS_TAG(tag, value) \ +while(true) { \ +index = msg.find(tag); \ +if (index == Jupiter::INVALID_INDEX) break; \ +msg.replace(index, tag.size(), value); } + /** Re-enable warnings */ #if defined _MSC_VER #pragma warning(pop) diff --git a/RenX.Ladder.Web/RenX.Ladder.Web.vcxproj b/RenX.Ladder.Web/RenX.Ladder.Web.vcxproj new file mode 100644 index 0000000..3bddbed --- /dev/null +++ b/RenX.Ladder.Web/RenX.Ladder.Web.vcxproj @@ -0,0 +1,86 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {57661A2A-EE94-4E9C-B792-AB756533DEFA} + RenX.Ladder.Web + + + + Application + true + v140 + MultiByte + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + $(SolutionDir)$(Configuration)\Plugins\ + AllRules.ruleset + + + + Level3 + Disabled + true + + + true + + + + + Level3 + MaxSpeed + true + true + true + ../Bot;../Jupiter;../RenX.Core;../HTTPServer + _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + + + true + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RenX.Ladder.Web/RenX.Ladder.Web.vcxproj.filters b/RenX.Ladder.Web/RenX.Ladder.Web.vcxproj.filters new file mode 100644 index 0000000..1bd8865 --- /dev/null +++ b/RenX.Ladder.Web/RenX.Ladder.Web.vcxproj.filters @@ -0,0 +1,41 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + + + Resource Files + + + Resource Files + + + Resource Files + + + Resource Files + + + \ No newline at end of file diff --git a/RenX.Ladder.Web/RenX_Ladder_Web.cpp b/RenX.Ladder.Web/RenX_Ladder_Web.cpp new file mode 100644 index 0000000..ee90699 --- /dev/null +++ b/RenX.Ladder.Web/RenX_Ladder_Web.cpp @@ -0,0 +1,339 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#include "Jupiter/IRC_Client.h" +#include "Jupiter/INIFile.h" +#include "Jupiter/HTTP.h" +#include "Jupiter/HTTP_QueryString.h" +#include "HTTPServer.h" +#include "RenX_Tags.h" +#include "RenX_Ladder_Web.h" + +using namespace Jupiter::literals; + +RenX_Ladder_WebPlugin::RenX_Ladder_WebPlugin() +{ + RenX_Ladder_WebPlugin::ladder_page_name = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "LadderPageName"_jrs, ""_jrs); + RenX_Ladder_WebPlugin::search_page_name = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "SearchPageName"_jrs, "search"_jrs); + RenX_Ladder_WebPlugin::profile_page_name = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "ProfilePageName"_jrs, "profile"_jrs); + RenX_Ladder_WebPlugin::web_hostname = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "Hostname"_jrs, ""_jrs); + RenX_Ladder_WebPlugin::web_path = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "Path"_jrs, "/"_jrs); + + this->OnRehash(); + + /** Initialize content */ + Jupiter::HTTP::Server &server = getHTTPServer(); + + Jupiter::HTTP::Server::Content *content = new Jupiter::HTTP::Server::Content(RenX_Ladder_WebPlugin::ladder_page_name, handle_ladder_page); + content->language = &Jupiter::HTTP::Content::Language::ENGLISH; + content->type = &Jupiter::HTTP::Content::Type::Text::HTML; + content->charset = &Jupiter::HTTP::Content::Type::Text::Charset::UTF8; + server.hook(RenX_Ladder_WebPlugin::web_hostname, RenX_Ladder_WebPlugin::web_path, content); + + content = new Jupiter::HTTP::Server::Content(RenX_Ladder_WebPlugin::search_page_name, handle_search_page); + content->language = &Jupiter::HTTP::Content::Language::ENGLISH; + content->type = &Jupiter::HTTP::Content::Type::Text::HTML; + content->charset = &Jupiter::HTTP::Content::Type::Text::Charset::UTF8; + server.hook(RenX_Ladder_WebPlugin::web_hostname, RenX_Ladder_WebPlugin::web_path, content); + + content = new Jupiter::HTTP::Server::Content(RenX_Ladder_WebPlugin::profile_page_name, handle_profile_page); + content->language = &Jupiter::HTTP::Content::Language::ENGLISH; + content->type = &Jupiter::HTTP::Content::Type::Text::HTML; + content->charset = &Jupiter::HTTP::Content::Type::Text::Charset::UTF8; + server.hook(RenX_Ladder_WebPlugin::web_hostname, RenX_Ladder_WebPlugin::web_path, content); +} + +RenX_Ladder_WebPlugin::~RenX_Ladder_WebPlugin() +{ + Jupiter::HTTP::Server &server = getHTTPServer(); + server.remove(RenX_Ladder_WebPlugin::web_hostname, RenX_Ladder_WebPlugin::web_path, RenX_Ladder_WebPlugin::ladder_page_name); + server.remove(RenX_Ladder_WebPlugin::web_hostname, RenX_Ladder_WebPlugin::web_path, RenX_Ladder_WebPlugin::search_page_name); + server.remove(RenX_Ladder_WebPlugin::web_hostname, RenX_Ladder_WebPlugin::web_path, RenX_Ladder_WebPlugin::profile_page_name); +} + +int RenX_Ladder_WebPlugin::OnRehash() +{ + FILE *file; + int chr; + + RenX_Ladder_WebPlugin::web_header_filename = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "HeaderFilename"_jrs, "RenX.Ladder.Web.Header.html"_jrs); + RenX_Ladder_WebPlugin::web_footer_filename = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "FooterFilename"_jrs, "RenX.Ladder.Web.Footer.html"_jrs); + RenX_Ladder_WebPlugin::web_profile_filename = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "ProfileFilename"_jrs, "RenX.Ladder.Web.Profile.html"_jrs); + RenX_Ladder_WebPlugin::web_ladder_table_header_filename = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "LadderTableHeaderFilename"_jrs, "RenX.Ladder.Web.Ladder.Table.Header.html"_jrs); + RenX_Ladder_WebPlugin::web_ladder_table_footer_filename = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "LadderTableFooterFilename"_jrs, "RenX.Ladder.Web.Ladder.Table.Footer.html"_jrs); + + RenX_Ladder_WebPlugin::entry_table_row = Jupiter::IRC::Client::Config->get(RenX_Ladder_WebPlugin::name, "EntryTableRow"_jrs, "{RANK}{NAME}{SCORE}{SPM}{KILLS}{DEATHS}{KDR}{NODGAMES}{NODWINS}{NODLOSSES}{NWLR}{GDIGAMES}{GDIWINS}{GDILOSSES}{GWLR}"_jrs); + RenX::sanitizeTags(RenX_Ladder_WebPlugin::entry_table_row); + + RenX_Ladder_WebPlugin::header.erase(); + RenX_Ladder_WebPlugin::footer.erase(); + RenX_Ladder_WebPlugin::entry_profile.erase(); + RenX_Ladder_WebPlugin::ladder_table_header.erase(); + RenX_Ladder_WebPlugin::ladder_table_footer.erase(); + + /** Load header */ + if (RenX_Ladder_WebPlugin::web_header_filename.isNotEmpty()) + { + file = fopen(RenX_Ladder_WebPlugin::web_header_filename.c_str(), "rb"); + if (file != nullptr) + { + while ((chr = fgetc(file)) != EOF) + RenX_Ladder_WebPlugin::header += chr; + fclose(file); + } + } + + /** Load footer */ + if (RenX_Ladder_WebPlugin::web_footer_filename.isNotEmpty()) + { + file = fopen(RenX_Ladder_WebPlugin::web_footer_filename.c_str(), "rb"); + if (file != nullptr) + { + while ((chr = fgetc(file)) != EOF) + RenX_Ladder_WebPlugin::footer += chr; + fclose(file); + } + } + + /** Load profile */ + if (RenX_Ladder_WebPlugin::web_profile_filename.isNotEmpty()) + { + file = fopen(RenX_Ladder_WebPlugin::web_profile_filename.c_str(), "rb"); + if (file != nullptr) + { + while ((chr = fgetc(file)) != EOF) + RenX_Ladder_WebPlugin::entry_profile += chr; + RenX::sanitizeTags(RenX_Ladder_WebPlugin::entry_profile); + fclose(file); + } + } + + /** Load table header */ + if (RenX_Ladder_WebPlugin::web_ladder_table_header_filename.isNotEmpty()) + { + file = fopen(RenX_Ladder_WebPlugin::web_ladder_table_header_filename.c_str(), "rb"); + if (file != nullptr) + { + while ((chr = fgetc(file)) != EOF) + RenX_Ladder_WebPlugin::ladder_table_header += chr; + fclose(file); + } + } + + /** Load table footer */ + if (RenX_Ladder_WebPlugin::web_ladder_table_footer_filename.isNotEmpty()) + { + file = fopen(RenX_Ladder_WebPlugin::web_ladder_table_footer_filename.c_str(), "rb"); + if (file != nullptr) + { + while ((chr = fgetc(file)) != EOF) + RenX_Ladder_WebPlugin::ladder_table_footer += chr; + fclose(file); + } + } + + return 0; +} + +/** Ladder page */ + +Jupiter::String RenX_Ladder_WebPlugin::generate_entry_table(size_t index, size_t count) +{ + if (RenX::ladder_databases.size() == 0) + return Jupiter::String("Error: No ladder databases loaded"_jrs); + + RenX::LadderDatabase *db = RenX::ladder_databases.get(0); + + if (db->getEntries() == 0) // No ladder data + return Jupiter::String("Error: No ladder data"_jrs); + + if (index >= db->getEntries() || count == 0) // Invalid entry range + return Jupiter::String("Error: Invalid range"_jrs); + + if (index + count > db->getEntries()) // Invalid entry range; use valid portion of range + count = db->getEntries() - index; + + RenX::LadderDatabase::Entry *node = db->getHead(); + + // iterate to requested index + while (index != 0) + { + node = node->next; + --index; + } + + // table header + Jupiter::String result(2048); + result = RenX_Ladder_WebPlugin::ladder_table_header; + + // append rows + Jupiter::String row(256); + while (count != 0) + { + row = RenX_Ladder_WebPlugin::entry_table_row; + RenX::processTags(row, *node); + result += row; + --count; + } + + // table footer + result += RenX_Ladder_WebPlugin::ladder_table_footer; + return result; +} + +Jupiter::String *RenX_Ladder_WebPlugin::generate_ladder_page(size_t index, size_t count) +{ + Jupiter::String *result = new Jupiter::String(RenX_Ladder_WebPlugin::header); + result->concat(this->generate_entry_table(index, count)); + result->concat(RenX_Ladder_WebPlugin::footer); + return result; +} + +/** Search page */ +Jupiter::String *RenX_Ladder_WebPlugin::generate_search_page(const Jupiter::ReadableString &name) +{ + Jupiter::String *result = new Jupiter::String(RenX_Ladder_WebPlugin::header); + + if (RenX::ladder_databases.size() == 0) + { + result->concat("Error: No ladder databases loaded"_jrs); + result->concat(RenX_Ladder_WebPlugin::footer); + return result; + } + + RenX::LadderDatabase *db = RenX::ladder_databases.get(0); + + if (db->getEntries() == 0) // No ladder data + { + result->concat("Error: No ladder data"_jrs); + result->concat(RenX_Ladder_WebPlugin::footer); + return result; + } + + result->concat(RenX_Ladder_WebPlugin::ladder_table_header); + + // append rows + Jupiter::String row(256); + RenX::LadderDatabase::Entry *node = db->getHead(); + while (node != nullptr) + { + if (node->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) // match found + { + row = RenX_Ladder_WebPlugin::entry_table_row; + RenX::processTags(row, *node); + result->concat(row); + } + node = node->next; + } + + result->concat(RenX_Ladder_WebPlugin::ladder_table_footer); + result->concat(RenX_Ladder_WebPlugin::footer); + return result; +} + +/** Profile page */ +Jupiter::String *RenX_Ladder_WebPlugin::generate_profile_page(uint64_t steam_id) +{ + Jupiter::String *result = new Jupiter::String(RenX_Ladder_WebPlugin::header); + + if (RenX::ladder_databases.size() == 0) + { + result->concat("Error: No ladder databases loaded"_jrs); + result->concat(RenX_Ladder_WebPlugin::footer); + return result; + } + + RenX::LadderDatabase *db = RenX::ladder_databases.get(0); + + if (db->getEntries() == 0) // No ladder data + { + result->concat("Error: No ladder data"_jrs); + result->concat(RenX_Ladder_WebPlugin::footer); + return result; + } + + RenX::LadderDatabase::Entry *entry = db->getHead(); + while (entry != nullptr) + { + if (entry->steam_id == steam_id) // match found + break; + entry = entry->next; + } + + if (entry == nullptr) + result->concat("Error: Player not found"_jrs); + else + { + Jupiter::String profile_data(RenX_Ladder_WebPlugin::entry_profile); + RenX::processTags(profile_data, *entry); + result->concat(profile_data); + } + + result->concat(RenX_Ladder_WebPlugin::footer); + return result; +} + +// Plugin instantiation and entry point. +RenX_Ladder_WebPlugin pluginInstance; + +/** Content functions */ + +Jupiter::ReadableString *handle_ladder_page(const Jupiter::ReadableString &query_string) +{ + size_t start_index = 0, count = 50; + + if (query_string.isNotEmpty()) + { + Jupiter::HTTP::HTMLFormResponse html_form_response(query_string); + start_index = static_cast(html_form_response.table.getInt("start"_jrs, 0)); + count = static_cast(html_form_response.table.getInt("count"_jrs, 50)); + } + return pluginInstance.generate_ladder_page(start_index, count); +} + +Jupiter::ReadableString *handle_search_page(const Jupiter::ReadableString &query_string) +{ + Jupiter::ReferenceString name; + if (query_string.isNotEmpty()) + { + Jupiter::HTTP::HTMLFormResponse html_form_response(query_string); + name = html_form_response.table.get("name"_jrs); + } + + if (name.isEmpty()) // Generate ladder page when no name specified + return handle_ladder_page(query_string); + + return pluginInstance.generate_search_page(name); +} + +Jupiter::ReadableString *handle_profile_page(const Jupiter::ReadableString &query_string) +{ + uint64_t steam_id = 0; + if (query_string.isNotEmpty()) + { + Jupiter::HTTP::HTMLFormResponse html_form_response(query_string); + steam_id = html_form_response.table.getLongLong("id"_jrs); + } + + return pluginInstance.generate_profile_page(steam_id); +} + +extern "C" __declspec(dllexport) Jupiter::Plugin *getPlugin() +{ + return &pluginInstance; +} diff --git a/RenX.Ladder.Web/RenX_Ladder_Web.h b/RenX.Ladder.Web/RenX_Ladder_Web.h new file mode 100644 index 0000000..c4478e3 --- /dev/null +++ b/RenX.Ladder.Web/RenX_Ladder_Web.h @@ -0,0 +1,66 @@ +/** + * Copyright (C) 2016 Jessica James. + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY + * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Written by Jessica James + */ + +#if !defined _RENX_LADDER_WEB_H +#define _RENX_LADDER_WEB_H + +#include "Jupiter/Plugin.h" +#include "Jupiter/Reference_String.h" +#include "Jupiter/CString.h" +#include "RenX_Plugin.h" + +class RenX_Ladder_WebPlugin : public RenX::Plugin +{ +protected: + Jupiter::String generate_entry_table(size_t index, size_t count); + +public: + Jupiter::StringS header; + Jupiter::StringS footer; + Jupiter::String *generate_ladder_page(size_t start_index, size_t count); + Jupiter::String *generate_search_page(const Jupiter::ReadableString &name); + Jupiter::String *generate_profile_page(uint64_t steam_id); + + RenX_Ladder_WebPlugin(); + ~RenX_Ladder_WebPlugin(); + +public: // Jupiter::Plugin + const Jupiter::ReadableString &getName() override { return name; } + int OnRehash() override; + +private: + STRING_LITERAL_AS_NAMED_REFERENCE(name, "RenX.Ladder.Web"); + + /** Configuration variables */ + Jupiter::StringS ladder_page_name, search_page_name, profile_page_name, ladder_table_header, ladder_table_footer; + Jupiter::StringS web_hostname; + Jupiter::StringS web_path; + Jupiter::CStringS web_header_filename; + Jupiter::CStringS web_footer_filename; + Jupiter::CStringS web_profile_filename; + Jupiter::CStringS web_ladder_table_header_filename; + Jupiter::CStringS web_ladder_table_footer_filename; + + Jupiter::StringS entry_table_row, entry_profile; +}; + +Jupiter::ReadableString *handle_ladder_page(const Jupiter::ReadableString ¶meters); +Jupiter::ReadableString *handle_search_page(const Jupiter::ReadableString ¶meters); +Jupiter::ReadableString *handle_profile_page(const Jupiter::ReadableString ¶meters); + +#endif // _RENX_LADDER_WEB_H \ No newline at end of file diff --git a/RenX.Ladder/RenX.Ladder.vcxproj b/RenX.Ladder/RenX.Ladder.vcxproj index 1bebfbc..fcc3980 100644 --- a/RenX.Ladder/RenX.Ladder.vcxproj +++ b/RenX.Ladder/RenX.Ladder.vcxproj @@ -60,7 +60,7 @@ true true ../Bot;../Jupiter;../RenX.Core - _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;RENX_LADDER_EXPORTS;%(PreprocessorDefinitions) true @@ -69,12 +69,10 @@ - - diff --git a/RenX.Ladder/RenX.Ladder.vcxproj.filters b/RenX.Ladder/RenX.Ladder.vcxproj.filters index e820651..a3835d6 100644 --- a/RenX.Ladder/RenX.Ladder.vcxproj.filters +++ b/RenX.Ladder/RenX.Ladder.vcxproj.filters @@ -29,16 +29,10 @@ Source Files - - Source Files - Header Files - - Header Files - \ No newline at end of file diff --git a/RenX.Ladder/RenX_Ladder.cpp b/RenX.Ladder/RenX_Ladder.cpp index 1af989e..0c4b41b 100644 --- a/RenX.Ladder/RenX_Ladder.cpp +++ b/RenX.Ladder/RenX_Ladder.cpp @@ -41,107 +41,6 @@ RenX_LadderPlugin::RenX_LadderPlugin() RenX_LadderPlugin::database.process_file(RenX_LadderPlugin::db_filename); } -void RenX_LadderPlugin::updateLadder(RenX::Server *server, const RenX::TeamType &team) -{ - if (server->players.size() != 0) - { - // update player stats in memory - RenX::PlayerInfo *player; - RenX_LadderDatabase::Entry *entry; - for (Jupiter::DLList::Node *node = server->players.getNode(0); node != nullptr; node = node->next) - { - player = node->data; - if (player->steamid != 0 && (player->ban_flags & RenX::BanDatabase::Entry::FLAG_TYPE_LADDER) == 0) - { - entry = RenX_LadderPlugin::database.getPlayerEntry(player->steamid); - if (entry == nullptr) - { - entry = new RenX_LadderDatabase::Entry(); - RenX_LadderPlugin::database.append(entry); - entry->steam_id = player->steamid; - } - - entry->total_score += static_cast(player->score); - - entry->total_kills += player->kills; - entry->total_deaths += player->deaths; - entry->total_headshot_kills += player->headshots; - entry->total_vehicle_kills += player->vehicleKills; - entry->total_building_kills += player->buildingKills; - entry->total_defence_kills += player->defenceKills; - entry->total_captures += player->captures; - entry->total_game_time += static_cast(std::chrono::duration_cast(server->getGameTime(player)).count()); - ++entry->total_games; - switch (player->team) - { - case RenX::TeamType::GDI: - ++entry->total_gdi_games; - if (player->team == team) - ++entry->total_wins, ++entry->total_gdi_wins; - break; - case RenX::TeamType::Nod: - ++entry->total_nod_games; - if (player->team == team) - ++entry->total_wins, ++entry->total_nod_wins; - break; - default: - if (player->team == team) - ++entry->total_wins; - break; - } - entry->total_beacon_placements += player->beaconPlacements; - entry->total_beacon_disarms += player->beaconDisarms; - entry->total_proxy_placements += player->proxy_placements; - entry->total_proxy_disarms += player->proxy_disarms; - - auto set_if_greater = [](uint32_t &src, const uint32_t &cmp) - { - if (cmp > src) - src = cmp; - }; - - set_if_greater(entry->top_score, static_cast(player->score)); - set_if_greater(entry->top_kills, player->kills); - set_if_greater(entry->most_deaths, player->deaths); - set_if_greater(entry->top_headshot_kills, player->headshots); - set_if_greater(entry->top_vehicle_kills, player->vehicleKills); - set_if_greater(entry->top_building_kills, player->buildingKills); - set_if_greater(entry->top_defence_kills, player->defenceKills); - set_if_greater(entry->top_captures, player->captures); - set_if_greater(entry->top_game_time, static_cast(std::chrono::duration_cast(server->getGameTime(player)).count())); - set_if_greater(entry->top_beacon_placements, player->beaconPlacements); - set_if_greater(entry->top_beacon_disarms, player->beaconDisarms); - set_if_greater(entry->top_proxy_placements, player->proxy_placements); - set_if_greater(entry->top_proxy_disarms, player->proxy_disarms); - - entry->most_recent_ip = player->ip32; - entry->last_game = time(nullptr); - entry->most_recent_name = player->name; - } - } - - // sort new stats - std::chrono::steady_clock::time_point start_time = std::chrono::steady_clock::now(); - RenX_LadderPlugin::database.sort_entries(); - std::chrono::steady_clock::duration sort_duration = std::chrono::steady_clock::now() - start_time; - - // write new stats - start_time = std::chrono::steady_clock::now(); - RenX_LadderPlugin::database.write(RenX_LadderPlugin::db_filename); - std::chrono::steady_clock::duration write_duration = std::chrono::steady_clock::now() - start_time; - - if (RenX_LadderPlugin::output_times) - { - Jupiter::StringS str = Jupiter::StringS::Format("Ladder: %u entries sorted in %f seconds; Database written in %f seconds." ENDL, - RenX_LadderPlugin::database.getEntries(), - static_cast(sort_duration.count()) * (static_cast(std::chrono::steady_clock::duration::period::num / static_cast(std::chrono::steady_clock::duration::period::den) * static_cast(std::chrono::seconds::duration::period::den / std::chrono::seconds::duration::period::num))), - static_cast(write_duration.count()) * (static_cast(std::chrono::steady_clock::duration::period::num) / static_cast(std::chrono::steady_clock::duration::period::den) * static_cast(std::chrono::seconds::duration::period::den / std::chrono::seconds::duration::period::num))); - str.println(stdout); - server->sendLogChan(str); - } - } -} - /** Wait until the client list has been updated to update the ladder */ void RenX_LadderPlugin::RenX_OnGameOver(RenX::Server *server, RenX::WinType winType, const RenX::TeamType &team, int gScore, int nScore) @@ -163,7 +62,7 @@ void RenX_LadderPlugin::RenX_OnCommand(RenX::Server *server, const Jupiter::Read { server->varData.set(this->name, "w"_jrs, "0"_jrs); RenX::TeamType team = static_cast(server->varData.get(this->name, "t"_jrs, "\0"_jrs).get(0)); - RenX_LadderPlugin::updateLadder(server, team); + RenX_LadderPlugin::database.updateLadder(server, team, RenX_LadderPlugin::output_times); } } } @@ -178,7 +77,7 @@ RenX_LadderPlugin pluginInstance; /** Ladder Commands */ -Jupiter::StringS FormatLadderResponse(RenX_LadderDatabase::Entry *entry, size_t rank) +Jupiter::StringS FormatLadderResponse(RenX::LadderDatabase::Entry *entry, size_t rank) { return Jupiter::StringS::Format("#%" PRIuPTR ": \"%.*s\" - Score: %" PRIu64 " - Kills: %" PRIu32 " - Deaths: %" PRIu32 " - KDR: %.2f - SPM: %.2f", rank, entry->most_recent_name.size(), entry->most_recent_name.ptr(), entry->total_score, entry->total_kills, entry->total_deaths, static_cast(entry->total_kills) / (entry->total_deaths == 0 ? 1 : static_cast(entry->total_deaths)), static_cast(entry->total_score) / (entry->total_game_time == 0 ? 1.0 : static_cast(entry->total_game_time)) * 60.0); } @@ -196,7 +95,7 @@ GenericCommand::ResponseLine *LadderGenericCommand::trigger(const Jupiter::Reada if (parameters.isEmpty()) return new GenericCommand::ResponseLine("Error: Too few parameters. Syntax: ladder "_jrs, GenericCommand::DisplayType::PrivateError); - RenX_LadderDatabase::Entry *entry; + RenX::LadderDatabase::Entry *entry; size_t rank; if (parameters.span("0123456789"_jrs) == parameters.size()) { @@ -211,11 +110,11 @@ GenericCommand::ResponseLine *LadderGenericCommand::trigger(const Jupiter::Reada return new GenericCommand::ResponseLine(FormatLadderResponse(entry, rank), GenericCommand::DisplayType::PublicSuccess); } - Jupiter::SLList> list = pluginInstance.database.getPlayerEntriesAndIndexByPartName(parameters, pluginInstance.getMaxLadderCommandPartNameOutput()); + Jupiter::SLList> list = pluginInstance.database.getPlayerEntriesAndIndexByPartName(parameters, pluginInstance.getMaxLadderCommandPartNameOutput()); if (list.size() == 0) return new GenericCommand::ResponseLine("Error: Player not found"_jrs, GenericCommand::DisplayType::PrivateError); - std::pair *pair = list.remove(0); + std::pair *pair = list.remove(0); GenericCommand::ResponseLine *response_head = new GenericCommand::ResponseLine(FormatLadderResponse(std::addressof(pair->first), pair->second + 1), GenericCommand::DisplayType::PrivateSuccess); GenericCommand::ResponseLine *response_end = response_head; delete pair; @@ -253,7 +152,7 @@ void LadderGameCommand::trigger(RenX::Server *source, RenX::PlayerInfo *player, { if (player->steamid != 0) { - std::pair pair = pluginInstance.database.getPlayerEntryAndIndex(player->steamid); + std::pair pair = pluginInstance.database.getPlayerEntryAndIndex(player->steamid); if (pair.first != nullptr) source->sendMessage(FormatLadderResponse(pair.first, pair.second + 1)); else diff --git a/RenX.Ladder/RenX_Ladder.h b/RenX.Ladder/RenX_Ladder.h index d910e36..88b97a7 100644 --- a/RenX.Ladder/RenX_Ladder.h +++ b/RenX.Ladder/RenX_Ladder.h @@ -26,6 +26,12 @@ #include "RenX_LadderDatabase.h" #include "RenX_GameCommand.h" +/** DLL Linkage Nagging */ +#if defined _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + class RenX_LadderPlugin : public RenX::Plugin { public: @@ -36,10 +42,8 @@ public: size_t getMaxLadderCommandPartNameOutput() const; RenX_LadderPlugin(); - RenX_LadderDatabase database; + RenX::LadderDatabase database; private: - void updateLadder(RenX::Server *server, const RenX::TeamType &team); - /** Configuration variables */ bool only_pure, output_times; size_t max_ladder_command_part_name_output; @@ -50,4 +54,9 @@ private: GENERIC_GENERIC_COMMAND(LadderGenericCommand) GENERIC_GAME_COMMAND(LadderGameCommand) +/** Re-enable warnings */ +#if defined _MSC_VER +#pragma warning(pop) +#endif + #endif // _RENX_LADDER_H_HEADER \ No newline at end of file diff --git a/RenX.Ladder/RenX_LadderDatabase.cpp b/RenX.Ladder/RenX_LadderDatabase.cpp deleted file mode 100644 index e4aa35a..0000000 --- a/RenX.Ladder/RenX_LadderDatabase.cpp +++ /dev/null @@ -1,347 +0,0 @@ -/** - * Copyright (C) 2015 Jessica James. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * Written by Jessica James - */ - -#include "RenX_LadderDatabase.h" - -RenX_LadderDatabase::~RenX_LadderDatabase() -{ - while (RenX_LadderDatabase::head != nullptr) - { - RenX_LadderDatabase::end = RenX_LadderDatabase::head; - RenX_LadderDatabase::head = RenX_LadderDatabase::head->next; - delete RenX_LadderDatabase::end; - } -} - -void RenX_LadderDatabase::process_data(Jupiter::DataBuffer &buffer, FILE *file, fpos_t pos) -{ - RenX_LadderDatabase::Entry *entry = new RenX_LadderDatabase::Entry(); - - // read data from buffer to entry - entry->steam_id = buffer.pop(); - entry->total_score = buffer.pop(); - - entry->total_kills = buffer.pop(); - entry->total_deaths = buffer.pop(); - entry->total_headshot_kills = buffer.pop(); - entry->total_vehicle_kills = buffer.pop(); - entry->total_building_kills = buffer.pop(); - entry->total_defence_kills = buffer.pop(); - entry->total_captures = buffer.pop(); - entry->total_game_time = buffer.pop(); - entry->total_games = buffer.pop(); - entry->total_gdi_games = buffer.pop(); - entry->total_nod_games = buffer.pop(); - entry->total_wins = buffer.pop(); - entry->total_gdi_wins = buffer.pop(); - entry->total_nod_wins = buffer.pop(); - entry->total_beacon_placements = buffer.pop(); - entry->total_beacon_disarms = buffer.pop(); - entry->total_proxy_placements = buffer.pop(); - entry->total_proxy_disarms = buffer.pop(); - - entry->top_score = buffer.pop(); - entry->top_kills = buffer.pop(); - entry->most_deaths = buffer.pop(); - entry->top_headshot_kills = buffer.pop(); - entry->top_vehicle_kills = buffer.pop(); - entry->top_building_kills = buffer.pop(); - entry->top_defence_kills = buffer.pop(); - entry->top_captures = buffer.pop(); - entry->top_game_time = buffer.pop(); - entry->top_beacon_placements = buffer.pop(); - entry->top_beacon_disarms = buffer.pop(); - entry->top_proxy_placements = buffer.pop(); - entry->top_proxy_disarms = buffer.pop(); - - entry->most_recent_ip = buffer.pop(); - entry->last_game = buffer.pop(); - entry->most_recent_name = buffer.pop(); - - // push data to list - if (RenX_LadderDatabase::head == nullptr) - { - RenX_LadderDatabase::head = entry; - RenX_LadderDatabase::end = RenX_LadderDatabase::head; - } - else - { - RenX_LadderDatabase::end->next = entry; - entry->prev = end; - end = entry; - } - - ++RenX_LadderDatabase::entries; -} - -void RenX_LadderDatabase::process_header(FILE *file) -{ - int chr = fgetc(file); - if (chr != EOF) - RenX_LadderDatabase::read_version = chr; -} - -void RenX_LadderDatabase::create_header(FILE *file) -{ - fputc(RenX_LadderDatabase::write_version, file); -} - -RenX_LadderDatabase::Entry *RenX_LadderDatabase::getHead() const -{ - return RenX_LadderDatabase::head; -} - -RenX_LadderDatabase::Entry *RenX_LadderDatabase::getPlayerEntry(uint64_t steamid) const -{ - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next) - if (itr->steam_id == steamid) - return itr; - return nullptr; -} - -std::pair RenX_LadderDatabase::getPlayerEntryAndIndex(uint64_t steamid) const -{ - size_t index = 0; - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) - if (itr->steam_id == steamid) - return std::pair(itr, index); - return std::pair(nullptr, Jupiter::INVALID_INDEX); -} - -RenX_LadderDatabase::Entry *RenX_LadderDatabase::getPlayerEntryByName(const Jupiter::ReadableString &name) const -{ - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next) - if (itr->most_recent_name.equalsi(name)) - return itr; - return nullptr; -} - -std::pair RenX_LadderDatabase::getPlayerEntryAndIndexByName(const Jupiter::ReadableString &name) const -{ - size_t index = 0; - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) - if (itr->most_recent_name.equalsi(name)) - return std::pair(itr, index); - return std::pair(nullptr, Jupiter::INVALID_INDEX); -} - -RenX_LadderDatabase::Entry *RenX_LadderDatabase::getPlayerEntryByPartName(const Jupiter::ReadableString &name) const -{ - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next) - if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) - return itr; - return nullptr; -} - -std::pair RenX_LadderDatabase::getPlayerEntryAndIndexByPartName(const Jupiter::ReadableString &name) const -{ - size_t index = 0; - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) - if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) - return std::pair(itr, index); - return std::pair(nullptr, Jupiter::INVALID_INDEX); -} - -Jupiter::SLList RenX_LadderDatabase::getPlayerEntriesByPartName(const Jupiter::ReadableString &name, size_t max) const -{ - Jupiter::SLList list; - if (max == 0) - { - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next) - if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) - list.add(new RenX_LadderDatabase::Entry(*itr)); - } - else - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next) - if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) - { - list.add(new RenX_LadderDatabase::Entry(*itr)); - if (--max == 0) - return list; - } - return list; -} - -Jupiter::SLList> RenX_LadderDatabase::getPlayerEntriesAndIndexByPartName(const Jupiter::ReadableString &name, size_t max) const -{ - Jupiter::SLList> list; - size_t index = 0; - if (max == 0) - { - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) - if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) - list.add(new std::pair(*itr, index)); - } - else - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next, ++index) - if (itr->most_recent_name.findi(name) != Jupiter::INVALID_INDEX) - { - list.add(new std::pair(*itr, index)); - if (--max) - return list; - } - return list; -} - -RenX_LadderDatabase::Entry *RenX_LadderDatabase::getPlayerEntryByIndex(size_t index) const -{ - for (RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; itr != nullptr; itr = itr->next, --index) - if (index == 0) - return itr; - return nullptr; -} - -size_t RenX_LadderDatabase::getEntries() const -{ - return RenX_LadderDatabase::entries; -} - -void RenX_LadderDatabase::append(RenX_LadderDatabase::Entry *entry) -{ - ++RenX_LadderDatabase::entries; - if (RenX_LadderDatabase::head == nullptr) - { - RenX_LadderDatabase::head = entry; - RenX_LadderDatabase::end = RenX_LadderDatabase::head; - return; - } - end->next = entry; - entry->prev = end; - end = entry; -} - -void RenX_LadderDatabase::write(const Jupiter::CStringType &filename) -{ - return RenX_LadderDatabase::write(filename.c_str()); -} - -void RenX_LadderDatabase::write(const char *filename) -{ - if (RenX_LadderDatabase::entries != 0) - { - FILE *file = fopen(filename, "wb"); - if (file != nullptr) - { - Jupiter::DataBuffer buffer; - RenX_LadderDatabase::create_header(file); - RenX_LadderDatabase::Entry *entry = RenX_LadderDatabase::head; - while (entry != nullptr) - { - // push data from entry to buffer - buffer.push(entry->steam_id); - buffer.push(entry->total_score); - - buffer.push(entry->total_kills); - buffer.push(entry->total_deaths); - buffer.push(entry->total_headshot_kills); - buffer.push(entry->total_vehicle_kills); - buffer.push(entry->total_building_kills); - buffer.push(entry->total_defence_kills); - buffer.push(entry->total_captures); - buffer.push(entry->total_game_time); - buffer.push(entry->total_games); - buffer.push(entry->total_gdi_games); - buffer.push(entry->total_nod_games); - buffer.push(entry->total_wins); - buffer.push(entry->total_gdi_wins); - buffer.push(entry->total_nod_wins); - buffer.push(entry->total_beacon_placements); - buffer.push(entry->total_beacon_disarms); - buffer.push(entry->total_proxy_placements); - buffer.push(entry->total_proxy_disarms); - - buffer.push(entry->top_score); - buffer.push(entry->top_kills); - buffer.push(entry->most_deaths); - buffer.push(entry->top_headshot_kills); - buffer.push(entry->top_vehicle_kills); - buffer.push(entry->top_building_kills); - buffer.push(entry->top_defence_kills); - buffer.push(entry->top_captures); - buffer.push(entry->top_game_time); - buffer.push(entry->top_beacon_placements); - buffer.push(entry->top_beacon_disarms); - buffer.push(entry->top_proxy_placements); - buffer.push(entry->top_proxy_disarms); - - buffer.push(entry->most_recent_ip); - buffer.push(entry->last_game); - buffer.push(entry->most_recent_name); - - // push buffer to file - buffer.push_to(file); - - // iterate - entry = entry->next; - } - fclose(file); - } - } -} - -void RenX_LadderDatabase::sort_entries() -{ - if (RenX_LadderDatabase::entries <= 1) - return; - - RenX_LadderDatabase::Entry *itr = RenX_LadderDatabase::head; - RenX_LadderDatabase::Entry *itr2, *ptr; - - // iterate forward (search for out-of-order content) - while (itr->next != nullptr) - { - // out-of-order content found - if (itr->next->total_score > itr->total_score) - { - // pull content out - ptr = itr->next; - itr->next = ptr->next; - if (itr->next != nullptr) - itr->next->prev = itr; - - // iterate backwards from our iterator, and insert - itr2 = itr; - while (true) - { - if (itr2->prev == nullptr) - { - // push ptr to head - ptr->next = itr2; - ptr->prev = nullptr; - itr2->prev = ptr; - RenX_LadderDatabase::head = ptr; - break; - } - itr2 = itr2->prev; - if (itr2->total_score > ptr->total_score) - { - // insert ptr after itr2 - ptr->next = itr2->next; - ptr->next->prev = ptr; - ptr->prev = itr2; - itr2->next = ptr; - break; - } - } - } - else // continue iterating - itr = itr->next; - } - - RenX_LadderDatabase::end = itr; -} \ No newline at end of file diff --git a/RenX.Ladder/RenX_LadderDatabase.h b/RenX.Ladder/RenX_LadderDatabase.h deleted file mode 100644 index e11c8a8..0000000 --- a/RenX.Ladder/RenX_LadderDatabase.h +++ /dev/null @@ -1,158 +0,0 @@ -/** - * Copyright (C) 2015 Jessica James. - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY - * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION - * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - * - * Written by Jessica James - */ - -#if !defined _RENX_LADDERDATABASE_H_HEADER -#define _RENX_LADDERDATABASE_H_HEADER - -#include "Jupiter/Database.h" -#include "Jupiter/String.h" -#include "Jupiter/SLList.h" - -class RenX_LadderDatabase : public Jupiter::Database -{ -public: // Jupiter::Database - /** - * @brief Processes a chunk of data in a database. - * - * @param buffer Buffer to process - * @param file File being processed - * @param pos position that the buffer starts at in the file - */ - void process_data(Jupiter::DataBuffer &buffer, FILE *file, fpos_t pos) override; - - /** - * @brief Processes the header for a database. - * - * @param file File being processed - */ - void process_header(FILE *file) override; - - /** - * @brief Generates a header for a database. - * - * @param file File being created - */ - void create_header(FILE *file) override; - -public: // RenX_LadderDatabase - struct Entry - { - uint64_t steam_id, total_score; - uint32_t total_kills, total_deaths, total_headshot_kills, total_vehicle_kills, total_building_kills, total_defence_kills, total_captures, total_game_time, total_games, total_gdi_games, total_nod_games, total_wins, total_gdi_wins, total_nod_wins, total_beacon_placements, total_beacon_disarms, total_proxy_placements, total_proxy_disarms, // totals (15) - top_score, top_kills, most_deaths, top_headshot_kills, top_vehicle_kills, top_building_kills, top_defence_kills, top_captures, top_game_time, top_beacon_placements, top_beacon_disarms, top_proxy_placements, top_proxy_disarms, // tops (12) - most_recent_ip; // other (1) - time_t last_game; - Jupiter::StringS most_recent_name; - Entry *next = nullptr; - Entry *prev = nullptr; - }; - - /** - * @brief Fetches the head of the entry list. - * - * @return Head of the ladder entry list. - */ - Entry *getHead() const; - - /** - * @brief Fetches a ladder entry by Steam ID. - * - * @param steamid Steam ID to search ladder for - * @return Ladder entry with a matching steamid if one exists, nullptr otherwise. - */ - Entry *getPlayerEntry(uint64_t steamid) const; - std::pair getPlayerEntryAndIndex(uint64_t steamid) const; - - /** - * @brief Searches for a ladder entry by name - * - * @param name Name to search ladder for - * @return Ladder entry with a matching name if one exists, nullptr otherwise. - */ - Entry *getPlayerEntryByName(const Jupiter::ReadableString &name) const; - std::pair getPlayerEntryAndIndexByName(const Jupiter::ReadableString &name) const; - - /** - * @brief Searches for a ladder entry by part name - * - * @param name Part of name to search ladder for - * @return Ladder entry with a matching name if one exists, nullptr otherwise. - */ - Entry *getPlayerEntryByPartName(const Jupiter::ReadableString &name) const; - std::pair getPlayerEntryAndIndexByPartName(const Jupiter::ReadableString &name) const; - - /** - * @brief Fetches all entries matching a part name. - * - * @param name Part of name to search for - * @param max Maximum number of entries to return - * @return List containing entries with matching names. - */ - Jupiter::SLList getPlayerEntriesByPartName(const Jupiter::ReadableString &name, size_t max) const; - Jupiter::SLList> getPlayerEntriesAndIndexByPartName(const Jupiter::ReadableString &name, size_t max) const; - - /** - * @brief Fetches a ladder entry at a specified index - * - * @param index Index of the element to fetch - * @return Ladder entry with a matching name if one exists, nullptr otherwise. - */ - Entry *getPlayerEntryByIndex(size_t index) const; - - /** - * @brief Fetches the total number of ladder entries in the list. - * - * @return Total number of entries. - */ - size_t getEntries() const; - - /** - * @brief Places a ladder entry at the end of the list, regardless of order - * Note: This does not copy data from the pointer -- the pointer is added to the list. - * - * @param entry Ladder entry to add - */ - void append(Entry *entry); - - /** - * @brief Writes the current ladder data to the disk. - */ - void write(const Jupiter::CStringType &filename); - void write(const char *filename); - - /** - * @brief Sorts the ladder data in memory. - */ - void sort_entries(); - - /** - * @brief Deconstructor for the RenX_LadderDatabase class - */ - ~RenX_LadderDatabase(); -private: - - /** Database version */ - const uint8_t write_version = 0; - uint8_t read_version = write_version; - - size_t entries = 0; - Entry *head = nullptr; - Entry *end; -}; - -#endif //_RENX_LADDERDATABASE_H_HEADER \ No newline at end of file diff --git a/RenX.Listen/RenX_Listen.cpp b/RenX.Listen/RenX_Listen.cpp index 758d3d6..de9fdf5 100644 --- a/RenX.Listen/RenX_Listen.cpp +++ b/RenX.Listen/RenX_Listen.cpp @@ -25,7 +25,7 @@ RenX_ListenPlugin::~RenX_ListenPlugin() { - RenX_ListenPlugin::socket.closeSocket(); + RenX_ListenPlugin::socket.close(); } bool RenX_ListenPlugin::init() @@ -59,7 +59,7 @@ int RenX_ListenPlugin::OnRehash() if (port != RenX_ListenPlugin::socket.getRemotePort() || address.equals(RenX_ListenPlugin::socket.getRemoteHostname()) == false) { puts("Notice: The Renegade-X listening socket has been changed!"); - RenX_ListenPlugin::socket.closeSocket(); + RenX_ListenPlugin::socket.close(); return RenX_ListenPlugin::socket.bind(Jupiter::CStringS(address).c_str(), port, true) == false || RenX_ListenPlugin::socket.setBlocking(false) == false; } return 0;