diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm
index 988acd3dae..0da05e7c1d 100644
--- a/code/__DEFINES/rust_g.dm
+++ b/code/__DEFINES/rust_g.dm
@@ -38,6 +38,9 @@
#define RUST_G (__rust_g || __detect_rust_g())
#endif
+/// Gets the version of rust_g
+/proc/rustg_get_version() return call(RUST_G, "get_version")()
+
/**
* This proc generates a cellular automata noise grid which can be used in procedural generation methods.
*
@@ -77,8 +80,8 @@
#define RUSTG_HTTP_METHOD_PATCH "patch"
#define RUSTG_HTTP_METHOD_HEAD "head"
#define RUSTG_HTTP_METHOD_POST "post"
-#define rustg_http_request_blocking(method, url, body, headers) call(RUST_G, "http_request_blocking")(method, url, body, headers)
-#define rustg_http_request_async(method, url, body, headers) call(RUST_G, "http_request_async")(method, url, body, headers)
+#define rustg_http_request_blocking(method, url, body, headers, options) call(RUST_G, "http_request_blocking")(method, url, body, headers, options)
+#define rustg_http_request_async(method, url, body, headers, options) call(RUST_G, "http_request_async")(method, url, body, headers, options)
#define rustg_http_check_request(req_id) call(RUST_G, "http_check_request")(req_id)
#define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET"
@@ -99,3 +102,11 @@
#define rustg_sql_disconnect_pool(handle) call(RUST_G, "sql_disconnect_pool")(handle)
#define rustg_sql_check_query(job_id) call(RUST_G, "sql_check_query")("[job_id]")
+#define rustg_url_encode(text) call(RUST_G, "url_encode")(text)
+#define rustg_url_decode(text) call(RUST_G, "url_decode")(text)
+
+#ifdef RUSTG_OVERRIDE_BUILTINS
+ #define url_encode(text) rustg_url_encode(text)
+ #define url_decode(text) rustg_url_decode(text)
+#endif
+
diff --git a/code/__DEFINES/rust_g_overrides.dm b/code/__DEFINES/rust_g_overrides.dm
new file mode 100644
index 0000000000..57de7d96ac
--- /dev/null
+++ b/code/__DEFINES/rust_g_overrides.dm
@@ -0,0 +1,3 @@
+// RUSTG_OVERRIDE_BUILTINS is not used since the file APIs don't work well over Linux.
+#define url_encode(text) rustg_url_encode("[text]")
+#define url_decode(text) rustg_url_decode("[text]")
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index eb9bdccc22..cd94e5790b 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -343,3 +343,7 @@
#define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name
#define TRAIT_SACRIFICED "sacrificed" //Makes sure that people cant be cult sacrificed twice.
#define TRAIT_SPACEWALK "spacewalk"
+
+
+/// obtained from mapping helper
+#define MAPPING_HELPER_TRAIT "mapping-helper"
diff --git a/code/controllers/configuration/entries/admin.dm b/code/controllers/configuration/entries/admin.dm
index 3b5b437742..1f9b5d460f 100644
--- a/code/controllers/configuration/entries/admin.dm
+++ b/code/controllers/configuration/entries/admin.dm
@@ -39,7 +39,9 @@
/datum/config_entry/flag/announce_admin_login
-/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
+/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API
+
+/datum/config_entry/string/centcom_source_whitelist
/datum/config_entry/flag/autoadmin // if autoadmin is enabled
protection = CONFIG_ENTRY_LOCKED
diff --git a/code/datums/http.dm b/code/datums/http.dm
index 2a9b53f131..49b183fde6 100644
--- a/code/datums/http.dm
+++ b/code/datums/http.dm
@@ -6,10 +6,12 @@
var/body
var/headers
var/url
+ /// If present response body will be saved to this file.
+ var/output_file
var/_raw_response
-/datum/http_request/proc/prepare(method, url, body = "", list/headers)
+/datum/http_request/proc/prepare(method, url, body = "", list/headers, output_file)
if (!length(headers))
headers = ""
else
@@ -19,15 +21,16 @@
src.url = url
src.body = body
src.headers = headers
+ src.output_file = output_file
/datum/http_request/proc/execute_blocking()
- _raw_response = rustg_http_request_blocking(method, url, body, headers)
+ _raw_response = rustg_http_request_blocking(method, url, body, headers, build_options())
/datum/http_request/proc/begin_async()
if (in_progress)
CRASH("Attempted to re-use a request object.")
- id = rustg_http_request_async(method, url, body, headers)
+ id = rustg_http_request_async(method, url, body, headers, build_options())
if (isnull(text2num(id)))
stack_trace("Proc error: [id]")
@@ -35,6 +38,11 @@
else
in_progress = TRUE
+/datum/http_request/proc/build_options()
+ if(output_file)
+ return json_encode(list("output_filename"=output_file,"body_filename"=null))
+ return null
+
/datum/http_request/proc/is_complete()
if (isnull(id))
return TRUE
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index 953835bff0..4224b406b3 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -2862,7 +2862,7 @@
return
if(!CONFIG_GET(string/centcom_ban_db))
- to_chat(usr, "Centcom Galactic Ban DB is disabled!")
+ to_chat(usr, span_warning("Centcom Galactic Ban DB is disabled!"))
return
var/ckey = href_list["centcomlookup"]
@@ -2889,8 +2889,19 @@
dat += "
0 bans detected for [ckey]"
else
bans = json_decode(response["body"])
- dat += "[bans.len] ban\s detected for [ckey]"
+
+ //Ignore bans from non-whitelisted sources, if a whitelist exists
+ var/list/valid_sources
+ if(CONFIG_GET(string/centcom_source_whitelist))
+ valid_sources = splittext(CONFIG_GET(string/centcom_source_whitelist), ",")
+ dat += "Bans detected for [ckey]"
+ else
+ //Ban count is potentially inaccurate if they're using a whitelist
+ dat += "[bans.len] ban\s detected for [ckey]"
+
for(var/list/ban in bans)
+ if(valid_sources && !(ban["sourceName"] in valid_sources))
+ continue
dat += "Server: [sanitize(ban["sourceName"])]
"
dat += "RP Level: [sanitize(ban["sourceRoleplayLevel"])]
"
dat += "Type: [sanitize(ban["type"])]
"
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index e3f5264568..239246cd59 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -641,6 +641,18 @@ Since Ramadan is an entire month that lasts 29.5 days on average, the start and
/datum/holiday/easter/getStationPrefix()
return pick("Fluffy","Bunny","Easter","Egg")
+/datum/holiday/ianbirthday
+ name = "Ian's Birthday" //github.com/tgstation/tgstation/commit/de7e4f0de0d568cd6e1f0d7bcc3fd34700598acb
+ begin_month = SEPTEMBER
+ begin_day = 9
+ end_day = 10
+
+/datum/holiday/ianbirthday/greet()
+ return "Happy birthday, Ian!"
+
+/datum/holiday/ianbirthday/getStationPrefix()
+ return pick("Ian", "Corgi", "Erro")
+
//Random citadel thing for halloween species
/proc/force_enable_halloween_species()
var/list/oldlist = SSevents.holidays
diff --git a/code/modules/mapping/mapping_helpers/_mapping_helpers.dm b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm
index 4de16ba350..a96b81528f 100644
--- a/code/modules/mapping/mapping_helpers/_mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm
@@ -8,61 +8,73 @@
..()
return late ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_QDEL
+
//airlock helpers
/obj/effect/mapping_helpers/airlock
layer = DOOR_HELPER_LAYER
+/obj/effect/mapping_helpers/airlock/Initialize(mapload)
+ . = ..()
+ if(!mapload)
+ log_mapping("[src] spawned outside of mapload!")
+ return
+ var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
+ if(!airlock)
+ log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
+ else
+ payload(airlock)
+
+/obj/effect/mapping_helpers/airlock/proc/payload(obj/machinery/door/airlock/payload)
+ return
+
/obj/effect/mapping_helpers/airlock/cyclelink_helper
name = "airlock cyclelink helper"
icon_state = "airlock_cyclelink_helper"
-/obj/effect/mapping_helpers/airlock/cyclelink_helper/Initialize(mapload)
- . = ..()
- if(!mapload)
- log_mapping("[src] spawned outside of mapload!")
- return
- var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
- if(airlock)
- if(airlock.cyclelinkeddir)
- log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
- else
- airlock.cyclelinkeddir = dir
+/obj/effect/mapping_helpers/airlock/cyclelink_helper/payload(obj/machinery/door/airlock/airlock)
+ if(airlock.cyclelinkeddir)
+ log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
else
- log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
+ airlock.cyclelinkeddir = dir
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi
+ name = "airlock multi-cyclelink helper"
+ icon_state = "airlock_multicyclelink_helper"
+ var/cycle_id
+
+/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi/payload(obj/machinery/door/airlock/airlock)
+ if(airlock.closeOtherId)
+ log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] closeOtherId, but it's already set!")
+ else
+ airlock.closeOtherId = cycle_id
/obj/effect/mapping_helpers/airlock/locked
name = "airlock lock helper"
icon_state = "airlock_locked_helper"
-/obj/effect/mapping_helpers/airlock/locked/Initialize(mapload)
- . = ..()
- if(!mapload)
- log_mapping("[src] spawned outside of mapload!")
- return
- var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
- if(airlock)
- if(airlock.locked)
- log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
- else
- airlock.locked = TRUE
+/obj/effect/mapping_helpers/airlock/locked/payload(obj/machinery/door/airlock/airlock)
+ if(airlock.locked)
+ log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
else
- log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
+ airlock.locked = TRUE
+
/obj/effect/mapping_helpers/airlock/unres
name = "airlock unresctricted side helper"
icon_state = "airlock_unres_helper"
-/obj/effect/mapping_helpers/airlock/unres/Initialize(mapload)
- . = ..()
- if(!mapload)
- log_mapping("[src] spawned outside of mapload!")
- return
- var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
- if(airlock)
- airlock.unres_sides ^= dir
+/obj/effect/mapping_helpers/airlock/unres/payload(obj/machinery/door/airlock/airlock)
+ airlock.unres_sides ^= dir
+
+/obj/effect/mapping_helpers/airlock/abandoned
+ name = "airlock abandoned helper"
+ icon_state = "airlock_abandoned"
+
+/obj/effect/mapping_helpers/airlock/abandoned/payload(obj/machinery/door/airlock/airlock)
+ if(airlock.abandoned)
+ log_mapping("[src] at [AREACOORD(src)] tried to make [airlock] abandoned but it's already abandoned!")
else
- log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
+ airlock.abandoned = TRUE
//needs to do its thing before spawn_rivers() is called
@@ -79,9 +91,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
//This helper applies components to things on the map directly.
/obj/effect/mapping_helpers/component_injector
name = "Component Injector"
+ icon_state = "component"
late = TRUE
- var/target_type
- var/target_name
+ var/all = FALSE //Will inject into all fitting the criteria if true, otherwise first found
+ var/target_type //Will inject into atoms of this type
+ var/target_name //Will inject into atoms with this name
var/component_type
//Late init so everything is likely ready and loaded (no warranty)
@@ -98,8 +112,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
continue
var/cargs = build_args()
A._AddComponent(cargs)
+ if(!all)
+ qdel(src)
+ return
+ if(all)
qdel(src)
- return
/obj/effect/mapping_helpers/component_injector/proc/build_args()
return list(component_type)
@@ -115,3 +132,276 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
CRASH("Wrong disease type passed in.")
var/datum/disease/D = new disease_type()
return list(component_type,D)
+
+// /obj/effect/mapping_helpers/component_injector/areabound
+// name = "Areabound Injector"
+// icon_state = "component_areabound"
+// component_type = /datum/component/areabound
+// target_type = /atom/movable
+
+/obj/effect/mapping_helpers/dead_body_placer
+ name = "Dead Body placer"
+ late = TRUE
+ icon_state = "deadbodyplacer"
+ var/bodycount = 2 //number of bodies to spawn
+
+/obj/effect/mapping_helpers/dead_body_placer/LateInitialize()
+ var/area/a = get_area(src)
+ var/list/trays = list()
+ for (var/i in a.contents)
+ if (istype(i, /obj/structure/bodycontainer/morgue))
+ trays += i
+ if(!trays.len)
+ log_mapping("[src] at [x],[y] could not find any morgues.")
+ return
+ for (var/i = 1 to bodycount)
+ var/obj/structure/bodycontainer/morgue/j = pick(trays)
+ var/mob/living/carbon/human/h = new /mob/living/carbon/human(j, 1)
+ h.death()
+ for (var/part in h.internal_organs) //randomly remove organs from each body, set those we keep to be in stasis
+ if (prob(40))
+ qdel(part)
+ else
+ var/obj/item/organ/O = part
+ O.organ_flags |= ORGAN_FROZEN
+ j.update_appearance()
+ qdel(src)
+
+
+//On Ian's birthday, the hop's office is decorated.
+/obj/effect/mapping_helpers/ianbirthday
+ name = "Ian's Bday Helper"
+ late = TRUE
+ icon_state = "iansbdayhelper"
+ var/balloon_clusters = 2
+
+/obj/effect/mapping_helpers/ianbirthday/LateInitialize()
+ if(locate(/datum/holiday/ianbirthday) in SSevents.holidays)
+ birthday()
+ qdel(src)
+
+/obj/effect/mapping_helpers/ianbirthday/proc/birthday()
+ var/area/a = get_area(src)
+ var/list/table = list()//should only be one aka the front desk, but just in case...
+ var/list/openturfs = list()
+
+ //confetti and a corgi balloon! (and some list stuff for more decorations)
+ for(var/thing in a.contents)
+ if(istype(thing, /obj/structure/table/reinforced))
+ table += thing
+ if(isopenturf(thing))
+ // new /obj/effect/decal/cleanable/confetti(thing)
+ // if(locate(/obj/structure/bed/dogbed/ian) in thing)
+ // new /obj/item/toy/balloon/corgi(thing)
+ // else
+ openturfs += thing
+
+ //cake + knife to cut it!
+ if(length(table))
+ var/turf/food_turf = get_turf(pick(table))
+ new /obj/item/kitchen/knife(food_turf)
+ var/obj/item/reagent_containers/food/snacks/store/cake/birthday/iancake = new(food_turf)
+ iancake.desc = "Happy birthday, Ian!"
+ // remind me to give ian proper baloons!
+ //some balloons! this picks an open turf and pops a few balloons in and around that turf, yay.
+ // for(var/i in 1 to balloon_clusters)
+ // var/turf/clusterspot = pick_n_take(openturfs)
+ // new /obj/item/toy/balloon(clusterspot)
+ // var/balloons_left_to_give = 3 //the amount of balloons around the cluster
+ // var/list/dirs_to_balloon = GLOB.cardinals.Copy()
+ // while(balloons_left_to_give > 0)
+ // balloons_left_to_give--
+ // var/chosen_dir = pick_n_take(dirs_to_balloon)
+ // var/turf/balloonstep = get_step(clusterspot, chosen_dir)
+ // var/placed = FALSE
+ // if(isopenturf(balloonstep))
+ // var/obj/item/toy/balloon/B = new(balloonstep)//this clumps the cluster together
+ // placed = TRUE
+ // if(chosen_dir == NORTH)
+ // B.pixel_y -= 10
+ // if(chosen_dir == SOUTH)
+ // B.pixel_y += 10
+ // if(chosen_dir == EAST)
+ // B.pixel_x -= 10
+ // if(chosen_dir == WEST)
+ // B.pixel_x += 10
+ // if(!placed)
+ // new /obj/item/toy/balloon(clusterspot)
+ //remind me to add wall decor!
+
+/obj/effect/mapping_helpers/ianbirthday/admin//so admins may birthday any room
+ name = "generic birthday setup"
+ icon_state = "bdayhelper"
+
+/obj/effect/mapping_helpers/ianbirthday/admin/LateInitialize()
+ birthday()
+ qdel(src)
+
+//Ian, like most dogs, loves a good new years eve party.
+/obj/effect/mapping_helpers/iannewyear
+ name = "Ian's New Years Helper"
+ late = TRUE
+ icon_state = "iansnewyrshelper"
+
+/obj/effect/mapping_helpers/iannewyear/LateInitialize()
+ if(SSevents.holidays && SSevents.holidays[NEW_YEAR])
+ fireworks()
+ qdel(src)
+
+/obj/effect/mapping_helpers/iannewyear/proc/fireworks()
+ var/area/a = get_area(src)
+ var/list/table = list()//should only be one aka the front desk, but just in case...
+ var/list/openturfs = list()
+
+ for(var/thing in a.contents)
+ if(istype(thing, /obj/structure/table/reinforced))
+ table += thing
+ else if(isopenturf(thing))
+ if(locate(/obj/structure/bed/dogbed/ian) in thing)
+ new /obj/item/clothing/head/festive(thing)
+ var/obj/item/reagent_containers/food/drinks/bottle/champagne/iandrink = new(thing)
+ iandrink.name = "dog champagne"
+ iandrink.pixel_y += 8
+ iandrink.pixel_x += 8
+ else
+ openturfs += thing
+
+ var/turf/fireworks_turf = get_turf(pick(table))
+ var/obj/item/storage/box/matches/matchbox = new(fireworks_turf)
+ matchbox.pixel_y += 8
+ matchbox.pixel_x -= 3
+ // new /obj/item/storage/box/fireworks/dangerous(fireworks_turf) //dangerous version for extra holiday memes.
+
+//lets mappers place notes on airlocks with custom info or a pre-made note from a path
+/obj/effect/mapping_helpers/airlock_note_placer
+ name = "Airlock Note Placer"
+ late = TRUE
+ icon_state = "airlocknoteplacer"
+ var/note_info //for writing out custom notes without creating an extra paper subtype
+ var/note_name //custom note name
+ var/note_path //if you already have something wrote up in a paper subtype, put the path here
+
+/obj/effect/mapping_helpers/airlock_note_placer/LateInitialize()
+ var/turf/turf = get_turf(src)
+ if(note_path && !istype(note_path, /obj/item/paper)) //don't put non-paper in the paper slot thank you
+ log_mapping("[src] at [x],[y] had an improper note_path path, could not place paper note.")
+ qdel(src)
+ if(locate(/obj/machinery/door/airlock) in turf)
+ var/obj/machinery/door/airlock/found_airlock = locate(/obj/machinery/door/airlock) in turf
+ if(note_path)
+ found_airlock.note = note_path
+ found_airlock.update_appearance()
+ qdel(src)
+ if(note_info)
+ var/obj/item/paper/paper = new /obj/item/paper(src)
+ if(note_name)
+ paper.name = note_name
+ paper.info = "[note_info]"
+ found_airlock.note = paper
+ paper.forceMove(found_airlock)
+ found_airlock.update_appearance()
+ qdel(src)
+ log_mapping("[src] at [x],[y] had no note_path or note_info, cannot place paper note.")
+ qdel(src)
+ log_mapping("[src] at [x],[y] could not find an airlock on current turf, cannot place paper note.")
+ qdel(src)
+
+//This helper applies traits to things on the map directly.
+/obj/effect/mapping_helpers/trait_injector
+ name = "Trait Injector"
+ icon_state = "trait"
+ late = TRUE
+ ///Will inject into all fitting the criteria if false, otherwise first found.
+ var/first_match_only = TRUE
+ ///Will inject into atoms of this type.
+ var/target_type
+ ///Will inject into atoms with this name.
+ var/target_name
+ ///Name of the trait, in the lower-case text (NOT the upper-case define) form.
+ var/trait_name
+
+//Late init so everything is likely ready and loaded (no warranty)
+/obj/effect/mapping_helpers/trait_injector/LateInitialize()
+ if(!GLOB.trait_name_map)
+ GLOB.trait_name_map = generate_trait_name_map()
+ if(!GLOB.trait_name_map.Find(trait_name))
+ CRASH("Wrong trait in [type] - [trait_name] is not a trait")
+ var/turf/target_turf = get_turf(src)
+ var/matches_found = 0
+ for(var/a in target_turf.GetAllContents())
+ var/atom/atom_on_turf = a
+ if(atom_on_turf == src)
+ continue
+ if(target_name && atom_on_turf.name != target_name)
+ continue
+ if(target_type && !istype(atom_on_turf,target_type))
+ continue
+ ADD_TRAIT(atom_on_turf, trait_name, MAPPING_HELPER_TRAIT)
+ matches_found++
+ if(first_match_only)
+ qdel(src)
+ return
+ if(!matches_found)
+ stack_trace("Trait mapper found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name] | trait name: [trait_name]")
+ qdel(src)
+
+/// Fetches an external dmi and applies to the target object
+/obj/effect/mapping_helpers/custom_icon
+ name = "Custom Icon Helper"
+ icon_state = "trait"
+ late = TRUE
+ ///Will inject into all fitting the criteria if false, otherwise first found.
+ var/first_match_only = TRUE
+ ///Will inject into atoms of this type.
+ var/target_type
+ ///Will inject into atoms with this name.
+ var/target_name
+ /// This is the var tha will be set with the fetched icon. In case you want to set some secondary icon sheets like inhands and such.
+ var/target_variable = "icon"
+ /// This should return raw dmi in response to http get request. For example: "https://github.com/tgstation/SS13-sprites/raw/master/mob/medu.dmi?raw=true"
+ var/icon_url
+
+/obj/effect/mapping_helpers/custom_icon/LateInitialize()
+ ///TODO put this injector stuff under common root
+ var/I = fetch_icon(icon_url)
+ var/turf/target_turf = get_turf(src)
+ var/matches_found = 0
+ for(var/a in target_turf.GetAllContents())
+ var/atom/atom_on_turf = a
+ if(atom_on_turf == src)
+ continue
+ if(target_name && atom_on_turf.name != target_name)
+ continue
+ if(target_type && !istype(atom_on_turf,target_type))
+ continue
+ atom_on_turf.vars[target_variable] = I
+ matches_found++
+ if(first_match_only)
+ qdel(src)
+ return
+ if(!matches_found)
+ stack_trace("[src] found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name]")
+ qdel(src)
+
+/obj/effect/mapping_helpers/custom_icon/proc/fetch_icon(url)
+ var/static/icon_cache = list()
+ var/static/query_in_progress = FALSE //We're using a single tmp file so keep it linear.
+ if(query_in_progress)
+ UNTIL(!query_in_progress)
+ if(icon_cache[url])
+ return icon_cache[url]
+ log_asset("Custom Icon Helper fetching dmi from: [url]")
+ var/datum/http_request/request = new()
+ var/file_name = "tmp/custom_map_icon.dmi"
+ request.prepare(RUSTG_HTTP_METHOD_GET, url , "", "", file_name)
+ query_in_progress = TRUE
+ request.begin_async()
+ UNTIL(request.is_complete())
+ var/datum/http_response/response = request.into_response()
+ if(response.errored || response.status_code != 200)
+ query_in_progress = FALSE
+ CRASH("Failed to fetch mapped custom icon from url [url], code: [response.status_code], error: [response.error]")
+ var/icon/I = new(file_name)
+ icon_cache[url] = I
+ query_in_progress = FALSE
+ return I
diff --git a/code/modules/mapping/mapping_helpers/baseturf.dm b/code/modules/mapping/mapping_helpers/baseturf.dm
index f4bd0d7c7f..4d79d3dba5 100644
--- a/code/modules/mapping/mapping_helpers/baseturf.dm
+++ b/code/modules/mapping/mapping_helpers/baseturf.dm
@@ -30,8 +30,8 @@
qdel(src)
/obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing)
- var/list/baseturf_cache = thing.baseturfs
- if(length(baseturf_cache))
+ if(length(thing.baseturfs))
+ var/list/baseturf_cache = thing.baseturfs.Copy()
for(var/i in baseturf_cache)
if(baseturf_to_replace[i])
baseturf_cache -= i
@@ -44,6 +44,8 @@
else
thing.PlaceOnBottom(null, baseturf)
+
+
/obj/effect/baseturf_helper/space
name = "space baseturf editor"
baseturf = /turf/open/space
@@ -79,4 +81,3 @@
/obj/effect/baseturf_helper/lava_land/surface
name = "lavaland baseturf editor"
baseturf = /turf/open/lava/smooth/lava_land_surface
-
diff --git a/config/entries/admin.txt b/config/entries/admin.txt
index 883bda9422..e583790b82 100644
--- a/config/entries/admin.txt
+++ b/config/entries/admin.txt
@@ -52,8 +52,12 @@ ANNOUNCE_ADMIN_LOGOUT
## Uncomment to have an admin message sent anytime an admin connects to a round in play, you can edit the messages in admin.dm
#ANNOUNCE_ADMIN_LOGIN
+## Uncomment to enable global ban DB using the provided URL. The API should expect to receive a ckey at the end of the URL.
## More API details can be found here: https://centcom.melonmesa.com
-CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
+#CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search
+## Uncomment to enable source whitelisting, a comma-separated list (no spaces) of CentCom sources (sourceName).
+## If enabled, only bans from these servers will be shown to admins using CentCom. The default sources list is an example.
+#CENTCOM_SOURCE_WHITELIST Beestation MRP,TGMC,FTL13
## AUTOADMIN
## The default admin rank
diff --git a/rust_g.dll b/rust_g.dll
index 8ef1c59a10..26f6942861 100644
Binary files a/rust_g.dll and b/rust_g.dll differ
diff --git a/tgstation.dme b/tgstation.dme
index f483bcbbad..c0271c113c 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -103,6 +103,7 @@
#include "code\__DEFINES\rockpaperscissors.dm"
#include "code\__DEFINES\role_preferences.dm"
#include "code\__DEFINES\rust_g.dm"
+#include "code\__DEFINES\rust_g_overrides.dm"
#include "code\__DEFINES\say.dm"
#include "code\__DEFINES\security_levels.dm"
#include "code\__DEFINES\shuttles.dm"