Merge pull request #177 from h1-mod/develop

Release v1.0.4
This commit is contained in:
fed 2022-07-08 23:53:31 +00:00 committed by GitHub
commit d89f3f2d13
39 changed files with 1534 additions and 187 deletions

2
.gitmodules vendored
View File

@ -40,7 +40,7 @@
[submodule "deps/protobuf"]
path = deps/protobuf
url = https://github.com/protocolbuffers/protobuf.git
branch = 3.17.x
branch = 3.20.x
[submodule "deps/zlib"]
path = deps/zlib
url = https://github.com/madler/zlib.git

View File

@ -0,0 +1,146 @@
if game:issingleplayer() then
return
end
-- from mpdepotbase.lua, global definition isn't working
InventoryCurrencyType = {
LaunchCredits = 1,
Credits = 2,
Parts = 3,
CoDPoints = 4,
Bonus = 5,
Max = 6
}
ItemRarity = {
Common = 0,
Rare = 1,
Legendary = 2,
Epic = 3
}
custom_depot = {
collection_details_menu = nil,
data = {
currencies = {
launchCredits = 0, -- LaunchCredits
credits = 0, -- Credits
parts = 0, -- Parts
codPoints = 0, -- CoDPoints
bonus = 0 -- Bonus
},
items = {},
reward_splashes = {},
has_accepted_mod_eula = false,
has_seen_mod_eula = false
},
directory_path = "players2/user",
file_name = "depot.json",
file_path = nil,
functions = {}
}
custom_depot.file_path = string.format("%s/%s", custom_depot.directory_path, custom_depot.file_name)
custom_depot.get_function = function(function_name)
if not function_name or not custom_depot.functions[function_name] then
return nil
end
return custom_depot.functions[function_name]
end
custom_depot.functions["save_depot_data"] = function()
io.writefile(custom_depot.file_path, json.encode(custom_depot.data), false)
end
custom_depot.functions["load_depot_data"] = function()
if not io.directoryexists(custom_depot.directory_path) then
io.createdirectory(custom_depot.directory_path)
end
if not io.fileexists(custom_depot.file_path) then
custom_depot.get_function("save_depot_data")()
end
custom_depot.data = json.decode(io.readfile(custom_depot.file_path))
end
local function convert_currency_to_string(type)
if type == InventoryCurrencyType.LaunchCredits then
return "launchCredits"
elseif type == InventoryCurrencyType.Credits then
return "credits"
elseif type == InventoryCurrencyType.Parts then
return "parts"
elseif type == InventoryCurrencyType.CoDPoints then
return "codPoints"
elseif type == InventoryCurrencyType.Bonus then
return "bonus"
end
end
custom_depot.functions["add_currency"] = function(currency_type, amount)
local type = convert_currency_to_string(currency_type)
custom_depot.data.currencies[type] = custom_depot.data.currencies[type] + amount
end
custom_depot.functions["remove_currency"] = function(currency_type, amount)
local type = convert_currency_to_string(currency_type)
custom_depot.data.currencies[type] = custom_depot.data.currencies[type] - amount
end
custom_depot.functions["get_currency"] = function(currency_type)
local type = convert_currency_to_string(currency_type)
if not currency_type or not custom_depot.data.currencies[type] then
return nil
end
return custom_depot.data.currencies[type]
end
custom_depot.functions["add_item"] = function(item, value)
custom_depot.data.items[item] = value
end
custom_depot.functions["has_item"] = function(item)
return custom_depot.data.items[item] ~= nil
end
custom_depot.functions["add_reward_splash"] = function(item, value)
custom_depot.data.reward_splashes[item] = value
end
custom_depot.functions["has_reward_splash"] = function(item)
return custom_depot.data.reward_splashes[item] ~= nil
end
custom_depot.functions["has_accepted_mod_eula"] = function()
return custom_depot.data.has_accepted_mod_eula
end
custom_depot.functions["set_has_accepted_mod_eula"] = function(value)
custom_depot.data.has_accepted_mod_eula = value
custom_depot.get_function("save_depot_data")()
end
custom_depot.functions["has_seen_mod_eula"] = function()
return custom_depot.data.has_seen_mod_eula
end
custom_depot.functions["set_has_seen_mod_eula"] = function(value)
custom_depot.data.has_seen_mod_eula = value
custom_depot.get_function("save_depot_data")()
end
custom_depot.get_function("load_depot_data")()
if Engine.InFrontend() then
require("mod_eula")
require("depot_override")
end
if not Engine.InFrontend() then
require("scoreboard_override")
end

View File

