diff --git a/SQL/paradise_schema.sql b/SQL/paradise_schema.sql
index 19521d79686..99c51c205f1 100644
--- a/SQL/paradise_schema.sql
+++ b/SQL/paradise_schema.sql
@@ -268,6 +268,7 @@ CREATE TABLE `player` (
`atklog` smallint(4) DEFAULT '0',
`fuid` BIGINT(20) NULL DEFAULT NULL,
`fupdate` SMALLINT(4) NULL DEFAULT 0,
+ `afk_watch` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `ckey` (`ckey`)
) ENGINE=InnoDB AUTO_INCREMENT=32446 DEFAULT CHARSET=latin1;
diff --git a/SQL/paradise_schema_prefixed.sql b/SQL/paradise_schema_prefixed.sql
index ff4026c7d00..c64317084d7 100644
--- a/SQL/paradise_schema_prefixed.sql
+++ b/SQL/paradise_schema_prefixed.sql
@@ -267,6 +267,7 @@ CREATE TABLE `SS13_player` (
`atklog` smallint(4) DEFAULT '0',
`fuid` BIGINT(20) NULL DEFAULT NULL,
`fupdate` SMALLINT(4) NULL DEFAULT 0,
+ `afk_watch` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `ckey` (`ckey`)
) ENGINE=InnoDB AUTO_INCREMENT=32446 DEFAULT CHARSET=latin1;
diff --git a/SQL/updates/7-8.sql b/SQL/updates/7-8.sql
new file mode 100644
index 00000000000..16bc90b7a50
--- /dev/null
+++ b/SQL/updates/7-8.sql
@@ -0,0 +1,2 @@
+# Add afk_watch which gives users the option to make use of the AFK watcher subsystem
+ALTER TABLE `player` ADD `afk_watch` tinyint(1) NOT NULL DEFAULT '0';
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index 9f2556df3a4..b491bc6e354 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -315,7 +315,7 @@
#define INVESTIGATE_BOMB "bombs"
// The SQL version required by this version of the code
-#define SQL_VERSION 7
+#define SQL_VERSION 8
// Vending machine stuff
#define CAT_NORMAL 1
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 3f155fa8031..5792aff1725 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -72,6 +72,11 @@
var/assistantlimit = 0 //enables assistant limiting
var/assistantratio = 2 //how many assistants to security members
+ // The AFK subsystem will not be activated if any of the below config values are equal or less than 0
+ var/warn_afk_minimum = 0 // How long till you get a warning while being AFK
+ var/auto_cryo_afk = 0 // How long till you get put into cryo when you're AFK
+ var/auto_despawn_afk = 0 // How long till you actually despawn in cryo when you're AFK (Not ssd so not automatic)
+
var/auto_cryo_ssd_mins = 0
var/ssd_warning = 0
@@ -314,6 +319,13 @@
if("shadowling_max_age")
config.shadowling_max_age = text2num(value)
+ if("warn_afk_minimum")
+ config.warn_afk_minimum = text2num(value)
+ if("auto_cryo_afk")
+ config.auto_cryo_afk = text2num(value)
+ if("auto_despawn_afk")
+ config.auto_despawn_afk = text2num(value)
+
if("auto_cryo_ssd_mins")
config.auto_cryo_ssd_mins = text2num(value)
if("ssd_warning")
diff --git a/code/controllers/subsystem/afk.dm b/code/controllers/subsystem/afk.dm
new file mode 100644
index 00000000000..9f409fd35b4
--- /dev/null
+++ b/code/controllers/subsystem/afk.dm
@@ -0,0 +1,81 @@
+#define AFK_WARNED 1
+#define AFK_CRYOD 2
+
+SUBSYSTEM_DEF(afk)
+ name = "AFK Watcher"
+ wait = 300
+ flags = SS_BACKGROUND
+ var/list/afk_players = list() // Associative list. ckey as key and AFK state as value
+
+
+/datum/controller/subsystem/afk/Initialize()
+ if(config.warn_afk_minimum <= 0 || config.auto_cryo_afk <= 0 || config.auto_despawn_afk <= 0)
+ flags |= SS_NO_FIRE
+
+/datum/controller/subsystem/afk/fire()
+ var/list/toRemove = list()
+ for(var/mob/living/carbon/human/H in GLOB.living_mob_list)
+ if(!H.ckey) // Useless non ckey creatures
+ continue
+
+ var/turf/T
+ // Only players and players with the AFK watch enabled
+ // No dead, unconcious, restrained, people without jobs, people on other Z levels than the station or antags
+ if(!H.client || !H.client.prefs.afk_watch || !H.mind || \
+ H.stat || H.restrained() || !H.job || H.mind.special_role || \
+ !is_station_level((T = get_turf(H)).z)) // Assign the turf as last. Small optimization
+ if(afk_players[H.ckey])
+ toRemove += H.ckey
+ continue
+
+ var/mins_afk = round(H.client.inactivity / 600)
+ if(mins_afk < config.warn_afk_minimum)
+ if(afk_players[H.ckey])
+ toRemove += H.ckey
+ continue
+
+ if(!afk_players[H.ckey])
+ afk_players[H.ckey] = AFK_WARNED
+ warn(H, "You are AFK for [mins_afk] minutes. You will be cryod after [config.auto_cryo_afk] total minutes and fully despawned after [config.auto_despawn_afk] total minutes. Please move or click in game if you want to avoid being despawned.")
+ else
+ var/area/A = T.loc // Turfs loc is the area
+ if(afk_players[H.ckey] == AFK_WARNED)
+ if(mins_afk >= config.auto_cryo_afk && A.can_get_auto_cryod)
+ if(A.fast_despawn)
+ toRemove += H.ckey
+ warn(H, "You are have been despawned after being AFK for [mins_afk] minutes. You have been despawned instantly due to you being in a secure area.")
+ msg_admins(H, mins_afk, T, "forcefully despawned", "AFK in a fast despawn area")
+ force_cryo_human(H)
+ else if(cryo_ssd(H))
+ afk_players[H.ckey] = AFK_CRYOD
+ msg_admins(H, mins_afk, T, "put into cryostorage")
+ warn(H, "You are AFK for [mins_afk] minutes and have been moved to cryostorage. After being AFK for [config.auto_despawn_afk] total minutes you will be fully despawned. Please eject yourself (right click, eject) out of the cryostorage if you want to avoid being despawned.")
+
+ else if(mins_afk >= config.auto_despawn_afk)
+ var/obj/machinery/cryopod/P = H.loc
+ msg_admins(H, mins_afk, T, "forcefully despawned")
+ warn(H, "You are have been despawned after being AFK for [mins_afk] minutes.")
+ toRemove += H.ckey
+ P.despawn_occupant()
+
+ removeFromWatchList(toRemove)
+
+/datum/controller/subsystem/afk/proc/warn(mob/living/carbon/human/H, text)
+ to_chat(H, text)
+ SEND_SOUND(H, 'sound/effects/adminhelp.ogg')
+ if(H.client)
+ window_flash(H.client)
+
+/datum/controller/subsystem/afk/proc/msg_admins(mob/living/carbon/human/H, mins_afk, turf/location, action, info)
+ log_admin("[key_name(H)] has been [action] by the AFK Watcher subsystem after being AFK for [mins_afk] minutes.[info ? " Extra info:" + info : ""]")
+ message_admins("[key_name_admin(H)] at ([get_area(location).name] [ADMIN_JMP(location)]) has been [action] by the AFK Watcher subsystem after being AFK for [mins_afk] minutes.[info ? " Extra info:" + info : ""]")
+
+/datum/controller/subsystem/afk/proc/removeFromWatchList(list/toRemove)
+ for(var/C in toRemove)
+ for(var/i in 1 to afk_players.len)
+ if(afk_players[i] == C)
+ afk_players.Cut(i, i + 1)
+ break
+
+#undef AFK_WARNED
+#undef AFK_CRYOD
\ No newline at end of file
diff --git a/code/controllers/subsystem/input.dm b/code/controllers/subsystem/input.dm
index fa46e50f779..f379deec72b 100644
--- a/code/controllers/subsystem/input.dm
+++ b/code/controllers/subsystem/input.dm
@@ -33,25 +33,22 @@ SUBSYSTEM_DEF(input)
"default" = list(
"Tab" = "\".winset \\\"input.focus=true?map.focus=true input.background-color=[COLOR_INPUT_DISABLED]:input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
"O" = "ooc",
- "T" = "say",
- "M" = "me",
- "F3" = "say",
+ "T" = ".say",
+ "M" = ".me",
"Back" = "\".winset \\\"input.focus=true input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
),
"old_default" = list(
"Tab" = "\".winset \\\"mainwindow.macro=old_hotkeys map.focus=true input.background-color=[COLOR_INPUT_DISABLED]\\\"\"",
- "Ctrl+T" = "say",
+ "Ctrl+T" = ".say",
"Ctrl+O" = "ooc",
- "F3" = "say",
),
"old_hotkeys" = list(
"Tab" = "\".winset \\\"mainwindow.macro=old_default input.focus=true input.background-color=[COLOR_INPUT_ENABLED]\\\"\"",
"O" = "ooc",
- "T" = "say",
- "M" = "me",
- "F3" = "say",
+ "T" = ".say",
+ "M" = ".me",
"Back" = "\".winset \\\"input.focus=true input.text=\\\"\\\"\\\"\"", // This makes it so backspace can remove default inputs
"Any" = "\"KeyDown \[\[*\]\]\"",
"Any+UP" = "\"KeyUp \[\[*\]\]\"",
@@ -118,6 +115,8 @@ SUBSYSTEM_DEF(input)
/datum/controller/subsystem/input/fire()
var/list/clients = GLOB.clients // Let's sing the list cache song
+ if(listclearnulls(clients)) // clear nulls before we run keyloop
+ log_world("Found a null in clients list!")
for(var/i in 1 to clients.len)
var/client/C = clients[i]
C.keyLoop()
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index 91af56437cf..7fe8696b325 100644
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -1400,10 +1400,12 @@ var/list/ghostteleportlocs = list()
name = "\improper Prison Wing"
icon_state = "sec_prison_perma"
fast_despawn = TRUE
+ can_get_auto_cryod = FALSE
/area/security/prison
name = "\improper Prison Wing"
icon_state = "sec_prison"
+ can_get_auto_cryod = FALSE
/area/security/prison/prison_break()
for(var/obj/structure/closet/secure_closet/brig/temp_closet in src)
@@ -1432,14 +1434,17 @@ var/list/ghostteleportlocs = list()
/area/security/execution
name = "\improper Execution"
icon_state = "execution"
+ can_get_auto_cryod = FALSE
/area/security/processing
name = "\improper Prisoner Processing"
icon_state = "prisonerprocessing"
+ can_get_auto_cryod = FALSE
/area/security/interrogation
name = "\improper Interrogation"
icon_state = "interrogation"
+ can_get_auto_cryod = FALSE
/area/security/seceqstorage
name = "\improper Security Equipment Storage"
@@ -1456,6 +1461,7 @@ var/list/ghostteleportlocs = list()
/area/security/interrogationobs
name = "\improper Interrogation Observation"
icon_state = "security"
+ can_get_auto_cryod = FALSE
/area/security/evidence
name = "\improper Evidence Room"
@@ -1464,6 +1470,7 @@ var/list/ghostteleportlocs = list()
/area/security/prisonlockers
name = "\improper Prisoner Lockers"
icon_state = "sec_prison_lockers"
+ can_get_auto_cryod = FALSE
/area/security/medbay
name = "\improper Security Medbay"
@@ -1472,6 +1479,7 @@ var/list/ghostteleportlocs = list()
/area/security/prisonershuttle
name = "\improper Security Prisoner Shuttle"
icon_state = "security"
+ can_get_auto_cryod = FALSE
/area/security/warden
name = "\improper Warden's Office"
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 638fe29ab6d..486dbdc8b55 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -64,6 +64,7 @@
'sound/ambience/ambigen12.ogg','sound/ambience/ambigen14.ogg')
var/fast_despawn = FALSE
+ var/can_get_auto_cryod = TRUE
/area/Initialize(mapload)
GLOB.all_areas += src
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index c52df8c7912..757f036c691 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -361,7 +361,7 @@
fingerprintshidden += text("\[[time_stamp()]\] (Wearing gloves). Real name: [], Key: []",H.real_name, H.key)
fingerprintslast = H.ckey
return 0
- if(!( fingerprints ))
+ if(!fingerprints)
if(fingerprintslast != H.ckey)
//Add the list if it does not exist.
if(!fingerprintshidden)
diff --git a/code/game/gamemodes/nuclear/pinpointer.dm b/code/game/gamemodes/nuclear/pinpointer.dm
index bfc30780b81..07ee2219157 100644
--- a/code/game/gamemodes/nuclear/pinpointer.dm
+++ b/code/game/gamemodes/nuclear/pinpointer.dm
@@ -165,10 +165,13 @@
var/targetitem = input("Select item to search for.", "Item Mode Select","") as null|anything in item_names
if(!targetitem)
return
- var/list/obj/item/target_candidates = get_all_of_type(item_paths[targetitem], subtypes = TRUE)
- for(var/obj/item/candidate in target_candidates)
- if(!is_admin_level(candidate.loc.z))
- target = candidate
+
+ var/list/target_candidates = get_all_of_type(item_paths[targetitem], subtypes = TRUE)
+ for(var/obj/item/candidate in target_candidates)
+ if(!is_admin_level((get_turf(candidate)).z))
+ target = candidate
+ break
+
if(!target)
to_chat(usr, "Failed to locate [targetitem]!")
return
diff --git a/code/game/gamemodes/vampire/vampire_powers.dm b/code/game/gamemodes/vampire/vampire_powers.dm
index 5eb4b3f0242..de956b9e7cb 100644
--- a/code/game/gamemodes/vampire/vampire_powers.dm
+++ b/code/game/gamemodes/vampire/vampire_powers.dm
@@ -261,8 +261,10 @@
for(var/mob/living/carbon/C in hearers(4))
if(C == user)
continue
- if(ishuman(C) && (C:l_ear || C:r_ear) && istype((C:l_ear || C:r_ear), /obj/item/clothing/ears/earmuffs))
- continue
+ if(ishuman(C))
+ var/mob/living/carbon/human/H = C
+ if(istype(H.l_ear, /obj/item/clothing/ears/earmuffs) || istype(H.r_ear, /obj/item/clothing/ears/earmuffs))
+ continue
if(!affects(C))
continue
to_chat(C, "You hear a ear piercing shriek and your senses dull!")
diff --git a/code/modules/admin/IsBanned.dm b/code/modules/admin/IsBanned.dm
index 9245f5174ce..045819c365b 100644
--- a/code/modules/admin/IsBanned.dm
+++ b/code/modules/admin/IsBanned.dm
@@ -108,7 +108,7 @@ world/IsBanned(key, address, computer_id, check_ipintel = TRUE)
var/appealmessage = ""
if(config.banappeals)
appealmessage = " You may appeal it at [config.banappeals]."
- expires = " This is a permanent ban.[appealmessage]"
+ expires = " This ban does not expire automatically and must be appealed.[appealmessage]"
var/desc = "\nReason: You, or another user of this computer or connection ([pckey]) is banned from playing here. The ban reason is:\n[reason]\nThis ban was applied by [ackey] on [bantime].[expires]"
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index ffa33df033f..85d835651e3 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -1015,14 +1015,14 @@
return
AddBan(M.ckey, M.computer_id, reason, usr.ckey, 0, 0, M.lastKnownIP)
to_chat(M, "You have been banned by [usr.client.ckey].\nReason: [reason].")
- to_chat(M, "This is a permanent ban.")
+ to_chat(M, "This ban does not expire automatically and must be appealed.")
if(config.banappeals)
to_chat(M, "To try to resolve this matter head to [config.banappeals]")
else
to_chat(M, "No ban appeals URL has been set.")
- ban_unban_log_save("[usr.client.ckey] has permabanned [M.ckey]. - Reason: [reason] - This is a permanent ban.")
- log_admin("[key_name(usr)] has banned [M.ckey].\nReason: [reason]\nThis is a permanent ban.")
- message_admins("[key_name_admin(usr)] has banned [M.ckey].\nReason: [reason]\nThis is a permanent ban.")
+ ban_unban_log_save("[usr.client.ckey] has permabanned [M.ckey]. - Reason: [reason] - This ban does not expire automatically and must be appealed.")
+ log_admin("[key_name(usr)] has banned [M.ckey].\nReason: [reason]\nThis ban does not expire automatically and must be appealed.")
+ message_admins("[key_name_admin(usr)] has banned [M.ckey].\nReason: [reason]\nThis ban does not expire automatically and must be appealed.")
feedback_inc("ban_perma",1)
DB_ban_record(BANTYPE_PERMA, M, -1, reason)
diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm
index e09fc89736a..3c8b293ea28 100644
--- a/code/modules/client/preference/preferences.dm
+++ b/code/modules/client/preference/preferences.dm
@@ -93,6 +93,7 @@ var/global/list/special_role_times = list( //minimum age (in days) for accounts
var/clientfps = 0
var/atklog = ATKLOG_ALL
var/fuid // forum userid
+ var/afk_watch = FALSE // If the player wants to be kept track of by the AFK system
//ghostly preferences
var/ghost_anonsay = 0
@@ -439,6 +440,7 @@ var/global/list/special_role_times = list( //minimum age (in days) for accounts
dat += "
General Settings
"
if(user.client.holder)
dat += "Adminhelp sound: [(sound & SOUND_ADMINHELP)?"On":"Off"]
"
+ dat += "AFK Cryoing: [(afk_watch) ? "Yes" : "No"]
"
dat += "Ambient Occlusion: [toggles & AMBIENT_OCCLUSION ? "Enabled" : "Disabled"]
"
dat += "Attack Animations: [(show_ghostitem_attack) ? "Yes" : "No"]
"
if(unlock_content)
@@ -1977,6 +1979,9 @@ var/global/list/special_role_times = list( //minimum age (in days) for accounts
if("winflash")
windowflashing = !windowflashing
+ if("afk_watch")
+ afk_watch = !afk_watch
+
if("UIcolor")
var/UI_style_color_new = input(user, "Choose your UI color, dark colors are not recommended!", UI_style_color) as color|null
if(!UI_style_color_new) return
diff --git a/code/modules/client/preference/preferences_mysql.dm b/code/modules/client/preference/preferences_mysql.dm
index 90a4bed6f14..cdbfe2afb2f 100644
--- a/code/modules/client/preference/preferences_mysql.dm
+++ b/code/modules/client/preference/preferences_mysql.dm
@@ -19,7 +19,8 @@
exp,
clientfps,
atklog,
- fuid
+ fuid,
+ afk_watch
FROM [format_table_name("player")]
WHERE ckey='[C.ckey]'"}
)
@@ -52,6 +53,7 @@
clientfps = text2num(query.item[17])
atklog = text2num(query.item[18])
fuid = text2num(query.item[19])
+ afk_watch = text2num(query.item[20])
//Sanitize
ooccolor = sanitize_hexcolor(ooccolor, initial(ooccolor))
@@ -72,6 +74,7 @@
clientfps = sanitize_integer(clientfps, 0, 1000, initial(clientfps))
atklog = sanitize_integer(atklog, 0, 100, initial(atklog))
fuid = sanitize_integer(fuid, 0, 10000000, initial(fuid))
+ afk_watch = sanitize_integer(afk_watch, 0, 1, initial(afk_watch))
return 1
/datum/preferences/proc/save_preferences(client/C)
@@ -90,7 +93,7 @@
UI_style_alpha='[UI_style_alpha]',
be_role='[sanitizeSQL(list2params(be_special))]',
default_slot='[default_slot]',
- toggles='[num2text(toggles, 7)]',
+ toggles='[num2text(toggles, Ceiling(log(10, (TOGGLES_TOTAL))))]',
atklog='[atklog]',
sound='[sound]',
randomslot='[randomslot]',
@@ -101,7 +104,8 @@
windowflashing='[windowflashing]',
ghost_anonsay='[ghost_anonsay]',
clientfps='[clientfps]',
- atklog='[atklog]'
+ atklog='[atklog]',
+ afk_watch='[afk_watch]'
WHERE ckey='[C.ckey]'"}
)
diff --git a/code/modules/food_and_drinks/drinks/drinks/bottle.dm b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
index 8a88cac3461..cd50dcedb71 100644
--- a/code/modules/food_and_drinks/drinks/drinks/bottle.dm
+++ b/code/modules/food_and_drinks/drinks/drinks/bottle.dm
@@ -256,6 +256,7 @@
desc = "Full of vitamins and deliciousness!"
icon_state = "orangejuice"
item_state = "carton"
+ throwforce = 0
isGlass = 0
list_reagents = list("orangejuice" = 100)
@@ -264,6 +265,7 @@
desc = "It's cream. Made from milk. What else did you think you'd find in there?"
icon_state = "cream"
item_state = "carton"
+ throwforce = 0
isGlass = 0
list_reagents = list("cream" = 100)
@@ -272,6 +274,7 @@
desc = "Well, at least it LOOKS like tomato juice. You can't tell with all that redness."
icon_state = "tomatojuice"
item_state = "carton"
+ throwforce = 0
isGlass = 0
list_reagents = list("tomatojuice" = 100)
@@ -280,6 +283,7 @@
desc = "Sweet-sour goodness."
icon_state = "limejuice"
item_state = "carton"
+ throwforce = 0
isGlass = 0
list_reagents = list("limejuice" = 100)
@@ -288,6 +292,7 @@
desc = "Soothing milk."
icon_state = "milk"
item_state = "carton"
+ throwforce = 0
isGlass = 0
list_reagents = list("milk" = 100)
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index 75c30f232b5..e3925aa5283 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -22,8 +22,11 @@
if("F2") // Screenshot. Hold shift to choose a name and location to save in
ooc()
return
+ if("F3")
+ mob.say_wrapper()
+ return
if("F4")
- mob.me_verb()
+ mob.me_wrapper()
return
if("F12") // Toggles minimal HUD
mob.button_pressed_F12()
diff --git a/code/modules/keybindings/bindings_robot.dm b/code/modules/keybindings/bindings_robot.dm
index a6b3cd3f21e..f9b39dc7351 100644
--- a/code/modules/keybindings/bindings_robot.dm
+++ b/code/modules/keybindings/bindings_robot.dm
@@ -10,6 +10,11 @@
cycle_modules()
return
if("Q")
- uneq_active()
- return
+ if(!(client.prefs.toggles & AZERTY))
+ uneq_active()
+ return
+ if("A")
+ if(client.prefs.toggles & AZERTY)
+ uneq_active()
+ return
return ..()
\ No newline at end of file
diff --git a/code/modules/keybindings/setup.dm b/code/modules/keybindings/setup.dm
index ad87622b709..cc76256386e 100644
--- a/code/modules/keybindings/setup.dm
+++ b/code/modules/keybindings/setup.dm
@@ -43,4 +43,4 @@
var/key = macro_set[k]
var/command = macro_set[key]
winset(src, "[setname]-\ref[key]", "parent=[setname];name=[key];command=[command]")
- winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=default")
+ winset(src, null, "input.focus=true input.background-color=[COLOR_INPUT_ENABLED] mainwindow.macro=old_default")
diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm
index c0e8badf35d..0c40ca76ea6 100644
--- a/code/modules/mob/living/carbon/human/emote.dm
+++ b/code/modules/mob/living/carbon/human/emote.dm
@@ -671,9 +671,9 @@
if(!restrained())
var/t1 = round(text2num(param))
if(isnum(t1))
- if(t1 <= 5 && (!r_hand || !l_hand))
+ if(t1 <= 5 && t1 >= 1 && (!r_hand || !l_hand))
message = "[src] raises [t1] finger\s."
- else if(t1 <= 10 && (!r_hand && !l_hand))
+ else if(t1 <= 10 && t1 >= 1 && (!r_hand && !l_hand))
message = "[src] raises [t1] finger\s."
m_type = 1
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 80d644152f4..17fd47d560b 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -634,7 +634,7 @@ var/list/ai_verbs_default = list(
return
if(href_list["trackbot"])
- var/mob/living/simple_animal/bot/target = locate(href_list["trackbot"]) in GLOB.simple_animals
+ var/mob/living/simple_animal/bot/target = locate(href_list["trackbot"]) in GLOB.bots_list
if(target)
ai_actual_track(target)
else
@@ -642,7 +642,7 @@ var/list/ai_verbs_default = list(
return
if(href_list["callbot"]) //Command a bot to move to a selected location.
- Bot = locate(href_list["callbot"]) in GLOB.simple_animals
+ Bot = locate(href_list["callbot"]) in GLOB.bots_list
if(!Bot || Bot.remote_disabled || control_disabled)
return //True if there is no bot found, the bot is manually emagged, or the AI is carded with wireless off.
waypoint_mode = 1
@@ -650,7 +650,7 @@ var/list/ai_verbs_default = list(
return
if(href_list["interface"]) //Remotely connect to a bot!
- Bot = locate(href_list["interface"]) in GLOB.simple_animals
+ Bot = locate(href_list["interface"]) in GLOB.bots_list
if(!Bot || Bot.remote_disabled || control_disabled)
return
Bot.attack_ai(src)
@@ -746,7 +746,7 @@ var/list/ai_verbs_default = list(
d += "Query network status
"
d += "Name | Status | Location | Control |
"
- for(var/mob/living/simple_animal/bot/Bot in GLOB.simple_animals)
+ for(var/mob/living/simple_animal/bot/Bot in GLOB.bots_list)
if(is_ai_allowed(Bot.z) && !Bot.remote_disabled) //Only non-emagged bots on the allowed Z-level are detected!
bot_area = get_area(Bot)
d += "| [Bot.hacked ? "(!) [Bot.name]" : Bot.name] ([Bot.model]) | "
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 0c6ddf80fc6..131477d086e 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1456,4 +1456,4 @@ var/list/robot_verbs_default = list(
SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT)
sync_lighting_plane_alpha()
-
\ No newline at end of file
+
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm b/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm
index 100225e0760..e34a5e0cb08 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm
@@ -21,6 +21,7 @@
maxHealth = 50
health = 50
pixel_x = -16
+ see_in_dark = 8
harm_intent_damage = 8
melee_damage_lower = 15
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 4e486baba20..02cb948c5c9 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -693,9 +693,13 @@ var/list/slot_equipment_priority = list( \
msg = copytext(msg, 1, MAX_MESSAGE_LEN)
msg = sanitize_simple(html_encode(msg), list("\n" = "
"))
-
- if(mind)
+
+ var/combined = length(memory + msg)
+ if(mind && (combined < MAX_PAPER_MESSAGE_LEN))
mind.store_memory(msg)
+ else if(combined >= MAX_PAPER_MESSAGE_LEN)
+ to_chat(src, "Your brain can't hold that much information!")
+ return
else
to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
@@ -1366,5 +1370,19 @@ var/list/slot_equipment_priority = list( \
/mob/proc/sync_lighting_plane_alpha()
if(hud_used)
var/obj/screen/plane_master/lighting/L = hud_used.plane_masters["[LIGHTING_PLANE]"]
- if (L)
+ if(L)
L.alpha = lighting_alpha
+
+ sync_nightvision_screen() //Sync up the overlay used for nightvision to the amount of see_in_dark a mob has. This needs to be called everywhere sync_lighting_plane_alpha() is.
+
+/mob/proc/sync_nightvision_screen()
+ var/obj/screen/fullscreen/see_through_darkness/S = screens["see_through_darkness"]
+ if(S)
+ var/suffix = ""
+ switch(see_in_dark)
+ if(3 to 8)
+ suffix = "_[see_in_dark]"
+ if(8 to INFINITY)
+ suffix = "_8"
+
+ S.icon_state = "[initial(S.icon_state)][suffix]"
diff --git a/code/modules/reagents/reagent_containers/glass_containers.dm b/code/modules/reagents/reagent_containers/glass_containers.dm
index 17d68f7e607..1d6f46bf46c 100644
--- a/code/modules/reagents/reagent_containers/glass_containers.dm
+++ b/code/modules/reagents/reagent_containers/glass_containers.dm
@@ -73,7 +73,7 @@
if(user.a_intent == INTENT_HARM)
M.visible_message("[user] splashes the contents of [src] onto [M]!", \
"[user] splashes the contents of [src] onto [M]!")
- add_attack_logs(M, user, "Splashed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
+ add_attack_logs(user, M, "Splashed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
if(!iscarbon(user))
M.LAssailant = null
else
@@ -90,11 +90,12 @@
if(!reagents || !reagents.total_volume)
return // The drink might be empty after the delay, such as by spam-feeding
M.visible_message("[user] feeds something to [M].", "[user] feeds something to you.")
- add_attack_logs(M, user, "Fed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
+ add_attack_logs(user, M, "Fed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
else
to_chat(user, "You swallow a gulp of [src].")
- reagents.reaction(M, INGEST)
+ var/fraction = min(5 / reagents.total_volume, 1)
+ reagents.reaction(M, INGEST, fraction)
addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, M, 5), 5)
playsound(M.loc,'sound/items/drink.ogg', rand(10,50), 1)
else
diff --git a/config/example/config.txt b/config/example/config.txt
index 87ed0ce5cf1..89b8767feab 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -85,6 +85,16 @@ LOG_WORLD_OUTPUT
## log admin warning messages
LOG_ADMINWARN
+
+## Amount of minutes that a person has to be AFK before he will be warned by the AFK subsystem. Leave this 0 to prevent the subsystem from activating
+WARN_AFK_MINIMUM 0
+
+## Amount of minutes that a person has to be AFK before he will be cryod by the AFK subsystem. Leave this 0 to prevent the subsystem from activating
+AUTO_CRYO_AFK 0
+
+## Amount of minutes that a person has to be AFK before he will be despawned by the AFK subsystem. Leave this 0 to prevent the subsystem from activating
+AUTO_DESPAWN_AFK 0
+
## probablities for game modes chosen in "secret" and "random" modes
##
## default probablity is 1, increase to make that mode more likely to be picked
diff --git a/config/example/dbconfig.txt b/config/example/dbconfig.txt
index fc1bced9d6a..2404119d52f 100644
--- a/config/example/dbconfig.txt
+++ b/config/example/dbconfig.txt
@@ -9,7 +9,7 @@
## This value must be set to the version of the paradise schema in use.
## If this value does not match, the SQL database will not be loaded and an error will be generated.
## Roundstart will be delayed.
-DB_VERSION 7
+DB_VERSION 8
## Server the MySQL database can be found at.
# Examples: localhost, 200.135.5.43, www.mysqldb.com, etc.
diff --git a/html/changelogs/AutoChangeLog-pr-11484.yml b/html/changelogs/AutoChangeLog-pr-11484.yml
new file mode 100644
index 00000000000..a6237397de8
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11484.yml
@@ -0,0 +1,4 @@
+author: "KasparoVy"
+delete-after: True
+changes:
+ - rscadd: "Re-adds the ability to see in the dark. Adds overlays for each level of darksight (>=8,7,6,5,4,3,<=2)."
diff --git a/html/changelogs/AutoChangeLog-pr-11732.yml b/html/changelogs/AutoChangeLog-pr-11732.yml
new file mode 100644
index 00000000000..8f44fc99dae
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11732.yml
@@ -0,0 +1,4 @@
+author: "farie82"
+delete-after: True
+changes:
+ - rscadd: "Adds the AFK auto cryo system. By default it won't affect players unless they activate it themselves by setting the preference in their game preferences tab."
diff --git a/html/changelogs/AutoChangeLog-pr-11825.yml b/html/changelogs/AutoChangeLog-pr-11825.yml
new file mode 100644
index 00000000000..ca3de2da45c
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11825.yml
@@ -0,0 +1,4 @@
+author: "Ty-Omaha"
+delete-after: True
+changes:
+ - tweak: "Reworded permanent bans to non-expiring bans."
diff --git a/html/changelogs/AutoChangeLog-pr-11836.yml b/html/changelogs/AutoChangeLog-pr-11836.yml
new file mode 100644
index 00000000000..24758efc273
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11836.yml
@@ -0,0 +1,4 @@
+author: "TheSardele"
+delete-after: True
+changes:
+ - tweak: "Lowers throwforce of drinking cartons from 15 to 0"
diff --git a/html/changelogs/AutoChangeLog-pr-11851.yml b/html/changelogs/AutoChangeLog-pr-11851.yml
new file mode 100644
index 00000000000..47a8eb82c03
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11851.yml
@@ -0,0 +1,4 @@
+author: "Allfd"
+delete-after: True
+changes:
+ - tweak: "Panthers can now see in the dark."
diff --git a/html/changelogs/AutoChangeLog-pr-11852.yml b/html/changelogs/AutoChangeLog-pr-11852.yml
new file mode 100644
index 00000000000..5d94998f148
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11852.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "fixes the runtime caused by running keyloop for clients"
diff --git a/html/changelogs/AutoChangeLog-pr-11856.yml b/html/changelogs/AutoChangeLog-pr-11856.yml
new file mode 100644
index 00000000000..cefc38d19db
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11856.yml
@@ -0,0 +1,5 @@
+author: "Markolie"
+delete-after: True
+changes:
+ - bugfix: "Drinking from a beaker now only applies the effect of five units of the ingested chemical, instead of the entire volume of the beaker."
+ - bugfix: "Resolved an issue where beaker attack logs were reversed."
diff --git a/html/changelogs/AutoChangeLog-pr-11861.yml b/html/changelogs/AutoChangeLog-pr-11861.yml
new file mode 100644
index 00000000000..c430f657cbf
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11861.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "Preferences not saving properly"
diff --git a/html/changelogs/AutoChangeLog-pr-11867.yml b/html/changelogs/AutoChangeLog-pr-11867.yml
new file mode 100644
index 00000000000..f18b03fc944
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11867.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - rscadd: "Restore hotkey mode"
diff --git a/html/changelogs/AutoChangeLog-pr-11872.yml b/html/changelogs/AutoChangeLog-pr-11872.yml
new file mode 100644
index 00000000000..066c6bb27ea
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11872.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "Q no longer drops items as a cyborg on AZERTY mode"
diff --git a/html/changelogs/AutoChangeLog-pr-11873.yml b/html/changelogs/AutoChangeLog-pr-11873.yml
new file mode 100644
index 00000000000..40626a2525b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11873.yml
@@ -0,0 +1,4 @@
+author: "SteelSlayer"
+delete-after: True
+changes:
+ - bugfix: "The AI's robot control window now allows you to see and interact with available bots again"
diff --git a/html/changelogs/AutoChangeLog-pr-11886.yml b/html/changelogs/AutoChangeLog-pr-11886.yml
new file mode 100644
index 00000000000..e0d2c5fe116
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11886.yml
@@ -0,0 +1,4 @@
+author: "TheSardele"
+delete-after: True
+changes:
+ - bugfix: "It is no longer possible to raise zero to -infinity fingers using the *signal emote"
diff --git a/html/changelogs/AutoChangeLog-pr-11920.yml b/html/changelogs/AutoChangeLog-pr-11920.yml
new file mode 100644
index 00000000000..224cb3173f7
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11920.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "typing indicators show up again"
diff --git a/html/changelogs/AutoChangeLog-pr-11933.yml b/html/changelogs/AutoChangeLog-pr-11933.yml
new file mode 100644
index 00000000000..29692d6a1d7
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11933.yml
@@ -0,0 +1,4 @@
+author: "and DominikPanic"
+delete-after: True
+changes:
+ - bugfix: "Limits IC notes"
diff --git a/html/changelogs/AutoChangeLog-pr-11935.yml b/html/changelogs/AutoChangeLog-pr-11935.yml
new file mode 100644
index 00000000000..2c480fcd02f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11935.yml
@@ -0,0 +1,4 @@
+author: "Ty-Omaha"
+delete-after: True
+changes:
+ - tweak: "Gave plastic surgery to line 364 of atoms.dm"
diff --git a/html/changelogs/AutoChangeLog-pr-11938.yml b/html/changelogs/AutoChangeLog-pr-11938.yml
new file mode 100644
index 00000000000..80b8fe04530
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11938.yml
@@ -0,0 +1,4 @@
+author: "farie82"
+delete-after: True
+changes:
+ - bugfix: "The syndicate can't use meta warfare no more. Advanced pinpointers no longer crash the server"
diff --git a/html/changelogs/AutoChangeLog-pr-11946.yml b/html/changelogs/AutoChangeLog-pr-11946.yml
new file mode 100644
index 00000000000..980f1fa2c52
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11946.yml
@@ -0,0 +1,4 @@
+author: "TheSardele"
+delete-after: True
+changes:
+ - bugfix: "Earmuffs now properly protect you from vampire screeches no matter what you are wearing on your other ear"
diff --git a/icons/mob/screen_full.dmi b/icons/mob/screen_full.dmi
index 595b870a172..d1aa736b352 100644
Binary files a/icons/mob/screen_full.dmi and b/icons/mob/screen_full.dmi differ
diff --git a/paradise.dme b/paradise.dme
index aa90c6b58b7..270d843845c 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -198,6 +198,7 @@
#include "code\controllers\master.dm"
#include "code\controllers\subsystem.dm"
#include "code\controllers\verbs.dm"
+#include "code\controllers\subsystem\afk.dm"
#include "code\controllers\subsystem\air.dm"
#include "code\controllers\subsystem\alarm.dm"
#include "code\controllers\subsystem\assets.dm"