diff --git a/data/cdata/ui_scripts/discord/__init__.lua b/data/cdata/ui_scripts/discord/__init__.lua index b3c701a0..b1cbd249 100644 --- a/data/cdata/ui_scripts/discord/__init__.lua +++ b/data/cdata/ui_scripts/discord/__init__.lua @@ -2,7 +2,7 @@ if (game:issingleplayer() or Engine.InFrontend()) then return end -local container = LUI.UIVerticalList.new({ +local container = LUI.UIElement.new({ topAnchor = true, rightAnchor = true, top = 20, @@ -11,18 +11,6 @@ local container = LUI.UIVerticalList.new({ spacing = 5 }) -history = {} - -function canasktojoin(userid) - if (history[userid] ~= nil) then - return false - end - - history[userid] = true - - return true -end - function truncatename(name, length) if (#name <= length - 3) then return name @@ -31,27 +19,57 @@ function truncatename(name, length) return name:sub(1, length - 3) .. "..." end +local requestlist = {} +local requestcount = 0 + function addrequest(request) - if (not canasktojoin(request.userid)) then - return + for i = 1, #requestlist do + if (requestlist[i].userid == request.userid or #requestlist > 5) then + return + end end - if (container.temp) then - container:removeElement(container.temp) - container.temp = nil - end + request.id = requestcount + requestcount = requestcount + 1 + local yoffset = #requestlist * (75 + 5) local invite = LUI.UIElement.new({ leftAnchor = true, rightAnchor = true, - height = 75 + height = 75, + top = yoffset }) + local getcurrentindex = function() + for i = 1, #requestlist do + if (requestlist[i].id == request.id) then + return i + end + end + + return 0 + end + + invite:registerEventHandler("update_position", function() + yoffset = (getcurrentindex() - 1) * (75 + 5) + local state = { + leftAnchor = true, + height = 75, + width = 200, + left = -220, + top = yoffset + } + + invite:registerAnimationState("default", state) + invite:animateToState("default", 50) + end) + invite:registerAnimationState("move_in", { leftAnchor = true, height = 75, width = 200, - left = -220 + left = -220, + top = yoffset }) invite:animateToState("move_in", 100) @@ -103,7 +121,7 @@ function addrequest(request) width = 32, height = 32, left = 1, - material = RegisterMaterial(avatarmaterial) + material = avatarmaterial }) local username = LUI.UIText.new({ @@ -117,8 +135,14 @@ function addrequest(request) font = CoD.TextSettings.BodyFontBold.Font }) - username:setText(string.format("%s^7#%s requested to join your game!", truncatename(request.username, 18), - request.discriminator)) + local requesttext = nil + if (request.discriminator == "0") then + requesttext = Engine.Localize("LUA_MENU_DISCORD_REQUEST", truncatename(request.username, 18)) + else + requesttext = Engine.Localize("LUA_MENU_DISCORD_REQUEST_DISCRIMINATOR", truncatename(request.username, 18), request.discriminator) + end + + username:setText(requesttext) local buttons = LUI.UIElement.new({ leftAnchor = true, @@ -154,57 +178,59 @@ function addrequest(request) return button end - buttons:addElement(createbutton("[F1] Accept", true)) - buttons:addElement(createbutton("[F2] Deny")) + local accepttext = Engine.Localize("LUA_MENU_DISCORD_ACCEPT", Engine.GetBinding("discord_accept")) + local denytext = Engine.Localize("LUA_MENU_DISCORD_DENY", Engine.GetBinding("discord_deny")) + + buttons:addElement(createbutton(accepttext, true)) + buttons:addElement(createbutton(denytext)) local fadeouttime = 50 local timeout = 10 * 1000 - fadeouttime local function close() - container:processEvent({ - name = "update_navigation", - dispatchToChildren = true + table.remove(requestlist, getcurrentindex()) + + invite:registerAnimationState("fade_out", { + leftAnchor = true, + rightAnchor = true, + height = 75, + alpha = 0, + left = 0, + top = yoffset }) + invite:animateToState("fade_out", fadeouttime) invite:addElement(LUI.UITimer.new(fadeouttime + 50, "remove")) invite:registerEventHandler("remove", function() container:removeElement(invite) - if (container.temp) then - container:removeElement(container.temp) - container.temp = nil - end - local temp = LUI.UIElement.new({}) - container.temp = temp - container:addElement(temp) + container:processEvent({ + name = "update_position", + dispatchToChildren = true + }) end) end - buttons:registerEventHandler("keydown_", function(element, event) - if (event.key == "F1") then - close() - discord.respond(request.userid, discord.reply.yes) + local closed = false + request.handleresponse = function(event) + if (closed) then + return end - if (event.key == "F2") then - close() + if (event.accept) then + discord.respond(request.userid, discord.reply.yes) + else discord.respond(request.userid, discord.reply.no) end - end) - invite:registerAnimationState("fade_out", { - leftAnchor = true, - rightAnchor = true, - height = 75, - alpha = 0, - left = 0 - }) + closed = true + close() + end invite:addElement(LUI.UITimer.new(timeout, "end_invite")) invite:registerEventHandler("end_invite", function() close() discord.respond(request.userid, discord.reply.ignore) - history[request.userid] = nil end) local bar = LUI.UIImage.new({ @@ -235,7 +261,7 @@ function addrequest(request) avatar:registerEventHandler("update", function() local avatarmaterial = discord.getavatarmaterial(request.userid) - avatar:setImage(RegisterMaterial(avatarmaterial)) + avatar:setImage(avatarmaterial) end) avatar:addElement(LUI.UITimer.new(100, "update")) @@ -249,19 +275,17 @@ function addrequest(request) padding:addElement(buttons) container:addElement(invite) + + table.insert(requestlist, request) end -container:registerEventHandler("keydown", function(element, event) - local first = container:getFirstChild() - - if (not first) then +LUI.roots.UIRoot0:registerEventHandler("discord_response", function(element, event) + if (#requestlist <= 0) then return end - first:processEvent({ - name = "keydown_", - key = event.key - }) + local request = requestlist[1] + request.handleresponse(event) end) LUI.roots.UIRoot0:registerEventHandler("discord_join_request", function(element, event) diff --git a/data/zonetool/localizedstrings/english.json b/data/zonetool/localizedstrings/english.json index f0b9b8dc..726e5cf0 100644 --- a/data/zonetool/localizedstrings/english.json +++ b/data/zonetool/localizedstrings/english.json @@ -2,5 +2,10 @@ "LUA_MENU_CAMPAIGN_UNLOCKED_ALL_TITLE": "Unlock All Missions and Intel", "LUA_MENU_CANCEL_UNLOCK_CAPS": "Cancel Unlock All Missions", "LUA_MENU_CHOOSE_LANGUAGE_DESC": "Choose your language.", - "MENU_APPLY_LANGUAGE_SETTINGS": "Apply language settings?" + "MENU_APPLY_LANGUAGE_SETTINGS": "Apply language settings?", + + "LUA_MENU_DISCORD_REQUEST": "&&1^7 requested to join your game!", + "LUA_MENU_DISCORD_REQUEST_DISCRIMINATOR": "&&1^7#&&2 requested to join your game!", + "LUA_MENU_DISCORD_ACCEPT": "[&&1] Accept", + "LUA_MENU_DISCORD_DENY": "[&&1] Deny" } \ No newline at end of file diff --git a/src/client/component/discord.cpp b/src/client/component/discord.cpp index d129974d..98fa6cd9 100644 --- a/src/client/component/discord.cpp +++ b/src/client/component/discord.cpp @@ -4,9 +4,7 @@ #include "console.hpp" #include "command.hpp" #include "discord.hpp" -#include "fastfiles.hpp" #include "materials.hpp" -#include "network.hpp" #include "party.hpp" #include "scheduler.hpp" @@ -29,15 +27,39 @@ namespace discord { namespace { - DiscordRichPresence discord_presence; + struct discord_presence_state_t + { + int start_timestamp; + int party_size; + int party_max; + }; + + struct discord_presence_strings_t + { + std::string state; + std::string details; + std::string small_image_key; + std::string small_image_text; + std::string large_image_key; + std::string large_image_text; + std::string party_id; + std::string join_secret; + }; + + DiscordRichPresence discord_presence{}; + discord_presence_strings_t discord_strings; + + std::mutex avatar_map_mutex; + std::unordered_map avatar_material_map; + game::Material* default_avatar_material{}; void update_discord_frontend() { discord_presence.details = SELECT_VALUE("Singleplayer", "Multiplayer"); discord_presence.startTimestamp = 0; - const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange"); - if (in_firing_range && in_firing_range->current.enabled == 1) + static const auto in_firing_range = game::Dvar_FindVar("virtualLobbyInFiringRange"); + if (in_firing_range != nullptr && in_firing_range->current.enabled == 1) { discord_presence.state = "Firing Range"; discord_presence.largeImageKey = "mp_vlobby_room"; @@ -56,7 +78,7 @@ namespace discord static const auto mapname_dvar = game::Dvar_FindVar("mapname"); auto mapname = mapname_dvar->current.string; - discord_presence.largeImageKey = mapname; + discord_strings.large_image_key = mapname; const auto presence_key = utils::string::va("PRESENCE_%s%s", SELECT_VALUE("SP_", ""), mapname); if (game::DB_XAssetExists(game::ASSET_TYPE_LOCALIZE, presence_key) && @@ -67,17 +89,15 @@ namespace discord if (game::environment::is_mp()) { - // setup Discord details (Free-for-all on Shipment) - static char gametype[0x80] = {0}; static const auto gametype_dvar = game::Dvar_FindVar("g_gametype"); + static const auto max_clients_dvar = game::Dvar_FindVar("sv_maxclients"); + static const auto hostname_dvar = game::Dvar_FindVar("sv_hostname"); + const auto gametype_display_name = game::UI_GetGameTypeDisplayName(gametype_dvar->current.string); - utils::string::strip(gametype_display_name, gametype, sizeof(gametype)); + const auto gametype = utils::string::strip(gametype_display_name); - static char details[0x80] = {0}; - strcpy_s(details, 0x80, utils::string::va("%s on %s", gametype, mapname)); - discord_presence.details = details; + discord_strings.details = std::format("{} on {}", gametype, mapname); - // setup Discord party (1 of 18) const auto client_state = *game::mp::client_state; if (client_state != nullptr) { @@ -86,21 +106,16 @@ namespace discord if (game::SV_Loaded()) { - discord_presence.state = "Private Match"; - static const auto maxclients_dvar = game::Dvar_FindVar("sv_maxclients"); - discord_presence.partyMax = maxclients_dvar->current.integer; + discord_strings.state = "Private Match"; + discord_presence.partyMax = max_clients_dvar->current.integer; discord_presence.partyPrivacy = DISCORD_PARTY_PRIVATE; } else { - static char hostname[0x80] = {0}; - static const auto hostname_dvar = game::Dvar_FindVar("sv_hostname"); - utils::string::strip(hostname_dvar->current.string, hostname, sizeof(hostname)); - discord_presence.state = hostname; + discord_strings.state = utils::string::strip(hostname_dvar->current.string); const auto server_connection_state = party::get_server_connection_state(); - - const auto server_ip_port = utils::string::va("%i.%i.%i.%i:%i", + const auto server_ip_port = std::format("{}.{}.{}.{}:{}", static_cast(server_connection_state.host.ip[0]), static_cast(server_connection_state.host.ip[1]), static_cast(server_connection_state.host.ip[2]), @@ -108,28 +123,22 @@ namespace discord static_cast(ntohs(server_connection_state.host.port)) ); - static char party_id[0x80] = {0}; - const auto server_ip_port_hash = utils::cryptography::sha1::compute(server_ip_port, true).substr(0, 8); - strcpy_s(party_id, 0x80, server_ip_port_hash.data()); - discord_presence.partyId = party_id; + discord_strings.party_id = utils::cryptography::sha1::compute(server_ip_port, true).substr(0, 8); discord_presence.partyMax = server_connection_state.max_clients; discord_presence.partyPrivacy = DISCORD_PARTY_PUBLIC; - - static char join_secret[0x80] = { 0 }; - strcpy_s(join_secret, 0x80, server_ip_port); - discord_presence.joinSecret = join_secret; + discord_strings.join_secret = server_ip_port; } - auto server_discord_information = party::get_server_discord_information(); - if (!server_discord_information.image.empty()) + auto server_discord_info = party::get_server_discord_info(); + if (server_discord_info.has_value()) { - discord_presence.smallImageKey = server_discord_information.image.data(); - discord_presence.smallImageText = server_discord_information.image_text.data(); + discord_strings.small_image_key = server_discord_info->image; + discord_strings.small_image_text = server_discord_info->image_text; } } else if (game::environment::is_sp()) { - discord_presence.details = mapname; + discord_strings.details = mapname; } if (discord_presence.startTimestamp == 0) @@ -138,12 +147,20 @@ namespace discord std::chrono::system_clock::now().time_since_epoch()).count(); } + discord_presence.state = discord_strings.state.data(); + discord_presence.details = discord_strings.details.data(); + discord_presence.smallImageKey = discord_strings.small_image_key.data(); + discord_presence.smallImageText = discord_strings.small_image_text.data(); + discord_presence.largeImageKey = discord_strings.large_image_key.data(); + discord_presence.largeImageText = discord_strings.large_image_text.data(); + discord_presence.partyId = discord_strings.party_id.data(); + discord_presence.joinSecret = discord_strings.join_secret.data(); + Discord_UpdatePresence(&discord_presence); } void update_discord() { - // reset presence data const auto saved_time = discord_presence.startTimestamp; discord_presence = {}; discord_presence.startTimestamp = saved_time; @@ -158,6 +175,33 @@ namespace discord } } + game::Material* create_avatar_material(const std::string& name, const std::string& data) + { + const auto material = materials::create_material(name); + try + { + if (!materials::setup_material_image(material, data)) + { + materials::free_material(material); + return nullptr; + } + + { + std::lock_guard _0(avatar_map_mutex); + avatar_material_map.insert(std::make_pair(name, material)); + } + + return material; + } + catch (const std::exception& e) + { + materials::free_material(material); + console::error("Failed to load user avatar image: %s\n", e.what()); + } + + return nullptr; + } + void download_user_avatar(const std::string& id, const std::string& avatar) { const auto data = utils::http::get_data( @@ -173,10 +217,10 @@ namespace discord return; } - materials::add(utils::string::va(AVATAR, id.data()), value.buffer); + const auto name = utils::string::va(AVATAR, id.data()); + create_avatar_material(name, value.buffer); } - bool has_default_avatar = false; void download_default_avatar() { const auto data = utils::http::get_data(DEFAULT_AVATAR_URL); @@ -191,8 +235,7 @@ namespace discord return; } - has_default_avatar = true; - materials::add(DEFAULT_AVATAR, value.buffer); + default_avatar_material = create_avatar_material(DEFAULT_AVATAR, value.buffer); } void ready(const DiscordUser* request) @@ -211,6 +254,8 @@ namespace discord void join_game(const char* join_secret) { + console::debug("Discord: join_game called with secret '%s'\n", join_secret); + scheduler::once([=] { game::netadr_s target{}; @@ -232,10 +277,30 @@ namespace discord return; } - std::string user_id = request->userId; - std::string avatar = request->avatar; - std::string discriminator = request->discriminator; - std::string username = request->username; + static std::unordered_map last_requests; + + const std::string user_id = request->userId; + const std::string avatar = request->avatar; + const std::string discriminator = request->discriminator; + const std::string username = request->username; + + const auto now = std::chrono::high_resolution_clock::now(); + auto iter = last_requests.find(user_id); + if (iter != last_requests.end()) + { + if ((now - iter->second) < 15s) + { + return; + } + else + { + iter->second = now; + } + } + else + { + last_requests.insert(std::make_pair(user_id, now)); + } scheduler::once([=] { @@ -251,27 +316,47 @@ namespace discord }); }, scheduler::pipeline::lui); - if (!materials::exists(utils::string::va(AVATAR, user_id.data()))) + const auto material_name = utils::string::va(AVATAR, user_id.data()); + if (!avatar.empty() && !avatar_material_map.contains(material_name)) { download_user_avatar(user_id, avatar); } } + + void set_default_bindings() + { + const auto set_binding = [](const std::string& command, const game::keyNum_t key) + { + const auto binding = game::Key_GetBindingForCmd(command.data()); + for (auto i = 0; i < 256; i++) + { + if (game::playerKeys[0].keys[i].binding == binding) + { + return; + } + } + + if (game::playerKeys[0].keys[key].binding == 0) + { + game::Key_SetBinding(0, key, binding); + } + }; + + set_binding("discord_accept", game::K_F1); + set_binding("discord_deny", game::K_F2); + } } - std::string get_avatar_material(const std::string& id) + game::Material* get_avatar_material(const std::string& id) { - const auto avatar_name = utils::string::va(AVATAR, id.data()); - if (materials::exists(avatar_name)) + const auto material_name = utils::string::va(AVATAR, id.data()); + const auto iter = avatar_material_map.find(material_name); + if (iter == avatar_material_map.end()) { - return avatar_name; + return default_avatar_material; } - if (has_default_avatar) - { - return DEFAULT_AVATAR; - } - - return "black"; + return iter->second; } void respond(const std::string& id, int reply) @@ -311,16 +396,29 @@ namespace discord Discord_Initialize("947125042930667530", &handlers, 1, nullptr); - scheduler::once(download_default_avatar, scheduler::pipeline::async); - - scheduler::once([]() + if (game::environment::is_mp()) { - scheduler::once(update_discord, scheduler::pipeline::async); - scheduler::loop(update_discord, scheduler::pipeline::async, 5s); - scheduler::loop(Discord_RunCallbacks, scheduler::pipeline::async, 1s); - }, scheduler::pipeline::main); + scheduler::on_game_initialized([] + { + scheduler::once(download_default_avatar, scheduler::async); + set_default_bindings(); + }, scheduler::main); + } + + scheduler::loop(Discord_RunCallbacks, scheduler::async, 500ms); + scheduler::loop(update_discord, scheduler::async, 5s); initialized_ = true; + + command::add("discord_accept", []() + { + ui_scripting::notify("discord_response", {{"accept", true}}); + }); + + command::add("discord_deny", []() + { + ui_scripting::notify("discord_response", {{"accept", false}}); + }); } void pre_destroy() override diff --git a/src/client/component/discord.hpp b/src/client/component/discord.hpp index 5399f952..54c75ca9 100644 --- a/src/client/component/discord.hpp +++ b/src/client/component/discord.hpp @@ -1,7 +1,9 @@ #pragma once +#include "game/game.hpp" + namespace discord { - std::string get_avatar_material(const std::string& id); + game::Material* get_avatar_material(const std::string& id); void respond(const std::string& id, int reply); } diff --git a/src/client/component/input.cpp b/src/client/component/input.cpp index 5112620e..36fbb49e 100644 --- a/src/client/component/input.cpp +++ b/src/client/component/input.cpp @@ -17,15 +17,6 @@ namespace input void cl_char_event_stub(const int local_client_num, const int key) { - if (game::environment::is_sp() && ui_scripting::lui_running()) - { - ui_scripting::notify("keypress", - { - {"keynum", key}, - {"key", game::Key_KeynumToString(key, 0, 1)}, - }); - } - if (!game_console::console_char_event(local_client_num, key)) { return; @@ -36,15 +27,6 @@ namespace input void cl_key_event_stub(const int local_client_num, const int key, const int down) { - if (ui_scripting::lui_running()) - { - ui_scripting::notify(down ? "keydown" : "keyup", - { - {"keynum", key}, - {"key", game::Key_KeynumToString(key, 0, 1)}, - }); - } - if (!game_console::console_key_event(local_client_num, key, down)) { return; diff --git a/src/client/component/materials.cpp b/src/client/component/materials.cpp index 5646bb06..60590679 100644 --- a/src/client/component/materials.cpp +++ b/src/client/component/materials.cpp @@ -21,7 +21,6 @@ namespace materials namespace { utils::hook::detour db_material_streaming_fail_hook; - utils::hook::detour material_register_handle_hook; utils::hook::detour db_get_material_index_hook; #ifdef DEBUG @@ -31,120 +30,8 @@ namespace materials const game::dvar_t* debug_materials = nullptr; #endif - struct material_data_t - { - std::unordered_map materials; - std::unordered_map images; - }; - char constant_table[0x20] = {}; - - utils::concurrency::container material_data; - - game::GfxImage* setup_image(game::GfxImage* image, const utils::image& raw_image) - { - image->imageFormat = 0x1000003; - image->resourceSize = -1; - - D3D11_SUBRESOURCE_DATA data{}; - data.SysMemPitch = raw_image.get_width() * 4; - data.SysMemSlicePitch = data.SysMemPitch * raw_image.get_height(); - data.pSysMem = raw_image.get_buffer(); - - game::Image_Setup(image, raw_image.get_width(), raw_image.get_height(), image->depth, image->numElements, - image->imageFormat, DXGI_FORMAT_R8G8B8A8_UNORM, image->name, &data); - - return image; - } - - game::Material* create_material(const std::string& name, const std::string& data) - { - const auto white = material_register_handle_hook.invoke("white"); - const auto material = utils::memory::get_allocator()->allocate(); - const auto texture_table = utils::memory::get_allocator()->allocate(); - const auto image = utils::memory::get_allocator()->allocate(); - - std::memcpy(material, white, sizeof(game::Material)); - std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef)); - std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage)); - - material->constantTable = &constant_table; - material->name = utils::memory::get_allocator()->duplicate_string(name); - image->name = material->name; - - material->textureTable = texture_table; - material->textureTable->u.image = setup_image(image, data); - - return material; - } - - void free_material(game::Material* material) - { - material->textureTable->u.image->textures.___u0.map->Release(); - material->textureTable->u.image->textures.shaderView->Release(); - utils::memory::get_allocator()->free(material->textureTable->u.image); - utils::memory::get_allocator()->free(material->textureTable); - utils::memory::get_allocator()->free(material->name); - utils::memory::get_allocator()->free(material); - } - - game::Material* load_material(const std::string& name) - { - return material_data.access([&](material_data_t& data_) -> game::Material* - { - if (const auto i = data_.materials.find(name); i != data_.materials.end()) - { - return i->second; - } - - std::string data{}; - if (const auto i = data_.images.find(name); i != data_.images.end()) - { - data = i->second; - } - - if (data.empty() && !filesystem::read_file(utils::string::va("materials/%s.png", name.data()), &data)) - { - data_.materials[name] = nullptr; - return nullptr; - } - - const auto material = create_material(name, data); - data_.materials[name] = material; - - return material; - }); - } - - game::Material* try_load_material(const std::string& name) - { - if (name == "white") - { - return nullptr; - } - - try - { - return load_material(name); - } - catch (const std::exception& e) - { - console::error("Failed to load material %s: %s\n", name.data(), e.what()); - } - - return nullptr; - } - - game::Material* material_register_handle_stub(const char* name) - { - auto result = try_load_material(name); - if (result == nullptr) - { - result = material_register_handle_hook.invoke(name); - } - return result; - } - + int db_material_streaming_fail_stub(game::Material* material) { if (material->constantTable == &constant_table) @@ -238,38 +125,73 @@ namespace materials #endif } - void add(const std::string& name, const std::string& data) + bool setup_material_image(game::Material* material, const std::string& data) { - material_data.access([&](material_data_t& data_) + if (*game::d3d11_device == nullptr) { - data_.images[name] = data; - }); + console::error("Tried to create texture while d3d11 device isn't initialized\n"); + return false; + } + + const auto image = material->textureTable->u.image; + image->imageFormat = 0x1000003; + image->resourceSize = -1; + + auto raw_image = utils::image{data}; + + D3D11_SUBRESOURCE_DATA resource_data{}; + resource_data.SysMemPitch = raw_image.get_width() * 4; + resource_data.SysMemSlicePitch = resource_data.SysMemPitch * raw_image.get_height(); + resource_data.pSysMem = raw_image.get_buffer(); + + game::Image_Setup(image, raw_image.get_width(), raw_image.get_height(), image->depth, image->numElements, + image->imageFormat, DXGI_FORMAT_R8G8B8A8_UNORM, image->name, &resource_data); + return true; } - bool exists(const std::string& name) + game::Material* create_material(const std::string& name) { - return material_data.access([&](material_data_t& data_) - { - return data_.images.find(name) != data_.images.end(); - }); + const auto white = game::Material_RegisterHandle("$white"); + const auto material = utils::memory::allocate(); + const auto texture_table = utils::memory::allocate(); + const auto image = utils::memory::allocate(); + + std::memcpy(material, white, sizeof(game::Material)); + std::memcpy(texture_table, white->textureTable, sizeof(game::MaterialTextureDef)); + std::memcpy(image, white->textureTable->u.image, sizeof(game::GfxImage)); + + material->constantTable = &constant_table; + material->name = utils::memory::duplicate_string(name); + image->name = material->name; + + image->textures.map = nullptr; + image->textures.shaderView = nullptr; + image->textures.shaderViewAlternate = nullptr; + + material->textureTable = texture_table; + + return material; } - void clear() + void free_material(game::Material* material) { - material_data.access([&](material_data_t& data_) + const auto try_release = [](T** resource) { - for (auto& material : data_.materials) + if (*resource != nullptr) { - if (material.second == nullptr) - { - continue; - } - - free_material(material.second); + (*resource)->Release(); + *resource = nullptr; } + }; - data_.materials.clear(); - }); + try_release(&material->textureTable->u.image->textures.map); + try_release(&material->textureTable->u.image->textures.shaderView); + try_release(&material->textureTable->u.image->textures.shaderViewAlternate); + + utils::memory::free(material->textureTable->u.image); + utils::memory::free(material->textureTable); + utils::memory::free(material->name); + utils::memory::free(material); } class component final : public component_interface @@ -282,7 +204,6 @@ namespace materials return; } - material_register_handle_hook.create(game::Material_RegisterHandle, material_register_handle_stub); db_material_streaming_fail_hook.create(SELECT_VALUE(0x1FB400_b, 0x3A1600_b), db_material_streaming_fail_stub); db_get_material_index_hook.create(SELECT_VALUE(0x1F1D80_b, 0x396000_b), db_get_material_index_stub); @@ -296,7 +217,7 @@ namespace materials scheduler::once([] { - debug_materials = dvars::register_bool("debug_materials", 0, 0x0, "Print current material and images"); + debug_materials = dvars::register_bool("debug_materials", false, game::DVAR_FLAG_NONE, "Print current material and images"); }, scheduler::main); } #endif diff --git a/src/client/component/materials.hpp b/src/client/component/materials.hpp index 0c02227f..7ca0ddfa 100644 --- a/src/client/component/materials.hpp +++ b/src/client/component/materials.hpp @@ -1,8 +1,10 @@ #pragma once +#include "game/game.hpp" + namespace materials { - void add(const std::string& name, const std::string& data); - bool exists(const std::string& name); - void clear(); + bool setup_material_image(game::Material* material, const std::string& data); + game::Material* create_material(const std::string& name); + void free_material(game::Material* material); } diff --git a/src/client/component/mods.cpp b/src/client/component/mods.cpp index 83c0f6dd..cfabb84d 100644 --- a/src/client/component/mods.cpp +++ b/src/client/component/mods.cpp @@ -30,7 +30,6 @@ namespace mods { if (release_assets) { - materials::clear(); fonts::clear(); } diff --git a/src/client/component/party.cpp b/src/client/component/party.cpp index 5aa10986..0ccf100b 100644 --- a/src/client/component/party.cpp +++ b/src/client/component/party.cpp @@ -33,7 +33,7 @@ namespace party namespace { connection_state server_connection_state{}; - discord_information server_discord_information{}; + std::optional server_discord_info{}; struct usermap_file { @@ -755,9 +755,9 @@ namespace party return server_connection_state; } - discord_information get_server_discord_information() + std::optional get_server_discord_info() { - return server_discord_information; + return server_discord_info; } class component final : public component_interface @@ -1152,8 +1152,14 @@ namespace party server_connection_state.motd = info.get("sv_motd"); server_connection_state.max_clients = std::stoi(info.get("sv_maxclients")); server_connection_state.base_url = info.get("sv_wwwBaseUrl"); - server_discord_information.image = info.get("sv_discordImageUrl"); - server_discord_information.image_text = info.get("sv_discordImageText"); + + discord_information discord_info{}; + discord_info.image = info.get("sv_discordImageUrl"); + discord_info.image_text = info.get("sv_discordImageText"); + if (!discord_info.image.empty() || !discord_info.image_text.empty()) + { + server_discord_info.emplace(discord_info); + } connect_to_party(target, mapname, gametype); }); diff --git a/src/client/component/party.hpp b/src/client/component/party.hpp index 6a367d66..a9c387bf 100644 --- a/src/client/component/party.hpp +++ b/src/client/component/party.hpp @@ -30,7 +30,7 @@ namespace party void clear_sv_motd(); connection_state get_server_connection_state(); - discord_information get_server_discord_information(); + std::optional get_server_discord_info(); int get_client_num_by_name(const std::string& name); diff --git a/src/client/component/ui_scripting.cpp b/src/client/component/ui_scripting.cpp index d0590f71..c62dd8ea 100644 --- a/src/client/component/ui_scripting.cpp +++ b/src/client/component/ui_scripting.cpp @@ -376,7 +376,19 @@ namespace ui_scripting lua["discord"] = discord_table; discord_table["respond"] = discord::respond; - discord_table["getavatarmaterial"] = discord::get_avatar_material; + + discord_table["getavatarmaterial"] = [](const std::string& id) + -> script_value + { + const auto material = discord::get_avatar_material(id); + if (material == nullptr) + { + return {}; + } + + return lightuserdata(material); + }; + discord_table["reply"] = table(); discord_table["reply"]["yes"] = DISCORD_REPLY_YES; discord_table["reply"]["ignore"] = DISCORD_REPLY_IGNORE; diff --git a/src/client/game/structs.hpp b/src/client/game/structs.hpp index 6150c8ee..f2f57509 100644 --- a/src/client/game/structs.hpp +++ b/src/client/game/structs.hpp @@ -1542,18 +1542,16 @@ namespace game char data[1]; }; - union $3FA29451CE6F1FA138A5ABAB84BE9676 - { - ID3D11Texture1D* linemap; - ID3D11Texture2D* map; - ID3D11Texture3D* volmap; - ID3D11Texture2D* cubemap; - GfxImageLoadDef* loadDef; - }; - struct GfxTexture { - $3FA29451CE6F1FA138A5ABAB84BE9676 ___u0; + union + { + ID3D11Texture1D* linemap; + ID3D11Texture2D* map; + ID3D11Texture3D* volmap; + ID3D11Texture2D* cubemap; + GfxImageLoadDef* loadDef; + }; ID3D11ShaderResourceView* shaderView; ID3D11ShaderResourceView* shaderViewAlternate; }; diff --git a/src/client/game/symbols.hpp b/src/client/game/symbols.hpp index 7f8b2ea0..3c77e591 100644 --- a/src/client/game/symbols.hpp +++ b/src/client/game/symbols.hpp @@ -125,6 +125,8 @@ namespace game WEAK symbol I_CleanStr{0x4293E0, 0x5AF2E0}; WEAK symbol Key_KeynumToString{0x1AC410, 0x199990}; + WEAK symbol Key_GetBindingForCmd{0x377280, 0x1572B0}; + WEAK symbol Key_SetBinding{0x1AC570, 0x199AE0}; WEAK symbol Live_SyncOnlineDataFlags{0x0, 0x1A5C10}; @@ -329,7 +331,7 @@ namespace game WEAK symbol g_zoneInfo{0x0, 0x5F5A370}; WEAK symbol g_zoneIndex{0x0, 0x3D1008C}; - WEAK symbol< DB_FileSysInterface*> db_fs{0x25C1168, 0x1566C08}; + WEAK symbol db_fs{0x25C1168, 0x1566C08}; WEAK symbol keyCatchers{0x252AF70, 0x2EC82C4}; WEAK symbol playerKeys{0x2395B0C, 0x2999E1C}; @@ -347,6 +349,8 @@ namespace game WEAK symbol maps{0x7CE5A0, 0x926C80}; + WEAK symbol d3d11_device{0x1163B98, 0x12DFBF8}; + namespace mp { WEAK symbol g_entities{0x0, 0x71F19E0}; diff --git a/src/common/utils/string.cpp b/src/common/utils/string.cpp index 30e35e9d..b28dab70 100644 --- a/src/common/utils/string.cpp +++ b/src/common/utils/string.cpp @@ -131,6 +131,14 @@ namespace utils::string *out = '\0'; } + std::string strip(const std::string& string) + { + std::string new_string; + new_string.resize(string.size(), 0); + strip(string.data(), new_string.data(), static_cast(new_string.size())); + return new_string; + } + std::string convert(const std::wstring& wstr) { std::string result; diff --git a/src/common/utils/string.hpp b/src/common/utils/string.hpp index 13bcbbf8..fa1c696f 100644 --- a/src/common/utils/string.hpp +++ b/src/common/utils/string.hpp @@ -92,6 +92,7 @@ namespace utils::string std::string get_clipboard_data(); void strip(const char* in, char* out, int max); + std::string strip(const std::string& string); std::string convert(const std::wstring& wstr); std::wstring convert(const std::string& str);