diff --git a/_maps/RandomZLevels/Cabin.dmm b/_maps/RandomZLevels/Cabin.dmm
index f4a18fd87c..4d07c02565 100644
--- a/_maps/RandomZLevels/Cabin.dmm
+++ b/_maps/RandomZLevels/Cabin.dmm
@@ -967,10 +967,6 @@
},
/turf/open/floor/plating,
/area/awaymission/cabin)
-"dw" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/closed/indestructible/rock/snow,
-/area/space)
(1,1,1) = {"
aa
@@ -1227,7 +1223,7 @@ aa
aa
aa
aa
-dw
+aa
"}
(2,1,1) = {"
aa
diff --git a/_maps/RandomZLevels/VR/snowdin_VR.dmm b/_maps/RandomZLevels/VR/snowdin_VR.dmm
index 8f6eda450c..bae0b05e3d 100644
--- a/_maps/RandomZLevels/VR/snowdin_VR.dmm
+++ b/_maps/RandomZLevels/VR/snowdin_VR.dmm
@@ -4,9 +4,15 @@
/turf/closed/indestructible/rock/snow,
/area/awaymission/snowdin/cave/mountain)
"ab" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/closed/indestructible/rock/snow,
-/area/awaymission/snowdin/cave/mountain)
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/mob/living/simple_animal/hostile/netherworld/migo,
+/turf/open/floor/plasteel,
+/area/awaymission/snowdin/post/mining_dock)
"ac" = (
/turf/closed/indestructible/rock/snow,
/area/awaymission/snowdin/cave/mountain)
@@ -10267,16 +10273,6 @@
/obj/item/shard,
/turf/open/floor/plating,
/area/awaymission/snowdin/post/mining_dock)
-"xA" = (
-/mob/living/simple_animal/hostile/netherworld/migo,
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/awaymission/snowdin/post/mining_dock)
"xB" = (
/obj/effect/turf_decal/stripes/line{
dir = 4
@@ -16013,7 +16009,7 @@ ac
ac
"}
(2,1,1) = {"
-ab
+ac
ac
ac
ac
@@ -66518,7 +66514,7 @@ wD
wT
xe
xs
-xA
+ab
xI
xN
wL
diff --git a/_maps/RandomZLevels/caves.dmm b/_maps/RandomZLevels/caves.dmm
index d50480c381..96c1a08b1f 100644
--- a/_maps/RandomZLevels/caves.dmm
+++ b/_maps/RandomZLevels/caves.dmm
@@ -2251,10 +2251,6 @@
initial_gas_mix = "n2=23;o2=14"
},
/area/awaymission/caves/BMP_asteroid)
-"gW" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/closed/indestructible/rock,
-/area/space/nearstation)
"gX" = (
/obj/effect/baseturf_helper/lava,
/turf/closed/mineral/volcanic,
@@ -2536,7 +2532,7 @@ aa
aa
aa
aa
-gW
+aa
"}
(2,1,1) = {"
aa
diff --git a/_maps/RandomZLevels/moonoutpost19.dmm b/_maps/RandomZLevels/moonoutpost19.dmm
index ddc8778ac1..8c33326703 100644
--- a/_maps/RandomZLevels/moonoutpost19.dmm
+++ b/_maps/RandomZLevels/moonoutpost19.dmm
@@ -7215,10 +7215,6 @@
heat_capacity = 1e+006
},
/area/awaymission/moonoutpost19/research)
-"oV" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/open/space,
-/area/space)
"vV" = (
/obj/machinery/door/airlock/external,
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
@@ -7512,7 +7508,7 @@ aa
aa
aa
aa
-oV
+aa
"}
(2,1,1) = {"
aa
diff --git a/_maps/RandomZLevels/snowdin.dmm b/_maps/RandomZLevels/snowdin.dmm
index 525c8fcf7b..57e03061dd 100644
--- a/_maps/RandomZLevels/snowdin.dmm
+++ b/_maps/RandomZLevels/snowdin.dmm
@@ -4,9 +4,15 @@
/turf/closed/indestructible/rock/snow,
/area/awaymission/snowdin/cave/mountain)
"ab" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/closed/indestructible/rock/snow,
-/area/awaymission/snowdin/cave/mountain)
+/obj/effect/turf_decal/tile/neutral{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/neutral{
+ dir = 8
+ },
+/mob/living/simple_animal/hostile/netherworld/migo,
+/turf/open/floor/plasteel,
+/area/awaymission/snowdin/post/mining_dock)
"ac" = (
/turf/closed/indestructible/rock/snow,
/area/awaymission/snowdin/cave/mountain)
@@ -10331,16 +10337,6 @@
/obj/item/shard,
/turf/open/floor/plating,
/area/awaymission/snowdin/post/mining_dock)
-"xA" = (
-/mob/living/simple_animal/hostile/netherworld/migo,
-/obj/effect/turf_decal/tile/neutral{
- dir = 1
- },
-/obj/effect/turf_decal/tile/neutral{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/awaymission/snowdin/post/mining_dock)
"xB" = (
/obj/effect/turf_decal/stripes/line{
dir = 4
@@ -16108,7 +16104,7 @@ ac
ac
"}
(2,1,1) = {"
-ab
+ac
ac
ac
ac
@@ -66613,7 +66609,7 @@ wD
wT
xe
xs
-xA
+ab
xI
xN
wL
diff --git a/_maps/RandomZLevels/spacebattle.dmm b/_maps/RandomZLevels/spacebattle.dmm
index 21cd37f78f..66d7f556b4 100644
--- a/_maps/RandomZLevels/spacebattle.dmm
+++ b/_maps/RandomZLevels/spacebattle.dmm
@@ -5,6 +5,16 @@
"ab" = (
/turf/open/space,
/area/space)
+"ac" = (
+/obj/effect/turf_decal/tile/blue{
+ dir = 1
+ },
+/obj/effect/turf_decal/tile/blue{
+ dir = 8
+ },
+/mob/living/simple_animal/hostile/syndicate/melee/sword,
+/turf/open/floor/plasteel,
+/area/awaymission/spacebattle/cruiser)
"ad" = (
/obj/structure/shuttle/engine/propulsion/right{
dir = 1
@@ -2597,16 +2607,6 @@
/obj/effect/spawner/lootdrop/armory_contraband,
/turf/open/floor/plating,
/area/awaymission/spacebattle/cruiser)
-"jK" = (
-/mob/living/simple_animal/hostile/syndicate/melee/sword,
-/obj/effect/turf_decal/tile/blue{
- dir = 1
- },
-/obj/effect/turf_decal/tile/blue{
- dir = 8
- },
-/turf/open/floor/plasteel,
-/area/awaymission/spacebattle/cruiser)
"jL" = (
/obj/machinery/door/poddoor{
id = "spacebattlearmory";
@@ -2894,10 +2894,6 @@
/obj/item/mecha_parts/mecha_equipment/weapon/energy/ion,
/turf/open/floor/plating,
/area/awaymission/spacebattle/cruiser)
-"kM" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/closed/mineral/random,
-/area/space/nearstation)
"vw" = (
/turf/closed/wall/mineral/plastitanium/nodiagonal,
/area/awaymission/spacebattle/syndicate5)
@@ -3175,7 +3171,7 @@ aa
aa
aa
aa
-kM
+aa
"}
(2,1,1) = {"
aa
@@ -35686,10 +35682,10 @@ eC
cn
fL
cp
-jK
+ac
fs
fO
-jK
+ac
eM
eM
eM
diff --git a/_maps/RandomZLevels/undergroundoutpost45.dmm b/_maps/RandomZLevels/undergroundoutpost45.dmm
index f6d46ef97d..56018b943a 100644
--- a/_maps/RandomZLevels/undergroundoutpost45.dmm
+++ b/_maps/RandomZLevels/undergroundoutpost45.dmm
@@ -25,6 +25,9 @@
"ag" = (
/turf/closed/wall/mineral/titanium,
/area/awaymission/undergroundoutpost45/central)
+"ah" = (
+/turf/open/space,
+/area/space/nearstation)
"aj" = (
/obj/effect/decal/cleanable/dirt,
/turf/open/floor/plasteel{
@@ -13945,10 +13948,6 @@
temperature = 363.9
},
/area/awaymission/undergroundoutpost45/caves)
-"zi" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/open/space,
-/area/space/nearstation)
"KE" = (
/obj/machinery/atmospherics/components/unary/vent_pump/on{
dir = 4
@@ -14211,7 +14210,7 @@ aa
aa
aa
aa
-zi
+ah
"}
(2,1,1) = {"
aa
diff --git a/_maps/map_files/Mining/Lavaland.dmm b/_maps/map_files/Mining/Lavaland.dmm
index de42e1ac63..99c536bd3c 100644
--- a/_maps/map_files/Mining/Lavaland.dmm
+++ b/_maps/map_files/Mining/Lavaland.dmm
@@ -3571,14 +3571,6 @@
},
/turf/open/floor/plating/asteroid/basalt/lava_land_surface,
/area/lavaland/surface/outdoors)
-"Wt" = (
-/obj/effect/baseturf_helper/lava_land/surface,
-/turf/closed/wall,
-/area/mine/laborcamp/security)
-"Wz" = (
-/obj/effect/mapping_helpers/planet_z,
-/turf/open/lava/smooth/lava_land_surface,
-/area/lavaland/surface/outdoors)
"WA" = (
/obj/effect/mapping_helpers/airlock/cyclelink_helper{
dir = 8
@@ -3630,26 +3622,6 @@
},
/turf/open/floor/plasteel,
/area/mine/production)
-"WF" = (
-/obj/effect/baseturf_helper/lava_land/surface,
-/turf/closed/wall,
-/area/mine/laborcamp)
-"WH" = (
-/obj/effect/baseturf_helper/lava_land/surface,
-/turf/closed/wall,
-/area/mine/eva)
-"WI" = (
-/obj/effect/baseturf_helper/lava_land/surface,
-/turf/closed/wall,
-/area/mine/production)
-"WJ" = (
-/obj/effect/baseturf_helper/lava_land/surface,
-/turf/closed/wall/r_wall,
-/area/mine/maintenance)
-"WK" = (
-/obj/effect/baseturf_helper/lava_land/surface,
-/turf/closed/wall,
-/area/mine/living_quarters)
(1,1,1) = {"
aa
@@ -3906,7 +3878,7 @@ aj
aj
aj
aj
-Wz
+aj
"}
(2,1,1) = {"
aa
@@ -8751,7 +8723,7 @@ aq
WB
aq
bi
-WF
+aq
WC
aq
bZ
@@ -9012,7 +8984,7 @@ az
az
aq
ca
-Wt
+ca
ca
aj
aj
@@ -12102,7 +12074,7 @@ ai
cQ
dk
dA
-WJ
+cQ
ed
er
eM
@@ -13134,7 +13106,7 @@ dR
ef
es
dZ
-WK
+cM
fg
cM
cM
@@ -20320,9 +20292,9 @@ bt
bH
bV
cq
-WH
+bf
+bq
bq
-WI
db
bP
bP
diff --git a/code/__DEFINES/dynamic.dm b/code/__DEFINES/dynamic.dm
new file mode 100644
index 0000000000..45c1ba9cb2
--- /dev/null
+++ b/code/__DEFINES/dynamic.dm
@@ -0,0 +1,14 @@
+#define CURRENT_LIVING_PLAYERS 1
+#define CURRENT_LIVING_ANTAGS 2
+#define CURRENT_DEAD_PLAYERS 3
+#define CURRENT_OBSERVERS 4
+
+#define NO_ASSASSIN (1<<0)
+#define WAROPS_ALWAYS_ALLOWED (1<<1)
+
+#define ONLY_RULESET (1<<0)
+#define HIGHLANDER_RULESET (1<<1)
+#define TRAITOR_RULESET (1<<2)
+#define MINOR_RULESET (1<<3)
+
+#define RULESET_STOP_PROCESSING 1
diff --git a/code/__DEFINES/maps.dm b/code/__DEFINES/maps.dm
index 1b42217fe1..73e900226e 100644
--- a/code/__DEFINES/maps.dm
+++ b/code/__DEFINES/maps.dm
@@ -38,8 +38,6 @@ require only minor tweaks.
#define ZTRAIT_SPACE_RUINS "Space Ruins"
#define ZTRAIT_LAVA_RUINS "Lava Ruins"
#define ZTRAIT_ISOLATED_RUINS "Isolated Ruins" //Placing ruins on z levels with this trait will use turf reservation instead of usual placement.
-// prevents certain turfs from being stripped by a singularity
-#define ZTRAIT_PLANET "Planet"
// number - bombcap is multiplied by this before being applied to bombs
#define ZTRAIT_BOMBCAP_MULTIPLIER "Bombcap Multiplier"
@@ -60,11 +58,18 @@ require only minor tweaks.
// CROSSLINKED - mixed in with the cross-linked space pool
#define CROSSLINKED "Cross"
+// string - type path of the z-level's baseturf (defaults to space)
+#define ZTRAIT_BASETURF "Baseturf"
+
// default trait definitions, used by SSmapping
#define ZTRAITS_CENTCOM list(ZTRAIT_CENTCOM = TRUE)
#define ZTRAITS_STATION list(ZTRAIT_LINKAGE = CROSSLINKED, ZTRAIT_STATION = TRUE)
#define ZTRAITS_SPACE list(ZTRAIT_LINKAGE = CROSSLINKED, ZTRAIT_SPACE_RUINS = TRUE)
-#define ZTRAITS_LAVALAND list(ZTRAIT_MINING = TRUE, ZTRAIT_LAVA_RUINS = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 5)
+#define ZTRAITS_LAVALAND list(\
+ ZTRAIT_MINING = TRUE, \
+ ZTRAIT_LAVA_RUINS = TRUE, \
+ ZTRAIT_BOMBCAP_MULTIPLIER = 5, \
+ ZTRAIT_BASETURF = /turf/open/lava/smooth/lava_land_surface)
#define ZTRAITS_REEBE list(ZTRAIT_REEBE = TRUE, ZTRAIT_BOMBCAP_MULTIPLIER = 0.5)
#define DL_NAME "name"
diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm
index d89743d2d2..d595abd3ec 100644
--- a/code/__DEFINES/misc.dm
+++ b/code/__DEFINES/misc.dm
@@ -427,6 +427,7 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
//Dummy mob reserve slots
#define DUMMY_HUMAN_SLOT_PREFERENCES "dummy_preference_preview"
+#define DUMMY_HUMAN_SLOT_HOLOFORM "dummy_holoform_generation"
#define DUMMY_HUMAN_SLOT_ADMIN "admintools"
#define DUMMY_HUMAN_SLOT_MANIFEST "dummy_manifest_generation"
@@ -529,4 +530,10 @@ GLOBAL_LIST_INIT(pda_reskins, list(PDA_SKIN_CLASSIC = 'icons/obj/pda.dmi', PDA_S
#define CRYOMOBS 'icons/obj/cryo_mobs.dmi'
+#define CUSTOM_HOLOFORM_DELAY 10 SECONDS //prevents spamming to make lag. it's pretty expensive to do this.
+
+#define HOLOFORM_FILTER_AI "FILTER_AI"
+#define HOLOFORM_FILTER_PAI "FILTER_PAI"
+#define HOLOFORM_FILTER_STATIC "FILTER_STATIC"
+
#define CANT_REENTER_ROUND -1
diff --git a/code/__DEFINES/vote.dm b/code/__DEFINES/vote.dm
new file mode 100644
index 0000000000..8fb2e6deab
--- /dev/null
+++ b/code/__DEFINES/vote.dm
@@ -0,0 +1,6 @@
+#define PLURALITY_VOTING 0
+#define APPROVAL_VOTING 1
+#define RANKED_CHOICE_VOTING 2
+#define SCORE_VOTING 3
+
+GLOBAL_LIST_INIT(vote_score_options,list("Bad","Poor","Acceptable","Good","Great"))
diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm
index c259831f94..3ee77d3edc 100644
--- a/code/__HELPERS/_logging.dm
+++ b/code/__HELPERS/_logging.dm
@@ -157,6 +157,8 @@
WRITE_LOG(GLOB.config_error_log, text)
SEND_TEXT(world.log, text)
+/proc/log_mapping(text)
+ WRITE_LOG(GLOB.world_map_error_log, text)
/* For logging round startup. */
/proc/start_log(log)
diff --git a/code/__HELPERS/custom_holoforms.dm b/code/__HELPERS/custom_holoforms.dm
new file mode 100644
index 0000000000..0e97314f1f
--- /dev/null
+++ b/code/__HELPERS/custom_holoforms.dm
@@ -0,0 +1,62 @@
+// Generates a holoform appearance
+// Equipment list is slot = path.
+/proc/generate_custom_holoform_from_prefs(datum/preferences/prefs, list/equipment_by_slot, list/inhand_equipment, copy_job = FALSE, apply_loadout = FALSE)
+ ASSERT(prefs)
+ var/mob/living/carbon/human/dummy/mannequin = generate_or_wait_for_human_dummy(DUMMY_HUMAN_SLOT_HOLOFORM)
+ prefs.copy_to(mannequin)
+ if(apply_loadout && prefs.parent)
+ SSjob.equip_loadout(prefs.parent.mob, mannequin, bypass_prereqs = TRUE)
+ if(copy_job)
+ var/datum/job/highest = prefs.get_highest_job()
+ if(highest && !istype(highest, /datum/job/ai) && !istype(highest, /datum/job/cyborg))
+ highest.equip(mannequin, TRUE, preference_source = prefs.parent)
+
+ if(length(equipment_by_slot))
+ for(var/slot in equipment_by_slot)
+ var/obj/item/I = new equipment_by_slot[slot]
+ mannequin.equip_to_slot_if_possible(I, slot, TRUE, TRUE, TRUE, TRUE)
+ if(length(inhand_equipment))
+ for(var/path in inhand_equipment)
+ var/obj/item/I = new path
+ mannequin.equip_to_slot_if_possible(I, SLOT_HANDS, TRUE, TRUE, TRUE, TRUE)
+
+
+ var/icon/combined = new
+ for(var/d in GLOB.cardinals)
+ mannequin.setDir(d)
+ COMPILE_OVERLAYS(mannequin)
+ CHECK_TICK
+ var/icon/capture = getFlatIcon(mannequin)
+ CHECK_TICK
+ combined.Insert(capture, dir = d)
+ CHECK_TICK
+
+ unset_busy_human_dummy(DUMMY_HUMAN_SLOT_HOLOFORM)
+ return combined
+
+/proc/process_holoform_icon_filter(icon/I, filter_type, clone = TRUE)
+ if(clone)
+ I = icon(I) //Clone
+ switch(filter_type)
+ if(HOLOFORM_FILTER_AI)
+ I = getHologramIcon(I)
+ if(HOLOFORM_FILTER_STATIC)
+ I = getStaticIcon(I)
+ if(HOLOFORM_FILTER_PAI)
+ I = getPAIHologramIcon(I)
+ return I
+
+//Errors go to user.
+/proc/generate_custom_holoform_from_prefs_safe(datum/preferences/prefs, mob/user)
+ if(user)
+ if(user.client.prefs.last_custom_holoform > world.time - CUSTOM_HOLOFORM_DELAY)
+ to_chat(user, "You are attempting to set your custom holoform too fast!")
+ return
+ return generate_custom_holoform_from_prefs(prefs, null, null, TRUE, TRUE)
+
+//Prompts this client for custom holoform parameters.
+/proc/user_interface_custom_holoform(client/C)
+ var/datum/preferences/target_prefs = C.prefs
+ ASSERT(target_prefs)
+ //In the future, maybe add custom path allowances a la admin create outfit but for now..
+ return generate_custom_holoform_from_prefs_safe(target_prefs, C.mob)
diff --git a/code/__HELPERS/icons.dm b/code/__HELPERS/icons.dm
index 5fb64004c0..1af3143d9d 100644
--- a/code/__HELPERS/icons.dm
+++ b/code/__HELPERS/icons.dm
@@ -939,7 +939,7 @@ world
I.pixel_y++
add_overlay(I)//And finally add the overlay.
-/proc/getHologramIcon(icon/A, safety=1)//If safety is on, a new icon is not created.
+/proc/getHologramIcon(icon/A, safety = TRUE)//If safety is on, a new icon is not created.
var/icon/flat_icon = safety ? A : new(A)//Has to be a new icon to not constantly change the same icon.
flat_icon.ColorTone(rgb(125,180,225))//Let's make it bluish.
flat_icon.ChangeOpacity(0.5)//Make it half transparent.
@@ -947,6 +947,14 @@ world
flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect.
return flat_icon
+/proc/getPAIHologramIcon(icon/A, safety = TRUE)
+ var/icon/flat_icon = safety? A : new(A)
+ flat_icon.SetIntensity(0.75, 1, 0.75)
+ flat_icon.ChangeOpacity(0.7)
+ var/icon/alpha_mask = new('icons/effects/effects.dmi', "scanlineslow")//Scanline effect.
+ flat_icon.AddAlphaMask(alpha_mask)//Finally, let's mix in a distortion effect.
+ return flat_icon
+
//What the mob looks like as animated static
//By vg's ComicIronic
/proc/getStaticIcon(icon/A, safety = TRUE)
diff --git a/code/__HELPERS/level_traits.dm b/code/__HELPERS/level_traits.dm
index 55ee069321..3e6e88c8fa 100644
--- a/code/__HELPERS/level_traits.dm
+++ b/code/__HELPERS/level_traits.dm
@@ -12,6 +12,3 @@
#define is_reserved_level(z) SSmapping.level_trait(z, ZTRAIT_RESERVED)
#define is_away_level(z) SSmapping.level_trait(z, ZTRAIT_AWAY)
-
-// If true, the singularity cannot strip away asteroid turf on this Z
-#define is_planet_level(z) SSmapping.level_trait(z, ZTRAIT_PLANET)
diff --git a/code/_globalvars/logging.dm b/code/_globalvars/logging.dm
index 8e5da083e3..5ca3513e66 100644
--- a/code/_globalvars/logging.dm
+++ b/code/_globalvars/logging.dm
@@ -28,6 +28,8 @@ GLOBAL_VAR(world_job_debug_log)
GLOBAL_PROTECT(world_job_debug_log)
GLOBAL_VAR(world_virus_log)
GLOBAL_PROTECT(world_virus_log)
+GLOBAL_VAR(world_map_error_log)
+GLOBAL_PROTECT(world_map_error_log)
GLOBAL_LIST_EMPTY(bombers)
GLOBAL_PROTECT(bombers)
diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm
index a5d27819b8..14954524fa 100644
--- a/code/controllers/configuration/configuration.dm
+++ b/code/controllers/configuration/configuration.dm
@@ -14,6 +14,7 @@
var/list/modes // allowed modes
var/list/gamemode_cache
var/list/votable_modes // votable modes
+ var/list/storyteller_cache
var/list/mode_names
var/list/mode_reports
var/list/mode_false_report_weight
@@ -37,6 +38,7 @@
CRASH("/datum/controller/configuration/Load() called more than once!")
InitEntries()
LoadModes()
+ storyteller_cache = typecacheof(/datum/dynamic_storyteller, TRUE)
if(fexists("[directory]/config.txt") && LoadEntries("config.txt") <= 1)
var/list/legacy_configs = list("game_options.txt", "dbconfig.txt", "comms.txt")
for(var/I in legacy_configs)
@@ -227,6 +229,7 @@
for(var/T in gamemode_cache)
// I wish I didn't have to instance the game modes in order to look up
// their information, but it is the only way (at least that I know of).
+ // for future reference: just use initial() lol
var/datum/game_mode/M = new T()
if(M.config_tag)
@@ -317,6 +320,14 @@
return new T
return new /datum/game_mode/extended()
+/datum/controller/configuration/proc/pick_storyteller(storyteller_name)
+ for(var/T in storyteller_cache)
+ var/datum/dynamic_storyteller/S = T
+ var/name = initial(S.name)
+ if(name && name == storyteller_name)
+ return T
+ return /datum/dynamic_storyteller/classic
+
/datum/controller/configuration/proc/get_runnable_modes()
var/list/datum/game_mode/runnable_modes = new
var/list/probabilities = Get(/datum/config_entry/keyed_list/probability)
diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm
index 61b5788ef8..b95ef43d37 100644
--- a/code/controllers/configuration/entries/game_options.dm
+++ b/code/controllers/configuration/entries/game_options.dm
@@ -386,6 +386,8 @@
config_entry_value = 72000
min_val = 0
+/datum/config_entry/flag/pai_custom_holoforms
+
/datum/config_entry/number/marauder_delay_non_reebe
config_entry_value = 1800
min_val = 0
diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm
index 1bfdc79d27..2a59558bcd 100644
--- a/code/controllers/subsystem/air.dm
+++ b/code/controllers/subsystem/air.dm
@@ -315,7 +315,7 @@ SUBSYSTEM_DEF(air)
var/starting_ats = active_turfs.len
sleep(world.tick_lag)
var/timer = world.timeofday
- warning("There are [starting_ats] active turfs at roundstart, this is a mapping error caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required)")
+ log_mapping("There are [starting_ats] active turfs at roundstart caused by a difference of the air between the adjacent turfs. You can see its coordinates using \"Mapping -> Show roundstart AT list\" verb (debug verbs required).")
for(var/turf/T in active_turfs)
GLOB.active_turfs_startlist += T
diff --git a/code/controllers/subsystem/job.dm b/code/controllers/subsystem/job.dm
index af7ae825f4..7597a9229a 100644
--- a/code/controllers/subsystem/job.dm
+++ b/code/controllers/subsystem/job.dm
@@ -658,7 +658,7 @@ SUBSYSTEM_DEF(job)
message_admins(msg)
CRASH(msg)
-/datum/controller/subsystem/job/proc/equip_loadout(mob/dead/new_player/N, mob/living/M, equipbackpackstuff)
+/datum/controller/subsystem/job/proc/equip_loadout(mob/dead/new_player/N, mob/living/M, equipbackpackstuff, bypass_prereqs = FALSE)
var/mob/the_mob = N
if(!the_mob)
the_mob = M // cause this doesn't get assigned if player is a latejoiner
@@ -671,7 +671,7 @@ SUBSYSTEM_DEF(job)
if(!G)
continue
var/permitted = TRUE
- if(G.restricted_roles && G.restricted_roles.len && !(M.mind.assigned_role in G.restricted_roles))
+ if(!bypass_prereqs && G.restricted_roles && G.restricted_roles.len && !(M.mind.assigned_role in G.restricted_roles))
permitted = FALSE
if(G.donoritem && !G.donator_ckey_check(the_mob.client.ckey))
permitted = FALSE
diff --git a/code/controllers/subsystem/persistence.dm b/code/controllers/subsystem/persistence.dm
index 8329493818..e43c8f21a2 100644
--- a/code/controllers/subsystem/persistence.dm
+++ b/code/controllers/subsystem/persistence.dm
@@ -13,13 +13,14 @@ SUBSYSTEM_DEF(persistence)
var/list/saved_messages = list()
var/list/saved_modes = list(1,2,3)
var/list/saved_dynamic_rules = list(list(),list(),list())
- var/list/saved_threat_levels = list(1,1,1)
+ var/list/saved_storytellers = list("foo","bar","baz","foo again","bar again")
var/list/saved_maps
var/list/saved_trophies = list()
var/list/spawned_objects = list()
var/list/antag_rep = list()
var/list/antag_rep_change = list()
var/list/picture_logging_information = list()
+ var/list/saved_votes = list()
var/list/obj/structure/sign/picture_frame/photo_frames
var/list/obj/item/storage/photo_album/photo_albums
@@ -29,9 +30,12 @@ SUBSYSTEM_DEF(persistence)
LoadChiselMessages()
LoadTrophies()
LoadRecentModes()
- LoadRecentThreats()
+ LoadRecentStorytellers()
+ LoadRecentRulesets()
LoadRecentMaps()
LoadPhotoPersistence()
+ for(var/client/C in GLOB.clients)
+ LoadSavedVote(C.ckey)
if(CONFIG_GET(flag/use_antag_rep))
LoadAntagReputation()
LoadRandomizedRecipes()
@@ -169,14 +173,23 @@ SUBSYSTEM_DEF(persistence)
return
saved_modes = json["data"]
-/datum/controller/subsystem/persistence/proc/LoadRecentThreats()
- var/json_file = file("data/RecentThreatLevels.json")
+/datum/controller/subsystem/persistence/proc/LoadRecentRulesets()
+ var/json_file = file("data/RecentRulesets.json")
if(!fexists(json_file))
return
var/list/json = json_decode(file2text(json_file))
if(!json)
return
- saved_threat_levels = json["data"]
+ saved_dynamic_rules = json["data"]
+
+/datum/controller/subsystem/persistence/proc/LoadRecentStorytellers()
+ var/json_file = file("data/RecentStorytellers.json")
+ if(!fexists(json_file))
+ return
+ var/list/json = json_decode(file2text(json_file))
+ if(!json)
+ return
+ saved_storytellers = json["data"]
/datum/controller/subsystem/persistence/proc/LoadRecentMaps()
var/json_file = file("data/RecentMaps.json")
@@ -197,6 +210,15 @@ SUBSYSTEM_DEF(persistence)
return
antag_rep = json_decode(json)
+/datum/controller/subsystem/persistence/proc/LoadSavedVote(var/ckey)
+ var/json_file = file("data/player_saves/[copytext(ckey,1,2)]/[ckey]/SavedVotes.json")
+ if(!fexists(json_file))
+ return
+ var/list/json = json_decode(file2text(json_file))
+ if(!json)
+ return
+ saved_votes[ckey] = json["data"]
+
/datum/controller/subsystem/persistence/proc/SetUpTrophies(list/trophy_items)
for(var/A in GLOB.trophy_cases)
var/obj/structure/displaycase/trophy/T = A
@@ -230,7 +252,7 @@ SUBSYSTEM_DEF(persistence)
CollectRoundtype()
if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode
- CollectThreatLevel(mode)
+ CollectStoryteller(mode)
CollectRulesets(mode)
RecordMaps()
SavePhotoPersistence() //THIS IS PERSISTENCE, NOT THE LOGGING PORTION.
@@ -388,13 +410,16 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
-/datum/controller/subsystem/persistence/proc/CollectThreatLevel(var/datum/game_mode/dynamic/mode)
- saved_threat_levels[3] = saved_threat_levels[2]
- saved_threat_levels[2] = saved_threat_levels [1]
- saved_threat_levels[1] = mode.threat_level
- var/json_file = file("data/RecentThreatLevels.json")
+/datum/controller/subsystem/persistence/proc/CollectStoryteller(var/datum/game_mode/dynamic/mode)
+ saved_storytellers.len = 5
+ saved_storytellers[5] = saved_storytellers[4]
+ saved_storytellers[4] = saved_storytellers[3]
+ saved_storytellers[3] = saved_storytellers[2]
+ saved_storytellers[2] = saved_storytellers[1]
+ saved_storytellers[1] = mode.storyteller.name
+ var/json_file = file("data/RecentStorytellers.json")
var/list/file_data = list()
- file_data["data"] = saved_threat_levels
+ file_data["data"] = saved_storytellers
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
@@ -402,8 +427,9 @@ SUBSYSTEM_DEF(persistence)
saved_dynamic_rules[3] = saved_dynamic_rules[2]
saved_dynamic_rules[2] = saved_dynamic_rules[1]
saved_dynamic_rules[1] = list()
- for(var/datum/dynamic_ruleset/ruleset in mode.executed_rules)
- saved_dynamic_rules[1] += ruleset.config_tag
+ for(var/r in mode.executed_rules)
+ var/datum/dynamic_ruleset/rule = r
+ saved_dynamic_rules[1] += rule.config_tag
var/json_file = file("data/RecentRulesets.json")
var/list/file_data = list()
file_data["data"] = saved_dynamic_rules
@@ -473,3 +499,11 @@ SUBSYSTEM_DEF(persistence)
fdel(json_file)
WRITE_FILE(json_file, json_encode(file_data))
+
+/datum/controller/subsystem/persistence/proc/SaveSavedVotes()
+ for(var/ckey in saved_votes)
+ var/json_file = file("data/player_saves/[copytext(ckey,1,2)]/[ckey]/SavedVotes.json")
+ var/list/file_data = list()
+ file_data["data"] = saved_votes[ckey]
+ fdel(json_file)
+ WRITE_FILE(json_file, json_encode(file_data))
diff --git a/code/controllers/subsystem/shuttle.dm b/code/controllers/subsystem/shuttle.dm
index fe40b3f01f..b406a8ccad 100644
--- a/code/controllers/subsystem/shuttle.dm
+++ b/code/controllers/subsystem/shuttle.dm
@@ -37,7 +37,7 @@ SUBSYSTEM_DEF(shuttle)
var/points = 5000 //number of trade-points we have
var/centcom_message = "" //Remarks from CentCom on how well you checked the last order.
var/list/discoveredPlants = list() //Typepaths for unusual plants we've already sent CentCom, associated with their potencies
- var/passive_supply_points_per_minute = 750
+ var/passive_supply_points_per_minute = 500
var/list/supply_packs = list()
var/list/shoppinglist = list()
diff --git a/code/controllers/subsystem/ticker.dm b/code/controllers/subsystem/ticker.dm
index 974d870a8a..45c8e3c8fe 100755
--- a/code/controllers/subsystem/ticker.dm
+++ b/code/controllers/subsystem/ticker.dm
@@ -483,7 +483,10 @@ SUBSYSTEM_DEF(ticker)
SSticker.timeLeft = 900
SSticker.modevoted = TRUE
var/dynamic = CONFIG_GET(flag/dynamic_voting)
- SSvote.initiate_vote(dynamic ? "dynamic" : "roundtype","server",TRUE)
+ if(dynamic)
+ SSvote.initiate_vote("dynamic","server",hideresults=TRUE,votesystem=SCORE_VOTING,forced=TRUE,vote_time = 2 MINUTES)
+ else
+ SSvote.initiate_vote("roundtype","server",hideresults=TRUE,votesystem=PLURALITY_VOTING,forced=TRUE, vote_time = 2 MINUTES)
/datum/controller/subsystem/ticker/Recover()
current_state = SSticker.current_state
diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm
index dee10e4998..573c89a5af 100644
--- a/code/controllers/subsystem/vote.dm
+++ b/code/controllers/subsystem/vote.dm
@@ -1,3 +1,5 @@
+#define VOTE_COOLDOWN 10
+
SUBSYSTEM_DEF(vote)
name = "Vote"
wait = 10
@@ -8,13 +10,17 @@ SUBSYSTEM_DEF(vote)
var/initiator = null
var/started_time = null
- var/time_remaining = 0
+ var/end_time = 0
var/mode = null
+ var/vote_system = PLURALITY_VOTING
var/question = null
var/list/choices = list()
+ var/list/choice_descs = list() // optional descriptions
var/list/voted = list()
var/list/voting = list()
+ var/list/saved = list()
var/list/generated_actions = list()
+ var/next_pop = 0
var/obfuscated = FALSE//CIT CHANGE - adds obfuscated/admin-only votes
@@ -22,28 +28,30 @@ SUBSYSTEM_DEF(vote)
/datum/controller/subsystem/vote/fire() //called by master_controller
if(mode)
- time_remaining = round((started_time + CONFIG_GET(number/vote_period) - world.time)/10)
-
- if(time_remaining < 0)
+ if(end_time < world.time)
result()
+ SSpersistence.SaveSavedVotes()
for(var/client/C in voting)
C << browse(null, "window=vote;can_close=0")
reset()
- else
+ else if(next_pop < world.time)
var/datum/browser/client_popup
for(var/client/C in voting)
- client_popup = new(C, "vote", "Voting Panel")
+ client_popup = new(C, "vote", "Voting Panel", nwidth=600,nheight=700)
client_popup.set_window_options("can_close=0")
client_popup.set_content(interface(C))
client_popup.open(0)
+ next_pop = world.time+VOTE_COOLDOWN
+
/datum/controller/subsystem/vote/proc/reset()
initiator = null
- time_remaining = 0
+ end_time = 0
mode = null
question = null
choices.Cut()
+ choice_descs.Cut()
voted.Cut()
voting.Cut()
obfuscated = FALSE //CIT CHANGE - obfuscated votes
@@ -84,17 +92,114 @@ SUBSYSTEM_DEF(vote)
. += option
return .
+/datum/controller/subsystem/vote/proc/calculate_condorcet_votes(var/blackbox_text)
+ // https://en.wikipedia.org/wiki/Schulze_method#Implementation
+ var/list/d[][] = new/list(choices.len,choices.len) // the basic vote matrix, how many times a beats b
+ for(var/ckey in voted)
+ var/list/this_vote = voted[ckey]
+ for(var/a in 1 to choices.len)
+ for(var/b in a+1 to choices.len)
+ var/a_rank = this_vote.Find(a)
+ var/b_rank = this_vote.Find(b)
+ a_rank = a_rank ? a_rank : choices.len+1
+ b_rank = b_rank ? b_rank : choices.len+1
+ if(a_rankopposite_pref)
+ p[i][j] = d[i][j]
+ p[j][i] = 0
+ else
+ p[i][j] = 0
+ p[j][i] = d[i][j]
+ for(var/i in 1 to choices.len)
+ for(var/j in 1 to choices.len)
+ if(i != j)
+ for(var/k in 1 to choices.len) // YEAH O(n^3) !!
+ if(i != k && j != k)
+ p[j][k] = max(p[j][k],min(p[j][i], p[i][k]))
+ //one last pass, now that we've done the math
+ for(var/i in 1 to choices.len)
+ for(var/j in 1 to choices.len)
+ if(i != j)
+ SSblackbox.record_feedback("nested tally","voting",p[i][j],list(blackbox_text,"Shortest Paths",choices[i],choices[j]))
+ if(p[i][j] >= p[j][i])
+ choices[choices[i]]++ // higher shortest path = better candidate, so we add to choices here
+ // choices[choices[i]] is the schulze ranking, here, rather than raw vote numbers
+
+/datum/controller/subsystem/vote/proc/calculate_majority_judgement_vote(var/blackbox_text)
+ // https://en.wikipedia.org/wiki/Majority_judgment
+ var/list/scores_by_choice = list()
+ for(var/choice in choices)
+ scores_by_choice[choice] = list()
+ for(var/ckey in voted)
+ var/list/this_vote = voted[ckey]
+ var/list/pretty_vote = list()
+ for(var/choice in this_vote)
+ sorted_insert(scores_by_choice[choice],this_vote[choice],/proc/cmp_numeric_asc)
+ // START BALLOT GATHERING
+ pretty_vote += choice
+ pretty_vote[choice] = GLOB.vote_score_options[this_vote[choice]]
+ SSblackbox.record_feedback("associative","voting_ballots",1,pretty_vote)
+ // END BALLOT GATHERING
+ for(var/score_name in scores_by_choice)
+ var/list/score = scores_by_choice[score_name]
+ for(var/indiv_score in score)
+ SSblackbox.record_feedback("nested tally","voting",1,list(blackbox_text,"Scores",score_name,GLOB.vote_score_options[indiv_score]))
+ if(score.len == 0)
+ scores_by_choice -= score_name
+ while(scores_by_choice.len > 1)
+ var/highest_median = 0
+ for(var/score_name in scores_by_choice) // first get highest median
+ var/list/score = scores_by_choice[score_name]
+ if(!score.len)
+ scores_by_choice -= score_name
+ continue
+ var/median = score[max(1,round(score.len/2))]
+ if(median >= highest_median)
+ highest_median = median
+ for(var/score_name in scores_by_choice) // then, remove
+ var/list/score = scores_by_choice[score_name]
+ var/median = score[max(1,round(score.len/2))]
+ if(median < highest_median)
+ scores_by_choice -= score_name
+ for(var/score_name in scores_by_choice) // after removals
+ var/list/score = scores_by_choice[score_name]
+ if(score.len == 0)
+ choices[score_name] += 100 // we're in a tie situation--just go with the first one
+ return
+ var/median_pos = max(1,round(score.len/2))
+ score.Cut(median_pos,median_pos+1)
+ choices[score_name]++
+
/datum/controller/subsystem/vote/proc/announce_result()
- var/list/winners = get_result()
+ var/vote_title_text
var/text
+ if(question)
+ text += "[question]"
+ vote_title_text = "[question]"
+ else
+ text += "[capitalize(mode)] Vote"
+ vote_title_text = "[capitalize(mode)] Vote"
+ if(vote_system == RANKED_CHOICE_VOTING)
+ calculate_condorcet_votes(vote_title_text)
+ if(vote_system == SCORE_VOTING)
+ calculate_majority_judgement_vote(vote_title_text)
+ var/list/winners = get_result()
var/was_roundtype_vote = mode == "roundtype" || mode == "dynamic"
if(winners.len > 0)
- if(question)
- text += "[question]"
- else
- text += "[capitalize(mode)] Vote"
if(was_roundtype_vote)
stored_gamemode_votes = list()
+ if(!obfuscated && vote_system == RANKED_CHOICE_VOTING)
+ text += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
if(!votes)
@@ -116,17 +221,25 @@ SUBSYSTEM_DEF(vote)
log_vote(text)
remove_action_buttons()
to_chat(world, "\n[text]")
+ switch(vote_system)
+ if(APPROVAL_VOTING,PLURALITY_VOTING)
+ for(var/i=1,i<=choices.len,i++)
+ SSblackbox.record_feedback("nested tally","voting",choices[choices[i]],list(vote_title_text,choices[i]))
+ if(RANKED_CHOICE_VOTING)
+ for(var/i=1,i<=voted.len,i++)
+ var/list/myvote = voted[voted[i]]
+ for(var/j=1,j<=myvote.len,j++)
+ SSblackbox.record_feedback("nested tally","voting",1,list(vote_title_text,"[j]\th",choices[myvote[j]]))
if(obfuscated) //CIT CHANGE - adds obfuscated votes. this messages admins with the vote's true results
var/admintext = "Obfuscated results"
+ if(vote_system == RANKED_CHOICE_VOTING)
+ admintext += "\nIt should be noted that this is not a raw tally of votes (impossible in ranked choice) but the score determined by the schulze method of voting, so the numbers will look weird!"
for(var/i=1,i<=choices.len,i++)
var/votes = choices[choices[i]]
admintext += "\n[choices[i]]: [votes]"
message_admins(admintext)
return .
-#define PEACE "calm"
-#define CHAOS "chaotic"
-
/datum/controller/subsystem/vote/proc/result()
. = announce_result()
var/restart = 0
@@ -152,33 +265,15 @@ SUBSYSTEM_DEF(vote)
if("dynamic")
if(SSticker.current_state > GAME_STATE_PREGAME)//Don't change the mode if the round already started.
return message_admins("A vote has tried to change the gamemode, but the game has already started. Aborting.")
- GLOB.master_mode = "dynamic"
- if("extended" in choices)
- if(. == "extended")
- GLOB.dynamic_forced_extended = TRUE // we still do the rest of the stuff
- choices[PEACE] += choices["extended"]
- var/mean = 0
- var/voters = 0
- for(var/client/c in GLOB.clients)
- var/vote = c.prefs.preferred_chaos
- if(vote)
- voters += 1
- switch(vote)
- if(CHAOS_NONE)
- mean -= 0.1
- if(CHAOS_LOW)
- mean -= 0.05
- if(CHAOS_HIGH)
- mean += 0.05
- if(CHAOS_MAX)
- mean += 0.1
- mean/=voters
- if(voted.len != 0)
- mean += (choices[PEACE]*-1+choices[CHAOS])/voted.len
- GLOB.dynamic_curve_centre = mean*20
- GLOB.dynamic_curve_width = CLAMP(2-abs(mean*5),0.5,4)
- to_chat(world,"Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width].")
- log_admin("Dynamic curve centre set to [GLOB.dynamic_curve_centre] and width set to [GLOB.dynamic_curve_width]")
+ if(. == "Secret")
+ GLOB.master_mode = "secret"
+ SSticker.save_mode(.)
+ message_admins("The gamemode has been voted for, and has been changed to: [GLOB.master_mode]")
+ log_admin("Gamemode has been voted for and switched to: [GLOB.master_mode].")
+ else
+ GLOB.master_mode = "dynamic"
+ var/datum/dynamic_storyteller/S = config.pick_storyteller(.)
+ GLOB.dynamic_storyteller_type = S
if("map")
var/datum/map_config/VM = config.maplist[.]
message_admins("The map has been voted for and will change to: [VM.map_name]")
@@ -196,27 +291,58 @@ SUBSYSTEM_DEF(vote)
else
to_chat(world, "Notice:Restart vote will not restart the server automatically because there are active admins on.")
message_admins("A restart vote has passed, but there are active admins on with +server, so it has been canceled. If you wish, you may restart the server.")
-
+
return .
-/datum/controller/subsystem/vote/proc/submit_vote(vote)
+/datum/controller/subsystem/vote/proc/submit_vote(vote, score = 0)
if(mode)
if(CONFIG_GET(flag/no_dead_vote) && usr.stat == DEAD && !usr.client.holder)
return 0
- if(!(usr.ckey in voted))
- if(vote && 1<=vote && vote<=choices.len)
- voted += usr.ckey
- voted[usr.ckey] = vote
- choices[choices[vote]]++ //check this
- return vote
- else if(vote && 1<=vote && vote<=choices.len)
- choices[choices[voted[usr.ckey]]]--
- voted[usr.ckey] = vote
- choices[choices[vote]]++
- return vote
+ if(vote && ISINRANGE(vote, 1, choices.len))
+ switch(vote_system)
+ if(PLURALITY_VOTING)
+ if(usr.ckey in voted)
+ choices[choices[voted[usr.ckey]]]--
+ voted[usr.ckey] = vote
+ choices[choices[vote]]++
+ return vote
+ else
+ voted += usr.ckey
+ voted[usr.ckey] = vote
+ choices[choices[vote]]++ //check this
+ return vote
+ if(APPROVAL_VOTING)
+ if(usr.ckey in voted)
+ if(vote in voted[usr.ckey])
+ voted[usr.ckey] -= vote
+ choices[choices[vote]]--
+ else
+ voted[usr.ckey] += vote
+ choices[choices[vote]]++
+ else
+ voted += usr.ckey
+ voted[usr.ckey] = list(vote)
+ choices[choices[vote]]++
+ return vote
+ if(RANKED_CHOICE_VOTING)
+ if(usr.ckey in voted)
+ if(vote in voted[usr.ckey])
+ voted[usr.ckey] -= vote
+ else
+ voted += usr.ckey
+ voted[usr.ckey] = list()
+ voted[usr.ckey] += vote
+ saved -= usr.ckey
+ if(SCORE_VOTING)
+ if(!(usr.ckey in voted))
+ voted += usr.ckey
+ voted[usr.ckey] = list()
+ voted[usr.ckey][choices[vote]] = score
+ saved -= usr.ckey
return 0
-/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes
+/datum/controller/subsystem/vote/proc/initiate_vote(vote_type, initiator_key, hideresults, votesystem = PLURALITY_VOTING, forced = FALSE,vote_time = -1)//CIT CHANGE - adds hideresults argument to votes to allow for obfuscated votes
+ vote_system = votesystem
if(!mode)
if(started_time)
var/next_allowed_time = (started_time + CONFIG_GET(number/vote_delay))
@@ -257,11 +383,17 @@ SUBSYSTEM_DEF(vote)
if("roundtype") //CIT CHANGE - adds the roundstart secret/extended vote
choices.Add("secret", "extended")
if("dynamic")
- var/saved_threats = SSpersistence.saved_threat_levels
- if((saved_threats[1]+saved_threats[2]+saved_threats[3])>150)
- choices.Add("extended",PEACE,CHAOS)
- else
- choices.Add(PEACE,CHAOS)
+ for(var/T in config.storyteller_cache)
+ var/datum/dynamic_storyteller/S = T
+ var/recent_rounds = 0
+ for(var/i in 1 to SSpersistence.saved_storytellers.len)
+ if(SSpersistence.saved_storytellers[i] == initial(S.name))
+ recent_rounds++
+ if(recent_rounds < initial(S.weight))
+ choices.Add(initial(S.name))
+ choice_descs.Add(initial(S.desc))
+ choices.Add("Secret")
+ choice_descs.Add("Standard secret. Switches mode if it wins.")
if("custom")
question = stripped_input(usr,"What is the vote for?")
if(!question)
@@ -280,9 +412,11 @@ SUBSYSTEM_DEF(vote)
if(mode == "custom")
text += "\n[question]"
log_vote(text)
- var/vp = CONFIG_GET(number/vote_period)
+ var/vp = vote_time
+ if(vp == -1)
+ vp = CONFIG_GET(number/vote_period)
to_chat(world, "\n[text]\nType vote or click here to place your votes.\nYou have [DisplayTimeText(vp)] to vote.")
- time_remaining = round(vp/10)
+ end_time = started_time+vp
for(var/c in GLOB.clients)
SEND_SOUND(c, sound('sound/misc/server-ready.ogg'))
var/client/C = c
@@ -292,6 +426,11 @@ SUBSYSTEM_DEF(vote)
C.player_details.player_actions += V
V.Grant(C.mob)
generated_actions += V
+ if(forced)
+ var/datum/browser/popup = new(C, "vote", "Voting Panel",nwidth=600,nheight=700)
+ popup.set_window_options("can_close=0")
+ popup.set_content(SSvote.interface(C))
+ popup.open(0)
return 1
return 0
@@ -311,14 +450,71 @@ SUBSYSTEM_DEF(vote)
. += "Vote: '[question]'
"
else
. += "Vote: [capitalize(mode)]
"
- . += "Time Left: [time_remaining] s
"
- for(var/i=1,i<=choices.len,i++)
- var/votes = choices[choices[i]]
- var/ivotedforthis = ((C.ckey in voted) && (voted[C.ckey] == i) ? TRUE : FALSE)
- if(!votes)
- votes = 0
- . += "- [ivotedforthis ? "" : ""][choices[i]] ([obfuscated ? (admin ? "??? ([votes])" : "???") : votes] votes)[ivotedforthis ? "" : ""]
" // CIT CHANGE - adds obfuscated votes
- . += "
"
+ switch(vote_system)
+ if(PLURALITY_VOTING)
+ . += "Vote one.
"
+ if(APPROVAL_VOTING)
+ . += "Vote any number of choices.
"
+ if(RANKED_CHOICE_VOTING)
+ . += "Vote by order of preference. Revoting will demote to the bottom. 1 is your favorite, and higher numbers are worse.
"
+ if(SCORE_VOTING)
+ . += "Grade the candidates by how much you like them.
"
+ . += "No-votes have no power--your opinion is only heard if you vote!
"
+ . += "Time Left: [DisplayTimeText(end_time-world.time)]
"
+ switch(vote_system)
+ if(PLURALITY_VOTING, APPROVAL_VOTING)
+ for(var/i=1,i<=choices.len,i++)
+ var/votes = choices[choices[i]]
+ var/ivotedforthis = FALSE
+ switch(vote_system)
+ if(PLURALITY_VOTING)
+ ivotedforthis = ((C.ckey in voted) && (voted[C.ckey] == i))
+ if(APPROVAL_VOTING)
+ ivotedforthis = ((C.ckey in voted) && (i in voted[C.ckey]))
+ if(!votes)
+ votes = 0
+ . += "- [ivotedforthis ? "" : ""][choices[i]] ([obfuscated ? (admin ? "??? ([votes])" : "???") : votes] votes)[ivotedforthis ? "" : ""]
" // CIT CHANGE - adds obfuscated votes
+ if(choice_descs.len >= i)
+ . += "- [choice_descs[i]]
"
+ . += "
"
+ if(RANKED_CHOICE_VOTING)
+ var/list/myvote = voted[C.ckey]
+ for(var/i=1,i<=choices.len,i++)
+ var/vote = (myvote ? (myvote.Find(i)) : 0)
+ if(vote)
+ . += "[choices[i]] ([vote])"
+ else
+ . += "[choices[i]]"
+ if(choice_descs.len >= i)
+ . += "[choice_descs[i]]"
+ . += "
"
+ if(!(C.ckey in saved))
+ . += "(Save vote)"
+ else
+ . += "(Saved!)"
+ . += "(Load vote from save)"
+ . += "(Reset votes)"
+ if(SCORE_VOTING)
+ var/list/myvote = voted[C.ckey]
+ for(var/i=1,i<=choices.len,i++)
+ . += "[choices[i]]"
+ for(var/r in 1 to GLOB.vote_score_options.len)
+ . += " "
+ if((choices[i] in myvote) && myvote[choices[i]] == r)
+ . +="([GLOB.vote_score_options[r]])"
+ else
+ . +="[GLOB.vote_score_options[r]]"
+ . += ""
+ . += ""
+ if(choice_descs.len >= i)
+ . += "[choice_descs[i]]"
+ . += "
"
+ if(!(C.ckey in saved))
+ . += "(Save vote)"
+ else
+ . += "(Saved!)"
+ . += "(Load vote from save)"
+ . += "(Reset votes)"
if(admin)
. += "(Cancel Vote) "
else
@@ -376,8 +572,31 @@ SUBSYSTEM_DEF(vote)
if("custom")
if(usr.client.holder)
initiate_vote("custom",usr.key)
+ if("reset")
+ if(usr.ckey in voted)
+ voted -= usr.ckey
+ if("save")
+ if(usr.ckey in voted)
+ if(!(usr.ckey in SSpersistence.saved_votes))
+ SSpersistence.saved_votes[usr.ckey] = list()
+ SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey]
+ saved += usr.ckey
+ if("load")
+ if(!(usr.ckey in SSpersistence.saved_votes))
+ SSpersistence.LoadSavedVote(usr.ckey)
+ if(!(usr.ckey in SSpersistence.saved_votes))
+ SSpersistence.saved_votes[usr.ckey] = list()
+ if(usr.ckey in voted)
+ SSpersistence.saved_votes[usr.ckey][mode] = voted[usr.ckey]
+ else
+ SSpersistence.saved_votes[usr.ckey][mode] = list()
+ voted[usr.ckey] = SSpersistence.saved_votes[usr.ckey][mode]
+ saved += usr.ckey
else
- submit_vote(round(text2num(href_list["vote"])))
+ if(vote_system == SCORE_VOTING)
+ submit_vote(round(text2num(href_list["vote"])),round(text2num(href_list["score"])))
+ else
+ submit_vote(round(text2num(href_list["vote"])))
usr.vote()
/datum/controller/subsystem/vote/proc/remove_action_buttons()
@@ -392,7 +611,7 @@ SUBSYSTEM_DEF(vote)
set category = "OOC"
set name = "Vote"
- var/datum/browser/popup = new(src, "vote", "Voting Panel")
+ var/datum/browser/popup = new(src, "vote", "Voting Panel",nwidth=600,nheight=700)
popup.set_window_options("can_close=0")
popup.set_content(SSvote.interface(client))
popup.open(0)
@@ -419,6 +638,3 @@ SUBSYSTEM_DEF(vote)
var/datum/player_details/P = GLOB.player_details[owner.ckey]
if(P)
P.player_actions -= src
-
-#undef PEACE
-#undef CHAOS
diff --git a/code/datums/emotes.dm b/code/datums/emotes.dm
index f0fca5db5a..599c492d4c 100644
--- a/code/datums/emotes.dm
+++ b/code/datums/emotes.dm
@@ -136,9 +136,10 @@
/datum/emote/sound
var/sound //Sound to play when emote is called
var/vary = FALSE //used for the honk borg emote
+ var/volume = 50
mob_type_allowed_typecache = list(/mob/living/brain, /mob/living/silicon)
/datum/emote/sound/run_emote(mob/user, params)
. = ..()
if(.)
- playsound(user.loc, sound, 50, vary)
+ playsound(user.loc, sound, volume, vary)
diff --git a/code/game/atoms.dm b/code/game/atoms.dm
index 773c12b6af..36b701aae1 100644
--- a/code/game/atoms.dm
+++ b/code/game/atoms.dm
@@ -47,7 +47,7 @@
/atom/New(loc, ...)
//atom creation method that preloads variables at creation
if(GLOB.use_preloader && (src.type == GLOB._preloader.target_path))//in case the instanciated atom is creating other atoms in New()
- GLOB._preloader.load(src)
+ world.preloader_load(src)
if(datum_flags & DF_USE_TAG)
GenerateTag()
diff --git a/code/game/gamemodes/dynamic/dynamic.dm b/code/game/gamemodes/dynamic/dynamic.dm
index 0890d81dfb..821c819664 100644
--- a/code/game/gamemodes/dynamic/dynamic.dm
+++ b/code/game/gamemodes/dynamic/dynamic.dm
@@ -1,14 +1,3 @@
-#define CURRENT_LIVING_PLAYERS 1
-#define CURRENT_LIVING_ANTAGS 2
-#define CURRENT_DEAD_PLAYERS 3
-#define CURRENT_OBSERVERS 4
-
-#define ONLY_RULESET 1
-#define HIGHLANDER_RULESET 2
-#define TRAITOR_RULESET 4
-#define MINOR_RULESET 8
-
-#define RULESET_STOP_PROCESSING 1
// -- Injection delays
GLOBAL_VAR_INIT(dynamic_latejoin_delay_min, (10 MINUTES))
@@ -52,6 +41,8 @@ GLOBAL_LIST_EMPTY(dynamic_forced_roundstart_ruleset)
// Forced threat level, setting this to zero or higher forces the roundstart threat to the value.
GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
+GLOBAL_VAR_INIT(dynamic_storyteller_type, null)
+
/datum/game_mode/dynamic
name = "dynamic mode"
config_tag = "dynamic"
@@ -60,7 +51,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
announce_text = "Dynamic mode!" // This needs to be changed maybe
reroll_friendly = FALSE;
-
+ // Current storyteller
+ var/datum/dynamic_storyteller/storyteller = null
// Threat logging vars
/// The "threat cap", threat shouldn't normally go above this and is used in ruleset calculations
var/threat_level = 0
@@ -164,6 +156,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
dat += "Threat to Spend: [threat] \[Adjust\] \[View Log\]
"
dat += "
"
+ dat += "Storyteller: [storyteller.name]
"
dat += "Parameters: centre = [GLOB.dynamic_curve_centre] ; width = [GLOB.dynamic_curve_width].
"
dat += "On average, [peaceful_percentage]% of the rounds are more peaceful.
"
dat += "Forced extended: [GLOB.dynamic_forced_extended ? "On" : "Off"]
"
@@ -178,7 +171,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
dat += "[DR.ruletype] - [DR.name]
"
else
dat += "none.
"
- dat += "
Injection Timers: ([get_injection_chance(TRUE)]% chance)
"
+ dat += "
Injection Timers: ([storyteller.get_injection_chance(TRUE)]% chance)
"
dat += "Latejoin: [(latejoin_injection_cooldown-world.time)>60*10 ? "[round((latejoin_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(latejoin_injection_cooldown-world.time)] seconds"] \[Now!\]
"
dat += "Midround: [(midround_injection_cooldown-world.time)>60*10 ? "[round((midround_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(midround_injection_cooldown-world.time)] seconds"] \[Now!\]
"
dat += "Event: [(event_injection_cooldown-world.time)>60*10 ? "[round((event_injection_cooldown-world.time)/60/10,0.1)] minutes" : "[(event_injection_cooldown-world.time)] seconds"] \[Now!\]
"
@@ -336,6 +329,9 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
SSblackbox.record_feedback("tally","dynamic_threat",peaceful_percentage,"Percent of same-vote rounds that are more peaceful")
/datum/game_mode/dynamic/can_start()
+ storyteller = new GLOB.dynamic_storyteller_type // this is where all the initialization happens
+ storyteller.on_start()
+ SSblackbox.record_feedback("text","dynamic_storyteller",1,storyteller.name)
message_admins("Dynamic mode parameters for the round:")
message_admins("Centre is [GLOB.dynamic_curve_centre], Width is [GLOB.dynamic_curve_width], Forced extended is [GLOB.dynamic_forced_extended ? "Enabled" : "Disabled"], No stacking is [GLOB.dynamic_no_stacking ? "Enabled" : "Disabled"].")
message_admins("Stacking limit is [GLOB.dynamic_stacking_limit], Classic secret is [GLOB.dynamic_classic_secret ? "Enabled" : "Disabled"], High population limit is [GLOB.dynamic_high_pop_limit].")
@@ -345,19 +341,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if(GLOB.dynamic_forced_threat_level >= 0)
threat_level = round(GLOB.dynamic_forced_threat_level, 0.1)
threat = threat_level
- SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Threat level (forced by admins)")
+ SSblackbox.record_feedback("tally","dynamic_threat",threat_level,"Threat level (forced)")
else
generate_threat()
- var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min)
- latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time
+ storyteller.start_injection_cooldowns()
- var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max)
- midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time
-
- var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
- event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
-
log_game("DYNAMIC: Dynamic Mode initialized with a Threat Level of... [threat_level]!")
initial_threat_level = threat_level
return TRUE
@@ -391,7 +380,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
log_game("DYNAMIC: [roundstart_rules.len] rules.")
return TRUE
SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Roundstart rules considered")
- SSblackbox.record_feedback("tally","dynamic",roundstart_rules.len,"Players readied up")
+ SSblackbox.record_feedback("tally","dynamic",roundstart_pop_ready,"Players readied up")
if(GLOB.dynamic_forced_roundstart_ruleset.len > 0)
rigged_roundstart()
else
@@ -429,13 +418,7 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (GLOB.dynamic_forced_extended)
log_game("DYNAMIC: Starting a round of forced extended.")
return TRUE
- var/list/drafted_rules = list()
- for (var/datum/dynamic_ruleset/roundstart/rule in roundstart_rules)
- if (rule.acceptable(roundstart_pop_ready, threat_level) && threat >= rule.cost) // If we got the population and threat required
- rule.candidates = candidates.Copy()
- rule.trim_candidates()
- if (rule.ready() && rule.candidates.len > 0)
- drafted_rules[rule] = rule.weight
+ var/list/drafted_rules = storyteller.roundstart_draft()
if(!drafted_rules.len)
message_admins("Not enough threat level for roundstart antags!")
log_game("DYNAMIC: Not enough threat level for roundstart antags!")
@@ -653,7 +636,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
if (rule.persistent)
current_rules += rule
return TRUE
- rule.clean_up()
stack_trace("The [rule.ruletype] rule \"[rule.name]\" failed to execute.")
return FALSE
@@ -667,62 +649,41 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
current_rules -= rule
SSblackbox.record_feedback("tally","dynamic",1,"Rulesets finished")
SSblackbox.record_feedback("associative","dynamic_rulesets_finished",1,rule.get_blackbox_info())
-
+
+ storyteller.do_process()
+
if (midround_injection_cooldown < world.time)
if (GLOB.dynamic_forced_extended)
return
// Somehow it managed to trigger midround multiple times so this was moved here.
// There is no way this should be able to trigger an injection twice now.
- var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
- midround_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max)) + world.time)
+ midround_injection_cooldown = storyteller.get_midround_cooldown() + world.time
// Time to inject some threat into the round
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // Unless the shuttle is gone
return
+ if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) // no rules after shuttle is auto-called
+ return
message_admins("DYNAMIC: Checking for midround injection.")
log_game("DYNAMIC: Checking for midround injection.")
update_playercounts()
- if (get_injection_chance())
+ if (prob(storyteller.get_injection_chance()))
SSblackbox.record_feedback("tally","dynamic",1,"Attempted midround injections")
- var/cur_threat_frac = threat/threat_level
- var/list/drafted_rules = list()
- var/antag_num = current_players[CURRENT_LIVING_ANTAGS].len
- for (var/datum/dynamic_ruleset/midround/rule in midround_rules)
- // if there are antags OR the rule is an antag rule, antag_acceptable will be true.
- if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
- // Classic secret : only autotraitor/minor roles
- if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
- continue
- rule.trim_candidates()
- if (rule.ready())
- if(!antag_num)
- drafted_rules[rule] = round(rule.get_weight() + (rule.cost * cur_threat_frac))
- else
- drafted_rules[rule] = rule.get_weight()
- else if(threat < rule.cost)
- SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
+ var/list/drafted_rules = storyteller.midround_draft()
if (drafted_rules.len > 0)
SSblackbox.record_feedback("tally","dynamic",1,"Successful midround injections")
picking_midround_latejoin_rule(drafted_rules)
- else
- midround_injection_cooldown = (midround_injection_cooldown + world.time)/2
+ // get_injection_chance can do things on fail
if(event_injection_cooldown < world.time)
SSblackbox.record_feedback("tally","dynamic",1,"Attempted event injections")
- var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
- event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
+ event_injection_cooldown = storyteller.get_event_cooldown() + world.time
message_admins("DYNAMIC: Doing event injection.")
log_game("DYNAMIC: Doing event injection.")
update_playercounts()
- var/list/drafted_rules = list()
- for(var/datum/dynamic_ruleset/event/rule in events)
- if(rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
- if(rule.ready())
- drafted_rules[rule] = rule.get_weight()
- else if(threat < rule.cost)
- SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
+ var/list/drafted_rules = storyteller.event_draft()
if(drafted_rules.len > 0)
SSblackbox.record_feedback("tally","dynamic",1,"Successful event injections")
picking_midround_latejoin_rule(drafted_rules)
@@ -748,31 +709,6 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
continue
current_players[CURRENT_DEAD_PLAYERS].Add(M) // Players who actually died (and admins who ghosted, would be nice to avoid counting them somehow)
-/// Gets the chance for latejoin and midround injection, the dry_run argument is only used for forced injection.
-/datum/game_mode/dynamic/proc/get_injection_chance(dry_run = FALSE)
- if(forced_injection)
- forced_injection = !dry_run
- return 100
- var/chance = 0
- // If the high pop override is in effect, we reduce the impact of population on the antag injection chance
- var/high_pop_factor = (current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit)
- var/max_pop_per_antag = max(5,15 - round(threat_level/10) - round(current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5)))
- if (!current_players[CURRENT_LIVING_ANTAGS].len)
- chance += 80 // No antags at all? let's boost those odds!
- else
- var/current_pop_per_antag = current_players[CURRENT_LIVING_PLAYERS].len / current_players[CURRENT_LIVING_ANTAGS].len
- if (current_pop_per_antag > max_pop_per_antag)
- chance += min(50, 25+10*(current_pop_per_antag-max_pop_per_antag))
- else
- chance += 25-10*(max_pop_per_antag-current_pop_per_antag)
- if (current_players[CURRENT_DEAD_PLAYERS].len > current_players[CURRENT_LIVING_PLAYERS].len)
- chance -= 30 // More than half the crew died? ew, let's calm down on antags
- if (threat > 70)
- chance += 15
- if (threat < 30)
- chance -= 15
- return round(max(0,chance))
-
/// Removes type from the list
/datum/game_mode/dynamic/proc/remove_from_list(list/type_list, type)
for(var/I in type_list)
@@ -803,7 +739,8 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
return
if(EMERGENCY_ESCAPED_OR_ENDGAMED) // No more rules after the shuttle has left
return
-
+ if((world.realtime - SSshuttle.realtimeofstart) > SSshuttle.auto_call) // no rules after shuttle is auto-called
+ return
update_playercounts()
if (forced_latejoin_rule)
@@ -814,28 +751,12 @@ GLOBAL_VAR_INIT(dynamic_forced_threat_level, -1)
picking_midround_latejoin_rule(list(forced_latejoin_rule), forced = TRUE)
forced_latejoin_rule = null
- else if (latejoin_injection_cooldown < world.time && prob(get_injection_chance()))
+ else if (latejoin_injection_cooldown < world.time && prob(storyteller.get_injection_chance()))
SSblackbox.record_feedback("tally","dynamic",1,"Attempted latejoin injections")
- var/list/drafted_rules = list()
- for (var/datum/dynamic_ruleset/latejoin/rule in latejoin_rules)
- if (rule.acceptable(current_players[CURRENT_LIVING_PLAYERS].len, threat_level) && threat >= rule.cost)
- // Classic secret : only autotraitor/minor roles
- if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
- continue
- // No stacking : only one round-ender, unless threat level > stacking_limit.
- if (threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
- if(rule.flags & HIGHLANDER_RULESET && highlander_executed)
- continue
-
- rule.candidates = list(newPlayer)
- rule.trim_candidates()
- if (rule.ready())
- drafted_rules[rule] = rule.get_weight()
-
+ var/list/drafted_rules = storyteller.latejoin_draft(newPlayer)
if (drafted_rules.len > 0 && picking_midround_latejoin_rule(drafted_rules))
SSblackbox.record_feedback("tally","dynamic",1,"Successful latejoin injections")
- var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
- latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max)) + world.time
+ latejoin_injection_cooldown = storyteller.get_latejoin_cooldown() + world.time
/// Refund threat, but no more than threat_level.
/datum/game_mode/dynamic/proc/refund_threat(regain)
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets.dm b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
index 7f6ea088a1..2f143f7233 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets.dm
@@ -80,9 +80,13 @@
/// Delay for when execute will get called from the time of post_setup (roundstart) or process (midround/latejoin).
/// Make sure your ruleset works with execute being called during the game when using this, and that the clean_up proc reverts it properly in case of faliure.
var/delay = 0
+ /// List of tags for use in storytellers.
+ var/list/property_weights = list()
/// Whether or not recent-round weight values are taken into account for this ruleset.
/// Weight reduction uses the same values as secret's recent-round mode weight reduction.
var/always_max_weight = FALSE
+ /// Weight reduction by recent-rounds. Saved on new.
+ var/weight_mult = 1
/datum/dynamic_ruleset/New()
..()
@@ -96,13 +100,11 @@
var/high_population_requirements = CONFIG_GET(keyed_list/dynamic_high_population_requirement)
var/list/repeated_mode_adjust = CONFIG_GET(number_list/repeated_mode_adjust)
if(config_tag in weights)
- var/weight_mult = 1
if(!always_max_weight && SSpersistence.saved_dynamic_rules.len == 3 && repeated_mode_adjust.len == 3)
var/saved_dynamic_rules = SSpersistence.saved_dynamic_rules
for(var/i in 1 to 3)
if(config_tag in saved_dynamic_rules[i])
weight_mult -= (repeated_mode_adjust[i]/100)
- weight = weights[config_tag] * weight_mult
if(config_tag in costs)
cost = costs[config_tag]
if(config_tag in requirementses)
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
index b6cd313bfc..23e94c4065 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_events.dm
@@ -58,6 +58,7 @@
cost = 10
blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear)
requirements = list(70,60,50,50,40,40,40,30,20,15)
+ property_weights = list("story_potential" = 1, "trust" = 1, "chaos" = 1)
high_population_requirement = 15
/datum/dynamic_ruleset/event/pirates/ready(forced = FALSE)
@@ -81,6 +82,7 @@
cost = 10
requirements = list(70,60,50,50,40,40,40,30,20,15)
high_population_requirement = 15
+ property_weights = list("chaos" = 1, "valid" = 1)
//////////////////////////////////////////////
// //
@@ -100,6 +102,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5) // yes, can happen on fake-extended
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("chaos" = 1, "extended" = 2)
/datum/dynamic_ruleset/event/ventclog/ready()
if(mode.threat_level > 30 && mode.threat >= 5 && prob(20))
@@ -133,10 +136,11 @@
required_enemies = list(1,1,0,0,0,0,0,0,0,0)
weight = 4
// no repeatable weight decrease. too variable to be unfun multiple times in one round
- cost = 3
+ cost = 1
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("story_potential" = 1, "extended" = 1)
always_max_weight = TRUE
//////////////////////////////////////////////
@@ -156,6 +160,7 @@
repeatable_weight_decrease = 2
requirements = list(60,50,40,30,30,30,30,30,30,30)
high_population_requirement = 30
+ property_weights = list("extended" = -2)
/datum/dynamic_ruleset/event/meteor_wave/ready()
if(mode.threat_level > 40 && mode.threat >= 25 && prob(20))
@@ -190,6 +195,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_flux
name = "Anomaly: Hyper-Energetic Flux"
@@ -203,6 +209,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 10
repeatable = TRUE
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_gravitational
name = "Anomaly: Gravitational"
@@ -214,6 +221,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_pyroclastic
name = "Anomaly: Pyroclastic"
@@ -227,6 +235,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
repeatable = TRUE
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/anomaly_vortex
name = "Anomaly: Vortex"
@@ -240,6 +249,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
repeatable = TRUE
+ property_weights = list("extended" = 1)
//////////////////////////////////////////////
// //
@@ -259,6 +269,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
repeatable = TRUE
+ property_weights = list("extended" = -1, "chaos" = 1)
/datum/dynamic_ruleset/event/carp_migration
name = "Carp Migration"
@@ -270,6 +281,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
repeatable = TRUE
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/communications_blackout
name = "Communications Blackout"
@@ -283,6 +295,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("extended" = 1, "chaos" = 1)
/datum/dynamic_ruleset/event/processor_overload
name = "Processor Overload"
@@ -296,6 +309,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("extended" = 1, "chaos" = 1)
always_max_weight = TRUE
/datum/dynamic_ruleset/event/space_dust
@@ -310,6 +324,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("extended" = 1)
always_max_weight = TRUE
/datum/dynamic_ruleset/event/major_dust
@@ -324,6 +339,7 @@
requirements = list(10,10,10,10,10,10,10,10,10,10)
high_population_requirement = 10
repeatable = TRUE
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/electrical_storm
name = "Electrical Storm"
@@ -337,6 +353,7 @@
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/event/heart_attack
name = "Random Heart Attack"
@@ -350,6 +367,7 @@
requirements = list(101,101,101,5,5,5,5,5,5,5)
high_population_requirement = 5
repeatable = TRUE
+ property_weights = list("extended" = 1)
always_max_weight = TRUE
/datum/dynamic_ruleset/event/radiation_storm
@@ -362,3 +380,4 @@
required_enemies = list(1,1,1,1,1,1,1,1,1,1)
requirements = list(5,5,5,5,5,5,5,5,5,5)
high_population_requirement = 5
+ property_weights = list("extended" = 1,"chaos" = 1)
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
index fe9347d9d5..f4e9971a05 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_latejoin.dm
@@ -69,8 +69,15 @@
high_population_requirement = 15
repeatable = TRUE
flags = TRAITOR_RULESET
+ property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1)
always_max_weight = TRUE
+/datum/dynamic_ruleset/latejoin/infiltrator/execute()
+ . = ..()
+ for(var/datum/mind/M in assigned)
+ log_admin("[M.name] was made into a traitor by dynamic.")
+ message_admins("[M.name] was made into a traitor by dynamic.")
+
//////////////////////////////////////////////
// //
// REVOLUTIONARY PROVOCATEUR //
@@ -94,6 +101,7 @@
requirements = list(101,101,70,40,40,40,40,40,40,40)
high_population_requirement = 40
flags = HIGHLANDER_RULESET
+ property_weights = list("trust" = -2, "chaos" = 2, "extended" = -2, "valid" = 2, "conversion" = 1)
var/required_heads_of_staff = 3
var/finished = FALSE
var/datum/team/revolution/revolution
@@ -123,6 +131,8 @@
revolution.update_objectives()
revolution.update_heads()
SSshuttle.registerHostileEnvironment(src)
+ log_admin("[M.name] was made into a revolutionary by dynamic.")
+ message_admins("[M.name] was made into a revolutionary by dynamic.")
return TRUE
else
log_game("DYNAMIC: [ruletype] [name] discarded [M.name] from head revolutionary due to ineligibility.")
@@ -187,30 +197,31 @@
//////////////////////////////////////////////
// //
-// VAMPIRE //
+// BLOODSUCKERS //
// //
//////////////////////////////////////////////
-/*
-/datum/dynamic_ruleset/latejoin/vampire
- name = "vampire"
- config_tag = "vampire_latejoin"
- antag_flag = ROLE_VAMPIRE
- antag_datum = ANTAG_DATUM_VAMPIRE
- protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
+/datum/dynamic_ruleset/latejoin/bloodsucker
+ name = "Bloodsucker Infiltrator"
+ config_tag = "latejoin_bloodsucker"
+ antag_datum = ANTAG_DATUM_BLOODSUCKER
+ antag_flag = ROLE_TRAITOR
restricted_roles = list("AI", "Cyborg")
+ protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
required_candidates = 1
- weight = 5
- cost = 15
- requirements = list(80,70,60,50,40,20,20,15,15,15)
+ weight = 3
+ cost = 10
+ property_weights = list("story_potential" = 2, "extended" = 2, "trust" = -2, "valid" = 1)
+ requirements = list(70,65,60,55,50,45,40,35,30,30)
+ high_population_requirement = 30
repeatable = TRUE
- high_population_requirement = 15
-/datum/dynamic_ruleset/latejoin/vampire/pre_execute()
+/datum/dynamic_ruleset/latejoin/bloodsucker/execute()
var/mob/M = pick(candidates)
- candidates -= M
assigned += M.mind
- M.mind.restricted_roles = restricted_roles
- M.mind.special_role = ROLE_VAMPIRE
+ M.mind.special_role = antag_flag
+ if(mode.make_bloodsucker(M.mind))
+ mode.bloodsuckers += M
+ log_admin("[M.name] was made into a bloodsucker by dynamic.")
+ message_admins("[M.name] was made into a bloodsucker by dynamic.")
return TRUE
-*/
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
index 91ac73d142..c89562c9f9 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_midround.dm
@@ -108,8 +108,12 @@
candidates = pollGhostCandidates("The mode is looking for volunteers to become a [name]", antag_flag, SSticker.mode, antag_flag, poll_time = 300)
- if(!candidates || candidates.len <= required_candidates)
+ if(!candidates || candidates.len < required_candidates)
message_admins("The ruleset [name] did not receive enough applications.")
+ if(candidates)
+ message_admins("Only received [candidates.len], needed [required_candidates].")
+ else
+ message_admins("There were no candidates.")
log_game("DYNAMIC: The ruleset [name] did not receive enough applications.")
return FALSE
@@ -180,6 +184,7 @@
repeatable = TRUE
high_population_requirement = 15
flags = TRAITOR_RULESET
+ property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1)
always_max_weight = TRUE
/datum/dynamic_ruleset/midround/autotraitor/acceptable(population = 0, threat = 0)
@@ -214,6 +219,8 @@
living_players -= M
var/datum/antagonist/traitor/newTraitor = new
M.mind.add_antag_datum(newTraitor)
+ log_admin("[M] was made into a traitor by dynamic.")
+ message_admins("[M] was made into a traitor by dynamic.")
return TRUE
@@ -237,6 +244,7 @@
requirements = list(101,101,70,50,50,50,40,30,30,30)
high_population_requirement = 30
required_type = /mob/living/silicon/ai
+ property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2)
var/ion_announce = 33
var/removeDontImproveChance = 10
@@ -261,6 +269,8 @@
var/datum/antagonist/traitor/AI = new
M.mind.special_role = antag_flag
M.mind.add_antag_datum(AI)
+ log_admin("[M] was made into a malf AI by dynamic.")
+ message_admins("[M] was made into a malf AI by dynamic.")
if(prob(ion_announce))
priority_announce("Ion storm detected near the station. Please check all AI-controlled equipment for errors.", "Anomaly Alert", "ionstorm")
if(prob(removeDontImproveChance))
@@ -289,6 +299,7 @@
requirements = list(90,90,70,50,50,50,50,40,30,30)
high_population_requirement = 30
repeatable = TRUE
+ property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2, "extended" = -2)
var/datum/mind/wizard
/datum/dynamic_ruleset/midround/from_ghosts/wizard/ready(forced = FALSE)
@@ -337,6 +348,7 @@
cost = 35
requirements = list(90,90,90,80,70,60,50,40,40,40)
high_population_requirement = 40
+ property_weights = list("story_potential" = 2, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2)
var/operative_cap = list(2,2,3,3,4,5,5,5,5,5)
var/datum/team/nuclear/nuke_team
flags = HIGHLANDER_RULESET
@@ -390,6 +402,7 @@
requirements = list(101,101,101,80,60,50,50,50,50,50)
high_population_requirement = 50
repeatable = TRUE
+ property_weights = list("story_potential" = -1, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2)
/datum/dynamic_ruleset/midround/from_ghosts/blob/ready(forced = FALSE)
if (required_candidates > (dead_players.len + list_observers.len))
@@ -421,6 +434,7 @@
high_population_requirement = 50
repeatable_weight_decrease = 2
repeatable = TRUE
+ property_weights = list("story_potential" = -1, "trust" = 1, "chaos" = 2, "extended" = -2, "valid" = 2)
var/list/vents = list()
/datum/dynamic_ruleset/midround/from_ghosts/xenomorph/ready(forced = FALSE)
@@ -476,6 +490,7 @@
high_population_requirement = 50
repeatable_weight_decrease = 2
repeatable = TRUE
+ property_weights = list("story_potential" = 1, "trust" = 1, "extended" = 1, "valid" = 2, "integrity" = 2)
var/list/spawn_locs = list()
/datum/dynamic_ruleset/midround/from_ghosts/nightmare/execute()
@@ -521,6 +536,7 @@
weight = 4
cost = 5
requirements = list(30,30,20,20,15,10,10,10,10,5) // yes, it can even happen in "extended"!
+ property_weights = list("story_potential" = 1, "extended" = 1, "valid" = -2)
high_population_requirement = 5
/datum/dynamic_ruleset/midround/from_ghosts/sentient_disease/ready(forced = FALSE)
@@ -555,14 +571,18 @@
cost = 5
requirements = list(30,30,30,30,20,15,15,15,15,15)
high_population_requirement = 15
+ property_weights = list("story_potential" = -2, "extended" = -1)
var/list/spawn_locs = list()
-/datum/dynamic_ruleset/midround/from_ghosts/revenant/ready(forced = FALSE)
+/datum/dynamic_ruleset/midround/from_ghosts/revenant/acceptable(population = 0,threat = 0)
var/deadMobs = 0
for(var/mob/M in GLOB.dead_mob_list)
deadMobs++
if(deadMobs < REVENANT_SPAWN_THRESHOLD)
return FALSE
+ return ..()
+
+/datum/dynamic_ruleset/midround/from_ghosts/revenant/ready(forced = FALSE)
if(required_candidates > (dead_players.len + list_observers.len))
SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough ghosts")
return FALSE
@@ -607,6 +627,7 @@
weight = 4
cost = 15
requirements = list(101,101,101,90,80,70,60,50,40,30)
+ property_weights = list("story_potential" = -2, "extended" = -2, "integrity" = 2, "valid" = 2, "trust" = 2)
high_population_requirement = 30
var/list/spawn_locs = list()
@@ -659,6 +680,7 @@
blocking_rules = list(/datum/dynamic_ruleset/roundstart/nuclear,/datum/dynamic_ruleset/midround/from_ghosts/nuclear)
high_population_requirement = 15
var/datum/team/abductor_team/team
+ property_weights = list("story_potential" = 1, "extended" = -2, "valid" = 1, "trust" = -1, "chaos" = 2)
repeatable_weight_decrease = 4
repeatable = TRUE
@@ -699,6 +721,7 @@
cost = 15
requirements = list(101,101,101,90,80,70,60,50,40,30)
high_population_requirement = 30
+ property_weights = list("story_potential" = 1, "extended" = -2, "valid" = 2)
var/list/spawn_locs = list()
var/spawn_loc
@@ -745,31 +768,3 @@
#undef ABDUCTOR_MAX_TEAMS
#undef REVENANT_SPAWN_THRESHOLD
-
-//////////////////////////////////////////////
-// //
-// BLOODSUCKERS //
-// //
-//////////////////////////////////////////////
-
-/datum/dynamic_ruleset/latejoin/bloodsucker
- name = "Bloodsucker Infiltrator"
- config_tag = "latejoin_bloodsucker"
- antag_datum = ANTAG_DATUM_BLOODSUCKER
- antag_flag = ROLE_TRAITOR
- restricted_roles = list("AI", "Cyborg")
- protected_roles = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain", "Head of Personnel", "Chief Engineer", "Chief Medical Officer", "Research Director", "Quartermaster")
- required_candidates = 1
- weight = 3
- cost = 10
- requirements = list(90,80,70,60,55,50,45,40,35,30)
- high_population_requirement = 30
- repeatable = TRUE
-
-/datum/dynamic_ruleset/latejoin/bloodsucker/execute()
- var/mob/M = pick(candidates)
- assigned += M.mind
- M.mind.special_role = antag_flag
- if(mode.make_bloodsucker(M.mind))
- mode.bloodsuckers += M
- return TRUE
diff --git a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
index e5bb0af464..abd40a1756 100644
--- a/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
+++ b/code/game/gamemodes/dynamic/dynamic_rulesets_roundstart.dm
@@ -21,6 +21,7 @@
requirements = list(50,50,50,50,50,50,50,50,50,50)
high_population_requirement = 40
antag_cap = list(1,1,1,1,2,2,2,2,3,3)
+ property_weights = list("story_potential" = 2, "trust" = -1, "extended" = 1, "valid" = 1)
always_max_weight = TRUE
var/autotraitor_cooldown = 450 // 15 minutes (ticks once per 2 sec)
@@ -61,6 +62,7 @@
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
antag_cap = list(2,2,2,2,2,2,2,2,2,2) // Can pick 3 per team, but rare enough it doesn't matter.
+ property_weights = list("story_potential" = 1, "trust" = -1, "extended" = 1, "valid" = 1)
var/list/datum/team/brother_team/pre_brother_teams = list()
var/const/min_team_size = 2
@@ -108,6 +110,7 @@
cost = 15
scaling_cost = 15
requirements = list(101,101,101,101,101,101,101,101,101,101)
+ property_weights = list("trust" = -2, "valid" = 2)
high_population_requirement = 10
antag_cap = list(1,1,1,1,1,2,2,2,2,3)
var/team_mode_probability = 30
@@ -160,6 +163,7 @@
cost = 30
requirements = list(101,101,101,60,50,50,50,50,50,50)
high_population_requirement = 50
+ property_weights = list("story_potential" = 2, "trust" = 1, "chaos" = 2, "extended" = -2, "valid" = 2)
var/list/roundstart_wizards = list()
/datum/dynamic_ruleset/roundstart/wizard/acceptable(population=0, threat=0)
@@ -222,6 +226,7 @@
weight = 3
cost = 30
requirements = list(101,101,101,80,70,60,50,50,50,50)
+ property_weights = list("story_potential" = -1, "trust" = -1, "chaos" = 1, "conversion" = 1, "extended" = -2, "valid" = 2)
high_population_requirement = 50
flags = HIGHLANDER_RULESET
antag_cap = list(2,2,2,3,3,4,4,4,4,4)
@@ -283,6 +288,7 @@
high_population_requirement = 50
flags = HIGHLANDER_RULESET
antag_cap = list(1,1,2,3,4,5,5,5,5,5)
+ property_weights = list("story_potential" = 2, "trust" = 2, "chaos" = 2, "extended" = -2, "valid" = 2)
var/datum/team/nuclear/nuke_team
/datum/dynamic_ruleset/roundstart/nuclear/ready(forced = FALSE)
@@ -373,6 +379,7 @@
flags = HIGHLANDER_RULESET
// I give up, just there should be enough heads with 35 players...
minimum_players = 35
+ property_weights = list("trust" = -2, "chaos" = 2, "extended" = -2, "valid" = 2, "conversion" = 1)
var/datum/team/revolution/revolution
var/finished = FALSE
@@ -490,6 +497,7 @@
weight = 3
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
+ property_weights = list("extended" = 2)
high_population_requirement = 101
/datum/dynamic_ruleset/roundstart/extended/pre_execute()
@@ -517,6 +525,7 @@
high_population_requirement = 50
flags = HIGHLANDER_RULESET
antag_cap = list(2,3,3,4,4,4,4,4,4,4)
+ property_weights = list("trust" = 2, "chaos" = 2, "extended" = -2, "conversion" = 1, "valid" = 2)
var/ark_time
/datum/dynamic_ruleset/roundstart/clockcult/pre_execute()
@@ -616,6 +625,8 @@
antag_leader_datum = /datum/antagonist/nukeop/leader/clownop
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
+ property_weights = list("trust" = 2, "chaos" = 2, "extended" = -2, "story_potential" = 2, "valid" = 2)
+
/datum/dynamic_ruleset/roundstart/nuclear/clown_ops/pre_execute()
. = ..()
@@ -647,6 +658,7 @@
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
antag_cap = list(1,1,1,2,2,2,3,3,3,4)
+ property_weights = list("extended" = 1)
/datum/dynamic_ruleset/roundstart/devil/pre_execute()
var/num_devils = antag_cap[indice_pop]
@@ -698,6 +710,7 @@
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
+ property_weights = list("extended" = -2, "chaos" = 2, "conversion" = 1, "valid" = 2)
var/players_per_carrier = 30
var/monkeys_to_win = 1
var/escaped_monkeys = 0
@@ -759,6 +772,7 @@
cost = 0
requirements = list(101,101,101,101,101,101,101,101,101,101)
high_population_requirement = 101
+ property_weights = list("extended" = -2, "chaos" = 2, "trust" = 2)
var/meteordelay = 2000
var/nometeors = 0
var/rampupdelta = 5
@@ -799,7 +813,8 @@
weight = 2
cost = 15
scaling_cost = 10
- requirements = list(90,80,70,60,50,50,50,50,50,50)
+ property_weights = list("story_potential" = 1, "extended" = 1, "trust" = -2, "valid" = 1)
+ requirements = list(70,65,60,55,50,50,50,50,50,50)
high_population_requirement = 50
antag_cap = list(1,1,1,1,1,2,2,2,2,2)
diff --git a/code/game/gamemodes/dynamic/dynamic_storytellers.dm b/code/game/gamemodes/dynamic/dynamic_storytellers.dm
new file mode 100644
index 0000000000..afa6ed8ba7
--- /dev/null
+++ b/code/game/gamemodes/dynamic/dynamic_storytellers.dm
@@ -0,0 +1,235 @@
+/datum/dynamic_storyteller
+ var/name = "none"
+ var/desc = "A coder's idiocy."
+ var/list/property_weights = list()
+ var/curve_centre = 0
+ var/curve_width = 1.8
+ var/forced_threat_level = -1
+ var/flags = 0
+ var/weight = 3 // how many rounds need to have been recently played for this storyteller to be left out of the vote
+ var/datum/game_mode/dynamic/mode = null
+
+/**
+Property weights are:
+"story_potential" -- essentially how many different ways the antag can be played.
+"trust" -- How much it makes the crew trust each other. Negative values means they're suspicious. Team antags are like this.
+"chaos" -- How chaotic it makes the round. Has some overlap with "valid" and somewhat contradicts "extended".
+"valid" -- How likely the non-antag-enemy crew are to get involved, e.g. nukies encouraging the warden to
+ let everyone into the armory, wizard moving around and being a nuisance, nightmare busting lights.
+"extended" -- How much the antag is conducive to a long round. Nukies and cults are bad for this; Wizard is less bad; and so on.
+"conversion" -- Basically a bool. Conversion antags, well, convert. It's its own class for a good reason.
+*/
+
+/datum/dynamic_storyteller/New()
+ ..()
+ if (istype(SSticker.mode, /datum/game_mode/dynamic))
+ mode = SSticker.mode
+ GLOB.dynamic_curve_centre = curve_centre
+ GLOB.dynamic_curve_width = curve_width
+ GLOB.dynamic_forced_threat_level = forced_threat_level
+
+/datum/dynamic_storyteller/proc/start_injection_cooldowns()
+ var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_latejoin_delay_max + GLOB.dynamic_first_latejoin_delay_min)
+ mode.latejoin_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_first_latejoin_delay_min, GLOB.dynamic_first_latejoin_delay_max)) + world.time
+
+ var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_first_midround_delay_min + GLOB.dynamic_first_midround_delay_max)
+ mode.midround_injection_cooldown = round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_first_midround_delay_min, GLOB.dynamic_first_midround_delay_max)) + world.time
+
+ var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
+ mode.event_injection_cooldown = (round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max)) + world.time)
+
+/datum/dynamic_storyteller/proc/do_process()
+ return
+
+/datum/dynamic_storyteller/proc/on_start()
+ return
+
+/datum/dynamic_storyteller/proc/get_midround_cooldown()
+ var/midround_injection_cooldown_middle = 0.5*(GLOB.dynamic_midround_delay_max + GLOB.dynamic_midround_delay_min)
+ return round(CLAMP(EXP_DISTRIBUTION(midround_injection_cooldown_middle), GLOB.dynamic_midround_delay_min, GLOB.dynamic_midround_delay_max))
+
+/datum/dynamic_storyteller/proc/get_event_cooldown()
+ var/event_injection_cooldown_middle = 0.5*(GLOB.dynamic_event_delay_max + GLOB.dynamic_event_delay_min)
+ return round(CLAMP(EXP_DISTRIBUTION(event_injection_cooldown_middle), GLOB.dynamic_event_delay_min, GLOB.dynamic_event_delay_max))
+
+/datum/dynamic_storyteller/proc/get_latejoin_cooldown()
+ var/latejoin_injection_cooldown_middle = 0.5*(GLOB.dynamic_latejoin_delay_max + GLOB.dynamic_latejoin_delay_min)
+ return round(CLAMP(EXP_DISTRIBUTION(latejoin_injection_cooldown_middle), GLOB.dynamic_latejoin_delay_min, GLOB.dynamic_latejoin_delay_max))
+
+/datum/dynamic_storyteller/proc/get_injection_chance(dry_run = FALSE)
+ if(mode.forced_injection)
+ mode.forced_injection = !dry_run
+ return 100
+ var/chance = 0
+ // If the high pop override is in effect, we reduce the impact of population on the antag injection chance
+ var/high_pop_factor = (mode.current_players[CURRENT_LIVING_PLAYERS].len >= GLOB.dynamic_high_pop_limit)
+ var/max_pop_per_antag = max(5,15 - round(mode.threat_level/10) - round(mode.current_players[CURRENT_LIVING_PLAYERS].len/(high_pop_factor ? 10 : 5)))
+ if (!mode.current_players[CURRENT_LIVING_ANTAGS].len)
+ chance += 80 // No antags at all? let's boost those odds!
+ else
+ var/current_pop_per_antag = mode.current_players[CURRENT_LIVING_PLAYERS].len / mode.current_players[CURRENT_LIVING_ANTAGS].len
+ if (current_pop_per_antag > max_pop_per_antag)
+ chance += min(50, 25+10*(current_pop_per_antag-max_pop_per_antag))
+ else
+ chance += 25-10*(max_pop_per_antag-current_pop_per_antag)
+ if (mode.current_players[CURRENT_DEAD_PLAYERS].len > mode.current_players[CURRENT_LIVING_PLAYERS].len)
+ chance -= 30 // More than half the crew died? ew, let's calm down on antags
+ if (mode.threat > 70)
+ chance += 15
+ if (mode.threat < 30)
+ chance -= 15
+ return round(max(0,chance))
+
+/datum/dynamic_storyteller/proc/roundstart_draft()
+ var/list/drafted_rules = list()
+ for (var/datum/dynamic_ruleset/roundstart/rule in mode.roundstart_rules)
+ if (rule.acceptable(mode.roundstart_pop_ready, mode.threat_level) && mode.threat >= rule.cost) // If we got the population and threat required
+ rule.candidates = mode.candidates.Copy()
+ rule.trim_candidates()
+ if (rule.ready() && rule.candidates.len > 0)
+ var/property_weight = 0
+ for(var/property in property_weights)
+ if(property in rule.property_weights) // just treat it as 0 if it's not in there
+ property_weight += rule.property_weights[property] * property_weights[property]
+ drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
+ return drafted_rules
+
+/datum/dynamic_storyteller/proc/midround_draft()
+ var/list/drafted_rules = list()
+ for (var/datum/dynamic_ruleset/midround/rule in mode.midround_rules)
+ // if there are antags OR the rule is an antag rule, antag_acceptable will be true.
+ if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost)
+ // Classic secret : only autotraitor/minor roles
+ if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
+ continue
+ rule.trim_candidates()
+ if (rule.ready())
+ var/property_weight = 0
+ for(var/property in property_weights)
+ if(property in rule.property_weights)
+ property_weight += rule.property_weights[property] * property_weights[property]
+ drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
+ else if(mode.threat < rule.cost)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
+ return drafted_rules
+
+/datum/dynamic_storyteller/proc/latejoin_draft(mob/living/carbon/human/newPlayer)
+ var/list/drafted_rules = list()
+ for (var/datum/dynamic_ruleset/latejoin/rule in mode.latejoin_rules)
+ if (rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost)
+ // Classic secret : only autotraitor/minor roles
+ if (GLOB.dynamic_classic_secret && !((rule.flags & TRAITOR_RULESET) || (rule.flags & MINOR_RULESET)))
+ continue
+ // No stacking : only one round-ender, unless threat level > stacking_limit.
+ if (mode.threat_level > GLOB.dynamic_stacking_limit && GLOB.dynamic_no_stacking)
+ if(rule.flags & HIGHLANDER_RULESET && mode.highlander_executed)
+ continue
+
+ rule.candidates = list(newPlayer)
+ rule.trim_candidates()
+ if (rule.ready())
+ var/property_weight = 0
+ for(var/property in property_weights)
+ if(property in rule.property_weights)
+ property_weight += rule.property_weights[property] * property_weights[property]
+ drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
+ else if(mode.threat < rule.cost)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
+ return drafted_rules
+
+/datum/dynamic_storyteller/proc/event_draft()
+ var/list/drafted_rules = list()
+ for(var/datum/dynamic_ruleset/event/rule in mode.events)
+ if(rule.acceptable(mode.current_players[CURRENT_LIVING_PLAYERS].len, mode.threat_level) && mode.threat >= rule.cost)
+ if(rule.ready())
+ var/property_weight = 0
+ for(var/property in property_weights)
+ if(property in rule.property_weights)
+ property_weight += rule.property_weights[property] * property_weights[property]
+ drafted_rules[rule] = (rule.get_weight() + property_weight)*rule.weight_mult
+ else if(mode.threat < rule.cost)
+ SSblackbox.record_feedback("tally","dynamic",1,"Times rulesets rejected due to not enough threat to spend")
+ return drafted_rules
+
+
+/datum/dynamic_storyteller/cowabunga
+ name = "Chaotic"
+ curve_centre = 10
+ desc = "Chaos: high. Variation: high. Likely antags: clock cult, revs, wizard."
+ property_weights = list("extended" = -1, "chaos" = 10)
+ weight = 2
+ flags = WAROPS_ALWAYS_ALLOWED
+ var/refund_cooldown
+
+/datum/dynamic_storyteller/cowabunga/get_midround_cooldown()
+ return ..() / 4
+
+/datum/dynamic_storyteller/cowabunga/get_latejoin_cooldown()
+ return ..() / 4
+
+/datum/dynamic_storyteller/cowabunga/do_process()
+ if(refund_cooldown < world.time)
+ mode.refund_threat(10)
+ mode.log_threat("Cowabunga it is. Refunded 10 threat. Threat is now [mode.threat].")
+ refund_cooldown = world.time + 300 SECONDS
+
+/datum/dynamic_storyteller/team
+ name = "Teamwork"
+ desc = "Chaos: high. Variation: low. Likely antags: nukies, clockwork cult, wizard, blob, xenomorph."
+ curve_centre = 2
+ curve_width = 1.5
+ weight = 2
+ flags = WAROPS_ALWAYS_ALLOWED
+ property_weights = list("valid" = 3, "trust" = 5)
+
+/datum/dynamic_storyteller/team/get_injection_chance(dry_run = FALSE)
+ return (mode.current_players[CURRENT_LIVING_ANTAGS].len ? 0 : ..())
+
+/datum/dynamic_storyteller/conversion
+ name = "Conversion"
+ desc = "Chaos: high. Variation: medium. Likely antags: cults, bloodsuckers, revs."
+ curve_centre = 3
+ curve_width = 1
+ weight = 2
+ flags = WAROPS_ALWAYS_ALLOWED
+ property_weights = list("valid" = 1, "conversion" = 20)
+
+/datum/dynamic_storyteller/classic
+ name = "Random"
+ desc = "Chaos: varies. Variation: highest. No special weights attached."
+ weight = 6
+ curve_width = 4
+
+/datum/dynamic_storyteller/memes
+ name = "Story"
+ desc = "Chaos: varies. Variation: high. Likely antags: abductors, nukies, wizard, traitor."
+ curve_width = 4
+ property_weights = list("story_potential" = 10)
+
+/datum/dynamic_storyteller/suspicion
+ name = "Intrigue"
+ desc = "Chaos: low. Variation: high. Likely antags: traitor, bloodsucker. Rare: revs, blood cult."
+ curve_width = 4
+ property_weights = list("trust" = -5)
+
+/datum/dynamic_storyteller/liteextended
+ name = "Calm"
+ desc = "Chaos: low. Variation: medium. Likely antags: bloodsuckers, traitors, sentient disease, revenant."
+ curve_centre = -5
+ curve_width = 0.5
+ flags = NO_ASSASSIN
+ weight = 2
+ property_weights = list("extended" = 1, "chaos" = -1, "valid" = -1, "story_potential" = 1, "conversion" = -10)
+
+/datum/dynamic_storyteller/liteextended/get_injection_chance(dry_run = FALSE)
+ return ..()/2
+
+/datum/dynamic_storyteller/extended
+ name = "Extended"
+ desc = "Chaos: none. Variation: none. Likely antags: none."
+ curve_centre = -20
+ weight = 2
+ curve_width = 0.5
+
+/datum/dynamic_storyteller/extended/on_start()
+ GLOB.dynamic_forced_extended = TRUE
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 52b52eb288..03c1bb3bc8 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -180,7 +180,7 @@
limit--
while(!FoundDoor && limit)
if (!FoundDoor)
- log_world("### MAP WARNING, [src] at [AREACOORD(src)] failed to find a valid airlock to cyclelink with!")
+ log_mapping("[src] at [AREACOORD(src)] failed to find a valid airlock to cyclelink with!")
return
FoundDoor.cyclelinkedairlock = src
cyclelinkedairlock = FoundDoor
diff --git a/code/game/machinery/hologram.dm b/code/game/machinery/hologram.dm
index 1e7d720ee9..b1783ba940 100644
--- a/code/game/machinery/hologram.dm
+++ b/code/game/machinery/hologram.dm
@@ -500,7 +500,7 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
else
return FALSE
-/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf)
+/obj/machinery/holopad/proc/move_hologram(mob/living/user, turf/new_turf, direction)
if(LAZYLEN(masters) && masters[user])
var/obj/effect/overlay/holo_pad_hologram/holo = masters[user]
var/transfered = FALSE
@@ -512,6 +512,8 @@ For the other part of the code, check silicon say.dm. Particularly robot talk.*/
transfered = TRUE
//All is good.
holo.forceMove(new_turf)
+ if(direction)
+ holo.setDir(direction)
if(!transfered)
update_holoray(user,new_turf)
return TRUE
diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm
index 36708fcf90..ca8e8cecd9 100644
--- a/code/game/mecha/equipment/tools/work_tools.dm
+++ b/code/game/mecha/equipment/tools/work_tools.dm
@@ -262,14 +262,14 @@
occupant_message("Deconstructing [W]...")
if(do_after_cooldown(W))
chassis.spark_system.start()
- W.ScrapeAway()
+ W.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
playsound(W, 'sound/items/deconstruct.ogg', 50, 1)
else if(isfloorturf(target))
var/turf/open/floor/F = target
occupant_message("Deconstructing [F]...")
if(do_after_cooldown(target))
chassis.spark_system.start()
- F.ScrapeAway()
+ F.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
playsound(F, 'sound/items/deconstruct.ogg', 50, 1)
else if (istype(target, /obj/machinery/door/airlock))
occupant_message("Deconstructing [target]...")
@@ -282,7 +282,7 @@
var/turf/open/space/S = target
occupant_message("Building Floor...")
if(do_after_cooldown(S))
- S.PlaceOnTop(/turf/open/floor/plating)
+ S.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
playsound(S, 'sound/items/deconstruct.ogg', 50, 1)
chassis.spark_system.start()
else if(isfloorturf(target))
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index 9c3df5395f..416644cada 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -107,7 +107,7 @@
//Wrapper procs that handle sanity and user feedback
/atom/movable/proc/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
- if(!in_range(user, src) || !isturf(user.loc) || user.incapacitated() || M.anchored)
+ if(!in_range(user, src) || !isturf(user.loc) || user.incapacitated() || M.anchored || !user.can_buckle_others(M, src))
return FALSE
add_fingerprint(user)
diff --git a/code/game/objects/effects/effect_system/effects_foam.dm b/code/game/objects/effects/effect_system/effects_foam.dm
index 10ee73915c..b6deaa1d06 100644
--- a/code/game/objects/effects/effect_system/effects_foam.dm
+++ b/code/game/objects/effects/effect_system/effects_foam.dm
@@ -123,7 +123,7 @@
if(metal)
var/turf/T = get_turf(src)
if(isspaceturf(T)) //Block up any exposed space
- T.PlaceOnTop(/turf/open/floor/plating/foam)
+ T.PlaceOnTop(/turf/open/floor/plating/foam, flags = CHANGETURF_INHERIT_AIR)
for(var/direction in GLOB.cardinals)
var/turf/cardinal_turf = get_step(T, direction)
if(get_area(cardinal_turf) != get_area(T)) //We're at an area boundary, so let's block off this turf!
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 7dc2f1d60b..3ee4a14a91 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -8,7 +8,8 @@
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_BELT
var/mob/living/silicon/pai/pai
- resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
+ resistance_flags = FIRE_PROOF | ACID_PROOF
+ max_integrity = 200
/obj/item/paicard/suicide_act(mob/living/user)
user.visible_message("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!")
@@ -45,6 +46,8 @@
dat += "Radio Uplink
"
dat += "Transmit: [(pai.radio.wires.is_cut(WIRE_TX)) ? "Disabled" : "Enabled"]
"
dat += "Receive: [(pai.radio.wires.is_cut(WIRE_RX)) ? "Disabled" : "Enabled"]
"
+ if(pai.radio_short)
+ dat += "Reset radio short: \[RESET\]
"
else
dat += "Radio Uplink
"
dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
"
@@ -82,7 +85,6 @@
pai.master = M.real_name
pai.master_dna = M.dna.unique_enzymes
to_chat(pai, "You have been bound to a new master.")
- pai.emittersemicd = FALSE
if(href_list["wipe"])
var/confirm = input("Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe") in list("Yes", "No")
if(confirm == "Yes")
@@ -96,6 +98,8 @@
var/wire = text2num(href_list["wires"])
if(pai.radio)
pai.radio.wires.cut(wire)
+ if(href_list["reset_radio_short"])
+ pai.unshort_radio()
if(href_list["setlaws"])
var/newlaws = copytext(sanitize(input("Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.laws.supplied[1]) as message),1,MAX_MESSAGE_LEN)
if(newlaws && pai)
diff --git a/code/game/objects/items/grenades/flashbang.dm b/code/game/objects/items/grenades/flashbang.dm
index 66897fa722..724cfc8f9d 100644
--- a/code/game/objects/items/grenades/flashbang.dm
+++ b/code/game/objects/items/grenades/flashbang.dm
@@ -20,10 +20,10 @@
/obj/item/grenade/flashbang/proc/flashbang_mobs(turf/source, range)
var/list/banged = get_hearers_in_view(range, source)
var/list/flashed = viewers(range, source)
- for(var/i in banged)
- bang(i, source)
- for(var/i in flashed)
- flash(i, source)
+ for(var/mob/living/l in banged)
+ bang(l, source)
+ for(var/mob/living/l in flashed)
+ flash(l, source)
/obj/item/grenade/flashbang/proc/bang(mob/living/M, turf/source)
if(M.stat == DEAD) //They're dead!
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 4fc31ea674..34146d89b3 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -673,7 +673,8 @@ GLOBAL_LIST_INIT(bronze_recipes, list ( \
merge_type = /obj/item/stack/sheet/bone
GLOBAL_LIST_INIT(plastic_recipes, list(
- new /datum/stack_recipe("plastic flaps", /obj/structure/plasticflaps, 5, one_per_turf = TRUE, on_floor = TRUE, time = 40), \
+ new /datum/stack_recipe("see-through plastic flaps", /obj/structure/plasticflaps, 5, one_per_turf = TRUE, on_floor = TRUE, time = 40), \
+ new /datum/stack_recipe("opaque plastic flaps", /obj/structure/plasticflaps/opaque, 5, one_per_turf = TRUE, on_floor = TRUE, time = 40), \
new /datum/stack_recipe("water bottle", /obj/item/reagent_containers/glass/beaker/waterbottle/empty), \
new /datum/stack_recipe("large water bottle", /obj/item/reagent_containers/glass/beaker/waterbottle/large/empty,3), \
new /datum/stack_recipe("large trash cart", /obj/structure/closet/crate/bin,50),\
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index e8bd50f19b..4180681754 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -192,9 +192,15 @@
var/obj/O
if(R.max_res_amount > 1) //Is it a stack?
O = new R.result_type(usr.drop_location(), R.res_amount * multiplier)
+ else if(ispath(R.result_type, /turf))
+ var/turf/T = usr.drop_location()
+ if(!isturf(T))
+ return
+ T.PlaceOnTop(R.result_type, flags = CHANGETURF_INHERIT_AIR)
else
O = new R.result_type(usr.drop_location())
- O.setDir(usr.dir)
+ if(O)
+ O.setDir(usr.dir)
use(R.req_amount * multiplier)
//START: oh fuck i'm so sorry
@@ -439,4 +445,4 @@
/datum/stack_recipe_list/New(title, recipes)
src.title = title
- src.recipes = recipes
\ No newline at end of file
+ src.recipes = recipes
diff --git a/code/game/objects/items/stunbaton.dm b/code/game/objects/items/stunbaton.dm
index b5c8f6b631..6972587263 100644
--- a/code/game/objects/items/stunbaton.dm
+++ b/code/game/objects/items/stunbaton.dm
@@ -36,7 +36,7 @@
. = ..()
if(preload_cell_type)
if(!ispath(preload_cell_type,/obj/item/stock_parts/cell))
- log_world("### MAP WARNING, [src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].")
+ log_mapping("[src] at [AREACOORD(src)] had an invalid preload_cell_type: [preload_cell_type].")
else
cell = new preload_cell_type(src)
update_icon()
diff --git a/code/game/objects/items/twohanded.dm b/code/game/objects/items/twohanded.dm
index dd7d46d0d7..d7891feaa0 100644
--- a/code/game/objects/items/twohanded.dm
+++ b/code/game/objects/items/twohanded.dm
@@ -872,7 +872,7 @@
user.visible_message("[user] blasts \the [target] with \the [src]!")
playsound(target, 'sound/magic/disintegrate.ogg', 100, 1)
W.break_wall()
- W.ScrapeAway()
+ W.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
return
//HF blade
diff --git a/code/game/objects/structures/lattice.dm b/code/game/objects/structures/lattice.dm
index b946a06432..a7eaea0d16 100644
--- a/code/game/objects/structures/lattice.dm
+++ b/code/game/objects/structures/lattice.dm
@@ -60,7 +60,7 @@
to_chat(user, "You build a floor.")
var/turf/T = src.loc
if(isspaceturf(T))
- T.PlaceOnTop(/turf/open/floor/plating)
+ T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
qdel(src)
return TRUE
return FALSE
diff --git a/code/game/objects/structures/stairs.dm b/code/game/objects/structures/stairs.dm
index bd657fe1e8..f2c4e628e0 100644
--- a/code/game/objects/structures/stairs.dm
+++ b/code/game/objects/structures/stairs.dm
@@ -102,13 +102,13 @@
/obj/structure/stairs/proc/force_open_above()
var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP)
if(T && !istype(T))
- T.ChangeTurf(/turf/open/openspace)
+ T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR)
/obj/structure/stairs/proc/on_multiz_new(turf/source, dir)
if(dir == UP)
var/turf/open/openspace/T = get_step_multiz(get_turf(src), UP)
if(T && !istype(T))
- T.ChangeTurf(/turf/open/openspace)
+ T.ChangeTurf(/turf/open/openspace, flags = CHANGETURF_INHERIT_AIR)
/obj/structure/stairs/intercept_zImpact(atom/movable/AM, levels = 1)
. = ..()
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 76c8f7f11f..e2e0f703f7 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -5,6 +5,8 @@
icon_state = "toilet00"
density = FALSE
anchored = TRUE
+ can_buckle = TRUE
+ buckle_lying = 0
var/open = FALSE //if the lid is up
var/cistern = 0 //if the cistern bit is open
var/w_items = 0 //the combined w_class of all the items in the cistern
@@ -682,4 +684,4 @@
else
playsound(loc, 'sound/weapons/tap.ogg', 50, 1)
if(BURN)
- playsound(loc, 'sound/items/welder.ogg', 80, 1)
\ No newline at end of file
+ playsound(loc, 'sound/items/welder.ogg', 80, 1)
diff --git a/code/game/turfs/baseturf_skipover.dm b/code/game/turfs/baseturf_skipover.dm
index 644714f8ed..4df8c86e62 100644
--- a/code/game/turfs/baseturf_skipover.dm
+++ b/code/game/turfs/baseturf_skipover.dm
@@ -10,4 +10,9 @@
/turf/baseturf_skipover/shuttle
name = "Shuttle baseturf skipover"
- desc = "Acts as the bottom of the shuttle, if this isn't here the shuttle floor is broken through."
\ No newline at end of file
+ desc = "Acts as the bottom of the shuttle, if this isn't here the shuttle floor is broken through."
+
+/turf/baseturf_bottom
+ name = "Z-level baseturf placeholder"
+ desc = "Marker for z-level baseturf, usually resolves to space."
+ baseturfs = /turf/baseturf_bottom
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index 6a055bbd35..bc4cd8df33 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -1,6 +1,7 @@
// This is a list of turf types we dont want to assign to baseturfs unless through initialization or explicitly
GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
/turf/open/space,
+ /turf/baseturf_bottom
)))
/turf/proc/empty(turf_type=/turf/open/space, baseturf_type, list/ignore_typecache, flags)
@@ -56,12 +57,20 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
// Creates a new turf
// new_baseturfs can be either a single type or list of types, formated the same as baseturfs. see turf.dm
/turf/proc/ChangeTurf(path, list/new_baseturfs, flags)
- if(!path)
- return
- if(path == /turf/open/space/basic)
- // basic doesn't initialize and this will cause issues
- // no warning though because this can happen naturaly as a result of it being built on top of
- path = /turf/open/space
+ switch(path)
+ if(null)
+ return
+ if(/turf/baseturf_bottom)
+ path = SSmapping.level_trait(z, ZTRAIT_BASETURF) || /turf/open/space
+ if (!ispath(path))
+ path = text2path(path)
+ if (!ispath(path))
+ warning("Z-level [z] has invalid baseturf '[SSmapping.level_trait(z, ZTRAIT_BASETURF)]'")
+ path = /turf/open/space
+ if(/turf/open/space/basic)
+ // basic doesn't initialize and this will cause issues
+ // no warning though because this can happen naturaly as a result of it being built on top of
+ path = /turf/open/space
if(!GLOB.use_preloader && path == type && !(flags & CHANGETURF_FORCEOP)) // Don't no-op if the map loader requires it to be reconstructed
return src
if(flags & CHANGETURF_SKIP)
@@ -128,16 +137,15 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
/turf/open/ChangeTurf(path, list/new_baseturfs, flags)
if ((flags & CHANGETURF_INHERIT_AIR) && ispath(path, /turf/open))
SSair.remove_from_active(src)
- var/stashed_air = air
- air = null // so that it doesn't get deleted
+ var/datum/gas_mixture/stashed_air = new()
+ stashed_air.copy_from(air)
. = ..()
- if (!. || . == src) // changeturf failed or didn't do anything
- air = stashed_air
+ if (!.) // changeturf failed or didn't do anything
+ QDEL_NULL(stashed_air)
return
var/turf/open/newTurf = .
- if (!istype(newTurf.air, /datum/gas_mixture/immutable/space))
- QDEL_NULL(newTurf.air)
- newTurf.air = stashed_air
+ newTurf.air.copy_from(stashed_air)
+ QDEL_NULL(stashed_air)
SSair.add_to_active(newTurf)
else
if(ispath(path,/turf/closed))
@@ -215,7 +223,7 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
newT.assemble_baseturfs(initial(fake_turf_type.baseturfs)) // The baseturfs list is created like roundstart
if(!length(newT.baseturfs))
newT.baseturfs = list(baseturfs)
- newT.baseturfs -= newT.baseturfs & GLOB.blacklisted_automated_baseturfs
+ newT.baseturfs -= GLOB.blacklisted_automated_baseturfs
newT.baseturfs.Insert(1, old_baseturfs) // The old baseturfs are put underneath
return newT
if(!length(baseturfs))
@@ -315,5 +323,5 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
SSair.add_to_active(src)
/turf/proc/ReplaceWithLattice()
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
new /obj/structure/lattice(locate(x, y, z))
diff --git a/code/game/turfs/closed.dm b/code/game/turfs/closed.dm
index 6297cadc63..9e593cafce 100644
--- a/code/game/turfs/closed.dm
+++ b/code/game/turfs/closed.dm
@@ -23,7 +23,7 @@
icon = 'icons/turf/walls.dmi'
explosion_block = 50
-/turf/closed/indestructible/TerraformTurf(path, defer_change = FALSE, ignore_air = FALSE)
+/turf/closed/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE)
return
/turf/closed/indestructible/acid_act(acidpwr, acid_volume, acid_id)
diff --git a/code/game/turfs/open.dm b/code/game/turfs/open.dm
index 4bdb13effc..577c45a7f0 100644
--- a/code/game/turfs/open.dm
+++ b/code/game/turfs/open.dm
@@ -58,7 +58,7 @@
/turf/open/indestructible/singularity_act()
return
-/turf/open/indestructible/TerraformTurf(path, defer_change = FALSE, ignore_air = FALSE)
+/turf/open/indestructible/TerraformTurf(path, new_baseturf, flags, defer_change = FALSE, ignore_air = FALSE)
return
/turf/open/indestructible/sound
diff --git a/code/game/turfs/openspace/openspace.dm b/code/game/turfs/openspace/openspace.dm
index e68aabcd08..66507e7053 100644
--- a/code/game/turfs/openspace/openspace.dm
+++ b/code/game/turfs/openspace/openspace.dm
@@ -106,7 +106,7 @@
qdel(L)
playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
to_chat(user, "You build a floor.")
- PlaceOnTop(/turf/open/floor/plating)
+ PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
to_chat(user, "You need one floor tile to build a floor!")
else
diff --git a/code/game/turfs/simulated/chasm.dm b/code/game/turfs/simulated/chasm.dm
index 0a7b507488..dab0c1c376 100644
--- a/code/game/turfs/simulated/chasm.dm
+++ b/code/game/turfs/simulated/chasm.dm
@@ -38,7 +38,7 @@
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, "You build a floor.")
- PlaceOnTop(/turf/open/floor/plating)
+ PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
@@ -70,7 +70,7 @@
playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
to_chat(user, "You build a floor.")
// Create a floor, which has this chasm underneath it
- PlaceOnTop(/turf/open/floor/plating)
+ PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
to_chat(user, "You need one floor tile to build a floor!")
else
diff --git a/code/game/turfs/simulated/floor.dm b/code/game/turfs/simulated/floor.dm
index d14a4c7385..3f46994da9 100644
--- a/code/game/turfs/simulated/floor.dm
+++ b/code/game/turfs/simulated/floor.dm
@@ -62,29 +62,29 @@
if(severity != 1 && shielded && target != src)
return
if(target == src)
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
return
if(target != null)
severity = 3
switch(severity)
if(1)
- ScrapeAway(2)
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
if(2)
switch(pick(1,2;75,3))
if(1)
if(!length(baseturfs) || !ispath(baseturfs[baseturfs.len-1], /turf/open/floor))
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
ReplaceWithLattice()
else
- ScrapeAway(2)
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
if(prob(33))
new /obj/item/stack/sheet/metal(src)
if(2)
- ScrapeAway(2)
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
if(3)
if(prob(80))
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
else
break_tile()
hotspot_expose(1000,CELL_VOLUME)
@@ -135,7 +135,7 @@
burnt = 1
/turf/open/floor/proc/make_plating()
- return ScrapeAway()
+ return ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
/turf/open/floor/ChangeTurf(path, new_baseturf, flags)
if(!isfloorturf(src))
@@ -213,15 +213,15 @@
/turf/open/floor/narsie_act(force, ignore_mobs, probability = 20)
. = ..()
if(.)
- ChangeTurf(/turf/open/floor/engine/cult)
+ ChangeTurf(/turf/open/floor/engine/cult, flags = CHANGETURF_INHERIT_AIR)
/turf/open/floor/ratvar_act(force, ignore_mobs)
. = ..()
if(.)
- ChangeTurf(/turf/open/floor/clockwork)
+ ChangeTurf(/turf/open/floor/clockwork, flags = CHANGETURF_INHERIT_AIR)
/turf/open/floor/acid_melt()
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
/turf/open/floor/rcd_vals(mob/user, obj/item/construction/rcd/the_rcd)
switch(the_rcd.mode)
@@ -267,7 +267,7 @@
A.autoclose = TRUE
return TRUE
if(RCD_DECONSTRUCT)
- if(ScrapeAway() == src)
+ if(ScrapeAway(flags = CHANGETURF_INHERIT_AIR) == src)
return FALSE
to_chat(user, "You deconstruct [src].")
return TRUE
diff --git a/code/game/turfs/simulated/floor/plating.dm b/code/game/turfs/simulated/floor/plating.dm
index ae67edf073..8e0eace9cd 100644
--- a/code/game/turfs/simulated/floor/plating.dm
+++ b/code/game/turfs/simulated/floor/plating.dm
@@ -11,7 +11,7 @@
name = "plating"
icon_state = "plating"
intact = FALSE
- baseturfs = /turf/open/space
+ baseturfs = /turf/baseturf_bottom
footstep = FOOTSTEP_PLATING
barefootstep = FOOTSTEP_HARD_BAREFOOT
clawfootstep = FOOTSTEP_HARD_CLAW
@@ -61,7 +61,7 @@
to_chat(user, "You begin reinforcing the floor...")
if(do_after(user, 30, target = src))
if (R.get_amount() >= 2 && !istype(src, /turf/open/floor/engine))
- PlaceOnTop(/turf/open/floor/engine)
+ PlaceOnTop(/turf/open/floor/engine, flags = CHANGETURF_INHERIT_AIR)
playsound(src, 'sound/items/deconstruct.ogg', 80, 1)
R.use(2)
to_chat(user, "You reinforce the floor.")
@@ -76,7 +76,7 @@
var/obj/item/stack/tile/W = C
if(!W.use(1))
return
- var/turf/open/floor/T = PlaceOnTop(W.turf_type)
+ var/turf/open/floor/T = PlaceOnTop(W.turf_type, flags = CHANGETURF_INHERIT_AIR)
if(istype(W, /obj/item/stack/tile/light)) //TODO: get rid of this ugly check somehow
var/obj/item/stack/tile/light/L = W
var/turf/open/floor/light/F = T
@@ -117,7 +117,7 @@
qdel(L)
to_chat(user, "You reinforce the foamed plating with tiling.")
playsound(src, 'sound/weapons/Genhit.ogg', 50, TRUE)
- ChangeTurf(/turf/open/floor/plating)
+ ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
playsound(src, 'sound/weapons/tap.ogg', 100, TRUE) //The attack sound is muffled by the foam itself
user.changeNext_move(CLICK_CD_MELEE)
@@ -125,7 +125,7 @@
if(prob(I.force * 20 - 25))
user.visible_message("[user] smashes through [src]!", \
"You smash through [src] with [I]!")
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
else
to_chat(user, "You hit [src], to no effect!")
@@ -136,13 +136,13 @@
/turf/open/floor/plating/foam/rcd_act(mob/user, obj/item/construction/rcd/the_rcd, passed_mode)
if(passed_mode == RCD_FLOORWALL)
to_chat(user, "You build a floor.")
- ChangeTurf(/turf/open/floor/plating)
+ ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
/turf/open/floor/plating/foam/ex_act()
..()
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
/turf/open/floor/plating/foam/tool_act(mob/living/user, obj/item/I, tool_type)
return
diff --git a/code/game/turfs/simulated/floor/plating/asteroid.dm b/code/game/turfs/simulated/floor/plating/asteroid.dm
index 9f2da57312..ecc8ee5e8d 100644
--- a/code/game/turfs/simulated/floor/plating/asteroid.dm
+++ b/code/game/turfs/simulated/floor/plating/asteroid.dm
@@ -76,11 +76,6 @@
for(var/obj/item/stack/ore/O in src)
SEND_SIGNAL(W, COMSIG_PARENT_ATTACKBY, O)
-/turf/open/floor/plating/asteroid/singularity_act()
- if(is_planet_level(z))
- return ..()
- ScrapeAway()
-
/turf/open/floor/plating/asteroid/ex_act(severity, target)
. = SEND_SIGNAL(src, COMSIG_ATOM_EX_ACT, severity, target)
contents_explosion(severity, target)
@@ -132,6 +127,7 @@
/turf/open/floor/plating/asteroid/airless
initial_gas_mix = AIRLESS_ATMOS
+ baseturfs = /turf/open/floor/plating/asteroid/airless
turf_type = /turf/open/floor/plating/asteroid/airless
diff --git a/code/game/turfs/simulated/floor/reinf_floor.dm b/code/game/turfs/simulated/floor/reinf_floor.dm
index b04f89f8be..28ffbbd1ff 100644
--- a/code/game/turfs/simulated/floor/reinf_floor.dm
+++ b/code/game/turfs/simulated/floor/reinf_floor.dm
@@ -43,7 +43,7 @@
return TRUE
if(floor_tile)
new floor_tile(src, 2)
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
return TRUE
/turf/open/floor/engine/acid_act(acidpwr, acid_volume)
@@ -56,23 +56,23 @@
if(severity != 1 && shielded && target != src)
return
if(target == src)
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
return
switch(severity)
if(1)
if(prob(80))
if(!length(baseturfs) || !ispath(baseturfs[baseturfs.len-1], /turf/open/floor))
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
ReplaceWithLattice()
else
- ScrapeAway(2)
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
else if(prob(50))
- ScrapeAway(2)
+ ScrapeAway(2, flags = CHANGETURF_INHERIT_AIR)
else
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
if(2)
if(prob(50))
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
/turf/open/floor/engine/singularity_pull(S, current_size)
..()
diff --git a/code/game/turfs/simulated/lava.dm b/code/game/turfs/simulated/lava.dm
index 362b410cbb..2f8ae0cb93 100644
--- a/code/game/turfs/simulated/lava.dm
+++ b/code/game/turfs/simulated/lava.dm
@@ -54,7 +54,7 @@
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, "You build a floor.")
- PlaceOnTop(/turf/open/floor/plating)
+ PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm
index f38f13e657..5278374abd 100644
--- a/code/game/turfs/space/space.dm
+++ b/code/game/turfs/space/space.dm
@@ -125,7 +125,7 @@
qdel(L)
playsound(src, 'sound/weapons/genhit.ogg', 50, 1)
to_chat(user, "You build a floor.")
- PlaceOnTop(/turf/open/floor/plating)
+ PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
to_chat(user, "You need one floor tile to build a floor!")
else
@@ -212,7 +212,7 @@
switch(passed_mode)
if(RCD_FLOORWALL)
to_chat(user, "You build a floor.")
- PlaceOnTop(/turf/open/floor/plating)
+ PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
return TRUE
return FALSE
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index d618e457b0..d280ba408b 100755
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -9,7 +9,7 @@
// A list will be created in initialization that figures out the baseturf's baseturf etc.
// In the case of a list it is sorted from bottom layer to top.
// This shouldn't be modified directly, use the helper procs.
- var/list/baseturfs = /turf/open/space
+ var/list/baseturfs = /turf/baseturf_bottom
var/temperature = T20C
var/to_be_destroyed = 0 //Used for fire, if a melting temperature was reached, it will be destroyed
@@ -391,7 +391,7 @@
continue
if(O.invisibility == INVISIBILITY_MAXIMUM)
O.singularity_act()
- ScrapeAway()
+ ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
return(2)
/turf/proc/can_have_cabling()
@@ -564,4 +564,4 @@
//Whatever happens after high temperature fire dies out or thermite reaction works.
//Should return new turf
/turf/proc/Melt()
- return ScrapeAway()
+ return ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
diff --git a/code/game/world.dm b/code/game/world.dm
index 25b8c4d9f3..4043f15f6f 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -108,6 +108,7 @@ GLOBAL_VAR(restart_counter)
GLOB.world_href_log = "[GLOB.log_directory]/hrefs.log"
GLOB.sql_error_log = "[GLOB.log_directory]/sql.log"
GLOB.world_qdel_log = "[GLOB.log_directory]/qdel.log"
+ GLOB.world_map_error_log = "[GLOB.log_directory]/map_errors.log"
GLOB.world_runtime_log = "[GLOB.log_directory]/runtime.log"
GLOB.query_debug_log = "[GLOB.log_directory]/query_debug.log"
GLOB.world_job_debug_log = "[GLOB.log_directory]/job_debug.log"
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index 895f8c9f88..1cecbbd0e5 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -693,7 +693,7 @@
var/prev_dynamic_voting = CONFIG_GET(flag/dynamic_voting)
CONFIG_SET(flag/dynamic_voting,!prev_dynamic_voting)
if (!prev_dynamic_voting)
- to_chat(world, "Vote is now between extended and dynamic chaos.")
+ to_chat(world, "Vote is now a ranked choice of dynamic storytellers.")
else
to_chat(world, "Vote is now between extended and secret.")
log_admin("[key_name(usr)] [prev_dynamic_voting ? "disabled" : "enabled"] dynamic voting.")
diff --git a/code/modules/admin/verbs/mapping.dm b/code/modules/admin/verbs/mapping.dm
index 2b8d365e15..acd320856a 100644
--- a/code/modules/admin/verbs/mapping.dm
+++ b/code/modules/admin/verbs/mapping.dm
@@ -35,6 +35,9 @@ GLOBAL_LIST_INIT(admin_verbs_debug_mapping, list(
/client/proc/cmd_admin_grantfullaccess,
/client/proc/cmd_admin_areatest_all,
/client/proc/cmd_admin_areatest_station,
+ #ifdef TESTING
+ /client/proc/see_dirty_varedits,
+ #endif
/client/proc/cmd_admin_test_atmos_controllers,
/client/proc/cmd_admin_rejuvenate,
/datum/admins/proc/show_traitor_panel,
@@ -84,8 +87,24 @@ GLOBAL_PROTECT(admin_verbs_debug_mapping)
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
SSblackbox.record_feedback("tally", "admin_verb", 1, "Show Camera Range")
+#ifdef TESTING
+GLOBAL_LIST_EMPTY(dirty_vars)
+/client/proc/see_dirty_varedits()
+ set category = "Mapping"
+ set name = "Dirty Varedits"
+
+ var/list/dat = list()
+ dat += "Abandon all hope ye who enter here
"
+ for(var/thing in GLOB.dirty_vars)
+ dat += "[thing]
"
+ CHECK_TICK
+ var/datum/browser/popup = new(usr, "dirty_vars", "Dirty Varedits", 900, 750)
+ popup.set_content(dat.Join())
+ popup.open()
+#endif
+
/client/proc/sec_camera_report()
set category = "Mapping"
set name = "Camera Report"
diff --git a/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm b/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm
index 0365ae63af..501bcdc1c4 100644
--- a/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm
+++ b/code/modules/antagonists/clockcult/clock_items/replica_fabricator.dm
@@ -139,7 +139,7 @@
var/new_thing_type = fabrication_values["new_obj_type"]
if(isturf(target)) //if our target is a turf, we're just going to ChangeTurf it and assume it'll work out.
var/turf/T = target
- T.ChangeTurf(new_thing_type)
+ T.ChangeTurf(new_thing_type, flags = CHANGETURF_INHERIT_AIR)
else
if(new_thing_type)
if(fabrication_values["dir_in_new"])
diff --git a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
index 32b1b61dd1..d823f19d4a 100644
--- a/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
+++ b/code/modules/antagonists/clockcult/clock_structures/wall_gear.dm
@@ -59,7 +59,7 @@
if(anchored)
T.PlaceOnTop(/turf/closed/wall/clockwork)
else
- T.PlaceOnTop(/turf/open/floor/clockwork)
+ T.PlaceOnTop(/turf/open/floor/clockwork, flags = CHANGETURF_INHERIT_AIR)
new /obj/structure/falsewall/brass(T)
qdel(src)
else
diff --git a/code/modules/antagonists/cult/cult_structures.dm b/code/modules/antagonists/cult/cult_structures.dm
index f38f379abe..6f340b9271 100644
--- a/code/modules/antagonists/cult/cult_structures.dm
+++ b/code/modules/antagonists/cult/cult_structures.dm
@@ -239,9 +239,9 @@
var/turf/T = safepick(validturfs)
if(T)
if(istype(T, /turf/open/floor/plating))
- T.PlaceOnTop(/turf/open/floor/engine/cult)
+ T.PlaceOnTop(/turf/open/floor/engine/cult, flags = CHANGETURF_INHERIT_AIR)
else
- T.ChangeTurf(/turf/open/floor/engine/cult)
+ T.ChangeTurf(/turf/open/floor/engine/cult, flags = CHANGETURF_INHERIT_AIR)
else
var/turf/open/floor/engine/cult/F = safepick(cultturfs)
if(F)
diff --git a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
index 0a223f8b10..b5ce5538e9 100644
--- a/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
+++ b/code/modules/antagonists/nukeop/equipment/nuclear_challenge.dm
@@ -76,9 +76,10 @@ GLOBAL_VAR_INIT(war_declared, FALSE)
CONFIG_SET(number/shuttle_refuel_delay, max(CONFIG_GET(number/shuttle_refuel_delay), CHALLENGE_SHUTTLE_DELAY))
if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode
- var/threat_spent = CONFIG_GET(number/dynamic_warops_cost)
- mode.spend_threat(threat_spent)
- mode.log_threat("Nuke ops spent [threat_spent] on war ops.")
+ if(!(mode.storyteller.flags & WAROPS_ALWAYS_ALLOWED))
+ var/threat_spent = CONFIG_GET(number/dynamic_warops_cost)
+ mode.spend_threat(threat_spent)
+ mode.log_threat("Nuke ops spent [threat_spent] on war ops.")
SSblackbox.record_feedback("amount", "nuclear_challenge_mode", 1)
qdel(src)
@@ -101,12 +102,13 @@ GLOBAL_VAR_INIT(war_declared, FALSE)
return FALSE
if(istype(SSticker.mode, /datum/game_mode/dynamic))
var/datum/game_mode/dynamic/mode = SSticker.mode
- if(mode.threat_level < CONFIG_GET(number/dynamic_warops_requirement))
- to_chat(user, "Due to the dynamic space in which the station resides, you are too deep into Nanotrasen territory to reasonably go loud.")
- return FALSE
- else if(mode.threat < CONFIG_GET(number/dynamic_warops_cost))
- to_chat(user, "Due to recent threats on the station, Nanotrasen is looking too closely for a war declaration to be wise.")
- return FALSE
+ if(!(mode.storyteller.flags & WAROPS_ALWAYS_ALLOWED))
+ if(mode.threat_level < CONFIG_GET(number/dynamic_warops_requirement))
+ to_chat(user, "Due to the dynamic space in which the station resides, you are too deep into Nanotrasen territory to reasonably go loud.")
+ return FALSE
+ else if(mode.threat < CONFIG_GET(number/dynamic_warops_cost))
+ to_chat(user, "Due to recent threats on the station, Nanotrasen is looking too closely for a war declaration to be wise.")
+ return FALSE
return TRUE
/obj/item/nuclear_challenge/clownops
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index ac19df3631..40d1712bc3 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -80,6 +80,8 @@
if(istype(SSticker.mode,/datum/game_mode/dynamic))
mode = SSticker.mode
is_dynamic = TRUE
+ if(mode.storyteller.flags & NO_ASSASSIN)
+ is_hijacker = FALSE
if(GLOB.joined_player_list.len>=GLOB.dynamic_high_pop_limit)
is_hijacker = (prob(10) && mode.threat_level > CONFIG_GET(number/dynamic_hijack_high_population_requirement))
else
@@ -180,7 +182,7 @@
destroy_objective.owner = owner
destroy_objective.find_target()
add_objective(destroy_objective)
- else if(prob(30))
+ else if(prob(30) || (mode.storyteller.flags & NO_ASSASSIN))
var/datum/objective/maroon/maroon_objective = new
maroon_objective.owner = owner
maroon_objective.find_target()
diff --git a/code/modules/atmospherics/machinery/datum_pipeline.dm b/code/modules/atmospherics/machinery/datum_pipeline.dm
index 38178a4339..db8a8e90ad 100644
--- a/code/modules/atmospherics/machinery/datum_pipeline.dm
+++ b/code/modules/atmospherics/machinery/datum_pipeline.dm
@@ -57,10 +57,10 @@
if(item.parent)
var/static/pipenetwarnings = 10
if(pipenetwarnings > 0)
- warning("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z])")
+ log_mapping("build_pipeline(): [item.type] added to a pipenet while still having one. (pipes leading to the same spot stacking in one turf) Nearby: ([item.x], [item.y], [item.z]).")
pipenetwarnings -= 1
if(pipenetwarnings == 0)
- warning("build_pipeline(): further messages about pipenets will be suppressed")
+ log_mapping("build_pipeline(): further messages about pipenets will be suppressed")
members += item
possible_expansions += item
diff --git a/code/modules/atmospherics/machinery/other/meter.dm b/code/modules/atmospherics/machinery/other/meter.dm
index d0c78d21b3..329c77ba14 100644
--- a/code/modules/atmospherics/machinery/other/meter.dm
+++ b/code/modules/atmospherics/machinery/other/meter.dm
@@ -56,7 +56,7 @@
pixel_y = (new_layer - PIPING_LAYER_DEFAULT) * PIPING_LAYER_P_Y
/obj/machinery/meter/process_atmos()
- if(!target)
+ if(!(target?.flags_1 & INITIALIZED_1))
icon_state = "meterX"
return 0
diff --git a/code/modules/buildmode/submodes/basic.dm b/code/modules/buildmode/submodes/basic.dm
index 4f7b176691..fe3d831d81 100644
--- a/code/modules/buildmode/submodes/basic.dm
+++ b/code/modules/buildmode/submodes/basic.dm
@@ -22,9 +22,9 @@
if(istype(object,/turf) && left_click && !alt_click && !ctrl_click)
var/turf/T = object
if(isspaceturf(object))
- T.PlaceOnTop(/turf/open/floor/plating)
+ T.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else if(isplatingturf(object))
- T.PlaceOnTop(/turf/open/floor/plasteel)
+ T.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
else if(isfloorturf(object))
T.PlaceOnTop(/turf/closed/wall)
else if(iswallturf(object))
@@ -35,7 +35,7 @@
log_admin("Build Mode: [key_name(c)] deleted [object] at [AREACOORD(object)]")
if(isturf(object))
var/turf/T = object
- T.ScrapeAway()
+ T.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
else if(isobj(object))
qdel(object)
return
diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm
index 96a32d50e6..7b7cf3413e 100644
--- a/code/modules/client/preferences.dm
+++ b/code/modules/client/preferences.dm
@@ -21,6 +21,10 @@ GLOBAL_LIST_EMPTY(preferences_datums)
var/last_ip
var/last_id
+ var/icon/custom_holoform_icon
+ var/list/cached_holoform_icons
+ var/last_custom_holoform = 0
+
//Cooldowns for saving/loading. These are four are all separate due to loading code calling these one after another
var/saveprefcooldown
var/loadprefcooldown
@@ -2422,3 +2426,11 @@ GLOBAL_LIST_EMPTY(preferences_datums)
return
else
custom_names[name_id] = sanitized_name
+
+/datum/preferences/proc/get_filtered_holoform(filter_type)
+ if(!custom_holoform_icon)
+ return
+ LAZYINITLIST(cached_holoform_icons)
+ if(!cached_holoform_icons[filter_type])
+ cached_holoform_icons[filter_type] = process_holoform_icon_filter(custom_holoform_icon, filter_type)
+ return cached_holoform_icons[filter_type]
diff --git a/code/modules/clothing/clothing.dm b/code/modules/clothing/clothing.dm
index d9e967e54d..6fd1584e75 100644
--- a/code/modules/clothing/clothing.dm
+++ b/code/modules/clothing/clothing.dm
@@ -57,7 +57,6 @@
//No idea what this is but eh -tori
var/force_alternate_icon = FALSE
-
/obj/item/clothing/Initialize()
. = ..()
if(CHECK_BITFIELD(clothing_flags, VOICEBOX_TOGGLABLE))
diff --git a/code/modules/holodeck/computer.dm b/code/modules/holodeck/computer.dm
index 889706744a..d551222836 100644
--- a/code/modules/holodeck/computer.dm
+++ b/code/modules/holodeck/computer.dm
@@ -59,9 +59,7 @@
return
var/area/AS = get_area(src)
if(istype(AS, /area/holodeck))
- log_world("### MAPPING ERROR")
- log_world("Holodeck computer cannot be in a holodeck.")
- log_world("This would cause circular power dependency.")
+ log_mapping("Holodeck computer cannot be in a holodeck, This would cause circular power dependency.")
qdel(src)
return
else
diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm
index 1f55f18823..3e361179de 100644
--- a/code/modules/mapping/map_template.dm
+++ b/code/modules/mapping/map_template.dm
@@ -70,7 +70,7 @@
//initialize things that are normally initialized after map load
parsed.initTemplateBounds()
smooth_zlevel(world.maxz)
- log_game("Z-level [name] loaded at at [x],[y],[world.maxz]")
+ log_game("Z-level [name] loaded at [x],[y],[world.maxz]")
return level
@@ -84,6 +84,13 @@
if(T.y+height > world.maxy)
return
+ var/list/border = block(locate(max(T.x-1, 1), max(T.y-1, 1), T.z),
+ locate(min(T.x+width+1, world.maxx), min(T.y+height+1, world.maxy), T.z))
+ for(var/L in border)
+ var/turf/turf_to_disable = L
+ SSair.remove_from_active(turf_to_disable) //stop processing turfs along the border to prevent runtimes, we return it in initTemplateBounds()
+ turf_to_disable.atmos_adjacent_turfs?.Cut()
+
// Accept cached maps, but don't save them automatically - we don't want
// ruins clogging up memory for the whole round.
var/datum/parsed_map/parsed = cached_map || new(file(mappath))
@@ -100,7 +107,7 @@
//initialize things that are normally initialized after map load
parsed.initTemplateBounds()
- log_game("[name] loaded at at [T.x],[T.y],[T.z]")
+ log_game("[name] loaded at [T.x],[T.y],[T.z]")
return bounds
/datum/map_template/proc/get_affected_turfs(turf/T, centered = FALSE)
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index e2459d780a..a7f84fd71f 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -18,7 +18,7 @@
/obj/effect/baseturf_helper/LateInitialize()
if(!baseturf_to_replace)
- baseturf_to_replace = typecacheof(/turf/open/space)
+ baseturf_to_replace = typecacheof(list(/turf/open/space,/turf/baseturf_bottom))
else if(!length(baseturf_to_replace))
baseturf_to_replace = list(baseturf_to_replace = TRUE)
else if(baseturf_to_replace[baseturf_to_replace[1]] != TRUE) // It's not associative
@@ -45,7 +45,6 @@
thing.PlaceOnBottom(null, baseturf)
else if(baseturf_to_replace[thing.baseturfs])
thing.assemble_baseturfs(baseturf)
- return
else
thing.PlaceOnBottom(null, baseturf)
@@ -107,16 +106,16 @@
/obj/effect/mapping_helpers/airlock/cyclelink_helper/Initialize(mapload)
. = ..()
if(!mapload)
- log_world("### MAP WARNING, [src] spawned outside of mapload!")
+ log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
if(airlock.cyclelinkeddir)
- log_world("### MAP WARNING, [src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
+ log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!")
else
airlock.cyclelinkeddir = dir
else
- log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]")
+ log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
/obj/effect/mapping_helpers/airlock/locked
@@ -126,16 +125,16 @@
/obj/effect/mapping_helpers/airlock/locked/Initialize(mapload)
. = ..()
if(!mapload)
- log_world("### MAP WARNING, [src] spawned outside of mapload!")
+ log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
if(airlock.locked)
- log_world("### MAP WARNING, [src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
+ log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!")
else
airlock.locked = TRUE
else
- log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]")
+ log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
/obj/effect/mapping_helpers/airlock/unres
name = "airlock unresctricted side helper"
@@ -144,13 +143,13 @@
/obj/effect/mapping_helpers/airlock/unres/Initialize(mapload)
. = ..()
if(!mapload)
- log_world("### MAP WARNING, [src] spawned outside of mapload!")
+ log_mapping("[src] spawned outside of mapload!")
return
var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc
if(airlock)
airlock.unres_sides ^= dir
else
- log_world("### MAP WARNING, [src] failed to find an airlock at [AREACOORD(src)]")
+ log_mapping("[src] failed to find an airlock at [AREACOORD(src)]")
//needs to do its thing before spawn_rivers() is called
@@ -164,17 +163,6 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
var/turf/T = get_turf(src)
T.flags_1 |= NO_LAVA_GEN_1
-/// Adds the map it is on to the z_is_planet list
-/obj/effect/mapping_helpers/planet_z
- name = "planet z helper"
- layer = POINT_LAYER
-
-/obj/effect/mapping_helpers/planet_z/Initialize()
- . = ..()
- var/datum/space_level/S = SSmapping.get_level(z)
- S.traits[ZTRAIT_PLANET] = TRUE
-
-
//This helper applies components to things on the map directly.
/obj/effect/mapping_helpers/component_injector
name = "Component Injector"
diff --git a/code/modules/mapping/preloader.dm b/code/modules/mapping/preloader.dm
index e6fa2421a0..4b61663f66 100644
--- a/code/modules/mapping/preloader.dm
+++ b/code/modules/mapping/preloader.dm
@@ -8,18 +8,26 @@ GLOBAL_DATUM_INIT(_preloader, /datum/map_preloader, new)
var/list/attributes
var/target_path
-/datum/map_preloader/proc/setup(list/the_attributes, path)
+/world/proc/preloader_setup(list/the_attributes, path)
if(the_attributes.len)
GLOB.use_preloader = TRUE
- attributes = the_attributes
- target_path = path
+ var/datum/map_preloader/preloader_local = GLOB._preloader
+ preloader_local.attributes = the_attributes
+ preloader_local.target_path = path
-/datum/map_preloader/proc/load(atom/what)
+/world/proc/preloader_load(atom/what)
GLOB.use_preloader = FALSE
- for(var/attribute in attributes)
- var/value = attributes[attribute]
+ var/datum/map_preloader/preloader_local = GLOB._preloader
+ for(var/attribute in preloader_local.attributes)
+ var/value = preloader_local.attributes[attribute]
if(islist(value))
value = deepCopyList(value)
+ #ifdef TESTING
+ if(what.vars[attribute] == value)
+ var/message = "[what.type] at [AREACOORD(what)] - VAR: [attribute] = [isnull(value) ? "null" : (isnum(value) ? value : "\"[value]\"")]"
+ log_mapping("DIRTY VAR: [message]")
+ GLOB.dirty_vars += message
+ #endif
what.vars[attribute] = value
/area/template_noop
diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm
index 22be4aa246..9b27691dd3 100644
--- a/code/modules/mapping/reader.dm
+++ b/code/modules/mapping/reader.dm
@@ -306,8 +306,8 @@
//first instance the /area and remove it from the members list
index = members.len
if(members[index] != /area/template_noop)
- GLOB._preloader.setup(members_attributes[index])//preloader for assigning set variables on atom creation
var/atype = members[index]
+ world.preloader_setup(members_attributes[index], atype)//preloader for assigning set variables on atom creation
var/atom/instance = areaCache[atype]
if (!instance)
instance = GLOB.areas_by_type[atype]
@@ -318,7 +318,7 @@
instance.contents.Add(crds)
if(GLOB.use_preloader && instance)
- GLOB._preloader.load(instance)
+ world.preloader_load(instance)
//then instance the /turf and, if multiple tiles are presents, simulates the DMM underlays piling effect
@@ -354,7 +354,7 @@
//Instance an atom at (x,y,z) and gives it the variables in attributes
/datum/parsed_map/proc/instance_atom(path,list/attributes, turf/crds, no_changeturf, placeOnTop)
- GLOB._preloader.setup(attributes, path)
+ world.preloader_setup(attributes, path)
if(crds)
if(ispath(path, /turf))
@@ -368,7 +368,7 @@
. = create_atom(path, crds)//first preloader pass
if(GLOB.use_preloader && .)//second preloader pass, for those atoms that don't ..() in New()
- GLOB._preloader.load(.)
+ world.preloader_load(.)
//custom CHECK_TICK here because we don't want things created while we're sleeping to not initialize
if(TICK_CHECK)
diff --git a/code/modules/mining/lavaland/necropolis_chests.dm b/code/modules/mining/lavaland/necropolis_chests.dm
index ab3b20ac9f..7bef9b652a 100644
--- a/code/modules/mining/lavaland/necropolis_chests.dm
+++ b/code/modules/mining/lavaland/necropolis_chests.dm
@@ -926,7 +926,7 @@
timer = world.time + create_delay + 1
if(do_after(user, create_delay, target = T))
var/old_name = T.name
- if(T.TerraformTurf(turf_type))
+ if(T.TerraformTurf(turf_type, flags = CHANGETURF_INHERIT_AIR))
user.visible_message("[user] turns \the [old_name] into [transform_string]!")
message_admins("[ADMIN_LOOKUPFLW(user)] fired the lava staff at [ADMIN_VERBOSEJMP(T)]")
log_game("[key_name(user)] fired the lava staff at [AREACOORD(T)].")
@@ -937,7 +937,7 @@
qdel(L)
else
var/old_name = T.name
- if(T.TerraformTurf(reset_turf_type))
+ if(T.TerraformTurf(reset_turf_type, flags = CHANGETURF_INHERIT_AIR))
user.visible_message("[user] turns \the [old_name] into [reset_string]!")
timer = world.time + reset_cooldown
playsound(T,'sound/magic/fireball.ogg', 200, 1)
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index 7aae26526e..5b664dc01a 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -27,12 +27,7 @@
/datum/preferences/proc/update_preview_icon(equip_job = TRUE)
// Determine what job is marked as 'High' priority, and dress them up as such.
- var/datum/job/previewJob
- var/highest_pref = 0
- for(var/job in job_preferences)
- if(job_preferences["[job]"] > highest_pref)
- previewJob = SSjob.GetJob(job)
- highest_pref = job_preferences["[job]"]
+ var/datum/job/previewJob = get_highest_job()
if(previewJob)
// Silicons only need a very basic preview since there is no customization for them.
@@ -57,3 +52,11 @@
parent.show_character_previews(new /mutable_appearance(mannequin))
unset_busy_human_dummy(DUMMY_HUMAN_SLOT_PREFERENCES)
+/datum/preferences/proc/get_highest_job()
+ var/highest_pref = 0
+ var/datum/job/highest_job
+ for(var/job in job_preferences)
+ if(job_preferences["[job]"] > highest_pref)
+ highest_job = SSjob.GetJob(job)
+ highest_pref = job_preferences["[job]"]
+ return highest_job
diff --git a/code/modules/mob/living/carbon/alien/larva/larva.dm b/code/modules/mob/living/carbon/alien/larva/larva.dm
index e1e079cbae..8e1e5aab8d 100644
--- a/code/modules/mob/living/carbon/alien/larva/larva.dm
+++ b/code/modules/mob/living/carbon/alien/larva/larva.dm
@@ -14,10 +14,9 @@
var/max_grown = 100
var/time_of_birth
- rotate_on_lying = 0
+ rotate_on_lying = FALSE
bodyparts = list(/obj/item/bodypart/chest/larva, /obj/item/bodypart/head/larva)
-
//This is fine right now, if we're adding organ specific damage this needs to be updated
/mob/living/carbon/alien/larva/Initialize()
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index a1184613fe..9629dc3484 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -40,7 +40,7 @@
var/gib_type = /obj/effect/decal/cleanable/blood/gibs
- var/rotate_on_lying = 1
+ rotate_on_lying = TRUE
var/tinttotal = 0 // Total level of visualy impairing items
diff --git a/code/modules/mob/living/carbon/emote.dm b/code/modules/mob/living/carbon/emote.dm
index 345279df29..559c2ebcdd 100644
--- a/code/modules/mob/living/carbon/emote.dm
+++ b/code/modules/mob/living/carbon/emote.dm
@@ -22,6 +22,7 @@
muzzle_ignore = TRUE
restraint_check = TRUE
emote_type = EMOTE_AUDIBLE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/carbon/clap/run_emote(mob/living/user, params)
. = ..()
diff --git a/code/modules/mob/living/carbon/update_icons.dm b/code/modules/mob/living/carbon/update_icons.dm
index 66be8058f1..0155f2575d 100644
--- a/code/modules/mob/living/carbon/update_icons.dm
+++ b/code/modules/mob/living/carbon/update_icons.dm
@@ -1,31 +1,3 @@
-//IMPORTANT: Multiple animate() calls do not stack well, so try to do them all at once if you can.
-/mob/living/carbon/update_transform()
- var/matrix/ntransform = matrix(transform) //aka transform.Copy()
- var/final_pixel_y = pixel_y
- var/final_dir = dir
- var/changed = 0
- if(lying != lying_prev && rotate_on_lying)
- changed++
- ntransform.TurnTo(lying_prev,lying)
- if(lying == 0) //Lying to standing
- final_pixel_y = get_standard_pixel_y_offset()
- else //if(lying != 0)
- if(lying_prev == 0) //Standing to lying
- pixel_y = get_standard_pixel_y_offset()
- final_pixel_y = get_standard_pixel_y_offset(lying)
- if(dir & (EAST|WEST)) //Facing east or west
- final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass
-
- if(resize != RESIZE_DEFAULT_SIZE)
- changed++
- ntransform.Scale(resize)
- resize = RESIZE_DEFAULT_SIZE
-
- if(changed)
- animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT)
- setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life().
-
-
/mob/living/carbon
var/list/overlays_standing[TOTAL_LAYERS]
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 4d2a36907d..783e044492 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -111,4 +111,6 @@
var/list/diseases = list() // list of all diseases in a mob
var/list/disease_resistances = list()
- var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob
\ No newline at end of file
+ var/drag_slowdown = TRUE //Whether the mob is slowed down when dragging another prone mob
+
+ var/rotate_on_lying = FALSE
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 0444458841..1694d04b82 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -260,15 +260,6 @@
viewalerts = 1
src << browse(dat, "window=aialerts&can_close=0")
-/mob/living/silicon/ai/proc/ai_roster()
- var/dat = "Crew RosterCrew Roster:
"
-
- dat += GLOB.data_core.get_manifest()
- dat += ""
-
- src << browse(dat, "window=airoster")
- onclose(src, "airoster")
-
/mob/living/silicon/ai/proc/ai_call_shuttle()
if(control_disabled)
to_chat(usr, "Wireless control is disabled!")
@@ -681,18 +672,23 @@
"green face" = 'icons/mob/ai.dmi',
"xeno queen" = 'icons/mob/alien.dmi',
"horror" = 'icons/mob/ai.dmi',
- "creature" = 'icons/mob/ai.dmi'
+ "creature" = 'icons/mob/ai.dmi',
+ "custom"
)
input = input("Please select a hologram:") as null|anything in icon_list
if(input)
qdel(holo_icon)
switch(input)
- if("xeno queen")
+ if("custom")
+ if(client?.prefs?.custom_holoform_icon)
+ holo_icon = client.prefs.get_filtered_holoform(HOLOFORM_FILTER_AI)
+ else
+ holo_icon = getHologramIcon(icon('icons/mob/ai.dmi', "female"))
+ else if("xeno queen")
holo_icon = getHologramIcon(icon(icon_list[input],"alienq"))
else
holo_icon = getHologramIcon(icon(icon_list[input], input))
- return
/mob/living/silicon/ai/proc/corereturn()
set category = "Malfunction"
diff --git a/code/modules/mob/living/silicon/ai/freelook/eye.dm b/code/modules/mob/living/silicon/ai/freelook/eye.dm
index 7477ba7713..38afaf8313 100644
--- a/code/modules/mob/living/silicon/ai/freelook/eye.dm
+++ b/code/modules/mob/living/silicon/ai/freelook/eye.dm
@@ -68,7 +68,7 @@
// Use this when setting the aiEye's location.
// It will also stream the chunk that the new loc is in.
-/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE)
+/mob/camera/aiEye/proc/setLoc(T, force_update = FALSE, dir)
if(ai)
if(!isturf(ai.loc))
return
@@ -88,7 +88,7 @@
//Holopad
if(istype(ai.current, /obj/machinery/holopad))
var/obj/machinery/holopad/H = ai.current
- H.move_hologram(ai, T)
+ H.move_hologram(ai, T, dir)
if(ai.camera_light_on)
ai.light_cameras()
if(ai.master_multicam)
@@ -139,7 +139,7 @@
for(var/i = 0; i < max(user.sprint, initial); i += 20)
var/turf/step = get_turf(get_step(user.eyeobj, direct))
if(step)
- user.eyeobj.setLoc(step)
+ user.eyeobj.setLoc(step, null, direct)
user.cooldown = world.timeofday + 5
if(user.acceleration)
diff --git a/code/modules/mob/living/silicon/custom_holoform.dm b/code/modules/mob/living/silicon/custom_holoform.dm
new file mode 100644
index 0000000000..8ce6e9a29a
--- /dev/null
+++ b/code/modules/mob/living/silicon/custom_holoform.dm
@@ -0,0 +1,28 @@
+/mob/living/silicon/verb/clear_custom_holoform()
+ set name = "Clear Custom Holoform"
+ set desc = "Clear your current custom holoform"
+ set category = "OOC"
+
+ if(!client.prefs)
+ to_chat(src, "No preferences datum on your client, contact an admin/coder!")
+ return
+ client.prefs.custom_holoform_icon = null
+ client.prefs.cached_holoform_icons = null
+ to_chat(src, "Holoform removed.")
+
+/mob/living/silicon/verb/set_custom_holoform()
+ set name = "Set Custom Holoform"
+ set desc = "Set your custom holoform using your current preferences slot and a specified set of gear."
+ set category = "OOC"
+
+ if(!client.prefs)
+ to_chat(src, "No preferences datum on your client, contact an admin/coder!")
+ return
+ if(client.prefs.last_custom_holoform > world.time - CUSTOM_HOLOFORM_DELAY)
+ to_chat(src, "You are attempting to change custom holoforms too fast!")
+ var/icon/new_holoform = user_interface_custom_holoform(client)
+ if(new_holoform)
+ client.prefs.custom_holoform_icon = new_holoform
+ client.prefs.cached_holoform_icons = null
+ client.prefs.last_custom_holoform = world.time
+ to_chat(src, "Holoform set.")
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 7ec63cf415..7055838039 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -58,10 +58,12 @@
var/canholo = TRUE
var/obj/item/card/id/access_card = null
var/chassis = "repairbot"
- var/list/possible_chassis = list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE,
- "fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "borgi" = FALSE ,
- "parrot" = FALSE, "bear" = FALSE , "mushroom" = FALSE, "crow" = FALSE ,
- "fairy" = FALSE , "spiderbot" = FALSE) //assoc value is whether it can be picked up.
+ var/dynamic_chassis
+ var/dynamic_chassis_sit = FALSE //whether we're sitting instead of resting spritewise
+ var/dynamic_chassis_bellyup = FALSE //whether we're lying down bellyup
+ var/list/possible_chassis //initialized in initialize.
+ var/list/dynamic_chassis_icons //ditto.
+ var/list/chassis_pixel_offsets_x //stupid dogborgs
var/static/item_head_icon = 'icons/mob/pai_item_head.dmi'
var/static/item_lh_icon = 'icons/mob/pai_item_lh.dmi'
var/static/item_rh_icon = 'icons/mob/pai_item_rh.dmi'
@@ -69,22 +71,20 @@
var/emitterhealth = 20
var/emittermaxhealth = 20
var/emitterregen = 0.25
+ var/emitter_next_use = 0
+ var/emitter_emp_cd = 300
var/emittercd = 50
var/emitteroverloadcd = 100
- var/emittersemicd = FALSE
- var/overload_ventcrawl = 0
- var/overload_bulletblock = 0 //Why is this a good idea?
- var/overload_maxhealth = 0
+ var/radio_short = FALSE
+ var/radio_short_cooldown = 5 MINUTES
+ var/radio_short_timerid
+
canmove = FALSE
var/silent = FALSE
var/brightness_power = 5
-/mob/living/silicon/pai/can_unbuckle()
- return FALSE
-
-/mob/living/silicon/pai/can_buckle()
- return FALSE
+ var/icon/custom_holoform_icon
/mob/living/silicon/pai/Destroy()
if (loc != card)
@@ -118,6 +118,13 @@
pda.owner = text("[]", src)
pda.name = pda.owner + " (" + pda.ownjob + ")"
+ possible_chassis = typelist(NAMEOF(src, possible_chassis), list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE,
+ "fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "borgi" = FALSE ,
+ "parrot" = FALSE, "bear" = FALSE , "mushroom" = FALSE, "crow" = FALSE ,
+ "fairy" = FALSE , "spiderbot" = FALSE)) //assoc value is whether it can be picked up.
+ dynamic_chassis_icons = typelist(NAMEOF(src, dynamic_chassis_icons), initialize_dynamic_chassis_icons())
+ chassis_pixel_offsets_x = typelist(NAMEOF(src, chassis_pixel_offsets_x), default_chassis_pixel_offsets_x())
+
. = ..()
var/datum/action/innate/pai/software/SW = new
@@ -133,8 +140,7 @@
AR.Grant(src)
AL.Grant(src)
ALM.Grant(src)
- emittersemicd = TRUE
- addtimer(CALLBACK(src, .proc/emittercool), 600)
+ emitter_next_use = world.time + 10 SECONDS
/mob/living/silicon/pai/Life()
if(hacking)
@@ -228,7 +234,7 @@
/datum/action/innate/pai/shell/Trigger()
..()
if(P.holoform)
- P.fold_in(0)
+ P.fold_in(FALSE)
else
P.fold_out()
@@ -292,3 +298,132 @@
/mob/living/silicon/pai/process()
emitterhealth = CLAMP((emitterhealth + emitterregen), -50, emittermaxhealth)
+
+/mob/living/silicon/pai/proc/short_radio()
+ if(radio_short_timerid)
+ deltimer(radio_short_timerid)
+ radio_short = TRUE
+ to_chat(src, "Your radio shorts out!")
+ radio_short_timerid = addtimer(CALLBACK(src, .proc/unshort_radio), radio_short_cooldown, flags = TIMER_STOPPABLE)
+
+/mob/living/silicon/pai/proc/unshort_radio()
+ radio_short = FALSE
+ to_chat(src, "You feel your radio is operational once more.")
+ if(radio_short_timerid)
+ deltimer(radio_short_timerid)
+
+/mob/living/silicon/pai/proc/initialize_dynamic_chassis_icons()
+ . = list()
+ var/icon/curr //for inserts
+
+ //This is a horrible system and I wish I was not as lazy and did something smarter, like just generating a new icon in memory which is probably more efficient.
+
+ //Basic /tg/ cyborgs
+ .["Cyborg - Engineering (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "engineer"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (loaderborg)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "loaderborg"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (handyeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "handyeng"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (sleekeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleekeng"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (marinaeng)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinaeng"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "medical"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (marinamed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinamed"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (eyebotmed)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "eyebotmed"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "sec"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (sleeksec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "sleeksec"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (marinasec)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/robots.dmi', "marinasec"), HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Clown (default)"] = process_holoform_icon_filter(icon('icons/mob/robots.dmi', "clown"), HOLOFORM_FILTER_PAI, FALSE)
+
+ //Citadel dogborgs
+ //Engi
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeeng-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (dog - valeeng)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "pupdozer-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Engineering (dog - pupdozer)"] = curr
+ //Med
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihound")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihound-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (dog - medihound)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "medihounddark-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (dog - medihounddark)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valemed")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valemed-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Medical (dog - valemed)"] = curr
+ //Sec
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (dog - k9)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "k9dark-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (dog - k9dark)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesec")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesec-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Security (dog - valesec)"] = curr
+ //Service
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeserv-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Service (dog - valeserv)"] = curr
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valeservdark-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Service (dog - valeservdark)"] = curr
+ //Sci
+ curr = icon('modular_citadel/icons/mob/widerobot.dmi', "valesci")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-rest"), "rest")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-sit"), "sit")
+ curr.Insert(icon('modular_citadel/icons/mob/widerobot.dmi', "valesci-bellyup"), "bellyup")
+ process_holoform_icon_filter(curr, HOLOFORM_FILTER_PAI, FALSE)
+ .["Cyborg - Science (dog - valesci)"] = curr
+ //Misc
+ .["Cyborg - Misc (dog - blade)"] = process_holoform_icon_filter(icon('modular_citadel/icons/mob/widerobot.dmi', "blade"), HOLOFORM_FILTER_PAI, FALSE)
+
+/mob/living/silicon/pai/proc/default_chassis_pixel_offsets_x()
+ . = list()
+ //Engi
+ .["Cyborg - Engineering (dog - valeeng)"] = -16
+ .["Cyborg - Engineering (dog - pupdozer)"] = -16
+ //Med
+ .["Cyborg - Medical (dog - medihound)"] = -16
+ .["Cyborg - Medical (dog - medihounddark)"] = -16
+ .["Cyborg - Medical (dog - valemed)"] = -16
+ //Sec
+ .["Cyborg - Security (dog - k9)"] = -16
+ .["Cyborg - Security (dog - valesec)"] = -16
+ .["Cyborg - Security (dog - k9dark)"] = -16
+ //Service
+ .["Cyborg - Service (dog - valeserv)"] = -16
+ .["Cyborg - Service (dog - valeservdark)"] = -16
+ //Sci
+ .["Cyborg - Security (dog - valesci)"] = -16
+ //Misc
+ .["Cyborg - Misc (dog - blade)"] = -16
diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm
index 5ac841326b..c727e100d8 100644
--- a/code/modules/mob/living/silicon/pai/pai_defense.dm
+++ b/code/modules/mob/living/silicon/pai/pai_defense.dm
@@ -8,9 +8,10 @@
return
take_holo_damage(50/severity)
Knockdown(400/severity)
- silent = max(30/severity, silent)
+ silent = max((3 MINUTES)/severity, silent)
if(holoform)
fold_in(force = TRUE)
+ emitter_next_use = world.time + emitter_emp_cd
//Need more effects that aren't instadeath or permanent law corruption.
/mob/living/silicon/pai/ex_act(severity, target)
diff --git a/code/modules/mob/living/silicon/pai/pai_shell.dm b/code/modules/mob/living/silicon/pai/pai_shell.dm
index 164a3e7389..6577c26402 100644
--- a/code/modules/mob/living/silicon/pai/pai_shell.dm
+++ b/code/modules/mob/living/silicon/pai/pai_shell.dm
@@ -12,12 +12,11 @@
. = fold_in(force)
return
- if(emittersemicd)
+ if(world.time < emitter_next_use)
to_chat(src, "Error: Holochassis emitters recycling. Please try again later.")
return FALSE
- emittersemicd = TRUE
- addtimer(CALLBACK(src, .proc/emittercool), emittercd)
+ emitter_next_use = world.time + emittercd
canmove = TRUE
density = TRUE
if(istype(card.loc, /obj/item/pda))
@@ -46,20 +45,17 @@
visible_message("[src] folds out its holochassis emitter and forms a holoshell around itself!")
holoform = TRUE
-/mob/living/silicon/pai/proc/emittercool()
- emittersemicd = FALSE
-
/mob/living/silicon/pai/proc/fold_in(force = FALSE)
- emittersemicd = TRUE
- if(!force)
- addtimer(CALLBACK(src, .proc/emittercool), emittercd)
- else
- addtimer(CALLBACK(src, .proc/emittercool), emitteroverloadcd)
+ emitter_next_use = world.time + (force? emitteroverloadcd : emittercd)
icon_state = "[chassis]"
if(!holoform)
. = fold_out(force)
return
- visible_message("[src] deactivates its holochassis emitter and folds back into a compact card!")
+ if(force)
+ short_radio()
+ visible_message("[src] shorts out, collapsing back into their storage card, sparks emitted from their radio antenna!")
+ else
+ visible_message("[src] deactivates its holochassis emitter and folds back into a compact card!")
stop_pulling()
if(client)
client.perspective = EYE_PERSPECTIVE
@@ -78,28 +74,39 @@
if(!isturf(loc) && loc != card)
to_chat(src, "You can not change your holochassis composite while not on the ground or in your card!")
return FALSE
- var/choice = input(src, "What would you like to use for your holochassis composite?") as null|anything in possible_chassis
- if(!choice)
+ var/list/choices = list("Preset - Basic", "Preset - Dynamic")
+ if(CONFIG_GET(flag/pai_custom_holoforms))
+ choices += "Custom"
+ var/choicetype = input(src, "What type of chassis do you want to use?") as null|anything in choices
+ if(!choicetype)
return FALSE
- chassis = choice
- icon_state = "[chassis]"
- if(resting)
- icon_state = "[chassis]_rest"
+ switch(choicetype)
+ if("Custom")
+ chassis = "custom"
+ if("Preset - Basic")
+ var/choice = input(src, "What would you like to use for your holochassis composite?") as null|anything in possible_chassis
+ if(!choice)
+ return FALSE
+ chassis = choice
+ if("Preset - Dynamic")
+ var/choice = input(src, "What would you like to use for your holochassis composite?") as null|anything in dynamic_chassis_icons
+ if(!choice)
+ return FALSE
+ chassis = "dynamic"
+ dynamic_chassis = choice
+ resist_a_rest(FALSE, TRUE)
+ update_icon()
to_chat(src, "You switch your holochassis projection composite to [chassis]")
/mob/living/silicon/pai/lay_down()
- ..()
- update_resting_icon(resting)
-
-/mob/living/silicon/pai/proc/update_resting_icon(rest)
- if(rest)
- icon_state = "[chassis]_rest"
- else
- icon_state = "[chassis]"
+ . = ..()
if(loc != card)
- visible_message("[src] [rest? "lays down for a moment..." : "perks up from the ground"]")
+ visible_message("[src] [resting? "lays down for a moment..." : "perks up from the ground"]")
+ update_icon()
-/mob/living/silicon/pai/start_pulling(atom/movable/AM)
+/mob/living/silicon/pai/start_pulling(atom/movable/AM, gs)
+ if(ispAI(AM))
+ return ..()
return FALSE
/mob/living/silicon/pai/proc/toggle_integrated_light()
@@ -122,3 +129,22 @@
to_chat(user, "[src]'s current form isn't able to be carried!")
return FALSE
return ..()
+
+/mob/living/silicon/pai/verb/toggle_chassis_sit()
+ set name = "Toggle Chassis Sit"
+ set category = "IC"
+ set desc = "Whether or not to try to use a sitting icon versus a resting icon. Takes priority over belly-up resting."
+ dynamic_chassis_sit = !dynamic_chassis_sit
+ to_chat(usr, "You are now [dynamic_chassis_sit? "sitting" : "lying down"].")
+ update_icon()
+
+/mob/living/silicon/pai/verb/toggle_chassis_bellyup()
+ set name = "Toggle Chassis Belly Up"
+ set category = "IC"
+ set desc = "Whether or not to try to use a belly up icon while resting. Overridden by sitting."
+ dynamic_chassis_bellyup = !dynamic_chassis_bellyup
+ to_chat(usr, "You are now lying on your [dynamic_chassis_bellyup? "back" : "front"].")
+ update_icon()
+
+/mob/living/silicon/pai/can_buckle_others(mob/living/target, atom/buckle_to)
+ return ispAI(target) && ..()
diff --git a/code/modules/mob/living/silicon/pai/say.dm b/code/modules/mob/living/silicon/pai/say.dm
index 9f07900a25..deedaa1382 100644
--- a/code/modules/mob/living/silicon/pai/say.dm
+++ b/code/modules/mob/living/silicon/pai/say.dm
@@ -5,4 +5,11 @@
..(message)
/mob/living/silicon/pai/binarycheck()
- return 0
+ return FALSE
+
+/mob/living/silicon/pai/radio(message, message_mode, list/spans, language)
+ if((message_mode == "robot") || (message_mode in GLOB.radiochannels))
+ if(radio_short)
+ to_chat(src, "Your radio is shorted out!")
+ return ITALICS | REDUCE_RANGE
+ return ..()
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index 9882d028d1..3ea018ccf6 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -283,7 +283,10 @@
dat += "Refresh
"
// Built-in
dat += "Directives
"
- dat += "Radio Configuration
"
+ if(radio_short)
+ dat += "\[RADIO SHORTED - Recalibrating!\]"
+ else
+ dat += "Radio Configuration
"
dat += "Screen Display
"
//dat += "Text Messaging
"
dat += "
"
diff --git a/code/modules/mob/living/silicon/pai/update_icon.dm b/code/modules/mob/living/silicon/pai/update_icon.dm
new file mode 100644
index 0000000000..9e40b71c5d
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/update_icon.dm
@@ -0,0 +1,27 @@
+/mob/living/silicon/pai/update_icon()
+ if(chassis == "custom") //Make sure custom exists if it's set to custom
+ custom_holoform_icon = client?.prefs?.get_filtered_holoform(HOLOFORM_FILTER_PAI)
+ if(!custom_holoform_icon)
+ chassis = pick(possible_chassis - "custom")
+ if(chassis == "dynamic") //handle dynamic generated icons
+ icon = dynamic_chassis_icons[dynamic_chassis]
+ var/list/states = icon_states(icon)
+ icon_state = ""
+ if(resting) //The next line is some bullshit but I can make it worse if you want and make it a single line instead of four.. :)
+ if(dynamic_chassis_sit && ("sit") in states)
+ icon_state = "sit"
+ else if(dynamic_chassis_bellyup && ("bellyup") in states)
+ icon_state = "bellyup"
+ else if("rest" in states)
+ icon_state = "rest"
+ rotate_on_lying = FALSE
+ else if(chassis == "custom")
+ icon = custom_holoform_icon
+ icon_state = ""
+ rotate_on_lying = TRUE
+ else
+ icon = initial(icon)
+ icon_state = "[chassis][resting? "_rest" : (stat == DEAD? "_dead" : "")]"
+ rotate_on_lying = FALSE
+ pixel_x = ((chassis == "dynamic") && chassis_pixel_offsets_x[dynamic_chassis]) || 0
+ update_transform()
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 5df24406c5..5f7d3ca243 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1298,4 +1298,12 @@
/mob/living/silicon/robot/adjustStaminaLossBuffered(amount, updating_health = 1)
if(istype(cell))
- cell.charge -= amount*5
\ No newline at end of file
+ cell.charge -= amount*5
+
+/mob/living/silicon/robot/verb/viewmanifest()
+ set category = "Robot Commands"
+ set name = "View Crew Manifest"
+
+ if(usr.stat == DEAD)
+ return //won't work if dead
+ ai_roster()
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index b9cee43138..699105ac4c 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -389,6 +389,15 @@
if (aicamera)
return aicamera.selectpicture(user)
+/mob/living/silicon/proc/ai_roster()
+ var/dat = "Crew RosterCrew Roster:
"
+
+ dat += GLOB.data_core.get_manifest()
+ dat += ""
+
+ src << browse(dat, "window=airoster")
+ onclose(src, "airoster")
+
/mob/living/silicon/update_transform()
var/matrix/ntransform = matrix(transform) //aka transform.Copy()
var/changed = 0
diff --git a/code/modules/mob/living/simple_animal/bot/floorbot.dm b/code/modules/mob/living/simple_animal/bot/floorbot.dm
index 396c6de166..cae707ce46 100644
--- a/code/modules/mob/living/simple_animal/bot/floorbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/floorbot.dm
@@ -318,9 +318,9 @@
sleep(50)
if(mode == BOT_REPAIRING && src.loc == target_turf)
if(autotile) //Build the floor and include a tile.
- target_turf.PlaceOnTop(/turf/open/floor/plasteel)
+ target_turf.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
else //Build a hull plating without a floor tile.
- target_turf.PlaceOnTop(/turf/open/floor/plating)
+ target_turf.PlaceOnTop(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
else
var/turf/open/floor/F = target_turf
@@ -334,7 +334,7 @@
if(mode == BOT_REPAIRING && F && src.loc == F)
F.broken = 0
F.burnt = 0
- F.PlaceOnTop(/turf/open/floor/plasteel)
+ F.PlaceOnTop(/turf/open/floor/plasteel, flags = CHANGETURF_INHERIT_AIR)
if(replacetiles && F.type != initial(tiletype.turf_type) && specialtiles && !isplatingturf(F))
anchored = TRUE
@@ -345,7 +345,7 @@
if(mode == BOT_REPAIRING && F && src.loc == F)
F.broken = 0
F.burnt = 0
- F.PlaceOnTop(initial(tiletype.turf_type))
+ F.PlaceOnTop(initial(tiletype.turf_type), flags = CHANGETURF_INHERIT_AIR)
specialtiles -= 1
if(specialtiles == 0)
speak("Requesting refill of custom floortiles to continue replacing.")
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
index 51a9d8d62b..c2c9f5a71f 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm
@@ -486,10 +486,7 @@ Difficulty: Very Hard
if(isturf(Stuff))
var/turf/T = Stuff
if((isspaceturf(T) || isfloorturf(T)) && NewTerrainFloors)
- var/turf/open/O = T.ChangeTurf(NewTerrainFloors)
- if(O.air)
- var/datum/gas_mixture/G = O.air
- G.copy_from_turf(O)
+ var/turf/open/O = T.ChangeTurf(NewTerrainFloors, flags = CHANGETURF_INHERIT_AIR)
if(prob(florachance) && NewFlora.len && !is_blocked_turf(O, TRUE))
var/atom/Picked = pick(NewFlora)
new Picked(O)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm
index e3c81b2639..27ebb71c0e 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/necropolis_tendril.dm
@@ -91,5 +91,5 @@
visible_message("The tendril falls inward, the ground around it widening into a yawning chasm!")
for(var/turf/T in range(2,src))
if(!T.density)
- T.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland)
+ T.TerraformTurf(/turf/open/chasm/lavaland, /turf/open/chasm/lavaland, flags = CHANGETURF_INHERIT_AIR)
qdel(src)
diff --git a/code/modules/mob/living/update_icons.dm b/code/modules/mob/living/update_icons.dm
new file mode 100644
index 0000000000..8bf0dc98a9
--- /dev/null
+++ b/code/modules/mob/living/update_icons.dm
@@ -0,0 +1,26 @@
+//IMPORTANT: Multiple animate() calls do not stack well, so try to do them all at once if you can.
+/mob/living/update_transform()
+ var/matrix/ntransform = matrix(transform) //aka transform.Copy()
+ var/final_pixel_y = pixel_y
+ var/final_dir = dir
+ var/changed = 0
+ if(lying != lying_prev && rotate_on_lying)
+ changed++
+ ntransform.TurnTo(lying_prev,lying)
+ if(lying == 0) //Lying to standing
+ final_pixel_y = get_standard_pixel_y_offset()
+ else //if(lying != 0)
+ if(lying_prev == 0) //Standing to lying
+ pixel_y = get_standard_pixel_y_offset()
+ final_pixel_y = get_standard_pixel_y_offset(lying)
+ if(dir & (EAST|WEST)) //Facing east or west
+ final_dir = pick(NORTH, SOUTH) //So you fall on your side rather than your face or ass
+
+ if(resize != RESIZE_DEFAULT_SIZE)
+ changed++
+ ntransform.Scale(resize)
+ resize = RESIZE_DEFAULT_SIZE
+
+ if(changed)
+ animate(src, transform = ntransform, time = 2, pixel_y = final_pixel_y, dir = final_dir, easing = EASE_IN|EASE_OUT)
+ setMovetype(movement_type & ~FLOATING) // If we were without gravity, the bouncing animation got stopped, so we make sure we restart it in next life().
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 427e30f2e2..fad9431b61 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -802,11 +802,14 @@ GLOBAL_VAR_INIT(exploit_warn_spam_prevention, 0)
//can the mob be buckled to something by default?
/mob/proc/can_buckle()
- return 1
+ return TRUE
//can the mob be unbuckled from something by default?
/mob/proc/can_unbuckle()
- return 1
+ return TRUE
+
+/mob/proc/can_buckle_others(mob/living/target, atom/buckle_to)
+ return TRUE
//Can the mob interact() with an atom?
/mob/proc/can_interact_with(atom/A)
diff --git a/code/modules/projectiles/projectile/magic.dm b/code/modules/projectiles/projectile/magic.dm
index 173d9721a9..791db320a2 100644
--- a/code/modules/projectiles/projectile/magic.dm
+++ b/code/modules/projectiles/projectile/magic.dm
@@ -93,7 +93,7 @@
/obj/item/projectile/magic/door/proc/CreateDoor(turf/T)
var/door_type = pick(door_types)
var/obj/structure/mineral_door/D = new door_type(T)
- T.ChangeTurf(/turf/open/floor/plating)
+ T.ChangeTurf(/turf/open/floor/plating, flags = CHANGETURF_INHERIT_AIR)
D.Open()
/obj/item/projectile/magic/door/proc/OpenDoor(var/obj/machinery/door/D)
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 512b8a21a1..bd6eb4244c 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -1870,133 +1870,82 @@
reagent_state = LIQUID
color = "#b51d05"
taste_description = "carpet" // Your tounge feels furry.
+ var/carpet_type = /turf/open/floor/carpet
/datum/reagent/carpet/reaction_turf(turf/T, reac_volume)
if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet)
+ F.PlaceOnTop(carpet_type, flags = CHANGETURF_INHERIT_AIR)
..()
/datum/reagent/carpet/black
name = "Liquid Black Carpet"
id = "blackcarpet"
color = "#363636"
-
-/datum/reagent/carpet/black/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/black)
- ..()
+ carpet_type = /turf/open/floor/carpet/black
/datum/reagent/carpet/blackred
name = "Liquid Red Black Carpet"
id = "blackredcarpet"
color = "#342125"
-
-/datum/reagent/carpet/blackred/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/blackred)
- ..()
+ carpet_type = /turf/open/floor/carpet/blackred
/datum/reagent/carpet/monochrome
name = "Liquid Monochrome Carpet"
id = "monochromecarpet"
color = "#b4b4b4"
-
-/datum/reagent/carpet/monochrome/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/monochrome)
- ..()
+ carpet_type = /turf/open/floor/carpet/monochrome
/datum/reagent/carpet/blue
name = "Liquid Blue Carpet"
id = "bluecarpet"
color = "#1256ff"
-
-/datum/reagent/carpet/blue/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/blue)
- ..()
+ carpet_type = /turf/open/floor/carpet/blue
/datum/reagent/carpet/cyan
name = "Liquid Cyan Carpet"
id = "cyancarpet"
color = "#3acfb9"
-
-/datum/reagent/carpet/cyan/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/cyan)
- ..()
+ carpet_type = /turf/open/floor/carpet/cyan
/datum/reagent/carpet/green
name = "Liquid Green Carpet"
id = "greencarpet"
color = "#619b62"
-
-/datum/reagent/carpet/green/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/green)
- ..()
+ carpet_type = /turf/open/floor/carpet/green
/datum/reagent/carpet/orange
name = "Liquid Orange Carpet"
id = "orangecarpet"
color = "#cc7900"
-
-/datum/reagent/carpet/orange/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/orange)
- ..()
+ carpet_type = /turf/open/floor/carpet/orange
/datum/reagent/carpet/purple
name = "Liquid Purple Carpet"
id = "purplecarpet"
color = "#6d3392"
+ carpet_type = /turf/open/floor/carpet/purple
-/datum/reagent/carpet/purple/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/purple)
- ..()
/datum/reagent/carpet/red
name = "Liquid Red Carpet"
id = "redcarpet"
color = "#871515"
+ carpet_type = /turf/open/floor/carpet/red
-/datum/reagent/carpet/red/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/red)
- ..()
/datum/reagent/carpet/royalblack
name = "Liquid Royal Black Carpet"
id = "royalblackcarpet"
color = "#483d05"
+ carpet_type = /turf/open/floor/carpet/royalblack
-/datum/reagent/carpet/royalblack/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/royalblack)
- ..()
/datum/reagent/carpet/royalblue
name = "Liquid Royal Blue Carpet"
id = "royalbluecarpet"
color = "#24227e"
-
-/datum/reagent/carpet/royalblue/reaction_turf(turf/T, reac_volume)
- if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
- var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/carpet/royalblue)
- ..()
+ carpet_type = /turf/open/floor/carpet/royalblue
// Virology virus food chems.
diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
index c170858d98..36621aa662 100644
--- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
@@ -53,7 +53,7 @@
if(isplatingturf(T))
var/turf/open/floor/plating/F = T
if(prob(10 + F.burnt + 5*F.broken)) //broken or burnt plating is more susceptible to being destroyed
- F.ScrapeAway()
+ F.ScrapeAway(flags = CHANGETURF_INHERIT_AIR)
if(isfloorturf(T))
var/turf/open/floor/F = T
if(prob(reac_volume))
diff --git a/code/modules/reagents/chemistry/recipes/special.dm b/code/modules/reagents/chemistry/recipes/special.dm
index 59394e6e4b..08d07b8a52 100644
--- a/code/modules/reagents/chemistry/recipes/special.dm
+++ b/code/modules/reagents/chemistry/recipes/special.dm
@@ -123,24 +123,11 @@ GLOBAL_LIST_INIT(food_reagents, build_reagents_to_food()) //reagentid = related
/datum/chemical_reaction/randomized/proc/LoadOldRecipe(recipe_data)
created = text2num(recipe_data["timestamp"])
-
- var/req_reag = unwrap_reagent_list(recipe_data["required_reagents"])
- if(!req_reag)
- return FALSE
- required_reagents = req_reag
-
- var/req_catalysts = unwrap_reagent_list(recipe_data["required_catalysts"])
- if(!req_catalysts)
- return FALSE
- required_catalysts = req_catalysts
-
+ required_reagents = SANITIZE_LIST(recipe_data["required_reagents"])
+ required_catalysts = SANITIZE_LIST(recipe_data["required_catalysts"])
required_temp = recipe_data["required_temp"]
is_cold_recipe = recipe_data["is_cold_recipe"]
-
- var/temp_results = unwrap_reagent_list(recipe_data["results"])
- if(!temp_results)
- return FALSE
- results = temp_results
+ results = SANITIZE_LIST(recipe_data["results"])
var/containerpath = text2path(recipe_data["required_container"])
if(!containerpath)
return FALSE
diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm
index 7701e58616..46ddb1e2ed 100644
--- a/code/modules/recycling/conveyor2.dm
+++ b/code/modules/recycling/conveyor2.dm
@@ -31,7 +31,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
/obj/machinery/conveyor/inverted/Initialize(mapload)
. = ..()
if(mapload && !(dir in GLOB.diagonals))
- log_game("### MAPPING ERROR: [src] at [AREACOORD(src)] spawned without using a diagonal dir. Please replace with a normal version.")
+ log_mapping("[src] at [AREACOORD(src)] spawned without using a diagonal dir. Please replace with a normal version.")
// Auto conveyour is always on unless unpowered
diff --git a/code/modules/ruins/objects_and_mobs/sin_ruins.dm b/code/modules/ruins/objects_and_mobs/sin_ruins.dm
index caa2ca7adf..76897b5276 100644
--- a/code/modules/ruins/objects_and_mobs/sin_ruins.dm
+++ b/code/modules/ruins/objects_and_mobs/sin_ruins.dm
@@ -108,7 +108,7 @@
if (levels.len)
dest = locate(T.x, T.y, pick(levels))
- T.ChangeTurf(/turf/open/chasm)
+ T.ChangeTurf(/turf/open/chasm, flags = CHANGETURF_INHERIT_AIR)
var/turf/open/chasm/C = T
C.set_target(dest)
C.drop(user)
diff --git a/code/modules/spells/spell_types/conjure.dm b/code/modules/spells/spell_types/conjure.dm
index 3ebded7487..38a2d5bddd 100644
--- a/code/modules/spells/spell_types/conjure.dm
+++ b/code/modules/spells/spell_types/conjure.dm
@@ -31,7 +31,7 @@
if(ispath(summoned_object_type, /turf))
var/turf/O = spawn_place
var/N = summoned_object_type
- O.ChangeTurf(N)
+ O.ChangeTurf(N, flags = CHANGETURF_INHERIT_AIR)
else
var/atom/summoned_object = new summoned_object_type(spawn_place)
diff --git a/code/modules/spells/spell_types/devil.dm b/code/modules/spells/spell_types/devil.dm
index 8b8328abbe..d97d466e4d 100644
--- a/code/modules/spells/spell_types/devil.dm
+++ b/code/modules/spells/spell_types/devil.dm
@@ -234,7 +234,7 @@
dancefloor_exists = FALSE
for(var/i in 1 to dancefloor_turfs.len)
var/turf/T = dancefloor_turfs[i]
- T.ChangeTurf(dancefloor_turfs_types[i])
+ T.ChangeTurf(dancefloor_turfs_types[i], flags = CHANGETURF_INHERIT_AIR)
else
var/list/funky_turfs = RANGE_TURFS(1, user)
for(var/turf/closed/solid in funky_turfs)
@@ -248,7 +248,7 @@
var/turf/T = t
dancefloor_turfs[i] = T
dancefloor_turfs_types[i] = T.type
- T.ChangeTurf((i % 2 == 0) ? /turf/open/floor/light/colour_cycle/dancefloor_a : /turf/open/floor/light/colour_cycle/dancefloor_b)
+ T.ChangeTurf((i % 2 == 0) ? /turf/open/floor/light/colour_cycle/dancefloor_a : /turf/open/floor/light/colour_cycle/dancefloor_b, flags = CHANGETURF_INHERIT_AIR)
i++
/datum/effect_system/smoke_spread/transparent/dancefloor_devil
diff --git a/html/changelogs/AutoChangeLog-pr-10189.yml b/html/changelogs/AutoChangeLog-pr-10189.yml
new file mode 100644
index 0000000000..5889e127d2
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10189.yml
@@ -0,0 +1,5 @@
+author: "Putnam3145"
+delete-after: True
+changes:
+ - rscadd: "Dynamic storytellers, a new voting paradigm for dynamic"
+ - rscadd: "Support for approval voting and condorcet (ranked choice) voting in server votes"
diff --git a/html/changelogs/AutoChangeLog-pr-10191.yml b/html/changelogs/AutoChangeLog-pr-10191.yml
new file mode 100644
index 0000000000..c1366727bd
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10191.yml
@@ -0,0 +1,5 @@
+author: "kevinz000"
+delete-after: True
+changes:
+ - rscadd: "Custom holoforms have been added for pAIs and AIs. oh and cyborg holograms of specific modules too."
+ - balance: "pais are no longer indestructible-flagged while in card form. pai radios now short out if they are forcefully collapsed from damage."
diff --git a/html/changelogs/AutoChangeLog-pr-10238.yml b/html/changelogs/AutoChangeLog-pr-10238.yml
new file mode 100644
index 0000000000..dda40c86c9
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10238.yml
@@ -0,0 +1,7 @@
+author: "SpaceManiac, bobbahbrown, ShizCalev, SpaceManiac (ported by Ghommie)"
+delete-after: True
+changes:
+ - code_imp: "It is now possible to set a different most-base-turf per z-level."
+ - spellcheck: "Removed unlawful reference to Disney's Star Wars franchise in map logging."
+ - tweak: "Moved mapping related errors to their own log file."
+ - bugfix: "Destruction on Lavaland will no longer reveal space in rare situations."
diff --git a/html/changelogs/AutoChangeLog-pr-10255.yml b/html/changelogs/AutoChangeLog-pr-10255.yml
new file mode 100644
index 0000000000..ebaccd0af6
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10255.yml
@@ -0,0 +1,4 @@
+author: "Xantholne"
+delete-after: True
+changes:
+ - bugfix: "Christmas clothes that where missing stuff should work again"
diff --git a/html/changelogs/AutoChangeLog-pr-10270.yml b/html/changelogs/AutoChangeLog-pr-10270.yml
new file mode 100644
index 0000000000..b47a249f9a
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10270.yml
@@ -0,0 +1,4 @@
+author: "Ghommie"
+delete-after: True
+changes:
+ - bugfix: "Fixed the secret sauce recipe being randomized every round."
diff --git a/html/changelogs/AutoChangeLog-pr-10388.yml b/html/changelogs/AutoChangeLog-pr-10388.yml
new file mode 100644
index 0000000000..fd06a5db3f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10388.yml
@@ -0,0 +1,4 @@
+author: "CameronWoof"
+delete-after: True
+changes:
+ - rscadd: "Robots can now check the crew manifest from anywhere with the \"View Crew Manifest\" verb."
diff --git a/html/changelogs/AutoChangeLog-pr-10399.yml b/html/changelogs/AutoChangeLog-pr-10399.yml
new file mode 100644
index 0000000000..cbdb00630c
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10399.yml
@@ -0,0 +1,4 @@
+author: "kevinz000"
+delete-after: True
+changes:
+ - balance: "Cargo passive gen is now 500 down from 750."
diff --git a/html/changelogs/AutoChangeLog-pr-10403.yml b/html/changelogs/AutoChangeLog-pr-10403.yml
new file mode 100644
index 0000000000..321789a546
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10403.yml
@@ -0,0 +1,5 @@
+author: "r4d6"
+delete-after: True
+changes:
+ - rscadd: "Added a way to make opaque plastic flaps
+change: Change the already existing flap recipe to show that they are see-through"
diff --git a/html/changelogs/AutoChangeLog-pr-10432.yml b/html/changelogs/AutoChangeLog-pr-10432.yml
new file mode 100644
index 0000000000..a0b40baa45
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-10432.yml
@@ -0,0 +1,4 @@
+author: "Trilbyspaceclone"
+delete-after: True
+changes:
+ - bugfix: "missing sprites with crushed cans"
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index e13ce10347..51e9453087 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi
index 5d243f93b8..2e53bb24db 100644
Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ
diff --git a/icons/obj/janitor.dmi b/icons/obj/janitor.dmi
index 4886cb8441..cb3870dc61 100644
Binary files a/icons/obj/janitor.dmi and b/icons/obj/janitor.dmi differ
diff --git a/modular_citadel/code/modules/mob/cit_emotes.dm b/modular_citadel/code/modules/mob/cit_emotes.dm
index 7e7a77538a..d0ad0d3232 100644
--- a/modular_citadel/code/modules/mob/cit_emotes.dm
+++ b/modular_citadel/code/modules/mob/cit_emotes.dm
@@ -70,113 +70,124 @@
message = "makes a very loud noise."
. = ..()
-/datum/emote/sound/carbon/snap
- key = "snap"
- key_third_person = "snaps"
- muzzle_ignore = TRUE
- restraint_check = TRUE
- emote_type = EMOTE_AUDIBLE
- sound = 'sound/effects/snap01.ogg'
- mob_type_allowed_typecache = list(/mob/living/carbon/)
-
-
/datum/emote/living/snap
key = "snap"
key_third_person = "snaps"
message = "snaps their fingers."
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = TRUE
+ restraint_check = TRUE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/snap.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/snap.ogg', 50, 1, -1)
/datum/emote/living/snap2
key = "snap2"
key_third_person = "snaps twice"
message = "snaps twice."
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = TRUE
+ restraint_check = TRUE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap2/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/snap2.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/snap2.ogg', 50, 1, -1)
/datum/emote/living/snap3
key = "snap3"
key_third_person = "snaps thrice"
message = "snaps thrice."
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = TRUE
+ restraint_check = TRUE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/snap3/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/snap3.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/snap3.ogg', 50, 1, -1)
/datum/emote/living/awoo
key = "awoo"
key_third_person = "lets out an awoo"
message = "lets out an awoo!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/awoo/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/awoo.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/awoo.ogg', 50, 1, -1)
/datum/emote/living/nya
key = "nya"
key_third_person = "lets out a nya"
message = "lets out a nya!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/nya/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/nya.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/nya.ogg', 50, 1, -1)
/datum/emote/living/weh
key = "weh"
key_third_person = "lets out a weh"
message = "lets out a weh!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/weh/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/weh.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/weh.ogg', 50, 1, -1)
/datum/emote/living/peep
key = "peep"
key_third_person = "peeps like a bird"
message = "peeps like a bird!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/peep/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/peep.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/peep.ogg', 50, 1, -1)
/datum/emote/living/dab
key = "dab"
@@ -190,41 +201,49 @@
key_third_person = "lets out a tiny squeak"
message = "lets out a tiny squeak!"
emote_type = EMOTE_AUDIBLE
- mob_type_allowed_typecache = list(/mob/living/carbon)
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/mothsqueak/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/mothsqueak.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/mothsqueak.ogg', 50, 1, -1)
/datum/emote/living/merp
key = "merp"
key_third_person = "merps"
message = "merps!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/merp/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- playsound(user, 'modular_citadel/sound/voice/merp.ogg', 50, 1, -1)
- . = ..()
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ playsound(user, 'modular_citadel/sound/voice/merp.ogg', 50, 1, -1)
/datum/emote/living/bark
key = "bark"
key_third_person = "barks"
message = "barks!"
emote_type = EMOTE_AUDIBLE
+ muzzle_ignore = FALSE
+ restraint_check = FALSE
+ mob_type_allowed_typecache = list(/mob/living/carbon, /mob/living/silicon/pai)
/datum/emote/living/bark/run_emote(mob/living/user, params)
- if(ishuman(user))
- if(user.nextsoundemote >= world.time)
- return
- user.nextsoundemote = world.time + 7
- var/sound = pick('modular_citadel/sound/voice/bark1.ogg', 'modular_citadel/sound/voice/bark2.ogg')
- playsound(user, sound, 50, 1, -1)
- . = ..()
\ No newline at end of file
+ if(!(. = ..()))
+ return
+ if(user.nextsoundemote >= world.time)
+ return
+ user.nextsoundemote = world.time + 7
+ var/sound = pick('modular_citadel/sound/voice/bark1.ogg', 'modular_citadel/sound/voice/bark2.ogg')
+ playsound(user, sound, 50, 1, -1)
diff --git a/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm b/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm
index d7ff42a9eb..7e094eddda 100644
--- a/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm
+++ b/modular_citadel/code/modules/reagents/chemistry/reagents/astrogen.dm
@@ -54,7 +54,7 @@ I'd like to point out from my calculations it'll take about 60-80 minutes to die
/datum/reagent/fermi/astral/reaction_turf(turf/T, reac_volume)
if(isplatingturf(T) || istype(T, /turf/open/floor/plasteel))
var/turf/open/floor/F = T
- F.PlaceOnTop(/turf/open/floor/fakespace)
+ F.PlaceOnTop(/turf/open/floor/fakespace, flags = CHANGETURF_INHERIT_AIR)
..()
/datum/reagent/fermi/astral/reaction_obj(obj/O, reac_volume)
diff --git a/tgstation.dme b/tgstation.dme
index d92fbff5d9..7e7d69d6f1 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -43,6 +43,7 @@
#include "code\__DEFINES\diseases.dm"
#include "code\__DEFINES\DNA.dm"
#include "code\__DEFINES\donator_groupings.dm"
+#include "code\__DEFINES\dynamic.dm"
#include "code\__DEFINES\events.dm"
#include "code\__DEFINES\exports.dm"
#include "code\__DEFINES\fantasy_affixes.dm"
@@ -108,6 +109,7 @@
#include "code\__DEFINES\typeids.dm"
#include "code\__DEFINES\vehicles.dm"
#include "code\__DEFINES\voreconstants.dm"
+#include "code\__DEFINES\vote.dm"
#include "code\__DEFINES\vv.dm"
#include "code\__DEFINES\wall_dents.dm"
#include "code\__DEFINES\wires.dm"
@@ -118,6 +120,7 @@
#include "code\__HELPERS\areas.dm"
#include "code\__HELPERS\AStar.dm"
#include "code\__HELPERS\cmp.dm"
+#include "code\__HELPERS\custom_holoforms.dm"
#include "code\__HELPERS\dates.dm"
#include "code\__HELPERS\donator_groupings.dm"
#include "code\__HELPERS\files.dm"
@@ -579,6 +582,7 @@
#include "code\game\gamemodes\dynamic\dynamic_rulesets_latejoin.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_midround.dm"
#include "code\game\gamemodes\dynamic\dynamic_rulesets_roundstart.dm"
+#include "code\game\gamemodes\dynamic\dynamic_storytellers.dm"
#include "code\game\gamemodes\extended\extended.dm"
#include "code\game\gamemodes\gangs\dominator.dm"
#include "code\game\gamemodes\gangs\dominator_countdown.dm"
@@ -2108,6 +2112,7 @@
#include "code\modules\mob\living\say.dm"
#include "code\modules\mob\living\status_procs.dm"
#include "code\modules\mob\living\taste.dm"
+#include "code\modules\mob\living\update_icons.dm"
#include "code\modules\mob\living\ventcrawling.dm"
#include "code\modules\mob\living\brain\brain.dm"
#include "code\modules\mob\living\brain\brain_item.dm"
@@ -2214,6 +2219,7 @@
#include "code\modules\mob\living\carbon\monkey\monkey_defense.dm"
#include "code\modules\mob\living\carbon\monkey\punpun.dm"
#include "code\modules\mob\living\carbon\monkey\update_icons.dm"
+#include "code\modules\mob\living\silicon\custom_holoform.dm"
#include "code\modules\mob\living\silicon\damage_procs.dm"
#include "code\modules\mob\living\silicon\death.dm"
#include "code\modules\mob\living\silicon\examine.dm"
@@ -2245,6 +2251,7 @@
#include "code\modules\mob\living\silicon\pai\personality.dm"
#include "code\modules\mob\living\silicon\pai\say.dm"
#include "code\modules\mob\living\silicon\pai\software.dm"
+#include "code\modules\mob\living\silicon\pai\update_icon.dm"
#include "code\modules\mob\living\silicon\robot\death.dm"
#include "code\modules\mob\living\silicon\robot\emote.dm"
#include "code\modules\mob\living\silicon\robot\examine.dm"