feat(lua): Allow lua scripts to flag modders with a custom reason if needed. (#2248)

This commit is contained in:
Quentin 2023-10-13 00:11:37 +02:00 committed by GitHub
parent f9c948f909
commit 94838af288
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 301 additions and 68 deletions

View File

@ -18,6 +18,7 @@ class DocKind(Enum):
Constructor = "constructor"
Function = "function"
Tabs = "tabs"
Infraction = "infraction"
class Table:
@ -324,7 +325,10 @@ def parse_lua_api_doc(folder_path):
.lower()
)
if doc_kind is not DocKind.Tabs:
if (
doc_kind is not DocKind.Tabs
and doc_kind is not DocKind.Infraction
):
continue
if doc_kind is not None and "//" in line:
@ -367,7 +371,10 @@ def parse_lua_api_doc(folder_path):
line,
line_lower,
)
case DocKind.Tabs: parse_tabs_doc(file)
case DocKind.Tabs:
parse_tabs_doc(file)
case DocKind.Infraction:
parse_infraction_doc(file)
case _:
# print("unsupported doc kind: " + str(doc_kind))
pass
@ -386,6 +393,7 @@ def parse_table_doc(cur_table, line, line_lower):
return cur_table
def parse_class_doc(cur_class, line, line_lower):
if is_lua_doc_comment_startswith(line_lower, "name"):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
@ -402,28 +410,43 @@ def parse_class_doc(cur_class, line, line_lower):
def parse_function_doc(cur_function, cur_table, cur_class, line, line_lower):
if is_lua_doc_comment_startswith(line_lower, "table") and lua_api_comment_separator in line_lower:
if (
is_lua_doc_comment_startswith(line_lower, "table")
and lua_api_comment_separator in line_lower
):
table_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_table = make_table(table_name)
cur_function = Function("Didnt get name yet", cur_table, [], None, "", "")
cur_table.functions.append(cur_function)
elif is_lua_doc_comment_startswith(line_lower, "class") and lua_api_comment_separator in line_lower:
elif (
is_lua_doc_comment_startswith(line_lower, "class")
and lua_api_comment_separator in line_lower
):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class = make_class(class_name)
cur_function = Function("Didnt get name yet", cur_class, [], None, "", "")
cur_class.functions.append(cur_function)
elif is_lua_doc_comment_startswith(line_lower, "name") and lua_api_comment_separator in line_lower:
elif (
is_lua_doc_comment_startswith(line_lower, "name")
and lua_api_comment_separator in line_lower
):
function_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_function.name = function_name
if function_name not in functions:
functions[function_name] = cur_function
elif is_lua_doc_comment_startswith(line_lower, "param") and lua_api_comment_separator in line_lower:
elif (
is_lua_doc_comment_startswith(line_lower, "param")
and lua_api_comment_separator in line_lower
):
parameter = make_parameter_from_doc_line(line)
cur_function.parameters.append(parameter)
elif is_lua_doc_comment_startswith(line_lower, "return") and lua_api_comment_separator in line_lower:
elif (
is_lua_doc_comment_startswith(line_lower, "return")
and lua_api_comment_separator in line_lower
):
return_info = line.split(lua_api_comment_separator, 2)
try:
cur_function.return_type = return_info[1].strip()
@ -439,19 +462,28 @@ def parse_function_doc(cur_function, cur_table, cur_class, line, line_lower):
def parse_field_doc(cur_field, cur_table, cur_class, line, line_lower):
if is_lua_doc_comment_startswith(line_lower, "table") and lua_api_comment_separator in line_lower:
if (
is_lua_doc_comment_startswith(line_lower, "table")
and lua_api_comment_separator in line_lower
):
table_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_table = make_table(table_name)
cur_field = Field("Didnt get name yet", "", "")
cur_table.fields.append(cur_field)
elif is_lua_doc_comment_startswith(line_lower, "class") and lua_api_comment_separator in line_lower:
elif (
is_lua_doc_comment_startswith(line_lower, "class")
and lua_api_comment_separator in line_lower
):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class = make_class(class_name)
cur_field = Field("Didnt get name yet", "", "")
cur_class.fields.append(cur_field)
elif is_lua_doc_comment_startswith(line_lower, "field") and lua_api_comment_separator in line_lower:
elif (
is_lua_doc_comment_startswith(line_lower, "field")
and lua_api_comment_separator in line_lower
):
field_info = line.split(lua_api_comment_separator, 2)
cur_field.name = field_info[1].strip()
cur_field.type_ = field_info[2].strip()
@ -467,13 +499,19 @@ def parse_field_doc(cur_field, cur_table, cur_class, line, line_lower):
def parse_constructor_doc(cur_constructor, cur_class, line, line_lower):
if is_lua_doc_comment_startswith(line_lower, "class") and lua_api_comment_separator in line_lower:
if (
is_lua_doc_comment_startswith(line_lower, "class")
and lua_api_comment_separator in line_lower
):
class_name = line.split(lua_api_comment_separator, 1)[1].strip()
cur_class = make_class(class_name)
cur_constructor = Constructor(cur_class, [], "")
cur_class.constructors.append(cur_constructor)
elif is_lua_doc_comment_startswith(line_lower, "param") and lua_api_comment_separator in line_lower:
elif (
is_lua_doc_comment_startswith(line_lower, "param")
and lua_api_comment_separator in line_lower
):
parameter = make_parameter_from_doc_line(line)
cur_constructor.parameters.append(parameter)
else:
@ -485,6 +523,8 @@ def parse_constructor_doc(cur_constructor, cur_class, line, line_lower):
tabs_enum = []
def parse_tabs_doc(file):
start_parsing = False
for line in file:
@ -503,7 +543,29 @@ def parse_tabs_doc(file):
continue
else:
tabs_enum.append(line.replace(",", "").strip())
infraction_enum = []
def parse_infraction_doc(file):
start_parsing = False
for line in file:
if "enum class" in line.lower():
start_parsing = True
continue
if start_parsing:
if "};" in line.lower():
return
if "{" == line.lower().strip():
continue
if "//" in line.lower():
continue
if "" == line.lower().strip():
continue
else:
infraction_enum.append(line.replace(",", "").strip())
def make_parameter_from_doc_line(line):
@ -519,13 +581,15 @@ def make_parameter_from_doc_line(line):
return Parameter(param_name, param_type, param_desc)
def sanitize_description(line):
if line.startswith("// ") and line[3] != ' ':
if line.startswith("// ") and line[3] != " ":
line = line[3:]
if line.startswith("//"):
line = line[2:]
return line.rstrip()
def is_lua_doc_comment_startswith(line_lower, starts_with_text):
return line_lower.replace("//", "").strip().startswith(starts_with_text)
@ -550,7 +614,8 @@ if os.path.exists(tabs_file_name):
os.remove(tabs_file_name)
f = open(tabs_file_name, "a")
f.write("""# Tabs
f.write(
"""# Tabs
All the tabs from the menu are listed below, used as parameter for adding gui elements to them.
@ -565,16 +630,42 @@ end)
For a complete list of available gui functions, please refer to the tab class documentation and the gui table documentation.
""")
"""
)
f.write(f"## Tab Count: {len(tabs_enum)}\n\n")
# Minus the first, because it's the `NONE` tab, minus the last one because it's for runtime defined tabs.
for i in range(1, len(tabs_enum) - 1 ):
for i in range(1, len(tabs_enum) - 1):
f.write("### `GUI_TAB_" + tabs_enum[i] + "`\n")
f.close()
infraction_file_name = f"../lua/infraction.md"
if os.path.exists(infraction_file_name):
os.remove(infraction_file_name)
f = open(infraction_file_name, "a")
f.write(
"""# Infraction
All the infraction from the menu are listed below, used as parameter for adding an infraction to a given player, for flagging them as modder.
**Example Usage:**
```lua
network.flag_player_as_modder(player_index, infraction.CUSTOM_REASON, "My custom reason on why the player is flagged as a modder")
```
"""
)
f.write(f"## Infraction Count: {len(infraction_enum)}\n\n")
for i in range(0, len(infraction_enum)):
f.write("### `" + infraction_enum[i] + "`\n")
f.close()
try:
os.makedirs("./classes/")
except:
@ -589,13 +680,13 @@ for class_name, class_ in classes.items():
f.close()
commands_file_name = f"./commands.md"
if os.path.exists(commands_file_name):
os.remove(commands_file_name)
f = open(commands_file_name, "a")
f.write("""# Commands
f.write(
"""# Commands
All the current commands from the menu are listed below.
@ -608,7 +699,8 @@ command.call_player(somePlayerIndex, "spawn", {joaat("adder")})
For a complete list of available command functions, please refer to the command table documentation.
""")
"""
)
commands = []
@ -629,4 +721,4 @@ for cmd in commands:
f.write(f"Arg Count: {arg_count}\n")
f.write("\n")
f.close()
f.close()