@ -0,0 +1,332 @@
GetCurrencyBalance = function(currency_type)
return custom_depot.get_function("get_currency")(currency_type)
end
Inventory_PurchaseItem_orig = Engine.Inventory_PurchaseItem
Engine.Inventory_PurchaseItem = function(controller, item_guid, unk2)
if not custom_depot.get_function("has_item")(item_guid) then
custom_depot.get_function("add_item")(item_guid, true)
local item_value = Engine.TableLookup(LootTable.File, LootTable.Cols.GUID, item_guid, LootTable.Cols.Value)
custom_depot.get_function("remove_currency")(InventoryCurrencyType.Parts, item_value)
custom_depot.get_function("save_depot_data")()
if custom_depot.collection_details_menu then
custom_depot.collection_details_menu:OnCraftedItem()
end
end
return Inventory_PurchaseItem_orig(controller, item_guid, unk2)
end
GetItemLockState_orig = Engine.GetItemLockState
Engine.GetItemLockState = function(controller, item_guid)
if custom_depot.get_function("has_item")(item_guid) then
return "Unlocked", 0, ""
end
return GetItemLockState_orig(controller, item_guid)
end
GetItemSet_orig = GetItemSet
GetItemSet = function(item_set_id)
local item_set = GetItemSet_orig(item_set_id)
local items_unlocked = 0
for k, v in pairs(item_set.setItems) do
if custom_depot.get_function("has_item")(v.guid) and (not v.isOwned or v.lockState == "Unlocked") then
v.isOwned = true
v.lockState = "Unlocked"
items_unlocked = items_unlocked + 1
end
end
if items_unlocked == #item_set.setItems then
if not item_set.completed then
item_set.completed = true
end
if not custom_depot.get_function("has_item")(item_set.setReward.guid) then
custom_depot.get_function("add_item")(item_set.setReward.guid, true)
custom_depot.get_function("save_depot_data")()
if custom_depot.collection_details_menu then
custom_depot.collection_details_menu:OnCompletedSet()
end
end
end
item_set.numOwned = items_unlocked
return item_set
end
GetItemSets_orig = GetItemSets
GetItemSets = function()
local item_sets = GetItemSets_orig()
local completed_sets = 0
for i = 1, #item_sets.seasons do
local seasons_completed_sets = 0
local sets = item_sets.seasons[i].sets
local rewardData = item_sets.seasons[i].rewardData
for i = 1, #sets do
if sets[i].completed then
completed_sets = completed_sets + 1
seasons_completed_sets = seasons_completed_sets + 1
end
end
if item_sets.seasons[i].completedSets == #sets then
rewardData.setReward.isOwned = true
rewardData.setReward.lockState = "Unlocked"
rewardData.completed = true
if not custom_depot.get_function("has_item")(rewardData.setReward.guid) then
custom_depot.get_function("add_item")(rewardData.setReward.guid, true)
custom_depot.get_function("save_depot_data")()
end
end
item_sets.seasons[i].completedSets = seasons_completed_sets
end
for k, v in pairs(item_sets.itemToSetMap) do
local items_unlocked = 0
for i = 1, #v.setItems do
if custom_depot.get_function("has_item")(v.setItems[i].guid) and
(not v.setItems[i].isOwned or v.setItems[i].lockState == "Unlocked") then
v.setItems[i].isOwned = true
v.setItems[i].lockState = "Unlocked"
items_unlocked = items_unlocked + 1
end
end
if items_unlocked == #v.setItems then
if not v.completed then
v.completed = true
completed_sets = completed_sets + 1
end
if not custom_depot.get_function("has_item")(v.setReward.guid) then
custom_depot.get_function("add_item")(v.setReward.guid, true)
custom_depot.get_function("save_depot_data")()
end
end
v.numOwned = items_unlocked
end
item_sets.completedSets = completed_sets
return item_sets
end
IsContentPromoUnlocked_orig = IsContentPromoUnlocked
IsContentPromoUnlocked = function()
return true
end
TryShowCollectionCompleted_orig = TryShowCollectionCompleted
TryShowCollectionCompleted = function(controller, reward_data, unk1)
if reward_data.completed then
if not custom_depot.get_function("has_reward_splash")(reward_data.setReward.guid) then
LUI.FlowManager.RequestAddMenu(nil, "MPDepotCollectionRewardSplash", true, controller, false, {
collectionData = reward_data
})
if custom_depot.collection_details_menu then
custom_depot.collection_details_menu:OnCompletedSet()
end
custom_depot.get_function("add_reward_splash")(reward_data.setReward.guid, true)
custom_depot.get_function("save_depot_data")()
end
return true
else
return false
end
end
TryShowSeasonCompleted_orig = TryShowSeasonCompleted
TryShowSeasonCompleted = function(controller, reward_data, unk1)
if reward_data.completed then
if not custom_depot.get_function("has_reward_splash")(reward_data.setReward.guid) then
LUI.FlowManager.RequestAddMenu(nil, "MPDepotCollectionRewardSplash", true, controller, false, {
collectionData = reward_data
})
if custom_depot.collection_details_menu then
custom_depot.collection_details_menu:OnCompletedSet()
end
custom_depot.get_function("add_reward_splash")(reward_data.setReward.guid, true)
custom_depot.get_function("save_depot_data")()
end
return true
else
return false
end
end
MPDepotCollectionDetailsMenu_orig = LUI.MenuBuilder.m_types_build["MPDepotCollectionDetailsMenu"]
MPDepotCollectionDetailsMenu = function(unk1, unk2)
custom_depot.collection_details_menu = MPDepotCollectionDetailsMenu_orig(unk1, unk2)
return custom_depot.collection_details_menu
end
LUI.MenuBuilder.m_types_build["MPDepotCollectionDetailsMenu"] = MPDepotCollectionDetailsMenu
MPDepotOpenLootMenu_orig = LUI.MenuBuilder.m_types_build["MPDepotOpenLootMenu"]
MPDepotOpenLootMenu = function(unk1, unk2)
local open_loot_menu = MPDepotOpenLootMenu_orig(unk1, unk2)
local supply_drop_orig = open_loot_menu.m_eventHandlers["supply_drop"]
open_loot_menu:registerEventHandler("supply_drop", function(f48_arg0, f48_arg1)
f48_arg1.success = true
f48_arg1.transaction = f48_arg0.supplyDropTransaction
f48_arg1.duplicateRefund = false
f48_arg1.items = {}
f48_arg1.currencies = {}
f48_arg1.replacements = {}
f48_arg1.cards = {}
local supply_drop_price = LUI.MPDepot.GetSupplyDropPrice(f48_arg0.supplyDropType)
custom_depot.get_function("remove_currency")(supply_drop_price.type, supply_drop_price.amount)
custom_depot.get_function("save_depot_data")()
for i = 1, unk2.crateType:find("_basic") and math.random(1, 2) or math.random(2, 3) do
local items_list = LUI.MPLootDropsBase.GetGenericItemList(x, LUI.MPDepot.LootDropsData[LUI.MPDepot
.SuppyDropLootStream[unk2.crateType]].lootTableColName)
local random_item = items_list[math.random(#items_list)]
while random_item.inventoryItemType ~= Cac.InventoryItemType.Loot do
random_item = items_list[math.random(#items_list)]
end
if random_item then
f48_arg1.items[i] = random_item.guid
end
end
for i = 1, #f48_arg1.items do
if not custom_depot.get_function("has_item")(f48_arg1.items[i]) then
custom_depot.get_function("add_item")(f48_arg1.items[i], true)
else
local item_rarity = tonumber(Engine.TableLookup(LootTable.File, LootTable.Cols.GUID, f48_arg1.items[i],
LootTable.Cols.Rarity))
local dismantled_amount = 0
if item_rarity == ItemRarity.Common then
dismantled_amount = math.random(1, 75)
elseif item_rarity == ItemRarity.Rare then
dismantled_amount = math.random(75, 155)
elseif item_rarity == ItemRarity.Legendary then
dismantled_amount = math.random(155, 260)
elseif item_rarity == ItemRarity.Epic then
dismantled_amount = math.random(260, 550)
end
table.insert(f48_arg1.replacements, {
item_index = i,
currency = {
amount = dismantled_amount
}
})
custom_depot.get_function("add_currency")(InventoryCurrencyType.Parts, dismantled_amount)
end
end
custom_depot.get_function("save_depot_data")()
supply_drop_orig(f48_arg0, f48_arg1)
end)
local slow_purchase_transfer_orig = open_loot_menu.m_eventHandlers["slow_purchase_transfer"]
open_loot_menu:registerEventHandler("slow_purchase_transfer", function(f33_arg0, f33_arg1)
local f33_local0 = 0
if f33_arg0.slowPurchaseTimer then
f33_arg0.slowPurchaseTimer:close()
f33_arg0.slowPurchaseTimer = nil
end
local f33_local1 = CoD.CreateState(-500, 0, 500, 20, CoD.AnchorTypes.Top)
f33_local1.font = CoD.TextSettings.BodyFont.Font
f33_local1.verticalAlignment = LUI.VerticalAlignment.Top
f33_local1.horizontalAlignment = LUI.HorizontalAlignment.Center
f33_local1.color = Colors.mw1_green
f33_local1.alpha = 1
f33_arg0.slowPurchaseText = LUI.UIText.new(f33_local1)
f33_arg0:addElement(f33_arg0.slowPurchaseText)
f33_arg0.slowPurchaseText:setText(Engine.Localize("@DEPOT_TRANSFER_IN_PROGRESS_DOT"))
f33_arg0.slowPurchaseText.textState = 1
f33_arg0.slowPurchaseText:registerEventHandler("update_slow_purchase_text", f0_local30)
f33_arg0.slowPurchaseText:addElement(LUI.UITimer.new(1000, "update_slow_purchase_text"))
local f33_local2 = LUI.MenuBuilder.BuildRegisteredType("progressBar")
f33_local2:registerAnimationState("default", {
topAnchor = false,
bottomAnchor = true,
leftAnchor = false,
rightAnchor = false,
top = 0,
height = 40
})
f33_local2:animateToState("default")
f33_arg0.slowPurchaseText:addElement(f33_local2)
f33_local2:animateFill(f33_local0)
f33_arg0.purchaseTimeoutTimer = LUI.UITimer.new(f33_local0, "abort_purchase_transfer", nil, true)
f33_arg0:addElement(f33_arg0.purchaseTimeoutTimer)
end)
return open_loot_menu
end
LUI.MenuBuilder.m_types_build["MPDepotOpenLootMenu"] = MPDepotOpenLootMenu
AddLootDropTabSelector_orig = LUI.MPDepotBase.AddLootDropTabSelector
LUI.MPDepotBase.AddLootDropTabSelector = function(unk1, unk2)
if not custom_depot.get_function("has_accepted_mod_eula")() then
local item_sets = GetItemSets()
unk1:AddButtonWithInfo("depot_collections", "@DEPOT_COLLECTIONS", "MPDepotCollectionsMenu", nil, nil,
Engine.Localize("@MPUI_X_SLASH_Y", item_sets.completedSets, item_sets.numSets))
unk1:AddButtonWithInfo("depot_armory", "@DEPOT_ARMORY", "MPDepotArmoryMenu")
return
end
AddLootDropTabSelector_orig(unk1, unk2)
end
MPDepotMenu_orig = LUI.MenuBuilder.m_types_build["MPDepotMenu"]
MPDepotMenu = function(unk1, unk2)
local depot_menu = MPDepotMenu_orig(unk1, unk2)
if not custom_depot.get_function("has_seen_mod_eula")() then
LUI.FlowManager.RequestAddMenu(nil, "mod_eula", true, 0, false, {
acceptCallback = function()
custom_depot.get_function("set_has_accepted_mod_eula")(true)
custom_depot.get_function("set_has_seen_mod_eula")(true)
LUI.FlowManager.RequestLeaveMenu(depot_menu)
end,
declineCallback = function()
custom_depot.get_function("set_has_accepted_mod_eula")(false)
custom_depot.get_function("set_has_seen_mod_eula")(true)
end
})
end
return depot_menu
end
LUI.MenuBuilder.m_types_build["MPDepotMenu"] = MPDepotMenu
GetLootDataForRef_orig = LUI.InventoryUtils.GetLootDataForRef
LUI.InventoryUtils.GetLootDataForRef = function(f13_arg0, f13_arg1, f13_arg2, f13_arg3, f13_arg4)
local loot_data = GetLootDataForRef_orig(f13_arg0, f13_arg1, f13_arg2, f13_arg3, f13_arg4)
if loot_data and custom_depot.get_function("has_item")(loot_data.guid) then
loot_data.lockState = "Unlocked"
end
return loot_data
end

View File

@ -0,0 +1,23 @@
game:addlocalizedstring("CUSTOM_DEPOT_EULA_1", "Dear User,")
game:addlocalizedstring("CUSTOM_DEPOT_EULA_2",
"By using this feature, you acknowledge that you are over 18 years old, and that any sort of chance games / gambling are allowed in your country (even if they do not involve real money).")
game:addlocalizedstring("CUSTOM_DEPOT_EULA_3",
"The H1-Mod team is not responsible if you break the law within your country, and the sole responsibility will be upon you to respect the same.")
game:addlocalizedstring("CUSTOM_DEPOT_EULA_4",
"The H1-Mod team will never include real money transactions within the modified systems. The only way to get currency, should you wish to, is by playing the game.")
game:addlocalizedstring("CUSTOM_DEPOT_EULA_5", "Best Regards,")
game:addlocalizedstring("CUSTOM_DEPOT_EULA_6", "The H1-Mod Team.")
local mod_eula = function(unk1, unk2)
return LUI.EULABase.new(CoD.CreateState(0, 0, 0, 0, CoD.AnchorTypes.All), {
textStrings = LUI.EULABase.CreateTextStrings("@CUSTOM_DEPOT_EULA_", 6),
declineCallback = function(unk3)
unk2.declineCallback(unk3)
end,
acceptCallback = function(unk4)
unk2.acceptCallback(unk4)
end
})
end
LUI.MenuBuilder.registerPopupType("mod_eula", mod_eula)

View File

@ -0,0 +1,56 @@
-- from roundend.lua, dev comments says that the game["round_end"] array contains indexes for this table
local ending_reasons = {"MP_DRAW", "LUA_MENU_REPORT_DRAW", "MP_ROUND_WIN", "MP_ROUND_LOSS", "LUA_MENU_REPORT_VICTORY",
"LUA_MENU_REPORT_DEFEAT", "MP_HALFTIME", "MP_OVERTIME", "MP_ROUNDEND", "MP_INTERMISSION",
"MP_SWITCHING_SIDES", "MP_MATCH_BONUS_IS", "MP_MATCH_TIE", "MP_GAME_END", "SPLASHES_BLANK"}
local function starts_with(str, start)
return str:sub(1, #start) == start
end
local player_old_score = 0
local scoreboard_orig = LUI.MenuBuilder.m_types_build["scoreboard"]
local scoreboard = function(unk1, unk2)
local scoreboard = scoreboard_orig(unk1, unk2)
scoreboard:registerOmnvarHandler("ui_round_end", function(f22_arg0, f22_arg1)
if GameX.IsRankedMatch() then
local player_score = 0
local gamemode = GameX.GetGameMode()
local player_stats = Game.GetPlayerScoreInfoAtRank(Game.GetPlayerTeam(), Game.GetPlayerScoreRanking())
--[[
this will do the job for when its needed, aka when round loss/win occurs
this check may be true more than once cuz this callback happens 3 times,
but the player_old_score variable will stop us from adding more currency
]] --
local unlocalized_string = ending_reasons[Game.GetOmnvar("ui_round_end_title")]
local is_round_based = starts_with(unlocalized_string, "MP_ROUND") or IsGameTypeRoundBased(gamemode)
if is_round_based or gamemode == "conf" or gamemode == "war" then
player_score = player_stats.score
else
player_score = player_stats.extrascore0
end
local currency_gain = math.floor((player_score - player_old_score) * 10 / 100)
if currency_gain <= 0 then
return
end
custom_depot.get_function("add_currency")(InventoryCurrencyType.Parts, currency_gain)
if custom_depot.functions["has_accepted_mod_eula"]() then
custom_depot.get_function("add_currency")(InventoryCurrencyType.Credits, math.random(2, 3))
end
player_old_score = player_score
custom_depot.get_function("save_depot_data")()
end
end)
return scoreboard
end
LUI.MenuBuilder.m_types_build["scoreboard"] = scoreboard

View File

@ -0,0 +1 @@
-- this patch has been moved to ui_scripts/patches/gamemodes.lua

View File

@ -9,6 +9,8 @@ game:addlocalizedstring("LUA_MENU_LATENCY_DESC", "Show server latency.")
game:addlocalizedstring("LUA_MENU_RED_DOT_BRIGHTNESS", "Red dot Brightness")
game:addlocalizedstring("LUA_MENU_RED_DOT_BRIGHTNESS_DESC", "Adjust the brightness of red dot reticles.")
game:replacelocalizedstring("MENU_SYSINFO_CUSTOMER_SUPPORT_URL", "https://h1.gg/")
function createdivider(menu, text)
local element = LUI.UIElement.new({
leftAnchor = true,

View File

@ -0,0 +1 @@
-- this patch has been moved to ui_scripts/patches/no_mode_switch.lua

View File

@ -5,6 +5,8 @@ end
if (Engine.InFrontend()) then
require("shaderdialog")
require("gamemodes")
require("no_mode_switch")
require("disable_useless_things")
end
-- defined in mp_hud/hudutils.lua

View File

@ -0,0 +1,17 @@
-- Disable CP
Engine.SetDvarInt("ui_enable_cp", 0)
-- Disable CP store
Engine.SetDvarInt("ui_show_store", 0)
-- Remove CoD account button
if Engine.IsMultiplayer() and CoD.IsCoDAccountRegistrationAvailableInMyRegion() then
LUI.removemenubutton("pc_controls", 4)
end
-- Remove social button
LUI.MenuBuilder.m_definitions["online_friends_widget"] = function()
return {
type = "UIElement"
}
end

View File

@ -0,0 +1,13 @@
LUI.MenuBuilder.m_definitions["main_choose_exe_popup_menu"] = function()
return {
type = "generic_yesno_popup",
id = "main_choose_exe_popup_menu_id",
properties = {
popup_title = Engine.Localize("@MENU_NOTICE"),
message_text = Engine.Localize("@MENU_QUIT_WARNING"),
yes_action = function()
Engine.Quit()
end
}
}
end

2
deps/asmjit vendored

@ -1 +1 @@
Subproject commit 35f92e8706db78c6aa7482b5e1cdb59c5972a965
Subproject commit c59847629d3a19da4d10f0be4ac33b43fc4a100f

2
deps/curl vendored

@ -1 +1 @@
Subproject commit e2e7f54b7bea521fa8373095d0f43261a720cda0
Subproject commit 9153ba708be87ed6e7c25e1b4864f86fadeb95ad

2
deps/protobuf vendored

@ -1 +1 @@
Subproject commit 5500c72c5b616da9f0125bcfab513987a1226e2b
Subproject commit 6e9e60367d8744e86856590d8ea0e793c61deeec

2
deps/zlib vendored

@ -1 +1 @@
Subproject commit ec3df00224d4b396e2ac6586ab5d25f673caa4c2
Subproject commit 2333419cd76cb9ae5f15c9b240b16a2052b27691

View File

@ -2,9 +2,11 @@
#include "loader/component_loader.hpp"
#include "command.hpp"
#include "console.hpp"
#include "scheduler.hpp"
#include "network.hpp"
#include "party.hpp"
#include "scripting.hpp"
#include "game/game.hpp"
#include "game/scripting/execution.hpp"
@ -12,6 +14,7 @@
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/cryptography.hpp>
#include <utils/io.hpp>
namespace bots
{
@ -53,14 +56,12 @@ namespace bots
return;
}
static auto first_bot = true;
const auto bot_name = game::SV_BotGetRandomName();
const auto bot_ent = game::SV_AddBot(bot_name);
const auto* const bot_name = game::SV_BotGetRandomName();
const auto* bot_ent = game::SV_AddBot(bot_name);
if (bot_ent)
{
spawn_bot(bot_ent->s.entityNum);
spawn_bot(bot_ent->s.number);
}
else
{
@ -70,6 +71,50 @@ namespace bots
}, scheduler::pipeline::server, 100ms);
}
}
utils::hook::detour get_bot_name_hook;
std::vector<std::string> bot_names{};
void load_bot_data()
{
static const char* bots_txt = "h1-mod/bots.txt";
std::string bots_content;
if (!utils::io::read_file(bots_txt, &bots_content))
{
return;
}
auto names = utils::string::split(bots_content, '\n');
for (auto& name : names)
{
name = utils::string::replace(name, "\r", "");
if (!name.empty())
{
bot_names.emplace_back(name);
}
}
}
size_t bot_id = 0;
const char* get_random_bot_name()
{
if (bot_names.empty())
{
load_bot_data();
}
// only use bot names once, no dupes in names
if (!bot_names.empty() && bot_id < bot_names.size())
{
bot_id %= bot_names.size();
const auto& entry = bot_names.at(bot_id++);
return utils::string::va("%.*s", static_cast<int>(entry.size()), entry.data());
}
return get_bot_name_hook.invoke<const char*>();
}
}
class component final : public component_interface
@ -82,6 +127,8 @@ namespace bots
return;
}
get_bot_name_hook.create(game::SV_BotGetRandomName, get_random_bot_name);
command::add("spawnBot", [](const command::params& params)
{
if (!can_add())
@ -102,6 +149,13 @@ namespace bots
scheduler::once(add_bot, scheduler::pipeline::server, 100ms * i);
}
});
// Clear bot names and reset ID on game shutdown to allow new names to be added without restarting
scripting::on_shutdown([]
{
bot_names.clear();
bot_id = 0;
});
}
};
}

