diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 96335a3c1acf..47aa0e732c76 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -146,8 +146,9 @@
#define SS_INIT_DATABASE -27
#define SS_INIT_ENTITYMANAGER -28
#define SS_INIT_PLAYTIME -29
-#define SS_INIT_PREDSHIPS -30
-#define SS_INIT_OBJECTIVES -31
+#define SS_INIT_STICKY -30
+#define SS_INIT_PREDSHIPS -31
+#define SS_INIT_OBJECTIVES -32
#define SS_INIT_MINIMAP -34
#define SS_INIT_STATPANELS -98
#define SS_INIT_CHAT -100 //Should be last to ensure chat remains smooth during init.
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index eb1b0540fb54..83929ecf8803 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -531,6 +531,8 @@ This maintains a list of ip addresses that are able to bypass topic filtering.
/datum/config_entry/string/round_results_webhook_url
+/datum/config_entry/string/important_log_channel
+
/// InfluxDB v2 Host to connect to for sending statistics (over HTTP API)
/datum/config_entry/string/influxdb_host
/// InfluxDB v2 Bucket to send staistics to
diff --git a/code/controllers/subsystem/stickyban.dm b/code/controllers/subsystem/stickyban.dm
new file mode 100644
index 000000000000..48e934addc1a
--- /dev/null
+++ b/code/controllers/subsystem/stickyban.dm
@@ -0,0 +1,284 @@
+SUBSYSTEM_DEF(stickyban)
+ name = "Sticky Ban"
+ init_order = SS_INIT_STICKY
+ flags = SS_NO_FIRE
+
+/datum/controller/subsystem/stickyban/Initialize()
+ var/list/all_bans = world.GetConfig("ban")
+
+ for(var/existing_ban in all_bans)
+ var/list/ban_data = params2list(world.GetConfig("ban", existing_ban))
+ INVOKE_ASYNC(src, PROC_REF(import_sticky), existing_ban, ban_data)
+
+ return SS_INIT_SUCCESS
+
+/**
+ * Returns a list of [/datum/view_record/stickyban]s, or null, if no stickybans are found. All arguments are optional, but you should pass at least one if you want any results.
+ */
+/datum/controller/subsystem/stickyban/proc/check_for_sticky_ban(ckey, address, computer_id)
+ var/list/stickyban_ids = list()
+
+ for(var/datum/view_record/stickyban_matched_ckey/matched_ckey as anything in get_impacted_ckey_records(ckey))
+ stickyban_ids += matched_ckey.linked_stickyban
+
+ for(var/datum/view_record/stickyban_matched_cid/matched_cid as anything in get_impacted_cid_records(computer_id))
+ stickyban_ids += matched_cid.linked_stickyban
+
+ for(var/datum/view_record/stickyban_matched_ip/matched_ip as anything in get_impacted_ip_records(address))
+ stickyban_ids += matched_ip.linked_stickyban
+
+ if(!length(stickyban_ids))
+ return FALSE
+
+ var/list/datum/view_record/stickyban/stickies = DB_VIEW(/datum/view_record/stickyban,
+ DB_AND(
+ DB_COMP("id", DB_IN, stickyban_ids),
+ DB_COMP("active", DB_EQUALS, TRUE)
+ )
+ )
+
+ for(var/datum/view_record/stickyban/current_sticky in stickies)
+ if(length(get_whitelisted_ckey_records(current_sticky.id, ckey)))
+ stickies -= current_sticky
+
+ if(!length(stickies))
+ return FALSE
+
+ return stickies
+
+/**
+ * Associates an existing stickyban with a new match, either of a ckey, address, or computer_id. Or all three.
+ *
+ * Arguments:
+ * - existing_ban_id, int, required
+ * - ckey, string, optional
+ * - address, string, optional
+ * - computer_id, string, optional
+ */
+/datum/controller/subsystem/stickyban/proc/match_sticky(existing_ban_id, ckey, address, computer_id)
+ if(!existing_ban_id)
+ return
+
+ if(ckey)
+ add_matched_ckey(existing_ban_id, ckey)
+
+ if(address)
+ add_matched_ip(existing_ban_id, address)
+
+ if(computer_id)
+ add_matched_cid(existing_ban_id, computer_id)
+
+/**
+ * Adds a new tracked stickyban, and returns a [/datum/entity/stickyban] if it was successful. Blocking, sleeps.
+ */
+/datum/controller/subsystem/stickyban/proc/add_stickyban(identifier, reason, message, datum/entity/player/banning_admin, override_date)
+ var/datum/entity/stickyban/new_sticky = DB_ENTITY(/datum/entity/stickyban)
+ new_sticky.identifier = identifier
+ new_sticky.reason = reason
+ new_sticky.message = message
+
+ if(banning_admin)
+ new_sticky.adminid = banning_admin.id
+
+ new_sticky.date = override_date ? override_date : "[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]"
+ new_sticky.save()
+ new_sticky.sync()
+
+ return new_sticky
+
+/// Adds a ckey match to the specified sticky ban.
+/datum/controller/subsystem/stickyban/proc/add_matched_ckey(existing_ban_id, key)
+ key = ckey(key)
+
+ if(length(DB_VIEW(/datum/view_record/stickyban_matched_ckey,
+ DB_AND(
+ DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id),
+ DB_COMP("ckey", DB_EQUALS, key)
+ )
+ )))
+ return
+
+ var/datum/entity/stickyban_matched_ckey/matched_ckey = DB_ENTITY(/datum/entity/stickyban_matched_ckey)
+
+ matched_ckey.ckey = key
+ matched_ckey.linked_stickyban = existing_ban_id
+
+ matched_ckey.save()
+
+/// Adds an IP match to the specified stickyban.
+/datum/controller/subsystem/stickyban/proc/add_matched_ip(existing_ban_id, ip)
+ if(length(DB_VIEW(/datum/view_record/stickyban_matched_ip,
+ DB_AND(
+ DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id),
+ DB_COMP("ip", DB_EQUALS, ip)
+ )
+ )))
+ return
+
+ var/datum/entity/stickyban_matched_ip/matched_ip = DB_ENTITY(/datum/entity/stickyban_matched_ip)
+
+ matched_ip.ip = ip
+ matched_ip.linked_stickyban = existing_ban_id
+
+ matched_ip.save()
+
+/// Adds a CID match to the specified stickyban.
+/datum/controller/subsystem/stickyban/proc/add_matched_cid(existing_ban_id, cid)
+ if(length(DB_VIEW(/datum/view_record/stickyban_matched_cid,
+ DB_AND(
+ DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id),
+ DB_COMP("cid", DB_EQUALS, cid)
+ )
+ )))
+ return
+
+
+ var/datum/entity/stickyban_matched_cid/matched_cid = DB_ENTITY(/datum/entity/stickyban_matched_cid)
+
+ matched_cid.cid = cid
+ matched_cid.linked_stickyban = existing_ban_id
+
+ matched_cid.save()
+
+/// Whitelists a specific CKEY to the specified stickyban, which will allow connection, even with matching CIDs and IPs.
+/datum/controller/subsystem/stickyban/proc/whitelist_ckey(existing_ban_id, key)
+ key = ckey(key)
+
+ if(!key)
+ return
+
+ var/id_to_select
+
+ var/list/datum/view_record/stickyban_matched_ckey/existing_matches = DB_VIEW(/datum/view_record/stickyban_matched_ckey,
+ DB_AND(
+ DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id),
+ DB_COMP("ckey", DB_EQUALS, key)
+ )
+ )
+
+ if(length(existing_matches))
+ var/datum/view_record/stickyban_matched_ckey/match = existing_matches[1]
+ id_to_select = match.id
+
+ var/datum/entity/stickyban_matched_ckey/whitelisted_ckey = DB_ENTITY(/datum/entity/stickyban_matched_ckey, id_to_select)
+
+ whitelisted_ckey.ckey = key
+ whitelisted_ckey.linked_stickyban = existing_ban_id
+ whitelisted_ckey.whitelisted = TRUE
+
+ whitelisted_ckey.save()
+
+/**
+ * Returns a [/list] of [/datum/view_record/stickyban_matched_ckey] where the ckey provided has not been
+ * whitelisted from the stickyban, and would be prevented from joining - provided that the stickyban itself
+ * remains active.
+ */
+/datum/controller/subsystem/stickyban/proc/get_impacted_ckey_records(key)
+ key = ckey(key)
+
+ return DB_VIEW(/datum/view_record/stickyban_matched_ckey,
+ DB_AND(
+ DB_COMP("ckey", DB_EQUALS, key),
+ DB_COMP("whitelisted", DB_EQUALS, FALSE)
+ )
+ )
+
+/**
+ * Returns a [/list] of [/datum/view_record/stickyban_matched_ckey] which have been manually whitelisted by an admin and matches the provided existing_ban_id and key.
+ */
+/datum/controller/subsystem/stickyban/proc/get_whitelisted_ckey_records(existing_ban_id, key)
+ key = ckey(key)
+
+ return DB_VIEW(/datum/view_record/stickyban_matched_ckey,
+ DB_AND(
+ DB_COMP("linked_stickyban", DB_EQUALS, existing_ban_id),
+ DB_COMP("ckey", DB_EQUALS, key),
+ DB_COMP("whitelisted", DB_EQUALS, TRUE),
+ )
+ )
+
+/**
+ * Returns a [/list] of [/datum/view_record/stickyban_matched_cid] where the impacted CID matches the CID provided.
+ * Connections matching this CID will be blocked - provided the linked stickyban is active.
+ */
+/datum/controller/subsystem/stickyban/proc/get_impacted_cid_records(cid)
+ return DB_VIEW(/datum/view_record/stickyban_matched_cid,
+ DB_COMP("cid", DB_EQUALS, cid)
+ )
+
+/**
+ * Returns a [/list] of [/datum/view_record/stickyban_matched_ip] where the impacted IP matches the IP provided.
+ * Connections matchin this IP will be blocked - provided the linked stickyban is active.
+ */
+/datum/controller/subsystem/stickyban/proc/get_impacted_ip_records(ip)
+ return DB_VIEW(/datum/view_record/stickyban_matched_ip,
+ DB_COMP("ip", DB_EQUALS, ip)
+ )
+
+/// Legacy import from pager bans to database bans.
+/datum/controller/subsystem/stickyban/proc/import_sticky(identifier, list/ban_data)
+ WAIT_DB_READY
+
+ if(ban_data["type"] != "sticky")
+ handle_old_perma(identifier, ban_data)
+ return
+
+ if(!ban_data["message"])
+ ban_data["message"] = "Evasion"
+
+ add_stickyban(identifier, ban_data["reason"], ban_data["message"], override_date = "LEGACY")
+
+/**
+ * We abuse the on_insert from ndatabase here to ensure we have the synced ID of the new stickyban when applying a *lot* of associated bans. If we don't have a matching pager ban with the new sticky's identifier, we stop.
+ */
+/datum/entity_meta/stickyban/on_insert(datum/entity/stickyban/new_sticky)
+ var/list/ban_data = params2list(world.GetConfig("ban", new_sticky.identifier))
+
+ if(!length(ban_data))
+ return
+
+ var/list/whitelisted = list()
+ if(ban_data["whitelist"])
+ whitelisted = splittext(ban_data["whitelist"], ",")
+ for(var/key in whitelisted)
+ SSstickyban.whitelist_ckey(new_sticky.id, key)
+
+ if(ban_data["keys"])
+ var/list/keys = splittext(ban_data["keys"], ",")
+ keys -= whitelisted
+ for(var/key in keys)
+ SSstickyban.add_matched_ckey(new_sticky.id, key)
+
+ if(ban_data["computer_id"])
+ var/list/cids = splittext(ban_data["computer_id"], ",")
+ for(var/cid in cids)
+ SSstickyban.add_matched_cid(new_sticky.id, cid)
+
+ if(ban_data["IP"])
+ var/list/ips = splittext(ban_data["IP"], ",")
+ for(var/ip in ips)
+ SSstickyban.add_matched_ip(new_sticky.id, ip)
+
+ world.SetConfig("ban", new_sticky.identifier, null)
+
+/// Imports permabans from the old ban.txt, and does *not* ban people that have been whitelisted.
+/datum/controller/subsystem/stickyban/proc/handle_old_perma(identifier, list/ban_data)
+ var/list/keys_to_ban = list()
+
+ keys_to_ban += splittext(ban_data["keys"], ",")
+
+ for(var/x in 1 to length(keys_to_ban))
+ keys_to_ban[x] = ckey(keys_to_ban[x])
+
+ var/list/keys = splittext(ban_data["whitelist"], ",")
+ for(var/key in keys)
+ keys_to_ban -= ckey(key)
+
+ for(var/key in keys_to_ban)
+ var/datum/entity/player/player_entity = get_player_from_key(key)
+ if(!player_entity)
+ continue
+
+ INVOKE_ASYNC(player_entity, TYPE_PROC_REF(/datum/entity/player, add_perma_ban), ban_data["message"])
+
+ world.SetConfig("ban", identifier, null)
diff --git a/code/datums/entities/player.dm b/code/datums/entities/player.dm
index 2973a174858f..f0b601652fe0 100644
--- a/code/datums/entities/player.dm
+++ b/code/datums/entities/player.dm
@@ -305,6 +305,36 @@ BSQL_PROTECT_DATUM(/datum/entity/player)
return TRUE
+/// Permanently bans this user, with the provided reason. The banner ([/datum/entity/player]) argument is optional, as this can be done without admin intervention.
+/datum/entity/player/proc/add_perma_ban(reason, internal_reason, datum/entity/player/banner)
+ if(is_permabanned)
+ return FALSE
+
+ is_permabanned = TRUE
+ permaban_date = "[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]"
+ permaban_reason = reason
+
+ if(banner)
+ permaban_admin_id = banner.id
+ message_admins("[key_name_admin(banner.owning_client)] has permanently banned [ckey] for '[reason]'.")
+ var/datum/tgs_chat_embed/field/reason_embed
+ if(internal_reason)
+ reason_embed = new("Permaban Reason", internal_reason)
+ important_message_external("[banner.owning_client] has permanently banned [ckey] for '[reason]'.", "Permaban Placed", reason_embed ? list(reason_embed) : null)
+
+ add_note("Permanently banned | [reason]", FALSE, NOTE_ADMIN, TRUE)
+ if(internal_reason)
+ add_note("Internal reason: [internal_reason]", TRUE, NOTE_ADMIN)
+
+ if(owning_client)
+ to_chat_forced(owning_client, SPAN_LARGE("You have been permanently banned by [banner.ckey].\nReason: [reason]."))
+ to_chat_forced(owning_client, SPAN_LARGE("This is a permanent ban. It will not be removed."))
+ QDEL_NULL(owning_client)
+
+ save()
+
+ return TRUE
+
/datum/entity/player/proc/auto_unban()
if(!is_time_banned)
return
@@ -447,54 +477,45 @@ BSQL_PROTECT_DATUM(/datum/entity/player)
record_login_triplet(player.ckey, address, computer_id)
player_data.sync()
-/datum/entity/player/proc/check_ban(computer_id, address)
+/datum/entity/player/proc/check_ban(computer_id, address, is_telemetry)
. = list()
- var/list/linked_bans = check_for_sticky_ban(address, computer_id)
- if(islist(linked_bans))
- var/datum/view_record/stickyban_list_view/SLW = LAZYACCESS(linked_bans, 1)
- if(SLW)
- var/reason = ""
-
- if(SLW.address == address)
- reason += "IP Address Matches; "
- if(SLW.computer_id == computer_id)
- reason += "CID Matches; "
- if(SLW.ckey == ckey)
- reason += "Ckey Matches; "
-
- var/source_id = SLW.linked_stickyban
- var/source_reason = SLW.linked_reason
- var/source_ckey = SLW.linked_ckey
- if(!source_id)
- source_id = "[SLW.entry_id]"
- source_reason = SLW.reason
- source_ckey = SLW.ckey
-
- log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Stickybanned (Linked to [source_ckey]; Reason: [source_reason])")
- message_admins("Failed Login: [ckey] (IP: [last_known_ip], CID: [last_known_cid]) - Stickybanned (Linked to ckey [source_ckey]; Reason: [source_reason])")
-
- DB_FILTER(/datum/entity/player_sticky_ban,
- DB_AND(
- DB_COMP("ckey", DB_EQUALS, ckey),
- DB_COMP("address", DB_EQUALS, address),
- DB_COMP("computer_id", DB_EQUALS, computer_id)
- ), CALLBACK(src, PROC_REF(process_stickyban), address, computer_id, source_id, reason, null))
-
- .["desc"] = "\nReason: Stickybanned\nExpires: PERMANENT"
- .["reason"] = "ckey/id"
- return .
+ var/list/datum/view_record/stickyban/all_stickies = SSstickyban.check_for_sticky_ban(ckey, address, computer_id)
+
+ if(length(all_stickies))
+ var/datum/view_record/stickyban/sticky = all_stickies[1]
+
+ if(!is_telemetry)
+ log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Stickybanned (Reason: [sticky.reason])")
+ message_admins("Failed Login: [ckey] (IP: [last_known_ip], CID: [last_known_cid]) - Stickybanned (Reason: [sticky.reason])")
+
+ var/appeal
+ if(CONFIG_GET(string/banappeals))
+ appeal = "\nFor more information on your ban, or to appeal, head to [CONFIG_GET(string/banappeals)]"
+
+ .["desc"] = "\nReason: Stickybanned - [sticky.message] Identifier: [sticky.identifier]\n[appeal]"
+ .["reason"] = "ckey/id"
+
+ if(!is_telemetry)
+ SSstickyban.match_sticky(sticky.id, ckey, address, computer_id)
+ return
+
if(!is_time_banned && !is_permabanned)
return null
+
var/appeal
if(CONFIG_GET(string/banappeals))
appeal = "\nFor more information on your ban, or to appeal, head to [CONFIG_GET(string/banappeals)]"
if(is_permabanned)
- permaban_admin.sync()
- log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [permaban_reason]")
- message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [permaban_reason]")
- .["desc"] = "\nReason: [permaban_reason]\nExpires: PERMANENT\nBy: [permaban_admin.ckey][appeal]"
+ var/banner = "Host"
+ if(permaban_admin_id)
+ var/datum/view_record/players/banning_admin = locate() in DB_VIEW(/datum/view_record/players, DB_COMP("id", DB_EQUALS, permaban_admin_id))
+ banner = banning_admin.ckey
+ if(!is_telemetry)
+ log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [permaban_reason]")
+ message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [permaban_reason]")
+ .["desc"] = "\nReason: [permaban_reason]\nExpires: PERMANENT\nBy: [banner][appeal]"
.["reason"] = "ckey/id"
return .
if(is_time_banned)
@@ -509,8 +530,9 @@ BSQL_PROTECT_DATUM(/datum/entity/player)
timeleftstring = "[round(time_left / 60, 0.1)] Hours"
else
timeleftstring = "[time_left] Minutes"
- log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [time_ban_reason]")
- message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [time_ban_reason]")
+ if(!is_telemetry)
+ log_access("Failed Login: [ckey] [last_known_cid] [last_known_ip] - Banned [time_ban_reason]")
+ message_admins("Failed Login: [ckey] id:[last_known_cid] ip:[last_known_ip] - Banned [time_ban_reason]")
.["desc"] = "\nReason: [time_ban_reason]\nExpires: [timeleftstring]\nBy: [time_ban_admin.ckey][appeal]"
.["reason"] = "ckey/id"
return .
@@ -681,7 +703,6 @@ BSQL_PROTECT_DATUM(/datum/entity/player)
parent_entity = /datum/entity/player
child_entity = /datum/entity/player
child_field = "permaban_admin_id"
-
parent_name = "permabanning_admin"
/datum/view_record/players
diff --git a/code/datums/entities/player_sticky_ban.dm b/code/datums/entities/player_sticky_ban.dm
index d79befddb04e..752334e8e001 100644
--- a/code/datums/entities/player_sticky_ban.dm
+++ b/code/datums/entities/player_sticky_ban.dm
@@ -1,94 +1,133 @@
-/datum/entity/player_sticky_ban
- var/player_id
- var/admin_id
+BSQL_PROTECT_DATUM(/datum/entity/stickyban)
+
+/datum/entity/stickyban
+ var/identifier
var/reason
+ var/message
var/date
- var/ckey
- var/address
- var/computer_id
-
- var/linked_stickyban
-
-BSQL_PROTECT_DATUM(/datum/entity/player_sticky_ban)
+ var/active = TRUE
+ var/adminid
-/datum/entity_meta/player_sticky_ban
- entity_type = /datum/entity/player_sticky_ban
- table_name = "player_sticky_bans"
+/datum/entity_meta/stickyban
+ entity_type = /datum/entity/stickyban
+ table_name = "stickyban"
field_types = list(
- "player_id"=DB_FIELDTYPE_BIGINT,
- "admin_id"=DB_FIELDTYPE_BIGINT,
- "reason"=DB_FIELDTYPE_STRING_MAX,
- "date"=DB_FIELDTYPE_STRING_LARGE,
- "address"=DB_FIELDTYPE_STRING_LARGE,
- "ckey" = DB_FIELDTYPE_STRING_LARGE,
- "computer_id"=DB_FIELDTYPE_STRING_LARGE,
- "linked_stickyban"=DB_FIELDTYPE_BIGINT,
+ "identifier" = DB_FIELDTYPE_STRING_LARGE,
+ "reason" = DB_FIELDTYPE_STRING_LARGE,
+ "message" = DB_FIELDTYPE_STRING_LARGE,
+ "date" = DB_FIELDTYPE_STRING_LARGE,
+ "active" = DB_FIELDTYPE_INT,
+ "adminid" = DB_FIELDTYPE_BIGINT,
)
+/datum/view_record/stickyban
+ var/id
+ var/identifier
+ var/reason
+ var/message
+ var/date
+ var/active
+ var/admin
-/datum/entity_link/linked_sticky_bans
- parent_entity = /datum/entity/player_sticky_ban
- child_entity = /datum/entity/player_sticky_ban
- child_field = "linked_stickyban"
-
- parent_name = "linked_ban"
- child_name = "linked_bans"
-
-/datum/entity_link/player_to_player_sticky_bans
- parent_entity = /datum/entity/player
- child_entity = /datum/entity/player_sticky_ban
- child_field = "player_id"
-
- parent_name = "player"
- child_name = "stickybans"
+/datum/entity_view_meta/stickyban
+ root_record_type = /datum/entity/stickyban
+ destination_entity = /datum/view_record/stickyban
+ fields = list(
+ "id",
+ "identifier",
+ "reason",
+ "message",
+ "date",
+ "active",
+ "admin" = DB_CASE(DB_COMP("adminid", DB_ISNOT), "stickybanning_admin.ckey", DB_CONST("AdminBot"))
+ )
-/datum/entity_link/admin_to_player_sticky_bans
+/datum/entity_link/stickyban_to_banning_admin
parent_entity = /datum/entity/player
- child_entity = /datum/entity/player_sticky_ban
- child_field = "admin_id"
+ child_entity = /datum/entity/stickyban
+ child_field = "adminid"
+ parent_name = "stickybanning_admin"
- parent_name = "admin"
+/datum/entity/stickyban_matched_ckey
+ var/ckey
+ var/linked_stickyban
+ var/whitelisted = FALSE
-/datum/view_record/stickyban_list_view
- var/entry_id
- var/player_id
- var/admin_id
+/datum/entity_meta/stickyban_matched_ckey
+ entity_type = /datum/entity/stickyban_matched_ckey
+ table_name = "stickyban_matched_ckey"
+ field_types = list(
+ "ckey" = DB_FIELDTYPE_STRING_LARGE,
+ "linked_stickyban" = DB_FIELDTYPE_BIGINT,
+ "whitelisted" = DB_FIELDTYPE_INT,
+ )
- var/reason
- var/date
- var/address
- var/computer_id
+/datum/view_record/stickyban_matched_ckey
+ var/id
var/ckey
+ var/linked_stickyban
var/whitelisted
+/datum/entity_view_meta/stickyban_matched_ckey
+ root_record_type = /datum/entity/stickyban_matched_ckey
+ destination_entity = /datum/view_record/stickyban_matched_ckey
+ fields = list(
+ "id",
+ "ckey",
+ "linked_stickyban",
+ "whitelisted",
+ )
+
+
+/datum/entity/stickyban_matched_cid
+ var/cid
var/linked_stickyban
- var/linked_ckey
- var/linked_reason
- var/admin_ckey
- var/linked_admin_ckey
+/datum/entity_meta/stickyban_matched_cid
+ entity_type = /datum/entity/stickyban_matched_cid
+ table_name = "stickyban_matched_cid"
+ field_types = list(
+ "cid" = DB_FIELDTYPE_STRING_LARGE,
+ "linked_stickyban" = DB_FIELDTYPE_BIGINT,
+ )
+/datum/view_record/stickyban_matched_cid
+ var/id
+ var/cid
+ var/linked_stickyban
-/datum/entity_view_meta/stickyban_list_view
- root_record_type = /datum/entity/player_sticky_ban
- destination_entity = /datum/view_record/stickyban_list_view
+/datum/entity_view_meta/stickyban_matched_cid
+ root_record_type = /datum/entity/stickyban_matched_cid
+ destination_entity = /datum/view_record/stickyban_matched_cid
fields = list(
- "entry_id" = "id",
- "player_id",
- "admin_id",
+ "id",
+ "cid",
+ "linked_stickyban",
+ )
- "reason",
- "date",
- "address",
- "computer_id",
- "ckey" = "player.ckey",
- "whitelisted" = "player.stickyban_whitelisted",
- "linked_stickyban",
- "linked_ckey" = "linked_ban.player.ckey",
- "linked_reason" = "linked_ban.reason",
+/datum/entity/stickyban_matched_ip
+ var/ip
+ var/linked_stickyban
- "admin_ckey" = "admin.ckey",
- "linked_admin_ckey" = "linked_ban.admin.ckey"
+/datum/entity_meta/stickyban_matched_ip
+ entity_type = /datum/entity/stickyban_matched_ip
+ table_name = "stickyban_matched_ip"
+ field_types = list(
+ "ip" = DB_FIELDTYPE_STRING_LARGE,
+ "linked_stickyban" = DB_FIELDTYPE_BIGINT,
+ )
+
+/datum/view_record/stickyban_matched_ip
+ var/id
+ var/ip
+ var/linked_stickyban
+
+/datum/entity_view_meta/stickyban_matched_ip
+ root_record_type = /datum/entity/stickyban_matched_ip
+ destination_entity = /datum/view_record/stickyban_matched_ip
+ fields = list(
+ "id",
+ "ip",
+ "linked_stickyban",
)
- order_by = list("entry_id" = DB_ORDER_BY_DESC)
diff --git a/code/defines/procs/admin.dm b/code/defines/procs/admin.dm
index 1e4f02e95cc0..d48c02127ea9 100644
--- a/code/defines/procs/admin.dm
+++ b/code/defines/procs/admin.dm
@@ -1,3 +1,15 @@
-/proc/log_and_message_admins(message as text)
- log_admin("[key_name(usr)] [message]")
- message_admins("[key_name(usr)] [message]")
+/proc/important_message_external(message, title, list/datum/tgs_chat_embed/field/fields)
+ if(CONFIG_GET(string/important_log_channel))
+ var/datum/tgs_message_content/to_send = new("")
+
+ var/datum/tgs_chat_embed/structure/embed = new()
+ embed.title = title ? title : "Important Log"
+ embed.description = message
+ embed.timestamp = time2text(world.timeofday, "YYYY-MM-DD hh:mm:ss")
+ embed.colour = "#ED2939"
+ if(length(fields))
+ embed.fields = fields
+
+ to_send.embed = embed
+
+ send2chat(to_send, CONFIG_GET(string/important_log_channel))
diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm
index 4a7307b247a5..85a1028c2296 100644
--- a/code/modules/admin/IsBanned.dm
+++ b/code/modules/admin/IsBanned.dm
@@ -1,6 +1,6 @@
#ifndef OVERRIDE_BAN_SYSTEM
//Blocks an attempt to connect before even creating our client datum thing.
-/world/IsBanned(key,address,computer_id, type, real_bans_only=FALSE)
+/world/IsBanned(key,address,computer_id, type, real_bans_only=FALSE, is_telemetry = FALSE)
var/ckey = ckey(key)
// This is added siliently. Thanks to MSO for this fix. You will see it when/if we go OS
@@ -28,11 +28,7 @@
var/datum/entity/player/P = get_player_from_key(ckey)
- . = P.check_ban(computer_id, address)
- if(.)
- return .
-
- return ..() //default pager ban stuff
+ . = P.check_ban(computer_id, address, is_telemetry)
#endif
diff --git a/code/modules/admin/NewBan.dm b/code/modules/admin/NewBan.dm
index 7dca354129ff..f4394738fb2d 100644
--- a/code/modules/admin/NewBan.dm
+++ b/code/modules/admin/NewBan.dm
@@ -180,15 +180,44 @@ GLOBAL_DATUM(Banlist, /savefile)
expiry = "Removal Pending"
else
expiry = "Permaban"
- var/unban_link = "(U)"
+ var/unban_link
+ if(ban.is_permabanned)
+ unban_link = "(UP)"
+ else
+ unban_link = "(UT)"
- dat += "
[unban_link] Key: [ban.ckey] | ComputerID: [ban.last_known_cid] | IP: [ban.last_known_ip] | [expiry] | (By: [ban.admin]) | (Reason: [ban.reason]) |
"
+ dat += "[unban_link] Key: [ban.ckey] | ComputerID: [ban.last_known_cid] | IP: [ban.last_known_ip] | [expiry] | (By: [ban.admin ? ban.admin : "AdminBot"]) | (Reason: [ban.reason]) |
"
dat += ""
- var/dat_header = "
Bans: (U) = Unban"
+ var/dat_header = "
Bans: (UP) = Unban Perma (UT) = Unban Timed"
dat_header += " - Ban Listing
[dat]"
show_browser(usr, dat_header, "Unban Panel", "unbanp", "size=875x400")
+/datum/admins/proc/stickypanel()
+ var/add_sticky = "Add Sticky Ban"
+ var/find_sticky = "Find Sticky Ban"
+
+ var/data = "
Sticky Bans: [add_sticky] [find_sticky] "
+
+ var/list/datum/view_record/stickyban/stickies = DB_VIEW(/datum/view_record/stickyban,
+ DB_COMP("active", DB_EQUALS, TRUE)
+ )
+
+ for(var/datum/view_record/stickyban/current_sticky in stickies)
+ var/whitelist_link = "(WHITELIST)"
+ var/remove_sticky_link = "(REMOVE)"
+ var/add_to_sticky_link = "(ADD)"
+
+ var/impacted_ckey_link = "CKEYs"
+ var/impacted_ip_link = "IPs"
+ var/impacted_cid_link = "CIDs"
+
+ data += "[whitelist_link][remove_sticky_link][add_to_sticky_link] | Identifier: [current_sticky.identifier] | Reason: [current_sticky.reason] | Message: [current_sticky.message] | Admin: [current_sticky.admin] | View: [impacted_ckey_link][impacted_ip_link][impacted_cid_link] |
"
+
+ data += "
"
+
+ show_browser(owner, data, "Stickyban Panel", "sticky", "size=875x400")
+
//////////////////////////////////// DEBUG ////////////////////////////////////
/proc/CreateBans()
@@ -251,3 +280,48 @@ GLOBAL_DATUM(Banlist, /savefile)
if(P.is_time_banned && alert(usr, "Ban already exists. Proceed?", "Confirmation", "Yes", "No") != "Yes")
return
P.add_timed_ban(reason, mins)
+
+/client/proc/cmd_admin_do_stickyban(identifier, reason, message, list/impacted_ckeys, list/impacted_cids, list/impacted_ips)
+ if(!identifier)
+ identifier = tgui_input_text(src, "Name of the primary CKEY you are adding a stickyban to.", "BuildABan")
+ if(!identifier)
+ return
+
+ if(!message)
+ message = tgui_input_text(src, "What message should be given to the impacted users?", "BuildABan", encode = FALSE)
+ if(!message)
+ return
+
+ if(!reason)
+ reason = tgui_input_text(src, "What's the reason for the ban? This is shown internally, and not displayed in public notes and ban messages. Include as much detail as necessary.", "BuildABan", multiline = TRUE, encode = FALSE)
+ if(!reason)
+ return
+
+ if(!length(impacted_ckeys))
+ impacted_ckeys = splittext(tgui_input_text(src, "Which CKEYs should be impacted by this ban? Include the primary ckey, separated by semicolons.", "BuildABan", "player1;player2;player3"), ";")
+
+ if(!length(impacted_cids))
+ impacted_cids = splittext(tgui_input_text(src, "Which CIDs should be impacted by this ban? Separate with semicolons.", "BuildABan", "12345678;87654321"), ";")
+
+ if(!length(impacted_ips))
+ impacted_ips = splittext(tgui_input_text(src, "Which IPs should be impacted by this ban? Separate with semicolons.", "BuildABan", "1.1.1.1;8.8.8.8"), ";")
+
+ var/datum/entity/stickyban/new_sticky = SSstickyban.add_stickyban(identifier, reason, message, player_data)
+
+ if(!new_sticky)
+ to_chat(src, SPAN_ADMIN("Failed to apply stickyban."))
+ return
+
+ for(var/ckey in impacted_ckeys)
+ SSstickyban.add_matched_ckey(new_sticky.id, ckey)
+
+ for(var/cid in impacted_cids)
+ SSstickyban.add_matched_cid(new_sticky.id, cid)
+
+ for(var/ip in impacted_ips)
+ SSstickyban.add_matched_ip(new_sticky.id, ip)
+
+ log_admin("STICKYBAN: Identifier: [identifier] Reason: [reason] Message: [message] CKEYs: [english_list(impacted_ckeys)] IPs: [english_list(impacted_ips)] CIDs: [english_list(impacted_cids)]")
+ message_admins("[key_name_admin(src)] has added a new stickyban with the identifier '[identifier]'.")
+ var/datum/tgs_chat_embed/field/reason_embed = new("Stickyban Reason", reason)
+ important_message_external("[src] has added a new stickyban with the identifier '[identifier]'.", "Stickyban Placed", list(reason_embed))
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 43b06b6d80bc..f3eae1447ba0 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -96,8 +96,9 @@ GLOBAL_LIST_INIT(admin_verbs_admin, list(
))
GLOBAL_LIST_INIT(admin_verbs_ban, list(
- /client/proc/unban_panel
- // /client/proc/jobbans // Disabled temporarily due to 15-30 second lag spikes. Don't forget the comma in the line above when uncommenting this!
+ /client/proc/unban_panel,
+ /client/proc/stickyban_panel,
+ // /client/proc/jobbans // Disabled temporarily due to 15-30 second lag spikes.
))
GLOBAL_LIST_INIT(admin_verbs_sounds, list(
diff --git a/code/modules/admin/player_panel/actions/punish.dm b/code/modules/admin/player_panel/actions/punish.dm
index 576de30ae7ff..eb80632d8afe 100644
--- a/code/modules/admin/player_panel/actions/punish.dm
+++ b/code/modules/admin/player_panel/actions/punish.dm
@@ -45,6 +45,58 @@
return TRUE
+/datum/player_action/permanent_ban
+ action_tag = "permanent_ban"
+ name = "Permanent Ban"
+ permissions_required = R_BAN
+
+/datum/player_action/permanent_ban/act(client/user, mob/target, list/params)
+ var/reason = tgui_input_text(user, "What message should be given to the permabanned user?", "Permanent Ban", encode = FALSE)
+ if(!reason)
+ return
+
+ var/internal_reason = tgui_input_text(user, "What's the reason for the ban? This is shown internally, and not displayed in public notes and ban messages. Include as much detail as necessary.", "Permanent Ban", multiline = TRUE, encode = FALSE)
+ if(!internal_reason)
+ return
+
+ var/datum/entity/player/target_entity = target.client?.player_data
+ if(!target_entity)
+ target_entity = get_player_from_key(target.ckey || target.persistent_ckey)
+
+ if(!target_entity)
+ return
+
+ if(!target_entity.add_perma_ban(reason, internal_reason, user.player_data))
+ to_chat(user, SPAN_ADMIN("The user is already permabanned! If necessary, you can remove the permaban, and place a new one."))
+
+/datum/player_action/sticky_ban
+ action_tag = "sticky_ban"
+ name = "Sticky Ban"
+ permissions_required = R_BAN
+
+/datum/player_action/sticky_ban/act(client/user, mob/target, list/params)
+ var/datum/entity/player/player = get_player_from_key(target.ckey || target.persistent_ckey)
+ if(!player)
+ return
+
+ var/persistent_ip = target.client?.address || player.last_known_ip
+ var/persistent_cid = target.client?.computer_id || player.last_known_cid
+
+ var/message = tgui_input_text(user, "What message should be given to the impacted users?", "BuildABan", encode = FALSE)
+ if(!message)
+ return
+
+ var/reason = tgui_input_text(user, "What's the reason for the ban? This is shown internally, and not displayed in public notes and ban messages. Include as much detail as necessary.", "BuildABan", multiline = TRUE, encode = FALSE)
+ if(!reason)
+ return
+
+ user.cmd_admin_do_stickyban(target.ckey, reason, message, impacted_ckeys = list(target.ckey), impacted_cids = list(persistent_cid), impacted_ips = list(persistent_ip))
+ player.add_note("Stickybanned | [message]", FALSE, NOTE_ADMIN, TRUE)
+ player.add_note("Internal reason: [reason]", TRUE, NOTE_ADMIN)
+
+ if(target.client)
+ qdel(target.client)
+
/datum/player_action/mute
action_tag = "mob_mute"
name = "Mute"
diff --git a/code/modules/admin/stickyban.dm b/code/modules/admin/stickyban.dm
deleted file mode 100644
index 69793a599596..000000000000
--- a/code/modules/admin/stickyban.dm
+++ /dev/null
@@ -1,66 +0,0 @@
-// BLOCKING PROC, RUN ASYNC
-
-/proc/stickyban_internal(ckey, address, cid, reason, linked_stickyban, datum/entity/player/banning_admin)
-
- if(!ckey || !address || !cid)
- CRASH("Incorrect data passed to stickyban_internal ([ckey], [address], [cid])")
-
- var/datum/entity/player/P = get_player_from_key(ckey)
-
- if(!P)
- message_admins("Tried stickybanning ckey \"[ckey]\", player entity was unable to be found. Please try again later.")
- return
-
- var/datum/entity/player_sticky_ban/PSB = DB_ENTITY(/datum/entity/player_sticky_ban)
- PSB.player_id = P.id
- if(reason)
- PSB.reason = reason
- PSB.address = address
- PSB.computer_id = cid
- PSB.ckey = P.ckey
- PSB.date = "[time2text(world.realtime, "YYYY-MM-DD hh:mm:ss")]"
-
- if(linked_stickyban)
- PSB.linked_stickyban = linked_stickyban
-
- if(!reason)
- reason = "No reason given."
-
- if(banning_admin)
- PSB.admin_id = banning_admin.id
- if(banning_admin.owning_client)
- message_admins("[banning_admin.owning_client.ckey] has stickybanned [ckey].")
-
- message_admins("[ckey] (IP: [address], CID: [cid]) has been stickybanned for: \"[reason]\".")
-
- if(P.owning_client)
- to_chat_forced(P.owning_client, SPAN_WARNING("You have been sticky banned by [banning_admin? banning_admin.ckey : "Host"].\nReason: [sanitize(reason)]."))
- to_chat_forced(P.owning_client, SPAN_WARNING("This is a permanent ban"))
- QDEL_NULL(P.owning_client)
-
- PSB.save()
-
-/datum/entity/player/proc/process_stickyban(address, computer_id, source_id, reason, datum/entity/player/banning_admin, list/PSB)
- if(length(PSB) > 0) // sticky ban with identical data already exists, no need for another copy
- if(banning_admin)
- to_chat(banning_admin, SPAN_WARNING("Failed to add stickyban to [ckey]. Reason: Stickyban already exists."))
- return
-
- stickyban_internal(ckey, address, computer_id, reason, source_id, banning_admin)
-
-
-/datum/entity/player/proc/check_for_sticky_ban(address, computer_id)
- var/list/datum/view_record/stickyban_list_view/SBLW = DB_VIEW(/datum/view_record/stickyban_list_view,
- DB_OR(
- DB_COMP("ckey", DB_EQUALS, ckey),
- DB_COMP("address", DB_EQUALS, address),
- DB_COMP("computer_id", DB_EQUALS, computer_id)
- ))
-
- if(length(SBLW) == 0)
- return
-
- if(stickyban_whitelisted)
- return
-
- return SBLW
diff --git a/code/modules/admin/tabs/admin_tab.dm b/code/modules/admin/tabs/admin_tab.dm
index 8dce41ac8235..356762b5edd7 100644
--- a/code/modules/admin/tabs/admin_tab.dm
+++ b/code/modules/admin/tabs/admin_tab.dm
@@ -53,6 +53,12 @@
admin_holder.unbanpanel()
return
+/client/proc/stickyban_panel()
+ set name = "Stickyban Panel"
+ set category = "Admin.Panels"
+
+ admin_holder?.stickypanel()
+
/client/proc/player_panel_new()
set name = "Player Panel"
set category = "Admin.Panels"
diff --git a/code/modules/admin/topic/topic.dm b/code/modules/admin/topic/topic.dm
index 825f7c3cdbf4..ecef2627ed3c 100644
--- a/code/modules/admin/topic/topic.dm
+++ b/code/modules/admin/topic/topic.dm
@@ -229,6 +229,200 @@
alert(usr, "This ban has already been lifted / does not exist.", "Error", "Ok")
unbanpanel()
+ else if(href_list["unban_perma"])
+ var/datum/entity/player/unban_player = get_player_from_key(href_list["unban_perma"])
+ if(!(tgui_alert(owner, "Do you want to unban [unban_player.ckey]? They are currently permabanned for: [unban_player.permaban_reason], since [unban_player.permaban_date].", "Unban Player", list("Yes", "No")) == "Yes"))
+ return
+
+ if(!unban_player.is_permabanned)
+ to_chat(owner, "The player is not currently permabanned.")
+
+ unban_player.is_permabanned = FALSE
+ unban_player.permaban_admin_id = null
+ unban_player.permaban_date = null
+ unban_player.permaban_reason = null
+
+ unban_player.save()
+
+ message_admins("[key_name_admin(owner)] has removed the permanent ban on [unban_player.ckey].")
+ important_message_external("[owner] has removed the permanent ban on [unban_player.ckey].", "Permaban Removed")
+
+ else if(href_list["sticky"])
+ if(href_list["view_all_ckeys"])
+ var/list/datum/view_record/stickyban_matched_ckey/all_ckeys = DB_VIEW(/datum/view_record/stickyban_matched_ckey,
+ DB_COMP("linked_stickyban", DB_EQUALS, href_list["sticky"])
+ )
+
+ var/list/keys = list()
+ var/list/whitelisted = list()
+ for(var/datum/view_record/stickyban_matched_ckey/match as anything in all_ckeys)
+ if(match.whitelisted)
+ whitelisted += match.ckey
+ else
+ keys += match.ckey
+
+ show_browser(owner, "Impacted: [english_list(keys)]
Whitelisted: [english_list(whitelisted)]", "Stickyban Keys", "stickykeys")
+ return
+
+ if(href_list["view_all_cids"])
+ var/list/datum/view_record/stickyban_matched_cid/all_cids = DB_VIEW(/datum/view_record/stickyban_matched_cid,
+ DB_COMP("linked_stickyban", DB_EQUALS, href_list["sticky"])
+ )
+
+ var/list/cids = list()
+ for(var/datum/view_record/stickyban_matched_cid/match as anything in all_cids)
+ cids += match.cid
+
+ show_browser(owner, english_list(cids), "Stickyban CIDs", "stickycids")
+ return
+
+ if(href_list["view_all_ips"])
+ var/list/datum/view_record/stickyban_matched_ip/all_ips = DB_VIEW(/datum/view_record/stickyban_matched_ip,
+ DB_COMP("linked_stickyban", DB_EQUALS, href_list["sticky"])
+ )
+
+ var/list/ips = list()
+ for(var/datum/view_record/stickyban_matched_ip/match as anything in all_ips)
+ ips += match.ip
+
+ show_browser(owner, english_list(ips), "Stickyban IPs", "stickycips")
+ return
+
+ if(href_list["find_sticky"])
+ var/ckey = ckey(tgui_input_text(owner, "Which CKEY should we attempt to find stickybans for?", "FindABan"))
+ if(!ckey)
+ return
+
+ var/list/datum/view_record/stickyban/stickies = SSstickyban.check_for_sticky_ban(ckey)
+ if(!stickies)
+ to_chat(owner, SPAN_ADMIN("Could not locate any stickbans impacting [ckey]."))
+ return
+
+ var/list/impacting_stickies = list()
+
+ for(var/datum/view_record/stickyban/sticky as anything in stickies)
+ impacting_stickies += sticky.identifier
+
+ to_chat(owner, SPAN_ADMIN("Found the following stickybans for [ckey]: [english_list(impacting_stickies)]"))
+
+ if(!check_rights_for(owner, R_BAN))
+ return
+
+ if(href_list["new_sticky"])
+ owner.cmd_admin_do_stickyban()
+ return
+
+ var/datum/entity/stickyban/sticky = DB_ENTITY(/datum/entity/stickyban, href_list["sticky"])
+ if(!sticky)
+ return
+
+ sticky.sync()
+
+ if(href_list["whitelist_ckey"])
+ var/ckey_to_whitelist = ckey(tgui_input_text(owner, "What CKEY should be whitelisted? Editing stickyban: [sticky.identifier]"))
+ if(!ckey_to_whitelist)
+ return
+
+ SSstickyban.whitelist_ckey(sticky.id, ckey_to_whitelist)
+ message_admins("[key_name_admin(owner)] has whitelisted [ckey_to_whitelist] against stickyban '[sticky.identifier]'.")
+ important_message_external("[owner] has whitelisted [ckey_to_whitelist] against stickyban '[sticky.identifier]'.", "CKEY Whitelisted")
+
+ if(href_list["add"])
+ var/option = tgui_input_list(owner, "What do you want to add?", "AddABan", list("CID", "CKEY", "IP"))
+ if(!option)
+ return
+
+ var/to_add = tgui_input_text(owner, "Provide the [option] to add to the stickyban.", "AddABan")
+ if(!to_add)
+ return
+
+ switch(option)
+ if("CID")
+ SSstickyban.add_matched_cid(sticky.id, to_add)
+ if("CKEY")
+ SSstickyban.add_matched_ckey(sticky.id, to_add)
+ if("IP")
+ SSstickyban.add_matched_ip(sticky.id, to_add)
+
+ message_admins("[key_name_admin(owner)] has added a [option] ([to_add]) to stickyban '[sticky.identifier]'.")
+ important_message_external("[owner] has added a [option] ([to_add]) to stickyban '[sticky.identifier]'.", "[option] Added to Stickyban")
+
+ if(href_list["remove"])
+ var/option = tgui_input_list(owner, "What do you want to remove?", "DelABan", list("Entire Stickyban", "CID", "CKEY", "IP"))
+ switch(option)
+ if("Entire Stickyban")
+ if(!(tgui_alert(owner, "Are you sure you want to remove this stickyban? Identifier: [sticky.identifier] Reason: [sticky.reason]", "Confirm", list("Yes", "No")) == "Yes"))
+ return
+
+ sticky.active = FALSE
+ sticky.save()
+
+ message_admins("[key_name_admin(owner)] has deactivated stickyban '[sticky.identifier]'.")
+ important_message_external("[owner] has deactivated stickyban '[sticky.identifier]'.", "Stickyban Deactivated")
+
+ if("CID")
+ var/list/datum/view_record/stickyban_matched_cid/all_cids = DB_VIEW(/datum/view_record/stickyban_matched_cid,
+ DB_COMP("linked_stickyban", DB_EQUALS, sticky.id)
+ )
+
+ var/list/cid_to_record_id = list()
+ for(var/datum/view_record/stickyban_matched_cid/match in all_cids)
+ cid_to_record_id["[match.cid]"] = match.id
+
+ var/picked = tgui_input_list(owner, "Which CID to remove?", "DelABan", cid_to_record_id)
+ if(!picked)
+ return
+
+ var/selected = cid_to_record_id[picked]
+
+ var/datum/entity/stickyban_matched_cid/sticky_cid = DB_ENTITY(/datum/entity/stickyban_matched_cid, selected)
+ sticky_cid.delete()
+
+ message_admins("[key_name_admin(owner)] has removed a CID ([picked]) from stickyban '[sticky.identifier]'.")
+ important_message_external("[owner] has removed a CID ([picked]) from stickyban '[sticky.identifier]'.", "CID Removed from Stickyban")
+
+ if("CKEY")
+ var/list/datum/view_record/stickyban_matched_ckey/all_ckeys = DB_VIEW(/datum/view_record/stickyban_matched_ckey,
+ DB_COMP("linked_stickyban", DB_EQUALS, sticky.id)
+ )
+
+ var/list/ckey_to_record_id = list()
+ for(var/datum/view_record/stickyban_matched_ckey/match in all_ckeys)
+ ckey_to_record_id["[match.ckey]"] = match.id
+
+ var/picked = tgui_input_list(owner, "Which CKEY to remove?", "DelABan", ckey_to_record_id)
+ if(!picked)
+ return
+
+ var/selected = ckey_to_record_id[picked]
+
+ var/datum/entity/stickyban_matched_ckey/sticky_ckey = DB_ENTITY(/datum/entity/stickyban_matched_ckey, selected)
+ sticky_ckey.delete()
+
+ message_admins("[key_name_admin(owner)] has removed a CKEY ([picked]) from stickyban '[sticky.identifier]'.")
+ important_message_external("[owner] has removed a CKEY ([picked]) from stickyban '[sticky.identifier]'.", "CKEY Removed from Stickyban")
+
+ if("IP")
+ var/list/datum/view_record/stickyban_matched_ip/all_ips = DB_VIEW(/datum/view_record/stickyban_matched_ip,
+ DB_COMP("linked_stickyban", DB_EQUALS, sticky.id)
+ )
+
+ var/list/ip_to_record_id = list()
+ for(var/datum/view_record/stickyban_matched_ip/match in all_ips)
+ ip_to_record_id["[match.ip]"] = match.id
+
+ var/picked = tgui_input_list(owner, "Which IP to remove?", "DelABan", ip_to_record_id)
+ if(!picked)
+ return
+
+ var/selected = ip_to_record_id[picked]
+
+ var/datum/entity/stickyban_matched_ip/sticky_ip = DB_ENTITY(/datum/entity/stickyban_matched_ip, selected)
+ sticky_ip.delete()
+
+ message_admins("[key_name_admin(owner)] has removed an IP ([picked]) from stickyban [sticky.identifier].")
+ important_message_external("[owner] has removed an IP ([picked]) from stickyban '[sticky.identifier].", "IP Removed from Stickyban")
+
else if(href_list["warn"])
usr.client.warn(href_list["warn"])
diff --git a/code/modules/admin/verbs/autoreplace.dm b/code/modules/admin/verbs/autoreplace.dm
index b2fe04cfb4a3..a896c751f5ed 100644
--- a/code/modules/admin/verbs/autoreplace.dm
+++ b/code/modules/admin/verbs/autoreplace.dm
@@ -32,7 +32,7 @@ GLOBAL_LIST_INIT_TYPED(admin_runtime_decorators, /datum/decorator/manual/admin_r
GLOB.admin_runtime_decorators.Add(SSdecorator.add_decorator(/datum/decorator/manual/admin_runtime, types, subtypes, field, value))
- log_and_message_admins("[src] activated new decorator id: [GLOB.admin_runtime_decorators.len] set for [hint_text] `[types]` for field `[field]` set value `[value]`")
+ message_admins("[src] activated new decorator id: [GLOB.admin_runtime_decorators.len] set for [hint_text] `[types]` for field `[field]` set value `[value]`")
/client/proc/deactivate_autoreplacer()
set category = "Admin.Events"
@@ -49,7 +49,7 @@ GLOBAL_LIST_INIT_TYPED(admin_runtime_decorators, /datum/decorator/manual/admin_r
GLOB.admin_runtime_decorators[num_value].enabled = FALSE
- log_and_message_admins("[src] deactivated decorator id: [num_value]")
+ message_admins("[src] deactivated decorator id: [num_value]")
/client/proc/rerun_decorators()
set category = "Admin.Events"
@@ -65,4 +65,4 @@ GLOBAL_LIST_INIT_TYPED(admin_runtime_decorators, /datum/decorator/manual/admin_r
SSdecorator.force_update()
- log_and_message_admins("[src] rerun all decorators.")
+ message_admins("[src] rerun all decorators.")
diff --git a/code/modules/clans/client.dm b/code/modules/clans/client.dm
index c4948b2a6923..3450b7553c51 100644
--- a/code/modules/clans/client.dm
+++ b/code/modules/clans/client.dm
@@ -234,7 +234,7 @@
return
- log_and_message_admins("[key_name_admin(src)] has set the name of [target_clan.name] to [input].")
+ message_admins("[key_name_admin(src)] has set the name of [target_clan.name] to [input].")
to_chat(src, SPAN_NOTICE("Set the name of [target_clan.name] to [input]."))
target_clan.name = trim(input)
@@ -247,7 +247,7 @@
if(!input || input == target_clan.description)
return
- log_and_message_admins("[key_name_admin(src)] has set the description of [target_clan.name].")
+ message_admins("[key_name_admin(src)] has set the description of [target_clan.name].")
to_chat(src, SPAN_NOTICE("Set the description of [target_clan.name]."))
target_clan.description = trim(input)
@@ -261,7 +261,7 @@
return
target_clan.color = color
- log_and_message_admins("[key_name_admin(src)] has set the color of [target_clan.name] to [color].")
+ message_admins("[key_name_admin(src)] has set the color of [target_clan.name] to [color].")
to_chat(src, SPAN_NOTICE("Set the name of [target_clan.name] to [color]."))
if(CLAN_ACTION_CLAN_SETHONOR)
if(!has_clan_permission(CLAN_PERMISSION_ADMIN_MANAGER))
@@ -272,7 +272,7 @@
if((!input && input != 0) || input == target_clan.honor)
return
- log_and_message_admins("[key_name_admin(src)] has set the honor of clan [target_clan.name] from [target_clan.honor] to [input].")
+ message_admins("[key_name_admin(src)] has set the honor of clan [target_clan.name] from [target_clan.honor] to [input].")
to_chat(src, SPAN_NOTICE("Set the honor of [target_clan.name] from [target_clan.honor] to [input]."))
target_clan.honor = input
@@ -286,7 +286,7 @@
to_chat(src, "You have decided not to delete [target_clan.name].")
return
- log_and_message_admins("[key_name_admin(src)] has deleted the clan [target_clan.name].")
+ message_admins("[key_name_admin(src)] has deleted the clan [target_clan.name].")
to_chat(src, SPAN_NOTICE("You have deleted [target_clan.name]."))
var/list/datum/view_record/clan_playerbase_view/CPV = DB_VIEW(/datum/view_record/clan_playerbase_view, DB_COMP("clan_id", DB_EQUALS, target_clan.id))
@@ -339,7 +339,7 @@
return
var/target_clan = target.clan_id
- log_and_message_admins("[key_name_admin(src)] has purged [player_name]'s clan profile.")
+ message_admins("[key_name_admin(src)] has purged [player_name]'s clan profile.")
to_chat(src, SPAN_NOTICE("You have purged [player_name]'s clan profile."))
target.delete()
@@ -379,20 +379,20 @@
target.clan_id = null
target.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_YOUNG]
to_chat(src, SPAN_NOTICE("Removed [player_name] from their clan."))
- log_and_message_admins("[key_name_admin(src)] has removed [player_name] from their current clan.")
+ message_admins("[key_name_admin(src)] has removed [player_name] from their current clan.")
else if(input == "Remove from Ancient")
target.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_YOUNG]
target.permissions = GLOB.clan_ranks[CLAN_RANK_YOUNG].permissions
to_chat(src, SPAN_NOTICE("Removed [player_name] from ancient."))
- log_and_message_admins("[key_name_admin(src)] has removed [player_name] from ancient.")
+ message_admins("[key_name_admin(src)] has removed [player_name] from ancient.")
else if(input == "Make Ancient" && is_clan_manager)
target.clan_rank = GLOB.clan_ranks_ordered[CLAN_RANK_ADMIN]
target.permissions = CLAN_PERMISSION_ADMIN_ANCIENT
to_chat(src, SPAN_NOTICE("Made [player_name] an ancient."))
- log_and_message_admins("[key_name_admin(src)] has made [player_name] an ancient.")
+ message_admins("[key_name_admin(src)] has made [player_name] an ancient.")
else
to_chat(src, SPAN_NOTICE("Moved [player_name] to [input]."))
- log_and_message_admins("[key_name_admin(src)] has moved [player_name] to clan [input].")
+ message_admins("[key_name_admin(src)] has moved [player_name] to clan [input].")
target.clan_id = clans[input]
@@ -455,7 +455,7 @@
target.clan_rank = GLOB.clan_ranks_ordered[chosen_rank.name]
target.permissions = chosen_rank.permissions
- log_and_message_admins("[key_name_admin(src)] has set the rank of [player_name] to [chosen_rank.name] for their clan.")
+ message_admins("[key_name_admin(src)] has set the rank of [player_name] to [chosen_rank.name] for their clan.")
to_chat(src, SPAN_NOTICE("Set [player_name]'s rank to [chosen_rank.name]"))
target.save()
diff --git a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
index 2e5c6a9112a3..d89e8572caff 100644
--- a/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
+++ b/code/modules/mob/living/carbon/xenomorph/castes/Queen.dm
@@ -595,7 +595,7 @@
xeno_announcement(input, hivenumber, "The words of the [name] reverberate in our head...")
- log_and_message_admins("[key_name_admin(src)] has created a Word of the Queen report:")
+ message_admins("[key_name_admin(src)] has created a Word of the Queen report:")
log_admin("[key_name_admin(src)] Word of the Queen: [input]")
return TRUE
diff --git a/code/modules/tgui_panel/telemetry.dm b/code/modules/tgui_panel/telemetry.dm
index eb5c7d96a4d9..4ef1f06bfac0 100644
--- a/code/modules/tgui_panel/telemetry.dm
+++ b/code/modules/tgui_panel/telemetry.dm
@@ -86,7 +86,7 @@
// Check for a malformed history object
if (!row || row.len < 3 || (!row["ckey"] || !row["address"] || !row["computer_id"]))
- return
+ continue
/* TODO - Reintroduce this when we get a proper round ID tracking,
and we want to log it to database
@@ -103,7 +103,7 @@
continue
*/
- if (world.IsBanned(row["ckey"], row["address"], row["computer_id"], real_bans_only = TRUE))
+ if (world.IsBanned(row["ckey"], row["address"], row["computer_id"], real_bans_only = TRUE, is_telemetry = TRUE))
found = row
break
diff --git a/colonialmarines.dme b/colonialmarines.dme
index 09370e73b4b1..16bb16212f71 100644
--- a/colonialmarines.dme
+++ b/colonialmarines.dme
@@ -298,6 +298,7 @@
#include "code\controllers\subsystem\sound_loops.dm"
#include "code\controllers\subsystem\soundscape.dm"
#include "code\controllers\subsystem\statpanel.dm"
+#include "code\controllers\subsystem\stickyban.dm"
#include "code\controllers\subsystem\techtree.dm"
#include "code\controllers\subsystem\tgui.dm"
#include "code\controllers\subsystem\ticker.dm"
@@ -1398,7 +1399,6 @@
#include "code\modules\admin\NewBan.dm"
#include "code\modules\admin\player_notes.dm"
#include "code\modules\admin\server_verbs.dm"
-#include "code\modules\admin\stickyban.dm"
#include "code\modules\admin\STUI.dm"
#include "code\modules\admin\tag.dm"
#include "code\modules\admin\medal_panel\medals_panel.dm"
diff --git a/tgui/packages/tgui/interfaces/PlayerPanel.jsx b/tgui/packages/tgui/interfaces/PlayerPanel.jsx
index 85f73b581c67..88b30aa51802 100644
--- a/tgui/packages/tgui/interfaces/PlayerPanel.jsx
+++ b/tgui/packages/tgui/interfaces/PlayerPanel.jsx
@@ -236,7 +236,6 @@ const GeneralActions = (props) => {
/>
{
/>