23
docs/lua/infraction.md Normal file
View File

@ -0,0 +1,23 @@
# Infraction
All the infraction from the menu are listed below, used as parameter for adding an infraction to a given player, for flagging them as modder.
**Example Usage:**
```lua
network.flag_player_as_modder(player_index, infraction.CUSTOM_REASON, "My custom reason on why the player is flagged as a modder")
```
## Infraction Count: 12
### `TRIGGERED_ANTICHEAT`
### `TRIED_CRASH_PLAYER`
### `TRIED_KICK_PLAYER`
### `ATTACKING_WITH_GODMODE`
### `ATTACKING_WITH_INVISIBILITY`
### `ATTACKING_WHEN_HIDDEN_FROM_PLAYER_LIST`
### `SPOOFED_DATA`
### `SPOOFED_HOST_TOKEN`
### `INVALID_PLAYER_MODEL`
### `SUPER_JUMP`
### `UNDEAD_OTR`
### `CUSTOM_REASON`

View File

@ -2,7 +2,7 @@
Table containing helper functions for network related features.
## Functions (10)
## Functions (11)
### `trigger_script_event(bitset, _args)`
@ -79,16 +79,18 @@ integer = network.get_selected_player()
integer = network.get_selected_database_player_rockstar_id()
```
### `flag_player_as_modder(player_idx)`
### `flag_player_as_modder(player_idx, reason, custom_reason)`
Flags the given player as a modder in our local database.
- **Parameters:**
- `player_idx` (integer): Index of the player.
- `reason` (Infraction): Reason why the player is flagged as a modder, if the infraction is CUSTOM_REASON, then the custom_reason string is added in the local database. For a full list of the possible infraction reasons to use, please check the infraction page.
- `custom_reason` (string): Optional, required only when the infraction is CUSTOM_REASON. The custom reason why the player is flagged as a modder.
**Example Usage:**
```lua
network.flag_player_as_modder(player_idx)
network.flag_player_as_modder(player_idx, reason, custom_reason)
```
### `is_player_flagged_as_modder(player_idx)`
@ -104,6 +106,19 @@ network.flag_player_as_modder(player_idx)
boolean = network.is_player_flagged_as_modder(player_idx)
```
### `get_flagged_modder_reason(player_idx)`
- **Parameters:**
- `player_idx` (integer): Index of the player.
- **Returns:**
- `string`: Returns a string which contains the reason(s) why the player is flagged as a modder.
**Example Usage:**
```lua
string = network.get_flagged_modder_reason(player_idx)
```
### `force_script_host(script_name)`
Try to force ourself to be host for the given GTA Script.