View File

@ -1,6 +1,10 @@
#include <std_include.hpp>
#include "loader/component_loader.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "game/scripting/execution.hpp"
#include "command.hpp"
#include "console.hpp"
#include "game_console.hpp"
@ -8,11 +12,6 @@
#include "scheduler.hpp"
#include "logfile.hpp"
#include "game/game.hpp"
#include "game/dvars.hpp"
#include "game/scripting/execution.hpp"
#include <utils/hook.hpp>
#include <utils/string.hpp>
#include <utils/memory.hpp>
@ -39,8 +38,14 @@ namespace command
}
}
void client_command(const int client_num)
void client_command(const char client_num)
{
if (game::mp::g_entities[client_num].client == nullptr)
{
// Client is not fully connected
return;
}
if (!logfile::client_command_stub(client_num))
{
return;
@ -539,9 +544,9 @@ namespace command
static void add_commands_generic()
{
add("quit", game::Quit);
add("crash", []()
add("crash", []
{
*reinterpret_cast<int*>(1) = 0;
*reinterpret_cast<int*>(1) = 0x12345678;
});
add("commandDump", [](const params& argument)

View File

@ -32,7 +32,7 @@ namespace logfile
return {};
}
const scripting::entity player{game::Scr_GetEntityId(ent->s.entityNum, 0)};
const scripting::entity player{game::Scr_GetEntityId(ent->s.number, 0)};
return scripting::lua::convert(state, player);
}
@ -262,7 +262,7 @@ namespace logfile
scheduler::once([cmd, message, self, hidden]()
{
const scripting::entity level{*game::levelEntityId};
const scripting::entity player{game::Scr_GetEntityId(self->s.entityNum, 0)};
const scripting::entity player{game::Scr_GetEntityId(self->s.number, 0)};
scripting::notify(level, cmd, {player, message, hidden});
scripting::notify(player, cmd, {message, hidden});

View File

@ -14,6 +14,8 @@ namespace lui
namespace
{
uint64_t event_count{};
uint64_t obituary_count{};
bool begin_game_message_event_stub(int a1, const char* name, void* a3)
{
if (event_count > 30)
@ -27,6 +29,16 @@ namespace lui
return utils::hook::invoke<bool>(0x2655A0_b, a1, name, a3);
}
void cg_entity_event_stub(void* a1, void* a2, unsigned int event_type, void* a4)
{
if (event_type == 140 && obituary_count++ >= 20)
{
return;
}
utils::hook::invoke<void>(0xF9400_b, a1, a2, event_type, a4);
}
}
class component final : public component_interface
@ -38,6 +50,7 @@ namespace lui
{
// Patch game message overflow
utils::hook::call(0x266E6B_b, begin_game_message_event_stub);
utils::hook::call(0xEAC1C_b, cg_entity_event_stub);
scheduler::loop([]()
{
@ -46,6 +59,11 @@ namespace lui
event_count--;
}
}, scheduler::pipeline::lui, 50ms);
scheduler::loop([]()
{
obituary_count = 0;
}, scheduler::pipeline::lui, 0ms);
}
// Increase max extra LUI memory

View File

@ -67,7 +67,8 @@ namespace patches
void set_client_dvar_from_server_stub(void* clientNum, void* cgameGlob, const char* dvar, const char* value)
{
if (dvar == "cg_fov"s || dvar == "cg_fovMin"s)
const auto dvar_lowercase = utils::string::to_lower(dvar);
if (dvar_lowercase == "cg_fov"s || dvar_lowercase == "cg_fovMin"s)
{
return;
}
@ -121,7 +122,13 @@ namespace patches
command::params_sv params{};
const auto menu_id = atoi(params.get(1));
const auto client = &svs_clients[ent->s.entityNum];
const auto client = &svs_clients[ent->s.number];
// 13 => change class
if (menu_id == 13 && ent->client->team == game::mp::TEAM_SPECTATOR)
{
return;
}
// 32 => "end_game"
if (menu_id == 32 && client->header.remoteAddress.type != game::NA_LOOPBACK)
@ -318,6 +325,13 @@ namespace patches
utils::hook::jump(SELECT_VALUE(0x511050_b, 0x620040_b), _aligned_realloc);
}
// Uncheat protect gamepad-related dvars
dvars::override::register_float("gpad_button_deadzone", 0.13f, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("gpad_stick_deadzone_min", 0.2f, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("gpad_stick_deadzone_max", 0.01f, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("gpad_stick_pressed", 0.4f, 0, 1, game::DVAR_FLAG_SAVED);
dvars::override::register_float("gpad_stick_pressed_hysteresis", 0.1f, 0, 1, game::DVAR_FLAG_SAVED);
if (!game::environment::is_sp())
{
patch_mp();
@ -326,6 +340,9 @@ namespace patches
static void patch_mp()
{
// fix vid_restart crash
utils::hook::set<uint8_t>(0x139680_b, 0xC3);
utils::hook::jump(0x5BB9C0_b, &live_get_local_client_name);
// Disable data validation error popup
@ -409,6 +426,9 @@ namespace patches
// Fix gamepad related crash
cl_gamepad_scrolling_buttons_hook.create(0x133210_b, cl_gamepad_scrolling_buttons_stub);
// Prevent the game from modifying Windows microphone volume (since voice chat isn't used)
utils::hook::set<uint8_t>(0x5BEEA0_b, 0xC3); // Mixer_SetWaveInRecordLevels
}
};
}

View File

@ -49,12 +49,8 @@ namespace renderer
{
switch (dvars::r_fullbright->current.integer)
{
case 4:
return 3;
case 3:
return 13;
case 2:
return 25;
return 13;
default:
return game::TECHNIQUE_UNLIT;
}
@ -148,7 +144,7 @@ namespace renderer
return;
}
dvars::r_fullbright = dvars::register_int("r_fullbright", 0, 0, 4, game::DVAR_FLAG_SAVED, "Toggles rendering without lighting");
dvars::r_fullbright = dvars::register_int("r_fullbright", 0, 0, 2, game::DVAR_FLAG_SAVED, "Toggles rendering without lighting");
r_init_draw_method_hook.create(SELECT_VALUE(0x5467E0_b, 0x669580_b), &r_init_draw_method_stub);
r_update_front_end_dvar_options_hook.create(SELECT_VALUE(0x583560_b, 0x6A78C0_b), &r_update_front_end_dvar_options_stub);

View File

@ -44,6 +44,8 @@ namespace scripting
game::dvar_t* g_dump_scripts;
std::vector<std::function<void()>> shutdown_callbacks;
void vm_notify_stub(const unsigned int notify_list_owner_id, const game::scr_string_t string_value,
game::VariableValue* top)
{
@ -94,6 +96,11 @@ namespace scripting
script_function_table.clear();
}
for (const auto& callback : shutdown_callbacks)
{
callback();
}
scripting::notify(*game::levelEntityId, "shutdownGame_called", {1});
lua::engine::stop();
return g_shutdown_game_hook.invoke<void>(free_scripts);
@ -162,6 +169,11 @@ namespace scripting
}
}
void on_shutdown(const std::function<void()>& callback)
{
shutdown_callbacks.push_back(callback);
}
class component final : public component_interface
{
public:

View File

@ -8,4 +8,6 @@ namespace scripting
extern std::unordered_map<int, std::unordered_map<std::string, int>> fields_table;
extern std::unordered_map<std::string, std::unordered_map<std::string, const char*>> script_function_table;
extern utils::concurrency::container<shared_table_t> shared_table;
void on_shutdown(const std::function<void()>& callback);
}

View File

@ -143,8 +143,9 @@ namespace server_list
if (column == 2)
{
return utils::string::va("%d/%d [%d]", servers[i].clients, servers[index].max_clients,
servers[i].bots);
const auto client_count = servers[i].clients - servers[i].bots;
return utils::string::va("%d/%d [%d]", client_count, servers[i].max_clients,
servers[i].clients);
}
if (column == 3)
@ -169,12 +170,20 @@ namespace server_list
{
std::stable_sort(servers.begin(), servers.end(), [](const server_info& a, const server_info& b)
{
if (a.clients == b.clients)
const auto a_players = a.clients - a.bots;
const auto b_players = b.clients - b.bots;
if (a_players == b_players)
{
return a.ping < b.ping;
if (a.clients == b.clients)
{
return a.ping < b.ping;
}
return a.clients > b.clients;
}
return a.clients > b.clients;
return a_players > b_players;
});
}

View File

@ -5040,6 +5040,11 @@ namespace dvars
"Turn on debug lines for melee traces",
generate_hash("melee_debug")
},
{
"mis_cheat",
"Set when level unlock cheat is performed",
generate_hash("mis_cheat")
},
{
"migration_dvarErrors",
"Whether to check for illegal script dvar changes.",

View File

@ -76,6 +76,8 @@ namespace scripting
{
return { this->id_, this->get_value_id(key.as<S>()) };
}
throw std::runtime_error("Invalid key type");
}
private:

View File

@ -1290,9 +1290,9 @@ namespace scripting
{"_meth_8215", 0x8215}, // SP 0x2CB4A0 MP 0x44CB00
{"_meth_8216", 0x8216}, // SP 0x2CB5D0 MP 0x44CCA0
{"_meth_8217", 0x8217}, // SP 0x2CB720 MP 0x44CCF0
{"circle", 0x8218}, // SP 0x2D0720 MP 0x439750
{"circle", 0x8218}, // SP 0x2D0720 MP 0x439750 - iw6 name is WorldPointInReticle_Circle
{"rect", 0x8219}, // SP 0x2D12D0 MP 0x439F20
{"_meth_821a", 0x821A}, // SP 0x2D1850 MP 0x43A680
{"getpointinbounds", 0x821A}, // SP 0x2D1850 MP 0x43A680
{"transfermarkstonewscriptmodel", 0x821B}, // SP 0x2D1FC0 MP 0x43B020
{"setwatersheeting", 0x821C}, // SP 0x2C47C0 MP 0x445400
{"_meth_821d", 0x821D}, // SP 0x2C02D0 MP 0x000000
@ -1303,8 +1303,8 @@ namespace scripting
{"_meth_8222", 0x8222}, // SP 0x2CDCF0 MP 0x437140
{"_meth_8223", 0x8223}, // SP 0x2CD9F0 MP 0x436F30
{"_meth_8224", 0x8224}, // SP 0x2CE3A0 MP 0x437750
{"_meth_8225", 0x8225}, // SP 0x2D1E80 MP 0x438700
{"_meth_8226", 0x8226}, // SP 0x2C2210 MP 0x4432D0
{"isitemunlocked", 0x8225}, // SP 0x2D1E80 MP 0x438700
{"getrankedplayerdata", 0x8226}, // SP 0x2C2210 MP 0x4432D0
{"vehicleturretcontroloff", 0x8227}, // SP 0x4E7230 MP 0x60E170
{"isturretready", 0x8228}, // SP 0x4E7310 MP 0x609890
{"_meth_8229", 0x8229}, // SP 0x4E7560 MP 0x609B20
@ -1332,7 +1332,7 @@ namespace scripting
{"_meth_8240", 0x8240}, // SP 0x2C4AD0 MP 0x000000
{"_meth_8241", 0x8241}, // SP 0x274CF0 MP 0x000000
{"_meth_8242", 0x8242}, // SP 0x2750F0 MP 0x000000
{"_meth_8243", 0x8243}, // SP 0x2C2550 MP 0x443C20
{"setcommonplayerdata", 0x8243}, // SP 0x2C2550 MP 0x443C20 - Could be also setrankedplayerdata
{"_meth_8244", 0x8244}, // SP 0x2C2A00 MP 0x4442D0
{"_meth_8245", 0x8245}, // SP 0x2C3190 MP 0x444850
{"trackerupdate", 0x8246}, // SP 0x2C51C0 MP 0x447650
@ -1455,7 +1455,7 @@ namespace scripting
{"notsolid", 0x82BB}, // SP 0x2E3230 MP 0x452FA0
{"setcandamage", 0x82BC}, // SP 0x2E2300 MP 0x453ED0
{"setcanradiusdamage", 0x82BD}, // SP 0x2E2360 MP 0x453F30
{"physicslaunchclient", 0x82BE}, // SP 0x2E23E0 MP 0x453FB0
{"physicslaunch", 0x82BE}, // SP 0x2E23E0 MP 0x453FB0
{"setcardicon", 0x82BF}, // SP 0x000000 MP 0x411100
{"setcardnameplate", 0x82C0}, // SP 0x000000 MP 0x411110
{"setcarddisplayslot", 0x82C1}, // SP 0x000000 MP 0x411120
@ -1492,11 +1492,11 @@ namespace scripting
{"player_recoilscaleoff", 0x82E0}, // SP 0x28D920 MP 0x4092D0
{"weaponlockstart", 0x82E1}, // SP 0x28DA00 MP 0x409530
{"weaponlockfinalize", 0x82E2}, // SP 0x28DCC0 MP 0x4097A0
{"disableautoreload", 0x82E3}, // SP 0x28DFC0 MP 0x409C50
{"weaponlockfree", 0x82E3}, // SP 0x28DFC0 MP 0x409C50 - was disableautoreload
{"setentertime", 0x82E4}, // SP 0x28E060 MP 0x409E60
{"usinggamepad", 0x82E5}, // SP 0x000000 MP 0x411680
{"_meth_82e6", 0x82E6}, // SP 0x000000 MP 0x411700
{"_meth_82e7", 0x82E7}, // SP 0x000000 MP 0x411710
{"issighted", 0x82E7}, // SP 0x000000 MP 0x411710
{"_meth_82e8", 0x82E8}, // SP 0x000000 MP 0x411730
{"_meth_82e9", 0x82E9}, // SP 0x000000 MP 0x411720
{"_meth_82ea", 0x82EA}, // SP 0x2905B0 MP 0x40BAD0
@ -2012,7 +2012,7 @@ namespace scripting
{"_meth_84fa", 0x84FA}, // SP 0x2C63F0 MP 0x44D230
{"_meth_84fb", 0x84FB}, // SP 0x2BF9D0 MP 0x447EB0
{"_meth_84fc", 0x84FC}, // SP 0x2C5650 MP 0x447220
{"_meth_84fd", 0x84FD}, // SP 0x000000 MP 0x4109F0
{"_meth_84fd", 0x84FD}, // SP 0x000000 MP 0x4109F0 - Could be setMLGCamVisibility or SetMLGSpectator
{"_meth_84fe", 0x84FE}, // SP 0x000000 MP 0x0B90C0
{"_meth_84ff", 0x84FF}, // SP 0x000000 MP 0x0B9280
{"_meth_8500", 0x8500}, // SP 0x2C17E0 MP 0x443F90
@ -2153,6 +2153,7 @@ namespace scripting
std::unordered_map<std::string, unsigned> token_map =
{
{"", 0x000},
{"pl#", 0x001},
{"-", 0x002},
{"radius`", 0x003},
@ -2187,7 +2188,7 @@ namespace scripting
{"ai_sight_line_group", 0x020},
{"aim_highest_bone", 0x021},
{"aim_vis_bone", 0x022},
{"aiSpread", 0x023},
{"aispread", 0x023},
{"aisquadmembers", 0x024},
{"alert", 0x025},
{"alertlevel", 0x026},
@ -2669,10 +2670,10 @@ namespace scripting
{"infinite_energy", 0x202},
{"info_notnull", 0x203},
{"info_player_start", 0x204},
{"init", 0x205},
{"not_init", 0x205},
{"initstructs", 0x206},
{"insolid", 0x207},
{"intermission", 0x208},
{"init", 0x208},
{"interval", 0x209},
{"inuse", 0x20A},
{"invalid_parent", 0x20B},
@ -2764,10 +2765,10 @@ namespace scripting
{"luinotifyserver", 0x261},
{"mag_eject", 0x262},
{"mag_eject_left", 0x263},
{"main", 0x264},
{"not_main", 0x264},
{"manual", 0x265},
{"manual_ai", 0x266},
{"manual_change", 0x267},
{"main", 0x267},
{"map", 0x268},
{"matchid", 0x269},
{"matchmakingsettingsversion", 0x26A},
@ -2887,13 +2888,13 @@ namespace scripting
{"operationsdeadline", 0x2DC},
{"oriented", 0x2DD},
{"orientto_complete", 0x2DE},
{"origin", 0x2DF},
{"_not_origin", 0x2DF},
{"other", 0x2E0},
{"over", 0x2E1},
{"_not_owner", 0x2E2}, // was "owner"
{"origin", 0x2E2},
{"pacifist", 0x2E3},
{"pacifistwait", 0x2E4},
{"owner", 0x2E5}, // was "pain"
{"owner", 0x2E5},
{"pantssize", 0x2E6},
{"parentindex", 0x2E7},
{"parentname", 0x2E8},
@ -2925,10 +2926,10 @@ namespace scripting
{"perkrestricted", 0x302},
{"perks", 0x303},
{"perkslots", 0x304},
{"_not_pers", 0x305}, // was "pers"
{"_not_pers", 0x305},
{"persistentperksunlocked", 0x306},
{"persistentweaponsunlocked", 0x307},
{"pers", 0x308}, // was "phone_off"
{"pers", 0x308},
{"phone_on", 0x309},
{"physics_finished", 0x30A},
{"physics_impact", 0x30B},
@ -2952,10 +2953,10 @@ namespace scripting
{"playerip", 0x31D},
{"playername", 0x31E},
{"playerpositions", 0x31F},
{"players", 0x320},
{"playerSpread", 0x321},
{"_not_players", 0x320},
{"playerspread", 0x321},
{"playerxuidhigh", 0x322},
{"playerxuidlow", 0x323},
{"players", 0x323},
{"playing", 0x324},
{"points", 0x325},
{"position", 0x326},
@ -2981,10 +2982,10 @@ namespace scripting
{"primaryfurniturekit", 0x33A},
{"primaryoffhand", 0x33B},
{"primaryreticle", 0x33C},
{"primaryweapon", 0x33D},
{"_not_primaryweapon", 0x33D},
{"privatematchactivesquadmember", 0x33E},
{"privatematchcustomclasses", 0x33F},
{"privatematchsquadmembers", 0x340},
{"primaryweapon", 0x340},
{"projectile_impact", 0x341},
{"projectile_impact_player", 0x342},
{"prone", 0x343},
@ -3058,10 +3059,10 @@ namespace scripting
{"scope_cap", 0x387},
{"scope_center", 0x388},
{"scope_top", 0x389},
{"score", 0x38A},
{"_not_score", 0x38A},
{"script", 0x38B},
{"script_brushmodel", 0x38C},
{"script_clut", 0x38D},
{"score", 0x38D},
{"script_context", 0x38E},
{"script_delay", 0x38F},
{"script_goal_changed", 0x390},
@ -3096,10 +3097,10 @@ namespace scripting
{"secondaryfurniturekit", 0x3AD},
{"secondaryoffhand", 0x3AE},
{"secondaryreticle", 0x3AF},
{"secondaryweapon", 0x3B0},
{"_not_secondaryweapon", 0x3B0},
{"sentry", 0x3B1},
{"sentry_manual", 0x3B2},
{"sentry_offline", 0x3B3},
{"secondaryweapon", 0x3B3},
{"servertimecount", 0x3B4},
{"servertimeexceedcount", 0x3B5},
{"servertimemax", 0x3B6},
@ -3205,7 +3206,7 @@ namespace scripting
{"suppressionduration", 0x41A},
{"suppressionmeter", 0x41B},
{"suppressionstarttime", 0x41C},
{"suppressionTime", 0x41D},
{"suppressiontime", 0x41D},
{"suppressionwait", 0x41E},
{"surfacetype", 0x41F},
{"surprisedbymedistsq", 0x420},
@ -3312,10 +3313,10 @@ namespace scripting
{"tag_sight_off", 0x485},
{"tag_sight_on", 0x486},
{"tag_stow_back_mid_attach", 0x487},
{"_not_tag_stowed_back", 0x488}, // was "tag_stowed_back"
{"_not_tag_stowed_back", 0x488},
{"tag_stowed_hip_rear", 0x489},
{"tag_sync", 0x48A},
{"tag_stowed_back", 0x48B}, // was "tag_tip"
{"tag_stowed_back", 0x48B},
{"tag_turret", 0x48C},
{"tag_turret_base", 0x48D},
{"tag_turret_pitch", 0x48E},
@ -3340,10 +3341,10 @@ namespace scripting
{"target", 0x4A1},
{"target_script_trigger", 0x4A2},
{"targetname", 0x4A3},
{"_not_team", 0x4A4}, // was "team"
{"_not_team", 0x4A4},
{"team3", 0x4A5},
{"teambalanced", 0x4A6},
{"team", 0x4A7}, // was "teammode_axisallies"
{"team", 0x4A7},
{"teammode_ffa", 0x4A8},
{"teammovewaittime", 0x4A9},
{"their_score", 0x4AA},
@ -3370,10 +3371,10 @@ namespace scripting
{"traversecost", 0x4BF},
{"traversesoonnotifydist", 0x4C0},
{"trend", 0x4C1},
{"_not_trigger", 0x4C2}, // was "trigger"
{"_not_trigger", 0x4C2},
{"trigger_damage", 0x4C3},
{"trigger_use", 0x4C4},
{"trigger", 0x4C5}, // was "trigger_use_touch"
{"trigger", 0x4C5},
{"truck_cam", 0x4C6},
{"turnrate", 0x4C7},
{"turret_deactivate", 0x4C8},
@ -3492,97 +3493,423 @@ namespace scripting
{"codescripts/struct", 0x53E},
{"codescripts/message", 0x53F},
{"maps/mp/gametypes/_callbacksetup", 0x540},
{"_createfx", 0x575},
{"_effect", 0x58F},
{"_hasperk", 0x5CB},
{"_objective_delete", 0x603},
{"_unsetperk", 0x67B},
{"ac130", 0x6CE},
{"ac130player", 0x6D1},
{"scorepercentagecutoff", 0x782},
{"addspawnpoints", 0x82F},
{"addstartspawnpoints", 0x831},
{"addtocharactersarray", 0x848},
{"addtoteam", 0x851},
{"agent_funcs", 0x897},
{"agent_gameparticipant", 0x898},
{"agent_teamparticipant", 0x89F},
{"agent_type", 0x8A0},
{"agentarray", 0x8A1},
{"airstrikeinprogress", 0x99C},
{"allowuse", 0xAB2},
{"allowvote", 0xAB4},
{"anim_prop_models", 0xBEC},
{"applyloadout", 0xCAE},
{"artillerydangercenter", 0xD33},
{"atbrinkofdeath", 0xD82},
{"attackers", 0xE34},
{"primaryprogressbartexty", 0xF88},
{"audio", 0x10F0},
{"avoidkillstreakonspawntimer", 0x11FC},
{"basefontscale", 0x1309},
{"bcsounds", 0x1358},
{"beingrevived", 0x13AB},
{"blockweapondrops", 0x14B5},
{"bombsquadicons", 0x154C},
{"bombsquadids", 0x154D},
{"bot_funcs", 0x161B},
{"breathingstoptime", 0x17E6},
{"brinkofdeathkillstreak", 0x1818},
{"greatestuniqueplayerkills", 0x18B1},
{"c4explodethisframe", 0x1974},
{"callback_playerdamage", 0x19F5},
{"callbackplayerdamage", 0x19FE},
{"candocombat", 0x1AC6},
{"canperformclienttraces", 0x1AD6},
{"carryflag", 0x1BB4},
{"challengeinfo", 0x1C62},
{"changingweapon", 0x1C87},
{"characters", 0x1C8E},
{"checkdynamicspawns", 0x1CFA},
{"chopper", 0x1D48},
{"class", 0x1E2E},
{"classtweaks", 0x1E40},
{"claymoredetectiondot", 0x1E44},
{"claymoredetectiongraceperiod", 0x1E45},
{"claymoredetectionmindist", 0x1E46},
{"claymoredetonateradius", 0x1E47},
{"clearonvictimdisconnect", 0x1EF9},
{"clientid", 0x1F0F},
{"clientmatchdataid", 0x1F10},
{"clienttweakables", 0x1F12},
{"combathigh", 0x20AB},
{"concussionendtime", 0x20DF},
{"conf_fx", 0x20E9},
{"connecttime", 0x214F},
{"console", 0x2153},
{"createuseobject", 0x244C},
{"curorigin", 0x24C8},
{"curprogress", 0x24C9},
{"currentweaponatspawn", 0x252E},
{"damagedplayers", 0x259A},
{"defaultvalue", 0x27A1},
{"delayminetime", 0x27E9},
{"deleteobjpoint", 0x2859},
{"detectedexploit", 0x2991},
{"detectexplosives", 0x2992},
{"detectid", 0x2994},
{"disabled", 0x2AFD},
{"disabledoffhandweapons", 0x2B05},
{"disabledusability", 0x2B0A},
{"disabledweapon", 0x2B0B},
{"disabledweaponswitch", 0x2B0C},
{"disablespawning", 0x2B28},
{"dogtags", 0x2CDF},
{"doingfinalkillcamfx", 0x2CE6},
{"doingsplash", 0x2CE9},
{"dont_delete_grenades_on_next_spawn", 0x2D1E},
{"drawfriend", 0x2DD7},
{"droppeddeathweapon", 0x2F74},
{"empendtime", 0x3082},
{"entity_number", 0x3314},
{"entityheadicons", 0x331A},
{"finalkill", 0x373E},
{"findboxcenter", 0x3779},
{"flashduration", 0x38AE},
{"flashendtime", 0x38B1},
{"flashrumbleduration", 0x38C4},
{"forfeitinprogress", 0x39DF},
{"freeplayers", 0x3A2A},
{"friendlydamage", 0x3A97},
{"fx", 0x3B23},
{"gameended", 0x3BDA},
{"gameendtime", 0x3BDC},
{"gamemodemodifyplayerdamage", 0x3BF6},
{"gametweaks", 0x3C02},
{"getnextobjid", 0x4041},
{"getotherteam", 0x4067},
{"getspawnpoint", 0x40D2},
{"getspawnpoint_freeforall", 0x40D5},
{"getteamspawnpoints", 0x411F},
{"getweaponclass", 0x4167},
{"giveloadout", 0x41E0},
{"gotpullbacknotify", 0x428B},
{"guid", 0x4450},
{"gunner", 0x4473},
{"hardcoremode", 0x46CA},
{"hardpointtweaks", 0x46CE},
{"hasdied", 0x4726},
{"headmodel", 0x477D},
{"healthoverlaycutoff", 0x478D},
{"healthregendisabled", 0x478E},
{"healthregenerationstreak", 0x4791},
{"hits", 0x4926},
{"hitsthismag", 0x4929},
{"hostname", 0x4A3E},
{"hud", 0x4AB3},
{"hudtweaks", 0x4AFF},
{"idflags", 0x4B56},
{"idflags_no_knockback", 0x4B58},
{"idflags_shield_explosive_impact", 0x4B5E},
{"idflags_shield_explosive_impact_huge", 0x4B5F},
{"idflags_shield_explosive_splash ", 0x4B60},
{"idflags_stun", 0x4B61},
{"idflagstime", 0x4B62},
{"inc", 0x4C13},
{"inframes", 0x4C67},
{"ingraceperiod", 0x4C6D},
{"init_animatedmodels", 0x4C77},
{"initedentityheadicons", 0x4DB6},
{"initializematchrules", 0x4DE0},
{"initializetagpathvariables", 0x4DE3},
{"initspawns", 0x4E26},
{"inlaststand", 0x4E3C},
{"inplayersmokescreen", 0x4E42},
{"isactive", 0x50A6},
{"isagent", 0x50AB},
{"iscooked", 0x50E8},
{"ishorde", 0x511D},
{"iskillstreakweapon", 0x513D},
{"isrocketcorpse", 0x5193},
{"issniper", 0x51AB},
{"istactical", 0x51C7},
{"isteamspeaking", 0x51D0},
{"joining_team", 0x528C},
{"kill_streak", 0x533C},
{"killcam", 0x534B},
{"killcamlength", 0x534F},
{"killedplayers", 0x5363},
{"killstreakrounddelay", 0x53A2},
{"killstreakspawnshield", 0x53A8},
{"largeprojectiledamage", 0x54C0},
{"lastclass", 0x5589},
{"lastconcussedtime", 0x558B},
{"lastkilldogtime", 0x558D},
{"lastdamagewasfromenemy", 0x5591},
{"lastdeathicon", 0x5592},
{"lastdroppableweapon", 0x5598},
{"lastflashedtime", 0x55A1},
{"lastgrenadesuicidetime", 0x55A5},
{"lasthittime", 0x55AB},
{"lastkilledby", 0x55B1},
{"lastkilltime", 0x55B6},
{"lastprimaryweaponswaptime", 0x55CD},
{"lastshotfiredtime", 0x55D9},
{"laststatustime", 0x55FC},
{"lastwave", 0x560E},
{"leaving_team", 0x566D},
{"lowertextfontsize", 0x58A8},
{"lowertexty", 0x58A9},
{"lowertextyalign", 0x58AA},
{"plantedlethalequip", 0x5979},
{"mapcenter", 0x5986},
{"mapsize", 0x5990},
{"matchbonus", 0x59DF},
{"matchrules_damagemultiplier", 0x59E6},
{"matchrules_vampirism", 0x59EB},
{"maxclients", 0x5A29},
{"maxevents", 0x5A37},
{"maxfontscale", 0x5A39},
{"maxkillstreaks", 0x5A3C},
{"maxlives", 0x5A40},
{"maxnumawardsperplayer", 0x5A41},
{"maxlogclients", 0x5A42},
{"maxnumchallengesperplayer", 0x5A45},
{"maxperplayerexplosives", 0x5A4A},
{"minedamagehalfheight", 0x5C51},
{"minedamagemax", 0x5C53},
{"minedamagemin", 0x5C54},
{"minedamageradius", 0x5C56},
{"minedetectiongraceperiod", 0x5C58},
{"minedetectionheight", 0x5C59},
{"minedetectionradius", 0x5C5A},
{"mineselfdestructtime", 0x5C5F},
{"missioncallbacks", 0x5CDC},
{"modifyplayerdamage", 0x5D51},
{"movespeedscaler", 0x5F7B},
{"mp_createfx", 0x5FAC},
{"multiteambased", 0x5FEC},
{"objectivepointsmod", 0x6301},
{"objectivescaler", 0x6303},
{"objid", 0x6304},
{"teamobjids", 0x6305},
{"oldheadicon", 0x63D4},
{"oldheadiconteam", 0x63D5},
{"omaclasschanged", 0x6436},
{"onforfeit", 0x64AF},
{"onlinegame", 0x64B8},
{"onnormaldeath", 0x64BF},
{"onplayerconnectaudioinit", 0x64C9},
{"onplayerscore", 0x64D5},
{"onstartgametype", 0x64EC},
{"onuse", 0x64F8},
{"outframes", 0x65C4},
{"participants", 0x669D},
{"tookweaponfrom", 0x680B},
{"placement", 0x6861},
{"planemodel", 0x687C},
{"playedstartingmusic", 0x6A41},
{"player_speed", 0x6C19},
{"playerhealth_regularregendelay", 0x6CC3},
{"playertweaks", 0x6D74},
{"bonusupdatetotal", 0x6E8A},
{"primaryprogressbarfontsize", 0x6F85},
{"primaryprogressbarheight", 0x6F86},
{"primaryprogressbartextx", 0x6F87},
{"primaryprogressbarwidth", 0x6F89},
{"primaryprogressbarx", 0x6F8A},
{"primaryprogressbary", 0x6F8B},
{"qafinished", 0x7073},
{"quickmessagetoall", 0x70A2},
{"rankedmatch", 0x7137},
{"ranktable", 0x713A},
{"recentkillcount", 0x7260},
{"recoilscale", 0x7268},
{"regenspeed", 0x72D4},
{"registerhalftimedvar", 0x72EF},
{"registernumlivesdvar", 0x72F4},
{"registerroundlimitdvar", 0x72F6},
{"registerroundswitchdvar", 0x72F7},
{"registerscorelimitdvar", 0x72F8},
{"registertimelimitdvar", 0x72F9},
{"registerwinlimitdvar", 0x72FE},
{"reinitializematchrulesonmigration", 0x7307},
{"removefromcharactersarray", 0x73A7},
{"requiredmapaspectratio", 0x740C},
{"reverb_settings", 0x74F1},
{"riotshieldxpbullets", 0x7556},
{"rules", 0x7674},
{"scavenger_altmode", 0x785B},
{"scavenger_secondary", 0x785C},
{"script_accumulate", 0x792B},
{"script_destructable_area", 0x799B},
{"script_fxcommand", 0x79F0},
{"script_fxid", 0x79F1},
{"script_gametype_atdm", 0x79F5},
{"script_gametype_ctf", 0x79F6},
{"script_gametype_dm", 0x79F7},
{"script_gametype_koth", 0x79F8},
{"script_gametype_sd", 0x79FA},
{"script_gametype_tdm", 0x79FB},
{"script_threshold", 0x7AF4},
{"setclass", 0x7F3B},
{"setcommonrulesfrommatchrulesdata", 0x7F3F},
{"setobjectivehinttext", 0x7FC3},
{"setobjectivescoretext", 0x7FC4},
{"setobjectivetext", 0x7FC5},
{"setupcallbacks", 0x8301},
{"setupminimap", 0x8324},
{"setusetime", 0x834C},
{"shieldbullethits", 0x84C5},
{"shielddamage", 0x84C6},
{"showingfinalkillcam", 0x8516},
{"showtoteam", 0x8535},
{"softlanding", 0x885F},
{"softlandingtriggers", 0x8860},
{"spamdelay", 0x88B9},
{"spawndogtags", 0x899E},
{"spawnmaxs", 0x89F3},
{"spawnmins", 0x89F6},
{"spawnpoints", 0x8A01},
{"speakers", 0x8A19},
{"spectateoverride", 0x8A4A},
{"splashqueue", 0x8A6B},
{"splitscreen", 0x8A7C},
{"stingerlockstarttime", 0x8E48},
{"stingerlostsightlinetime", 0x8E49},
{"stingerstage", 0x8E59},
{"stingertarget", 0x8E5A},
{"stingeruseentered", 0x8E5C},
{"stuckenemyentity", 0x8F6C},
{"stunscaler", 0x8F77},
{"suicides", 0x8FAF},
{"switching_teams", 0x907E},
{"tag_stowed_hip", 0x90D3},
{"tagteamupdater", 0x910A},
{"teambalance", 0x91E9},
{"teambased", 0x91EB},
{"teamkillsthisround", 0x91F3},
{"teamnamelist", 0x91F7},
{"teamprogressbarfontsize", 0x91FB},
{"teamprogressbarheight", 0x91FC},
{"teamprogressbartexty", 0x91FD},
{"teamprogressbarwidth", 0x91FE},
{"teamprogressbary", 0x91FF},
{"teamspawnpoints", 0x9201},
{"teamtweaks", 0x9205},
{"throwinggrenade", 0x933E},
{"timeplayed", 0x9372},
{"trackingweaponname", 0x94FB},
{"trigunderwater", 0x9822},
{"tweakablesinitialized", 0x99E0},
{"updateddmscores", 0x9B0F},
{"userate", 0x9C10},
{"usestartspawns", 0x9C14},
{"usingremote", 0x9C34},
{"v", 0x9C42},
{"voice_count", 0x9D33},
{"visuals", 0x9E9C},
{"waitingtodeactivate", 0xA04B},
{"wasaliveatmatchstart", 0xA1BE},
{"waschained", 0xA1C0},
{"wasdamaged", 0xA1C3},
{"wasdamagedfrombulletpenetration", 0xA1C5},
{"wasti", 0xA1D2},
{"waswinning", 0xA1D4},
{"waterdeletez", 0xA297},
{"wavedelay", 0xA2AB},
{"weaponattachments", 0xA2D3},
{"weaponlist", 0xA2DD},
{"weapontweaks", 0xA2F2},
{"whizby_settings", 0xA315},
{"scoreinfo", 0xA3A5},
{"xpupdatetotal", 0xA3AB},
// additional findings from gametype/map scripts - mikey (6/26/2022)
{"common_scripts/_fx", 0xA4FB},
{"codescripts/character", 0xA4EF},
{"common_scripts/_artcommon", 0xA4F0},
{"common_scripts/_bcs_location_trigs", 0xA4F1},
{"common_scripts/_createfx", 0xA4F2},
{"common_scripts/_createfxmenu", 0xA4F3},
{"common_scripts/_destructible", 0xA4F4},
{"common_scripts/_dynamic_world", 0xA4F5},
{"common_scripts/_elevator", 0xA4F6},
{"common_scripts/_exploder", 0xA4F7},
{"common_scripts/_fx", 0xA4F8},
{"common_scripts/_pipes", 0xA4F9},
{"common_scripts/utility", 0xA4FA},
{"QuickMessageToAll", 0x70a2},
{"SetupCallbacks", 0x8301},
{"_effect", 0x58f},
{"_objective_delete", 0x603},
{"addSpawnPoints", 0x82f},
{"addStartSpawnPoints", 0x831},
{"addToCharactersArray", 0x848},
{"allowUse", 0xab2},
{"applyLoadout", 0xcae}, // has applyLoadout notify like IW6's giveLoadout does at the end + similar logic
{"characters", 0x1c8e},
{"checkDynamicSpawns", 0x1cfa},
{"clearOnVictimDisconnect", 0x1ef9},
{"conf_fx", 0x20e9},
{"console", 0x2153},
{"createUseObject", 0x244c},
{"curOrigin", 0x24c8},
{"deleteObjPoint", 0x2859},
{"dogtags", 0x2cdf},
{"finalKill", 0x373e},
{"findBoxCenter", 0x3779},
{"forfeitInProgress", 0x39df},
{"gamemodeModifyPlayerDamage", 0x3bf6},
{"getNextObjID", 0x4041},
{"getOtherTeam", 0x4067},
{"getSpawnPoint", 0x40d2},
{"getSpawnpoint_FreeForAll", 0x40d5},
{"getTeamSpawnPoints", 0x411f},
{"giveLoadout", 0x41e0}, // this may not even be giveLoadout but it's a wrapper for it and it does the same logic so
{"guid", 0x4450},
{"inGracePeriod", 0x4c6d},
{"initSpawns", 0x4e26},
{"initializeMatchRules", 0x4de0},
{"initializeTagPathVariables", 0x4de3},
{"mapCenter", 0x5986},
{"maps/mp/_compass", 0xa731},
{"maps/mp/_load", 0xa74c},
{"maps/mp/_utility", 0xa764},
{"maps/mp/_animatedmodels", 0xA72B},
{"maps/mp/_areas", 0xA72C},
{"maps/mp/_art", 0xA72D},
{"maps/mp/_audio", 0xA72E},
{"maps/mp/_awards", 0xA72F},
{"maps/mp/_compass", 0xA731},
{"maps/mp/_createfx", 0xA732},
{"maps/mp/_crib", 0xA733},
{"maps/mp/_destructables", 0xA734},
{"maps/mp/_entityheadicons", 0xA737},
{"maps/mp/_events", 0xA738},
{"maps/mp/_flashgranades", 0xA747},
{"maps/mp/_fx", 0xA748},
{"maps/mp/_global_fx", 0xA749},
{"maps/mp/_global_fx_code", 0xA74A},
{"maps/mp/_load", 0xA74C},
{"maps/mp/_matchdata", 0xA74E},
{"maps/mp/_sun", 0xA752},
{"maps/mp/_scope", 0xA755},
{"maps/mp/_scoreboard", 0xA758},
{"maps/mp/_shutter", 0xA759},
{"maps/mp/_stinger", 0xA75B},
{"maps/mp/_utility", 0xA764},
{"maps/mp/_zipline", 0xA76A},
{"maps/mp/gametypes/_battlechatter_mp", 0xA78A},
{"maps/mp/gametypes/_class", 0xA78B},
{"maps/mp/gametypes/_damage", 0xA78D},
{"maps/mp/gametypes/_damagefeedback", 0xA78E},
{"maps/mp/gametypes/_deathicons", 0xA78F},
{"maps/mp/gametypes/_dev", 0xA790},
{"maps/mp/gametypes/_equipment", 0xA791},
{"maps/mp/gametypes/_friendicons", 0xA792},
{"maps/mp/gametypes/_gamelogic", 0xA793},
{"maps/mp/gametypes/_gameobjects", 0xA794},
{"maps/mp/gametypes/_gamescores", 0xA795},
{"maps/mp/gametypes/_globalentities", 0xA796},
{"maps/mp/gametypes/_globallogic", 0xA797},
{"maps/mp/gametypes/_menus", 0xa7a9},
{"maps/mp/gametypes/_objpoints", 0xa7ac},
{"maps/mp/gametypes/_spawnlogic", 0xa7b9},
{"maps/mp/gametypes/_spawnscoring", 0xa7ba},
{"matchRules_damageMultiplier", 0x59e6},
{"matchRules_vampirism", 0x59eb},
{"modifyPlayerDamage", 0x5d51},
{"objId", 0x6304},
{"onForfeit", 0x64af},
{"onNormalDeath", 0x64bf},
{"onPlayerScore", 0x64d5},
{"onStartGameType", 0x64ec},
{"onUse", 0x64f8},
{"participants", 0x669d},
{"reInitializeMatchRulesOnMigration", 0x7307},
{"registerHalfTimeDvar", 0x72ef},
{"registerNumLivesDvar", 0x72f4},
{"registerRoundLimitDvar", 0x72f6},
{"registerRoundSwitchDvar", 0x72f7},
{"registerScoreLimitDvar", 0x72f8},
{"registerTimeLimitDvar", 0x72f9},
{"registerWinLimitDvar", 0x72fe},
{"removeFromCharactersArray", 0x73a7},
{"setClass", 0x7f3b},
{"setCommonRulesFromMatchRulesData", 0x7f3f},
{"setObjectiveHintText", 0x7fc3},
{"setObjectiveScoreText", 0x7fc4},
{"setObjectiveText", 0x7fc5},
{"setUseTime", 0x834c},
{"setupMiniMap", 0x8324},
{"showToTeam", 0x8535},
{"spawnDogTags", 0x899e},
{"spawnMaxs", 0x89f3},
{"spawnMins", 0x89f6},
{"spawnPoints", 0x8a01},
{"splitscreen", 0x8a7c},
{"tag_stowed_hip", 0x90d3},
{"tagTeamUpdater", 0x910a},
{"teamBased", 0x91eb},
{"teamNameList", 0x91f7},
{"teamObjIds", 0x6305},
{"teamSpawnPoints", 0x9201},
{"v", 0x9c42},
{"visuals", 0x9e9c},
{"multiTeamBased", 0x5fec},
{"maps/mp/gametypes/_hardpoints", 0xA798},
{"maps/mp/gametypes/_healthoverlay", 0xA799},
{"maps/mp/gametypes/_hostmigration", 0xA7A4},
{"maps/mp/gametypes/_hud", 0xA7A5},
{"maps/mp/gametypes/_hud_message", 0xA7A6},
{"maps/mp/gametypes/_hud_util", 0xA7A7},
{"maps/mp/gametypes/_killcam", 0xA7A8},
{"maps/mp/gametypes/_menus", 0xA7A9},
{"maos/mp/gametypes/_missions", 0xA7AA},
{"maps/mp/gametypes/_music_and_dialog", 0xA7AB},
{"maps/mp/gametypes/_objpoints", 0xA7AC},
{"maps/mp/gametypes/_presistence", 0xA7AE},
{"maps/mp/gametypes/_playerlogic", 0xA7B1},
{"maps/mp/gametypes/_portable_radar", 0xA7B2},
{"maps/mp/gametypes/_quickmessages", 0xA7B3},
{"maps/mp/gametypes/_rank", 0xA7B4},
{"maps/mp/gametypes/_portableaoegenerator", 0xA7B5},
{"maps/mp/gametypes/_serversettings", 0xA7B6},
{"maps/mp/gametypes/_shellshock", 0xA7B7},
{"maps/mp/gametypes/_spawnlogic", 0xA7B9},
{"maps/mp/gametypes/_spawnscoring", 0xA7BA},
{"maps/mp/gametypes/_spectating", 0xA7BB},
{"maps/mp/gametypes/_teams", 0xA7BC},
{"maps/mp/gametypes/_tweakables", 0xA7BD},
{"maps/mp/gametypes/_weapons", 0xA7BE},
{"maps/mp/gametypes/_perkfunctions", 0xA7E5},
{"maps/mp/gametypes/_perks", 0xA7E6},
};
}

View File

@ -10,14 +10,31 @@
#include "../../../component/logfile.hpp"
#include "../../../component/scripting.hpp"
#include "../../../component/fastfiles.hpp"
#include "../../../component/scheduler.hpp"
#include <utils/string.hpp>
#include <utils/io.hpp>
#include <utils/http.hpp>
namespace scripting::lua
{
namespace
{
struct http_request
{
sol::protected_function on_error;
sol::protected_function on_progress;
sol::protected_function on_load;
};
std::unordered_map<uint64_t, http_request> http_requests;
std::string get_as_string(const sol::this_state s, sol::object o)
{
sol::state_view state(s);
return state["tostring"](o);
}
vector normalize_vector(const vector& vec)
{
const auto length = sqrt(
@ -567,6 +584,162 @@ namespace scripting::lua
return sol::lua_value{s, sol::lua_nil};
}
};
static uint64_t task_id = 0;
state["http"] = sol::table::create(state.lua_state());
state["http"]["get"] = [](const sol::this_state, const std::string& url,
const sol::protected_function& callback, sol::variadic_args va)
{
bool async = false;
if (va.size() >= 1 && va[0].get_type() == sol::type::boolean)
{
async = va[0].as<bool>();
}
const auto cur_task_id = task_id++;
auto request_callbacks = &http_requests[cur_task_id];
request_callbacks->on_load = callback;
::scheduler::once([url, cur_task_id]()
{
if (http_requests.find(cur_task_id) == http_requests.end())
{
return;
}
const auto data = utils::http::get_data(url);
::scheduler::once([data, cur_task_id]()
{
if (http_requests.find(cur_task_id) == http_requests.end())
{
return;
}
const auto& request_callbacks_ = http_requests[cur_task_id];
const auto has_value = data.has_value();
handle_error(request_callbacks_.on_load(has_value ? data.value().buffer : "", has_value));
http_requests.erase(cur_task_id);
}, ::scheduler::pipeline::server);
}, ::scheduler::pipeline::async);
};
state["http"]["request"] = [](const sol::this_state s, const std::string& url, sol::variadic_args va)
{
auto request = sol::table::create(s.lua_state());
std::string buffer{};
std::string fields_string{};
std::unordered_map<std::string, std::string> headers_map;
if (va.size() >= 1 && va[0].get_type() == sol::type::table)
{
const auto options = va[0].as<sol::table>();
const auto fields = options["parameters"];
const auto body = options["body"];
const auto headers = options["headers"];
if (fields.get_type() == sol::type::table)
{
const auto _fields = fields.get<sol::table>();
for (const auto& field : _fields)
{
const auto key = field.first.as<std::string>();
const auto value = get_as_string(s, field.second);
fields_string += key + "=" + value + "&";
}
}
if (body.get_type() == sol::type::string)
{
fields_string = body.get<std::string>();
}
if (headers.get_type() == sol::type::table)
{
const auto _headers = headers.get<sol::table>();
for (const auto& header : _headers)
{
const auto key = header.first.as<std::string>();
const auto value = get_as_string(s, header.second);
headers_map[key] = value;
}
}
}
request["onerror"] = []() {};
request["onprogress"] = []() {};
request["onload"] = []() {};
request["send"] = [url, fields_string, request, headers_map]()
{
const auto cur_task_id = task_id++;
auto request_callbacks = &http_requests[cur_task_id];
request_callbacks->on_error = request["onerror"];
request_callbacks->on_progress = request["onprogress"];
request_callbacks->on_load = request["onload"];
::scheduler::once([url, fields_string, cur_task_id, headers_map]()
{
if (http_requests.find(cur_task_id) == http_requests.end())
{
return;
}
const auto result = utils::http::get_data(url, fields_string, headers_map, [cur_task_id](size_t value)
{
::scheduler::once([cur_task_id, value]()
{
if (http_requests.find(cur_task_id) == http_requests.end())
{
return;
}
const auto& request_callbacks_ = http_requests[cur_task_id];
handle_error(request_callbacks_.on_progress(value));
}, ::scheduler::pipeline::server);
});
::scheduler::once([cur_task_id, result]()
{
if (http_requests.find(cur_task_id) == http_requests.end())
{
return;
}
const auto& request_callbacks_ = http_requests[cur_task_id];
if (!result.has_value())
{
request_callbacks_.on_error("Unknown", -1);
return;
}
const auto& http_result = result.value();
if (http_result.code == CURLE_OK)
{
handle_error(request_callbacks_.on_load(http_result.buffer));
}
else
{
handle_error(request_callbacks_.on_error(curl_easy_strerror(http_result.code), http_result.code));
}
http_requests.erase(cur_task_id);
}, ::scheduler::pipeline::server);
}, ::scheduler::pipeline::async);
};
return request;
};
}
}

View File

@ -2,6 +2,7 @@
#include "script_value.hpp"
#include "entity.hpp"
#include "array.hpp"
#include "functions.hpp"
namespace scripting
{
@ -290,4 +291,39 @@ namespace scripting
{
return this->value_.get();
}
std::string script_value::to_string() const
{
if (this->is<int>())
{
return utils::string::va("%i", this->as<int>());
}
if (this->is<float>())
{
return utils::string::va("%f", this->as<float>());
}
if (this->is<std::string>())
{
return this->as<std::string>();
}
if (this->is<vector>())
{
const auto vec = this->as<vector>();
return utils::string::va("(%g, %g, %g)",
vec.get_x(),
vec.get_y(),
vec.get_z()
);
}
if (this->is<std::function<void()>>())
{
return utils::string::va("[[ function ]]");
}
return this->type_name();
}
}

View File

@ -3,6 +3,8 @@
#include "variable_value.hpp"
#include "vector.hpp"
#include <utils/string.hpp>
namespace scripting
{
class entity;
@ -32,6 +34,14 @@ namespace scripting
template <typename T>
bool is() const;
// was gonna do this but no clue if this is the same on H1 so just return string (https://github.com/fedddddd/t6-gsc-utils/blob/main/src/game/scripting/script_value.hpp#L18)
std::string type_name() const
{
return utils::string::va("%s", this->get_raw().type);
}
std::string to_string() const;
template <typename T>
T as() const
{

View File

@ -1519,16 +1519,39 @@ namespace game
int usesDelta;
};
enum sessionState_t
{
SESS_STATE_PLAYING = 0x0,
SESS_STATE_DEAD = 0x1,
SESS_STATE_SPECTATOR = 0x2,
SESS_STATE_INTERMISSION = 0x3,
};
enum team_t
{
TEAM_FREE = 0x0,
TEAM_BAD = 0x0,
TEAM_AXIS = 0x1,
TEAM_ALLIES = 0x2,
TEAM_SPECTATOR = 0x3,
TEAM_NUM_TEAMS = 0x4,
};
struct gclient_s
{
char __pad0[2];
char pm_type; // 2
char __pad1[18831];
char __pad1[18573];
sessionState_t sessionState;
char __pad2[220]; // 254
team_t team;
char __pad3[30];
char name[32]; // 18834
char __pad2[622];
char __pad4[622];
int flags; // 19488
}; // size = ?
static_assert(offsetof(gclient_s, team) == 18800);
static_assert(offsetof(gclient_s, name) == 18834);
static_assert(offsetof(gclient_s, flags) == 19488);
@ -1542,7 +1565,7 @@ namespace game
struct EntityState
{
uint16_t entityNum;
uint16_t number;
}; // size = ?
#pragma pack(push, 1)

View File

@ -147,6 +147,24 @@ LUI.addmenubutton = function(name, data)
end)
end
LUI.removemenubutton = function(name, index)
LUI.onmenuopen(name, function(menu)
if (not menu.list) then
return
end
local buttonlist = menu:getChildById(menu.type .. "_list")
local button = buttonlist.childRecord[string.format("%s_button_%s", name, index)]
buttonlist:removeElement(button)
local hintbox = menu.optionTextInfo
menu:removeElement(hintbox)
LUI.Options.InitScrollingList(menu.list, nil)
menu.optionTextInfo = LUI.Options.AddOptionTextInfo(menu)
end)
end
LUI.openmenu = function(menu, args)
stack = args
LUI.FlowManager.RequestAddMenu(nil, menu)

View File

@ -21,7 +21,8 @@ namespace utils::flags
if (wide_flag[0] == L'-')
{
wide_flag.erase(wide_flag.begin());
flags.emplace_back(string::convert(wide_flag));
const auto flag = string::convert(wide_flag);
flags.emplace_back(string::to_lower(flag));
}
}
@ -40,14 +41,7 @@ namespace utils::flags
parsed = true;
}
for (const auto& entry : enabled_flags)
{
if (string::to_lower(entry) == string::to_lower(flag))
{
return true;
}
}
return false;
return std::ranges::any_of(enabled_flags.cbegin(), enabled_flags.cend(),
[flag](const auto& elem) { return elem == string::to_lower(flag); });
}
}

