| [Bot.hacked ? "(!) [Bot.name]" : Bot.name] ([Bot.model]) | "
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 0c6ddf80fc6..131477d086e 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -1456,4 +1456,4 @@ var/list/robot_verbs_default = list(
SEND_SIGNAL(src, COMSIG_MOB_UPDATE_SIGHT)
sync_lighting_plane_alpha()
-
\ No newline at end of file
+
diff --git a/code/modules/mob/living/simple_animal/hostile/alien.dm b/code/modules/mob/living/simple_animal/hostile/alien.dm
index d1982ac359d..0aa148eb031 100644
--- a/code/modules/mob/living/simple_animal/hostile/alien.dm
+++ b/code/modules/mob/living/simple_animal/hostile/alien.dm
@@ -4,7 +4,7 @@
icon = 'icons/mob/alien.dmi'
icon_state = "alienh_running"
icon_living = "alienh_running"
- icon_dead = "alien_l"
+ icon_dead = "alienh_dead"
icon_gib = "syndicate_gib"
response_help = "pokes the"
response_disarm = "shoves the"
@@ -40,7 +40,7 @@
name = "alien drone"
icon_state = "aliend_running"
icon_living = "aliend_running"
- icon_dead = "aliend_l"
+ icon_dead = "aliend_dead"
health = 60
maxHealth = 60
melee_damage_lower = 15
@@ -61,7 +61,7 @@
name = "alien sentinel"
icon_state = "aliens_running"
icon_living = "aliens_running"
- icon_dead = "aliens_l"
+ icon_dead = "aliens_dead"
health = 120
maxHealth = 120
melee_damage_lower = 15
@@ -77,7 +77,7 @@
name = "alien queen"
icon_state = "alienq_running"
icon_living = "alienq_running"
- icon_dead = "alienq_l"
+ icon_dead = "alienq_d"
health = 250
maxHealth = 250
melee_damage_lower = 15
diff --git a/code/modules/mob/living/simple_animal/hostile/creature.dm b/code/modules/mob/living/simple_animal/hostile/creature.dm
index 13c695387bd..5b97579b146 100644
--- a/code/modules/mob/living/simple_animal/hostile/creature.dm
+++ b/code/modules/mob/living/simple_animal/hostile/creature.dm
@@ -1,7 +1,6 @@
/mob/living/simple_animal/hostile/creature
name = "creature"
desc = "A sanity-destroying otherthing."
- icon = 'icons/mob/critter.dmi'
speak_emote = list("gibbers")
icon_state = "otherthing"
icon_living = "otherthing"
diff --git a/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm b/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm
index 100225e0760..e34a5e0cb08 100644
--- a/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm
+++ b/code/modules/mob/living/simple_animal/hostile/jungle_animals.dm
@@ -21,6 +21,7 @@
maxHealth = 50
health = 50
pixel_x = -16
+ see_in_dark = 8
harm_intent_damage = 8
melee_damage_lower = 15
diff --git a/code/modules/mob/living/simple_animal/hostile/mining/necropolis_tendril.dm b/code/modules/mob/living/simple_animal/hostile/mining/necropolis_tendril.dm
index 2ee9c387140..c6bc205627e 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining/necropolis_tendril.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining/necropolis_tendril.dm
@@ -12,7 +12,7 @@
maxHealth = 250
max_mobs = 3
spawn_time = 300 //30 seconds default
- mob_type = /mob/living/simple_animal/hostile/asteroid/basilisk/watcher/tendril
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/basilisk/watcher/tendril)
spawn_text = "emerges from"
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 5, "min_n2" = 0, "max_n2" = 0)
minbodytemp = 0
@@ -73,7 +73,7 @@
qdel(src)
/mob/living/simple_animal/hostile/spawner/lavaland/goliath
- mob_type = /mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/goliath/beast/tendril)
/mob/living/simple_animal/hostile/spawner/lavaland/legion
- mob_type = /mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril
\ No newline at end of file
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord/legion/tendril)
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/hostile/netherworld.dm b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
new file mode 100644
index 00000000000..fc39035618e
--- /dev/null
+++ b/code/modules/mob/living/simple_animal/hostile/netherworld.dm
@@ -0,0 +1,111 @@
+/mob/living/simple_animal/hostile/netherworld
+ name = "creature"
+ desc = "A sanity-destroying otherthing from the netherworld."
+ icon_state = "otherthing"
+ icon_living = "otherthing"
+ icon_dead = "otherthing-dead"
+ health = 80
+ maxHealth = 80
+ obj_damage = 100
+ melee_damage_lower = 25
+ melee_damage_upper = 50
+ attacktext = "slashes"
+ attack_sound = 'sound/weapons/bladeslice.ogg'
+ faction = list("creature")
+ speak_emote = list("screams")
+ gold_core_spawnable = CHEM_MOB_SPAWN_HOSTILE
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ faction = list("nether")
+
+/mob/living/simple_animal/hostile/netherworld/migo
+ name = "mi-go"
+ desc = "A pinkish, fungoid crustacean-like creature with numerous pairs of clawed appendages and a head covered with waving antennae."
+ speak_emote = list("screams", "clicks", "chitters", "barks", "moans", "growls", "meows", "reverberates", "roars", "squeaks", "rattles", "exclaims", "yells", "remarks", "mumbles", "jabbers", "stutters", "seethes")
+ icon_state = "mi-go"
+ icon_living = "mi-go"
+ icon_dead = "mi-go-dead"
+ attacktext = "lacerates"
+ speed = -0.5
+ var/static/list/migo_sounds
+ deathmessage = "wails as its form turns into a pulpy mush."
+ death_sound = 'sound/voice/hiss6.ogg'
+
+/mob/living/simple_animal/hostile/netherworld/migo/Initialize()
+ . = ..()
+ migo_sounds = list('sound/items/bubblewrap.ogg', 'sound/items/change_jaws.ogg', 'sound/items/crowbar.ogg', 'sound/items/drink.ogg', 'sound/items/deconstruct.ogg', 'sound/items/change_drill.ogg', 'sound/items/dodgeball.ogg', 'sound/items/eatfood.ogg', 'sound/items/screwdriver.ogg', 'sound/items/weeoo1.ogg', 'sound/items/wirecutter.ogg', 'sound/items/welder.ogg', 'sound/items/zip.ogg', 'sound/items/rped.ogg', 'sound/items/ratchet.ogg', 'sound/items/polaroid1.ogg', 'sound/items/pshoom.ogg', 'sound/items/airhorn.ogg', 'sound/voice/bcreep.ogg', 'sound/voice/biamthelaw.ogg', 'sound/voice/ed209_20sec.ogg', 'sound/voice/hiss3.ogg', 'sound/voice/hiss6.ogg', 'sound/voice/mpatchedup.ogg', 'sound/voice/mfeelbetter.ogg', 'sound/weapons/sear.ogg', 'sound/ambience/antag/tatoralert.ogg', 'sound/mecha/nominal.ogg', 'sound/mecha/weapdestr.ogg', 'sound/mecha/critdestr.ogg', 'sound/mecha/imag_enh.ogg', 'sound/effects/adminhelp.ogg', 'sound/effects/alert.ogg', 'sound/effects/attackblob.ogg', 'sound/effects/bamf.ogg', 'sound/effects/blobattack.ogg', 'sound/effects/break_stone.ogg', 'sound/effects/bubbles.ogg', 'sound/effects/bubbles2.ogg', 'sound/effects/clang.ogg', 'sound/effects/clownstep2.ogg', 'sound/effects/dimensional_rend.ogg', 'sound/effects/doorcreaky.ogg', 'sound/effects/empulse.ogg', 'sound/effects/explosionfar.ogg', 'sound/effects/explosion1.ogg', 'sound/effects/grillehit.ogg', 'sound/effects/genetics.ogg', 'sound/effects/heartbeat.ogg', 'sound/effects/hyperspace_begin.ogg', 'sound/effects/hyperspace_end.ogg', 'sound/goonstation/effects/screech.ogg', 'sound/effects/phasein.ogg', 'sound/effects/picaxe1.ogg', 'sound/effects/sparks1.ogg', 'sound/effects/smoke.ogg', 'sound/effects/splat.ogg', 'sound/effects/snap.ogg', 'sound/effects/tendril_destroyed.ogg', 'sound/effects/supermatter.ogg', 'sound/misc/desceration-01.ogg', 'sound/misc/desceration-02.ogg', 'sound/misc/desceration-03.ogg', 'sound/misc/bloblarm.ogg', 'sound/goonstation/misc/airraid_loop.ogg', 'sound/misc/interference.ogg', 'sound/misc/notice1.ogg', 'sound/misc/notice2.ogg', 'sound/misc/sadtrombone.ogg', 'sound/misc/slip.ogg', 'sound/weapons/armbomb.ogg', 'sound/weapons/chainsaw.ogg', 'sound/weapons/emitter.ogg', 'sound/weapons/emitter2.ogg', 'sound/weapons/blade1.ogg', 'sound/weapons/bladeslice.ogg', 'sound/weapons/blastcannon.ogg', 'sound/weapons/blaster.ogg', 'sound/weapons/bulletflyby3.ogg', 'sound/weapons/circsawhit.ogg', 'sound/weapons/cqchit2.ogg', 'sound/weapons/drill.ogg', 'sound/weapons/genhit1.ogg', 'sound/weapons/gunshots/gunshot_silenced.ogg', 'sound/weapons/gunshots/gunshot.ogg', 'sound/weapons/handcuffs.ogg', 'sound/weapons/homerun.ogg', 'sound/weapons/kenetic_accel.ogg', 'sound/machines/fryer/deep_fryer_emerge.ogg', 'sound/machines/airlock_alien_prying.ogg', 'sound/machines/airlock_close.ogg', 'sound/machines/airlockforced.ogg', 'sound/machines/airlock_open.ogg', 'sound/machines/alarm.ogg', 'sound/machines/blender.ogg', 'sound/machines/boltsdown.ogg', 'sound/machines/boltsup.ogg', 'sound/machines/buzz-sigh.ogg', 'sound/machines/buzz-two.ogg', 'sound/machines/chime.ogg', 'sound/machines/defib_charge.ogg', 'sound/machines/defib_failed.ogg', 'sound/machines/defib_ready.ogg', 'sound/machines/defib_zap.ogg', 'sound/machines/deniedbeep.ogg', 'sound/machines/ding.ogg', 'sound/machines/disposalflush.ogg', 'sound/machines/door_close.ogg', 'sound/machines/door_open.ogg', 'sound/machines/engine_alert1.ogg', 'sound/machines/engine_alert2.ogg', 'sound/machines/hiss.ogg', 'sound/machines/honkbot_evil_laugh.ogg', 'sound/machines/juicer.ogg', 'sound/machines/ping.ogg', 'sound/ambience/signal.ogg', 'sound/machines/synth_no.ogg', 'sound/machines/synth_yes.ogg', 'sound/machines/terminal_alert.ogg', 'sound/machines/twobeep.ogg', 'sound/machines/ventcrawl.ogg', 'sound/machines/warning-buzzer.ogg', 'sound/ai/outbreak5.ogg', 'sound/ai/outbreak7.ogg', 'sound/ai/poweroff.ogg', 'sound/ai/radiation.ogg', 'sound/ai/shuttlecalled.ogg', 'sound/ai/shuttledock.ogg', 'sound/ai/shuttlerecalled.ogg', 'sound/ai/aimalf.ogg') //hahahaha fuck you code divers
+
+/mob/living/simple_animal/hostile/netherworld/migo/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ ..()
+ if(stat)
+ return
+ var/chosen_sound = pick(migo_sounds)
+ playsound(src, chosen_sound, 50, TRUE)
+
+/mob/living/simple_animal/hostile/netherworld/migo/Life()
+ ..()
+ if(stat)
+ return
+ if(prob(10))
+ var/chosen_sound = pick(migo_sounds)
+ playsound(src, chosen_sound, 50, TRUE)
+
+/mob/living/simple_animal/hostile/netherworld/blankbody
+ name = "blank body"
+ desc = "This looks human enough, but its flesh has an ashy texture, and it's face is featureless save an eerie smile."
+ icon_state = "blank-body"
+ icon_living = "blank-body"
+ icon_dead = "blank-dead"
+ gold_core_spawnable = CHEM_MOB_SPAWN_INVALID
+ health = 100
+ maxHealth = 100
+ melee_damage_lower = 5
+ melee_damage_upper = 10
+ attacktext = "punches"
+ deathmessage = "falls apart into a fine dust."
+
+/mob/living/simple_animal/hostile/spawner/nether
+ name = "netherworld link"
+ desc = null //see examine()
+ icon_state = "nether"
+ health = 50
+ spawn_time = 600 //1 minute
+ max_mobs = 15
+ icon = 'icons/mob/nest.dmi'
+ spawn_text = "crawls through"
+ mob_types = list(/mob/living/simple_animal/hostile/netherworld/migo, /mob/living/simple_animal/hostile/netherworld, /mob/living/simple_animal/hostile/netherworld/blankbody)
+ faction = list("nether")
+
+/mob/living/simple_animal/hostile/spawner/nether/Initialize()
+ .=..()
+ START_PROCESSING(SSprocessing, src)
+
+/mob/living/simple_animal/hostile/spawner/nether/examine(mob/user)
+ ..()
+ if(isskeleton(user))
+ to_chat(user, "A direct link to another dimension full of creatures very happy to see you. You can see your house from here!")
+ else
+ to_chat(user, "A direct link to another dimension full of creatures not very happy to see you. Entering the link would be a very bad idea.")
+
+/mob/living/simple_animal/hostile/spawner/nether/attack_hand(mob/user)
+ . = ..()
+ if(isskeleton(user))
+ to_chat(user, "You don't feel like going home yet...")
+ else
+ user.visible_message("[user] is violently pulled into the link!", \
+ "Touching the portal, you are quickly pulled through into a world of unimaginable horror!")
+ contents.Add(user)
+
+/mob/living/simple_animal/hostile/spawner/nether/process()
+ for(var/mob/living/M in contents)
+ if(M)
+ playsound(src, 'sound/magic/demon_consume.ogg', 50, 1)
+ M.adjustBruteLoss(60)
+ new /obj/effect/gibspawner/generic(get_turf(M), M)
+ if(M.stat == DEAD)
+ var/mob/living/simple_animal/hostile/netherworld/blankbody/blank
+ blank = new(loc)
+ blank.name = "[M]"
+ blank.desc = "It's [M], but [M.p_their()] flesh has an ashy texture, and [M.p_their()] face is featureless save an eerie smile."
+ visible_message("[M] reemerges from the link!")
+ qdel(M)
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/spawner.dm b/code/modules/mob/living/simple_animal/spawner.dm
index 38a9c176be7..747c7690150 100644
--- a/code/modules/mob/living/simple_animal/spawner.dm
+++ b/code/modules/mob/living/simple_animal/spawner.dm
@@ -7,7 +7,7 @@
var/max_mobs = 5
var/spawn_delay = 0
var/spawn_time = 300 //30 seconds default
- var/mob_type = /mob/living/simple_animal/hostile/carp
+ var/mob_types = list(/mob/living/simple_animal/hostile/carp)
var/spawn_text = "emerges from"
status_flags = 0
move_resist = MOVE_FORCE_VERY_STRONG
@@ -39,7 +39,8 @@
if(spawn_delay > world.time)
return 0
spawn_delay = world.time + spawn_time
- var/mob/living/simple_animal/L = new mob_type(src.loc)
+ var/chosen_type = pick(mob_types)
+ var/mob/living/simple_animal/L = new chosen_type(loc)
L.admin_spawned = admin_spawned //If we were admin spawned, lets have our children count as that as well.
spawned_mobs += L
L.nest = src
@@ -51,7 +52,7 @@
icon = 'icons/obj/device.dmi'
icon_state = "syndbeacon"
spawn_text = "warps in from"
- mob_type = /mob/living/simple_animal/hostile/syndicate/ranged
+ mob_types = list(/mob/living/simple_animal/hostile/syndicate/ranged)
faction = list("syndicate")
/mob/living/simple_animal/hostile/spawner/mining
@@ -64,21 +65,21 @@
max_mobs = 3
icon = 'icons/mob/nest.dmi'
spawn_text = "crawls out of"
- mob_type = /mob/living/simple_animal/hostile/asteroid/goldgrub
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/goldgrub)
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
faction = list("mining")
/mob/living/simple_animal/hostile/spawner/mining/goliath
name = "goliath den"
desc = "A den housing a nest of goliaths, oh god why?"
- mob_type = /mob/living/simple_animal/hostile/asteroid/goliath
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/goliath)
/mob/living/simple_animal/hostile/spawner/mining/hivelord
name = "hivelord den"
desc = "A den housing a nest of hivelords."
- mob_type = /mob/living/simple_animal/hostile/asteroid/hivelord
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/hivelord)
/mob/living/simple_animal/hostile/spawner/mining/basilisk
name = "basilisk den"
desc = "A den housing a nest of basilisks, bring a coat."
- mob_type = /mob/living/simple_animal/hostile/asteroid/basilisk
+ mob_types = list(/mob/living/simple_animal/hostile/asteroid/basilisk)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index 4e486baba20..02cb948c5c9 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -693,9 +693,13 @@ var/list/slot_equipment_priority = list( \
msg = copytext(msg, 1, MAX_MESSAGE_LEN)
msg = sanitize_simple(html_encode(msg), list("\n" = "
"))
-
- if(mind)
+
+ var/combined = length(memory + msg)
+ if(mind && (combined < MAX_PAPER_MESSAGE_LEN))
mind.store_memory(msg)
+ else if(combined >= MAX_PAPER_MESSAGE_LEN)
+ to_chat(src, "Your brain can't hold that much information!")
+ return
else
to_chat(src, "The game appears to have misplaced your mind datum, so we can't show you your notes.")
@@ -1366,5 +1370,19 @@ var/list/slot_equipment_priority = list( \
/mob/proc/sync_lighting_plane_alpha()
if(hud_used)
var/obj/screen/plane_master/lighting/L = hud_used.plane_masters["[LIGHTING_PLANE]"]
- if (L)
+ if(L)
L.alpha = lighting_alpha
+
+ sync_nightvision_screen() //Sync up the overlay used for nightvision to the amount of see_in_dark a mob has. This needs to be called everywhere sync_lighting_plane_alpha() is.
+
+/mob/proc/sync_nightvision_screen()
+ var/obj/screen/fullscreen/see_through_darkness/S = screens["see_through_darkness"]
+ if(S)
+ var/suffix = ""
+ switch(see_in_dark)
+ if(3 to 8)
+ suffix = "_[see_in_dark]"
+ if(8 to INFINITY)
+ suffix = "_8"
+
+ S.icon_state = "[initial(S.icon_state)][suffix]"
diff --git a/code/modules/nano/modules/nano_module.dm b/code/modules/nano/modules/nano_module.dm
index d33b59528f9..c62067bfaf8 100644
--- a/code/modules/nano/modules/nano_module.dm
+++ b/code/modules/nano/modules/nano_module.dm
@@ -1,6 +1,6 @@
/datum/nano_module
var/name
- var/host
+ var/datum/host
/datum/nano_module/New(var/host)
src.host = host
@@ -8,5 +8,9 @@
/datum/nano_module/nano_host()
return host ? host : src
+/datum/nano_module/on_ui_close(mob/user)
+ if(host)
+ host.on_ui_close(user)
+
/datum/nano_module/proc/can_still_topic(var/datum/topic_state/state = default_state)
return CanUseTopic(usr, state) == STATUS_INTERACTIVE
diff --git a/code/modules/nano/nanoexternal.dm b/code/modules/nano/nanoexternal.dm
index 6864c834f3a..8e7269972fd 100644
--- a/code/modules/nano/nanoexternal.dm
+++ b/code/modules/nano/nanoexternal.dm
@@ -40,6 +40,14 @@
/datum/proc/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/datum/nano_ui/master_ui = null, var/datum/topic_state/state = default_state)
return
+/**
+ * The UI-close proc is called when the UI is closed.
+ *
+ * @param user /mob The mob who was interacting with this UI.
+ */
+/datum/proc/on_ui_close(mob/user)
+ return
+
/**
* The ui_data proc is used to get data for the interface
*
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
index 1dbdcc05b92..5bd0f3827ac 100644
--- a/code/modules/nano/nanoui.dm
+++ b/code/modules/nano/nanoui.dm
@@ -444,6 +444,8 @@ nanoui is used to open and update nano browser uis
for(var/datum/nanoui/child in children)
child.close()
+ src_object.on_ui_close(user)
+
/**
* Set the UI window to call the nanoclose verb when the window is closed
* This allows Nano to handle closed windows
diff --git a/code/modules/projectiles/guns/projectile/revolver.dm b/code/modules/projectiles/guns/projectile/revolver.dm
index 647c690a40d..3cba868a379 100644
--- a/code/modules/projectiles/guns/projectile/revolver.dm
+++ b/code/modules/projectiles/guns/projectile/revolver.dm
@@ -299,6 +299,18 @@
user.apply_damage(300, BRUTE, affecting)
user.visible_message("[user.name] fires [src] at [user.p_their()] head!", "You fire [src] at your head!", "You hear a gunshot!")
+/obj/item/gun/projectile/revolver/russian/soul
+ name = "cursed Russian revolver"
+ desc = "To play with this revolver requires wagering your very soul."
+
+/obj/item/gun/projectile/revolver/russian/soul/shoot_self(mob/living/user)
+ ..()
+ var/obj/item/soulstone/anybody/SS = new /obj/item/soulstone/anybody(get_turf(src))
+ if(!SS.transfer_soul("FORCE", user)) //Something went wrong
+ qdel(SS)
+ return
+ user.visible_message("[user.name]'s soul is captured by \the [src]!", "You've lost the gamble! Your soul is forfeit!")
+
/obj/item/gun/projectile/revolver/capgun
name = "cap gun"
desc = "Looks almost like the real thing! Ages 8 and up."
diff --git a/code/modules/reagents/chemistry/reagents/alcohol.dm b/code/modules/reagents/chemistry/reagents/alcohol.dm
index 6c22360face..6dde9d2a4ec 100644
--- a/code/modules/reagents/chemistry/reagents/alcohol.dm
+++ b/code/modules/reagents/chemistry/reagents/alcohol.dm
@@ -115,6 +115,24 @@
update_flags |= M.adjustToxLoss(1, FALSE)
return list(0, update_flags)
+/datum/reagent/consumable/ethanol/hooch
+ name = "Hooch"
+ id = "hooch"
+ description = "Either someone's failure at cocktail making or attempt in alcohol production. In any case, do you really want to drink that?"
+ color = "#664300" // rgb: 102, 67, 0
+ dizzy_adj = 7
+ alcohol_perc = 1
+ drink_icon = "glass_brown2"
+ drink_name = "Hooch"
+ drink_desc = "You've really hit rock bottom now... your liver packed its bags and left last night."
+ taste_message = "pure resignation"
+
+/datum/reagent/consumable/ethanol/hooch/on_mob_life(mob/living/carbon/M)
+ if(M.mind && M.mind.assigned_role == "Assistant")
+ M.heal_organ_damage(1, 1)
+ . = 1
+ return ..() || .
+
/datum/reagent/consumable/ethanol/rum
name = "Rum"
id = "rum"
@@ -1361,3 +1379,15 @@
taste_message = flavor
if(holder.my_atom)
holder.my_atom.on_reagent_change()
+
+/datum/reagent/consumable/ethanol/bacchus_blessing //An EXTREMELY powerful drink. Smashed in seconds, dead in minutes.
+ name = "Bacchus' Blessing"
+ id = "bacchus_blessing"
+ description = "Unidentifiable mixture. Unmeasurably high alcohol content."
+ color = rgb(51, 19, 3) //Sickly brown
+ dizzy_adj = 21
+ alcohol_perc = 3 //I warned you
+ drink_icon = "glass_brown2"
+ drink_name = "Bacchus' Blessing"
+ drink_desc = "You didn't think it was possible for a liquid to be so utterly revolting. Are you sure about this...?"
+ taste_message = "a wall of bricks"
diff --git a/code/modules/reagents/chemistry/reagents/medicine.dm b/code/modules/reagents/chemistry/reagents/medicine.dm
index 345ac4a9c6f..77118450aaa 100644
--- a/code/modules/reagents/chemistry/reagents/medicine.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine.dm
@@ -290,6 +290,11 @@
addiction_chance = 5
taste_message = "health"
+/datum/reagent/medicine/omnizine/godblood
+ name = "Godblood"
+ description = "Slowly heals all damage types. Has a rather high overdose threshold. Glows with mysterious power."
+ overdose_threshold = 150
+
/datum/reagent/medicine/omnizine/on_mob_life(mob/living/M)
var/update_flags = STATUS_UPDATE_NONE
update_flags |= M.adjustToxLoss(-1*REAGENTS_EFFECT_MULTIPLIER, FALSE)
diff --git a/code/modules/reagents/chemistry/reagents/toxins.dm b/code/modules/reagents/chemistry/reagents/toxins.dm
index 4cf50c9f661..8048c21a283 100644
--- a/code/modules/reagents/chemistry/reagents/toxins.dm
+++ b/code/modules/reagents/chemistry/reagents/toxins.dm
@@ -1174,3 +1174,14 @@
M.electrocute_act(rand(5, 20), "Teslium in their body", 1, TRUE) //Override because it's caused from INSIDE of you
playsound(M, "sparks", 50, 1)
return ..()
+
+/datum/reagent/gluttonytoxin
+ name = "Gluttony's Blessing"
+ id = "gluttonytoxin"
+ description = "An advanced corruptive toxin produced by something terrible."
+ reagent_state = LIQUID
+ color = "#5EFF3B" //RGB: 94, 255, 59
+ taste_message = "decay"
+
+/datum/reagent/gluttonytoxin/reaction_mob(mob/living/L, method=TOUCH, reac_volume)
+ L.ForceContractDisease(new /datum/disease/transformation/morph())
diff --git a/code/modules/reagents/chemistry/recipes/drinks.dm b/code/modules/reagents/chemistry/recipes/drinks.dm
index 2f3eda996b0..5d088bd9ccb 100644
--- a/code/modules/reagents/chemistry/recipes/drinks.dm
+++ b/code/modules/reagents/chemistry/recipes/drinks.dm
@@ -851,4 +851,20 @@
required_reagents = list("rum" = 5, "cream" = 5, "egg" = 5)
result_amount = 15
mix_message = "The eggs nog together. Pretend that \"nog\" is a verb."
- mix_sound = 'sound/goonstation/misc/drinkfizz.ogg'
\ No newline at end of file
+ mix_sound = 'sound/goonstation/misc/drinkfizz.ogg'
+
+/datum/chemical_reaction/hooch
+ name = "Hooch"
+ id = "hooch"
+ result = "hooch"
+ required_reagents = list("ethanol" = 2, "fuel" = 1)
+ result_amount = 3
+ required_catalysts = list("enzyme" = 1)
+
+/datum/chemical_reaction/bacchus_blessing
+ name = "Bacchus' Blessing"
+ id = "bacchus_blessing"
+ result = "bacchus_blessing"
+ required_reagents = list("hooch" = 1, "absinthe" = 1, "manlydorf" = 1, "syndicatebomb" = 1)
+ result_amount = 4
+ mix_message = "The mixture turns to a sickening froth."
\ No newline at end of file
diff --git a/code/modules/reagents/reagent_containers/glass_containers.dm b/code/modules/reagents/reagent_containers/glass_containers.dm
index 17d68f7e607..1d6f46bf46c 100644
--- a/code/modules/reagents/reagent_containers/glass_containers.dm
+++ b/code/modules/reagents/reagent_containers/glass_containers.dm
@@ -73,7 +73,7 @@
if(user.a_intent == INTENT_HARM)
M.visible_message("[user] splashes the contents of [src] onto [M]!", \
"[user] splashes the contents of [src] onto [M]!")
- add_attack_logs(M, user, "Splashed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
+ add_attack_logs(user, M, "Splashed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
if(!iscarbon(user))
M.LAssailant = null
else
@@ -90,11 +90,12 @@
if(!reagents || !reagents.total_volume)
return // The drink might be empty after the delay, such as by spam-feeding
M.visible_message("[user] feeds something to [M].", "[user] feeds something to you.")
- add_attack_logs(M, user, "Fed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
+ add_attack_logs(user, M, "Fed with [name] containing [contained]", !!M.ckey ? null : ATKLOG_ALL)
else
to_chat(user, "You swallow a gulp of [src].")
- reagents.reaction(M, INGEST)
+ var/fraction = min(5 / reagents.total_volume, 1)
+ reagents.reaction(M, INGEST, fraction)
addtimer(CALLBACK(reagents, /datum/reagents.proc/trans_to, M, 5), 5)
playsound(M.loc,'sound/items/drink.ogg', rand(10,50), 1)
else
diff --git a/code/modules/reagents/reagent_containers/syringes.dm b/code/modules/reagents/reagent_containers/syringes.dm
index e28ec4c0cff..0d3d4f17252 100644
--- a/code/modules/reagents/reagent_containers/syringes.dm
+++ b/code/modules/reagents/reagent_containers/syringes.dm
@@ -289,12 +289,6 @@
icon_state = "[rounded_vol]"
item_state = "syringe_[rounded_vol]"
-
-////////////////////////////////////////////////////////////////////////////////
-/// Syringes. END
-////////////////////////////////////////////////////////////////////////////////
-
-
/obj/item/reagent_containers/syringe/antiviral
name = "Syringe (spaceacillin)"
desc = "Contains antiviral agents."
@@ -327,3 +321,10 @@
name = "bioterror syringe"
desc = "Contains several paralyzing reagents."
list_reagents = list("neurotoxin" = 5, "capulettium_plus" = 5, "sodium_thiopental" = 5)
+
+/obj/item/reagent_containers/syringe/gluttony
+ name = "Gluttony's Blessing"
+ desc = "A syringe recovered from a dread place. It probably isn't wise to use."
+ amount_per_transfer_from_this = 1
+ volume = 1
+ list_reagents = list("gluttonytoxin" = 1)
\ No newline at end of file
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index b3c98f82b54..e06a0dcb3fb 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -220,6 +220,7 @@
icon_state = "water_cooler"
anchored = 1
tank_volume = 500
+ reagent_id = "water"
var/paper_cups = 25 //Paper cups left from the cooler
/obj/structure/reagent_dispensers/water_cooler/examine(mob/user)
diff --git a/code/modules/ruins/lavalandruin_code/dead_ratvar.dm b/code/modules/ruins/lavalandruin_code/dead_ratvar.dm
new file mode 100644
index 00000000000..b634aba5c4c
--- /dev/null
+++ b/code/modules/ruins/lavalandruin_code/dead_ratvar.dm
@@ -0,0 +1,248 @@
+// Dead Ratvar
+/obj/structure/dead_ratvar
+ name = "hulking wreck"
+ desc = "The remains of a monstrous war machine."
+ icon = 'icons/obj/lavaland/dead_ratvar.dmi'
+ icon_state = "dead_ratvar"
+ flags = ON_BORDER
+ appearance_flags = 0
+ layer = FLY_LAYER
+ anchored = TRUE
+ density = TRUE
+ bound_width = 416
+ bound_height = 64
+ pixel_y = -10
+ unacidable = TRUE
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF
+
+// An "overlay" used by clockwork walls and floors to appear normal to mesons.
+/obj/effect/clockwork/overlay
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+ var/atom/linked
+
+/obj/effect/clockwork/overlay/examine(mob/user)
+ if(linked)
+ linked.examine(user)
+
+/obj/effect/clockwork/overlay/ex_act()
+ return FALSE
+
+/obj/effect/clockwork/overlay/singularity_act()
+ return
+/obj/effect/clockwork/overlay/singularity_pull()
+ return
+
+/obj/effect/clockwork/overlay/singularity_pull(S, current_size)
+ return
+
+/obj/effect/clockwork/overlay/Destroy()
+ if(linked)
+ linked = null
+ . = ..()
+
+/obj/effect/clockwork/overlay/wall
+ name = "clockwork wall"
+ icon = 'icons/turf/walls/clockwork_wall.dmi'
+ icon_state = "clockwork_wall"
+ canSmoothWith = list(/obj/effect/clockwork/overlay/wall, /obj/structure/falsewall/brass)
+ smooth = SMOOTH_TRUE
+ layer = CLOSED_TURF_LAYER
+
+/obj/effect/clockwork/overlay/wall/Initialize(mapload)
+ . = ..()
+ queue_smooth_neighbors(src)
+ addtimer(CALLBACK(GLOBAL_PROC, .proc/queue_smooth, src), 1)
+
+/obj/effect/clockwork/overlay/wall/Destroy()
+ queue_smooth_neighbors(src)
+ return ..()
+
+/obj/effect/clockwork/overlay/floor
+ icon = 'icons/turf/floors.dmi'
+ icon_state = "clockwork_floor"
+ layer = TURF_LAYER
+ plane = FLOOR_PLANE
+
+/obj/effect/clockwork/overlay/floor/bloodcult //this is used by BLOOD CULT, it shouldn't use such a path...
+ icon_state = "cult"
+
+// Wall gears
+//A massive gear, effectively a girder for clocks.
+/obj/structure/clockwork/wall_gear
+ name = "massive gear"
+ icon = 'icons/obj/clockwork_objects.dmi'
+ icon_state = "wall_gear"
+ climbable = TRUE
+ max_integrity = 100
+ can_deconstruct = TRUE
+ anchored = TRUE
+ density = TRUE
+ unacidable = TRUE
+ resistance_flags = FIRE_PROOF
+ desc = "A massive brass gear. You could probably secure or unsecure it with a wrench, or just climb over it."
+
+/obj/structure/clockwork/wall_gear/displaced
+ anchored = FALSE
+
+/obj/structure/clockwork/wall_gear/Initialize()
+ . = ..()
+ new /obj/effect/temp_visual/ratvar/gear(get_turf(src))
+
+/obj/structure/clockwork/wall_gear/emp_act(severity)
+ return
+
+/obj/structure/clockwork/wall_gear/attackby(obj/item/I, mob/user, params)
+ if(iswrench(I))
+ default_unfasten_wrench(user, I, 10)
+ return 1
+ else if(isscrewdriver(I))
+ if(anchored)
+ to_chat(user, "[src] needs to be unsecured to disassemble it!")
+ else
+ var/obj/item/screwdriver/S = I
+ user.visible_message("[user] starts to disassemble [src].", "You start to disassemble [src]...")
+ if(do_after(user, 30 * S.toolspeed, target = src) && !anchored)
+ playsound(loc, S.usesound, 50, 1)
+ to_chat(user, "You disassemble [src].")
+ deconstruct(TRUE)
+ return 1
+ else if(istype(I, /obj/item/stack/tile/brass))
+ var/obj/item/stack/tile/brass/W = I
+ if(W.get_amount() < 1)
+ to_chat(user, "You need one brass sheet to do this!")
+ return
+ var/turf/T = get_turf(src)
+ if(iswallturf(T))
+ to_chat(user, "There is already a wall present!")
+ return
+ if(!isfloorturf(T))
+ to_chat(user, "A floor must be present to build a [anchored ? "false ":""]wall!")
+ return
+ if(locate(/obj/structure/falsewall) in T.contents)
+ to_chat(user, "There is already a false wall present!")
+ return
+ to_chat(user, "You start adding [W] to [src]...")
+ if(do_after(user, 20, target = src))
+ var/brass_floor = FALSE
+ if(istype(T, /turf/simulated/floor/clockwork)) //if the floor is already brass, costs less to make(conservation of masssssss)
+ brass_floor = TRUE
+ if(W.use(2 - brass_floor))
+ if(anchored)
+ T.ChangeTurf(/turf/simulated/wall/clockwork)
+ else
+ T.ChangeTurf(/turf/simulated/floor/clockwork)
+ new /obj/structure/falsewall/brass(T)
+ qdel(src)
+ else
+ to_chat(user, "You need more brass to make a [anchored ? "false ":""]wall!")
+ return 1
+ return ..()
+
+/obj/structure/clockwork/wall_gear/deconstruct(disassembled = TRUE)
+ if(can_deconstruct && disassembled)
+ new /obj/item/stack/tile/brass(loc, 3)
+ return ..()
+
+//Shards of Alloy, suitable only as a source of power for a replica fabricator.
+/obj/item/clockwork/alloy_shards
+ name = "replicant alloy shards"
+ desc = "Broken shards of some oddly malleable metal. They occasionally move and seem to glow."
+ icon = 'icons/obj/clockwork_objects.dmi'
+ icon_state = "alloy_shards"
+ resistance_flags = LAVA_PROOF | FIRE_PROOF
+ unacidable = TRUE
+ var/randomsinglesprite = FALSE
+ var/randomspritemax = 2
+ var/sprite_shift = 9
+
+/obj/item/clockwork/alloy_shards/Initialize()
+ . = ..()
+ if(randomsinglesprite)
+ replace_name_desc()
+ icon_state = "[icon_state][rand(1, randomspritemax)]"
+ pixel_x = rand(-sprite_shift, sprite_shift)
+ pixel_y = rand(-sprite_shift, sprite_shift)
+
+/obj/item/clockwork/alloy_shards/proc/replace_name_desc()
+ name = "replicant alloy shard"
+ desc = "A broken shard of some oddly malleable metal. It occasionally moves and seems to glow."
+
+/obj/item/clockwork/alloy_shards/clockgolem_remains
+ name = "clockwork golem scrap"
+ desc = "A pile of scrap metal. It seems damaged beyond repair."
+ icon_state = "clockgolem_dead"
+ sprite_shift = 0
+
+/obj/item/clockwork/alloy_shards/large
+ w_class = WEIGHT_CLASS_TINY
+ randomsinglesprite = TRUE
+ icon_state = "shard_large"
+ sprite_shift = 9
+
+/obj/item/clockwork/alloy_shards/medium
+ w_class = WEIGHT_CLASS_TINY
+ randomsinglesprite = TRUE
+ icon_state = "shard_medium"
+ sprite_shift = 10
+
+/obj/item/clockwork/alloy_shards/medium/gear_bit
+ randomspritemax = 4
+ icon_state = "gear_bit"
+ sprite_shift = 12
+
+/obj/item/clockwork/alloy_shards/medium/gear_bit/replace_name_desc()
+ name = "gear bit"
+ desc = "A broken chunk of a gear. You want it."
+
+/obj/item/clockwork/alloy_shards/medium/gear_bit/large //gives more power
+
+/obj/item/clockwork/alloy_shards/medium/gear_bit/large/replace_name_desc()
+ ..()
+ name = "complex gear bit"
+
+/obj/item/clockwork/alloy_shards/small
+ w_class = WEIGHT_CLASS_TINY
+ randomsinglesprite = TRUE
+ randomspritemax = 3
+ icon_state = "shard_small"
+ sprite_shift = 12
+
+/obj/item/clockwork/alloy_shards/pinion_lock
+ name = "pinion lock"
+ desc = "A dented and scratched gear. It's very heavy."
+ icon_state = "pinion_lock"
+
+/obj/item/clockwork/component/belligerent_eye
+ name = "belligerent eye"
+ desc = "A brass construct with a rotating red center. It's as though it's looking for something to hurt."
+ icon = 'icons/obj/clockwork_objects.dmi'
+ icon_state = "belligerent_eye"
+
+/obj/item/clockwork/component/belligerent_eye/blind_eye
+ name = "blind eye"
+ desc = "A heavy brass eye, its red iris fallen dark."
+ icon_state = "blind_eye"
+ w_class = WEIGHT_CLASS_NORMAL
+
+/obj/item/clockwork/component/geis_capacitor/fallen_armor
+ name = "fallen armor"
+ desc = "Lifeless chunks of armor. They're designed in a strange way and won't fit on you."
+ icon = 'icons/obj/clockwork_objects.dmi'
+ icon_state = "fallen_armor"
+ w_class = WEIGHT_CLASS_NORMAL
+
+//Ratvarian spear
+/obj/item/clockwork/weapon/ratvarian_spear
+ name = "ratvarian spear"
+ desc = "A razor-sharp spear made of brass. It thrums with barely-contained energy."
+ icon = 'icons/obj/clockwork_objects.dmi'
+ icon_state = "ratvarian_spear"
+ item_state = "ratvarian_spear"
+ force = 15 //Extra damage is dealt to targets in attack()
+ throwforce = 25
+ armour_penetration = 10
+ sharp = TRUE
+ attack_verb = list("stabbed", "poked", "slashed")
+ hitsound = 'sound/weapons/bladeslice.ogg'
+ w_class = WEIGHT_CLASS_BULKY
+ var/bonus_burn = 5
diff --git a/code/modules/ruins/lavalandruin_code/fountain_hall.dm b/code/modules/ruins/lavalandruin_code/fountain_hall.dm
new file mode 100644
index 00000000000..917ea4e4bd0
--- /dev/null
+++ b/code/modules/ruins/lavalandruin_code/fountain_hall.dm
@@ -0,0 +1,49 @@
+/obj/structure/sacrificealtar
+ name = "sacrificial altar"
+ desc = "An altar designed to perform blood sacrifice for a deity."
+ icon = 'icons/obj/hand_of_god_structures.dmi'
+ icon_state = "sacrificealtar"
+ anchored = TRUE
+ density = FALSE
+ can_buckle = TRUE
+
+/obj/structure/sacrificealtar/attack_hand(mob/living/user)
+ if(user.incapacitated())
+ return
+ if(!has_buckled_mobs())
+ return
+ var/mob/living/L = buckled_mob
+ if(!L)
+ return
+ to_chat(user, "You attempt to sacrifice [L] by invoking the sacrificial ritual.")
+ L.gib()
+ message_admins("[ADMIN_LOOKUPFLW(user)] has sacrificed [key_name_admin(L)] on the sacrificial altar at [AREACOORD(src)].")
+
+/obj/structure/healingfountain
+ name = "healing fountain"
+ desc = "A fountain containing the waters of life."
+ icon = 'icons/obj/hand_of_god_structures.dmi'
+ icon_state = "fountain"
+ anchored = TRUE
+ density = TRUE
+ var/time_between_uses = 1800
+ var/last_process = 0
+
+/obj/structure/healingfountain/attack_hand(mob/living/user)
+ . = ..()
+ if(.)
+ return
+ if(last_process + time_between_uses > world.time)
+ to_chat(user, "The fountain appears to be empty.")
+ return
+ last_process = world.time
+ to_chat(user, "The water feels warm and soothing as you touch it. The fountain immediately dries up shortly afterwards.")
+ user.reagents.add_reagent(/datum/reagent/medicine/omnizine/godblood, 20)
+ update_icon()
+ addtimer(CALLBACK(src, .proc/update_icon), time_between_uses)
+
+/obj/structure/healingfountain/update_icon()
+ if(last_process + time_between_uses > world.time)
+ icon_state = "fountain"
+ else
+ icon_state = "fountain-red"
\ No newline at end of file
diff --git a/code/modules/ruins/lavalandruin_code/pizzaparty.dm b/code/modules/ruins/lavalandruin_code/pizzaparty.dm
new file mode 100644
index 00000000000..3201652c371
--- /dev/null
+++ b/code/modules/ruins/lavalandruin_code/pizzaparty.dm
@@ -0,0 +1,11 @@
+/obj/effect/spawner/lootdrop/pizzaparty
+ name = "pizza bomb spawner"
+ loot = list(/obj/item/pizzabox/margherita = 3,
+ /obj/item/pizzabox/meat = 3,
+ /obj/item/pizzabox/mushroom = 3,
+ /obj/item/pizza_bomb = 1)
+ lootdoubles = FALSE
+
+/obj/structure/reagent_dispensers/water_cooler/pizzaparty
+ name = "punch cooler"
+ reagent_id = "bacchus_blessing"
\ No newline at end of file
diff --git a/code/modules/ruins/lavalandruin_code/puzzle.dm b/code/modules/ruins/lavalandruin_code/puzzle.dm
new file mode 100644
index 00000000000..44383f11a0a
--- /dev/null
+++ b/code/modules/ruins/lavalandruin_code/puzzle.dm
@@ -0,0 +1,351 @@
+/obj/effect/sliding_puzzle
+ name = "Sliding puzzle generator"
+ icon = 'icons/obj/items.dmi' //mapping
+ icon_state = "syndballoon"
+ invisibility = INVISIBILITY_ABSTRACT
+ anchored = TRUE
+ var/list/elements
+ var/floor_type = /turf/simulated/floor/vault
+ var/finished = FALSE
+ var/reward_type = /obj/item/reagent_containers/food/snacks/cookie
+ var/element_type = /obj/structure/puzzle_element
+ var/auto_setup = TRUE
+ var/empty_tile_id
+
+//Gets the turf where the tile with given id should be
+/obj/effect/sliding_puzzle/proc/get_turf_for_id(id)
+ var/turf/center = get_turf(src)
+ switch(id)
+ if(1)
+ return get_step(center,NORTHWEST)
+ if(2)
+ return get_step(center,NORTH)
+ if(3)
+ return get_step(center,NORTHEAST)
+ if(4)
+ return get_step(center,WEST)
+ if(5)
+ return center
+ if(6)
+ return get_step(center,EAST)
+ if(7)
+ return get_step(center,SOUTHWEST)
+ if(8)
+ return get_step(center,SOUTH)
+ if(9)
+ return get_step(center,SOUTHEAST)
+
+/obj/effect/sliding_puzzle/Initialize(mapload)
+ ..()
+ return INITIALIZE_HINT_LATELOAD
+
+/obj/effect/sliding_puzzle/LateInitialize()
+ if(auto_setup)
+ setup()
+
+/obj/effect/sliding_puzzle/proc/check_setup_location()
+ for(var/id in 1 to 9)
+ var/turf/T = get_turf_for_id(id)
+ if(!T)
+ return FALSE
+ if(istype(T, /turf/simulated/wall/indestructible) || istype(T, /turf/simulated/floor/indestructible))
+ return FALSE
+ return TRUE
+
+
+/obj/effect/sliding_puzzle/proc/validate()
+ if(finished)
+ return
+
+ if(elements.len < 8) //Someone broke it
+ qdel(src)
+
+ //Check if everything is in place
+ for(var/id in 1 to 9)
+ var/target_turf = get_turf_for_id(id)
+ var/obj/structure/puzzle_element/E = locate() in target_turf
+ if(id == empty_tile_id && !E) // This location should be empty.
+ continue
+ if(!E || E.id != id) //wrong tile or no tile at all
+ return
+ //Ding ding
+ finish()
+
+/obj/effect/sliding_puzzle/Destroy()
+ if(LAZYLEN(elements))
+ for(var/obj/structure/puzzle_element/E in elements)
+ E.source = null
+ elements.Cut()
+ return ..()
+
+#define COLLAPSE_DURATION 7
+
+/obj/effect/sliding_puzzle/proc/finish()
+ finished = TRUE
+ for(var/mob/M in range(7,src))
+ shake_camera(M, COLLAPSE_DURATION , 1)
+ for(var/obj/structure/puzzle_element/E in elements)
+ E.collapse()
+
+ dispense_reward()
+
+/obj/effect/sliding_puzzle/proc/dispense_reward()
+ new reward_type(get_turf(src))
+
+/obj/effect/sliding_puzzle/proc/is_solvable()
+ var/list/current_ordering = list()
+ for(var/obj/structure/puzzle_element/E in elements_in_order())
+ current_ordering += E.id
+
+ var/swap_tally = 0
+ for(var/i in 1 to current_ordering.len)
+ var/checked_value = current_ordering[i]
+ for(var/j in i to current_ordering.len)
+ if(current_ordering[j] < checked_value)
+ swap_tally++
+
+ return swap_tally % 2 == 0
+
+//swap two tiles in same row
+/obj/effect/sliding_puzzle/proc/make_solvable()
+ var/first_tile_id = 1
+ var/other_tile_id = 2
+ if(empty_tile_id == 1 || empty_tile_id == 2) //Can't swap with empty one so just grab some in second row
+ first_tile_id = 4
+ other_tile_id = 5
+
+ var/turf/T1 = get_turf_for_id(first_tile_id)
+ var/turf/T2 = get_turf_for_id(other_tile_id)
+
+ var/obj/structure/puzzle_element/E1 = locate() in T1
+ var/obj/structure/puzzle_element/E2 = locate() in T2
+
+ E1.forceMove(T2)
+ E2.forceMove(T1)
+
+/proc/cmp_xy_desc(atom/movable/A, atom/movable/B)
+ if(A.y > B.y)
+ return -1
+ if(A.y < B.y)
+ return 1
+ if(A.x > B.x)
+ return 1
+ if(A.x < B.x)
+ return -1
+ return 0
+
+/obj/effect/sliding_puzzle/proc/elements_in_order()
+ return sortTim(elements,cmp=/proc/cmp_xy_desc)
+
+/obj/effect/sliding_puzzle/proc/get_base_icon()
+ var/icon/I = new('icons/obj/puzzle.dmi')
+ var/list/puzzles = icon_states(I)
+ var/puzzle_state = pick(puzzles)
+ var/icon/P = new('icons/obj/puzzle.dmi',puzzle_state)
+ return P
+
+/obj/effect/sliding_puzzle/proc/setup()
+ //First we slice the 96x96 icon into 32x32 pieces
+ var/list/puzzle_pieces = list() //id -> icon list
+
+ var/width = 3
+ var/height = 3
+ var/list/left_ids = list()
+ var/tile_count = width * height
+
+ //Generate per tile icons
+ for(var/id in 1 to tile_count)
+ var/y = width - round((id - 1) / width)
+ var/x = ((id - 1) % width) + 1
+
+ var/x_start = 1 + (x - 1) * world.icon_size
+ var/x_end = x_start + world.icon_size - 1
+ var/y_start = 1 + ((y - 1) * world.icon_size)
+ var/y_end = y_start + world.icon_size - 1
+
+ var/icon/T = get_base_icon()
+ T.Crop(x_start,y_start,x_end,y_end)
+ puzzle_pieces["[id]"] = T
+ left_ids += id
+
+ //Setup random empty tile
+ empty_tile_id = pick_n_take(left_ids)
+ var/turf/empty_tile_turf = get_turf_for_id(empty_tile_id)
+ empty_tile_turf.ChangeTurf(floor_type, keep_icon = FALSE, ignore_air = FALSE)
+ var/mutable_appearance/MA = new(puzzle_pieces["[empty_tile_id]"])
+ MA.layer = empty_tile_turf.layer + 0.1
+ empty_tile_turf.add_overlay(MA)
+
+ elements = list()
+ var/list/empty_spots = left_ids.Copy()
+ for(var/spot_id in empty_spots)
+ var/turf/T = get_turf_for_id(spot_id)
+ T = T.ChangeTurf(floor_type, keep_icon = FALSE, ignore_air = FALSE)
+ var/obj/structure/puzzle_element/E = new element_type(T)
+ elements += E
+ var/chosen_id = pick_n_take(left_ids)
+ E.puzzle_icon = puzzle_pieces["[chosen_id]"]
+ E.source = src
+ E.id = chosen_id
+ E.set_puzzle_icon()
+
+ if(!is_solvable())
+ make_solvable()
+
+/obj/structure/puzzle_element
+ name = "mysterious pillar"
+ desc = "puzzling..."
+ icon = 'icons/obj/lavaland/artefacts.dmi'
+ icon_state = "puzzle_pillar"
+ anchored = FALSE
+ density = TRUE
+ var/id = 0
+ var/obj/effect/sliding_puzzle/source
+ var/icon/puzzle_icon
+
+/obj/structure/puzzle_element/Move(nloc, dir)
+ if(!isturf(nloc) || moving_diagonally || get_dist(get_step(src,dir),get_turf(source)) > 1)
+ return 0
+ else
+ return ..()
+
+/obj/structure/puzzle_element/proc/set_puzzle_icon()
+ cut_overlays()
+ if(puzzle_icon)
+ //Need to scale it down a bit to fit the static border
+ var/icon/C = new(puzzle_icon)
+ C.Scale(19,19)
+ var/mutable_appearance/puzzle_small = new(C)
+ puzzle_small.layer = layer + 0.1
+ puzzle_small.pixel_x = 7
+ puzzle_small.pixel_y = 7
+ add_overlay(puzzle_small)
+
+/obj/structure/puzzle_element/Destroy()
+ if(source)
+ source.elements -= src
+ source.validate()
+ return ..()
+
+//Set the full image on the turf and delete yourself
+/obj/structure/puzzle_element/proc/collapse()
+ var/turf/T = get_turf(src)
+ var/mutable_appearance/MA = new(puzzle_icon)
+ MA.layer = T.layer + 0.1
+ T.add_overlay(MA)
+ //Some basic shaking animation
+ for(var/i in 1 to COLLAPSE_DURATION)
+ animate(src, pixel_x=rand(-5,5), pixel_y=rand(-2,2), time=1)
+ QDEL_IN(src,COLLAPSE_DURATION)
+
+/obj/structure/puzzle_element/Moved()
+ . = ..()
+ source.validate()
+
+//Admin abuse version so you can pick the icon before it sets up
+/obj/effect/sliding_puzzle/admin
+ auto_setup = FALSE
+ var/icon/puzzle_icon
+ var/puzzle_state
+
+/obj/effect/sliding_puzzle/admin/get_base_icon()
+ var/icon/I = new(puzzle_icon,puzzle_state)
+ return I
+
+//Ruin version
+/obj/effect/sliding_puzzle/lavaland
+ reward_type = /obj/structure/closet/crate/necropolis/puzzle
+
+/obj/effect/sliding_puzzle/lavaland/dispense_reward()
+ if(prob(25))
+ //If it's not roaming somewhere else already.
+ var/mob/living/simple_animal/hostile/megafauna/bubblegum/B = locate() in GLOB.mob_list
+ if(!B)
+ reward_type = /mob/living/simple_animal/hostile/megafauna/bubblegum
+ return ..()
+
+//Prison cube version
+/obj/effect/sliding_puzzle/prison
+ auto_setup = FALSE //This will be done by cube proc
+ var/mob/living/prisoner
+ element_type = /obj/structure/puzzle_element/prison
+
+/obj/effect/sliding_puzzle/prison/get_base_icon()
+ if(!prisoner)
+ CRASH("Prison cube without prisoner")
+ prisoner.setDir(SOUTH)
+ var/icon/I = getFlatIcon(prisoner)
+ I.Scale(96,96)
+ return I
+
+/obj/effect/sliding_puzzle/prison/Destroy()
+ if(prisoner)
+ to_chat(prisoner,"With the cube broken by force, you can feel your body falling apart.")
+ prisoner.death()
+ qdel(prisoner)
+ . = ..()
+
+/obj/effect/sliding_puzzle/prison/dispense_reward()
+ prisoner.forceMove(get_turf(src))
+ prisoner.notransform = FALSE
+ prisoner = null
+
+//Some armor so it's harder to kill someone by mistake.
+/obj/structure/puzzle_element/prison
+ armor = list("melee" = 50, "bullet" = 50, "laser" = 50, "energy" = 50, "bomb" = 50, "bio" = 50, "rad" = 50, "fire" = 50, "acid" = 50)
+
+/obj/structure/puzzle_element/prison/relaymove(mob/user)
+ return
+
+/obj/item/prisoncube
+ name = "Prison Cube"
+ desc = "Dusty cube with humanoid imprint on it."
+ icon = 'icons/obj/lavaland/artefacts.dmi'
+ icon_state = "prison_cube"
+
+/obj/item/prisoncube/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || !isliving(target))
+ return
+ var/mob/living/victim = target
+ var/mob/living/carbon/carbon_victim = victim
+ //Handcuffed or unconcious
+ if(istype(carbon_victim) && carbon_victim.handcuffed || victim.stat != CONSCIOUS)
+ if(!puzzle_imprison(target))
+ to_chat(user,"[src] does nothing.")
+ return
+ to_chat(user,"You trap [victim] in the prison cube!")
+ qdel(src)
+ else
+ to_chat(user,"[src] only accepts restrained or unconcious prisoners.")
+
+/proc/puzzle_imprison(mob/living/prisoner)
+ var/turf/T = get_turf(prisoner)
+ var/obj/effect/sliding_puzzle/prison/cube = new(T)
+ if(!cube.check_setup_location())
+ qdel(cube)
+ return FALSE
+
+ //First grab the prisoner and move them temporarily into the generator so they won't get thrown around.
+ prisoner.notransform = TRUE
+ prisoner.forceMove(cube)
+ to_chat(prisoner,"You're trapped by the prison cube! You will remain trapped until someone solves it.")
+
+ //Clear the area from objects (and cube user)
+ var/list/things_to_throw = list()
+ for(var/atom/movable/AM in range(1,T))
+ if(!AM.anchored)
+ things_to_throw += AM
+
+ for(var/atom/movable/AM in things_to_throw)
+ var/throwtarget = get_edge_target_turf(T, get_dir(T, get_step_away(AM, T)))
+ AM.throw_at(throwtarget, 2, 3)
+
+ //Create puzzle itself
+ cube.prisoner = prisoner
+ cube.setup()
+
+ //Move them into random block
+ var/obj/structure/puzzle_element/E = pick(cube.elements)
+ prisoner.forceMove(E)
+ return TRUE
\ No newline at end of file
diff --git a/code/modules/ruins/lavalandruin_code/sin_ruins.dm b/code/modules/ruins/lavalandruin_code/sin_ruins.dm
new file mode 100644
index 00000000000..82fbd410ea1
--- /dev/null
+++ b/code/modules/ruins/lavalandruin_code/sin_ruins.dm
@@ -0,0 +1,156 @@
+//These objects are used in the cardinal sin-themed ruins (i.e. Gluttony, Pride...)
+
+// Greed
+/obj/structure/cursed_slot_machine //Greed's slot machine: Used in the Greed ruin. Deals clone damage on each use, with a successful use giving a d20 of fate.
+ name = "greed's slot machine"
+ desc = "High stakes, high rewards."
+ icon = 'icons/obj/economy.dmi'
+ icon_state = "slots-off"
+ anchored = TRUE
+ density = TRUE
+ var/win_prob = 5
+
+/obj/structure/cursed_slot_machine/attack_hand(mob/user)
+ interact(user)
+
+/obj/structure/cursed_slot_machine/interact(mob/living/carbon/human/user)
+ if(!istype(user))
+ return
+
+ if(in_use)
+ return
+
+ in_use = TRUE
+ user.adjustCloneLoss(20)
+ if(user.stat)
+ to_chat(user, "No... just one more try...")
+ user.gib()
+ else
+ user.visible_message("[user] pulls [src]'s lever with a glint in [user.p_their()] eyes!", "You feel a draining as you pull the lever, but you \
+ know it'll be worth it.")
+ icon_state = "slots-on"
+ playsound(src, 'sound/lavaland/cursed_slot_machine.ogg', 50, 0)
+ addtimer(CALLBACK(src, .proc/determine_victor, user), 50)
+
+/obj/structure/cursed_slot_machine/proc/determine_victor(mob/living/user)
+ icon_state = "slots-off"
+ in_use = FALSE
+ if(prob(win_prob))
+ playsound(src, 'sound/lavaland/cursed_slot_machine_jackpot.ogg', 50, 0)
+ new/obj/structure/cursed_money(get_turf(src))
+ if(user)
+ to_chat(user, "You've hit jackpot. Laughter echoes around you as your reward appears in the machine's place.")
+ qdel(src)
+ else
+ if(user)
+ to_chat(user, "Fucking machine! Must be rigged. Still... one more try couldn't hurt, right?")
+
+/obj/structure/cursed_money
+ name = "bag of money"
+ desc = "RICH! YES! YOU KNEW IT WAS WORTH IT! YOU'RE RICH! RICH! RICH!"
+ icon = 'icons/obj/storage.dmi'
+ icon_state = "moneybag"
+ anchored = FALSE
+ density = TRUE
+
+/obj/structure/cursed_money/Initialize()
+ . = ..()
+ addtimer(CALLBACK(src, .proc/collapse), 600)
+
+/obj/structure/cursed_money/proc/collapse()
+ visible_message("[src] falls in on itself, \
+ canvas rotting away and contents vanishing.")
+ qdel(src)
+
+/obj/structure/cursed_money/attack_hand(mob/living/user)
+ . = ..()
+ if(.)
+ return .
+
+ user.visible_message("[user] opens the bag and \
+ and removes a die. The bag then vanishes.",
+ "You open the bag...!\n\
+ And see a bag full of dice. Confused, \
+ you take one... and the bag vanishes.")
+ var/turf/T = get_turf(user)
+ var/obj/item/dice/d20/fate/one_use/critical_fail = new(T)
+ user.put_in_hands(critical_fail)
+ qdel(src)
+
+// Gluttony
+/obj/effect/gluttony //Gluttony's wall: Used in the Gluttony ruin. Only lets the overweight through.
+ name = "gluttony's wall"
+ desc = "Only those who truly indulge may pass."
+ anchored = TRUE
+ density = TRUE
+ icon_state = "blob"
+ icon = 'icons/mob/blob.dmi'
+ color = rgb(145, 150, 0)
+
+/obj/effect/gluttony/CanPass(atom/movable/mover, turf/target)//So bullets will fly over and stuff.
+ if(ishuman(mover))
+ var/mob/living/carbon/human/H = mover
+ if(H.nutrition >= NUTRITION_LEVEL_FAT || (FAT in H.mutations))
+ H.visible_message("[H] pushes through [src]!", "You've seen and eaten worse than this.")
+ return TRUE
+ else
+ to_chat(H, "You're repulsed by even looking at [src]. Only a pig could force themselves to go through it.")
+ if(istype(mover, /mob/living/simple_animal/hostile/morph))
+ return TRUE
+ else
+ return FALSE
+
+// Pride
+/obj/structure/mirror/magic/pride //Pride's mirror: Used in the Pride ruin.
+ name = "pride's mirror"
+ desc = "Pride cometh before the..."
+ icon_state = "magic_mirror"
+
+/obj/structure/mirror/magic/pride/curse(mob/user)
+ user.visible_message("The ground splits beneath [user] as [user.p_their()] hand leaves the mirror!", \
+ "Perfect. Much better! Now nobody will be able to resist yo-")
+
+ var/turf/T = get_turf(user)
+ var/list/levels = space_manager.z_list.Copy()
+ for(var/level in levels)
+ if(!is_teleport_allowed(level))
+ levels -= level
+
+ T.ChangeTurf(/turf/simulated/floor/chasm)
+ var/turf/simulated/floor/chasm/C = T
+ C.drop_x = T.x
+ C.drop_y = T.y
+ C.drop_z = pick(levels)
+ C.drop(user)
+
+// Envy
+/obj/item/kitchen/knife/envy //Envy's knife: Found in the Envy ruin. Attackers take on the appearance of whoever they strike.
+ name = "envy's knife"
+ desc = "Their success will be yours."
+ icon = 'icons/obj/wizard.dmi'
+ icon_state = "render"
+ item_state = "knife"
+ force = 18
+ throwforce = 10
+ w_class = WEIGHT_CLASS_NORMAL
+ hitsound = 'sound/weapons/bladeslice.ogg'
+
+/obj/item/kitchen/knife/envy/afterattack(atom/movable/AM, mob/living/carbon/human/user, proximity)
+ . = ..()
+ if(!proximity)
+ return
+ if(!istype(user))
+ return
+ if(ishuman(AM))
+ var/mob/living/carbon/human/H = AM
+ if(user.real_name != H.dna.real_name)
+ user.real_name = H.dna.real_name
+ H.dna.transfer_identity(user)
+ user.visible_message("[user]'s appearance shifts into [H]'s!", \
+ "[H.p_they(TRUE)] think[H.p_s()] [H.p_theyre()] sooo much better than you. Not anymore, [H.p_they()] won't.")
+
+// Sloth
+/obj/item/paper/fluff/stations/lavaland/sloth/note
+ name = "note from sloth"
+ icon_state = "paper_words"
+ info = "have not gotten around to finishing my cursed item yet sorry - sloth"
diff --git a/config/example/config.txt b/config/example/config.txt
index 87ed0ce5cf1..89b8767feab 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -85,6 +85,16 @@ LOG_WORLD_OUTPUT
## log admin warning messages
LOG_ADMINWARN
+
+## Amount of minutes that a person has to be AFK before he will be warned by the AFK subsystem. Leave this 0 to prevent the subsystem from activating
+WARN_AFK_MINIMUM 0
+
+## Amount of minutes that a person has to be AFK before he will be cryod by the AFK subsystem. Leave this 0 to prevent the subsystem from activating
+AUTO_CRYO_AFK 0
+
+## Amount of minutes that a person has to be AFK before he will be despawned by the AFK subsystem. Leave this 0 to prevent the subsystem from activating
+AUTO_DESPAWN_AFK 0
+
## probablities for game modes chosen in "secret" and "random" modes
##
## default probablity is 1, increase to make that mode more likely to be picked
diff --git a/config/example/dbconfig.txt b/config/example/dbconfig.txt
index fc1bced9d6a..2404119d52f 100644
--- a/config/example/dbconfig.txt
+++ b/config/example/dbconfig.txt
@@ -9,7 +9,7 @@
## This value must be set to the version of the paradise schema in use.
## If this value does not match, the SQL database will not be loaded and an error will be generated.
## Roundstart will be delayed.
-DB_VERSION 7
+DB_VERSION 8
## Server the MySQL database can be found at.
# Examples: localhost, 200.135.5.43, www.mysqldb.com, etc.
diff --git a/config/example/lavaRuinBlacklist.txt b/config/example/lavaRuinBlacklist.txt
index ee3f0245a07..934d86514b0 100644
--- a/config/example/lavaRuinBlacklist.txt
+++ b/config/example/lavaRuinBlacklist.txt
@@ -8,17 +8,33 @@
##RESPAWN
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_ash_walker1.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_seed_vault.dmm
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_syndicate_base1.dmm
##SIN
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_envy.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_gluttony.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_greed.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_pride.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_sloth.dmm
-
-##MISC
-#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_animal_hospital.dmm
+##MEGAFAUNA
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_blooddrunk1.dmm
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_blooddrunk2.dmm
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_blooddrunk3.dmm
-#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_hermit.dmm
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_hierophant.dmm
#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_swarmer_crash.dmm
+
+##MISC
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_animal_hospital.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_cube.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_cultaltar.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_dead_ratvar.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_fountain_hell.dmm"
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_pizza_party.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_puzzle.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_random_ripley.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_survivalpod.dmm
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_ufo_crash.dmm"
+#_maps/map_files/RandomRuins/LavaRuins/lavaland_surface_xeno_nest.dmm"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-11484.yml b/html/changelogs/AutoChangeLog-pr-11484.yml
new file mode 100644
index 00000000000..a6237397de8
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11484.yml
@@ -0,0 +1,4 @@
+author: "KasparoVy"
+delete-after: True
+changes:
+ - rscadd: "Re-adds the ability to see in the dark. Adds overlays for each level of darksight (>=8,7,6,5,4,3,<=2)."
diff --git a/html/changelogs/AutoChangeLog-pr-11695.yml b/html/changelogs/AutoChangeLog-pr-11695.yml
new file mode 100644
index 00000000000..d3208ba926b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11695.yml
@@ -0,0 +1,4 @@
+author: "Markolie"
+delete-after: True
+changes:
+ - rscadd: "All remaining Lavaland ruins have been ported over from /tg/."
diff --git a/html/changelogs/AutoChangeLog-pr-11697.yml b/html/changelogs/AutoChangeLog-pr-11697.yml
new file mode 100644
index 00000000000..03370b18a45
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11697.yml
@@ -0,0 +1,8 @@
+author: "Markolie"
+delete-after: True
+changes:
+ - rscadd: "Tribal splints have been added to the game, which can be crafted with two bones and one piece of sinew."
+ - rscadd: "The Lavaland Syndicate base now has a defibrillator and mounted defibrillator. The animal hospital now has a mounted defibrillator."
+ - tweak: "The Ash Walker storage area now comes with an advanced medkit instead of a regular one and one set of medical splints. It also comes with aloe vera, comfrey and wheat seeds."
+ - tweak: "The water tank in the Ash Walker nest has been replaced with a puddle."
+ - tweak: "The items in vending machines on the beach ruin and animal hospital are now free."
diff --git a/html/changelogs/AutoChangeLog-pr-11732.yml b/html/changelogs/AutoChangeLog-pr-11732.yml
new file mode 100644
index 00000000000..8f44fc99dae
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11732.yml
@@ -0,0 +1,4 @@
+author: "farie82"
+delete-after: True
+changes:
+ - rscadd: "Adds the AFK auto cryo system. By default it won't affect players unless they activate it themselves by setting the preference in their game preferences tab."
diff --git a/html/changelogs/AutoChangeLog-pr-11825.yml b/html/changelogs/AutoChangeLog-pr-11825.yml
new file mode 100644
index 00000000000..ca3de2da45c
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11825.yml
@@ -0,0 +1,4 @@
+author: "Ty-Omaha"
+delete-after: True
+changes:
+ - tweak: "Reworded permanent bans to non-expiring bans."
diff --git a/html/changelogs/AutoChangeLog-pr-11836.yml b/html/changelogs/AutoChangeLog-pr-11836.yml
new file mode 100644
index 00000000000..24758efc273
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11836.yml
@@ -0,0 +1,4 @@
+author: "TheSardele"
+delete-after: True
+changes:
+ - tweak: "Lowers throwforce of drinking cartons from 15 to 0"
diff --git a/html/changelogs/AutoChangeLog-pr-11851.yml b/html/changelogs/AutoChangeLog-pr-11851.yml
new file mode 100644
index 00000000000..47a8eb82c03
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11851.yml
@@ -0,0 +1,4 @@
+author: "Allfd"
+delete-after: True
+changes:
+ - tweak: "Panthers can now see in the dark."
diff --git a/html/changelogs/AutoChangeLog-pr-11852.yml b/html/changelogs/AutoChangeLog-pr-11852.yml
new file mode 100644
index 00000000000..5d94998f148
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11852.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "fixes the runtime caused by running keyloop for clients"
diff --git a/html/changelogs/AutoChangeLog-pr-11856.yml b/html/changelogs/AutoChangeLog-pr-11856.yml
new file mode 100644
index 00000000000..cefc38d19db
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11856.yml
@@ -0,0 +1,5 @@
+author: "Markolie"
+delete-after: True
+changes:
+ - bugfix: "Drinking from a beaker now only applies the effect of five units of the ingested chemical, instead of the entire volume of the beaker."
+ - bugfix: "Resolved an issue where beaker attack logs were reversed."
diff --git a/html/changelogs/AutoChangeLog-pr-11861.yml b/html/changelogs/AutoChangeLog-pr-11861.yml
new file mode 100644
index 00000000000..c430f657cbf
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11861.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "Preferences not saving properly"
diff --git a/html/changelogs/AutoChangeLog-pr-11867.yml b/html/changelogs/AutoChangeLog-pr-11867.yml
new file mode 100644
index 00000000000..f18b03fc944
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11867.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - rscadd: "Restore hotkey mode"
diff --git a/html/changelogs/AutoChangeLog-pr-11872.yml b/html/changelogs/AutoChangeLog-pr-11872.yml
new file mode 100644
index 00000000000..066c6bb27ea
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11872.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "Q no longer drops items as a cyborg on AZERTY mode"
diff --git a/html/changelogs/AutoChangeLog-pr-11873.yml b/html/changelogs/AutoChangeLog-pr-11873.yml
new file mode 100644
index 00000000000..40626a2525b
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11873.yml
@@ -0,0 +1,4 @@
+author: "SteelSlayer"
+delete-after: True
+changes:
+ - bugfix: "The AI's robot control window now allows you to see and interact with available bots again"
diff --git a/html/changelogs/AutoChangeLog-pr-11886.yml b/html/changelogs/AutoChangeLog-pr-11886.yml
new file mode 100644
index 00000000000..e0d2c5fe116
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11886.yml
@@ -0,0 +1,4 @@
+author: "TheSardele"
+delete-after: True
+changes:
+ - bugfix: "It is no longer possible to raise zero to -infinity fingers using the *signal emote"
diff --git a/html/changelogs/AutoChangeLog-pr-11920.yml b/html/changelogs/AutoChangeLog-pr-11920.yml
new file mode 100644
index 00000000000..224cb3173f7
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11920.yml
@@ -0,0 +1,4 @@
+author: "Couls"
+delete-after: True
+changes:
+ - bugfix: "typing indicators show up again"
diff --git a/html/changelogs/AutoChangeLog-pr-11933.yml b/html/changelogs/AutoChangeLog-pr-11933.yml
new file mode 100644
index 00000000000..29692d6a1d7
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11933.yml
@@ -0,0 +1,4 @@
+author: "and DominikPanic"
+delete-after: True
+changes:
+ - bugfix: "Limits IC notes"
diff --git a/html/changelogs/AutoChangeLog-pr-11935.yml b/html/changelogs/AutoChangeLog-pr-11935.yml
new file mode 100644
index 00000000000..2c480fcd02f
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11935.yml
@@ -0,0 +1,4 @@
+author: "Ty-Omaha"
+delete-after: True
+changes:
+ - tweak: "Gave plastic surgery to line 364 of atoms.dm"
diff --git a/html/changelogs/AutoChangeLog-pr-11938.yml b/html/changelogs/AutoChangeLog-pr-11938.yml
new file mode 100644
index 00000000000..80b8fe04530
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11938.yml
@@ -0,0 +1,4 @@
+author: "farie82"
+delete-after: True
+changes:
+ - bugfix: "The syndicate can't use meta warfare no more. Advanced pinpointers no longer crash the server"
diff --git a/html/changelogs/AutoChangeLog-pr-11946.yml b/html/changelogs/AutoChangeLog-pr-11946.yml
new file mode 100644
index 00000000000..980f1fa2c52
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-11946.yml
@@ -0,0 +1,4 @@
+author: "TheSardele"
+delete-after: True
+changes:
+ - bugfix: "Earmuffs now properly protect you from vampire screeches no matter what you are wearing on your other ear"
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 26cc4faf2ee..0167380d885 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/critter.dmi b/icons/mob/critter.dmi
index 7bf89c1aab1..ddb2ebe5822 100644
Binary files a/icons/mob/critter.dmi and b/icons/mob/critter.dmi differ
diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi
index 409cf04e862..7f84b29d04e 100644
Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ
diff --git a/icons/mob/nest.dmi b/icons/mob/nest.dmi
index 7dab335929e..d838ef6c5da 100644
Binary files a/icons/mob/nest.dmi and b/icons/mob/nest.dmi differ
diff --git a/icons/mob/screen_full.dmi b/icons/mob/screen_full.dmi
index 595b870a172..d1aa736b352 100644
Binary files a/icons/mob/screen_full.dmi and b/icons/mob/screen_full.dmi differ
diff --git a/icons/obj/chairs.dmi b/icons/obj/chairs.dmi
index 78ad28272e4..dba239a064f 100644
Binary files a/icons/obj/chairs.dmi and b/icons/obj/chairs.dmi differ
diff --git a/icons/obj/clockwork_objects.dmi b/icons/obj/clockwork_objects.dmi
new file mode 100644
index 00000000000..c147f01fdff
Binary files /dev/null and b/icons/obj/clockwork_objects.dmi differ
diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi
index 1bfe41618c4..a20cf61eb33 100644
Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ
diff --git a/icons/obj/hand_of_god_structures.dmi b/icons/obj/hand_of_god_structures.dmi
new file mode 100644
index 00000000000..4f18b2e68d4
Binary files /dev/null and b/icons/obj/hand_of_god_structures.dmi differ
diff --git a/icons/obj/items.dmi b/icons/obj/items.dmi
index 0cfe6dc554c..05a5768ce89 100644
Binary files a/icons/obj/items.dmi and b/icons/obj/items.dmi differ
diff --git a/icons/obj/lavaland/artefacts.dmi b/icons/obj/lavaland/artefacts.dmi
index a0486389b74..cd2c6da08ee 100644
Binary files a/icons/obj/lavaland/artefacts.dmi and b/icons/obj/lavaland/artefacts.dmi differ
diff --git a/icons/obj/lavaland/dead_ratvar.dmi b/icons/obj/lavaland/dead_ratvar.dmi
new file mode 100644
index 00000000000..5e6e9087bd0
Binary files /dev/null and b/icons/obj/lavaland/dead_ratvar.dmi differ
diff --git a/icons/obj/library.dmi b/icons/obj/library.dmi
index f57c6ded447..f28e5f83a33 100644
Binary files a/icons/obj/library.dmi and b/icons/obj/library.dmi differ
diff --git a/icons/obj/puzzle.dmi b/icons/obj/puzzle.dmi
new file mode 100644
index 00000000000..f623142beb4
Binary files /dev/null and b/icons/obj/puzzle.dmi differ
diff --git a/icons/obj/smooth_structures/catwalk_clockwork.dmi b/icons/obj/smooth_structures/catwalk_clockwork.dmi
new file mode 100644
index 00000000000..ecef1df3897
Binary files /dev/null and b/icons/obj/smooth_structures/catwalk_clockwork.dmi differ
diff --git a/icons/obj/smooth_structures/catwalk_clockwork_large.dmi b/icons/obj/smooth_structures/catwalk_clockwork_large.dmi
new file mode 100644
index 00000000000..675ebd91f6d
Binary files /dev/null and b/icons/obj/smooth_structures/catwalk_clockwork_large.dmi differ
diff --git a/icons/obj/smooth_structures/lattice_clockwork.dmi b/icons/obj/smooth_structures/lattice_clockwork.dmi
new file mode 100644
index 00000000000..23beeecb276
Binary files /dev/null and b/icons/obj/smooth_structures/lattice_clockwork.dmi differ
diff --git a/icons/obj/smooth_structures/lattice_clockwork_large.dmi b/icons/obj/smooth_structures/lattice_clockwork_large.dmi
new file mode 100644
index 00000000000..3b035d8736b
Binary files /dev/null and b/icons/obj/smooth_structures/lattice_clockwork_large.dmi differ
diff --git a/icons/turf/floors.dmi b/icons/turf/floors.dmi
index 411f371af9b..400b42105f4 100644
Binary files a/icons/turf/floors.dmi and b/icons/turf/floors.dmi differ
diff --git a/icons/turf/walls/clockwork_wall.dmi b/icons/turf/walls/clockwork_wall.dmi
new file mode 100644
index 00000000000..8263e901d00
Binary files /dev/null and b/icons/turf/walls/clockwork_wall.dmi differ
diff --git a/paradise.dme b/paradise.dme
index aa90c6b58b7..0110aa952af 100644
--- a/paradise.dme
+++ b/paradise.dme
@@ -198,6 +198,7 @@
#include "code\controllers\master.dm"
#include "code\controllers\subsystem.dm"
#include "code\controllers\verbs.dm"
+#include "code\controllers\subsystem\afk.dm"
#include "code\controllers\subsystem\air.dm"
#include "code\controllers\subsystem\alarm.dm"
#include "code\controllers\subsystem\assets.dm"
@@ -1257,7 +1258,6 @@
#include "code\modules\awaymissions\loot.dm"
#include "code\modules\awaymissions\map_rng.dm"
#include "code\modules\awaymissions\pamphlet.dm"
-#include "code\modules\awaymissions\trigger.dm"
#include "code\modules\awaymissions\zlevel.dm"
#include "code\modules\awaymissions\zvis.dm"
#include "code\modules\awaymissions\maploader\dmm_suite.dm"
@@ -1923,6 +1923,7 @@
#include "code\modules\mob\living\simple_animal\hostile\killertomato.dm"
#include "code\modules\mob\living\simple_animal\hostile\mimic.dm"
#include "code\modules\mob\living\simple_animal\hostile\mushroom.dm"
+#include "code\modules\mob\living\simple_animal\hostile\netherworld.dm"
#include "code\modules\mob\living\simple_animal\hostile\pirate.dm"
#include "code\modules\mob\living\simple_animal\hostile\russian.dm"
#include "code\modules\mob\living\simple_animal\hostile\spaceworms.dm"
@@ -2305,8 +2306,13 @@
#include "code\modules\ruins\ruin_areas.dm"
#include "code\modules\ruins\lavalandruin_code\animal_hospital.dm"
#include "code\modules\ruins\lavalandruin_code\ash_walker_den.dm"
+#include "code\modules\ruins\lavalandruin_code\dead_ratvar.dm"
+#include "code\modules\ruins\lavalandruin_code\fountain_hall.dm"
#include "code\modules\ruins\lavalandruin_code\hermit.dm"
+#include "code\modules\ruins\lavalandruin_code\pizzaparty.dm"
+#include "code\modules\ruins\lavalandruin_code\puzzle.dm"
#include "code\modules\ruins\lavalandruin_code\seed_vault.dm"
+#include "code\modules\ruins\lavalandruin_code\sin_ruins.dm"
#include "code\modules\ruins\lavalandruin_code\syndicate_base.dm"
#include "code\modules\ruins\objects_and_mobs\gym.dm"
#include "code\modules\ruins\objects_and_mobs\necropolis_gate.dm"
diff --git a/sound/lavaland/cursed_slot_machine.ogg b/sound/lavaland/cursed_slot_machine.ogg
new file mode 100644
index 00000000000..80911861bab
Binary files /dev/null and b/sound/lavaland/cursed_slot_machine.ogg differ
diff --git a/sound/lavaland/cursed_slot_machine_jackpot.ogg b/sound/lavaland/cursed_slot_machine_jackpot.ogg
new file mode 100644
index 00000000000..19a4c417a96
Binary files /dev/null and b/sound/lavaland/cursed_slot_machine_jackpot.ogg differ
diff --git a/sound/machines/fryer/deep_fryer_emerge.ogg b/sound/machines/fryer/deep_fryer_emerge.ogg
new file mode 100644
index 00000000000..a803dd4c677
Binary files /dev/null and b/sound/machines/fryer/deep_fryer_emerge.ogg differ