View File

@ -1,11 +1,12 @@
#pragma once
#include "gta/joaat.hpp"
namespace big
{
// Add new values to the bottom
// Lua API: Infraction
enum class Infraction
{
// Add new values to the bottom (for serialization)
DESYNC_PROTECTION, // do not use
BREAKUP_KICK_DETECTED, // do not use
LOST_CONNECTION_KICK_DETECTED, // do not use
@ -22,24 +23,7 @@ namespace big
INVALID_PLAYER_MODEL,
SUPER_JUMP,
UNDEAD_OTR,
};
inline std::unordered_map<Infraction, const char*> infraction_desc = {
{Infraction::DESYNC_PROTECTION, "Used desync protections"},
{Infraction::BREAKUP_KICK_DETECTED, "Kicked someone using breakup kick"},
{Infraction::LOST_CONNECTION_KICK_DETECTED, "Tried to kick someone using lost connection kick"},
{Infraction::SPOOFED_ROCKSTAR_ID, "Had spoofed RID"},
{Infraction::TRIGGERED_ANTICHEAT, "Triggered Rockstar's anticheat"},
{Infraction::TRIED_CRASH_PLAYER, "Tried to crash you"},
{Infraction::TRIED_KICK_PLAYER, "Tried to kick you"},
{Infraction::BLAME_EXPLOSION_DETECTED, "Tried to blame someone for their explosion"},
{Infraction::ATTACKING_WITH_GODMODE, "Attacked someone while using godmode"},
{Infraction::ATTACKING_WITH_INVISIBILITY, "Attacked someone while being invisible"},
{Infraction::ATTACKING_WHEN_HIDDEN_FROM_PLAYER_LIST, "Attacked someone while being hidden from the player list"},
{Infraction::SPOOFED_DATA, "Had spoofed data"},
{Infraction::SPOOFED_HOST_TOKEN, "Had spoofed their host token"},
{Infraction::INVALID_PLAYER_MODEL, "Had used an invalid player model"},
{Infraction::SUPER_JUMP, "Had used super jump"},
{Infraction::UNDEAD_OTR, "Had used undead OTR"},
// So that lua scripts can add a custom runtime reason.
CUSTOM_REASON,
};
}

