mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-23 12:07:27 +01:00
Improved antag fishing busting (#19542)
This commit is contained in:
@@ -73,6 +73,9 @@
|
||||
/// Note "ckey" for CID info tracking. Do not EVER update this.
|
||||
#define CIDTRACKING_PSUEDO_CKEY "ALICE-CIDTRACKING"
|
||||
|
||||
/// Note "ckey" for roundstart antag rolling tracking. Do not EVER update this.
|
||||
#define ANTAGTRACKING_PSUEDO_CKEY "ALICE-ANTAGTRACKING"
|
||||
|
||||
// Connection types. These match enums in the SQL DB. Dont change them
|
||||
/// Client was let into the server
|
||||
#define CONNECTION_TYPE_ESTABLISHED "ESTABLISHED"
|
||||
|
||||
@@ -276,12 +276,12 @@ SUBSYSTEM_DEF(jobs)
|
||||
|
||||
//Get the players who are ready
|
||||
for(var/mob/new_player/player in GLOB.player_list)
|
||||
if(player.ready && player.has_valid_preferences() && player.mind && !player.mind.assigned_role)
|
||||
if(player.ready && player.mind && !player.mind.assigned_role)
|
||||
unassigned += player
|
||||
|
||||
Debug("DO, Len: [unassigned.len]")
|
||||
if(unassigned.len == 0)
|
||||
return 0
|
||||
if(!length(unassigned))
|
||||
return FALSE
|
||||
|
||||
//Shuffle players and jobs
|
||||
unassigned = shuffle(unassigned)
|
||||
@@ -392,7 +392,7 @@ SUBSYSTEM_DEF(jobs)
|
||||
unassigned -= player
|
||||
|
||||
log_debug("Dividing Occupations took [stop_watch(watch)]s")
|
||||
return 1
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/jobs/proc/AssignRank(mob/living/carbon/human/H, rank, joined_late = FALSE, log_to_db = TRUE)
|
||||
if(!H)
|
||||
|
||||
@@ -65,6 +65,8 @@ SUBSYSTEM_DEF(ticker)
|
||||
var/real_reboot_time = 0
|
||||
/// Datum used to generate the end of round scoreboard.
|
||||
var/datum/scoreboard/score = null
|
||||
/// List of ckeys who had antag rolling issues flagged
|
||||
var/list/flagged_antag_rollers = list()
|
||||
|
||||
/datum/controller/subsystem/ticker/Initialize()
|
||||
login_music = pick(\
|
||||
@@ -197,13 +199,35 @@ SUBSYSTEM_DEF(ticker)
|
||||
if(player.client.prefs.toggles2 & PREFTOGGLE_2_RANDOMSLOT)
|
||||
player.client.prefs.load_random_character_slot(player.client)
|
||||
|
||||
// Lets check if people who ready should or shouldnt be
|
||||
for(var/mob/new_player/P in GLOB.player_list)
|
||||
// Not logged in
|
||||
if(!P.client)
|
||||
continue
|
||||
// Not ready
|
||||
if(!P.ready)
|
||||
continue
|
||||
// Not set to return if nothing available
|
||||
if(P.client.prefs.active_character.alternate_option != RETURN_TO_LOBBY)
|
||||
continue
|
||||
|
||||
var/has_antags = (length(P.client.prefs.be_special) > 0)
|
||||
if(!P.client.prefs.active_character.check_any_job())
|
||||
to_chat(P, "<span class='danger'>You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.</span>")
|
||||
if(has_antags)
|
||||
// We add these to a list so we can deal with them as a batch later
|
||||
// A lot of DB tracking stuff needs doing, so we may as well async it
|
||||
flagged_antag_rollers |= P.ckey
|
||||
|
||||
P.ready = FALSE
|
||||
|
||||
//Configure mode and assign player to special mode stuff
|
||||
mode.pre_pre_setup()
|
||||
var/can_continue
|
||||
can_continue = mode.pre_setup() //Setup special modes
|
||||
var/can_continue = FALSE
|
||||
can_continue = mode.pre_setup() //Setup special modes. This also does the antag fishing checks.
|
||||
SSjobs.DivideOccupations() //Distribute jobs
|
||||
if(!can_continue)
|
||||
qdel(mode)
|
||||
QDEL_NULL(mode)
|
||||
to_chat(world, "<B>Error setting up [GLOB.master_mode].</B> Reverting to pre-game lobby.")
|
||||
current_state = GAME_STATE_PREGAME
|
||||
force_start = FALSE
|
||||
@@ -307,6 +331,9 @@ SUBSYSTEM_DEF(ticker)
|
||||
#ifdef UNIT_TESTS
|
||||
RunUnitTests()
|
||||
#endif
|
||||
|
||||
// Do this 10 second after roundstart because of roundstart lag, and make it more visible
|
||||
addtimer(CALLBACK(src, .proc/handle_antagfishing_reporting), 10 SECONDS)
|
||||
return TRUE
|
||||
|
||||
|
||||
@@ -653,3 +680,53 @@ SUBSYSTEM_DEF(ticker)
|
||||
sleep(sound_length)
|
||||
|
||||
world.Reboot()
|
||||
|
||||
// Timers invoke this async
|
||||
/datum/controller/subsystem/ticker/proc/handle_antagfishing_reporting()
|
||||
// This needs the DB
|
||||
if(!SSdbcore.IsConnected())
|
||||
return
|
||||
// Dont need to do anything
|
||||
if(!length(flagged_antag_rollers))
|
||||
return
|
||||
|
||||
// Records themselves
|
||||
var/list/datum/antag_record/records = list()
|
||||
// Queries to load data (executed as async batch)
|
||||
var/list/datum/db_query/load_queries = list()
|
||||
// Queries to save data (executed as async batch)
|
||||
var/list/datum/db_query/save_queries = list()
|
||||
|
||||
|
||||
for(var/ckey in flagged_antag_rollers)
|
||||
var/datum/antag_record/AR = new /datum/antag_record(ckey)
|
||||
records[ckey] = AR
|
||||
load_queries[ckey] = AR.get_load_query()
|
||||
|
||||
// Explanation for parameters:
|
||||
// TRUE: We want warnings if these fail
|
||||
// FALSE: Do NOT qdel() queries here, otherwise they wont be read. At all.
|
||||
// TRUE: This is an assoc list, so it needs to prepare for that
|
||||
SSdbcore.MassExecute(load_queries, TRUE, FALSE, TRUE)
|
||||
|
||||
// Report on things
|
||||
var/list/log_text = list("The following players attempted to roll antag with no jobs (total infractions listed)")
|
||||
|
||||
for(var/ckey in flagged_antag_rollers)
|
||||
var/datum/antag_record/AR = records[ckey]
|
||||
AR.handle_data(load_queries[ckey])
|
||||
save_queries[ckey] = AR.get_save_query()
|
||||
|
||||
log_text += "<small>- <a href='?priv_msg=[ckey]'>[ckey]</a>: [AR.infraction_count]</small>"
|
||||
|
||||
log_text += "Investigation advised if there are a high number of infractions"
|
||||
|
||||
message_admins(log_text.Join("<br>"))
|
||||
|
||||
// Now do a ton of saves
|
||||
SSdbcore.MassExecute(save_queries, TRUE, TRUE, TRUE)
|
||||
|
||||
// And cleanup
|
||||
QDEL_LIST_ASSOC_VAL(load_queries)
|
||||
records.Cut()
|
||||
flagged_antag_rollers.Cut()
|
||||
|
||||
@@ -240,7 +240,7 @@
|
||||
|
||||
// Assemble a list of active players without jobbans.
|
||||
for(var/mob/new_player/player in GLOB.player_list)
|
||||
if(player.client && player.ready && player.has_valid_preferences())
|
||||
if(player.client && player.ready)
|
||||
if(!jobban_isbanned(player, ROLE_SYNDICATE) && !jobban_isbanned(player, roletext))
|
||||
if(player_old_enough_antag(player.client,role))
|
||||
players += player
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
// This file houses all the code for antag tracking datum serialization and deserialization
|
||||
//
|
||||
// If you are going to modify ANYTHING in here, please test it THOROUGHLY
|
||||
// The serialization and deserialization here is so complicated that you WILL break something here
|
||||
// PLEASE test things properly if you modify this file. -aa07
|
||||
//
|
||||
//////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// All these defines are integral to the workings and mesh together with the database.
|
||||
// DO NOT EDIT THESE UNDER ANY CIRCUMSTANCES EVER
|
||||
#define ANTAGRECORD_FIRST_INFRACTION "First infraction: "
|
||||
#define ANTAGRECORD_LAST_INFRACTION "Last infraction: "
|
||||
#define ANTAGRECORD_TOTAL_INFRACTIONS "Total infractions: "
|
||||
|
||||
/datum/antag_record
|
||||
var/holder_ckey
|
||||
var/first_infraction_date
|
||||
var/last_infraction_date
|
||||
var/infraction_count
|
||||
var/has_note = FALSE
|
||||
|
||||
/datum/antag_record/New(_holder_ckey)
|
||||
holder_ckey = _holder_ckey
|
||||
|
||||
/datum/antag_record/proc/handle_data(datum/db_query/Q)
|
||||
var/notetxt = null
|
||||
|
||||
while(Q.NextRow())
|
||||
notetxt = Q.item[1]
|
||||
|
||||
if(notetxt)
|
||||
has_note = TRUE
|
||||
deserialize_and_load(notetxt)
|
||||
infraction_count++
|
||||
else
|
||||
// Sane defaults
|
||||
first_infraction_date = SQLtime()
|
||||
infraction_count = 1
|
||||
|
||||
last_infraction_date = SQLtime()
|
||||
|
||||
// Seems pointless but it allows code to flow smoother
|
||||
// Also I grew too dependant on languages that have nice syntax stuff
|
||||
/datum/antag_record/proc/get_load_query()
|
||||
return SSdbcore.NewQuery("SELECT notetext FROM notes WHERE ckey=:ckey AND adminckey=:ackey LIMIT 1", list(
|
||||
"ckey" = holder_ckey,
|
||||
"ackey" = ANTAGTRACKING_PSUEDO_CKEY
|
||||
))
|
||||
|
||||
/*
|
||||
Expected output below. These are parsed from raw_text by splitting by <br>
|
||||
[1] ANTAGRECORD_FIRST_INFRACTION
|
||||
[2] ANTAGRECORD_LAST_INFRACTION
|
||||
[3] ANTAGRECORD_TOTAL_INFRACTIONS
|
||||
*/
|
||||
|
||||
/datum/antag_record/proc/deserialize_and_load(raw_text)
|
||||
var/list/lines = splittext(raw_text, "<br>")
|
||||
// Text
|
||||
first_infraction_date = splittext(lines[1], ANTAGRECORD_FIRST_INFRACTION)[2]
|
||||
last_infraction_date = splittext(lines[2], ANTAGRECORD_LAST_INFRACTION)[2]
|
||||
// Number
|
||||
infraction_count = text2num(splittext(lines[3], ANTAGRECORD_TOTAL_INFRACTIONS)[2]) // Make sure its a number
|
||||
|
||||
/datum/antag_record/proc/get_save_query()
|
||||
var/serialized_text
|
||||
var/list/serialized_list = list()
|
||||
serialized_list.len = 3 // Make it 3 off the bat
|
||||
|
||||
serialized_list[1] = "[ANTAGRECORD_FIRST_INFRACTION][first_infraction_date]"
|
||||
serialized_list[2] = "[ANTAGRECORD_LAST_INFRACTION][last_infraction_date]"
|
||||
serialized_list[3] = "[ANTAGRECORD_TOTAL_INFRACTIONS][infraction_count]"
|
||||
|
||||
serialized_text = serialized_list.Join("<br>")
|
||||
|
||||
if(has_note) // They have a note. Update.
|
||||
var/datum/db_query/update_existing_note = SSdbcore.NewQuery("UPDATE notes SET notetext=:nt, timestamp=NOW(), round_id=:rid WHERE ckey=:ckey AND adminckey=:ackey", list(
|
||||
"nt" = serialized_text,
|
||||
"rid" = GLOB.round_id,
|
||||
"ckey" = holder_ckey,
|
||||
"ackey" = ANTAGTRACKING_PSUEDO_CKEY
|
||||
))
|
||||
|
||||
return update_existing_note
|
||||
else
|
||||
// They dont have a note. Make an insert query.
|
||||
// To the person who asks "why dont you juse use add_note()"
|
||||
// By making a query batch, we can use async magic to make it go faster
|
||||
var/datum/db_query/query_noteadd = SSdbcore.NewQuery({"
|
||||
INSERT INTO notes (ckey, timestamp, notetext, adminckey, server, round_id, automated)
|
||||
VALUES (:targetckey, NOW(), :notetext, :adminkey, :server, :roundid, 1)
|
||||
"}, list(
|
||||
"targetckey" = holder_ckey,
|
||||
"notetext" = serialized_text,
|
||||
"adminkey" = ANTAGTRACKING_PSUEDO_CKEY,
|
||||
"server" = GLOB.configuration.system.instance_id,
|
||||
"roundid" = GLOB.round_id,
|
||||
"automated" = TRUE
|
||||
))
|
||||
|
||||
return query_noteadd
|
||||
@@ -730,28 +730,6 @@ GLOBAL_LIST_INIT(intents, list(INTENT_HELP,INTENT_DISARM,INTENT_GRAB,INTENT_HARM
|
||||
// Cast to 1/0
|
||||
return !!(client.prefs.toggles & toggleflag)
|
||||
|
||||
// Used to make sure that a player has a valid job preference setup, used to knock players out of eligibility for anything if their prefs don't make sense.
|
||||
// A "valid job preference setup" in this situation means at least having one job set to low, or not having "return to lobby" enabled
|
||||
// Prevents "antag rolling" by setting antag prefs on, all jobs to never, and "return to lobby if preferences not availible"
|
||||
// Doing so would previously allow you to roll for antag, then send you back to lobby if you didn't get an antag role
|
||||
// This also does some admin notification and logging as well
|
||||
/mob/proc/has_valid_preferences()
|
||||
if(!client)
|
||||
return FALSE //Not sure how this would get run without the mob having a client, but let's just be safe.
|
||||
if(client.prefs.active_character.alternate_option != RETURN_TO_LOBBY)
|
||||
return TRUE
|
||||
// If they have antags enabled, they're potentially doing this on purpose instead of by accident. Notify admins if so.
|
||||
var/has_antags = FALSE
|
||||
if(client.prefs.be_special.len > 0)
|
||||
has_antags = TRUE
|
||||
if(!client.prefs.active_character.check_any_job())
|
||||
to_chat(src, "<span class='danger'>You have no jobs enabled, along with return to lobby if job is unavailable. This makes you ineligible for any round start role, please update your job preferences.</span>")
|
||||
if(has_antags)
|
||||
log_admin("[src.ckey] just got booted back to lobby with no jobs, but antags enabled.")
|
||||
message_admins("[src.ckey] just got booted back to lobby with no jobs enabled, but antag rolling enabled. Likely antag rolling abuse.")
|
||||
return FALSE //This is the only case someone should actually be completely blocked from antag rolling as well
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Helper proc to determine if a mob can use emotes that make sound or not.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user