View File

@ -6,6 +6,32 @@ namespace utils::http
{
namespace
{
struct progress_helper
{
const std::function<void(size_t)>* callback{};
std::exception_ptr exception{};
};
int progress_callback(void* clientp, const curl_off_t /*dltotal*/, const curl_off_t dlnow, const curl_off_t /*ultotal*/, const curl_off_t /*ulnow*/)
{
auto* helper = static_cast<progress_helper*>(clientp);
try
{
if (*helper->callback)
{
(*helper->callback)(dlnow);
}
}
catch (...)
{
helper->exception = std::current_exception();
return -1;
}
return 0;
}
size_t write_callback(void* contents, const size_t size, const size_t nmemb, void* userp)
{
auto* buffer = static_cast<std::string*>(userp);
@ -16,7 +42,8 @@ namespace utils::http
}
}
std::optional<result> get_data(const std::string& url)
std::optional<result> get_data(const std::string& url, const std::string& fields,
const headers& headers, const std::function<void(size_t)>& callback)
{
curl_slist* header_list = nullptr;
auto* curl = curl_easy_init();
@ -31,13 +58,28 @@ namespace utils::http
curl_easy_cleanup(curl);
});
for (const auto& header : headers)
{
auto data = header.first + ": " + header.second;
header_list = curl_slist_append(header_list, data.data());
}
std::string buffer{};
progress_helper helper{};
helper.callback = &callback;
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, header_list);
curl_easy_setopt(curl, CURLOPT_URL, url.data());
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1);
curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback);
curl_easy_setopt(curl, CURLOPT_XFERINFODATA, &helper);
curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0);
if (!fields.empty())
{
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, fields.data());
}
const auto code = curl_easy_perform(curl);
@ -49,20 +91,24 @@ namespace utils::http
return result;
}
else
{
result result;
result.code = code;
return result;
if (helper.exception)
{
std::rethrow_exception(helper.exception);
}
result result;
result.code = code;
return result;
}
std::future<std::optional<result>> get_data_async(const std::string& url)
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields,
const headers& headers, const std::function<void(size_t)>& callback)
{
return std::async(std::launch::async, [url]()
return std::async(std::launch::async, [url, fields, headers, callback]()
{
return get_data(url);
return get_data(url, fields, headers, callback);
});
}
}