View File

@ -6,6 +6,7 @@
#include "services/player_database/player_database_service.hpp"
#include "util/notify.hpp"
#include "util/scripts.hpp"
#include "util/session.hpp"
#include "util/system.hpp"
#include "util/teleport.hpp"
@ -100,19 +101,26 @@ namespace lua::network
return -1;
}
static void flag_player_as_modder(int player_idx, big::Infraction reason)
{
if (auto player = big::g_player_service->get_by_id(player_idx))
{
big::session::add_infraction(player, reason);
}
}
// Lua API: Function
// Table: network
// Name: flag_player_as_modder
// Param: player_idx: integer: Index of the player.
// Param: reason: Infraction: Reason why the player is flagged as a modder, if the infraction is CUSTOM_REASON, then the custom_reason string is added in the local database. For a full list of the possible infraction reasons to use, please check the infraction page.
// Param: custom_reason: string: Optional, required only when the infraction is CUSTOM_REASON. The custom reason why the player is flagged as a modder.
// Flags the given player as a modder in our local database.
static void flag_player_as_modder(int player_idx)
static void flag_player_as_modder_custom_reason(int player_idx, big::Infraction reason, const std::string& custom_reason)
{
if (auto player = big::g_player_service->get_by_id(player_idx))
{
auto pers = big::g_player_database_service->get_or_create_player(player);
pers->is_modder = true;
big::g_player_database_service->save();
player->is_modder = true;
big::session::add_infraction(player, reason, custom_reason);
}
}
@ -129,6 +137,21 @@ namespace lua::network
return false;
}
// Lua API: Function
// Table: network
// Name: get_flagged_modder_reason
// Param: player_idx: integer: Index of the player.
// Returns: string: Returns a string which contains the reason(s) why the player is flagged as a modder.
static std::string get_flagged_modder_reason(int player_idx)
{
if (auto player = big::g_player_service->get_by_id(player_idx))
{
return big::g_player_database_service->get_or_create_player(player)->get_all_infraction_descriptions();
}
return "";
}
// Lua API: Function
// Table: network
// Name: force_script_host
@ -158,16 +181,39 @@ namespace lua::network
void bind(sol::state& state)
{
auto ns = state["network"].get_or_create<sol::table>();
state.new_enum<big::Infraction>("infraction",
{
{"ATTACKING_WHEN_HIDDEN_FROM_PLAYER_LIST", big::Infraction::ATTACKING_WHEN_HIDDEN_FROM_PLAYER_LIST},
{"ATTACKING_WITH_GODMODE", big::Infraction::ATTACKING_WITH_GODMODE},
{"ATTACKING_WITH_INVISIBILITY", big::Infraction::ATTACKING_WITH_INVISIBILITY},
{"BLAME_EXPLOSION_DETECTED", big::Infraction::BLAME_EXPLOSION_DETECTED},
{"BREAKUP_KICK_DETECTED", big::Infraction::BREAKUP_KICK_DETECTED},
{"CUSTOM_REASON", big::Infraction::CUSTOM_REASON},
{"DESYNC_PROTECTION", big::Infraction::DESYNC_PROTECTION},
{"INVALID_PLAYER_MODEL", big::Infraction::INVALID_PLAYER_MODEL},
{"LOST_CONNECTION_KICK_DETECTED", big::Infraction::LOST_CONNECTION_KICK_DETECTED},
{"SPOOFED_DATA", big::Infraction::SPOOFED_DATA},
{"SPOOFED_HOST_TOKEN", big::Infraction::SPOOFED_HOST_TOKEN},
{"SPOOFED_ROCKSTAR_ID", big::Infraction::SPOOFED_ROCKSTAR_ID},
{"SUPER_JUMP", big::Infraction::SUPER_JUMP},
{"TRIED_CRASH_PLAYER", big::Infraction::TRIED_CRASH_PLAYER},
{"TRIED_KICK_PLAYER", big::Infraction::TRIED_KICK_PLAYER},
{"TRIGGERED_ANTICHEAT", big::Infraction::TRIGGERED_ANTICHEAT},
{"UNDEAD_OTR", big::Infraction::UNDEAD_OTR},
});
auto ns = state["network"].get_or_create<sol::table>();
ns["trigger_script_event"] = trigger_script_event;
ns["give_pickup_rewards"] = give_pickup_rewards;
ns["set_player_coords"] = set_player_coords;
ns["set_all_player_coords"] = set_all_player_coords;
ns["get_selected_player"] = get_selected_player;
ns["get_selected_database_player_rockstar_id"] = get_selected_database_player_rockstar_id;
ns["flag_player_as_modder"] = flag_player_as_modder;
ns["is_player_flagged_as_modder"] = is_player_flagged_as_modder;
ns["force_script_host"] = force_script_host;
ns["send_chat_message"] = send_chat_message;
ns["flag_player_as_modder"] = sol::overload(flag_player_as_modder, flag_player_as_modder_custom_reason);
ns["is_player_flagged_as_modder"] = is_player_flagged_as_modder;
ns["get_flagged_modder_reason"] = get_flagged_modder_reason;
ns["force_script_host"] = force_script_host;
ns["send_chat_message"] = send_chat_message;
}
}

