diff --git a/code/controllers/configuration/entries/connections.dm b/code/controllers/configuration/entries/connections.dm
index acdf588e5a..6ec41590f2 100644
--- a/code/controllers/configuration/entries/connections.dm
+++ b/code/controllers/configuration/entries/connections.dm
@@ -1,6 +1,12 @@
/datum/config_entry/flag/panic_bunker // prevents people the server hasn't seen before from connecting
-/datum/config_entry/number/panic_bunker_living // living time in minutes that a player needs to pass the panic bunker
+/datum/config_entry/number/panic_bunker_living // living time in minutes that a player needs to pass the panic bunker. they pass **if they are above this amount**
+ config_entry_value = 0 // default: <= 0 meaning any playtime works. -1 to disable criteria.
+ integer = TRUE
+
+/datum/config_entry/number/panic_bunker_living_vpn
+ config_entry_value = 0 // default: <= 0 meaning anytime works. -1 to disable criteria.
+ integer = TRUE
/datum/config_entry/string/panic_bunker_message
config_entry_value = "Sorry but the server is currently not accepting connections from never before seen players."
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index 99f24107c7..950b2b5b25 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -187,3 +187,6 @@
var/list/block_parry_hinted = list()
/// moused over objects, currently capped at 7. this is awful, and should be replaced with a component to track it using signals for parrying at some point.
var/list/moused_over_objects = list()
+
+ /// AFK tracking
+ var/last_activity = 0
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 735fdb592e..0ab198fa79 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -85,6 +85,8 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
return
+ last_activity = world.time
+
//Logs all hrefs
log_href("[src] (usr:[usr]\[[COORD(usr)]\]) : [hsrc ? "[hsrc] " : ""][href]")
@@ -227,6 +229,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
///////////
/client/New(TopicData)
+ last_activity = world.time
world.SetConfig("APP/admin", ckey, "role=admin")
var/tdata = TopicData //save this for later use
TopicData = null //Prevent calls to client.Topic from connect
@@ -583,9 +586,10 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
//If we aren't an admin, and the flag is set
if(CONFIG_GET(flag/panic_bunker) && !holder && !GLOB.deadmins[ckey] && !(ckey in GLOB.bunker_passthrough))
var/living_recs = CONFIG_GET(number/panic_bunker_living)
+ var/vpn_living_recs = CONFIG_GET(number/panic_bunker_living_vpn)
//Relies on pref existing, but this proc is only called after that occurs, so we're fine.
var/minutes = get_exp_living(pure_numeric = TRUE)
- if(minutes <= living_recs) // && !CONFIG_GET(flag/panic_bunker_interview)
+ if((minutes <= living_recs) || (IsVPN() && (minutes < vpn_living_recs)))
var/reject_message = "Failed Login: [key] - Account attempting to connect during panic bunker, but they do not have the required living time [minutes]/[living_recs]"
log_access(reject_message)
message_admins("[reject_message]")
@@ -855,6 +859,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
/client/Click(atom/object, atom/location, control, params, ignore_spam = FALSE, extra_info)
if(last_click > world.time - world.tick_lag)
return
+ last_activity = world.time
last_click = world.time
var/ab = FALSE
var/list/L = params2list(params)
@@ -927,6 +932,7 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
//checks if a client is afk
//3000 frames = 5 minutes
/client/proc/is_afk(duration = CONFIG_GET(number/inactivity_period))
+ var/inactivity = world.time - last_activity
if(inactivity > duration)
return inactivity
return FALSE
diff --git a/code/modules/client/client_vpn_detect.dm b/code/modules/client/client_vpn_detect.dm
new file mode 100644
index 0000000000..bd35a65ef2
--- /dev/null
+++ b/code/modules/client/client_vpn_detect.dm
@@ -0,0 +1,3 @@
+/client/proc/IsVPN()
+ var/datum/ipintel/res = get_ip_intel(address)
+ return res.intel >= CONFIG_GET(number/ipintel_rating_bad)
diff --git a/code/modules/keybindings/bindings_client.dm b/code/modules/keybindings/bindings_client.dm
index ca548c85ed..695617d2fc 100644
--- a/code/modules/keybindings/bindings_client.dm
+++ b/code/modules/keybindings/bindings_client.dm
@@ -6,6 +6,7 @@
set hidden = TRUE
client_keysend_amount += 1
+ last_activity = world.time
var/cache = client_keysend_amount
@@ -90,6 +91,7 @@
set hidden = TRUE
keys_held -= _key
+ last_activity = world.time
var/movement = movement_keys[_key]
if(!(next_move_dir_add & movement))
next_move_dir_sub |= movement
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index fb2d034e22..bc6cb75d03 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -1111,10 +1111,10 @@
return
if(!HAS_TRAIT(src, TRAIT_IGNOREDAMAGESLOWDOWN)) //if we want to ignore slowdown from damage, but not from equipment
var/scaling = maxHealth / 100
- var/health_deficiency = max(((maxHealth / scaling) - (health / scaling)), (getStaminaLoss()*0.75))
+ var/health_deficiency = max(((maxHealth / scaling) - (health / scaling)), max(0, getStaminaLoss() - 39))
if(health_deficiency >= 40)
- add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown, TRUE, (health_deficiency - 15) / 75)
- add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying, TRUE, (health_deficiency - 15) / 25)
+ add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown, TRUE, health_deficiency / 75)
+ add_or_update_variable_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying, TRUE, health_deficiency / 25)
else
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown)
remove_movespeed_modifier(/datum/movespeed_modifier/damage_slowdown_flying)
diff --git a/code/modules/mob/say.dm b/code/modules/mob/say.dm
index d3305c0c62..c622ebbd4b 100644
--- a/code/modules/mob/say.dm
+++ b/code/modules/mob/say.dm
@@ -4,6 +4,7 @@
set name = "say_indicator"
set hidden = TRUE
set category = "IC"
+ client?.last_activity = world.time
display_typing_indicator()
var/message = input(usr, "", "say") as text|null
// If they don't type anything just drop the message.
@@ -21,12 +22,16 @@
to_chat(usr, "Speech is currently admin-disabled.")
return
clear_typing_indicator() // clear it immediately!
+
+ client?.last_activity = world.time
+
say(message)
/mob/verb/me_typing_indicator()
set name = "me_indicator"
set hidden = TRUE
set category = "IC"
+ client?.last_activity = world.time
display_typing_indicator()
var/message = input(usr, "", "me") as message|null
// If they don't type anything just drop the message.
@@ -52,6 +57,8 @@
message = trim(copytext_char(sanitize(message), 1, MAX_MESSAGE_LEN))
clear_typing_indicator() // clear it immediately!
+ client?.last_activity = world.time
+
usr.emote("me",1,message,TRUE)
/mob/say_mod(input, message_mode)
@@ -73,6 +80,7 @@
return lowertext(copytext_char(input, 1, customsayverb))
/mob/proc/whisper_keybind()
+ client?.last_activity = world.time
var/message = input(src, "", "whisper") as text|null
if(!length(message))
return
@@ -89,6 +97,7 @@
whisper(message)
/mob/proc/whisper(message, datum/language/language=null)
+ client?.last_activity = world.time
say(message, language) //only living mobs actually whisper, everything else just talks
/mob/proc/say_dead(var/message)
@@ -132,6 +141,7 @@
message = emoji_parse(message)
var/rendered = "DEAD: [name][alt_name] [emoji_parse(spanned)]"
log_talk(message, LOG_SAY, tag="DEAD")
+ client?.last_activity = world.time
deadchat_broadcast(rendered, follow_target = src, speaker_key = key)
/mob/proc/check_emote(message)
diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm
index a0c39258b9..c739d69d0b 100644
--- a/code/modules/tgui/tgui.dm
+++ b/code/modules/tgui/tgui.dm
@@ -300,6 +300,7 @@
process_status()
if(src_object.ui_act(act_type, payload, src, state))
SStgui.update_uis(src_object)
+ usr?.client?.last_activity = world.time
return FALSE
switch(type)
if("ready")
diff --git a/config/entries/connections.txt b/config/entries/connections.txt
index 6351a77fcf..41df00bc40 100644
--- a/config/entries/connections.txt
+++ b/config/entries/connections.txt
@@ -2,9 +2,12 @@
## Requires database
#PANIC_BUNKER
-## If a player connects during a bunker with less then or this amount of living time (Minutes), we deny the connection
+## If a player connects during a bunker with less then or this amount of living time (Minutes), we deny the connection. Set to -1 to disable criteria.
#PANIC_BUNKER_LIVING 90
+## The above but for VPN connections. Set to -1 to disable criteria. You can set the above to disabled and set this to only enforce bunker on VPNs.
+#PANIC_BUNKER_LIVING_VPN 120
+
## The message the Panic Bunker gives when someone is rejected by it
## %minutes% is replaced with PANIC_BUNKER_LIVING on runtime, remove it if you don't want this
#PANIC_BUNKER_MESSAGE Sorry, but the server is currently not accepting connections from players with less than %minutes% minutes of living time.
diff --git a/tgstation.dme b/tgstation.dme
index 0ed28d6f8a..2bc4008796 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1984,6 +1984,7 @@
#include "code\modules\client\client_colour.dm"
#include "code\modules\client\client_defines.dm"
#include "code\modules\client\client_procs.dm"
+#include "code\modules\client\client_vpn_detect.dm"
#include "code\modules\client\message.dm"
#include "code\modules\client\player_details.dm"
#include "code\modules\client\preferences.dm"