View File

@ -11,10 +11,14 @@ namespace utils::http
{
struct result
{
CURLcode code;
std::string buffer;
CURLcode code{};
std::string buffer{};
};
std::optional<result> get_data(const std::string& url);
std::future<std::optional<result>> get_data_async(const std::string& url);
using headers = std::unordered_map<std::string, std::string>;
std::optional<result> get_data(const std::string& url, const std::string& fields = {},
const headers& headers = {}, const std::function<void(size_t)>& callback = {});
std::future<std::optional<result>> get_data_async(const std::string& url, const std::string& fields = {},
const headers& headers = {}, const std::function<void(size_t)>& callback = {});
}

View File

@ -26,7 +26,7 @@ namespace utils
return value->second;
}
return "";
return {};
}
void info_string::parse(std::string buffer)
@ -49,15 +49,15 @@ namespace utils
{
//auto first = true;
std::string info_string;
for (auto i = this->key_value_pairs_.begin(); i != this->key_value_pairs_.end(); ++i)
for (const auto& [key, val] : this->key_value_pairs_)
{
//if (first) first = false;
/*else*/
info_string.append("\\");
info_string.append(i->first); // Key
info_string.append(key);
info_string.append("\\");
info_string.append(i->second); // Value
info_string.append(val);
}
return info_string;

View File

@ -32,7 +32,7 @@ namespace utils::io
if (stream.is_open())
{
stream.write(data.data(), data.size());
stream.write(data.data(), static_cast<std::streamsize>(data.size()));
stream.close();
return true;
}

View File

@ -72,7 +72,7 @@ namespace utils
void* memory::allocate(const size_t length)
{
return calloc(length, 1);
return std::calloc(length, 1);
}
char* memory::duplicate_string(const std::string& string)
@ -86,7 +86,7 @@ namespace utils
{
if (data)
{
::free(data);
std::free(data);
}
}

Binary file not shown.