View File

@ -0,0 +1,63 @@
#pragma once
#include "persistent_player.hpp"
#include "core/data/infractions.hpp"
namespace big
{
static std::unordered_map<Infraction, const char*> infraction_desc = {
{Infraction::DESYNC_PROTECTION, "Used desync protections"},
{Infraction::BREAKUP_KICK_DETECTED, "Kicked someone using breakup kick"},
{Infraction::LOST_CONNECTION_KICK_DETECTED, "Tried to kick someone using lost connection kick"},
{Infraction::SPOOFED_ROCKSTAR_ID, "Had spoofed RID"},
{Infraction::TRIGGERED_ANTICHEAT, "Triggered Rockstar's anticheat"},
{Infraction::TRIED_CRASH_PLAYER, "Tried to crash you"},
{Infraction::TRIED_KICK_PLAYER, "Tried to kick you"},
{Infraction::BLAME_EXPLOSION_DETECTED, "Tried to blame someone for their explosion"},
{Infraction::ATTACKING_WITH_GODMODE, "Attacked someone while using godmode"},
{Infraction::ATTACKING_WITH_INVISIBILITY, "Attacked someone while being invisible"},
{Infraction::ATTACKING_WHEN_HIDDEN_FROM_PLAYER_LIST, "Attacked someone while being hidden from the player list"},
{Infraction::SPOOFED_DATA, "Had spoofed data"},
{Infraction::SPOOFED_HOST_TOKEN, "Had spoofed their host token"},
{Infraction::INVALID_PLAYER_MODEL, "Had used an invalid player model"},
{Infraction::SUPER_JUMP, "Had used super jump"},
{Infraction::UNDEAD_OTR, "Had used undead OTR"},
};
const char* persistent_player::get_infraction_description(int infraction)
{
if (static_cast<Infraction>(infraction) == Infraction::CUSTOM_REASON)
{
return custom_infraction_reason.c_str();
}
return infraction_desc[static_cast<Infraction>(infraction)];
}
std::string persistent_player::get_all_infraction_descriptions()
{
if (!infractions.size())
{
return "";
}
constexpr auto separator = ", ";
constexpr size_t separator_length = (std::string(separator)).size();
std::string s;
for (const auto infr : infractions)
{
s += get_infraction_description(infr);
s += separator;
}
// Remove last useless separator
for (size_t i = 0; i < separator_length; i++)
{
s.pop_back();
}
return s;
}
}

