diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 4dfa55a792..a29cf7d360 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -26,6 +26,6 @@
# Zonespace
-/code/modules/gear_presets/survivors.dm @zonespace27
+/code/modules/admin/verbs/SDQL2/ @Zonespace27
# MULTIPLE OWNERS
diff --git a/.github/alternate_byond_versions.txt b/.github/alternate_byond_versions.txt
index 005803964c..7b50af4688 100644
--- a/.github/alternate_byond_versions.txt
+++ b/.github/alternate_byond_versions.txt
@@ -5,5 +5,3 @@
# Format is version: map
# Example:
# 500.1337: runtimestation
-
-515.1610: lv624
diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml
index 978ede1e65..9636c6c48e 100644
--- a/.github/workflows/ci_suite.yml
+++ b/.github/workflows/ci_suite.yml
@@ -66,30 +66,16 @@ jobs:
odlint:
name: Lint with OpenDream
- runs-on: ubuntu-20.04
+ runs-on: ubuntu-22.04
steps:
- - uses: actions/checkout@v3
- - name: Get OpenDream Version
- run: |
- source dependencies.sh
- echo "OPENDREAM_VERSION=$OPENDREAM_VERSION" >> $GITHUB_ENV
- - name: Restore OpenDream cache
- uses: actions/cache@v3
- id: cache-od
+ - uses: actions/checkout@v4
+ - uses: robinraju/release-downloader@v1.9
with:
- path: ~/OpenDream
- key: ${{ runner.os }}-opendream-${{ env.OPENDREAM_VERSION }}
- - name: Download OpenDream
- if: steps.cache-od.outputs.cache-hit != 'true'
- run: |
- bash tools/ci/download_od.sh
- - name: Setup OpenDream
- if: steps.cache-od.outputs.cache-hit != 'true'
- run: |
- bash tools/ci/setup_od.sh
- - name: Run OpenDream
- run: |
- bash tools/ci/run_od.sh
+ repository: "OpenDreamProject/OpenDream"
+ tag: "latest"
+ fileName: "DMCompiler_linux-x64.tar.gz"
+ extract: true
+ - run: ./DMCompiler_linux-x64/DMCompiler --suppress-unimplemented colonialmarines.dme
compile_all_maps:
if: "!contains(github.event.head_commit.message, '[ci skip]')"
diff --git a/.tgs.yml b/.tgs.yml
index ba3fc6b26c..ed84385e3c 100644
--- a/.tgs.yml
+++ b/.tgs.yml
@@ -1,5 +1,5 @@
version: 1
-byond: "514.1588"
+byond: "515.1627"
static_files:
- name: config
- name: data
diff --git a/code/__DEFINES/__spacemandmm.dm b/code/__DEFINES/__spacemandmm.dm
index b62bbee425..9a044949db 100644
--- a/code/__DEFINES/__spacemandmm.dm
+++ b/code/__DEFINES/__spacemandmm.dm
@@ -40,5 +40,5 @@
/world/Del()
var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
if (debug_server)
- LIBCALL(debug_server, "auxtools_shutdown")()
+ call_ext(debug_server, "auxtools_shutdown")()
. = ..()
diff --git a/code/__DEFINES/_macros.dm b/code/__DEFINES/_macros.dm
index 31f4b2aca0..07c3eb664e 100644
--- a/code/__DEFINES/_macros.dm
+++ b/code/__DEFINES/_macros.dm
@@ -8,17 +8,8 @@
#define subtypesof(A) (typesof(A) - A)
-#ifdef EXPERIMENT_515_DONT_CACHE_REF
/// Takes a datum as input, returns its ref string
#define text_ref(datum) ref(datum)
-#else
-/// Takes a datum as input, returns its ref string, or a cached version of it
-/// This allows us to cache \ref creation, which ensures it'll only ever happen once per datum, saving string tree time
-/// It is slightly less optimal then a []'d datum, but the cost is massively outweighed by the potential savings
-/// It will only work for datums mind, for datum reasons
-/// : because of the embedded typecheck
-#define text_ref(datum) (isdatum(datum) ? (datum:cached_ref ||= "\ref[datum]") : ("\ref[datum]"))
-#endif
#define addToListNoDupe(L, index) if(L) L[index] = null; else L = list(index)
diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm
index de7eb672e8..b466475222 100644
--- a/code/__DEFINES/atmospherics.dm
+++ b/code/__DEFINES/atmospherics.dm
@@ -44,3 +44,4 @@ var/MAX_EXPLOSION_RANGE = 14
#define VENT_GAS_SMOKE "Smoke"
#define VENT_GAS_CN20 "CN20 Nerve Gas"
#define VENT_GAS_CN20_XENO "CN20-X Nerve Gas"
+#define VENT_GAS_LSD "ALD-91 LSD Gas"
diff --git a/code/__DEFINES/layers.dm b/code/__DEFINES/layers.dm
index c0ccd5164b..598cfcdfec 100644
--- a/code/__DEFINES/layers.dm
+++ b/code/__DEFINES/layers.dm
@@ -11,6 +11,9 @@
//#define AREA_LAYER 1
+#define DISPLACEMENT_PLATE_RENDER_LAYER 1
+#define DISPLACEMENT_PLATE_RENDER_TARGET "*DISPLACEMENT_PLATE_RENDER_TARGET"
+
#define UNDER_TURF_LAYER 1.99
#define TURF_LAYER 2
diff --git a/code/__DEFINES/shuttles.dm b/code/__DEFINES/shuttles.dm
index b27d178b12..3356782946 100644
--- a/code/__DEFINES/shuttles.dm
+++ b/code/__DEFINES/shuttles.dm
@@ -109,6 +109,7 @@
#define MOBILE_SHUTTLE_LIFEBOAT_PORT "lifeboat-port"
#define MOBILE_SHUTTLE_LIFEBOAT_STARBOARD "lifeboat-starboard"
+#define MOBILE_SHUTTLE_LIFEBOAT_COMPACT "lifeboat-compact"
#define MOBILE_SHUTTLE_VEHICLE_ELEVATOR "vehicle_elevator"
#define DROPSHIP_MIDWAY "dropship_midway"
diff --git a/code/__DEFINES/sounds.dm b/code/__DEFINES/sounds.dm
index 541d95d281..6c5d2879f3 100644
--- a/code/__DEFINES/sounds.dm
+++ b/code/__DEFINES/sounds.dm
@@ -1,6 +1,6 @@
#define FALLOFF_SOUNDS 1
-#define FREE_CHAN_END 1016
+#define FREE_CHAN_END 1014
#define INITIAL_SOUNDSCAPE_COOLDOWN 20
#define EAR_DEAF_MUTE 1
@@ -21,6 +21,7 @@
#define ITEM_EQUIP_VOLUME 50
//Reserved channels
+#define SOUND_CHANNEL_TEST 1015
#define SOUND_CHANNEL_NOTIFY 1016
#define SOUND_CHANNEL_VOX 1017
#define SOUND_CHANNEL_MUSIC 1018
diff --git a/code/__DEFINES/tgs.dm b/code/__DEFINES/tgs.dm
index a4fb6d40be..e2c89df90e 100644
--- a/code/__DEFINES/tgs.dm
+++ b/code/__DEFINES/tgs.dm
@@ -1,6 +1,6 @@
// tgstation-server DMAPI
-#define TGS_DMAPI_VERSION "7.1.1"
+#define TGS_DMAPI_VERSION "7.1.2"
// All functions and datums outside this document are subject to change with any version and should not be relied on.
@@ -312,6 +312,7 @@
var/datum/tgs_chat_embed/structure/embed
/datum/tgs_message_content/New(text)
+ ..()
if(!istext(text))
TGS_ERROR_LOG("[/datum/tgs_message_content] created with no text!")
text = null
@@ -354,6 +355,7 @@
var/proxy_url
/datum/tgs_chat_embed/media/New(url)
+ ..()
if(!istext(url))
CRASH("[/datum/tgs_chat_embed/media] created with no url!")
@@ -367,6 +369,7 @@
var/proxy_icon_url
/datum/tgs_chat_embed/footer/New(text)
+ ..()
if(!istext(text))
CRASH("[/datum/tgs_chat_embed/footer] created with no text!")
@@ -383,6 +386,7 @@
var/proxy_icon_url
/datum/tgs_chat_embed/provider/author/New(name)
+ ..()
if(!istext(name))
CRASH("[/datum/tgs_chat_embed/provider/author] created with no name!")
@@ -395,6 +399,7 @@
var/is_inline
/datum/tgs_chat_embed/field/New(name, value)
+ ..()
if(!istext(name))
CRASH("[/datum/tgs_chat_embed/field] created with no name!")
@@ -510,7 +515,7 @@
/*
The MIT License
-Copyright (c) 2017-2023 Jordan Brown
+Copyright (c) 2017-2024 Jordan Brown
Permission is hereby granted, free of charge,
to any person obtaining a copy of this software and
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 24e39ff16c..73b494e989 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -920,8 +920,9 @@ world
// From /datum/preferences/proc/copy_appearance_to
body.age = original.age
body.gender = original.gender
- body.ethnicity = original.ethnicity
+ body.skin_color = original.skin_color
body.body_type = original.body_type
+ body.body_size = original.body_size
body.r_eyes = original.r_eyes
body.g_eyes = original.g_eyes
diff --git a/code/__HELPERS/mobs.dm b/code/__HELPERS/mobs.dm
index 9aa1bdc3ea..38d84c2922 100644
--- a/code/__HELPERS/mobs.dm
+++ b/code/__HELPERS/mobs.dm
@@ -1,11 +1,14 @@
#define isdeaf(A) (ismob(A) && ((A?:sdisabilities & DISABILITY_DEAF) || A?:ear_deaf))
#define xeno_hivenumber(A) (isxeno(A) ? A?:hivenumber : FALSE)
-/proc/random_ethnicity()
- return pick(GLOB.ethnicities_list)
+/proc/random_skin_color()
+ return pick(GLOB.skin_color_list)
/proc/random_body_type()
- return pick(GLOB.body_types_list)
+ return pick(GLOB.body_type_list)
+
+/proc/random_body_size()
+ return pick(GLOB.body_size_list)
/proc/random_hair_style(gender, species = "Human")
var/h_style = "Crewcut"
diff --git a/code/__HELPERS/nameof.dm b/code/__HELPERS/nameof.dm
index 7cd5777f46..5a2fd60e71 100644
--- a/code/__HELPERS/nameof.dm
+++ b/code/__HELPERS/nameof.dm
@@ -8,8 +8,4 @@
/**
* NAMEOF that actually works in static definitions because src::type requires src to be defined
*/
-#if DM_VERSION >= 515
#define NAMEOF_STATIC(datum, X) (nameof(type::##X))
-#else
-#define NAMEOF_STATIC(datum, X) (#X || ##datum.##X)
-#endif
diff --git a/code/__HELPERS/sanitize_values.dm b/code/__HELPERS/sanitize_values.dm
index 85e102a3c1..decec60d36 100644
--- a/code/__HELPERS/sanitize_values.dm
+++ b/code/__HELPERS/sanitize_values.dm
@@ -45,18 +45,24 @@
else return default
return default
-/proc/sanitize_ethnicity(ethnicity, default = "Western")
- if (ethnicity in GLOB.ethnicities_list)
- return ethnicity
+/proc/sanitize_skin_color(skin_color, default = "Pale 2")
+ if(skin_color in GLOB.skin_color_list)
+ return skin_color
return default
-/proc/sanitize_body_type(body_type, default = "Mesomorphic (Average)")
- if (body_type in GLOB.body_types_list)
+/proc/sanitize_body_type(body_type, default = "Lean")
+ if(body_type in GLOB.body_type_list)
return body_type
return default
+/proc/sanitize_body_size(body_size, default = "Average")
+ if(body_size in GLOB.body_size_list)
+ return body_size
+
+ return default
+
/proc/sanitize_hexcolor(color, default="#000000")
if(!istext(color)) return default
var/len = length(color)
diff --git a/code/__pragmas.dm b/code/__pragmas.dm
index 39c14e1bbc..84fcc0dfc3 100644
--- a/code/__pragmas.dm
+++ b/code/__pragmas.dm
@@ -12,7 +12,6 @@
#pragma SoftReservedKeyword error
#pragma DuplicateVariable error
#pragma DuplicateProcDefinition error
-#pragma TooManyArguments error
#pragma PointlessParentCall error
#pragma PointlessBuiltinCall error
#pragma SuspiciousMatrixCall error
diff --git a/code/_byond_version_compat.dm b/code/_byond_version_compat.dm
index 26968f0f83..c41fdc830e 100644
--- a/code/_byond_version_compat.dm
+++ b/code/_byond_version_compat.dm
@@ -1,56 +1,17 @@
// This file contains defines allowing targeting byond versions newer than the supported
//Update this whenever you need to take advantage of more recent byond features
-#define MIN_COMPILER_VERSION 514
-#define MIN_COMPILER_BUILD 1588
+#define MIN_COMPILER_VERSION 515
+#define MIN_COMPILER_BUILD 1627
#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM) && !defined(OPENDREAM)
//Don't forget to update this part
-#error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update.
-#error You need version 514.1588 or higher
-#endif
-
-/*
-#if (DM_VERSION == 514 && DM_BUILD > 1575 && DM_BUILD <= 1577)
-#error Your version of BYOND currently has a crashing issue that will prevent you from running Dream Daemon test servers.
-#error We require developers to test their content, so an inability to test means we cannot allow the compile.
-#error Please consider downgrading to 514.1575 or lower.
-#endif
-*/
-
-/*
-// Keep savefile compatibilty at minimum supported level
-#if DM_VERSION >= 515
-/savefile/byond_version = MIN_COMPILER_VERSION
-#endif
-*/
-
-// 515 split call for external libraries into call_ext
-#if DM_VERSION < 515
-#define LIBCALL call
-#else
-#define LIBCALL call_ext
+#error Your version of BYOND is too out-of-date to compile this project. Go to https://www.byond.com/download and update.
+#error You need version 515.1627 or higher
#endif
// So we want to have compile time guarantees these methods exist on local type, unfortunately 515 killed the .proc/procname and .verb/verbname syntax so we have to use nameof()
// For the record: GLOBAL_VERB_REF would be useless as verbs can't be global.
-#if DM_VERSION < 515
-
-/// Call by name proc references, checks if the proc exists on either this type or as a global proc.
-#define PROC_REF(X) (.proc/##X)
-/// Call by name verb references, checks if the verb exists on either this type or as a global verb.
-#define VERB_REF(X) (.verb/##X)
-
-/// Call by name proc reference, checks if the proc exists on either the given type or as a global proc
-#define TYPE_PROC_REF(TYPE, X) (##TYPE.proc/##X)
-/// Call by name verb reference, checks if the verb exists on either the given type or as a global verb
-#define TYPE_VERB_REF(TYPE, X) (##TYPE.verb/##X)
-
-/// Call by name proc reference, checks if the proc is an existing global proc
-#define GLOBAL_PROC_REF(X) (/proc/##X)
-
-#else
-
/// Call by name proc references, checks if the proc exists on either this type or as a global proc.
#define PROC_REF(X) (nameof(.proc/##X))
/// Call by name verb references, checks if the verb exists on either this type or as a global verb.
@@ -64,16 +25,4 @@
/// Call by name proc reference, checks if the proc is an existing global proc
#define GLOBAL_PROC_REF(X) (/proc/##X)
-#endif
-
-#if (DM_VERSION == 515)
-/// fcopy will crash on 515 linux if given a non-existant file, instead of returning 0 like on 514 linux or 515 windows
-/// var case matches documentation for fcopy.
-/world/proc/__fcopy(Src, Dst)
- if (istext(Src) && !fexists(Src))
- return 0
- return fcopy(Src, Dst)
-#define fcopy(Src, Dst) world.__fcopy(Src, Dst)
-
-#endif
diff --git a/code/_experiments.dm b/code/_experiments.dm
index 6e5addb5f9..39c4c45e7e 100644
--- a/code/_experiments.dm
+++ b/code/_experiments.dm
@@ -3,29 +3,7 @@
// Any flag you see here can be flipped with the `-D` CLI argument.
// For example, if you want to enable EXPERIMENT_MY_COOL_FEATURE, compile with -DEXPERIMENT_MY_COOL_FEATURE
-// EXPERIMENT_515_QDEL_HARD_REFERENCE
-// - Hold a hard reference for qdeleted items, and check ref_count, rather than using refs. Requires 515+.
-
-// EXPERIMENT_515_DONT_CACHE_REF
-// - Avoids `text_ref` caching, aided by improvements to ref() speed in 515.
-
-#if DM_VERSION < 515
-
-// You can't X-macro custom names :(
-#ifdef EXPERIMENT_515_QDEL_HARD_REFERENCE
-#warn EXPERIMENT_515_QDEL_HARD_REFERENCE is only available on 515+
-#undef EXPERIMENT_515_QDEL_HARD_REFERENCE
-#endif
-
-#ifdef EXPERIMENT_515_DONT_CACHE_REF
-#warn EXPERIMENT_515_DONT_CACHE_REF is only available on 515+
-#undef EXPERIMENT_515_DONT_CACHE_REF
-#endif
-
-#elif defined(UNIT_TESTS)
-
-//#define EXPERIMENT_515_QDEL_HARD_REFERENCE
-#define EXPERIMENT_515_DONT_CACHE_REF
+#if defined(UNIT_TESTS)
#endif
diff --git a/code/_globalvars/global_lists.dm b/code/_globalvars/global_lists.dm
index 3ba92a7c4d..c2cfb8263f 100644
--- a/code/_globalvars/global_lists.dm
+++ b/code/_globalvars/global_lists.dm
@@ -198,10 +198,11 @@ GLOBAL_LIST_INIT(custom_event_info_list, setup_custom_event_info())
GLOBAL_LIST_INIT(poster_designs, subtypesof(/datum/poster))
//Preferences stuff
- // Ethnicities
-GLOBAL_REFERENCE_LIST_INDEXED(ethnicities_list, /datum/ethnicity, name) // Stores /datum/ethnicity indexed by name
- // Body Types
-GLOBAL_REFERENCE_LIST_INDEXED(body_types_list, /datum/body_type, name) // Stores /datum/body_type indexed by name
+ // Skin colors
+GLOBAL_REFERENCE_LIST_INDEXED(skin_color_list, /datum/skin_color, name) // Stores /datum/skin_color indexed by name
+ // Body
+GLOBAL_REFERENCE_LIST_INDEXED(body_type_list, /datum/body_type, name) // Stores /datum/body_type indexed by name
+GLOBAL_REFERENCE_LIST_INDEXED(body_size_list, /datum/body_size, name) // Stores /datum/body_size indexed by name
//Hairstyles
GLOBAL_REFERENCE_LIST_INDEXED(hair_styles_list, /datum/sprite_accessory/hair, name) //stores /datum/sprite_accessory/hair indexed by name
GLOBAL_REFERENCE_LIST_INDEXED(facial_hair_styles_list, /datum/sprite_accessory/facial_hair, name) //stores /datum/sprite_accessory/facial_hair indexed by name
diff --git a/code/_onclick/adjacent.dm b/code/_onclick/adjacent.dm
index 6504db0d9f..49dfdf35e3 100644
--- a/code/_onclick/adjacent.dm
+++ b/code/_onclick/adjacent.dm
@@ -25,7 +25,7 @@
* If you are diagonally adjacent, ensure you can pass through at least one of the mutually adjacent square.
* Passing through in this case ignores anything with the throwpass flag, such as tables, racks, and morgue trays.
*/
-/turf/Adjacent(atom/neighbor, atom/target = null)
+/turf/Adjacent(atom/neighbor, atom/target = null, list/ignore_list)
var/turf/T0 = get_turf(neighbor)
if(T0 == src)
return TRUE
@@ -34,7 +34,7 @@
if(T0.x == x || T0.y == y)
// Check for border blockages
- return T0.ClickCross(get_dir(T0,src), border_only = 1) && src.ClickCross(get_dir(src,T0), border_only = 1, target_atom = target)
+ return T0.ClickCross(get_dir(T0,src), border_only = 1, ignore_list = ignore_list) && src.ClickCross(get_dir(src,T0), border_only = 1, target_atom = target, ignore_list = ignore_list)
// Not orthagonal
var/in_dir = get_dir(neighbor,src) // eg. northwest (1+8)
@@ -42,14 +42,14 @@
var/d2 = in_dir - d1 // eg north (1+8) - 8 = 1
for(var/d in list(d1,d2))
- if(!T0.ClickCross(d, border_only = 1))
+ if(!T0.ClickCross(d, border_only = 1, ignore_list = ignore_list))
continue // could not leave T0 in that direction
var/turf/T1 = get_step(T0,d)
- if(!T1 || T1.density || !T1.ClickCross(get_dir(T1,T0)|get_dir(T1,src), border_only = 0))
+ if(!T1 || T1.density || !T1.ClickCross(get_dir(T1,T0)|get_dir(T1,src), border_only = 0, ignore_list = ignore_list))
continue // couldn't enter or couldn't leave T1
- if(!src.ClickCross(get_dir(src,T1), border_only = 1, target_atom = target))
+ if(!src.ClickCross(get_dir(src,T1), border_only = 1, target_atom = target, ignore_list = ignore_list))
continue // could not enter src
return TRUE // we don't care about our own density
@@ -131,8 +131,11 @@ Quick adjacency (to turf):
This is defined as any dense ON_BORDER object, or any dense object without throwpass.
The border_only flag allows you to not objects (for source and destination squares)
*/
-/turf/proc/ClickCross(target_dir, border_only, target_atom = null)
+/turf/proc/ClickCross(target_dir, border_only, target_atom = null, list/ignore_list)
for(var/obj/O in src)
+ if(O in ignore_list)
+ continue
+
if(!O.density || O == target_atom || O.throwpass)
continue // throwpass is used for anything you can click through
diff --git a/code/_onclick/hud/rendering/plane_master.dm b/code/_onclick/hud/rendering/plane_master.dm
index 91c0e24fae..c337ee198e 100644
--- a/code/_onclick/hud/rendering/plane_master.dm
+++ b/code/_onclick/hud/rendering/plane_master.dm
@@ -175,3 +175,10 @@
plane = ESCAPE_MENU_PLANE
appearance_flags = PLANE_MASTER|NO_CLIENT_COLOR
render_relay_plane = RENDER_PLANE_MASTER
+
+/atom/movable/screen/plane_master/displacement
+ name = "displacement plane"
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ plane = DISPLACEMENT_PLATE_RENDER_LAYER
+ render_target = DISPLACEMENT_PLATE_RENDER_TARGET
+ render_relay_plane = null
diff --git a/code/_onclick/hud/rendering/render_plate.dm b/code/_onclick/hud/rendering/render_plate.dm
index 18236c6ee7..cb579eb4ff 100644
--- a/code/_onclick/hud/rendering/render_plate.dm
+++ b/code/_onclick/hud/rendering/render_plate.dm
@@ -39,6 +39,10 @@
plane = RENDER_PLANE_GAME
render_relay_plane = RENDER_PLANE_MASTER
+/atom/movable/screen/plane_master/rendering_plate/game_world/Initialize(mapload, datum/hud/hud_owner)
+ . = ..()
+ add_filter("displacer", 1, displacement_map_filter(render_source = DISPLACEMENT_PLATE_RENDER_TARGET, size = 10))
+
///render plate for OOC stuff like ghosts, hud-screen effects, etc
/atom/movable/screen/plane_master/rendering_plate/non_game
name = "non-game rendering plate"
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index bbbb0fc5f5..a499bc769c 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -620,3 +620,27 @@ This maintains a list of ip addresses that are able to bypass topic filtering.
protection = CONFIG_ENTRY_HIDDEN|CONFIG_ENTRY_LOCKED
/datum/config_entry/flag/auto_profile
+
+/datum/config_entry/number/client_warn_version
+ default = null
+ min_val = 500
+
+/datum/config_entry/number/client_warn_build
+ default = null
+ min_val = 0
+
+/datum/config_entry/string/client_warn_message
+ default = "Your version of BYOND may have issues or be blocked from accessing this server in the future."
+
+/datum/config_entry/flag/client_warn_popup
+
+/datum/config_entry/number/client_error_version
+ default = null
+ min_val = 500
+
+/datum/config_entry/string/client_error_message
+ default = "Your version of BYOND is too old, may have issues, and is blocked from accessing this server."
+
+/datum/config_entry/number/client_error_build
+ default = null
+ min_val = 0
diff --git a/code/controllers/mc/globals.dm b/code/controllers/mc/globals.dm
index 7b5cc94d36..b5547564b7 100644
--- a/code/controllers/mc/globals.dm
+++ b/code/controllers/mc/globals.dm
@@ -13,7 +13,10 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars)
GLOB = src
var/datum/controller/exclude_these = new
- gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
+ // I know this is dumb but the nested vars list hangs a ref to the datum. This fixes that
+ var/list/controller_vars = exclude_these.vars.Copy()
+ controller_vars["vars"] = null
+ gvars_datum_in_built_vars = controller_vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order))
QDEL_IN(exclude_these, 0) //signal logging isn't ready
log_world("[vars.len - gvars_datum_in_built_vars.len] global variables")
diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm
index e94d6b1aff..37c305d59c 100644
--- a/code/controllers/subsystem/garbage.dm
+++ b/code/controllers/subsystem/garbage.dm
@@ -139,13 +139,6 @@ SUBSYSTEM_DEF(garbage)
pass_counts[i] = 0
fail_counts[i] = 0
-#ifdef EXPERIMENT_515_QDEL_HARD_REFERENCE
-// 1 from the hard reference in the queue, and 1 from the variable used before this
-#define IS_DELETED(datum, _) (refcount(##datum) == 2)
-#else
-#define IS_DELETED(datum, gcd_at_time) (isnull(##datum) || ##datum.gc_destroyed != gcd_at_time)
-#endif
-
/datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_FILTER)
if (level == GC_QUEUE_FILTER)
delslasttick = 0
@@ -162,7 +155,7 @@ SUBSYSTEM_DEF(garbage)
lastlevel = level
- //We do this rather then for(var/refID in queue) because that sort of for loop copies the whole list.
+ //We do this rather then for(var/list/ref_info in queue) because that sort of for loop copies the whole list.
//Normally this isn't expensive, but the gc queue can grow to 40k items, and that gets costly/causes overrun.
for (var/i in 1 to length(queue))
var/list/L = queue[i]
@@ -173,21 +166,15 @@ SUBSYSTEM_DEF(garbage)
continue
var/queued_at_time = L[GC_QUEUE_ITEM_QUEUE_TIME]
-
if(queued_at_time > cut_off_time)
break // Everything else is newer, skip them
count++
-#ifdef EXPERIMENT_515_QDEL_HARD_REFERENCE
var/datum/D = L[GC_QUEUE_ITEM_REF]
-#else
- var/GCd_at_time = L[GC_QUEUE_ITEM_GCD_DESTROYED]
- var/refID = L[GC_QUEUE_ITEM_REF]
- var/datum/D
- D = locate(refID)
-#endif
-
- if (IS_DELETED(D, GCd_at_time)) // So if something else coincidently gets the same ref, it's not deleted by mistake
+
+ // 1 from the hard reference in the queue, and 1 from the variable used before this
+ // If that's all we've got, send er off
+ if (refcount(D) == 2)
++gcedlasttick
++totalgcs
pass_counts[level]++
@@ -221,11 +208,7 @@ SUBSYSTEM_DEF(garbage)
var/type = D.type
var/datum/qdel_item/I = items[type]
- var/message = "## TESTING: GC: -- [text_ref(D)] | [type] was unable to be GC'd --"
-#if DM_VERSION >= 515
- message = "[message] (ref count of [refcount(D)])"
-#endif
- log_world(message)
+ log_world("## TESTING: GC: -- [text_ref(D)] | [type] was unable to be GC'd -- (ref count of [refcount(D)])")
#ifdef TESTING
for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage
@@ -261,8 +244,6 @@ SUBSYSTEM_DEF(garbage)
queue.Cut(1,count+1)
count = 0
-#undef IS_DELETED
-
/datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_FILTER)
if (isnull(D))
return
@@ -271,20 +252,11 @@ SUBSYSTEM_DEF(garbage)
return
var/queue_time = world.time
-#ifdef EXPERIMENT_515_QDEL_HARD_REFERENCE
- var/refid = D
-#else
- var/refid = text_ref(D)
-#endif
-
- var/static/uid = 0
- uid = WRAP(uid+1, 1, SHORT_REAL_LIMIT - 1)
if (D.gc_destroyed <= 0)
- D.gc_destroyed = uid
+ D.gc_destroyed = queue_time
var/list/queue = queues[level]
-
- queue[++queue.len] = list(queue_time, refid, D.gc_destroyed) // not += for byond reasons
+ queue[++queue.len] = list(queue_time, D, D.gc_destroyed) // not += for byond reasons
//this is mainly to separate things profile wise.
/datum/controller/subsystem/garbage/proc/HardDelete(datum/D, force)
diff --git a/code/controllers/subsystem/statpanel.dm b/code/controllers/subsystem/statpanel.dm
index b65ca1e758..ba07898dec 100644
--- a/code/controllers/subsystem/statpanel.dm
+++ b/code/controllers/subsystem/statpanel.dm
@@ -212,17 +212,23 @@ SUBSYSTEM_DEF(statpanels)
/// Sets the current tab to the SDQL tab
/datum/controller/subsystem/statpanels/proc/set_SDQL2_tab(client/target)
+ if(!target)
+ return
+
var/list/sdql2_initial = list()
- //sdql2_initial[length(sdql2_initial)++] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj))
+ sdql2_initial[++sdql2_initial.len] = list("", "Access Global SDQL2 List", REF(GLOB.sdql2_vv_statobj))
var/list/sdql2_querydata = list()
- //for(var/datum/sdql2_query/query as anything in GLOB.sdql2_queries)
- //sdql2_querydata = query.generate_stat()
+ for(var/datum/sdql2_query/query as anything in GLOB.sdql2_queries)
+ sdql2_querydata += query.generate_stat()
sdql2_initial += sdql2_querydata
target.stat_panel.send_message("update_sdql2", sdql2_initial)
///immediately update the active statpanel tab of the target client
/datum/controller/subsystem/statpanels/proc/immediate_send_stat_data(client/target)
+ if(!target)
+ return FALSE
+
if(!target.stat_panel.is_ready())
return FALSE
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 7fe2b56eba..e06e1ac458 100644
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -8,6 +8,10 @@ SUBSYSTEM_DEF(ticker)
var/current_state = GAME_STATE_STARTUP //State of current round used by process()
var/force_ending = FALSE //Round was ended by admin intervention
+
+ /// If TRUE, there is no lobby phase, the game starts immediately.
+ var/start_immediately = FALSE
+
var/bypass_checks = FALSE //Bypass mode init checks
var/setup_failed = FALSE //If the setup has failed at any point
var/setup_started = FALSE
@@ -79,6 +83,10 @@ SUBSYSTEM_DEF(ticker)
var/mob/new_player/player = i
if(player.ready) // TODO: port this == PLAYER_READY_TO_PLAY)
++totalPlayersReady
+
+ if(start_immediately)
+ time_left = 0
+
if(time_left < 0 || delay_start)
return
diff --git a/code/datums/ammo/bullet/pistol.dm b/code/datums/ammo/bullet/pistol.dm
index 488b4e22c8..1c39d4ac99 100644
--- a/code/datums/ammo/bullet/pistol.dm
+++ b/code/datums/ammo/bullet/pistol.dm
@@ -8,7 +8,7 @@
/datum/ammo/bullet/pistol
name = "pistol bullet"
headshot_state = HEADSHOT_OVERLAY_MEDIUM
- accuracy = -HIT_ACCURACY_TIER_3
+ accuracy = HIT_ACCURACY_TIER_3
accuracy_var_low = PROJECTILE_VARIANCE_TIER_6
damage = 40
penetration= ARMOR_PENETRATION_TIER_2
@@ -98,7 +98,7 @@
/datum/ammo/bullet/pistol/heavy
name = "heavy pistol bullet"
headshot_state = HEADSHOT_OVERLAY_MEDIUM
- accuracy = -HIT_ACCURACY_TIER_3
+ accuracy = HIT_ACCURACY_TIER_3
accuracy_var_low = PROJECTILE_VARIANCE_TIER_6
damage = 55
penetration = ARMOR_PENETRATION_TIER_3
@@ -140,7 +140,7 @@
name = ".50 heavy pistol bullet"
damage = 45
headshot_state = HEADSHOT_OVERLAY_HEAVY
- accuracy = -HIT_ACCURACY_TIER_3
+ accuracy = HIT_ACCURACY_TIER_3
accuracy_var_low = PROJECTILE_VARIANCE_TIER_6
penetration = ARMOR_PENETRATION_TIER_6
shrapnel_chance = SHRAPNEL_CHANCE_TIER_5
diff --git a/code/datums/ammo/bullet/shotgun.dm b/code/datums/ammo/bullet/shotgun.dm
index c5f81a67c0..95db0c6c39 100644
--- a/code/datums/ammo/bullet/shotgun.dm
+++ b/code/datums/ammo/bullet/shotgun.dm
@@ -155,6 +155,7 @@
/datum/ammo/bullet/shotgun/buckshot/special
name = "buckshot shell, USCM special type"
+ handful_state = "special_buck"
bonus_projectiles_type = /datum/ammo/bullet/shotgun/spread/special
accurate_range = 8
diff --git a/code/datums/ammo/misc.dm b/code/datums/ammo/misc.dm
index bdb284753d..2fcc92d7d2 100644
--- a/code/datums/ammo/misc.dm
+++ b/code/datums/ammo/misc.dm
@@ -51,6 +51,8 @@
/datum/ammo/flamethrower/tank_flamer
flamer_reagent_type = /datum/reagent/napalm/blue
+ max_range = 8
+
/datum/ammo/flamethrower/sentry_flamer
flags_ammo_behavior = AMMO_IGNORE_ARMOR|AMMO_IGNORE_COVER|AMMO_FLAME
flamer_reagent_type = /datum/reagent/napalm/blue
@@ -265,6 +267,9 @@
nade_type = /obj/item/explosive/grenade/smokebomb
icon_state = "smoke_shell"
+/datum/ammo/grenade_container/tank_glauncher
+ max_range = 8
+
/datum/ammo/hugger_container
name = "hugger shell"
ping = null
diff --git a/code/datums/ammo/rocket.dm b/code/datums/ammo/rocket.dm
index 53da7953d4..3d649ffc3f 100644
--- a/code/datums/ammo/rocket.dm
+++ b/code/datums/ammo/rocket.dm
@@ -143,6 +143,8 @@
return
return ..()
+/datum/ammo/rocket/ap/tank_towlauncher
+ max_range = 8
/datum/ammo/rocket/ltb
name = "cannon round"
diff --git a/code/datums/autocells/explosion.dm b/code/datums/autocells/explosion.dm
index 0b54a53a57..2efc7a8a3e 100644
--- a/code/datums/autocells/explosion.dm
+++ b/code/datums/autocells/explosion.dm
@@ -282,6 +282,9 @@ as having entered the turf.
if(QDELETED(E))
return
+ if(power >= 150) //shockwave for anything over 150 power
+ new /obj/effect/shockwave(epicenter, power/60)
+
E.power = power
E.power_falloff = falloff
E.falloff_shape = falloff_shape
diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm
index 7c50c34338..26ef0e5d0a 100644
--- a/code/datums/datacore.dm
+++ b/code/datums/datacore.dm
@@ -355,31 +355,38 @@ GLOBAL_DATUM_INIT(data_core, /datum/datacore, new)
var/icon/icobase = H.species.icobase
var/icon/temp
- var/datum/ethnicity/ET = GLOB.ethnicities_list[H.ethnicity]
- var/datum/body_type/B = GLOB.body_types_list[H.body_type]
+ var/datum/skin_color/set_skin_color = GLOB.skin_color_list[H.skin_color]
+ var/datum/body_type/set_body_type = GLOB.body_type_list[H.body_type]
+ var/datum/body_size/set_body_size = GLOB.body_size_list[H.body_size]
- var/e_icon
- var/b_icon
+ var/skin_color_icon
+ var/body_type_icon
+ var/body_size_icon
- if (!ET)
- e_icon = "western"
+ if(!set_skin_color)
+ skin_color_icon = "pale2"
else
- e_icon = ET.icon_name
+ skin_color_icon = set_skin_color.icon_name
- if (!B)
- b_icon = "mesomorphic"
+ if(!set_body_type)
+ body_type_icon = "lean"
else
- b_icon = B.icon_name
+ body_type_icon = set_body_type.icon_name
- preview_icon = new /icon(icobase, get_limb_icon_name(H.species, b_icon, H.gender, "torso", e_icon))
- temp = new /icon(icobase, get_limb_icon_name(H.species, b_icon, H.gender, "groin", e_icon))
+ if(!set_body_size)
+ body_size_icon = "avg"
+ else
+ body_size_icon = set_body_size.icon_name
+
+ preview_icon = new /icon(icobase, get_limb_icon_name(H.species, body_size_icon, body_type_icon, H.gender, "torso", skin_color_icon))
+ temp = new /icon(icobase, get_limb_icon_name(H.species, body_size_icon, body_type_icon, H.gender, "groin", skin_color_icon))
preview_icon.Blend(temp, ICON_OVERLAY)
- temp = new /icon(icobase, get_limb_icon_name(H.species, b_icon, H.gender, "head", e_icon))
+ temp = new /icon(icobase, get_limb_icon_name(H.species, body_size_icon, body_type_icon, H.gender, "head", skin_color_icon))
preview_icon.Blend(temp, ICON_OVERLAY)
for(var/obj/limb/E in H.limbs)
if(E.status & LIMB_DESTROYED) continue
- temp = new /icon(icobase, get_limb_icon_name(H.species, b_icon, H.gender, E.name, e_icon))
+ temp = new /icon(icobase, get_limb_icon_name(H.species, body_size_icon, body_type_icon, H.gender, E.name, skin_color_icon))
if(E.status & LIMB_ROBOT)
temp.MapColors(rgb(77,77,77), rgb(150,150,150), rgb(28,28,28), rgb(0,0,0))
preview_icon.Blend(temp, ICON_OVERLAY)
diff --git a/code/datums/datum.dm b/code/datums/datum.dm
index 7d497785a7..3e317ffd60 100644
--- a/code/datums/datum.dm
+++ b/code/datums/datum.dm
@@ -54,13 +54,6 @@
*/
var/list/cooldowns
-#ifndef EXPERIMENT_515_DONT_CACHE_REF
- /// A cached version of our \ref
- /// The brunt of \ref costs are in creating entries in the string tree (a tree of immutable strings)
- /// This avoids doing that more then once per datum by ensuring ref strings always have a reference to them after they're first pulled
- var/cached_ref
-#endif
-
/// A weak reference to another datum
var/datum/weakref/weak_reference
diff --git a/code/datums/global_variables.dm b/code/datums/global_variables.dm
index 953f42f172..53e9c0391e 100644
--- a/code/datums/global_variables.dm
+++ b/code/datums/global_variables.dm
@@ -118,15 +118,11 @@
/client/proc/debug_global_variable(name, value, level)
var/html = ""
- var/change = 0
//to make the value bold if changed
if(!(admin_holder.rights & R_DEBUG))
return html
html += "
EC "
- if(value != initial(global.vars[name]))
- html += ""
- change = 1
if (isnull(value))
html += "[name] = null"
@@ -175,8 +171,6 @@
else
html += "[name] = [value]"
- if(change)
- html += ""
html += ""
@@ -353,7 +347,6 @@
if(admin_holder && admin_holder.marked_datum)
possible_classes += "marked datum"
possible_classes += "edit referenced object"
- possible_classes += "restore to default"
class = tgui_input_list(usr, "What kind of variable?","Variable Type", possible_classes)
if(!class)
@@ -365,9 +358,6 @@
mod_list(global.vars[variable])
return
- if("restore to default")
- global.vars[variable] = initial(global.vars[variable])
-
if("edit referenced object")
return .(global.vars[variable])
diff --git a/code/datums/keybinding/mob.dm b/code/datums/keybinding/mob.dm
index b7b83249b3..b2bf989a7a 100644
--- a/code/datums/keybinding/mob.dm
+++ b/code/datums/keybinding/mob.dm
@@ -2,16 +2,6 @@
category = CATEGORY_HUMAN
weight = WEIGHT_MOB
-/datum/keybinding/mob/down(client/user)
- . = ..()
- if(isobserver(user.mob))
- return TRUE
-
-/datum/keybinding/mob/up(client/user)
- . = ..()
- if(isobserver(user.mob))
- return TRUE
-
/datum/keybinding/mob/stop_pulling
hotkey_keys = list("H", "Delete")
classic_keys = list("Delete")
diff --git a/code/datums/medal_awards.dm b/code/datums/medal_awards.dm
index c60409dab2..fe4e7e3c0e 100644
--- a/code/datums/medal_awards.dm
+++ b/code/datums/medal_awards.dm
@@ -174,7 +174,7 @@ GLOBAL_LIST_EMPTY(jelly_awards)
to_chat(user, SPAN_WARNING("You must have an authenticated ID Card to award medals."))
return
- if(!((card.paygrade in GLOB.co_paygrades) || (card.paygrade in GLOB.highcom_paygrades)))
+ if(!((card.paygrade in GLOB.co_paygrades) || (card.paygrade in GLOB.platco_paygrades) || (card.paygrade in GLOB.highcom_paygrades)))
to_chat(user, SPAN_WARNING("Only a Senior Officer can award medals!"))
return
diff --git a/code/datums/paygrades/paygrade.dm b/code/datums/paygrades/paygrade.dm
index 947f9dc094..968956539d 100644
--- a/code/datums/paygrades/paygrade.dm
+++ b/code/datums/paygrades/paygrade.dm
@@ -52,6 +52,9 @@ GLOBAL_LIST_INIT(co_paygrades, list(
"MO6C",
"MO5",
"MO4",
+))
+
+GLOBAL_LIST_INIT(platco_paygrades, list(
"MO3",
"MO2",
"MO1",
diff --git a/code/datums/skills/uscm.dm b/code/datums/skills/uscm.dm
index 7e6658d9c1..c2d05bd786 100644
--- a/code/datums/skills/uscm.dm
+++ b/code/datums/skills/uscm.dm
@@ -8,6 +8,10 @@ United States Colonial Marines
name = "Private"
//same as default
+/datum/skills/pfc/recon
+ name = "Recon Private"
+ skills = list(SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED)
+
/datum/skills/combat_medic
name = "Combat Medic"
skills = list(
@@ -16,6 +20,15 @@ United States Colonial Marines
SKILL_JTAC = SKILL_JTAC_BEGINNER,
)
+/datum/skills/combat_medic/recon
+ name = "Recon Medic"
+ skills = list(
+ SKILL_MEDICAL = SKILL_MEDICAL_MEDIC,
+ SKILL_SURGERY = SKILL_SURGERY_NOVICE,
+ SKILL_JTAC = SKILL_JTAC_BEGINNER,
+ SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED,
+ )
+
/datum/skills/combat_engineer
name = "Combat Engineer"
skills = list(
@@ -32,6 +45,16 @@ United States Colonial Marines
SKILL_JTAC = SKILL_JTAC_BEGINNER,
)
+/datum/skills/smartgunner/recon
+ name = "Recon Smartgunner"
+ skills = list(
+ SKILL_SPEC_WEAPONS = SKILL_SPEC_SMARTGUN,
+ SKILL_JTAC = SKILL_JTAC_BEGINNER,
+ SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED,
+ )
+
+
+
/datum/skills/specialist
name = "Squad Weapons Specialist"
skills = list(
@@ -51,6 +74,15 @@ United States Colonial Marines
SKILL_LEADERSHIP = SKILL_LEAD_TRAINED,
)
+/datum/skills/tl/recon
+ name = "Recon Fireteam Leader"
+ skills = list(
+ SKILL_JTAC = SKILL_JTAC_TRAINED,
+ SKILL_LEADERSHIP = SKILL_LEAD_TRAINED,
+ SKILL_ENDURANCE = SKILL_ENDURANCE_TRAINED,
+ )
+
+
/datum/skills/SL
name = "Squad Leader"
skills = list(
diff --git a/code/datums/soundOutput.dm b/code/datums/soundOutput.dm
index 1f4512b28d..85548d6c90 100644
--- a/code/datums/soundOutput.dm
+++ b/code/datums/soundOutput.dm
@@ -13,7 +13,7 @@
. = ..()
/datum/soundOutput/proc/process_sound(datum/sound_template/T)
- var/sound/S = sound(T.file, T.wait, T.repeat)
+ var/sound/S = sound(T.file, T.repeat, T.wait)
S.volume = owner.volume_preferences[T.volume_cat] * T.volume
if(T.channel == 0)
S.channel = get_free_channel()
@@ -21,6 +21,8 @@
S.channel = T.channel
S.frequency = T.frequency
S.falloff = T.falloff
+ S.offset = T.offset
+ S.pitch = T.pitch
S.status = T.status
S.echo = T.echo
if(T.x && T.y && T.z)
diff --git a/code/datums/vehicles.dm b/code/datums/vehicles.dm
index ac71cc1fc7..9370909eba 100644
--- a/code/datums/vehicles.dm
+++ b/code/datums/vehicles.dm
@@ -30,6 +30,10 @@
name = "Movie APC"
interior_id = "apc_movie"
+/datum/map_template/interior/arc
+ name = "ARC"
+ interior_id = "arc"
+
/datum/map_template/interior/fancy_locker
name = "Fancy Locker"
interior_id = "fancylocker"
@@ -38,6 +42,10 @@
name = "Tank"
interior_id = "tank"
+/datum/map_template/interior/aev
+ name = "AEV"
+ interior_id = "aev"
+
/datum/map_template/interior/van
name = "Van"
interior_id = "van"
diff --git a/code/defines/procs/records.dm b/code/defines/procs/records.dm
index a1e2ade2b7..b4612f10f4 100644
--- a/code/defines/procs/records.dm
+++ b/code/defines/procs/records.dm
@@ -6,7 +6,7 @@
G.fields["real_rank"] = "Unassigned"
G.fields["sex"] = "Male"
G.fields["age"] = "Unknown"
- G.fields["ethnicity"] = "Unknown"
+ G.fields["skin_color"] = "Unknown"
G.fields["p_stat"] = "Active"
G.fields["m_stat"] = "Stable"
G.fields["species"] = "Human"
diff --git a/code/game/area/golden_arrow.dm b/code/game/area/golden_arrow.dm
index 7c35640d06..20d67e3bad 100644
--- a/code/game/area/golden_arrow.dm
+++ b/code/game/area/golden_arrow.dm
@@ -54,6 +54,10 @@
name = "\improper Platoon Sergeant Office"
icon_state = "alpha"
+/area/golden_arrow/shared_office
+ name = "\improper Shared Office"
+ icon_state = "alpha"
+
/area/golden_arrow/squad_one
name = "\improper Squad One Prep"
icon_state = "charlie"
@@ -69,3 +73,11 @@
/area/golden_arrow/firingrange
name = "\improper Firing Range"
icon_state = "firingrange"
+
+/area/golden_arrow/platoonprep
+ name = "\improper Platoon Prep"
+ icon_state = "bravo"
+
+/area/golden_arrow/platoonarmory
+ name = "\improper Platoon Armory"
+ icon_state = "alpha"
diff --git a/code/game/gamemodes/colonialmarines/ai/colonialmarines_ai.dm b/code/game/gamemodes/colonialmarines/ai/colonialmarines_ai.dm
index b1fe23d236..9fe36dec66 100644
--- a/code/game/gamemodes/colonialmarines/ai/colonialmarines_ai.dm
+++ b/code/game/gamemodes/colonialmarines/ai/colonialmarines_ai.dm
@@ -77,6 +77,9 @@
/datum/game_mode/colonialmarines/ai/get_roles_list()
return GLOB.platoon_to_role_list[MAIN_SHIP_PLATOON]
+/datum/game_mode/colonialmarines/ai/check_queen_status()
+ return
+
GLOBAL_LIST_INIT(platoon_to_jobs, list(/datum/squad/marine/alpha = list(/datum/job/command/bridge/ai = JOB_SO,\
/datum/job/marine/leader/ai = JOB_SQUAD_LEADER,\
/datum/job/marine/medic/ai = JOB_SQUAD_MEDIC,\
diff --git a/code/game/machinery/ARES/ARES_procs.dm b/code/game/machinery/ARES/ARES_procs.dm
index 831f76e489..64a30a230d 100644
--- a/code/game/machinery/ARES/ARES_procs.dm
+++ b/code/game/machinery/ARES/ARES_procs.dm
@@ -174,6 +174,8 @@ GLOBAL_LIST_INIT(maintenance_categories, list(
return ARES_ACCESS_HIGH
if(card.paygrade in GLOB.co_paygrades)
return ARES_ACCESS_CO
+ if(card.paygrade in GLOB.platco_paygrades)
+ return ARES_ACCESS_COMMAND
if(ACCESS_MARINE_SENIOR in card.access)
return ARES_ACCESS_SENIOR
if(ACCESS_WY_GENERAL in card.access)
diff --git a/code/game/machinery/autolathe.dm b/code/game/machinery/autolathe.dm
index 6ccb0b5b18..76ff409669 100644
--- a/code/game/machinery/autolathe.dm
+++ b/code/game/machinery/autolathe.dm
@@ -79,7 +79,7 @@
recipe.resources[material] = I.matter[material] //Doesn't take more if it's just a sheet or something. Get what you put in.
else
recipe.resources[material] = round(I.matter[material]*1.25) // More expensive to produce than they are to recycle.
- qdel(I)
+ QDEL_NULL(I)
//Create parts for lathe.
for(var/component in components)
diff --git a/code/game/machinery/doors/brig_system.dm b/code/game/machinery/doors/brig_system.dm
index 58c3282abe..f36e5b72c2 100644
--- a/code/game/machinery/doors/brig_system.dm
+++ b/code/game/machinery/doors/brig_system.dm
@@ -279,7 +279,7 @@
var/obj/item/card/id/id_card = human.get_idcard()
if (id_card)
- if ((id_card.paygrade in GLOB.co_paygrades) || (id_card.paygrade in GLOB.highcom_paygrades) || (id_card.paygrade == "PvI"))
+ if ((id_card.paygrade in GLOB.co_paygrades) || ((id_card.paygrade in GLOB.platco_paygrades)) || (id_card.paygrade in GLOB.highcom_paygrades) || (id_card.paygrade == "PvI"))
return TRUE
return FALSE
diff --git a/code/game/machinery/vending/vendor_types/medical.dm b/code/game/machinery/vending/vendor_types/medical.dm
index d0bcf73778..52d4e98396 100644
--- a/code/game/machinery/vending/vendor_types/medical.dm
+++ b/code/game/machinery/vending/vendor_types/medical.dm
@@ -165,6 +165,7 @@
list("Pill Bottle (Kelotane)", round(scale * 3), /obj/item/storage/pill_bottle/kelotane, VENDOR_ITEM_REGULAR),
list("Pill Bottle (Peridaxon)", round(scale * 2), /obj/item/storage/pill_bottle/peridaxon, VENDOR_ITEM_REGULAR),
list("Pill Bottle (Tramadol)", round(scale * 3), /obj/item/storage/pill_bottle/tramadol, VENDOR_ITEM_REGULAR),
+ list("Pill Bottle (Tricordazine)", round(scale * 3), /obj/item/storage/pill_bottle/tricord, VENDOR_ITEM_REGULAR),
list("MEDICAL UTILITIES", -1, null, null),
list("Surgical Line", round(scale * 2), /obj/item/tool/surgery/surgical_line, VENDOR_ITEM_REGULAR),
diff --git a/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm b/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm
index 1bfa2792de..465edd3c24 100644
--- a/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm
+++ b/code/game/machinery/vending/vendor_types/squad_prep/squad_medic.dm
@@ -157,7 +157,6 @@ GLOBAL_LIST_INIT(cm_vending_clothing_medic, list(
list("Pressurized Reagent Canister Pouch (Bicaridine)", 0, /obj/item/storage/pouch/pressurized_reagent_canister/bicaridine, MARINE_CAN_BUY_POUCH, VENDOR_ITEM_RECOMMENDED),
list("Pressurized Reagent Canister Pouch (Kelotane)", 0, /obj/item/storage/pouch/pressurized_reagent_canister/kelotane, MARINE_CAN_BUY_POUCH, VENDOR_ITEM_RECOMMENDED),
list("Pressurized Reagent Canister Pouch (Tricordrazine)", 0, /obj/item/storage/pouch/pressurized_reagent_canister/tricordrazine, MARINE_CAN_BUY_POUCH, VENDOR_ITEM_RECOMMENDED),
- list("Pressurized Reagent Canister Pouch (EMPTY)", 0, /obj/item/storage/pouch/pressurized_reagent_canister, MARINE_CAN_BUY_POUCH, VENDOR_ITEM_RECOMMENDED),
list("Pistol Pouch", 0, /obj/item/storage/pouch/pistol, MARINE_CAN_BUY_POUCH, VENDOR_ITEM_REGULAR),
list("Vial Pouch (Full)", 0, /obj/item/storage/pouch/vials/full, MARINE_CAN_BUY_POUCH, VENDOR_ITEM_REGULAR),
@@ -336,3 +335,33 @@ GLOBAL_LIST_INIT(cm_vending_clothing_forecon_medic, list(
/obj/structure/machinery/cm_vending/clothing/medic/forecon/get_listed_products(mob/user)
return GLOB.cm_vending_clothing_forecon_medic
+
+
+// Chemical vendor
+
+GLOBAL_LIST_INIT(cm_vending_chemical_medic, list(
+ list("PILL BOTTLES", 0, null, null, null),
+ list("Pill Bottle (Imidazoline-Alkysine)", 40, /obj/item/storage/pill_bottle/imialk, null, VENDOR_ITEM_REGULAR),
+ list("Pill Bottle (Meralyne-Bicardine)", 40, /obj/item/storage/pill_bottle/merabica, null, VENDOR_ITEM_REGULAR),
+ list("Pill Bottle (Kelotane-Dermaline)", 40, /obj/item/storage/pill_bottle/keloderm, null, VENDOR_ITEM_REGULAR),
+ list("Pill Bottle (Nitrogen-Water)", 40, /obj/item/storage/pill_bottle/nitrogenwater, null, VENDOR_ITEM_REGULAR),
+ list("Pill Bottle (Dexalin+)", 40, /obj/item/storage/pill_bottle/dexalinplus, null, VENDOR_ITEM_REGULAR),
+ list("Pill Bottle (Iron)", 40, /obj/item/storage/pill_bottle/iron, null, VENDOR_ITEM_REGULAR),
+ ))
+
+/obj/structure/machinery/cm_vending/gear/medic_chemical
+ name = "\improper ColMarTech Squad Medical Chemical Rack"
+ desc = "An automated gear rack for specialized chemicals for the hospital corpsman."
+ icon_state = "med_chem"
+ show_points = TRUE
+ use_snowflake_points = TRUE
+ vendor_role = list(JOB_SQUAD_MEDIC)
+ req_access = list(ACCESS_MARINE_MEDPREP)
+
+/obj/structure/machinery/cm_vending/gear/medic_chemical/get_listed_products(mob/user)
+ return GLOB.cm_vending_chemical_medic
+
+/obj/structure/machinery/cm_vending/gear/medic_chemical/upp
+ name = "\improper UnTech Squad Medical Equipment Rack"
+ req_access = list(ACCESS_UPP_MEDPREP)
+ vendor_theme = VENDOR_THEME_UPP
diff --git a/code/game/objects/effects/effect.dm b/code/game/objects/effects/effect.dm
index 96de3cd931..a1f0721484 100644
--- a/code/game/objects/effects/effect.dm
+++ b/code/game/objects/effects/effect.dm
@@ -1,5 +1,6 @@
/obj/effect
icon = 'icons/effects/effects.dmi'
+ blocks_emissive = EMISSIVE_BLOCK_GENERIC
/obj/effect/get_applying_acid_time()
return -1
diff --git a/code/game/objects/effects/effect_system/smoke.dm b/code/game/objects/effects/effect_system/smoke.dm
index b80f53b14d..4cd3c4a37d 100644
--- a/code/game/objects/effects/effect_system/smoke.dm
+++ b/code/game/objects/effects/effect_system/smoke.dm
@@ -258,6 +258,7 @@
var/xeno_affecting = FALSE
opacity = FALSE
alpha = 75
+ time_to_live = 20
/obj/effect/particle_effect/smoke/cn20/xeno
name = "CN20-X nerve gas"
@@ -276,10 +277,14 @@
/obj/effect/particle_effect/smoke/cn20/affect(mob/living/carbon/creature)
var/mob/living/carbon/xenomorph/xeno_creature
var/mob/living/carbon/human/human_creature
+ var/datum/internal_organ/lungs/lungs
+ var/datum/internal_organ/eyes/eyes
if(isxeno(creature))
xeno_creature = creature
else if(ishuman(creature))
human_creature = creature
+ lungs = human_creature.internal_organs_by_name["lungs"]
+ eyes = human_creature.internal_organs_by_name["eyes"]
if(!istype(creature) || issynth(creature) || creature.stat == DEAD)
return FALSE
if(!xeno_affecting && xeno_creature)
@@ -297,14 +302,18 @@
if(xeno_creature)
if(xeno_creature.interference < 4)
to_chat(xeno_creature, SPAN_XENOHIGHDANGER("Your awareness dims to a small area!"))
+ creature.apply_damage(20, BRUTE)
xeno_creature.interference = 10
xeno_creature.blinded = TRUE
else
- creature.apply_damage(12, OXY)
+ creature.apply_damage(12, TOX)
+ creature.apply_damage(2, BRAIN)
+ lungs.take_damage(2)
creature.SetEarDeafness(max(creature.ear_deaf, round(effect_amt*1.5))) //Paralysis of hearing system, aka deafness
- if(!xeno_creature && !creature.eye_blind) //Eye exposure damage
+ if(!xeno_creature) //Eye exposure damage
to_chat(creature, SPAN_DANGER("Your eyes sting. You can't see!"))
creature.SetEyeBlind(round(effect_amt/3))
+ eyes.take_damage(2)
if(!xeno_creature && creature.coughedtime != 1 && !creature.stat) //Coughing/gasping
creature.coughedtime = 1
if(prob(50))
@@ -330,6 +339,40 @@
human_creature.recalculate_move_delay = TRUE
return TRUE
+/////////////////////////////////////////////
+// ALD-91 LSD Gas
+/////////////////////////////////////////////
+
+/obj/effect/particle_effect/smoke/LSD
+ name = "ALD-91 LSD Gas"
+ smokeranking = SMOKE_RANK_HIGH
+ color = "#6e006e"
+ opacity = FALSE
+ alpha = 75
+ time_to_live = 20
+ var/stun_chance = 60
+
+/obj/effect/particle_effect/smoke/LSD/Move()
+ . = ..()
+ for(var/mob/living/carbon/human/human in get_turf(src))
+ affect(human)
+
+/obj/effect/particle_effect/smoke/LSD/affect(mob/living/carbon/human/creature)
+ if(!istype(creature) || issynth(creature) || creature.stat == DEAD || isyautja(creature))
+ return FALSE
+
+ if(creature.wear_mask && (creature.wear_mask.flags_inventory & BLOCKGASEFFECT))
+ return FALSE
+ if(creature.head.flags_inventory & BLOCKGASEFFECT)
+ return FALSE
+
+ creature.hallucination += 15
+ creature.druggy += 1
+
+ if(prob(stun_chance))
+ creature.apply_effect(1, WEAKEN)
+
+
//////////////////////////////////////
// FLASHBANG SMOKE
////////////////////////////////////
@@ -633,6 +676,9 @@
/datum/effect_system/smoke_spread/cn20/xeno
smoke_type = /obj/effect/particle_effect/smoke/cn20/xeno
+/datum/effect_system/smoke_spread/LSD
+ smoke_type = /obj/effect/particle_effect/smoke/LSD
+
// XENO SMOKES
/datum/effect_system/smoke_spread/xeno_acid
diff --git a/code/game/objects/effects/temporary_visuals.dm b/code/game/objects/effects/temporary_visuals.dm
index 4dc07b76f3..1f3800fa01 100644
--- a/code/game/objects/effects/temporary_visuals.dm
+++ b/code/game/objects/effects/temporary_visuals.dm
@@ -96,3 +96,25 @@
splatter_type = "csplatter"
color = BLOOD_COLOR_SYNTHETIC
+//------------------------------------------
+//Shockwaves
+//------------------------------------------
+
+/obj/effect/shockwave
+ icon = 'icons/effects/light_overlays/shockwave.dmi'
+ icon_state = "shockwave"
+ plane = DISPLACEMENT_PLATE_RENDER_LAYER
+ pixel_x = -496
+ pixel_y = -496
+
+/obj/effect/shockwave/Initialize(mapload, radius, speed, easing_type = LINEAR_EASING, y_offset, x_offset)
+ . = ..()
+ if(!speed)
+ speed = 1
+ if(y_offset)
+ pixel_y += y_offset
+ if(x_offset)
+ pixel_x += x_offset
+ QDEL_IN(src, 0.5 * radius * speed)
+ transform = matrix().Scale(32 / 1024, 32 / 1024)
+ animate(src, time = 0.5 * radius * speed, transform=matrix().Scale((32 / 1024) * radius * 1.5, (32 / 1024) * radius * 1.5), easing = easing_type)
diff --git a/code/game/objects/items/devices/personal_data_transmitter.dm b/code/game/objects/items/devices/personal_data_transmitter.dm
index 6e8aa001ca..2e92b3f0b0 100644
--- a/code/game/objects/items/devices/personal_data_transmitter.dm
+++ b/code/game/objects/items/devices/personal_data_transmitter.dm
@@ -139,6 +139,7 @@
/obj/item/device/pdt_locator_tube/Destroy()
linked_bracelet = null
+ QDEL_NULL(battery)
return ..()
/obj/item/clothing/accessory/pdt_bracelet
diff --git a/code/game/objects/items/explosives/grenades/marines.dm b/code/game/objects/items/explosives/grenades/marines.dm
index 46d2d4eba9..36ba614041 100644
--- a/code/game/objects/items/explosives/grenades/marines.dm
+++ b/code/game/objects/items/explosives/grenades/marines.dm
@@ -484,7 +484,7 @@
/// The typepath of the nerve gas
var/nerve_gas_type = /datum/effect_system/smoke_spread/cn20
/// The radius the gas will reach
- var/nerve_gas_radius = 2
+ var/nerve_gas_radius = 4
/obj/item/explosive/grenade/nerve_gas/Initialize(mapload, ...)
. = ..()
@@ -505,6 +505,38 @@
name = "\improper CN20-X canister grenade"
nerve_gas_type = /datum/effect_system/smoke_spread/cn20/xeno
+/*
+//================================================
+ LSD Gas Grenades
+//================================================
+*/
+/obj/item/explosive/grenade/LSD
+ name = "\improper ALD-91 canister grenade"
+ desc = "A canister grenade of nonlethal LSD gas. It is set to detonate in 4 seconds."
+ icon_state = "flashbang2"//temp icon
+ det_time = 40
+ item_state = "grenade_phos_clf"//temp icon
+ underslug_launchable = FALSE
+ harmful = TRUE
+ antigrief_protection = FALSE
+ var/datum/effect_system/smoke_spread/LSD/LSD_gas
+ var/LSD_gas_radius = 4
+
+/obj/item/explosive/grenade/LSD/Initialize()
+ . = ..() //if it ain't broke don't fix it
+ LSD_gas = new /datum/effect_system/smoke_spread/LSD
+ LSD_gas.attach(src)
+
+/obj/item/explosive/grenade/LSD/Destroy()
+ QDEL_NULL(LSD_gas)
+ return ..()
+
+/obj/item/explosive/grenade/LSD/prime()
+ playsound(src.loc, 'sound/effects/smoke.ogg', 25, 1, 4)
+ LSD_gas.set_up(LSD_gas_radius, 0, get_turf(src), null, 6)
+ LSD_gas.start()
+ qdel(src)
+
/*
//================================================
Airburst Smoke Grenades
diff --git a/code/game/objects/items/pamphlets.dm b/code/game/objects/items/pamphlets.dm
index 682215be67..c1544d6d73 100644
--- a/code/game/objects/items/pamphlets.dm
+++ b/code/game/objects/items/pamphlets.dm
@@ -200,7 +200,7 @@
to_chat(user, SPAN_WARNING("You know this already!"))
return FALSE
- if(user.job != JOB_SQUAD_MARINE)
+ if(!(user.job in JOB_SQUAD_ROLES_LIST))
to_chat(user, SPAN_WARNING("Only squad riflemen can use this."))
return FALSE
diff --git a/code/game/objects/items/props/helmetgarb.dm b/code/game/objects/items/props/helmetgarb.dm
index 35558bf019..ce63aaa13a 100644
--- a/code/game/objects/items/props/helmetgarb.dm
+++ b/code/game/objects/items/props/helmetgarb.dm
@@ -496,6 +496,30 @@
desc = "The USCM had its funding pulled for these when it became apparent that not every deployed enlisted was wearing a helmet 24/7; much to the bafflement of UA High Command."
icon_state = "helmet_gasmask"
+/obj/item/prop/helmetgarb/helmet_gasmask/on_enter_storage(obj/item/storage/internal/helmet_internal_inventory)
+ ..()
+ if(!istype(helmet_internal_inventory))
+ return
+ var/obj/item/clothing/head/helmet/helmet_item = helmet_internal_inventory.master_object
+
+ if(!istype(helmet_item))
+ return
+
+ helmet_item.flags_inventory |= BLOCKGASEFFECT
+ helmet_item.flags_inv_hide |= HIDEFACE
+
+/obj/item/prop/helmetgarb/helmet_gasmask/on_exit_storage(obj/item/storage/internal/helmet_internal_inventory)
+ ..()
+ if(!istype(helmet_internal_inventory))
+ return
+ var/obj/item/clothing/head/helmet/helmet_item = helmet_internal_inventory.master_object
+
+ if(!istype(helmet_item))
+ return
+
+ helmet_item.flags_inventory &= ~(BLOCKGASEFFECT)
+ helmet_item.flags_inv_hide &= ~(HIDEFACE)
+
/obj/item/prop/helmetgarb/trimmed_wire
name = "trimmed barbed wire"
desc = "It is a length of barbed wire that's had most of the sharp points filed down so that it is safe to handle."
diff --git a/code/game/objects/items/reagent_containers/food/cans.dm b/code/game/objects/items/reagent_containers/food/cans.dm
index aab2ee066e..d6d1b9b423 100644
--- a/code/game/objects/items/reagent_containers/food/cans.dm
+++ b/code/game/objects/items/reagent_containers/food/cans.dm
@@ -270,7 +270,7 @@
/obj/item/reagent_container/food/drinks/cans/boda
name = "\improper Boda"
desc = "State regulated soda beverage. Enjoy comrades."
- desc_lore = "Designed back in 2159, the advertising campaign for BODA started out as an attempt by the UPP to win the hearts and minds of colonists and settlers across the galaxy. Soon after, the ubiquitous cyan vendors and large supplies of the drink began to crop up in UA warehouses with seemingly no clear origin. Despite some concerns, after initial testing determined that the stored products were safe for consumption and surprisingly popular when blind-tested with focus groups, the strange surplus of BODA was authorized for usage within the UA-associated colonies. Subsequently, it enjoyed a relative popularity before falling into obscurity in the coming decades as supplies dwindled."
+ desc_lore = "Designed back in 2159, the advertising campaign for BODA started out as an attempt by the UPP to win the hearts and minds of colonists and settlers across the galaxy. Soon after, the ubiquitous cyan vendors and large supplies of the drink began to crop up in UA warehouses with seemingly no clear origin. Despite some concerns, after initial testing determined that the stored products were safe for consumption and surprisingly popular when blind-tested with focus groups, the strange surplus of BODA was authorized for usage within the UA-associated colonies. Subsequently, it enjoyed a relative popularity before falling into obscurity in the coming decades as supplies dwindled."
icon_state = "boda"
center_of_mass = "x=16;y=10"
@@ -347,10 +347,6 @@
icon_state = "souto_diet_classic"
item_state = "souto_diet_classic"
-/obj/item/reagent_container/food/drinks/cans/souto/diet/Initialize()
- . = ..()
- reagents.add_reagent("water", 25)
-
/obj/item/reagent_container/food/drinks/cans/souto/classic
name = "\improper Souto Classic"
desc = "The can boldly proclaims it to be tangerine flavored. You can't help but think that's a lie. Canned in Havana."
@@ -359,7 +355,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/classic/Initialize()
. = ..()
- reagents.add_reagent("souto_classic", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_classic", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/classic
name = "\improper Diet Souto"
@@ -369,7 +367,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/classic/Initialize()
. = ..()
- reagents.add_reagent("souto_classic", 25)
+ reagents.add_reagent("sucralose", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_classic", 20)
/obj/item/reagent_container/food/drinks/cans/souto/cherry
name = "\improper Cherry Souto"
@@ -379,7 +379,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/cherry/Initialize()
. = ..()
- reagents.add_reagent("souto_cherry", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_cherry", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/cherry
name = "\improper Diet Cherry Souto"
@@ -389,7 +391,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/cherry/Initialize()
. = ..()
- reagents.add_reagent("souto_cherry", 25)
+ reagents.add_reagent("sucralose", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_cherry", 20)
/obj/item/reagent_container/food/drinks/cans/souto/lime
name = "\improper Lime Souto"
@@ -399,7 +403,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/lime/Initialize()
. = ..()
- reagents.add_reagent("souto_lime", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_lime", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/lime
name = "\improper Diet Lime Souto"
@@ -409,7 +415,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/lime/Initialize()
. = ..()
- reagents.add_reagent("souto_lime", 25)
+ reagents.add_reagent("sucralose", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_lime", 20)
/obj/item/reagent_container/food/drinks/cans/souto/grape
name = "\improper Grape Souto"
@@ -419,7 +427,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/grape/Initialize()
. = ..()
- reagents.add_reagent("souto_grape", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_grape", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/grape
name = "\improper Diet Grape Souto"
@@ -429,7 +439,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/grape/Initialize()
. = ..()
- reagents.add_reagent("souto_grape", 25)
+ reagents.add_reagent("sucralose", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_grape", 20)
/obj/item/reagent_container/food/drinks/cans/souto/blue
name = "\improper Blue Raspberry Souto"
@@ -440,7 +452,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/blue/Initialize()
. = ..()
- reagents.add_reagent("souto_blueraspberry", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_blueraspberry", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/blue
name = "\improper Diet Blue Raspberry Souto"
@@ -450,7 +464,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/blue/Initialize()
. = ..()
- reagents.add_reagent("souto_blueraspberry", 25)
+ reagents.add_reagent("sucralose", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_blueraspberry", 20)
/obj/item/reagent_container/food/drinks/cans/souto/peach
name = "\improper Peach Souto"
@@ -460,7 +476,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/peach/Initialize()
. = ..()
- reagents.add_reagent("souto_peach", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_peach", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/peach
name = "\improper Diet Peach Souto"
@@ -470,7 +488,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/peach/Initialize()
. = ..()
- reagents.add_reagent("souto_peach", 25)
+ reagents.add_reagent("sucralose", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_peach", 20)
/obj/item/reagent_container/food/drinks/cans/souto/cranberry
name = "\improper Cranberry Souto"
@@ -480,7 +500,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/cranberry/Initialize()
. = ..()
- reagents.add_reagent("souto_cranberry", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_cranberry", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/cranberry
name = "\improper Diet Cranberry Souto"
@@ -490,8 +512,9 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/cranberry/Initialize()
. = ..()
- reagents.add_reagent("souto_cranberry", 25)
- reagents.add_reagent("water", 25)
+ reagents.add_reagent("sucralose", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_cranberry", 20)
/obj/item/reagent_container/food/drinks/cans/souto/vanilla
name = "\improper Vanilla Souto"
@@ -511,29 +534,32 @@
/obj/item/reagent_container/food/drinks/cans/souto/diet/vanilla/Initialize()
. = ..()
+ reagents.add_reagent("sodawater", 25)
reagents.add_reagent("souto_vanilla", 25)
- reagents.add_reagent("water", 25)
/obj/item/reagent_container/food/drinks/cans/souto/pineapple
name = "\improper Pineapple Souto"
- desc = "This tastes like battery acid with a full cup of sugar mixed in. Canned in Havana."
+ desc = "This tastes like battery acid with a full cup of corn syrup mixed in. Canned in Havana."
icon_state = "souto_pineapple"
item_state = "souto_pineapple"
/obj/item/reagent_container/food/drinks/cans/souto/pineapple/Initialize()
. = ..()
- reagents.add_reagent("souto_pineapple", 50)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_pineapple", 20)
/obj/item/reagent_container/food/drinks/cans/souto/diet/pineapple
name = "\improper Diet Pineapple Souto"
- desc = "This tastes like battery acid with a half cup of sugar mixed in. Canned in Havana."
+ desc = "This tastes like battery acid with a full cup of sugar substitute mixed in. Canned in Havana."
icon_state = "souto_diet_pineapple"
item_state = "souto_diet_pineapple"
/obj/item/reagent_container/food/drinks/cans/souto/diet/pineapple/Initialize()
. = ..()
- reagents.add_reagent("souto_pineapple", 25)
- reagents.add_reagent("water", 25)
+ reagents.add_reagent("cornsyrup", 15)
+ reagents.add_reagent("sodawater", 15)
+ reagents.add_reagent("souto_pineapple", 20)
//ASPEN
diff --git a/code/game/objects/items/reagent_containers/food/snacks.dm b/code/game/objects/items/reagent_containers/food/snacks.dm
index 8aae33b5f2..d27d6adb09 100644
--- a/code/game/objects/items/reagent_containers/food/snacks.dm
+++ b/code/game/objects/items/reagent_containers/food/snacks.dm
@@ -2760,6 +2760,10 @@
var/list/boxes = list() // If the boxes are stacked, they come here
var/boxtag = ""
+/obj/item/pizzabox/Destroy(force)
+ QDEL_NULL(pizza)
+ return ..()
+
/obj/item/pizzabox/update_icon()
overlays = list()
diff --git a/code/game/objects/items/reagent_containers/pill.dm b/code/game/objects/items/reagent_containers/pill.dm
index 6c71d8be3c..d82f3bb338 100644
--- a/code/game/objects/items/reagent_containers/pill.dm
+++ b/code/game/objects/items/reagent_containers/pill.dm
@@ -178,6 +178,11 @@
pill_initial_reagents = list("kelotane" = 15)
pill_icon_class = "kelo"
+/obj/item/reagent_container/pill/keloderm
+ pill_desc = "A Kelotane-Dermaline pill. Used to rapidly treat burns."
+ pill_initial_reagents = list("kelotane" = 10, "dermaline" = 10)
+ pill_icon_class = "kelo"
+
/obj/item/reagent_container/pill/oxycodone
pill_desc = "A Oxycodone pill. A powerful painkiller."
pill_initial_reagents = list("oxycodone" = 15)
@@ -213,6 +218,11 @@
pill_initial_reagents = list("dexalin" = 15)
pill_icon_class = "dex"
+/obj/item/reagent_container/pill/dexalinplus
+ pill_desc = "A Dexalin+ pill. Used to instantly treat oxygen deprivation."
+ pill_initial_reagents = list("dexalinp" = 10)
+ pill_icon_class = "qc"
+
/obj/item/reagent_container/pill/spaceacillin
pill_desc = "A Spaceacillin pill. Used to slow down viral infections."
pill_initial_reagents = list("spaceacillin" = 10)
@@ -248,11 +258,21 @@
pill_initial_reagents = list("alkysine" = 10)
pill_icon_class = "alky"
+/obj/item/reagent_container/pill/imialk
+ pill_desc = "A pill containing Imidazoline and Alkysine, used to heal brain and ear damage."
+ pill_initial_reagents = list("imidazoline" = 10, "alkysine" = 10)
+ pill_icon_class = "imi"
+
/obj/item/reagent_container/pill/bicaridine
pill_desc = "A Bicaridine pill. Heals brute damage."
pill_initial_reagents = list("bicaridine" = 15)
pill_icon_class = "bica"
+/obj/item/reagent_container/pill/merabica
+ pill_desc = "A Meralyne-Bicaridine pill. Rapidly heals brute damage."
+ pill_initial_reagents = list("bicaridine" = 10, "meralyne" = 10)
+ pill_icon_class = "bica"
+
/obj/item/reagent_container/pill/ultrazine
pill_desc = "An Ultrazine pill. A highly-potent, long-lasting combination CNS and muscle stimulant. Extremely addictive."
pill_initial_reagents = list("ultrazine" = 5)
@@ -269,3 +289,13 @@
/obj/item/reagent_container/pill/stimulant
pill_initial_reagents = list("antag_stimulant" = 10)
pill_icon_class = "stim"
+
+/obj/item/reagent_container/pill/iron
+ pill_desc = "An iron pill. Used to regenerate blood."
+ pill_initial_reagents = list("iron" = 15)
+ pill_icon_class = "spac"
+
+/obj/item/reagent_container/pill/nitrogenwater
+ pill_desc = "A pill containing nitrogen and water. Used to treat Tramadol overdoses."
+ pill_initial_reagents = list("nitrogen" = 15, "water" = 15)
+ pill_icon_class = "spac"
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index c798acd047..693acd61ef 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -611,6 +611,38 @@
/obj/item/storage/belt/marine/smartgunner/standard
has_gamemode_skin = FALSE
+/obj/item/storage/belt/marine/smartgunner/upp
+ name = "\improper Type 90 pattern machinegunner belt"
+ desc = "Recently adopted by UPP military, this belt allows machinegunners to carry more ammo boxes into battle. It also found use with SOF breachers using Type 23 shotguns."
+ icon_state = "upp_machinegun"
+ item_state = "upp_machinegun"
+ storage_slots = 7
+ bypass_w_limit = list(
+ /obj/item/ammo_magazine/pkp,
+ )
+ can_hold = list(
+ /obj/item/attachable/bayonet,
+ /obj/item/device/flashlight/flare,
+ /obj/item/ammo_magazine/pkp,
+ /obj/item/ammo_magazine/rifle,
+ /obj/item/ammo_magazine/smg,
+ /obj/item/ammo_magazine/pistol,
+ /obj/item/ammo_magazine/revolver,
+ /obj/item/ammo_magazine/sniper,
+ /obj/item/ammo_magazine/handful,
+ /obj/item/explosive/grenade,
+ /obj/item/explosive/mine,
+ /obj/item/reagent_container/food/snacks,
+ )
+
+ has_gamemode_skin = FALSE
+ item_state_slots = list(
+ WEAR_L_HAND = "upp_belt",
+ WEAR_R_HAND = "upp_belt")
+
+/obj/item/storage/belt/marine/smartgunner/upp/fill_preset_inventory()
+ return
+
/obj/item/storage/belt/marine/quackers
name = "Mr. Quackers"
desc = "What are we going to do today, Mr. Quackers?"
@@ -1721,6 +1753,26 @@
new /obj/item/ammo_magazine/smartgun(src)
new /obj/item/ammo_magazine/smartgun(src)
+/obj/item/storage/belt/gun/smartgunner/upp
+ name = "\improper Type 92 pattern machinegunner sidearm rig"
+ desc = "Type 92 is an experimental ammo-carrying rig issued to UPP machinegunners which combines a sidearm holster with box-shaped pouches for limited storage."
+ icon_state = "upp_machinegun_pistol"
+ storage_slots = 7
+ can_hold = list(
+ /obj/item/weapon/gun/pistol/t73,
+ /obj/item/ammo_magazine/pistol/t73,
+ /obj/item/ammo_magazine/pistol/t73_impact,
+ /obj/item/weapon/gun/pistol/np92,
+ /obj/item/ammo_magazine/pistol/np92,
+ /obj/item/weapon/gun/revolver/upp,
+ /obj/item/ammo_magazine/revolver/upp,
+ /obj/item/ammo_magazine/pkp,
+ )
+ has_gamemode_skin = FALSE
+ item_state_slots = list(
+ WEAR_L_HAND = "upp_belt",
+ WEAR_R_HAND = "upp_belt")
+
/obj/item/storage/belt/gun/mortarbelt
name="\improper M276 pattern mortar operator belt"
desc="An M276 load-bearing rig configured to carry ammunition for the M402 mortar, along with a sidearm."
diff --git a/code/game/objects/items/storage/firstaid.dm b/code/game/objects/items/storage/firstaid.dm
index 2514e2e5f1..49f790410c 100644
--- a/code/game/objects/items/storage/firstaid.dm
+++ b/code/game/objects/items/storage/firstaid.dm
@@ -536,6 +536,12 @@
/obj/item/storage/pill_bottle/kelotane/skillless
skilllock = SKILL_MEDICAL_DEFAULT
+/obj/item/storage/pill_bottle/keloderm
+ name = "\improper Kelotane-Dermaline pill bottle"
+ icon_state = "pill_canister15"
+ pill_type_to_fill = /obj/item/reagent_container/pill/keloderm
+ maptext_label = "KD"
+
/obj/item/storage/pill_bottle/antitox
name = "\improper Dylovene pill bottle"
icon_state = "pill_canister6"
@@ -582,6 +588,12 @@
/obj/item/storage/pill_bottle/bicaridine/skillless
skilllock = SKILL_MEDICAL_DEFAULT
+/obj/item/storage/pill_bottle/merabica
+ name = "\improper Meralyne-Bicaridine pill bottle"
+ icon_state = "pill_canister8"
+ pill_type_to_fill = /obj/item/reagent_container/pill/merabica
+ maptext_label = "MB"
+
/obj/item/storage/pill_bottle/dexalin
name = "\improper Dexalin pill bottle"
icon_state = "pill_canister1"
@@ -591,6 +603,12 @@
/obj/item/storage/pill_bottle/dexalin/skillless
skilllock = SKILL_MEDICAL_DEFAULT
+/obj/item/storage/pill_bottle/dexalinplus
+ name = "\improper Dexalin+ pill bottle"
+ icon_state = "pill_canister13"
+ pill_type_to_fill = /obj/item/reagent_container/pill/dexalinplus
+ maptext_label = "D+"
+
//Alkysine
/obj/item/storage/pill_bottle/alkysine
name = "\improper Alkysine pill bottle"
@@ -720,6 +738,30 @@
skilllock = SKILL_MEDICAL_DEFAULT
maptext_label = "Pc"
+/obj/item/storage/pill_bottle/imialk
+ name = "\improper Imidazoline-Alkysine pill bottle"
+ icon_state = "pill_canister14"
+ pill_type_to_fill = /obj/item/reagent_container/pill/imialk
+ maptext_label = "IA"
+
+/obj/item/storage/pill_bottle/iron
+ name = "\improper Iron pill bottle"
+ icon_state = "pill_canister4"
+ pill_type_to_fill = /obj/item/reagent_container/pill/iron
+ maptext_label = "FE"
+
+/obj/item/storage/pill_bottle/nitrogenwater
+ name = "\improper Nitrogen-Water pill bottle"
+ icon_state = "pill_canister12"
+ pill_type_to_fill = /obj/item/reagent_container/pill/nitrogenwater
+ maptext_label = "NW"
+
+/obj/item/storage/pill_bottle/tricord
+ name = "\improper Tricordazine pill bottle"
+ icon_state = "pill_canister"
+ pill_type_to_fill = /obj/item/reagent_container/pill/tricordrazine
+ maptext_label = "Ti"
+
//---------PILL PACKETS---------
/obj/item/storage/pill_bottle/packet
name = "\improper pill packet"
diff --git a/code/game/objects/structures/crates_lockers/largecrate_supplies.dm b/code/game/objects/structures/crates_lockers/largecrate_supplies.dm
index b9486e4f09..9a45b4256d 100644
--- a/code/game/objects/structures/crates_lockers/largecrate_supplies.dm
+++ b/code/game/objects/structures/crates_lockers/largecrate_supplies.dm
@@ -161,6 +161,9 @@
desc = "An ammunition case containing 20 M41A MK1 magazines."
supplies = list(/obj/item/ammo_magazine/rifle/m41aMK1 = 20)
+/obj/structure/largecrate/supply/ammo/m41amk1/forecon
+ supplies = list(/obj/item/ammo_magazine/rifle/m41aMK1/recon = 20)
+
/obj/structure/largecrate/supply/ammo/m41a_box
name = "\improper M41A ammunition box case (x4)"
desc = "An ammunition case containing four M41A 600 round boxes of ammunition."
diff --git a/code/game/objects/structures/flora.dm b/code/game/objects/structures/flora.dm
index 7b01243290..3461b9049e 100644
--- a/code/game/objects/structures/flora.dm
+++ b/code/game/objects/structures/flora.dm
@@ -706,15 +706,15 @@ ICEY GRASS. IT LOOKS LIKE IT'S MADE OF ICE.
var/new_slowdown = H.next_move_slowdown + rand(4,7)
H.next_move_slowdown = new_slowdown
if(prob(10))
- to_chat(H, SPAN_WARNING("It is very hard to move trough this [src]..."))
+ to_chat(H, SPAN_WARNING("It is very hard to move through this [src]..."))
if(8 to 9)
var/new_slowdown = H.next_move_slowdown + rand(8,11)
H.next_move_slowdown = new_slowdown
- to_chat(H, SPAN_WARNING("You got tangeled in [src]!"))
+ to_chat(H, SPAN_WARNING("You got tangled in [src]!"))
if(10)
var/new_slowdown = H.next_move_slowdown + rand(12,20)
H.next_move_slowdown = new_slowdown
- to_chat(H, SPAN_WARNING("You got completely tangeled in [src]! Oh boy..."))
+ to_chat(H, SPAN_WARNING("You got completely tangled in [src]! Oh boy..."))
/obj/structure/flora/jungle/thickbush/attackby(obj/item/I as obj, mob/user as mob)
//hatchets and shiet can clear away undergrowth
@@ -724,9 +724,9 @@ ICEY GRASS. IT LOOKS LIKE IT'S MADE OF ICE.
damage = rand(8,18)
if(indestructable)
//this bush marks the edge of the map, you can't destroy it
- to_chat(user, SPAN_DANGER("You flail away at the undergrowth, but it's too thick here."))
+ to_chat(user, SPAN_DANGER("You chop at the undergrowth, but it's too thick here."))
else
- user.visible_message(SPAN_DANGER("[user] flails away at the [src] with [I]."),SPAN_DANGER("You flail away at the [src] with [I]."))
+ user.visible_message(SPAN_DANGER("[user] chops at the [src] with [I]."),SPAN_DANGER("You chop at the [src] with [I]."))
playsound(src.loc, 'sound/effects/vegetation_hit.ogg', 25, 1)
health -= damage
if(health < 0)
diff --git a/code/game/objects/structures/gun_rack.dm b/code/game/objects/structures/gun_rack.dm
index 400002fa35..099d8d4c20 100644
--- a/code/game/objects/structures/gun_rack.dm
+++ b/code/game/objects/structures/gun_rack.dm
@@ -5,6 +5,7 @@
icon_state = "m41a"
density = TRUE
var/allowed_type
+ var/populate_type
var/max_stored = 5
var/initial_stored = 5
@@ -17,7 +18,7 @@
if(initial_stored)
var/i = 0
while(i < initial_stored)
- contents += new allowed_type(src)
+ contents += new populate_type(src)
i++
update_icon()
@@ -47,6 +48,10 @@
/obj/structure/gun_rack/m41
allowed_type = /obj/item/weapon/gun/rifle/m41aMK1
+ populate_type = /obj/item/weapon/gun/rifle/m41aMK1
+
+/obj/structure/gun_rack/m41/unloaded
+ populate_type = /obj/item/weapon/gun/rifle/m41aMK1/unloaded
/obj/structure/gun_rack/type71
icon_state = "type71"
@@ -54,6 +59,10 @@
max_stored = 6
initial_stored = 6
allowed_type = /obj/item/weapon/gun/rifle/type71
+ populate_type = /obj/item/weapon/gun/rifle/type71
+
+/obj/structure/gun_rack/type71/unloaded
+ populate_type = /obj/item/weapon/gun/rifle/type71/unloaded
/obj/structure/gun_rack/apc
name = "APC ammo compartment"
@@ -75,3 +84,10 @@
max_stored = 2
initial_stored = 0
allowed_type = /obj/item/ammo_magazine/hardpoint/boyars_dualcannon
+
+/obj/structure/gun_rack/m41/recon
+ icon_state = "m41arecon"
+ populate_type = /obj/item/weapon/gun/rifle/m41aMK1/forecon
+
+/obj/structure/gun_rack/m41/recon/unloaded
+ populate_type = /obj/item/weapon/gun/rifle/m41aMK1/forecon/unloaded
diff --git a/code/game/objects/structures/pipes/pipes.dm b/code/game/objects/structures/pipes/pipes.dm
index 8459b9c100..35b3ba4711 100644
--- a/code/game/objects/structures/pipes/pipes.dm
+++ b/code/game/objects/structures/pipes/pipes.dm
@@ -53,6 +53,7 @@
for(var/obj/structure/pipes/P in connected_to)
P.remove_connection(src)
+ connected_to.Cut()
GLOB.mainship_pipes -= src
@@ -97,6 +98,7 @@
/obj/structure/pipes/proc/remove_connection(obj/structure/pipes/P)
connected_to -= P
+ P.connected_to -= src
/obj/structure/pipes/proc/get_connection(direction)
var/obj/structure/pipes/best_connected_pipe = null
diff --git a/code/game/objects/structures/pipes/vents/vents.dm b/code/game/objects/structures/pipes/vents/vents.dm
index 298fbc57f4..926f14cd2f 100644
--- a/code/game/objects/structures/pipes/vents/vents.dm
+++ b/code/game/objects/structures/pipes/vents/vents.dm
@@ -139,7 +139,7 @@
if(welded)
to_chat(usr, SPAN_WARNING("You cannot release gas from a welded vent."))
return FALSE
- var/list/options = list(VENT_GAS_SMOKE, VENT_GAS_CN20, VENT_GAS_CN20_XENO)
+ var/list/options = list(VENT_GAS_SMOKE, VENT_GAS_CN20, VENT_GAS_CN20_XENO, VENT_GAS_LSD)
var/gas_choice = tgui_input_list(user, "What gas do you wish to use?", "Gas Choice", options, 20 SECONDS)
if(!gas_choice)
return FALSE
@@ -166,6 +166,8 @@
spreader = new /datum/effect_system/smoke_spread/cn20
if(VENT_GAS_CN20_XENO)
spreader = new /datum/effect_system/smoke_spread/cn20/xeno
+ if(VENT_GAS_LSD)
+ spreader = new /datum/effect_system/smoke_spread/LSD
if(!spreader)
return FALSE
gas_holder = spreader
diff --git a/code/game/objects/structures/props.dm b/code/game/objects/structures/props.dm
index c7cee6b5c8..4a4af8fef3 100644
--- a/code/game/objects/structures/props.dm
+++ b/code/game/objects/structures/props.dm
@@ -658,6 +658,7 @@
health = 150
light_range = 6
light_on = TRUE
+ light_color = LIGHT_COLOUR_FIRE
unslashable = TRUE
/// What obj this becomes when it gets to its next stage of construction / ignition
var/frame_type
diff --git a/code/game/objects/structures/vulture_spotter.dm b/code/game/objects/structures/vulture_spotter.dm
index ab23a80867..4111bdfcdd 100644
--- a/code/game/objects/structures/vulture_spotter.dm
+++ b/code/game/objects/structures/vulture_spotter.dm
@@ -183,7 +183,10 @@
unscope()
scope_attached = FALSE
desc = initial(desc) + " Though, it doesn't seem to have one attached yet."
- new /obj/item/device/vulture_spotter_scope(get_turf(src), bound_rifle)
+ if(skillless)
+ new /obj/item/device/vulture_spotter_scope/skillless(get_turf(src), bound_rifle)
+ else
+ new /obj/item/device/vulture_spotter_scope(get_turf(src), bound_rifle)
/// Handler for user folding up the tripod, picking it up
/obj/structure/vulture_spotter_tripod/proc/fold_up(mob/user)
diff --git a/code/game/sound.dm b/code/game/sound.dm
index 02be8dbace..f2574975a7 100644
--- a/code/game/sound.dm
+++ b/code/game/sound.dm
@@ -1,22 +1,44 @@
/datum/sound_template //Basically a sound datum, but only serves as a way to carry info to soundOutput
- var/file //The sound itself
- var/file_muffled // Muffled variant for those that are deaf
- var/wait = 0
- var/repeat = 0
+ //copied sound datum vars
+ ///This is the file that will be played when the sound is sent to a player.
+ var/file
+ ///Set to TRUE to repeat the sound indefinitely once it begins playing, 2 to repeat it forwards and backwards.
+ var/repeat = FALSE
+ ///Set to TRUE to wait for other sounds in this channel to finish before playing this one.
+ var/wait = FALSE
+ ///For sound effects, set to 1 through 1024 to choose a specific sound channel. For values of 0 or less, any available channel will be chosen.
var/channel = 0
+ ///Set to a percentage from 0 to 100 of the sound's full volume.
var/volume = 100
- var/status = 0 //Sound status flags
+ ///Any value from -100 to 100 will play this sound at a multiple of its normal frequency. A value of 0 or 1 will play the sound at its normal frequency.
var/frequency = 1
+ ///Can be used to set a starting time, in seconds, for a sound.
+ var/offset = 0
+ ///Can be used to shift the pitch of a sound up or down. This works similarly to frequency except that it doesn't impact playback speed.
+ var/pitch = 1
+ ///Alter the way the sound is heard by affecting several different on/off values which combine as bit flags: SOUND_MUTE, SOUND_PAUSED, SOUND_STREAM, SOUND_UPDATE
+ var/status = NONE
+ ///Within the falloff distance a 3D sound stays at the constant loudest volume possible. Outside of this distance it attenuates at a rate determined by the falloff.
var/falloff = 1
+ ///Changes the environmental reverb for all 3D sounds until another environment is specified. The default value (-1) specifies no change in environment. A numeric value from 0 to 25 specifies a set of reverb presets for the environment.
+ var/environment = -1
+ ///If set to an 18-element list, this value customizes reverbration settings for this sound only.
+ var/list/echo
+
+ //custom vars
+ ///The category of this sound for client volume purposes: VOLUME_SFX (Sound effects), VOLUME_AMB (Ambience and Soundscapes) and VOLUME_ADM (Admin sounds and some other stuff)
var/volume_cat = VOLUME_SFX
+ ///Maximum theoretical range (in tiles) of the sound, by default is equal to the volume.
var/range = 0
- var/list/echo
- var/x //Map coordinates, not sound coordinates
+ //Map coordinates, not sound coordinates, generated by the procs
+ var/x
var/y
var/z
- var/y_s_offset // Vertical sound offset
- var/x_s_offset // Horizontal sound offset
+ ///Horizontal sound position offset.
+ var/x_s_offset
+ ///Vertical sound position offset.
+ var/y_s_offset
/proc/get_free_channel()
var/static/cur_chan = 1
@@ -24,58 +46,73 @@
if(cur_chan > FREE_CHAN_END)
cur_chan = 1
-//Proc used to play a sound effect. Avoid using this proc for non-IC sounds, as there are others
-//source: self-explanatory.
-//soundin: the .ogg to use.
-//vol: the initial volume of the sound, 0 is no sound at all, 75 is loud queen screech.
-//freq: the frequency of the sound. Setting it to 1 will assign it a random frequency
-//sound_range: the maximum theoretical range (in tiles) of the sound, by default is equal to the volume.
-//vol_cat: the category of this sound, used in client volume. There are 3 volume categories: VOLUME_SFX (Sound effects), VOLUME_AMB (Ambience and Soundscapes) and VOLUME_ADM (Admin sounds and some other stuff)
-//channel: use this only when you want to force the sound to play on a specific channel
-//status: the regular 4 sound flags
-//falloff: max range till sound volume starts dropping as distance increases
-
-/proc/playsound(atom/source, soundin, vol = 100, vary = FALSE, sound_range, vol_cat = VOLUME_SFX, channel = 0, status , falloff = 1, echo, y_s_offset,x_s_offset)
+/**
+ * Play a spatialized sound effect to everyone within hearing distance.
+ *
+ * Arguments:
+ * * source - origin atom for the sound
+ * * soundin - sound datum ( sound() ), sound file ('mysound.ogg'), or string to get a SFX ("male_warcry")
+ * * vol - the initial volume of the sound, 0 is no sound at all, 75 is loud queen screech.
+ * * vary - the frequency of the sound. Setting it to 1 will assign it a random frequency
+ * * sound_range - maximum theoretical range (in tiles) of the sound, by default is equal to the volume.
+ * * vol_cat - the category of this sound for client volume purposes: VOLUME_SFX (Sound effects), VOLUME_AMB (Ambience and Soundscapes), VOLUME_ADM (Admin sounds)
+ * * channel - use this only when you want to force the sound to play on a specific channel
+ * * status - combined bit flags: SOUND_MUTE, SOUND_PAUSED, SOUND_STREAM, SOUND_UPDATE
+ * * falloff - max range till sound volume starts dropping as distance increases
+ * * echo - customizes reverbration settings for this sound
+ * * y_s_offset - vertical sound position offset
+ * * x_s_offset - horizontal sound position offset
+ *
+ * Returns selected channel on success, FALSE on failure
+ */
+/proc/playsound(atom/source, sound/soundin, vol = 100, vary = FALSE, sound_range, vol_cat = VOLUME_SFX, channel = 0, status, falloff = 1, list/echo, y_s_offset, x_s_offset)
if(isarea(source))
error("[source] is an area and is trying to make the sound: [soundin]")
return FALSE
- var/datum/sound_template/S = new()
-
- var/sound/SD = soundin
- if(istype(SD))
- S.file = SD.file
- S.wait = SD.wait
- S.repeat = SD.repeat
- else
- S.file = get_sfx(soundin)
- S.channel = channel ? channel : get_free_channel()
- S.status = status
- S.falloff = falloff
- S.volume = vol
- S.volume_cat = vol_cat
- S.echo = echo
- S.y_s_offset = y_s_offset
- S.x_s_offset = x_s_offset
- if(vary != FALSE)
- if(vary > 1)
- S.frequency = vary
- else
- S.frequency = GET_RANDOM_FREQ // Same frequency for everybody
-
- if(!sound_range)
- sound_range = round(0.25*vol) //if no specific range, the max range is equal to a quarter of the volume.
- S.range = sound_range
var/turf/turf_source = get_turf(source)
- if(!turf_source || !turf_source.z)
+ if(!turf_source?.z)
return FALSE
- S.x = turf_source.x
- S.y = turf_source.y
- S.z = turf_source.z
+
+ var/datum/sound_template/template = new()
+
+ if(istype(soundin))
+ template.file = soundin.file
+ template.repeat = soundin.repeat
+ template.wait = soundin.wait
+ //template.channel = soundin.channel
+ //template.volume = soundin.volume
+ template.frequency = soundin.frequency
+ template.offset = soundin.offset
+ template.pitch = soundin.pitch
+ //template.status = soundin.status
+ //template.falloff = soundin.falloff
+ //template.environment = soundin.environment
+ //template.echo = soundin.echo
+ else
+ template.file = get_sfx(soundin)
+
+ template.channel = channel || get_free_channel()
+ template.volume = vol
+ if(vary > 1)
+ template.frequency = vary
+ else if(vary)
+ template.frequency = GET_RANDOM_FREQ // Same frequency for everybody
+ template.status = status
+ template.falloff = falloff
+ template.echo = echo
+
+ template.volume_cat = vol_cat
+ template.range = sound_range || floor(0.25 * vol) //if no specific range, the max range is equal to a quarter of the volume.
+ template.x = turf_source.x
+ template.y = turf_source.y
+ template.z = turf_source.z
+ template.x_s_offset = x_s_offset
+ template.y_s_offset = y_s_offset
if(!SSinterior)
- SSsound.queue(S)
- return S.channel
+ SSsound.queue(template)
+ return template.channel
var/list/datum/interior/extra_interiors = list()
// If we're in an interior, range the chunk, then adjust to do so from outside instead
@@ -85,66 +122,127 @@
extra_interiors |= VI
if(VI.exterior)
var/turf/new_turf_source = get_turf(VI.exterior)
- S.x = new_turf_source.x
- S.y = new_turf_source.y
- S.z = new_turf_source.z
- else sound_range = 0
+ template.x = new_turf_source.x
+ template.y = new_turf_source.y
+ template.z = new_turf_source.z
// Range for 'nearby interiors' aswell
for(var/datum/interior/VI in SSinterior.interiors)
if(VI?.ready && VI.exterior?.z == turf_source.z && get_dist(VI.exterior, turf_source) <= sound_range)
extra_interiors |= VI
- SSsound.queue(S, null, extra_interiors)
- return S.channel
+ SSsound.queue(template, null, extra_interiors)
+ return template.channel
-//This is the replacement for playsound_local. Use this for sending sounds directly to a client
-/proc/playsound_client(client/C, soundin, atom/origin, vol = 100, random_freq, vol_cat = VOLUME_SFX, channel = 0, status, list/echo, y_s_offset, x_s_offset)
+/**
+ * Play a sound effect directly to a client.
+ *
+ * Arguments:
+ * * C - client to hear the sound
+ * * soundin - sound datum ( sound() ), sound file ('mysound.ogg'), or string to get a SFX ("male_warcry")
+ * * origin - origin atom for the sound
+ * * vol - the initial volume of the sound, 0 is no sound at all, 75 is loud queen screech.
+ * * random_freq - assign the sound a random frequency
+ * * vol_cat - the category of this sound for client volume purposes: VOLUME_SFX (Sound effects), VOLUME_AMB (Ambience and Soundscapes), VOLUME_ADM (Admin sounds)
+ * * channel - use this only when you want to force the sound to play on a specific channel
+ * * status - combined bit flags: SOUND_MUTE, SOUND_PAUSED, SOUND_STREAM, SOUND_UPDATE
+ * * echo - customizes reverbration settings for this sound
+ * * y_s_offset - vertical sound position offset
+ * * x_s_offset - horizontal sound position offset
+ *
+ * Returns FALSE on failure
+ */
+/proc/playsound_client(client/C, sound/soundin, atom/origin, vol = 100, random_freq, vol_cat = VOLUME_SFX, channel = 0, status, list/echo, y_s_offset, x_s_offset)
if(!istype(C) || !C.soundOutput) return FALSE
- var/datum/sound_template/S = new()
- if(origin)
- var/turf/T = get_turf(origin)
- if(T)
- S.x = T.x
- S.y = T.y
- S.z = T.z
- var/sound/SD = soundin
- if(istype(SD))
- S.file = SD.file
- S.wait = SD.wait
- S.repeat = SD.repeat
+
+ var/datum/sound_template/template = new()
+
+ if(istype(soundin))
+ template.file = soundin.file
+ template.repeat = soundin.repeat
+ template.wait = soundin.wait
+ //template.channel = soundin.channel
+ //template.volume = soundin.volume
+ template.frequency = soundin.frequency
+ template.offset = soundin.offset
+ template.pitch = soundin.pitch
+ //template.status = soundin.status
+ //template.falloff = soundin.falloff
+ //template.environment = soundin.environment
+ //template.echo = soundin.echo
else
- S.file = get_sfx(soundin)
+ template.file = get_sfx(soundin)
+ template.channel = channel
+ template.volume = vol
if(random_freq)
- S.frequency = GET_RANDOM_FREQ
- S.volume = vol
- S.volume_cat = vol_cat
- S.channel = channel
- S.status = status
- S.echo = echo
- S.y_s_offset = y_s_offset
- S.x_s_offset = x_s_offset
- SSsound.queue(S, list(C))
-
-/// Plays sound to all mobs that are map-level contents of an area
-/proc/playsound_area(area/A, soundin, vol = 100, channel = 0, status, vol_cat = VOLUME_SFX, list/echo, y_s_offset, x_s_offset)
+ template.frequency = GET_RANDOM_FREQ
+ template.status = status
+ template.echo = echo
+
+ template.volume_cat = vol_cat
+ var/turf/turf_origin = get_turf(origin)
+ if(turf_origin)
+ template.x = turf_origin.x
+ template.y = turf_origin.y
+ template.z = turf_origin.z
+ template.x_s_offset = x_s_offset
+ template.y_s_offset = y_s_offset
+
+ SSsound.queue(template, list(C))
+
+/**
+ * Play a sound effect to all mobs that are map-level contents of an area.
+ *
+ * Arguments:
+ * * A - affected area to hear the sound
+ * * soundin - sound datum ( sound() ), sound file ('mysound.ogg'), or string to get a SFX ("male_warcry")
+ * * vol - the initial volume of the sound, 0 is no sound at all, 75 is loud queen screech.
+ * * channel - use this only when you want to force the sound to play on a specific channel
+ * * status - combined bit flags: SOUND_MUTE, SOUND_PAUSED, SOUND_STREAM, SOUND_UPDATE
+ * * vol_cat - the category of this sound for client volume purposes: VOLUME_SFX (Sound effects), VOLUME_AMB (Ambience and Soundscapes), VOLUME_ADM (Admin sounds)
+ * * echo - customizes reverbration settings for this sound
+ *
+ * Returns FALSE on failure
+ */
+/proc/playsound_area(area/A, sound/soundin, vol = 100, channel = 0, status, vol_cat = VOLUME_SFX, list/echo, y_s_offset, x_s_offset)
if(!isarea(A))
return FALSE
- var/datum/sound_template/S = new()
- S.file = soundin
- S.volume = vol
- S.channel = channel
- S.status = status
- S.volume_cat = vol_cat
+
+ var/datum/sound_template/template = new()
+
+ if(istype(soundin))
+ template.file = soundin.file
+ template.repeat = soundin.repeat
+ template.wait = soundin.wait
+ //template.channel = soundin.channel
+ //template.volume = soundin.volume
+ template.frequency = soundin.frequency
+ template.offset = soundin.offset
+ template.pitch = soundin.pitch
+ //template.status = soundin.status
+ //template.falloff = soundin.falloff
+ //template.environment = soundin.environment
+ //template.echo = soundin.echo
+ else
+ template.file = get_sfx(soundin)
+
+ template.channel = channel
+ template.volume = vol
+ template.status = status
+
+ template.volume_cat = vol_cat
+ template.x_s_offset = x_s_offset
+ template.y_s_offset = y_s_offset
var/list/hearers = list()
- for(var/mob/living/M in A.contents)
- if(!M || !M.client || !M.client.soundOutput)
+ for(var/mob/living/living_mob in A.contents)
+ if(!living_mob || !living_mob.client || !living_mob.client.soundOutput)
continue
- hearers += M.client
- SSsound.queue(S, hearers)
+ hearers += living_mob.client
+
+ SSsound.queue(template, hearers)
/client/proc/playtitlemusic()
if(!SSticker?.login_music)
@@ -153,21 +251,53 @@
playsound_client(src, SSticker.login_music, null, 70, 0, VOLUME_LOBBY, SOUND_CHANNEL_LOBBY, SOUND_STREAM)
-/// Play sound for all on-map clients on a given Z-level. Good for ambient sounds.
-/proc/playsound_z(z, soundin, volume = 100, vol_cat = VOLUME_SFX, echo, y_s_offset, x_s_offset)
- var/datum/sound_template/S = new()
- S.file = soundin
- S.volume = volume
- S.channel = SOUND_CHANNEL_Z
- S.volume_cat = vol_cat
- S.echo = echo
- S.y_s_offset = y_s_offset
- S.x_s_offset = x_s_offset
+/**
+ * Play a sound effect for all on-map clients on a given Z-level.
+ *
+ * Arguments:
+ * * z - list of affected [/datum/space_level] to hear the sound
+ * * soundin - sound datum ( sound() ), sound file ('mysound.ogg'), or string to get a SFX ("male_warcry")
+ * * volume - the initial volume of the sound, 0 is no sound at all, 75 is loud queen screech.
+ * * vol_cat - the category of this sound for client volume purposes: VOLUME_SFX (Sound effects), VOLUME_AMB (Ambience and Soundscapes), VOLUME_ADM (Admin sounds)
+ * * echo - customizes reverbration settings for this sound
+ * * y_s_offset - vertical sound position offset
+ * * x_s_offset - horizontal sound position offset
+ *
+ * Returns selected channel on success, FALSE on failure
+ */
+/proc/playsound_z(list/z, sound/soundin, volume = 100, vol_cat = VOLUME_SFX, echo, y_s_offset, x_s_offset)
+ var/datum/sound_template/template = new()
+
+ if(istype(soundin))
+ template.file = soundin.file
+ template.repeat = soundin.repeat
+ template.wait = soundin.wait
+ //template.channel = soundin.channel
+ //template.volume = soundin.volume
+ template.frequency = soundin.frequency
+ template.offset = soundin.offset
+ template.pitch = soundin.pitch
+ //template.status = soundin.status
+ //template.falloff = soundin.falloff
+ //template.environment = soundin.environment
+ //template.echo = soundin.echo
+ else
+ template.file = get_sfx(soundin)
+
+ template.channel = SOUND_CHANNEL_Z
+ template.volume = volume
+ template.echo = echo
+
+ template.volume_cat = vol_cat
+ template.x_s_offset = x_s_offset
+ template.y_s_offset = y_s_offset
+
var/list/hearers = list()
- for(var/mob/M in GLOB.player_list)
- if((M.z in z) && M.client.soundOutput)
- hearers += M.client
- SSsound.queue(S, hearers)
+ for(var/mob/mob in GLOB.player_list)
+ if((mob.z in z) && mob.client.soundOutput)
+ hearers += mob.client
+
+ SSsound.queue(template, hearers)
// The pick() proc has a built-in chance that can be added to any option by adding ,X; to the end of an option, where X is the % chance it will play.
/proc/get_sfx(S)
diff --git a/code/game/world.dm b/code/game/world.dm
index e67fca2856..77d48d3593 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -16,7 +16,7 @@ var/list/reboot_sfx = file2list("config/reboot_sfx.txt")
/world/New()
var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL")
if (debug_server)
- LIBCALL(debug_server, "auxtools_init")()
+ call_ext(debug_server, "auxtools_init")()
enable_debugging()
internal_tick_usage = 0.2 * world.tick_lag
hub_password = "kMZy3U5jJHSiBQjr"
@@ -381,13 +381,14 @@ var/datum/BSQL_Connection/connection
else
CRASH("unsupported platform")
- var/init = LIBCALL(lib, "init")()
+ var/init = call_ext(lib, "init")()
if("0" != init)
CRASH("[lib] init error: [init]")
/world/proc/HandleTestRun()
// Wait for the game ticker to initialize
Master.sleep_offline_after_initializations = FALSE
+ SSticker.start_immediately = TRUE
UNTIL(SSticker.initialized)
//trigger things to run the whole process
diff --git a/code/global.dm b/code/global.dm
index dca2fe7701..3ed9249ca6 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -1,7 +1,7 @@
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:31
#define MAIN_SHIP_NAME SSmapping.get_main_ship_name()
#define MAIN_SHIP_DEFAULT_NAME "USS Golden Arrow"
-#define SHIP_MAP_NAMES list("USS Almayer", "USS Golden Arrow", "SSV Chapaev", "USS Rover")
+#define SHIP_MAP_NAMES list("USS Almayer", "USS Golden Arrow", "USS Golden Arrow (Classic)", "SSV Chapaev", "USS Rover")
#define MAIN_SHIP_PLATOON text2path(SSmapping.get_main_ship_platoon())
#define MAIN_SHIP_DEFAULT_PLATOON "/datum/squad/marine/alpha"
@@ -120,7 +120,7 @@ var/list/AAlarmWireColorToIndex
//Don't set this very much higher then 1024 unless you like inviting people in to dos your server with message spam
#define MAX_MESSAGE_LEN 1024
-#define MAX_EMOTE_LEN 256
+#define MAX_EMOTE_LEN 1024
#define MAX_PAPER_MESSAGE_LEN 3072
#define MAX_BOOK_MESSAGE_LEN 9216
#define MAX_NAME_LEN 28
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 34cc3ee3c8..6e47391fc1 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -330,6 +330,8 @@ var/list/roundstart_mod_verbs = list(
if(CLIENT_HAS_RIGHTS(src, R_BUILDMODE))
add_verb(src, /client/proc/togglebuildmodeself)
add_verb(src, /client/proc/toggle_game_master)
+ add_verb(src, /client/proc/open_resin_panel)
+ add_verb(src, /client/proc/open_sound_panel)
add_verb(src, /client/proc/toggle_join_xeno)
add_verb(src, /client/proc/game_master_rename_platoon)
add_verb(src, /client/proc/toggle_vehicle_blockers)
@@ -364,6 +366,8 @@ var/list/roundstart_mod_verbs = list(
admin_verbs_default,
/client/proc/togglebuildmodeself,
/client/proc/toggle_game_master,
+ /client/proc/open_resin_panel,
+ /client/proc/open_sound_panel,
/client/proc/toggle_join_xeno,
/client/proc/game_master_rename_platoon,
/client/proc/toggle_vehicle_blockers,
diff --git a/code/modules/admin/game_master/extra_buttons/rename_platoon.dm b/code/modules/admin/game_master/extra_buttons/rename_platoon.dm
index 9d805ab9d1..575319ad73 100644
--- a/code/modules/admin/game_master/extra_buttons/rename_platoon.dm
+++ b/code/modules/admin/game_master/extra_buttons/rename_platoon.dm
@@ -28,8 +28,8 @@ GLOBAL_VAR_INIT(main_platoon_initial_name, GLOB.main_platoon_name)
if(!new_name || !istext(new_name))
return
- if(length(new_name) > 10)
- to_chat(src, SPAN_NOTICE("The platoon name should be 10 characters or less."))
+ if(length(new_name) > 16)
+ to_chat(src, SPAN_NOTICE("The platoon name should be 16 characters or less."))
return
var/old_name = GLOB.main_platoon_name
diff --git a/code/modules/admin/game_master/game_master.dm b/code/modules/admin/game_master/game_master.dm
index 947c49aeb5..2745287727 100644
--- a/code/modules/admin/game_master/game_master.dm
+++ b/code/modules/admin/game_master/game_master.dm
@@ -158,7 +158,8 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100)
data["game_master_objectives"] = length(GLOB.game_master_objectives) ? GLOB.game_master_objectives : ""
// Communication stuff
- data["communication_clarity"] = GLOB.radio_communication_clarity
+ data["radio_clarity"] = GLOB.radio_communication_clarity
+ data["radio_clarity_example"] = stars("The quick brown fox jumped over the lazy dog.", GLOB.radio_communication_clarity)
return data
@@ -294,7 +295,7 @@ GLOBAL_VAR_INIT(radio_communication_clarity, 100)
if("use_game_master_phone")
game_master_phone.attack_hand(ui.user)
- if("set_communication_clarity")
+ if("set_radio_clarity")
var/new_clarity = text2num(params["clarity"])
if(!isnum(new_clarity))
return
diff --git a/code/modules/admin/game_master/resin_panel.dm b/code/modules/admin/game_master/resin_panel.dm
new file mode 100644
index 0000000000..bab4097173
--- /dev/null
+++ b/code/modules/admin/game_master/resin_panel.dm
@@ -0,0 +1,177 @@
+#define RESIN_PANEL_STRUCTURES \
+list( \
+ /datum/resin_construction/resin_turf/wall, \
+ /datum/resin_construction/resin_turf/wall/thick, \
+ /datum/resin_construction/resin_turf/wall/reflective, \
+ /datum/resin_construction/resin_turf/membrane, \
+ /datum/resin_construction/resin_turf/membrane/thick, \
+ /datum/resin_construction/resin_obj/door, \
+ /datum/resin_construction/resin_obj/door/thick, \
+ /datum/resin_construction/resin_obj/resin_node, \
+ /datum/resin_construction/resin_obj/sticky_resin, \
+ /datum/resin_construction/resin_obj/fast_resin, \
+ /datum/resin_construction/resin_obj/resin_spike, \
+ /datum/resin_construction/resin_obj/acid_pillar, \
+ /turf/closed/wall/mineral/bone_resin \
+)
+
+/client/proc/open_resin_panel()
+ set name = "Resin Panel"
+ set category = "Game Master"
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ new /datum/resin_panel(usr)
+
+/datum/resin_panel
+ var/static/list/structure_list
+ var/static/list/removal_allowlist
+ var/selected_structure
+ var/selected_hive = XENO_HIVE_NORMAL
+ var/client/holder
+ var/build_click_intercept = FALSE
+
+/datum/resin_panel/New(user)
+ if(isnull(structure_list)) //first run, init statics
+ structure_list = get_structures()
+
+ removal_allowlist = list()
+ for(var/structure as anything in RESIN_PANEL_STRUCTURES)
+ if(structure in GLOB.resin_constructions_list)
+ var/datum/resin_construction/construct = structure
+ removal_allowlist += construct.build_path
+ else
+ removal_allowlist += structure
+
+ if(isclient(user))
+ holder = user
+ else
+ var/mob/mob = user
+ holder = mob.client
+
+ holder.click_intercept = src
+ tgui_interact(holder.mob)
+
+/datum/resin_panel/proc/get_structures()
+ var/list/structures = list()
+ for(var/structure as anything in RESIN_PANEL_STRUCTURES)
+ var/list/entry = list()
+
+ if(structure in GLOB.resin_constructions_list)
+ var/datum/resin_construction/construct = structure
+ entry["name"] = construct.name
+ entry["image"] = replacetext(construct.construction_name, " ", "-")
+ entry["id"] = "[construct]"
+ else if(structure in typesof(/turf))
+ var/turf/turf = structure
+ entry["name"] = turf.name
+ switch(turf)
+ if(/turf/closed/wall/mineral/bone_resin)
+ entry["image"] = "reflective-resin-wall" //looks just like it, saves on making new spritesheet for one image
+ else
+ entry["image"] = turf.icon_state
+ entry["id"] = "[turf]"
+
+ structures += list(entry)
+
+ return structures
+
+/datum/resin_panel/ui_assets(mob/user)
+ return list(
+ get_asset_datum(/datum/asset/spritesheet/choose_resin),
+ )
+
+/datum/resin_panel/ui_static_data(mob/user)
+ var/list/data = list()
+
+ data["structure_list"] = structure_list
+ data["hives_list"] = ALL_XENO_HIVES
+
+ return data
+
+/datum/resin_panel/ui_data(mob/user)
+ var/list/data = list()
+
+ data["selected_structure"] = selected_structure
+ data["selected_hive"] = selected_hive
+ data["build_click_intercept"] = build_click_intercept
+
+ return data
+
+/datum/resin_panel/ui_close(mob/user)
+ holder = null
+ build_click_intercept = FALSE
+ qdel(src)
+
+/datum/resin_panel/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/resin_panel/ui_status(mob/user, datum/ui_state/state)
+ return UI_INTERACTIVE
+
+/datum/resin_panel/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ResinPanel", "Resin Panel")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/datum/resin_panel/proc/InterceptClickOn(mob/user, params, atom/object)
+ if(!build_click_intercept)
+ return
+
+ var/list/modifiers = params2list(params)
+
+ if(LAZYACCESS(modifiers, MIDDLE_CLICK)) //remove
+ if(!(object.type in removal_allowlist))
+ return
+
+ if(isturf(object))
+ var/turf/turf = object
+ turf.ScrapeAway()
+ else
+ qdel(object)
+ else //add
+ if(!selected_structure)
+ return
+
+ var/turf/current_turf = get_turf(object)
+
+ var/atom/new_structure
+ if(selected_structure in GLOB.resin_constructions_list)
+ var/datum/resin_construction/construct = GLOB.resin_constructions_list[selected_structure]
+ new_structure = construct.build(current_turf, selected_hive)
+ else if(selected_structure in typesof(/turf))
+ var/turf/turf = selected_structure
+ new_structure = current_turf.PlaceOnTop(turf)
+ new_structure?.add_hiddenprint(user) //so admins know who placed it
+
+ return TRUE
+
+/datum/resin_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ if(!check_rights(R_ADMIN))
+ return
+
+ switch(action)
+ if("set_selected_structure")
+ var/selected_type = text2path(params["type"])
+ if(!(selected_type in RESIN_PANEL_STRUCTURES))
+ return
+ selected_structure = selected_type
+ return TRUE
+ if("set_selected_hive")
+ var/hive = params["selected_hive"]
+ if(!(hive in ALL_XENO_HIVES))
+ return
+ selected_hive = hive
+ return TRUE
+ if("toggle_build_click_intercept")
+ build_click_intercept = !build_click_intercept
+ return TRUE
+
+#undef RESIN_PANEL_STRUCTURES
diff --git a/code/modules/admin/game_master/sound_panel.dm b/code/modules/admin/game_master/sound_panel.dm
new file mode 100644
index 0000000000..8aff8b0f23
--- /dev/null
+++ b/code/modules/admin/game_master/sound_panel.dm
@@ -0,0 +1,272 @@
+/client/proc/open_sound_panel()
+ set name = "Sound Panel"
+ set category = "Admin.Panels"
+
+ if(!check_rights(R_SOUNDS))
+ return
+
+ new /datum/sound_panel(usr)
+
+/datum/sound_panel
+ var/static/list/sound_list
+ var/static/list/category_list
+ var/static/list/category_lookup
+ var/static/list/zlevel_list
+ var/static/list/zlevel_lookup
+ var/static/list/group_list
+ var/client/holder
+ var/sound_path = ""
+ var/sound_category
+ var/sound_volume = 50
+ var/sound_pitch = 1
+ var/sound_duration = 1
+ var/mob/target_player
+ var/turf/target_loc
+ var/loc_click_intercept = FALSE
+ var/loc_click_play = FALSE
+ var/target_zlevel
+ var/target_group
+
+/datum/sound_panel/New(user)
+ if(isnull(sound_list)) //first run, init statics
+ sound_list = get_sounds()
+
+ category_list = list("Sound FX", "Ambience", "Admin")
+ category_lookup = list("Sound FX" = VOLUME_SFX, "Ambience" = VOLUME_AMB, "Admin" = VOLUME_ADM)
+
+ zlevel_list = list()
+ zlevel_lookup = list()
+ for(var/datum/space_level/level as anything in SSmapping.z_list)
+ zlevel_list += level.name
+ zlevel_lookup[level.name] = level.z_value
+
+ group_list = list("Global", "Humans", "Xenos", "Ghosts")
+
+ sound_category = category_list[1]
+ target_zlevel = zlevel_list[1]
+ target_group = group_list[1]
+
+ if(isclient(user))
+ holder = user
+ else
+ var/mob/mob = user
+ holder = mob.client
+
+ holder.click_intercept = src
+ tgui_interact(holder.mob)
+
+/datum/sound_panel/proc/get_sounds()
+ var/static/list/extensions = list("mid", "midi", "mod", "it", "s3m", "xm", "oxm", "wav", "ogg", "mp3", "raw", "wma", "aiff")
+ var/static/regex/ext_rgx = new("\\.(?:[jointext(extensions, "|")])$", "i")
+
+ var/list/dirs = list("sound/")
+ var/list/file_paths = list()
+ for(var/i = 1, i <= length(dirs), i++)
+ var/path = dirs[i]
+
+ var/list/filenames = flist(path)
+ for(var/filename as anything in filenames)
+ if(findtext(filename, "/", -1)) //found directory, add to search
+ dirs += "[path][filename]"
+ continue
+ if(!findtext(filename, ext_rgx)) //extension check
+ continue
+ file_paths += "[path][filename]"
+
+ return file_paths
+
+/datum/sound_panel/ui_static_data(mob/user)
+ var/list/data = list()
+
+ data["sound_list"] = sound_list
+ data["category_list"] = category_list
+ data["zlevel_list"] = zlevel_list
+ data["group_list"] = group_list
+
+ return data
+
+/datum/sound_panel/ui_data(mob/user)
+ var/list/data = list()
+
+ data["sound_path"] = sound_path
+ data["sound_category"] = sound_category
+ data["sound_volume"] = sound_volume
+ data["sound_pitch"] = sound_pitch
+ data["sound_duration"] = sound_duration
+ data["target_player_desc"] = target_player?.name
+ data["target_loc_desc"] = target_loc ? "[target_loc.name]: [target_loc.x],[target_loc.y],[target_loc.z]" : null
+ data["loc_click_intercept"] = loc_click_intercept
+ data["loc_click_play"] = loc_click_play
+ data["target_zlevel"] = target_zlevel
+ data["target_group"] = target_group
+
+ return data
+
+/datum/sound_panel/ui_close(mob/user)
+ holder = null
+ target_loc = null
+ target_player = null
+ loc_click_intercept = FALSE
+ qdel(src)
+
+/datum/sound_panel/ui_state(mob/user)
+ return GLOB.admin_state
+
+/datum/sound_panel/ui_status(mob/user, datum/ui_state/state)
+ return UI_INTERACTIVE
+
+/datum/sound_panel/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "SoundPanel", "Sound Panel")
+ ui.set_autoupdate(FALSE)
+ ui.open()
+
+/datum/sound_panel/proc/InterceptClickOn(mob/user, params, atom/object)
+ if(loc_click_intercept)
+ var/turf/chosen_loc = get_turf(object)
+ if(QDELETED(chosen_loc))
+ return
+
+ target_loc = chosen_loc
+ SStgui.update_uis(src)
+
+ if(loc_click_play)
+ if(!sound_path)
+ return
+
+ var/sound/sound_datum = sound(sound_path)
+ sound_datum.frequency = 1 / sound_duration
+ sound_datum.pitch = sound_pitch * sound_duration
+
+ playsound(target_loc, sound_datum, sound_volume, vol_cat = category_lookup[sound_category])
+
+ return TRUE
+
+/datum/sound_panel/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+ . = ..()
+ if(.)
+ return
+
+ if(!check_rights(R_SOUNDS))
+ return
+
+ switch(action)
+ if("set_sound_path")
+ var/sound = params["sound_path"]
+ if(!(sound in sound_list))
+ return
+ sound_path = sound
+ return TRUE
+ if("set_sound_category")
+ var/category = params["sound_category"]
+ if(isnull(category_lookup[category]))
+ return
+ sound_category = category
+ return TRUE
+ if("set_sound_volume")
+ sound_volume = clamp(params["sound_volume"], 0, 100)
+ return TRUE
+ if("set_sound_pitch")
+ sound_pitch = clamp(params["sound_pitch"], 0.5, 2)
+ return TRUE
+ if("set_sound_duration")
+ sound_duration = clamp(params["sound_duration"], 0.5, 2)
+ return TRUE
+ if("set_target_zlevel")
+ var/target_z = params["target_zlevel"]
+ if(isnull(zlevel_lookup[target_z]))
+ return
+ target_zlevel = target_z
+ return TRUE
+ if("set_target_group")
+ var/group = params["target_group"]
+ if(!(group in group_list))
+ return
+ target_group = group
+ return TRUE
+ if("play_preview")
+ if(!sound_path)
+ return
+
+ var/sound/sound_datum = sound(sound_path)
+ sound_datum.frequency = 1 / sound_duration
+ sound_datum.pitch = sound_pitch * sound_duration
+
+ playsound_client(holder, sound_datum, vol = sound_volume, vol_cat = category_lookup[sound_category], channel = SOUND_CHANNEL_TEST)
+ return TRUE
+ if("stop_preview")
+ var/sound/sound_datum = sound()
+ sound_datum.channel = SOUND_CHANNEL_TEST
+ sound_datum.status = SOUND_MUTE|SOUND_UPDATE
+ sound_to(holder, sound_datum)
+ return TRUE
+ if("select_client")
+ var/mob/chosen_player = tgui_input_list(holder.mob, "Who should hear the sound?", "Player Select", GLOB.player_list)
+ if(QDELETED(chosen_player))
+ return
+
+ target_player = chosen_player
+ return TRUE
+ if("play_client")
+ if(!sound_path)
+ return
+ if(QDELETED(target_player))
+ return
+
+ var/sound/sound_datum = sound(sound_path)
+ sound_datum.frequency = 1 / sound_duration
+ sound_datum.pitch = sound_pitch * sound_duration
+
+ playsound_client(target_player.client, sound_datum, vol = sound_volume, vol_cat = category_lookup[sound_category])
+ return TRUE
+ if("toggle_loc_click_intercept")
+ loc_click_intercept = !loc_click_intercept
+ return TRUE
+ if("toggle_loc_click_play")
+ loc_click_play = !loc_click_play
+ return TRUE
+ if("play_local")
+ if(!sound_path)
+ return
+ if(QDELETED(target_loc))
+ return
+
+ var/sound/sound_datum = sound(sound_path)
+ sound_datum.frequency = 1 / sound_duration
+ sound_datum.pitch = sound_pitch * sound_duration
+
+ playsound(target_loc, sound_datum, sound_volume, vol_cat = category_lookup[sound_category])
+ return TRUE
+ if("play_zlevel")
+ if(!sound_path)
+ return
+
+ var/sound/sound_datum = sound(sound_path)
+ sound_datum.frequency = 1 / sound_duration
+ sound_datum.pitch = sound_pitch * sound_duration
+
+ playsound_z(list(zlevel_lookup[target_zlevel]), sound_datum, sound_volume, vol_cat = category_lookup[sound_category])
+ return TRUE
+ if("play_group")
+ if(!sound_path)
+ return
+
+ var/sound/sound_datum = sound(sound_path)
+ sound_datum.frequency = 1 / sound_duration
+ sound_datum.pitch = sound_pitch * sound_duration
+
+ var/list/targets = list()
+ switch(target_group)
+ if("Global")
+ targets = GLOB.mob_list
+ if("Humans")
+ targets = GLOB.human_mob_list + GLOB.dead_mob_list
+ if("Xenos")
+ targets = GLOB.xeno_mob_list + GLOB.dead_mob_list
+ if("Ghosts")
+ targets = GLOB.observer_list + GLOB.dead_mob_list
+
+ for(var/mob/target as anything in targets)
+ playsound_client(target.client, sound_datum, vol = sound_volume, vol_cat = category_lookup[sound_category])
+ return TRUE
diff --git a/code/modules/admin/verbs/SDQL2/SDQL_2.dm b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
index 05da6d3c86..7f0d61d671 100644
--- a/code/modules/admin/verbs/SDQL2/SDQL_2.dm
+++ b/code/modules/admin/verbs/SDQL2/SDQL_2.dm
@@ -113,12 +113,13 @@
Here's a slightly more formal quick reference.
- The 4 queries you can do are:
+ The 5 queries you can do are:
"SELECT "
"CALL ON "
"UPDATE SET var=,var2="
"DELETE "
+ "SINGLECALL