diff --git a/code/controllers/configuration/sections/system_configuration.dm b/code/controllers/configuration/sections/system_configuration.dm
index 075f46b5771..1351da3643f 100644
--- a/code/controllers/configuration/sections/system_configuration.dm
+++ b/code/controllers/configuration/sections/system_configuration.dm
@@ -28,6 +28,8 @@
var/external_tos_handler = FALSE
/// Map datum of the map to use, overriding the defaults, and `data/next_map.txt`
var/override_map = null
+ /// Assoc list of region names and their server IPs. Used for geo-routing.
+ var/list/region_map = list()
/datum/configuration_section/system_configuration/load_data(list/data)
// Use the load wrappers here. That way the default isnt made 'null' if you comment out the config line
@@ -48,3 +50,9 @@
CONFIG_LOAD_STR(internal_ip, data["internal_ip"])
CONFIG_LOAD_STR(override_map, data["override_map"])
+
+ // Load region overrides
+ if(islist(data["regional_servers"]))
+ region_map.Cut()
+ for(var/list/kvset in data["regional_servers"])
+ region_map[kvset["name"]] = kvset["ip"]
diff --git a/code/game/world.dm b/code/game/world.dm
index 16bcfea989c..a62f7395050 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -159,8 +159,13 @@ GLOBAL_LIST_EMPTY(world_topic_handlers)
// Send the reboot banner to all players
for(var/client/C in GLOB.clients)
C << output(list2params(list(secs_before_auto_reconnect)), "browseroutput:reboot")
- if(GLOB.configuration.url.server_url) // If you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
- C << link("byond://[GLOB.configuration.url.server_url]")
+ if(C.prefs.server_region)
+ // Keep them on the same relay
+ C << link(GLOB.configuration.system.region_map[C.prefs.server_region])
+ else
+ // Use the default
+ if(GLOB.configuration.url.server_url) // If you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite
+ C << link("byond://[GLOB.configuration.url.server_url]")
// And begin the real shutdown
rustg_log_close_all() // Past this point, no logging procs can be used, at risk of data loss.
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index 81583716b9a..20adff91cd8 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -417,6 +417,9 @@
if(karmaholder)
karmaholder.processRefunds(mob)
+ // Tell client about their connection
+ to_chat(src, "You are currently connected [prefs.server_region ? "via the [prefs.server_region] relay" : "directly"] to Paradise.")
+ to_chat(src, "You can change this using the Change Region verb in the OOC tab, as selecting a region closer to you may reduce latency.")
/client/proc/is_connecting_from_localhost()
var/static/list/localhost_addresses = list("127.0.0.1", "::1")
@@ -1196,6 +1199,36 @@
popup.set_content(output)
popup.open(FALSE)
+/client/verb/change_region()
+ set category = "OOC"
+ set name = "Change Region"
+
+ if(!length(GLOB.configuration.system.region_map))
+ to_chat(usr, "Error: No extra regions defined for this server")
+ return
+
+ var/list/target_regions = list("--DIRECT--")
+ for(var/region in GLOB.configuration.system.region_map)
+ target_regions += region
+
+ var/formatted_existing = (prefs.server_region ? prefs.server_region : "--DIRECT--")
+
+ var/choice = input(usr, "Select a region for routing optimisation.\nSelecting --DIRECT-- will bypass routing optimisations.", "Region", formatted_existing) as null|anything in target_regions
+
+ if(!choice || (choice == formatted_existing))
+ return
+
+ prefs.server_region = choice
+ prefs.save_preferences(src)
+
+ alert(usr, "Now routing you from [formatted_existing] to [choice]. You will briefly disconnect.", "ALERT", "Ok")
+
+ if(choice == "--DIRECT--")
+ src << link("byond://[GLOB.configuration.url.server_url]")
+ else
+ src << link(GLOB.configuration.system.region_map[choice])
+
+
#undef LIMITER_SIZE
#undef CURRENT_SECOND
#undef SECOND_COUNT
diff --git a/code/modules/client/login_processing/10-load_preferences.dm b/code/modules/client/login_processing/10-load_preferences.dm
index 8794eac1b9c..8d6e506e34a 100644
--- a/code/modules/client/login_processing/10-load_preferences.dm
+++ b/code/modules/client/login_processing/10-load_preferences.dm
@@ -2,6 +2,8 @@
priority = 10
/datum/client_login_processor/load_preferences/get_query(client/C)
+ // If you ever need to remove a column from here, just replace the column name with NULL
+ // This saves you having to go around the entire codebase and fix columns
var/datum/db_query/query = SSdbcore.NewQuery({"SELECT
ooccolor,
UI_style,
@@ -24,7 +26,8 @@
screentip_color,
ghost_darkness_level,
colourblind_mode,
- keybindings
+ keybindings,
+ server_region
FROM player
WHERE ckey=:ckey"}, list(
"ckey" = C.ckey
diff --git a/code/modules/client/preference/preferences.dm b/code/modules/client/preference/preferences.dm
index 94e84c318f9..b9fdb805cb2 100644
--- a/code/modules/client/preference/preferences.dm
+++ b/code/modules/client/preference/preferences.dm
@@ -123,6 +123,8 @@ GLOBAL_LIST_INIT(special_role_times, list( //minimum age (in days) for accounts
var/list/datum/keybindings = list()
/// Keybinding overrides ("name" => ["key"...])
var/list/keybindings_overrides = null
+ /// Player's region override for routing optimisation
+ var/server_region = null
/datum/preferences/New(client/C, datum/db_query/Q) // Process our query
parent = C
diff --git a/code/modules/client/preference/preferences_mysql.dm b/code/modules/client/preference/preferences_mysql.dm
index 63b035f4273..0e9c4a6eb40 100644
--- a/code/modules/client/preference/preferences_mysql.dm
+++ b/code/modules/client/preference/preferences_mysql.dm
@@ -26,6 +26,7 @@
ghost_darkness_level = query.item[20]
colourblind_mode = query.item[21]
keybindings = init_keybindings(raw = query.item[22])
+ server_region = query.item[23]
lastchangelog_2 = lastchangelog // Clone please
@@ -48,6 +49,11 @@
screentip_color = sanitize_hexcolor(screentip_color, initial(screentip_color))
ghost_darkness_level = sanitize_integer(ghost_darkness_level, 0, 255, initial(ghost_darkness_level))
colourblind_mode = sanitize_inlist(colourblind_mode, list(COLOURBLIND_MODE_NONE, COLOURBLIND_MODE_DEUTER, COLOURBLIND_MODE_PROT, COLOURBLIND_MODE_TRIT), COLOURBLIND_MODE_NONE)
+
+ // Sanitize the region
+ if(!(server_region in GLOB.configuration.system.region_map))
+ server_region = null // This region doesnt exist anymore
+
return TRUE
/datum/preferences/proc/save_preferences(client/C)
@@ -82,7 +88,8 @@
screentip_color=:screentip_color,
ghost_darkness_level=:ghost_darkness_level,
colourblind_mode=:colourblind_mode,
- keybindings=:keybindings
+ keybindings=:keybindings,
+ server_region=:server_region
WHERE ckey=:ckey"}, list(
// OH GOD THE PARAMETERS
"ooccolour" = ooccolor,
@@ -107,6 +114,7 @@
"colourblind_mode" = colourblind_mode,
"keybindings" = json_encode(keybindings_overrides),
"ckey" = C.ckey,
+ "server_region" = server_region,
))
if(!query.warn_execute())
diff --git a/config/example/config.toml b/config/example/config.toml
index a6d9da3c8de..3ee08a85cf4 100644
--- a/config/example/config.toml
+++ b/config/example/config.toml
@@ -747,6 +747,12 @@ external_tos_handler = false
# Do not use in production.
# Comment out to disable.
#override_map = "/datum/map/test_tiny"
+# Regional servers.
+# This is not for separate DD instances per region, but if you have a geo-routing setup deployed to optimise routing for players
+# I am 99.9% certain you will never need to fill this out
+regional_servers = [
+ #{name = "UK", ip = "byond://uk.paradisestation.org:6666"},
+]
################################################################