View File

@ -1,5 +1,4 @@
#pragma once
#include "core/data/infractions.hpp"
#include "json_util.hpp"
#include <unordered_set>
@ -43,12 +42,13 @@ namespace big
struct persistent_player
{
std::string name;
uint64_t rockstar_id = 0;
bool block_join = false;
int block_join_reason = 1;
bool is_modder = false;
bool notify_online = false;
uint64_t rockstar_id = 0;
bool block_join = false;
int block_join_reason = 1;
bool is_modder = false;
bool notify_online = false;
std::unordered_set<int> infractions;
std::string custom_infraction_reason = "";
std::string notes = "";
std::optional<CommandAccessLevel> command_access_level = std::nullopt;
bool join_redirect = false;
@ -66,7 +66,9 @@ namespace big
std::string game_mode_id = "";
rage::rlSessionInfo redirect_info{};
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(persistent_player, name, rockstar_id, block_join, block_join_reason, is_modder, notify_online, infractions, notes, command_access_level, join_redirect, join_redirect_preference)
};
NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(persistent_player, name, rockstar_id, block_join, block_join_reason, is_modder, notify_online, infractions, custom_infraction_reason, notes, command_access_level, join_redirect, join_redirect_preference)
const char* get_infraction_description(int infraction);
std::string get_all_infraction_descriptions();
};
};

View File

@ -1,4 +1,5 @@
#pragma once
#include "core/data/infractions.hpp"
#include "core/data/session_types.hpp"
#include "fiber_pool.hpp"
#include "gta/joaat.hpp"
@ -48,7 +49,7 @@ namespace big::session
else
{
*scr_globals::session.at(2).as<int*>() = 0;
*scr_globals::session2.as<int*>() = (int)session;
*scr_globals::session2.as<int*>() = (int)session;
}
*scr_globals::session.as<int*>() = 1;
@ -63,7 +64,7 @@ namespace big::session
}
scr_functions::reset_session_data({true, true});
*scr_globals::session3.as<int*>() = 0;
*scr_globals::session3.as<int*>() = 0;
*scr_globals::session4.as<int*>() = 1;
*scr_globals::session5.as<int*>() = 32;
@ -158,7 +159,7 @@ namespace big::session
});
}
inline void add_infraction(player_ptr player, Infraction infraction)
inline void add_infraction(player_ptr player, Infraction infraction, const std::string& custom_reason = "")
{
if (g.debug.fuzzer.enabled)
return;
@ -168,8 +169,15 @@ namespace big::session
{
plyr->is_modder = true;
player->is_modder = true;
plyr->infractions.insert((int)infraction);
if (infraction == Infraction::CUSTOM_REASON)
{
plyr->custom_infraction_reason += plyr->custom_infraction_reason.size() ? (std::string(", ") + custom_reason) : custom_reason;
}
g_player_database_service->save();
g.reactions.modder_detection.process(player);
}
}

View File

@ -182,7 +182,7 @@ namespace big
for (auto& infraction : current_player->infractions)
{
ImGui::BulletText(infraction_desc[(Infraction)infraction]);
ImGui::BulletText(current_player->get_infraction_description(infraction));
}
}

View File

@ -71,7 +71,7 @@ namespace big
{
ImGui::BeginTooltip();
for (auto infraction : sorted_player->infractions)
ImGui::BulletText(infraction_desc[(Infraction)infraction]);
ImGui::BulletText(sorted_player->get_infraction_description(infraction));
ImGui::EndTooltip();
}
}