diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm
index f0e668df7e..a58f813ef4 100644
--- a/code/__DEFINES/jobs.dm
+++ b/code/__DEFINES/jobs.dm
@@ -56,6 +56,7 @@
#define JOB_UNAVAILABLE_JOB_COOLDOWN 11
#define JOB_UNAVAILABLE_SLOTFULL 12
#define JOB_UNAVAILABLE_VIRTUESVICE 13
+#define JOB_UNAVAILABLE_AGEVET 14
#define DEFAULT_RELIGION "Christianity"
#define DEFAULT_DEITY "Space Jesus"
@@ -235,7 +236,7 @@
#define TUORO (1<<5)
#define PAFANTO (1<<6) //heavy weapon technician - melee weapon and machine gun
#define MULO (1<<7) // heavy weapon ammo bearer - stripped down soldato gear and ammo storage
-#define SERVISTO (1<<8) //support role - can probably shit meds out the wazoo
+#define SERVISTO (1<<8) //support role - can probably shit meds out the wazoo
#define CURACISTO (1<<9)
#define CAMPFOLLOWER (1<<10)
#define CONSULO (1<<11)
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index d8c1fa6988..ae84f6f853 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -440,6 +440,9 @@
/datum/config_entry/string/channel_announce_new_game_message
default = null
+/datum/config_entry/string/chat_announce_verify
+ config_entry_value = null
+
/datum/config_entry/flag/debug_admin_hrefs
/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index fb96d53166..d612e154ff 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -164,9 +164,14 @@ SUBSYSTEM_DEF(job)
if(!job.special_job_check(player))
JobDebug("FOC player did not pass special check, Player: [player], Job:[job.title]")
continue
+ if(job.agevet_req && !(player.client.ckey in GLOB.agevetted_list))
+ JobDebug("FOC player is not agevetted, Player: [player], Job: [job.title]")
+ continue
+
if(CONFIG_GET(flag/usewhitelist))
if(job.whitelist_req && (!player.client.whitelisted()))
continue
+
if(player.client.prefs.job_preferences[job.title] == level)
JobDebug("FOC pass, Player: [player], Level:[level]")
candidates += player
@@ -252,6 +257,10 @@ SUBSYSTEM_DEF(job)
JobDebug("GRJ player did not pass special check, Player: [player], Job:[job.title]")
continue
+ if(job.agevet_req && !(player.client.ckey in GLOB.agevetted_list))
+ JobDebug("GRJ player is not agevetted, Player: [player], Job: [job.title]")
+ continue
+
if(CONFIG_GET(flag/usewhitelist))
if(job.whitelist_req && (!player.client.whitelisted()))
continue
diff --git a/code/datums/sexcon/sexcon_helpers.dm b/code/datums/sexcon/sexcon_helpers.dm
index d4fb09361a..e074e4c52a 100644
--- a/code/datums/sexcon/sexcon_helpers.dm
+++ b/code/datums/sexcon/sexcon_helpers.dm
@@ -61,6 +61,16 @@
if(!user?.client?.prefs.sexable)
to_chat(user, "I don't want to touch [target]. (Your ERP preference, in the options)")
return
+ if(!user.check_agevet())
+ to_chat(user, "You're not age verified.")
+ log_combat(user, target, "tried ERP while non verified")
+ message_admins("[ADMIN_LOOKUPFLW(user)] has tried to use the ERP panel despite not being vetted.")
+ log_admin("[key_name(user)] has tried to use the ERP panel despite not being vetted.")
+ return
+ if(!target.check_agevet())
+ to_chat(user, "[target] is not age verified.")
+ log_combat(user, target, "tried ERP against non verified")
+ return
if(!target?.client?.prefs)
to_chat(user, span_warning("[target] is simply not there. I can't do this."))
log_combat(user, target, "tried ERP menu against d/ced")
@@ -180,7 +190,7 @@
if(my_demihuman || their_demihuman)
return (my_demihuman && their_demihuman)
return TRUE
-
+
/mob/living/carbon/human/proc/try_impregnate(mob/living/carbon/human/wife)
var/obj/item/organ/testicles/testes = getorganslot(ORGAN_SLOT_TESTICLES)
if(!testes)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index dccf6e1704..ce1941a814 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -90,6 +90,16 @@
patron = initial(living.patron.name)
body += "
Current Patron: [patron]"
+ var/idstatus = "
ID Status: "
+ if(!M.ckey)
+ idstatus += "No key!"
+ else if(!M.check_agevet())
+ idstatus += "Unverified"
+ else
+ var/vetadmin = LAZYACCESS(GLOB.agevetted_list, M.ckey)
+ idstatus += "Age Verified by [vetadmin]"
+ body += idstatus
+
//Azure port. Incompatibility.
/*var/curse_string = ""
if(ishuman(M))
@@ -328,7 +338,7 @@
if(!check_rights())
return
-
+
if(!M.ckey)
to_chat(src, span_warning("There is no ckey attached to this mob."))
return
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index bfab60f4e4..5e207aaa04 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -38,6 +38,7 @@ GLOBAL_PROTECT(admin_verbs_default)
/client/proc/admin_spread_effect,
/client/proc/open_bounty_menu,
/client/proc/remove_bounty,
+ /client/proc/agevet_player,
// RATWOOD MODULAR START
/client/proc/bunker_bypass,
// RATWOOD MODULAR END
@@ -887,7 +888,7 @@ GLOBAL_PROTECT(admin_verbs_hideable)
scom_announce("An unknown force has erased the bounty on [target_name]. The gods are displeased.")
message_admins("[ADMIN_LOOKUPFLW(src)] has removed the bounty on [ADMIN_LOOKUPFLW(target_name)]")
return
- to_chat(src, "Error. Bounty no longer active.")
+ to_chat(src, "Error. Bounty no longer active.")
/client/proc/enable_browser_debug()
set category = "Debug"
diff --git a/code/modules/admin/agevetting.dm b/code/modules/admin/agevetting.dm
new file mode 100644
index 0000000000..4e041b6c30
--- /dev/null
+++ b/code/modules/admin/agevetting.dm
@@ -0,0 +1,83 @@
+// This is almost entirely a copy paste of the Ratwood bunker system repurposed for adding in Age vetted people.
+// Agevets matter more than simple whitelist access, so the approach is a bit different.
+// We currently store age vets in an assoc list locally.
+// The keys: player ckeys, values: the admin who added them.
+
+GLOBAL_LIST_INIT(agevetted_list, load_agevets_from_file())
+GLOBAL_PROTECT(agevetted_list)
+
+/client/proc/check_agevet()
+ if(LAZYACCESS(GLOB.agevetted_list, ckey) || holder)
+ return TRUE
+ return FALSE
+
+/mob/proc/check_agevet()
+ if(client)
+ return client.check_agevet()
+ if(LAZYACCESS(GLOB.agevetted_list, ckey) || copytext(key,1,2)=="@") //aghosted people stay verified
+ return TRUE
+ return FALSE
+
+/client/proc/agevet_player()
+ set category = "-Server-"
+ set name = "BC - Add Age Vetted"
+
+ if(!check_rights())
+ return
+
+ var/selection = input("Who would you like to verify?", "CKEY", "") as text|null
+ if(selection)
+ if(alert(src, "Confirm: [selection] as being ID verified?", "Age Vetting", "Yes!", "No") == "Yes!")
+ add_agevet(selection, ckey, src) // keep the client ref to save us a duplicate list call
+
+/proc/add_agevet(target_ckey, admin_ckey = "SYSTEM", clientref)
+ if(!target_ckey || (target_ckey in GLOB.agevetted_list))
+ return
+
+ if(IsAdminAdvancedProcCall())
+ return
+
+ if(LAZYACCESS(GLOB.agevetted_list, target_ckey))
+ to_chat(clientref, span_warning("The ckey \"[target_ckey]\" has already been ID vetted."))
+ return
+
+ target_ckey = ckey(target_ckey)
+ GLOB.agevetted_list[target_ckey] = admin_ckey
+ message_admins("ID VETTING: Added [target_ckey] to the agevetted list[admin_ckey? " by [admin_ckey]":""]")
+ log_admin("ID VETTING: Added [target_ckey] to the agevetted list[admin_ckey? " by [admin_ckey]":""]")
+ save_agevets_to_file()
+ log_agevet_to_csv(target_ckey, admin_ckey)
+ if(CONFIG_GET(string/chat_announce_verify))
+ send2chat(new /datum/tgs_message_content("ID VETTING: Added [target_ckey] to the agevetted list[admin_ckey? " by [admin_ckey]":""]"), CONFIG_GET(string/chat_announce_verify))
+
+ // if they're online, notify
+ var/recipient = LAZYACCESS(GLOB.directory, target_ckey)
+ if(recipient)
+ to_chat(recipient, span_notice("Good news! You are now ID verified."))
+
+// Read/write the assoc list. Player ckey maps to vetting admin ckey.
+/proc/load_agevets_from_file()
+ var/json_file = file("data/agevets.json")
+ if(fexists(json_file))
+ var/list/json = json_decode(file2text(json_file))
+ return json
+ else
+ return list()
+
+/proc/save_agevets_to_file()
+ var/json_file = file("data/agevets.json")
+ var/list/file_data = list()
+ file_data = GLOB.agevetted_list
+ fdel(json_file)
+ WRITE_FILE(json_file,json_encode(file_data))
+
+// for more convenient host oversight and perhaps an eventual database import.
+/proc/log_agevet_to_csv(target_ckey, admin_ckey = "SYSTEM")
+ if(IsAdminAdvancedProcCall()) // sorry for using this twice
+ return
+ var/csv_file = file("data/agevets_log.csv")
+ var/current_date = time2text(world.timeofday, "YYYY-MM-DD")
+ if(!fexists(csv_file))
+ var/csv_columns = "player_ckey,admin_ckey,datestamp,rogue_round_id"
+ WRITE_FILE(csv_file,csv_columns)
+ csv_file << "[target_ckey],[admin_ckey],[current_date],[GLOB.rogue_round_id]"
diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm
index 0c63179cd0..1f09425f12 100644
--- a/code/modules/admin/player_panel.dm
+++ b/code/modules/admin/player_panel.dm
@@ -246,7 +246,9 @@
M_job = "Observer"
else
M_job = "Ghost"
-
+ var/M_vetstatus = "Unverified"
+ if(M.check_agevet())
+ M_vetstatus = "Verified"
var/M_name = html_encode(M.name)
var/M_rname = html_encode(M.real_name)
var/M_key = html_encode(M.key)
@@ -266,7 +268,7 @@
- [M_name] - [M_rname] - [M_key] ([M_job])
+ [M_name] - [M_rname] - [M_key] ([M_job]) - [M_vetstatus]
[M_name] [M_rname] [M_key] [M_job] [previous_names]
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 7e8a0d188a..af4ca41b9e 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -313,6 +313,13 @@ GLOBAL_LIST_EMPTY(chosen_names)
dat += ""
+ var/agevetted = user.check_agevet()
+ dat += "