\n"
if (scanner.give_wound_treatment_bonus)
ADD_TRAIT(current_wound, TRAIT_WOUND_SCANNED, ANALYZER_TRAIT)
if(!advised)
@@ -525,11 +525,9 @@
advised = TRUE
render_list += ""
- var/obj/item/healthanalyzer/simple/simple_scanner
- if(istype(scanner, /obj/item/healthanalyzer/simple))
- simple_scanner = scanner
if(render_list == "")
- if (simple_scanner)
+ if(simple_scan)
+ var/obj/item/healthanalyzer/simple/simple_scanner = scanner
// Only emit the cheerful scanner message if this scan came from a scanner
playsound(simple_scanner, 'sound/machines/ping.ogg', 50, FALSE)
to_chat(user, span_notice("\The [simple_scanner] makes a happy ping and briefly displays a smiley face with several exclamation points! It's really excited to report that [patient] has no wounds!"))
@@ -537,7 +535,8 @@
to_chat(user, "No wounds detected in subject.")
else
to_chat(user, examine_block(jointext(render_list, "")), type = MESSAGE_TYPE_INFO)
- if (simple_scanner)
+ if(simple_scan)
+ var/obj/item/healthanalyzer/simple/simple_scanner = scanner
simple_scanner.show_emotion(AID_EMOTION_WARN)
playsound(simple_scanner, 'sound/machines/twobeep.ogg', 50, FALSE)
@@ -597,7 +596,7 @@
show_emotion(AI_EMOTION_SAD)
return
- woundscan(user, patient, src)
+ woundscan(user, patient, src, simple_scan = TRUE)
flick(icon_state + "_pinprick", src)
/obj/item/healthanalyzer/simple/update_overlays()
diff --git a/code/game/objects/items/eightball.dm b/code/game/objects/items/eightball.dm
index 7b8354b0a777bd..737ba7e947c5ae 100644
--- a/code/game/objects/items/eightball.dm
+++ b/code/game/objects/items/eightball.dm
@@ -11,8 +11,8 @@
var/shaking = FALSE
var/on_cooldown = FALSE
- var/shake_time = 50
- var/cooldown_time = 100
+ var/shake_time = 5 SECONDS
+ var/cooldown_time = 10 SECONDS
var/static/list/possible_answers = list(
"It is certain",
@@ -42,11 +42,16 @@
return INITIALIZE_HINT_QDEL
/obj/item/toy/eightball/proc/MakeHaunted()
- . = prob(1)
- if(.)
+ if(prob(1))
new /obj/item/toy/eightball/haunted(loc)
+ return TRUE
+ return FALSE
/obj/item/toy/eightball/attack_self(mob/user)
+ if(..())
+ return
+
+ . = TRUE
if(shaking)
return
@@ -60,23 +65,19 @@
start_shaking(user)
if(do_after(user, shake_time))
- var/answer = get_answer()
- say(answer)
+ say(get_answer())
on_cooldown = TRUE
- addtimer(CALLBACK(src, PROC_REF(clear_cooldown)), cooldown_time)
+ addtimer(VARSET_CALLBACK(src, on_cooldown, FALSE), cooldown_time)
shaking = FALSE
-/obj/item/toy/eightball/proc/start_shaking(user)
+/obj/item/toy/eightball/proc/start_shaking(mob/user)
return
/obj/item/toy/eightball/proc/get_answer()
return pick(possible_answers)
-/obj/item/toy/eightball/proc/clear_cooldown()
- on_cooldown = FALSE
-
// A broken magic eightball, it only says "YOU SUCK" over and over again.
/obj/item/toy/eightball/broken
@@ -97,7 +98,7 @@
/obj/item/toy/eightball/haunted
shake_time = 30 SECONDS
cooldown_time = 3 MINUTES
- var/last_message
+ var/last_message = "Nothing!"
var/selected_message
//these kind of store the same thing but one is easier to work with.
var/list/votes = list()
@@ -160,6 +161,7 @@
notify_ghosts("[user] is shaking [src], hoping to get an answer to \"[selected_message]\"", source=src, enter_link="(Click to help)", action=NOTIFY_ATTACK, header = "Magic eightball")
/obj/item/toy/eightball/haunted/Topic(href, href_list)
+ . = ..()
if(href_list["interact"])
if(isobserver(usr))
interact(usr)
@@ -169,7 +171,7 @@
var/top_vote
for(var/vote in votes)
- var/amount_of_votes = length(votes[vote])
+ var/amount_of_votes = votes[vote]
if(amount_of_votes > top_amount)
top_vote = vote
top_amount = amount_of_votes
@@ -186,12 +188,19 @@
voted.Cut()
- return top_vote
+ var/list/top_options = haunted_answers[top_vote]
+ return pick(top_options)
+
+// Only ghosts can interact because only ghosts can open the ui
+/obj/item/toy/eightball/haunted/can_interact(mob/living/user)
+ return isobserver(user)
/obj/item/toy/eightball/haunted/ui_state(mob/user)
return GLOB.observer_state
/obj/item/toy/eightball/haunted/ui_interact(mob/user, datum/tgui/ui)
+ if(!isobserver(user))
+ return
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "EightBallVote", name)
@@ -224,9 +233,11 @@
var/selected_answer = params["answer"]
if(!(selected_answer in haunted_answers))
return
- if(user.ckey in voted)
- return
- else
- votes[selected_answer] += 1
- voted[user.ckey] = selected_answer
- . = TRUE
+ var/oldvote = voted[user.ckey]
+ if(oldvote)
+ // detract their old vote
+ votes[oldvote] -= 1
+
+ votes[selected_answer] += 1
+ voted[user.ckey] = selected_answer
+ return TRUE
diff --git a/code/game/objects/items/food/_food.dm b/code/game/objects/items/food/_food.dm
index 2b7e4b4cf36869..69cfdde4668bc9 100644
--- a/code/game/objects/items/food/_food.dm
+++ b/code/game/objects/items/food/_food.dm
@@ -51,10 +51,7 @@
///Buff given when a hand-crafted version of this item is consumed. Randomized according to crafting_complexity if not assigned.
var/datum/status_effect/food/crafted_food_buff = null
-/obj/item/food/Initialize(mapload, starting_reagent_purity, no_base_reagents = FALSE)
- src.starting_reagent_purity = starting_reagent_purity
- if(no_base_reagents)
- food_reagents = null
+/obj/item/food/Initialize(mapload)
if(food_reagents)
food_reagents = string_assoc_list(food_reagents)
. = ..()
diff --git a/code/game/objects/items/food/bread.dm b/code/game/objects/items/food/bread.dm
index 08722f101c5e9c..ba1845bb40da4a 100644
--- a/code/game/objects/items/food/bread.dm
+++ b/code/game/objects/items/food/bread.dm
@@ -55,10 +55,6 @@
. = ..()
AddComponent(/datum/component/customizable_reagent_holder, /obj/item/food/bread/empty, CUSTOM_INGREDIENT_ICON_FILL, max_ingredients = 8)
-// special subtype we use for the "Bread" Admin Smite (or the breadify proc)
-/obj/item/food/bread/plain/smite
- desc = "If you hold it up to your ear, you can hear the screams of the damned."
-
/obj/item/food/breadslice/plain
name = "bread slice"
desc = "A slice of home."
diff --git a/code/game/objects/items/food/burgers.dm b/code/game/objects/items/food/burgers.dm
index 311d87b319228d..191cc0eaf25733 100644
--- a/code/game/objects/items/food/burgers.dm
+++ b/code/game/objects/items/food/burgers.dm
@@ -226,10 +226,15 @@
verb_yell = "wails"
venue_value = FOOD_PRICE_EXOTIC
crafting_complexity = FOOD_COMPLEXITY_3
+ preserved_food = TRUE // It's made of ghosts
-/obj/item/food/burger/ghost/Initialize(mapload)
+/obj/item/food/burger/ghost/Initialize(mapload, starting_reagent_purity, no_base_reagents)
. = ..()
START_PROCESSING(SSobj, src)
+ AddComponent(/datum/component/ghost_edible, bite_consumption = bite_consumption)
+
+/obj/item/food/burger/ghost/make_germ_sensitive()
+ return // This burger moves itself so it shouldn't pick up germs from walking onto the floor
/obj/item/food/burger/ghost/process()
if(!isturf(loc)) //no floating out of bags
@@ -256,8 +261,6 @@
new /obj/effect/decal/cleanable/greenglow/ecto(loc)
playsound(loc, 'sound/effects/splat.ogg', 200, TRUE)
- //If i was less lazy i would make the burger forcefeed itself to a nearby mob here.
-
/obj/item/food/burger/ghost/Destroy()
STOP_PROCESSING(SSobj, src)
. = ..()
diff --git a/code/game/objects/items/food/pancakes.dm b/code/game/objects/items/food/pancakes.dm
index 6e4d33fb35049b..52829ab4c3acd4 100644
--- a/code/game/objects/items/food/pancakes.dm
+++ b/code/game/objects/items/food/pancakes.dm
@@ -147,6 +147,7 @@
var/mutable_appearance/pancake_visual = mutable_appearance(icon, "[pancake.stack_name]_[rand(1, 3)]")
pancake_visual.pixel_x = rand(-1, 1)
pancake_visual.pixel_y = 3 * contents.len - 1
+ pancake_visual.layer = layer + (contents.len * 0.01)
add_overlay(pancake_visual)
update_appearance()
diff --git a/code/game/objects/items/food/pastries.dm b/code/game/objects/items/food/pastries.dm
index 02782c3e3f1444..46da05dea14039 100644
--- a/code/game/objects/items/food/pastries.dm
+++ b/code/game/objects/items/food/pastries.dm
@@ -34,6 +34,10 @@
foodtypes = GRAIN | FRUIT | SUGAR | BREAKFAST
crafting_complexity = FOOD_COMPLEXITY_4
+/obj/item/food/muffin/booberry/Initialize(mapload, starting_reagent_purity, no_base_reagents)
+ . = ..()
+ AddComponent(/datum/component/ghost_edible, bite_consumption = bite_consumption)
+
/obj/item/food/muffin/moffin
name = "moffin"
icon_state = "moffin_1"
@@ -367,7 +371,10 @@
*/
var/list/prefill_flavours
-/obj/item/food/icecream/Initialize(mapload, starting_reagent_purity, no_base_reagents, list/prefill_flavours)
+/obj/item/food/icecream/New(loc, list/prefill_flavours)
+ return ..()
+
+/obj/item/food/icecream/Initialize(mapload, list/prefill_flavours)
if(prefill_flavours)
src.prefill_flavours = prefill_flavours
return ..()
diff --git a/code/game/objects/items/hand_items.dm b/code/game/objects/items/hand_items.dm
index b136fc68cda38a..5dfb27e77eaadb 100644
--- a/code/game/objects/items/hand_items.dm
+++ b/code/game/objects/items/hand_items.dm
@@ -128,7 +128,7 @@
return FALSE
var/obj/item/bodypart/head/the_head = target.get_bodypart(BODY_ZONE_HEAD)
- if(!(the_head.biological_state & BIO_FLESH) || !IS_ORGANIC_LIMB(the_head))
+ if(!(the_head.biological_state & BIO_FLESH))
to_chat(user, span_warning("You can't noogie [target], [target.p_they()] [target.p_have()] no skin on [target.p_their()] head!"))
return
diff --git a/code/game/objects/items/kirby_plants/kirbyplants.dm b/code/game/objects/items/kirby_plants/kirbyplants.dm
index 6e7f5a99d958bb..74a0c8637e8949 100644
--- a/code/game/objects/items/kirby_plants/kirbyplants.dm
+++ b/code/game/objects/items/kirby_plants/kirbyplants.dm
@@ -75,21 +75,6 @@
var/next = WRAP(current+1,1,length(random_plant_states))
icon_state = random_plant_states[next]
-/obj/item/kirbyplants/random
- icon = 'icons/obj/fluff/flora/_flora.dmi'
- icon_state = "random_plant"
-
-/obj/item/kirbyplants/random/Initialize(mapload)
- . = ..()
- //icon = 'icons/obj/flora/plants.dmi' // ORIGINAL
- icon = 'modular_skyrat/modules/aesthetics/plants/plants.dmi' //SKYRAT EDIT CHANGE
- if(!random_plant_states)
- generate_states()
- var/current = random_plant_states.Find(icon_state)
- var/next = WRAP(current+1,1,length(random_plant_states))
- base_icon_state = random_plant_states[next]
- update_appearance(UPDATE_ICON)
-
/obj/item/kirbyplants/proc/generate_states()
random_plant_states = list()
for(var/i in 1 to random_state_cap) //SKYRAT EDIT CHANGE - ORIGINAL: for(var/i in 1 to 24)
@@ -107,7 +92,8 @@
/obj/item/kirbyplants/random/Initialize(mapload)
. = ..()
- icon = 'icons/obj/fluff/flora/plants.dmi'
+ //icon = 'icons/obj/flora/plants.dmi' // ORIGINAL
+ icon = 'modular_skyrat/modules/aesthetics/plants/plants.dmi' //SKYRAT EDIT CHANGE
randomize_base_icon_state()
//Handles randomizing the icon during initialize()
diff --git a/code/game/objects/items/melee/misc.dm b/code/game/objects/items/melee/misc.dm
index f24efb7d26380e..cd196ed48cfa23 100644
--- a/code/game/objects/items/melee/misc.dm
+++ b/code/game/objects/items/melee/misc.dm
@@ -60,8 +60,8 @@
//very imprecise
/obj/item/melee/sabre
- name = "officer's sabre"
- desc = "An elegant weapon, its monomolecular edge is capable of cutting through flesh and bone with ease."
+ name = "officer's sabre" //SKYRAT EDIT - Buffed in modular_skyrat/modules/modular_weapons/code/melee.dm
+ desc = "An elegant weapon, its monomolecular edge is capable of cutting through flesh and bone with ease."
icon = 'icons/obj/weapons/sword.dmi'
icon_state = "sabre"
inhand_icon_state = "sabre"
diff --git a/code/game/objects/items/robot/items/food.dm b/code/game/objects/items/robot/items/food.dm
index 832dcfd2cd6caf..a747f813ace869 100644
--- a/code/game/objects/items/robot/items/food.dm
+++ b/code/game/objects/items/robot/items/food.dm
@@ -61,10 +61,8 @@
food_item = new /obj/item/food/lollipop/cyborg(turf_to_dispense_to)
if(DISPENSE_ICECREAM_MODE)
food_item = new /obj/item/food/icecream(
- /* loc = */ turf_to_dispense_to,
- /* starting_reagent_purity = */ null,
- /* no_base_reagents = */ FALSE,
- /* prefill_flavours = */ list(ICE_CREAM_VANILLA),
+ loc = turf_to_dispense_to,
+ prefill_flavours = list(ICE_CREAM_VANILLA),
)
food_item.desc = "Eat the ice cream."
diff --git a/code/game/objects/items/robot/items/storage.dm b/code/game/objects/items/robot/items/storage.dm
index 25f85644a1c439..6570e159b6a094 100644
--- a/code/game/objects/items/robot/items/storage.dm
+++ b/code/game/objects/items/robot/items/storage.dm
@@ -190,6 +190,21 @@
handle_reflling(arrived)
return ..()
+///Used by the service borg drink apparatus upgrade, holds drink-related items
+/obj/item/borg/apparatus/beaker/drink
+ name = "secondary beverage storage apparatus"
+ desc = "A special apparatus for carrying drinks and condiment packets without spilling their contents. Will NOT resynthesize drinks unlike your standard apparatus."
+ icon_state = "borg_beaker_apparatus"
+ storable = list(
+ /obj/item/reagent_containers/cup/glass,
+ /obj/item/reagent_containers/condiment,
+ /obj/item/reagent_containers/cup/coffeepot,
+ /obj/item/reagent_containers/cup/bottle/syrup_bottle,
+ )
+
+/obj/item/borg/apparatus/beaker/service2/add_glass()
+ stored = new /obj/item/reagent_containers/cup/glass/drinkingglass(src)
+
/// allows medical cyborgs to manipulate organs without hands
/obj/item/borg/apparatus/organ_storage
name = "organ storage bag"
diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm
index 496e4b39a46e32..94432b35d3df0d 100644
--- a/code/game/objects/items/robot/robot_upgrades.dm
+++ b/code/game/objects/items/robot/robot_upgrades.dm
@@ -731,6 +731,33 @@
if (E)
R.model.remove_module(E, TRUE)
+/obj/item/borg/upgrade/drink_app
+ name = "glass storage apparatus"
+ desc = "A supplementary drinking glass storage apparatus for service cyborgs."
+ icon_state = "cyborg_upgrade3"
+ require_model = TRUE
+ model_type = list(/obj/item/robot_model/service)
+ model_flags = BORG_MODEL_SERVICE
+
+/obj/item/borg/upgrade/drink_app/action(mob/living/silicon/robot/R, user = usr)
+ . = ..()
+ if(.)
+ var/obj/item/borg/apparatus/beaker/drink/E = locate() in R.model.modules
+ if(E)
+ to_chat(user, span_warning("This unit has no room for additional drink storage!"))
+ return FALSE
+
+ E = new(R.model)
+ R.model.basic_modules += E
+ R.model.add_module(E, FALSE, TRUE)
+
+/obj/item/borg/upgrade/drink_app/deactivate(mob/living/silicon/robot/R, user = usr)
+ . = ..()
+ if (.)
+ var/obj/item/borg/apparatus/beaker/drink/E = locate() in R.model.modules
+ if (E)
+ R.model.remove_module(E, TRUE)
+
/obj/item/borg/upgrade/broomer
name = "experimental push broom"
desc = "An experimental push broom used for efficiently pushing refuse."
diff --git a/code/game/objects/items/stacks/medical.dm b/code/game/objects/items/stacks/medical.dm
index d071e3465afde2..081ab5d78e015e 100644
--- a/code/game/objects/items/stacks/medical.dm
+++ b/code/game/objects/items/stacks/medical.dm
@@ -433,7 +433,7 @@
patient.emote("scream")
for(var/i in patient.bodyparts)
- var/obj/item/bodypart/bone = i
+ var/obj/item/bodypart/bone = i // fine to just, use these raw, its a meme anyway
var/datum/wound/blunt/bone/severe/oof_ouch = new
oof_ouch.apply_wound(bone, wound_source = "bone gel")
var/datum/wound/blunt/bone/critical/oof_OUCH = new
diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm
index 9257035d8dee2b..289920c8889858 100644
--- a/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -505,6 +505,9 @@ GLOBAL_LIST_INIT(durathread_recipes, list ( \
. = ..()
. += GLOB.durathread_recipes
+/obj/item/stack/sheet/durathread/on_item_crafted(mob/builder, atom/created)
+ created.set_armor_rating(CONSUME, max(50, created.get_armor_rating(CONSUME)))
+
/obj/item/stack/sheet/cotton
name = "raw cotton bundle"
desc = "A bundle of raw cotton ready to be spun on the loom."
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 54fb486683510e..7050309268cd0e 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -145,18 +145,49 @@
/obj/item/stack/set_custom_materials(list/materials, multiplier=1, is_update=FALSE)
return is_update ? ..() : set_mats_per_unit(materials, multiplier/(amount || 1))
-
-/obj/item/stack/on_grind()
- . = ..()
- for(var/i in 1 to length(grind_results)) //This should only call if it's ground, so no need to check if grind_results exists
- grind_results[grind_results[i]] *= get_amount() //Gets the key at position i, then the reagent amount of that key, then multiplies it by stack size
-
/obj/item/stack/grind_requirements()
if(is_cyborg)
to_chat(usr, span_warning("[src] is too integrated into your chassis and can't be ground up!"))
return
return TRUE
+/obj/item/stack/grind(datum/reagents/target_holder, mob/user)
+ var/current_amount = get_amount()
+ if(current_amount <= 0 || QDELETED(src)) //just to get rid of this 0 amount/deleted stack we return success
+ return TRUE
+ if(on_grind() == -1)
+ return FALSE
+ if(isnull(target_holder))
+ return TRUE
+
+ if(reagents)
+ reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user)
+ var/available_volume = target_holder.maximum_volume - target_holder.total_volume
+
+ //compute total volume of reagents that will be occupied by grind_results
+ var/total_volume = 0
+ for(var/reagent in grind_results)
+ total_volume += grind_results[reagent]
+
+ //compute number of pieces(or sheets) from available_volume
+ var/available_amount = min(current_amount, round(available_volume / total_volume))
+ if(available_amount <= 0)
+ return FALSE
+
+ //Now transfer the grind results scaled by available_amount
+ var/list/grind_reagents = grind_results.Copy()
+ for(var/reagent in grind_reagents)
+ grind_reagents[reagent] *= available_amount
+ target_holder.add_reagent_list(grind_reagents)
+
+ /**
+ * use available_amount of sheets/pieces, return TRUE only if all sheets/pieces of this stack were used
+ * we don't delete this stack when it reaches 0 because we expect the all in one grinder, etc to delete
+ * this stack if grinding was successfull
+ */
+ use(available_amount, check = FALSE)
+ return available_amount == current_amount
+
/obj/item/stack/proc/get_main_recipes()
RETURN_TYPE(/list)
SHOULD_CALL_PARENT(TRUE)
@@ -401,6 +432,7 @@
if(created)
created.setDir(builder.dir)
+ on_item_crafted(builder, created)
// Use up the material
use(recipe.req_amount * multiplier)
@@ -431,6 +463,10 @@
return TRUE
+/// Run special logic on created items after they've been successfully crafted.
+/obj/item/stack/proc/on_item_crafted(mob/builder, atom/created)
+ return
+
/obj/item/stack/vv_edit_var(vname, vval)
if(vname == NAMEOF(src, amount))
add(clamp(vval, 1-amount, max_amount - amount)) //there must always be one.
diff --git a/code/game/objects/items/storage/belt.dm b/code/game/objects/items/storage/belt.dm
index 484797ca29ce14..a67d85979f1040 100644
--- a/code/game/objects/items/storage/belt.dm
+++ b/code/game/objects/items/storage/belt.dm
@@ -75,6 +75,7 @@
/obj/item/wirecutters,
/obj/item/wrench,
/obj/item/spess_knife,
+ /obj/item/melee/sickly_blade/knock,
))
/obj/item/storage/belt/utility/chief
diff --git a/code/game/objects/items/storage/boxes/job_boxes.dm b/code/game/objects/items/storage/boxes/job_boxes.dm
index 658b07a3980a32..335ccbe7185e9c 100644
--- a/code/game/objects/items/storage/boxes/job_boxes.dm
+++ b/code/game/objects/items/storage/boxes/job_boxes.dm
@@ -48,6 +48,9 @@
if(HAS_TRAIT(SSstation, STATION_TRAIT_RADIOACTIVE_NEBULA))
new /obj/item/storage/pill_bottle/potassiodide(src)
+
+ if(SSmapping.is_planetary() && LAZYLEN(SSmapping.multiz_levels))
+ new /obj/item/climbing_hook/emergency(src)
new /obj/item/oxygen_candle(src) //SKYRAT EDIT ADDITION
diff --git a/code/game/objects/items/storage/secure.dm b/code/game/objects/items/storage/secure.dm
index 46eb8356c79d45..ad4be8d6e4088a 100644
--- a/code/game/objects/items/storage/secure.dm
+++ b/code/game/objects/items/storage/secure.dm
@@ -181,6 +181,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/item/storage/secure/safe, 32)
. = ..()
atom_storage.set_holdable(cant_hold_list = list(/obj/item/storage/secure/briefcase))
atom_storage.max_specific_storage = WEIGHT_CLASS_GIGANTIC
+ find_and_hang_on_wall()
/obj/item/storage/secure/safe/PopulateContents()
new /obj/item/paper(src)
diff --git a/code/game/objects/items/surgery_tray.dm b/code/game/objects/items/surgery_tray.dm
index 5ef5bfdae7a584..df597abee2576f 100644
--- a/code/game/objects/items/surgery_tray.dm
+++ b/code/game/objects/items/surgery_tray.dm
@@ -1,8 +1,31 @@
+/datum/storage/surgery_tray
+ max_total_storage = 30
+ max_specific_storage = WEIGHT_CLASS_NORMAL
+ max_slots = 14
+
+/datum/storage/surgery_tray/New()
+ . = ..()
+ set_holdable(list(
+ /obj/item/blood_filter,
+ /obj/item/bonesetter,
+ /obj/item/cautery,
+ /obj/item/circular_saw,
+ /obj/item/clothing/mask/surgical,
+ /obj/item/hemostat,
+ /obj/item/razor,
+ /obj/item/reagent_containers/medigel,
+ /obj/item/retractor,
+ /obj/item/scalpel,
+ /obj/item/stack/medical/bone_gel,
+ /obj/item/stack/sticky_tape/surgical,
+ /obj/item/surgical_drapes,
+ /obj/item/surgicaldrill,
+ ))
+
/**
* Surgery Trays
* A storage object that displays tools in its contents based on tier, better tools are more visible.
* Can be folded up and carried. Click it to draw a random tool.
- *
*/
/obj/item/surgery_tray
name = "surgery tray"
@@ -15,25 +38,10 @@
/// If true we're currently portable
var/is_portable = TRUE
- /// List of things that we spawn containing
- var/list/initial_contents = list(
- /obj/item/blood_filter,
- /obj/item/bonesetter,
- /obj/item/cautery,
- /obj/item/circular_saw,
- /obj/item/clothing/mask/surgical,
- /obj/item/hemostat,
- /obj/item/razor/surgery,
- /obj/item/retractor,
- /obj/item/scalpel,
- /obj/item/stack/medical/bone_gel,
- /obj/item/stack/sticky_tape/surgical,
- /obj/item/surgical_drapes,
- /obj/item/surgicaldrill,
- )
-/obj/item/surgery_tray/deployed
- is_portable = FALSE
+/// Fills the tray with items it should contain on creation
+/obj/item/surgery_tray/proc/populate_contents()
+ return
/obj/item/surgery_tray/Initialize(mapload)
. = ..()
@@ -66,6 +74,7 @@
. += is_portable \
? span_notice("You can click and drag it to yourself to pick it up, then use it in your hand to make it a cart!") \
: span_notice("You can click and drag it to yourself to turn it into a tray!")
+ . += span_notice("The top is screwed on.")
/obj/item/surgery_tray/update_overlays()
. = ..()
@@ -102,12 +111,6 @@
for(var/surgery_tool in surgery_overlays)
. |= surgery_overlays[surgery_tool]
-///Spawn the things we contain on initialisation
-/obj/item/surgery_tray/proc/populate_contents()
- for (var/thing_path in initial_contents)
- new thing_path(src)
- update_appearance(UPDATE_OVERLAYS)
-
///Sets the surgery tray's deployment state. Silent if user is null.
/obj/item/surgery_tray/proc/set_tray_mode(new_mode, mob/user)
is_portable = new_mode
@@ -154,54 +157,82 @@
user.put_in_hands(grabbies)
return TRUE
+/obj/item/surgery_tray/screwdriver_act_secondary(mob/living/user, obj/item/tool)
+ . = ..()
+ tool.play_tool_sound(src)
+ to_chat(user, span_notice("You begin taking apart [src]."))
+ if(!tool.use_tool(src, user, 1 SECONDS))
+ return
+ deconstruct(TRUE)
+ to_chat(user, span_notice("[src] has been taken apart."))
+
/obj/item/surgery_tray/dump_contents()
var/atom/drop_point = drop_location()
for(var/atom/movable/tool as anything in contents)
tool.forceMove(drop_point)
/obj/item/surgery_tray/deconstruct(disassembled = TRUE)
- dump_contents()
+ if(!(flags_1 & NODECONSTRUCT_1))
+ dump_contents()
+ new /obj/item/stack/rods(drop_location(), 2)
+ new /obj/item/stack/sheet/mineral/silver(drop_location())
return ..()
-/obj/item/surgery_tray/morgue
+/obj/item/surgery_tray/deployed
+ is_portable = FALSE
+
+/obj/item/surgery_tray/full
+
+/obj/item/surgery_tray/full/deployed
+ is_portable = FALSE
+
+/obj/item/surgery_tray/full/populate_contents()
+ new /obj/item/blood_filter(src)
+ new /obj/item/bonesetter(src)
+ new /obj/item/cautery(src)
+ new /obj/item/circular_saw(src)
+ new /obj/item/clothing/mask/surgical(src)
+ new /obj/item/hemostat(src)
+ new /obj/item/razor/surgery(src)
+ new /obj/item/retractor(src)
+ new /obj/item/scalpel(src)
+ new /obj/item/stack/medical/bone_gel(src)
+ new /obj/item/stack/sticky_tape/surgical(src)
+ new /obj/item/surgical_drapes(src)
+ new /obj/item/surgicaldrill(src)
+ update_appearance(UPDATE_OVERLAYS)
+
+/obj/item/surgery_tray/full/morgue
name = "autopsy tray"
desc = "A Deforest brand surgery tray, made for use in morgues. It is a folding model, \
meaning the wheels on the bottom can be extended outwards, making it a cart."
- initial_contents = list(
- /obj/item/blood_filter,
- /obj/item/bonesetter,
- /obj/item/cautery/cruel,
- /obj/item/circular_saw,
- /obj/item/clothing/mask/surgical,
- /obj/item/hemostat/cruel,
- /obj/item/razor/surgery,
- /obj/item/retractor/cruel,
- /obj/item/scalpel/cruel,
- /obj/item/stack/medical/bone_gel,
- /obj/item/stack/sticky_tape/surgical,
- /obj/item/surgical_drapes,
- /obj/item/surgicaldrill,
- )
-
-/datum/storage/surgery_tray
- max_total_storage = 30
- max_specific_storage = WEIGHT_CLASS_NORMAL
- max_slots = 14
-
-/datum/storage/surgery_tray/New()
- . = ..()
- set_holdable(list(
- /obj/item/blood_filter,
- /obj/item/bonesetter,
- /obj/item/cautery,
- /obj/item/circular_saw,
- /obj/item/clothing/mask/surgical,
- /obj/item/hemostat,
- /obj/item/razor,
- /obj/item/retractor,
- /obj/item/scalpel,
- /obj/item/stack/medical/bone_gel,
- /obj/item/stack/sticky_tape/surgical,
- /obj/item/surgical_drapes,
- /obj/item/surgicaldrill,
- ))
+
+/obj/item/surgery_tray/full/morgue/populate_contents()
+ new /obj/item/blood_filter(src)
+ new /obj/item/bonesetter(src)
+ new /obj/item/cautery/cruel(src)
+ new /obj/item/circular_saw(src)
+ new /obj/item/clothing/mask/surgical(src)
+ new /obj/item/hemostat/cruel(src)
+ new /obj/item/razor/surgery(src)
+ new /obj/item/retractor/cruel(src)
+ new /obj/item/scalpel/cruel(src)
+ new /obj/item/stack/medical/bone_gel(src)
+ new /obj/item/stack/sticky_tape/surgical(src)
+ new /obj/item/surgical_drapes(src)
+ new /obj/item/surgicaldrill(src)
+
+/// Surgery tray with advanced tools for debug
+/obj/item/surgery_tray/full/advanced
+
+/obj/item/surgery_tray/full/advanced/populate_contents()
+ new /obj/item/scalpel/advanced
+ new /obj/item/retractor/advanced
+ new /obj/item/cautery/advanced
+ new /obj/item/surgical_drapes
+ new /obj/item/reagent_containers/medigel/sterilizine
+ new /obj/item/bonesetter
+ new /obj/item/blood_filter
+ new /obj/item/stack/medical/bone_gel
+ new /obj/item/stack/sticky_tape/surgical
+ new /obj/item/clothing/mask/surgical
diff --git a/code/game/objects/items/toys.dm b/code/game/objects/items/toys.dm
index 2eef8cc116e443..c7f93c6ea68bfe 100644
--- a/code/game/objects/items/toys.dm
+++ b/code/game/objects/items/toys.dm
@@ -285,7 +285,7 @@
var/obj/item/bodypart/chest/CH = user.get_bodypart(BODY_ZONE_CHEST)
if(CH.cavity_item) // if he's (un)bright enough to have a round and full belly...
user.visible_message(span_danger("[user] regurgitates [src]!")) // I swear i dont have a fetish
- user.vomit(100, TRUE, distance = 0)
+ user.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 100, distance = 0)
user.adjustOxyLoss(120)
user.dropItemToGround(src) // incase the crit state doesn't drop the singulo to the floor
user.set_suicide(FALSE)
diff --git a/code/game/objects/items/wall_mounted.dm b/code/game/objects/items/wall_mounted.dm
index 3cfafe74f9735d..48142778a925e9 100644
--- a/code/game/objects/items/wall_mounted.dm
+++ b/code/game/objects/items/wall_mounted.dm
@@ -40,20 +40,19 @@
span_hear("You hear clicking."))
var/floor_to_wall = get_dir(user, on_wall)
- var/obj/O = new result_path(get_turf(user), floor_to_wall, TRUE)
- O.setDir(floor_to_wall)
-
+ var/obj/hanging_object = new result_path(get_turf(user), floor_to_wall, TRUE)
+ hanging_object.setDir(floor_to_wall)
if(pixel_shift)
switch(floor_to_wall)
if(NORTH)
- O.pixel_y = pixel_shift
+ hanging_object.pixel_y = pixel_shift
if(SOUTH)
- O.pixel_y = -pixel_shift
+ hanging_object.pixel_y = -pixel_shift
if(EAST)
- O.pixel_x = pixel_shift
+ hanging_object.pixel_x = pixel_shift
if(WEST)
- O.pixel_x = -pixel_shift
- after_attach(O)
+ hanging_object.pixel_x = -pixel_shift
+ after_attach(hanging_object)
qdel(src)
diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm
index 1e05bae62989d1..f4758b9019f460 100644
--- a/code/game/objects/structures/crates_lockers/closets.dm
+++ b/code/game/objects/structures/crates_lockers/closets.dm
@@ -1,5 +1,9 @@
#define LOCKER_FULL -1
+///A comprehensive list of all closets (NOT CRATES) in the game world
+GLOBAL_LIST_EMPTY(roundstart_station_closets)
+
+
/obj/structure/closet
name = "closet"
desc = "It's a basic storage unit."
@@ -15,7 +19,7 @@
contents_pressure_protection = 0
/// How insulated the thing is, for the purposes of calculating body temperature. Must be between 0 and 1!
contents_thermal_insulation = 0
- pass_flags_self = LETPASSCLICKS
+ pass_flags_self = PASSSTRUCTURE | LETPASSCLICKS
/// The overlay for the closet's door
var/obj/effect/overlay/closet_door/door_obj
@@ -120,6 +124,9 @@
if(access_choices)
access_choices = card_reader_choices
+ if(is_station_level(z) && mapload)
+ add_to_roundstart_list()
+
// if closed, any item at the crate's loc is put in the contents
if (mapload && !opened)
. = INITIALIZE_HINT_LATELOAD
@@ -155,6 +162,7 @@
/obj/structure/closet/Destroy()
id_card = null
QDEL_NULL(door_obj)
+ GLOB.roundstart_station_closets -= src
return ..()
/obj/structure/closet/update_appearance(updates=ALL)
@@ -1142,4 +1150,8 @@
/obj/structure/closet/preopen
opened = TRUE
+///Adds the closet to a global list. Placed in its own proc so that crates may be excluded.
+/obj/structure/closet/proc/add_to_roundstart_list()
+ GLOB.roundstart_station_closets += src
+
#undef LOCKER_FULL
diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
index 8f7f34164c0f48..9700a3e80fa98f 100755
--- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm
+++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm
@@ -183,6 +183,7 @@
new /obj/item/pinpointer/crew(src)
new /obj/item/binoculars(src)
new /obj/item/storage/box/rxglasses/spyglasskit(src)
+ new /obj/item/clothing/head/fedora/inspector_hat(src)
/obj/structure/closet/secure_closet/injection
name = "lethal injections"
diff --git a/code/game/objects/structures/crates_lockers/closets/syndicate.dm b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
index fdba3e10adb6db..aab8e4b8582869 100644
--- a/code/game/objects/structures/crates_lockers/closets/syndicate.dm
+++ b/code/game/objects/structures/crates_lockers/closets/syndicate.dm
@@ -29,6 +29,7 @@
new /obj/item/clothing/under/syndicate/skirt(src)
new /obj/item/clothing/shoes/sneakers/black(src)
new /obj/item/mod/module/plasma_stabilizer(src)
+ new /obj/item/climbing_hook/syndicate(src)
/obj/structure/closet/syndicate/nuclear
desc = "It's a storage unit for a Syndicate boarding party."
diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm
index 4059839641cca3..1f8322ad5dc9f1 100644
--- a/code/game/objects/structures/crates_lockers/crates.dm
+++ b/code/game/objects/structures/crates_lockers/crates.dm
@@ -352,3 +352,6 @@
. = ..()
for(var/i in 1 to 4)
new /obj/effect/spawner/random/decoration/generic(src)
+
+/obj/structure/closet/crate/add_to_roundstart_list()
+ return
diff --git a/code/game/objects/structures/door_assembly.dm b/code/game/objects/structures/door_assembly.dm
index ce000e91e696ab..62638f44eeb778 100644
--- a/code/game/objects/structures/door_assembly.dm
+++ b/code/game/objects/structures/door_assembly.dm
@@ -293,7 +293,6 @@
door = new airlock_type( loc )
door.setDir(dir)
door.unres_sides = electronics.unres_sides
- //door.req_access = req_access
door.electronics = electronics
door.heat_proof = heat_proof_finished
door.security_level = 0
@@ -321,9 +320,11 @@
door.unres_sensor = TRUE
door.previous_airlock = previous_assembly
electronics.forceMove(door)
+ door.autoclose = TRUE
+ door.close()
door.update_appearance()
+
qdel(src)
- return door
/obj/structure/door_assembly/update_overlays()
. = ..()
diff --git a/code/game/objects/structures/extinguisher.dm b/code/game/objects/structures/extinguisher.dm
index 04e48e489ac914..56d9147867d53f 100644
--- a/code/game/objects/structures/extinguisher.dm
+++ b/code/game/objects/structures/extinguisher.dm
@@ -22,6 +22,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/extinguisher_cabinet, 29)
stored_extinguisher = new /obj/item/extinguisher(src)
update_appearance(UPDATE_ICON)
register_context()
+ find_and_hang_on_wall()
/obj/structure/extinguisher_cabinet/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
diff --git a/code/game/objects/structures/fireaxe.dm b/code/game/objects/structures/fireaxe.dm
index 75294549ebe4c6..59a00618b0e915 100644
--- a/code/game/objects/structures/fireaxe.dm
+++ b/code/game/objects/structures/fireaxe.dm
@@ -36,6 +36,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/fireaxecabinet, 32)
if(populate_contents)
held_item = new item_path(src)
update_appearance()
+ find_and_hang_on_wall()
/obj/structure/fireaxecabinet/Destroy()
if(held_item)
diff --git a/code/game/objects/structures/mirror.dm b/code/game/objects/structures/mirror.dm
index 21767551886cf1..3c99aee7233d30 100644
--- a/code/game/objects/structures/mirror.dm
+++ b/code/game/objects/structures/mirror.dm
@@ -27,6 +27,10 @@
MAPPING_DIRECTIONAL_HELPERS(/obj/structure/mirror, 28)
+/obj/structure/mirror/Initialize(mapload)
+ . = ..()
+ find_and_hang_on_wall()
+
/obj/structure/mirror/broken
icon_state = "mirror_broke"
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 19e43c8bc3793a..c8a97f0cb8a113 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -362,10 +362,8 @@ GLOBAL_LIST_EMPTY(crematoriums)
var/list/icecreams = list()
for(var/mob/living/i_scream as anything in get_all_contents_type(/mob/living))
var/obj/item/food/icecream/IC = new /obj/item/food/icecream(
- /* loc = */ null,
- /* starting_reagent_purity = */ null,
- /* no_base_reagents = */ FALSE,
- /* prefill_flavours = */ list(ICE_CREAM_MOB = list(null, i_scream.name))
+ loc = null,
+ prefill_flavours = list(ICE_CREAM_MOB = list(null, i_scream.name))
)
icecreams += IC
. = ..()
diff --git a/code/game/objects/structures/noticeboard.dm b/code/game/objects/structures/noticeboard.dm
index 9d09a7470185d4..534622619d122f 100644
--- a/code/game/objects/structures/noticeboard.dm
+++ b/code/game/objects/structures/noticeboard.dm
@@ -26,6 +26,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/noticeboard, 32)
I.forceMove(src)
notices++
update_appearance(UPDATE_ICON)
+ find_and_hang_on_wall()
//attaching papers!!
/obj/structure/noticeboard/attackby(obj/item/O, mob/user, params)
diff --git a/code/game/objects/structures/signs/_signs.dm b/code/game/objects/structures/signs/_signs.dm
index cdb7ba0811f2aa..b8709334c37f6c 100644
--- a/code/game/objects/structures/signs/_signs.dm
+++ b/code/game/objects/structures/signs/_signs.dm
@@ -14,6 +14,8 @@
var/is_editable = FALSE
///sign_change_name is used to make nice looking, alphebetized and categorized names when you use a pen on any sign item or structure which is_editable.
var/sign_change_name
+ ///Callback to the knock down proc for wallmounting behavior.
+ var/knock_down_callback
/datum/armor/structure_sign
melee = 50
@@ -23,6 +25,12 @@
/obj/structure/sign/Initialize(mapload)
. = ..()
register_context()
+ knock_down_callback = CALLBACK(src, PROC_REF(knock_down))
+ find_and_hang_on_wall(custom_drop_callback = knock_down_callback)
+
+/obj/structure/sign/Destroy()
+ . = ..()
+ knock_down_callback = null
/obj/structure/sign/add_context(atom/source, list/context, obj/item/held_item, mob/user)
. = ..()
@@ -55,18 +63,7 @@
playsound(src, 'sound/items/deconstruct.ogg', 50, TRUE)
user.visible_message(span_notice("[user] unfastens [src]."), \
span_notice("You unfasten [src]."))
- var/obj/item/sign/unwrenched_sign = new (get_turf(user))
- if(type != /obj/structure/sign/blank) //If it's still just a basic sign backing, we can (and should) skip some of the below variable transfers.
- unwrenched_sign.name = name //Copy over the sign structure variables to the sign item we're creating when we unwrench a sign.
- unwrenched_sign.desc = "[desc] It can be placed on a wall."
- unwrenched_sign.icon = icon
- unwrenched_sign.icon_state = icon_state
- unwrenched_sign.sign_path = type
- unwrenched_sign.set_custom_materials(custom_materials) //This is here so picture frames and wooden things don't get messed up.
- unwrenched_sign.is_editable = is_editable
- unwrenched_sign.update_integrity(get_integrity()) //Transfer how damaged it is.
- unwrenched_sign.setDir(dir)
- qdel(src) //The sign structure on the wall goes poof and only the sign item from unwrenching remains.
+ knock_down(user)
return TRUE
/obj/structure/sign/welder_act(mob/living/user, obj/item/I)
@@ -115,6 +112,29 @@
return
return ..()
+/**
+ * This is called when a sign is removed from a wall, either through deconstruction or being knocked down.
+ * @param mob/living/user The user who removed the sign, if it was knocked down by a mob.
+ */
+/obj/structure/sign/proc/knock_down(mob/living/user)
+ var/turf/drop_turf
+ if(user)
+ drop_turf = get_turf(user)
+ else
+ drop_turf = drop_location()
+ var/obj/item/sign/unwrenched_sign = new (drop_turf)
+ if(type != /obj/structure/sign/blank) //If it's still just a basic sign backing, we can (and should) skip some of the below variable transfers.
+ unwrenched_sign.name = name //Copy over the sign structure variables to the sign item we're creating when we unwrench a sign.
+ unwrenched_sign.desc = "[desc] It can be placed on a wall."
+ unwrenched_sign.icon = icon
+ unwrenched_sign.icon_state = icon_state
+ unwrenched_sign.sign_path = type
+ unwrenched_sign.set_custom_materials(custom_materials) //This is here so picture frames and wooden things don't get messed up.
+ unwrenched_sign.is_editable = is_editable
+ unwrenched_sign.update_integrity(get_integrity()) //Transfer how damaged it is.
+ unwrenched_sign.setDir(dir)
+ qdel(src) //The sign structure on the wall goes poof and only the sign item from unwrenching remains.
+
/obj/structure/sign/blank //This subtype is necessary for now because some other things (posters, picture frames, paintings) inheret from the parent type.
icon_state = "backing"
name = "sign backing"
@@ -211,6 +231,7 @@
playsound(target_turf, 'sound/items/deconstruct.ogg', 50, TRUE)
placed_sign.update_integrity(get_integrity())
placed_sign.setDir(dir)
+ placed_sign.find_and_hang_on_wall(TRUE, placed_sign.knock_down_callback)
qdel(src)
/obj/item/sign/welder_act(mob/living/user, obj/item/I)
diff --git a/code/game/objects/structures/tables_racks.dm b/code/game/objects/structures/tables_racks.dm
index 708291182341c6..a3ff544800f542 100644
--- a/code/game/objects/structures/tables_racks.dm
+++ b/code/game/objects/structures/tables_racks.dm
@@ -66,7 +66,7 @@
if(istype(held_item, /obj/item/toy/cards/deck))
var/obj/item/toy/cards/deck/dealer_deck = held_item
- if(dealer_deck.wielded)
+ if(HAS_TRAIT(dealer_deck, TRAIT_WIELDED))
context[SCREENTIP_CONTEXT_LMB] = "Deal card"
context[SCREENTIP_CONTEXT_RMB] = "Deal card faceup"
. = CONTEXTUAL_SCREENTIP_SET
@@ -238,7 +238,7 @@
if(istype(I, /obj/item/toy/cards/deck))
var/obj/item/toy/cards/deck/dealer_deck = I
- if(dealer_deck.wielded) // deal a card facedown on the table
+ if(HAS_TRAIT(dealer_deck, TRAIT_WIELDED)) // deal a card facedown on the table
var/obj/item/toy/singlecard/card = dealer_deck.draw(user)
if(card)
attackby(card, user, params)
@@ -284,7 +284,7 @@
/obj/structure/table/attackby_secondary(obj/item/weapon, mob/user, params)
if(istype(weapon, /obj/item/toy/cards/deck))
var/obj/item/toy/cards/deck/dealer_deck = weapon
- if(dealer_deck.wielded) // deal a card faceup on the table
+ if(HAS_TRAIT(dealer_deck, TRAIT_WIELDED)) // deal a card faceup on the table
var/obj/item/toy/singlecard/card = dealer_deck.draw(user)
if(card)
card.Flip()
diff --git a/code/game/objects/structures/toiletbong.dm b/code/game/objects/structures/toiletbong.dm
index cb8d98305126f1..45ce79f9c32e48 100644
--- a/code/game/objects/structures/toiletbong.dm
+++ b/code/game/objects/structures/toiletbong.dm
@@ -53,7 +53,7 @@
to_chat(user, span_userdanger("There was something disgusting in the pipes!"))
user.visible_message(span_danger("[user] spits out a mouse."))
user.adjust_disgust(50)
- user.vomit(10)
+ user.vomit(VOMIT_CATEGORY_DEFAULT)
var/mob/living/spawned_mob = new /mob/living/basic/mouse(get_turf(user))
spawned_mob.faction |= "[REF(user)]"
if(prob(50))
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index ec0ae01fbf6cae..b991bc0c2746f7 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -168,6 +168,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/urinal, 32)
/obj/structure/urinal/Initialize(mapload)
. = ..()
hidden_item = new /obj/item/food/urinalcake
+ find_and_hang_on_wall()
/obj/structure/urinal/attack_hand(mob/living/user, list/modifiers)
. = ..()
diff --git a/code/game/objects/structures/windoor_assembly.dm b/code/game/objects/structures/windoor_assembly.dm
index b4498699bf70c3..46eb13ee2b7c14 100644
--- a/code/game/objects/structures/windoor_assembly.dm
+++ b/code/game/objects/structures/windoor_assembly.dm
@@ -30,7 +30,7 @@
var/state = "01" //How far the door assembly has progressed
can_atmos_pass = ATMOS_PASS_PROC
-/obj/structure/windoor_assembly/Initialize(mapload, loc, set_dir)
+/obj/structure/windoor_assembly/Initialize(mapload, set_dir)
. = ..()
if(set_dir)
setDir(set_dir)
@@ -267,55 +267,11 @@
span_notice("You start prying the windoor into the frame..."))
if(W.use_tool(src, user, 40, volume=100) && electronics)
-
set_density(TRUE) //Shouldn't matter but just incase
- to_chat(user, span_notice("You finish the windoor."))
-
- if(secure)
- var/obj/machinery/door/window/brigdoor/windoor = new /obj/machinery/door/window/brigdoor(loc)
- if(facing == "l")
- windoor.icon_state = "leftsecureopen"
- windoor.base_state = "leftsecure"
- else
- windoor.icon_state = "rightsecureopen"
- windoor.base_state = "rightsecure"
- windoor.setDir(dir)
- windoor.set_density(FALSE)
-
- if(electronics.one_access)
- windoor.req_one_access = electronics.accesses
- else
- windoor.req_access = electronics.accesses
- windoor.electronics = electronics
- electronics.forceMove(windoor)
- if(created_name)
- windoor.name = created_name
- qdel(src)
- windoor.close()
-
- else
- var/obj/machinery/door/window/windoor = new /obj/machinery/door/window(loc)
- if(facing == "l")
- windoor.icon_state = "leftopen"
- windoor.base_state = "left"
- else
- windoor.icon_state = "rightopen"
- windoor.base_state = "right"
- windoor.setDir(dir)
- windoor.set_density(FALSE)
-
- if(electronics.one_access)
- windoor.req_one_access = electronics.accesses
- else
- windoor.req_access = electronics.accesses
- windoor.electronics = electronics
- electronics.forceMove(windoor)
- if(created_name)
- windoor.name = created_name
- qdel(src)
- windoor.close()
+ to_chat(user, span_notice("You finish the windoor."))
+ finish_door()
else
return ..()
@@ -323,6 +279,54 @@
//Update to reflect changes(if applicable)
update_appearance()
+/obj/structure/windoor_assembly/proc/finish_door()
+ var/obj/machinery/door/window/windoor
+ if(secure)
+ windoor = new /obj/machinery/door/window/brigdoor(loc)
+ if(facing == "l")
+ windoor.icon_state = "leftsecureopen"
+ windoor.base_state = "leftsecure"
+ else
+ windoor.icon_state = "rightsecureopen"
+ windoor.base_state = "rightsecure"
+
+ else
+ windoor = new /obj/machinery/door/window(loc)
+ if(facing == "l")
+ windoor.icon_state = "leftopen"
+ windoor.base_state = "left"
+ else
+ windoor.icon_state = "rightopen"
+ windoor.base_state = "right"
+
+ windoor.setDir(dir)
+ windoor.set_density(FALSE)
+ if(created_name)
+ windoor.name = created_name
+ else if(electronics.passed_name)
+ windoor.name = electronics.passed_name
+ if(electronics.one_access)
+ windoor.req_one_access = electronics.accesses
+ else
+ windoor.req_access = electronics.accesses
+ if(electronics.unres_sides)
+ windoor.unres_sides = electronics.unres_sides
+ switch(dir)
+ if(NORTH,SOUTH)
+ windoor.unres_sides &= ~EAST
+ windoor.unres_sides &= ~WEST
+ if(EAST,WEST)
+ windoor.unres_sides &= ~NORTH
+ windoor.unres_sides &= ~SOUTH
+ windoor.unres_sensor = TRUE
+ electronics.forceMove(windoor)
+ windoor.electronics = electronics
+ windoor.autoclose = TRUE
+ windoor.close()
+ windoor.update_appearance()
+
+ qdel(src)
+
/obj/structure/windoor_assembly/AltClick(mob/user)
return ..() // This hotkey is BLACKLISTED since it's used by /datum/component/simple_rotation
diff --git a/code/game/shuttle_engines.dm b/code/game/shuttle_engines.dm
index 4d6a96ca12c75d..159dab78ceeb83 100644
--- a/code/game/shuttle_engines.dm
+++ b/code/game/shuttle_engines.dm
@@ -33,6 +33,10 @@
fire = 50
acid = 70
+/obj/machinery/power/shuttle_engine/Initialize(mapload)
+ . = ..()
+ register_context()
+
/obj/machinery/power/shuttle_engine/connect_to_shuttle(mapload, obj/docking_port/mobile/port, obj/docking_port/stationary/dock)
. = ..()
if(!port)
@@ -49,6 +53,27 @@
unsync_ship()
return ..()
+/obj/machinery/power/shuttle_engine/examine(mob/user)
+ . = ..()
+ switch(engine_state)
+ if(ENGINE_UNWRENCHED)
+ . += span_notice("\The [src] is unbolted from the floor. It needs to be wrenched to the floor to be installed.")
+ if(ENGINE_WRENCHED)
+ . += span_notice("\The [src] is bolted to the floor and can be unbolted with a wrench. It needs to be welded to the floor to finish installation.")
+ if(ENGINE_WELDED)
+ . += span_notice("\The [src] is welded to the floor and can be unwelded. It is currently fully installed.")
+
+/obj/machinery/power/shuttle_engine/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
+ if(held_item?.tool_behaviour == TOOL_WELDER && engine_state == ENGINE_WRENCHED)
+ context[SCREENTIP_CONTEXT_LMB] = "Weld to Floor"
+ if(held_item?.tool_behaviour == TOOL_WELDER && engine_state == ENGINE_WELDED)
+ context[SCREENTIP_CONTEXT_LMB] = "Unweld from Floor"
+ if(held_item?.tool_behaviour == TOOL_WRENCH && engine_state == ENGINE_UNWRENCHED)
+ context[SCREENTIP_CONTEXT_LMB] = "Wrench to Floor"
+ if(held_item?.tool_behaviour == TOOL_WRENCH && engine_state == ENGINE_WRENCHED)
+ context[SCREENTIP_CONTEXT_LMB] = "Unwrench from Floor"
+ return CONTEXTUAL_SCREENTIP_SET
+
/**
* Called on destroy and when we need to unsync an engine from their ship.
*/
diff --git a/code/game/turfs/change_turf.dm b/code/game/turfs/change_turf.dm
index 6a7f533e4de8d9..f5b40238ed60c7 100644
--- a/code/game/turfs/change_turf.dm
+++ b/code/game/turfs/change_turf.dm
@@ -105,7 +105,9 @@ GLOBAL_LIST_INIT(blacklisted_automated_baseturfs, typecacheof(list(
//We do this here so anything that doesn't want to persist can clear itself
var/list/old_listen_lookup = _listen_lookup?.Copy()
var/list/old_signal_procs = _signal_procs?.Copy()
+ var/carryover_turf_flags = (RESERVATION_TURF | UNUSED_RESERVATION_TURF) & turf_flags
var/turf/new_turf = new path(src)
+ new_turf.turf_flags |= carryover_turf_flags
// WARNING WARNING
// Turfs DO NOT lose their signals when they get replaced, REMEMBER THIS
diff --git a/code/game/turfs/closed/_closed.dm b/code/game/turfs/closed/_closed.dm
index 5d913f196012ba..8ccaabc46c0afa 100644
--- a/code/game/turfs/closed/_closed.dm
+++ b/code/game/turfs/closed/_closed.dm
@@ -15,361 +15,3 @@
/turf/closed/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
return FALSE
-
-/turf/closed/indestructible
- name = "wall"
- desc = "Effectively impervious to conventional methods of destruction."
- icon = 'icons/turf/walls.dmi'
- explosive_resistance = 50
-
-/turf/closed/indestructible/rust_heretic_act()
- return
-
-/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)
- return FALSE
-
-/turf/closed/indestructible/Melt()
- to_be_destroyed = FALSE
- return src
-
-/turf/closed/indestructible/singularity_act()
- return
-
-/turf/closed/indestructible/attackby(obj/item/attacking_item, mob/user, params)
- if(istype(attacking_item, /obj/item/poster) && Adjacent(user))
- return place_poster(attacking_item, user)
-
- return ..()
-
-/turf/closed/indestructible/oldshuttle
- name = "strange shuttle wall"
- icon = 'icons/turf/shuttleold.dmi'
- icon_state = "block"
-
-/turf/closed/indestructible/weeb
- name = "paper wall"
- desc = "Reinforced paper walling. Someone really doesn't want you to leave."
- icon = 'icons/obj/smooth_structures/paperframes.dmi'
- icon_state = "paperframes-0"
- base_icon_state = "paperframes"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_PAPERFRAME
- canSmoothWith = SMOOTH_GROUP_PAPERFRAME
- var/static/mutable_appearance/indestructible_paper = mutable_appearance('icons/obj/smooth_structures/paperframes.dmi',icon_state = "paper", layer = CLOSED_TURF_LAYER - 0.1)
-
-/turf/closed/indestructible/weeb/Initialize(mapload)
- . = ..()
- update_appearance()
-
-/turf/closed/indestructible/weeb/update_overlays()
- . = ..()
- . += indestructible_paper
-
-/turf/closed/indestructible/sandstone
- name = "sandstone wall"
- desc = "A wall with sandstone plating. Rough."
- icon = 'icons/turf/walls/sandstone_wall.dmi'
- icon_state = "sandstone_wall-0"
- base_icon_state = "sandstone_wall"
- baseturfs = /turf/closed/indestructible/sandstone
- smoothing_flags = SMOOTH_BITMASK
-
-/turf/closed/indestructible/oldshuttle/corner
- icon_state = "corner"
-
-/turf/closed/indestructible/splashscreen
- name = "Space Station 13"
- desc = null
- icon = 'icons/blanks/blank_title.png'
- icon_state = ""
- pixel_x = 0 // SKYRAT EDIT - Re-centering the title screen - ORIGINAL: pixel_x = -64
- plane = SPLASHSCREEN_PLANE
- bullet_bounce_sound = null
-
-INITIALIZE_IMMEDIATE(/turf/closed/indestructible/splashscreen)
-/* SKYRAT EDIT REMOVAL
-/turf/closed/indestructible/splashscreen/Initialize(mapload)
- . = ..()
- SStitle.splash_turf = src
- if(SStitle.icon)
- icon = SStitle.icon
- handle_generic_titlescreen_sizes()
-
-///helper proc that will center the screen if the icon is changed to a generic width, to make admins have to fudge around with pixel_x less. returns null
-/turf/closed/indestructible/splashscreen/proc/handle_generic_titlescreen_sizes()
- var/icon/size_check = icon(SStitle.icon, icon_state)
- var/width = size_check.Width()
- if(width == 480) // 480x480 is nonwidescreen
- pixel_x = 0
- else if(width == 608) // 608x480 is widescreen
- pixel_x = -64
- // SKYRAT EDIT START - Wider widescreen
- else if(width == 672) // Skyrat's widescreen is slightly wider than /tg/'s, so we need to accomodate that too.
- pixel_x = -96
- // SKYRAT EDIT END
-
-/turf/closed/indestructible/splashscreen/vv_edit_var(var_name, var_value)
- . = ..()
- if(.)
- switch(var_name)
- if(NAMEOF(src, icon))
- SStitle.icon = icon
- handle_generic_titlescreen_sizes()
-*/
-/turf/closed/indestructible/start_area
- name = null
- desc = null
- mouse_opacity = MOUSE_OPACITY_TRANSPARENT
-
-/turf/closed/indestructible/reinforced
- name = "reinforced wall"
- desc = "A huge chunk of reinforced metal used to separate rooms. Effectively impervious to conventional methods of destruction."
- icon = 'icons/turf/walls/reinforced_wall.dmi'
- icon_state = "reinforced_wall-0"
- base_icon_state = "reinforced_wall"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
- canSmoothWith = SMOOTH_GROUP_WALLS
-
-/turf/closed/indestructible/riveted
- icon = 'icons/turf/walls/riveted.dmi'
- icon_state = "riveted-0"
- base_icon_state = "riveted"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS
- canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
-
-/turf/closed/indestructible/syndicate
- icon = 'icons/turf/walls/plastitanium_wall.dmi'
- icon_state = "plastitanium_wall-0"
- base_icon_state = "plastitanium_wall"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_SYNDICATE_WALLS
- canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_SYNDICATE_WALLS
-
-/turf/closed/indestructible/riveted/uranium
- icon = 'icons/turf/walls/uranium_wall.dmi'
- icon_state = "uranium_wall-0"
- base_icon_state = "uranium_wall"
- smoothing_flags = SMOOTH_BITMASK
-
-/turf/closed/indestructible/riveted/plastinum
- name = "plastinum wall"
- desc = "A luxurious wall made out of a plasma-platinum alloy. Effectively impervious to conventional methods of destruction."
- icon = 'icons/turf/walls/plastinum_wall.dmi'
- icon_state = "plastinum_wall-0"
- base_icon_state = "plastinum_wall"
- smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
- smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_PLASTINUM_WALLS + SMOOTH_GROUP_CLOSED_TURFS
- canSmoothWith = SMOOTH_GROUP_PLASTINUM_WALLS
-
-/turf/closed/indestructible/riveted/plastinum/nodiagonal
- icon_state = "map-shuttle_nd"
- smoothing_flags = SMOOTH_BITMASK
-
-/turf/closed/indestructible/wood
- icon = 'icons/turf/walls/wood_wall.dmi'
- icon_state = "wood_wall-0"
- base_icon_state = "wood_wall"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_WOOD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
- canSmoothWith = SMOOTH_GROUP_WOOD_WALLS
-
-
-/turf/closed/indestructible/alien
- name = "alien wall"
- desc = "A wall with alien alloy plating."
- icon = 'icons/turf/walls/abductor_wall.dmi'
- icon_state = "abductor_wall-0"
- base_icon_state = "abductor_wall"
- smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
- smoothing_groups = SMOOTH_GROUP_ABDUCTOR_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
- canSmoothWith = SMOOTH_GROUP_ABDUCTOR_WALLS
-
-
-/turf/closed/indestructible/cult
- name = "runed metal wall"
- desc = "A cold metal wall engraved with indecipherable symbols. Studying them causes your head to pound. Effectively impervious to conventional methods of destruction."
- icon = 'icons/turf/walls/cult_wall.dmi'
- icon_state = "cult_wall-0"
- base_icon_state = "cult_wall"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
- canSmoothWith = SMOOTH_GROUP_WALLS
-
-
-/turf/closed/indestructible/abductor
- icon_state = "alien1"
-
-/turf/closed/indestructible/opshuttle
- icon_state = "wall3"
-
-
-/turf/closed/indestructible/fakeglass
- name = "window"
- icon = 'icons/obj/smooth_structures/reinforced_window.dmi'
- icon_state = "fake_window"
- base_icon_state = "reinforced_window"
- opacity = FALSE
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
- canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
-
-/turf/closed/indestructible/fakeglass/Initialize(mapload)
- . = ..()
- underlays += mutable_appearance('icons/obj/structures.dmi', "grille", layer - 0.01) //add a grille underlay
- underlays += mutable_appearance('icons/turf/floors.dmi', "plating", layer - 0.02) //add the plating underlay, below the grille
-
-/turf/closed/indestructible/opsglass
- name = "window"
- icon = 'icons/obj/smooth_structures/plastitanium_window.dmi'
- icon_state = "plastitanium_window-0"
- base_icon_state = "plastitanium_window"
- opacity = FALSE
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
- canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
-
-/turf/closed/indestructible/opsglass/Initialize(mapload)
- . = ..()
- icon_state = null
- underlays += mutable_appearance('icons/obj/structures.dmi', "grille", layer - 0.01)
- underlays += mutable_appearance('icons/turf/floors.dmi', "plating", layer - 0.02)
-
-/turf/closed/indestructible/fakedoor
- name = "airlock"
- icon = 'icons/obj/doors/airlocks/centcom/centcom.dmi'
- icon_state = "fake_door"
-
-/turf/closed/indestructible/fakedoor/maintenance
- icon = 'icons/obj/doors/airlocks/hatch/maintenance.dmi'
-
-/turf/closed/indestructible/fakedoor/glass_airlock
- icon = 'icons/obj/doors/airlocks/external/external.dmi'
- opacity = FALSE
-
-/turf/closed/indestructible/fakedoor/engineering
- icon = 'icons/obj/doors/airlocks/station/engineering.dmi'
-
-/turf/closed/indestructible/rock
- name = "dense rock"
- desc = "An extremely densely-packed rock, most mining tools or explosives would never get through this."
- icon = 'icons/turf/mining.dmi'
- icon_state = "rock"
-
-/turf/closed/indestructible/rock/snow
- name = "mountainside"
- desc = "An extremely densely-packed rock, sheeted over with centuries worth of ice and snow."
- icon = 'icons/turf/walls.dmi'
- icon_state = "snowrock"
- bullet_sizzle = TRUE
- bullet_bounce_sound = null
-
-/turf/closed/indestructible/rock/snow/ice
- name = "iced rock"
- desc = "Extremely densely-packed sheets of ice and rock, forged over the years of the harsh cold."
- icon = 'icons/turf/walls.dmi'
- icon_state = "icerock"
-
-/turf/closed/indestructible/rock/snow/ice/ore
- icon = 'icons/turf/walls/icerock_wall.dmi'
- icon_state = "icerock_wall-0"
- base_icon_state = "icerock_wall"
- smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
- canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
- pixel_x = -4
- pixel_y = -4
-
-/turf/closed/indestructible/paper
- name = "thick paper wall"
- desc = "A wall layered with impenetrable sheets of paper."
- icon = 'icons/turf/walls.dmi'
- icon_state = "paperwall"
-
-/turf/closed/indestructible/necropolis
- name = "necropolis wall"
- desc = "A seemingly impenetrable wall."
- icon = 'icons/turf/walls.dmi'
- icon_state = "necro"
- explosive_resistance = 50
- baseturfs = /turf/closed/indestructible/necropolis
-
-/turf/closed/indestructible/necropolis/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "necro1"
- return TRUE
-
-/turf/closed/indestructible/iron
- name = "impervious iron wall"
- desc = "A wall with tough iron plating."
- icon = 'icons/turf/walls/iron_wall.dmi'
- icon_state = "iron_wall-0"
- base_icon_state = "iron_wall"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_IRON_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
- canSmoothWith = SMOOTH_GROUP_IRON_WALLS
- opacity = FALSE
-
-/turf/closed/indestructible/riveted/boss
- name = "necropolis wall"
- desc = "A thick, seemingly indestructible stone wall."
- icon = 'icons/turf/walls/boss_wall.dmi'
- icon_state = "boss_wall-0"
- base_icon_state = "boss_wall"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_BOSS_WALLS
- canSmoothWith = SMOOTH_GROUP_BOSS_WALLS
- explosive_resistance = 50
- baseturfs = /turf/closed/indestructible/riveted/boss
-
-/turf/closed/indestructible/riveted/boss/see_through
- opacity = FALSE
-
-/turf/closed/indestructible/riveted/boss/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
- underlay_appearance.icon = 'icons/turf/floors.dmi'
- underlay_appearance.icon_state = "basalt"
- return TRUE
-
-/turf/closed/indestructible/riveted/hierophant
- name = "wall"
- desc = "A wall made out of a strange metal. The squares on it pulse in a predictable pattern."
- icon = 'icons/turf/walls/hierophant_wall.dmi'
- icon_state = "wall"
- smoothing_flags = SMOOTH_CORNERS
- smoothing_groups = SMOOTH_GROUP_HIERO_WALL
- canSmoothWith = SMOOTH_GROUP_HIERO_WALL
-
-/turf/closed/indestructible/resin
- name = "resin wall"
- icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi'
- icon_state = "resin_wall-0"
- base_icon_state = "resin_wall"
- smoothing_flags = SMOOTH_BITMASK
- smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN
- canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS
-
-/turf/closed/indestructible/resin/membrane
- name = "resin membrane"
- icon = 'icons/obj/smooth_structures/alien/resin_membrane.dmi'
- icon_state = "resin_membrane-0"
- base_icon_state = "resin_membrane"
- opacity = FALSE
- smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN
- canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS
-
-/turf/closed/indestructible/resin/membrane/Initialize(mapload)
- . = ..()
- underlays += mutable_appearance('icons/turf/floors.dmi', "engine") // add the reinforced floor underneath
-
-/turf/closed/indestructible/grille
- name = "grille"
- icon = 'icons/obj/structures.dmi'
- icon_state = "grille"
- base_icon_state = "grille"
-
-/turf/closed/indestructible/grille/Initialize(mapload)
- . = ..()
- underlays += mutable_appearance('icons/turf/floors.dmi', "plating")
diff --git a/code/game/turfs/closed/indestructible.dm b/code/game/turfs/closed/indestructible.dm
new file mode 100644
index 00000000000000..b364ad428d0359
--- /dev/null
+++ b/code/game/turfs/closed/indestructible.dm
@@ -0,0 +1,363 @@
+/turf/closed/indestructible
+ name = "wall"
+ desc = "Effectively impervious to conventional methods of destruction."
+ icon = 'icons/turf/walls.dmi'
+ explosive_resistance = 50
+
+/turf/closed/indestructible/rust_heretic_act()
+ return
+
+/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)
+ return FALSE
+
+/turf/closed/indestructible/Melt()
+ to_be_destroyed = FALSE
+ return src
+
+/turf/closed/indestructible/singularity_act()
+ return
+
+/turf/closed/indestructible/attackby(obj/item/attacking_item, mob/user, params)
+ if(istype(attacking_item, /obj/item/poster) && Adjacent(user))
+ return place_poster(attacking_item, user)
+
+ return ..()
+
+/turf/closed/indestructible/oldshuttle
+ name = "strange shuttle wall"
+ icon = 'icons/turf/shuttleold.dmi'
+ icon_state = "block"
+
+/turf/closed/indestructible/weeb
+ name = "paper wall"
+ desc = "Reinforced paper walling. Someone really doesn't want you to leave."
+ icon = 'icons/obj/smooth_structures/paperframes.dmi'
+ icon_state = "paperframes-0"
+ base_icon_state = "paperframes"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_PAPERFRAME
+ canSmoothWith = SMOOTH_GROUP_PAPERFRAME
+ var/static/mutable_appearance/indestructible_paper = mutable_appearance('icons/obj/smooth_structures/paperframes.dmi',icon_state = "paper", layer = CLOSED_TURF_LAYER - 0.1)
+
+/turf/closed/indestructible/weeb/Initialize(mapload)
+ . = ..()
+ update_appearance()
+
+/turf/closed/indestructible/weeb/update_overlays()
+ . = ..()
+ . += indestructible_paper
+
+/turf/closed/indestructible/sandstone
+ name = "sandstone wall"
+ desc = "A wall with sandstone plating. Rough."
+ icon = 'icons/turf/walls/sandstone_wall.dmi'
+ icon_state = "sandstone_wall-0"
+ base_icon_state = "sandstone_wall"
+ baseturfs = /turf/closed/indestructible/sandstone
+ smoothing_flags = SMOOTH_BITMASK
+
+/turf/closed/indestructible/oldshuttle/corner
+ icon_state = "corner"
+
+/turf/closed/indestructible/splashscreen
+ name = "Space Station 13"
+ desc = null
+ icon = 'icons/blanks/blank_title.png'
+ icon_state = ""
+ pixel_x = 0 // SKYRAT EDIT - Re-centering the title screen - ORIGINAL: pixel_x = -64
+ plane = SPLASHSCREEN_PLANE
+ bullet_bounce_sound = null
+
+INITIALIZE_IMMEDIATE(/turf/closed/indestructible/splashscreen)
+/* SKYRAT EDIT REMOVAL
+/turf/closed/indestructible/splashscreen/Initialize(mapload)
+ . = ..()
+ SStitle.splash_turf = src
+ if(SStitle.icon)
+ icon = SStitle.icon
+ handle_generic_titlescreen_sizes()
+
+///helper proc that will center the screen if the icon is changed to a generic width, to make admins have to fudge around with pixel_x less. returns null
+/turf/closed/indestructible/splashscreen/proc/handle_generic_titlescreen_sizes()
+ var/icon/size_check = icon(SStitle.icon, icon_state)
+ var/width = size_check.Width()
+ if(width == 480) // 480x480 is nonwidescreen
+ pixel_x = 0
+ else if(width == 608) // 608x480 is widescreen
+ pixel_x = -64
+ // SKYRAT EDIT START - Wider widescreen
+ else if(width == 672) // Skyrat's widescreen is slightly wider than /tg/'s, so we need to accomodate that too.
+ pixel_x = -96
+ // SKYRAT EDIT END
+
+/turf/closed/indestructible/splashscreen/vv_edit_var(var_name, var_value)
+ . = ..()
+ if(.)
+ switch(var_name)
+ if(NAMEOF(src, icon))
+ SStitle.icon = icon
+ handle_generic_titlescreen_sizes()
+
+/turf/closed/indestructible/splashscreen/examine()
+ desc = pick(strings(SPLASH_FILE, "splashes"))
+ return ..()
+SKYRAT EDIT REMOVAL END */
+
+/turf/closed/indestructible/start_area
+ name = null
+ desc = null
+ mouse_opacity = MOUSE_OPACITY_TRANSPARENT
+
+/turf/closed/indestructible/reinforced
+ name = "reinforced wall"
+ desc = "A huge chunk of reinforced metal used to separate rooms. Effectively impervious to conventional methods of destruction."
+ icon = 'icons/turf/walls/reinforced_wall.dmi'
+ icon_state = "reinforced_wall-0"
+ base_icon_state = "reinforced_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_WALLS
+
+
+/turf/closed/indestructible/riveted
+ icon = 'icons/turf/walls/riveted.dmi'
+ icon_state = "riveted-0"
+ base_icon_state = "riveted"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
+
+/turf/closed/indestructible/syndicate
+ icon = 'icons/turf/walls/plastitanium_wall.dmi'
+ icon_state = "plastitanium_wall-0"
+ base_icon_state = "plastitanium_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_SYNDICATE_WALLS
+ canSmoothWith = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_AIRLOCK + SMOOTH_GROUP_PLASTITANIUM_WALLS + SMOOTH_GROUP_SYNDICATE_WALLS
+
+/turf/closed/indestructible/riveted/uranium
+ icon = 'icons/turf/walls/uranium_wall.dmi'
+ icon_state = "uranium_wall-0"
+ base_icon_state = "uranium_wall"
+ smoothing_flags = SMOOTH_BITMASK
+
+/turf/closed/indestructible/riveted/plastinum
+ name = "plastinum wall"
+ desc = "A luxurious wall made out of a plasma-platinum alloy. Effectively impervious to conventional methods of destruction."
+ icon = 'icons/turf/walls/plastinum_wall.dmi'
+ icon_state = "plastinum_wall-0"
+ base_icon_state = "plastinum_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_PLASTINUM_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_PLASTINUM_WALLS
+
+/turf/closed/indestructible/riveted/plastinum/nodiagonal
+ icon_state = "map-shuttle_nd"
+ smoothing_flags = SMOOTH_BITMASK
+
+/turf/closed/indestructible/wood
+ icon = 'icons/turf/walls/wood_wall.dmi'
+ icon_state = "wood_wall-0"
+ base_icon_state = "wood_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WOOD_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_WOOD_WALLS
+
+
+/turf/closed/indestructible/alien
+ name = "alien wall"
+ desc = "A wall with alien alloy plating."
+ icon = 'icons/turf/walls/abductor_wall.dmi'
+ icon_state = "abductor_wall-0"
+ base_icon_state = "abductor_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_DIAGONAL_CORNERS
+ smoothing_groups = SMOOTH_GROUP_ABDUCTOR_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_ABDUCTOR_WALLS
+
+
+/turf/closed/indestructible/cult
+ name = "runed metal wall"
+ desc = "A cold metal wall engraved with indecipherable symbols. Studying them causes your head to pound. Effectively impervious to conventional methods of destruction."
+ icon = 'icons/turf/walls/cult_wall.dmi'
+ icon_state = "cult_wall-0"
+ base_icon_state = "cult_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_WALLS
+
+
+/turf/closed/indestructible/abductor
+ icon_state = "alien1"
+
+/turf/closed/indestructible/opshuttle
+ icon_state = "wall3"
+
+
+/turf/closed/indestructible/fakeglass
+ name = "window"
+ icon = 'icons/obj/smooth_structures/reinforced_window.dmi'
+ icon_state = "fake_window"
+ base_icon_state = "reinforced_window"
+ opacity = FALSE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_WINDOW_FULLTILE
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE
+
+/turf/closed/indestructible/fakeglass/Initialize(mapload)
+ . = ..()
+ underlays += mutable_appearance('icons/obj/structures.dmi', "grille", layer - 0.01) //add a grille underlay
+ underlays += mutable_appearance('icons/turf/floors.dmi', "plating", layer - 0.02) //add the plating underlay, below the grille
+
+/turf/closed/indestructible/opsglass
+ name = "window"
+ icon = 'icons/obj/smooth_structures/plastitanium_window.dmi'
+ icon_state = "plastitanium_window-0"
+ base_icon_state = "plastitanium_window"
+ opacity = FALSE
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_SHUTTLE_PARTS + SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
+ canSmoothWith = SMOOTH_GROUP_WINDOW_FULLTILE_PLASTITANIUM
+
+/turf/closed/indestructible/opsglass/Initialize(mapload)
+ . = ..()
+ icon_state = null
+ underlays += mutable_appearance('icons/obj/structures.dmi', "grille", layer - 0.01)
+ underlays += mutable_appearance('icons/turf/floors.dmi', "plating", layer - 0.02)
+
+/turf/closed/indestructible/fakedoor
+ name = "airlock"
+ icon = 'icons/obj/doors/airlocks/centcom/centcom.dmi'
+ icon_state = "fake_door"
+
+/turf/closed/indestructible/fakedoor/maintenance
+ icon = 'icons/obj/doors/airlocks/hatch/maintenance.dmi'
+
+/turf/closed/indestructible/fakedoor/glass_airlock
+ icon = 'icons/obj/doors/airlocks/external/external.dmi'
+ opacity = FALSE
+
+/turf/closed/indestructible/fakedoor/engineering
+ icon = 'icons/obj/doors/airlocks/station/engineering.dmi'
+
+/turf/closed/indestructible/rock
+ name = "dense rock"
+ desc = "An extremely densely-packed rock, most mining tools or explosives would never get through this."
+ icon = 'icons/turf/mining.dmi'
+ icon_state = "rock"
+
+/turf/closed/indestructible/rock/snow
+ name = "mountainside"
+ desc = "An extremely densely-packed rock, sheeted over with centuries worth of ice and snow."
+ icon = 'icons/turf/walls.dmi'
+ icon_state = "snowrock"
+ bullet_sizzle = TRUE
+ bullet_bounce_sound = null
+
+/turf/closed/indestructible/rock/snow/ice
+ name = "iced rock"
+ desc = "Extremely densely-packed sheets of ice and rock, forged over the years of the harsh cold."
+ icon = 'icons/turf/walls.dmi'
+ icon_state = "icerock"
+
+/turf/closed/indestructible/rock/snow/ice/ore
+ icon = 'icons/turf/walls/icerock_wall.dmi'
+ icon_state = "icerock_wall-0"
+ base_icon_state = "icerock_wall"
+ smoothing_flags = SMOOTH_BITMASK | SMOOTH_BORDER
+ canSmoothWith = SMOOTH_GROUP_CLOSED_TURFS
+ pixel_x = -4
+ pixel_y = -4
+
+/turf/closed/indestructible/paper
+ name = "thick paper wall"
+ desc = "A wall layered with impenetrable sheets of paper."
+ icon = 'icons/turf/walls.dmi'
+ icon_state = "paperwall"
+
+/turf/closed/indestructible/necropolis
+ name = "necropolis wall"
+ desc = "A seemingly impenetrable wall."
+ icon = 'icons/turf/walls.dmi'
+ icon_state = "necro"
+ explosive_resistance = 50
+ baseturfs = /turf/closed/indestructible/necropolis
+
+/turf/closed/indestructible/necropolis/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ underlay_appearance.icon = 'icons/turf/floors.dmi'
+ underlay_appearance.icon_state = "necro1"
+ return TRUE
+
+/turf/closed/indestructible/iron
+ name = "impervious iron wall"
+ desc = "A wall with tough iron plating."
+ icon = 'icons/turf/walls/iron_wall.dmi'
+ icon_state = "iron_wall-0"
+ base_icon_state = "iron_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_IRON_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
+ canSmoothWith = SMOOTH_GROUP_IRON_WALLS
+ opacity = FALSE
+
+/turf/closed/indestructible/riveted/boss
+ name = "necropolis wall"
+ desc = "A thick, seemingly indestructible stone wall."
+ icon = 'icons/turf/walls/boss_wall.dmi'
+ icon_state = "boss_wall-0"
+ base_icon_state = "boss_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_CLOSED_TURFS + SMOOTH_GROUP_BOSS_WALLS
+ canSmoothWith = SMOOTH_GROUP_BOSS_WALLS
+ explosive_resistance = 50
+ baseturfs = /turf/closed/indestructible/riveted/boss
+
+/turf/closed/indestructible/riveted/boss/see_through
+ opacity = FALSE
+
+/turf/closed/indestructible/riveted/boss/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir)
+ underlay_appearance.icon = 'icons/turf/floors.dmi'
+ underlay_appearance.icon_state = "basalt"
+ return TRUE
+
+/turf/closed/indestructible/riveted/hierophant
+ name = "wall"
+ desc = "A wall made out of a strange metal. The squares on it pulse in a predictable pattern."
+ icon = 'icons/turf/walls/hierophant_wall.dmi'
+ icon_state = "wall"
+ smoothing_flags = SMOOTH_CORNERS
+ smoothing_groups = SMOOTH_GROUP_HIERO_WALL
+ canSmoothWith = SMOOTH_GROUP_HIERO_WALL
+
+/turf/closed/indestructible/resin
+ name = "resin wall"
+ icon = 'icons/obj/smooth_structures/alien/resin_wall.dmi'
+ icon_state = "resin_wall-0"
+ base_icon_state = "resin_wall"
+ smoothing_flags = SMOOTH_BITMASK
+ smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN
+ canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS
+
+/turf/closed/indestructible/resin/membrane
+ name = "resin membrane"
+ icon = 'icons/obj/smooth_structures/alien/resin_membrane.dmi'
+ icon_state = "resin_membrane-0"
+ base_icon_state = "resin_membrane"
+ opacity = FALSE
+ smoothing_groups = SMOOTH_GROUP_ALIEN_WALLS + SMOOTH_GROUP_ALIEN_RESIN
+ canSmoothWith = SMOOTH_GROUP_ALIEN_WALLS
+
+/turf/closed/indestructible/resin/membrane/Initialize(mapload)
+ . = ..()
+ underlays += mutable_appearance('icons/turf/floors.dmi', "engine") // add the reinforced floor underneath
+
+/turf/closed/indestructible/grille
+ name = "grille"
+ icon = 'icons/obj/structures.dmi'
+ icon_state = "grille"
+ base_icon_state = "grille"
+
+/turf/closed/indestructible/grille/Initialize(mapload)
+ . = ..()
+ underlays += mutable_appearance('icons/turf/floors.dmi', "plating")
diff --git a/code/game/turfs/open/_open.dm b/code/game/turfs/open/_open.dm
index 72e056288f3386..24c4c4914ec218 100644
--- a/code/game/turfs/open/_open.dm
+++ b/code/game/turfs/open/_open.dm
@@ -124,6 +124,9 @@
/turf/open/indestructible/light
icon_state = "light_on-1"
+/turf/open/indestructible/plating
+ icon_state = "plating"
+
/turf/open/indestructible/permalube
icon_state = "darkfull"
diff --git a/code/game/turfs/open/asteroid.dm b/code/game/turfs/open/asteroid.dm
index cf7e07e75f359d..c15261a988059d 100644
--- a/code/game/turfs/open/asteroid.dm
+++ b/code/game/turfs/open/asteroid.dm
@@ -235,7 +235,8 @@ GLOBAL_LIST_EMPTY(dug_up_basalt)
turf_flags = CAN_BE_DIRTY_1 | IS_SOLID | NO_RUST | NO_RUINS
/turf/open/misc/asteroid/snow/icemoon/do_not_scrape
- turf_flags = CAN_BE_DIRTY_1 | IS_SOLID | NO_RUST | NO_CLEARING
+ flags_1 = CAN_BE_DIRTY_1
+ turf_flags = IS_SOLID | NO_RUST | NO_CLEARING
/turf/open/lava/plasma/ice_moon
initial_gas_mix = ICEMOON_DEFAULT_ATMOS
diff --git a/code/game/turfs/open/floor.dm b/code/game/turfs/open/floor.dm
index 6e8acf576cbcb6..a0d1a191bc6294 100644
--- a/code/game/turfs/open/floor.dm
+++ b/code/game/turfs/open/floor.dm
@@ -295,15 +295,11 @@
continue
balloon_alert(user, "there's already a door!")
return FALSE
- var/obj/machinery/door/window/new_window = new the_rcd.airlock_type(src, user.dir, the_rcd.airlock_electronics?.unres_sides)
- if(the_rcd.airlock_electronics)
- new_window.name = the_rcd.airlock_electronics.passed_name || initial(new_window.name)
- if(the_rcd.airlock_electronics.one_access)
- new_window.req_one_access = the_rcd.airlock_electronics.accesses.Copy()
- else
- new_window.req_access = the_rcd.airlock_electronics.accesses.Copy()
- new_window.autoclose = TRUE
- new_window.update_appearance()
+ //create the assembly and let it finish itself
+ var/obj/structure/windoor_assembly/assembly = new /obj/structure/windoor_assembly(src, user.dir)
+ assembly.secure = ispath(the_rcd.airlock_type, /obj/machinery/door/window/brigdoor)
+ assembly.electronics = the_rcd.airlock_electronics.create_copy(assembly)
+ assembly.finish_door()
return TRUE
for(var/obj/machinery/door/door in src)
@@ -311,29 +307,15 @@
continue
balloon_alert(user, "there's already a door!")
return FALSE
- var/obj/machinery/door/airlock/new_airlock = new the_rcd.airlock_type(src)
- new_airlock.electronics = new /obj/item/electronics/airlock(new_airlock)
- if(the_rcd.airlock_electronics)
- new_airlock.electronics.accesses = the_rcd.airlock_electronics.accesses.Copy()
- new_airlock.electronics.one_access = the_rcd.airlock_electronics.one_access
- new_airlock.electronics.unres_sides = the_rcd.airlock_electronics.unres_sides
- new_airlock.electronics.passed_name = the_rcd.airlock_electronics.passed_name
- new_airlock.electronics.passed_cycle_id = the_rcd.airlock_electronics.passed_cycle_id
- new_airlock.electronics.shell = the_rcd.airlock_electronics.shell
- if(new_airlock.electronics.one_access)
- new_airlock.req_one_access = new_airlock.electronics.accesses
+ //create the assembly and let it finish itself
+ var/obj/structure/door_assembly/assembly = new (src)
+ if(ispath(the_rcd.airlock_type, /obj/machinery/door/airlock/glass))
+ assembly.glass = TRUE
+ assembly.glass_type = the_rcd.airlock_type
else
- new_airlock.req_access = new_airlock.electronics.accesses
- if(new_airlock.electronics.unres_sides)
- new_airlock.unres_sides = new_airlock.electronics.unres_sides
- new_airlock.unres_sensor = TRUE
- if(new_airlock.electronics.passed_name)
- new_airlock.name = sanitize(new_airlock.electronics.passed_name)
- if(new_airlock.electronics.passed_cycle_id)
- new_airlock.closeOtherId = new_airlock.electronics.passed_cycle_id
- new_airlock.update_other_id()
- new_airlock.autoclose = TRUE
- new_airlock.update_appearance()
+ assembly.airlock_type = the_rcd.airlock_type
+ assembly.electronics = the_rcd.airlock_electronics.create_copy(assembly)
+ assembly.finish_door()
return TRUE
if(RCD_DECONSTRUCT)
if(rcd_proof)
diff --git a/code/game/turfs/open/openspace.dm b/code/game/turfs/open/openspace.dm
index 12c7ade160c41d..91437e727a79b7 100644
--- a/code/game/turfs/open/openspace.dm
+++ b/code/game/turfs/open/openspace.dm
@@ -179,7 +179,7 @@
/turf/open/openspace/icemoon/Initialize(mapload)
. = ..()
- var/turf/T = below()
+ var/turf/T = GET_TURF_BELOW(src)
//I wonder if I should error here
if(!T)
return
diff --git a/code/game/turfs/open/space/transit.dm b/code/game/turfs/open/space/transit.dm
index 3e3f7b0c55ef88..02d56a3be1e17f 100644
--- a/code/game/turfs/open/space/transit.dm
+++ b/code/game/turfs/open/space/transit.dm
@@ -4,7 +4,7 @@
icon_state = "black"
dir = SOUTH
baseturfs = /turf/open/space/transit
- flags_1 = NOJAUNT //This line goes out to every wizard that ever managed to escape the den. I'm sorry.
+ turf_flags = NOJAUNT //This line goes out to every wizard that ever managed to escape the den. I'm sorry.
explosive_resistance = INFINITY
/turf/open/space/transit/Initialize(mapload)
diff --git a/code/game/turfs/turf.dm b/code/game/turfs/turf.dm
index 6010df76cd3cc8..3e2f43517aa31f 100644
--- a/code/game/turfs/turf.dm
+++ b/code/game/turfs/turf.dm
@@ -639,32 +639,19 @@ GLOBAL_LIST_EMPTY(station_turfs)
/turf/AllowDrop()
return TRUE
-/turf/proc/add_vomit_floor(mob/living/M, vomit_type = VOMIT_TOXIC, purge_ratio = 0.1)
+/turf/proc/add_vomit_floor(mob/living/vomiter, vomit_type = /obj/effect/decal/cleanable/vomit, vomit_flags, purge_ratio = 0.1)
+ var/obj/effect/decal/cleanable/vomit/throw_up = new vomit_type (src, vomiter.get_static_viruses())
- var/obj/effect/decal/cleanable/vomit/vomit
- if (vomit_type == VOMIT_NEBULA)
- vomit = new /obj/effect/decal/cleanable/vomit/nebula(src, M.get_static_viruses())
- else
- vomit = new /obj/effect/decal/cleanable/vomit(src, M.get_static_viruses())
+ // if the vomit combined, apply toxicity and reagents to the old vomit
+ if (QDELETED(throw_up))
+ throw_up = locate() in src
+ if(isnull(throw_up))
+ return
- //if the vomit combined, apply toxicity and reagents to the old vomit
- if (QDELETED(vomit))
- vomit = locate() in src
- if(!vomit)
+ if(!iscarbon(vomiter) || (purge_ratio == 0))
return
- // Apply the proper icon set based on vomit type
- if(vomit_type == VOMIT_PURPLE)
- vomit.icon_state = "vomitpurp_[pick(1,4)]"
- else if (vomit_type == VOMIT_TOXIC)
- vomit.icon_state = "vomittox_[pick(1,4)]"
- //SKYRAT EDIT START - Nanite Slurry
- else if (vomit_type == VOMIT_NANITE)
- vomit.name = "metallic slurry"
- vomit.desc = "A puddle of metallic slurry that looks vaguely like very fine sand. It almost seems like it's moving..."
- vomit.icon_state = "vomitnanite_[pick(1,4)]"
- // SKYRAT EDIT END
- if (purge_ratio && iscarbon(M))
- clear_reagents_to_vomit_pool(M, vomit, purge_ratio)
+
+ clear_reagents_to_vomit_pool(vomiter, throw_up, purge_ratio)
/proc/clear_reagents_to_vomit_pool(mob/living/carbon/M, obj/effect/decal/cleanable/vomit/V, purge_ratio = 0.1)
var/obj/item/organ/internal/stomach/belly = M.get_organ_slot(ORGAN_SLOT_STOMACH)
diff --git a/code/game/world.dm b/code/game/world.dm
index 380448d9fcfe77..e851bb992d4e32 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -399,31 +399,35 @@ GLOBAL_VAR(restart_counter)
else
hub_password = "SORRYNOPASSWORD"
-// If this is called as a part of maploading you cannot call it on the newly loaded map zs, because those get handled later on in the pipeline
-/world/proc/increaseMaxX(new_maxx, max_zs_to_load = maxz)
+/**
+ * Handles incresing the world's maxx var and intializing the new turfs and assigning them to the global area.
+ * If map_load_z_cutoff is passed in, it will only load turfs up to that z level, inclusive.
+ * This is because maploading will handle the turfs it loads itself.
+ */
+/world/proc/increase_max_x(new_maxx, map_load_z_cutoff = maxz)
if(new_maxx <= maxx)
return
var/old_max = world.maxx
maxx = new_maxx
- if(!max_zs_to_load)
+ if(!map_load_z_cutoff)
return
var/area/global_area = GLOB.areas_by_type[world.area] // We're guaranteed to be touching the global area, so we'll just do this
var/list/to_add = block(
locate(old_max + 1, 1, 1),
- locate(maxx, maxy, max_zs_to_load))
+ locate(maxx, maxy, map_load_z_cutoff))
global_area.contained_turfs += to_add
-/world/proc/increaseMaxY(new_maxy, max_zs_to_load = maxz)
+/world/proc/increase_max_y(new_maxy, map_load_z_cutoff = maxz)
if(new_maxy <= maxy)
return
var/old_maxy = maxy
maxy = new_maxy
- if(!max_zs_to_load)
+ if(!map_load_z_cutoff)
return
var/area/global_area = GLOB.areas_by_type[world.area] // We're guarenteed to be touching the global area, so we'll just do this
var/list/to_add = block(
locate(1, old_maxy + 1, 1),
- locate(maxx, maxy, max_zs_to_load))
+ locate(maxx, maxy, map_load_z_cutoff))
global_area.contained_turfs += to_add
/world/proc/incrementMaxZ()
diff --git a/code/modules/actionspeed/_actionspeed_modifier.dm b/code/modules/actionspeed/_actionspeed_modifier.dm
index 71bc966acf4dbf..761bfc3ff74a42 100644
--- a/code/modules/actionspeed/_actionspeed_modifier.dm
+++ b/code/modules/actionspeed/_actionspeed_modifier.dm
@@ -37,8 +37,11 @@ can next move
/// Other modification datums this conflicts with.
var/conflicts_with
-/datum/actionspeed_modifier/New()
+/datum/actionspeed_modifier/New(init_id)
. = ..()
+
+ id = init_id
+
if(!id)
id = "[type]" //We turn the path into a string.
diff --git a/code/modules/actionspeed/modifiers/wound.dm b/code/modules/actionspeed/modifiers/wound.dm
new file mode 100644
index 00000000000000..845399e07616b6
--- /dev/null
+++ b/code/modules/actionspeed/modifiers/wound.dm
@@ -0,0 +1,10 @@
+/datum/actionspeed_modifier/wound_interaction_inefficiency
+ variable = TRUE
+
+ var/datum/wound/parent
+
+/datum/actionspeed_modifier/wound_interaction_inefficiency/New(new_id, datum/wound/parent)
+
+ src.parent = parent
+
+ return ..()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index 913a0c55e53943..34e855230cb559 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -1059,7 +1059,7 @@ GLOBAL_PROTECT(admin_verbs_poll)
if(!isobserver(usr))
admin_ghost()
- usr.forceMove(coords2turf(reservation.bottom_left_coords))
+ usr.forceMove(reservation.bottom_left_turfs[1])
message_admins("[key_name_admin(usr)] has loaded lazy template '[choice]'")
to_chat(usr, span_boldnicegreen("Template loaded, you have been moved to the bottom left of the reservation."))
diff --git a/code/modules/admin/permissionedit.dm b/code/modules/admin/permissionedit.dm
index 5544f90fb74816..cf816fef8a7a1f 100644
--- a/code/modules/admin/permissionedit.dm
+++ b/code/modules/admin/permissionedit.dm
@@ -192,6 +192,10 @@
admin_ckey = add_admin(admin_ckey, admin_key, use_db)
if(!admin_ckey)
return
+
+ if(!admin_key) // Prevents failures in logging admin rank changes.
+ admin_key = admin_ckey
+
change_admin_rank(admin_ckey, admin_key, use_db, null, legacy_only)
if("remove")
remove_admin(admin_ckey, admin_key, use_db, D)
diff --git a/code/modules/admin/smites/become_object.dm b/code/modules/admin/smites/become_object.dm
new file mode 100644
index 00000000000000..5f1af4bee280ff
--- /dev/null
+++ b/code/modules/admin/smites/become_object.dm
@@ -0,0 +1,42 @@
+#define OBJECTIFY_TIME (5 SECONDS)
+
+/// Turns the target into an object (for instance bread)
+/datum/smite/objectify
+ name = "Become Object"
+ /// What are we going to turn them into?
+ var/atom/transform_path = /obj/item/food/bread/plain
+
+/datum/smite/objectify/configure(client/user)
+ var/attempted_target_path = input(
+ user,
+ "Enter typepath of an atom you'd like to turn your victim into.",
+ "Typepath",
+ "[/obj/item/food/bread/plain]",
+ ) as null|text
+
+ if (isnull(attempted_target_path))
+ return FALSE //The user pressed "Cancel"
+
+ var/desired_object = text2path(attempted_target_path)
+ if(!ispath(desired_object))
+ desired_object = pick_closest_path(attempted_target_path, get_fancy_list_of_atom_types())
+ if(isnull(desired_object) || !ispath(desired_object))
+ return FALSE //The user pressed "Cancel"
+ if(!ispath(desired_object, /atom))
+ tgui_alert(user, "ERROR: Incorrect / improper path given.")
+ return FALSE
+ transform_path = desired_object
+
+/datum/smite/objectify/effect(client/user, mob/living/target)
+ if (!isliving(target))
+ return // This doesn't work on ghosts
+ . = ..()
+ var/mutable_appearance/objectified_player = mutable_appearance(initial(transform_path.icon), initial(transform_path.icon_state))
+ objectified_player.pixel_x = initial(transform_path.pixel_x)
+ objectified_player.pixel_y = initial(transform_path.pixel_y)
+ var/mutable_appearance/transform_scanline = mutable_appearance('icons/effects/effects.dmi', "transform_effect")
+ target.transformation_animation(objectified_player, OBJECTIFY_TIME, transform_scanline.appearance)
+ target.Immobilize(OBJECTIFY_TIME, ignore_canstun = TRUE)
+ addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(objectify), target, transform_path), OBJECTIFY_TIME)
+
+#undef OBJECTIFY_TIME
diff --git a/code/modules/admin/smites/bloodless.dm b/code/modules/admin/smites/bloodless.dm
index db68a1cd3a2788..c970e920f2253d 100644
--- a/code/modules/admin/smites/bloodless.dm
+++ b/code/modules/admin/smites/bloodless.dm
@@ -9,7 +9,7 @@
return
var/mob/living/carbon/carbon_target = target
for(var/_limb in carbon_target.bodyparts)
- var/obj/item/bodypart/limb = _limb
+ var/obj/item/bodypart/limb = _limb // fine to use this raw, its a meme smite
var/type_wound = pick(list(/datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/moderate))
limb.force_wound_upwards(type_wound, smited = TRUE)
type_wound = pick(list(/datum/wound/slash/flesh/critical, /datum/wound/slash/flesh/severe, /datum/wound/slash/flesh/moderate))
diff --git a/code/modules/admin/smites/boneless.dm b/code/modules/admin/smites/boneless.dm
index 5d859669a6874c..bf402abdfdb629 100644
--- a/code/modules/admin/smites/boneless.dm
+++ b/code/modules/admin/smites/boneless.dm
@@ -11,11 +11,11 @@
var/mob/living/carbon/carbon_target = target
for(var/obj/item/bodypart/limb as anything in carbon_target.bodyparts)
- var/type_wound = pick(list(
- /datum/wound/blunt/bone/critical,
- /datum/wound/blunt/bone/severe,
- /datum/wound/blunt/bone/critical,
- /datum/wound/blunt/bone/severe,
- /datum/wound/blunt/bone/moderate,
+ var/severity = pick(list(
+ "[WOUND_SEVERITY_MODERATE]",
+ "[WOUND_SEVERITY_SEVERE]",
+ "[WOUND_SEVERITY_SEVERE]",
+ "[WOUND_SEVERITY_CRITICAL]",
+ "[WOUND_SEVERITY_CRITICAL]",
))
- limb.force_wound_upwards(type_wound, smited = TRUE)
+ carbon_target.cause_wound_of_type_and_severity(WOUND_BLUNT, limb, severity)
diff --git a/code/modules/admin/smites/bread.dm b/code/modules/admin/smites/bread.dm
deleted file mode 100644
index 22ed8836df3b22..00000000000000
--- a/code/modules/admin/smites/bread.dm
+++ /dev/null
@@ -1,14 +0,0 @@
-#define BREADIFY_TIME (5 SECONDS)
-
-/// Turns the target into bread
-/datum/smite/bread
- name = "Bread"
-
-/datum/smite/bread/effect(client/user, mob/living/target)
- . = ..()
- var/mutable_appearance/bread_appearance = mutable_appearance('icons/obj/food/burgerbread.dmi', "bread")
- var/mutable_appearance/transform_scanline = mutable_appearance('icons/effects/effects.dmi', "transform_effect")
- target.transformation_animation(bread_appearance, BREADIFY_TIME, transform_scanline.appearance)
- addtimer(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(breadify), target), BREADIFY_TIME)
-
-#undef BREADIFY_TIME
diff --git a/code/modules/admin/verbs/adminfun.dm b/code/modules/admin/verbs/adminfun.dm
index 32734a21d94d46..b1e0327510a5f1 100644
--- a/code/modules/admin/verbs/adminfun.dm
+++ b/code/modules/admin/verbs/adminfun.dm
@@ -219,9 +219,9 @@
return
smite.effect(src, target)
-///"Turns" people into bread. Really, we just add them to the contents of the bread food item.
-/proc/breadify(atom/movable/target)
- var/obj/item/food/bread/plain/smite/tomb = new(get_turf(target))
+/// "Turns" people into objects. Really, we just add them to the contents of the item.
+/proc/objectify(atom/movable/target, path)
+ var/atom/tomb = new path(get_turf(target))
target.forceMove(tomb)
target.AddComponent(/datum/component/itembound, tomb)
diff --git a/code/modules/admin/verbs/admingame.dm b/code/modules/admin/verbs/admingame.dm
index 13178d32a5d68f..8bc58a7876c29b 100644
--- a/code/modules/admin/verbs/admingame.dm
+++ b/code/modules/admin/verbs/admingame.dm
@@ -32,6 +32,20 @@
if(M.client)
body += " \[First Seen: [M.client.player_join_date]\]\[Byond account registered on: [M.client.account_join_date]\]"
+ // SKYRAT EDIT ADDITION START - Player Ranks
+ var/list/player_ranks = list()
+
+ if(SSplayer_ranks.is_donator(M.client, admin_bypass = FALSE))
+ player_ranks += "Donator"
+
+ if(SSplayer_ranks.is_mentor(M.client, admin_bypass = FALSE))
+ player_ranks += "Mentor"
+
+ if(SSplayer_ranks.is_veteran(M.client, admin_bypass = FALSE))
+ player_ranks += "Veteran"
+
+ body += "
Player Ranks: [length(player_ranks) ? player_ranks.Join(", ") : "None"]"
+ // SKYRAT EDIT END
body += "
CentCom Galactic Ban DB: "
if(CONFIG_GET(string/centcom_ban_db))
body += "Search"
diff --git a/code/modules/admin/verbs/commandreport.dm b/code/modules/admin/verbs/commandreport.dm
index 635cb28cbb28d5..badf3babb6698d 100644
--- a/code/modules/admin/verbs/commandreport.dm
+++ b/code/modules/admin/verbs/commandreport.dm
@@ -46,6 +46,8 @@
var/command_report_content
/// Whether the report's contents are announced.
var/announce_contents = TRUE
+ /// Whether a copy of the report is printed at every console.
+ var/print_report = TRUE
/// The sound that's going to accompany our message.
var/played_sound = DEFAULT_ANNOUNCEMENT_SOUND
/// A static list of preset names that can be chosen.
@@ -75,6 +77,7 @@
data["custom_name"] = custom_name
data["command_report_content"] = command_report_content
data["announce_contents"] = announce_contents
+ data["print_report"] = print_report
data["played_sound"] = played_sound
return data
@@ -103,6 +106,8 @@
played_sound = params["picked_sound"]
if("toggle_announce")
announce_contents = !announce_contents
+ if("toggle_printing")
+ print_report = !print_report
if("submit_report")
if(!command_name)
to_chat(ui_user, span_danger("You can't send a report with no command name."))
@@ -132,7 +137,9 @@
if(announce_contents)
priority_announce(command_report_content, null, report_sound, has_important_message = TRUE)
- print_command_report(command_report_content, "[announce_contents ? "" : "Classified "][command_name] Update", !announce_contents)
+
+ if(!announce_contents || print_report)
+ print_command_report(command_report_content, "[announce_contents ? "" : "Classified "][command_name] Update", !announce_contents)
change_command_name(original_command_name)
diff --git a/code/modules/admin/view_variables/debug_variables.dm b/code/modules/admin/view_variables/debug_variables.dm
index 24ee0032a33f5c..230a0e462844b0 100644
--- a/code/modules/admin/view_variables/debug_variables.dm
+++ b/code/modules/admin/view_variables/debug_variables.dm
@@ -90,7 +90,10 @@
for (var/i in GLOB.bitfields[name])
if (value & GLOB.bitfields[name][i])
flags += i
+ if(length(flags))
item = "[name_part] = [VV_HTML_ENCODE(jointext(flags, ", "))]"
+ else
+ item = "[name_part] = NONE"
else
item = "[name_part] = [VV_HTML_ENCODE(value)]"
diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm
index 067179e1ecc3b6..a5b2af68c772e2 100644
--- a/code/modules/admin/view_variables/reference_tracking.dm
+++ b/code/modules/admin/view_variables/reference_tracking.dm
@@ -22,10 +22,6 @@
if(usr?.client)
usr.client.running_find_references = type
-#ifdef UNIT_TESTS
- log_reftracker("Currently sleeping procs [byond_status()]")
-#endif
-
log_reftracker("Beginning search for references to a [type].")
var/starting_time = world.time
diff --git a/code/modules/antagonists/_common/antag_datum.dm b/code/modules/antagonists/_common/antag_datum.dm
index f97ecaeb42b2cf..5bbbfaa40b8898 100644
--- a/code/modules/antagonists/_common/antag_datum.dm
+++ b/code/modules/antagonists/_common/antag_datum.dm
@@ -59,6 +59,8 @@ GLOBAL_LIST_EMPTY(antagonists)
var/can_assign_self_objectives = FALSE
/// Default to fill in when entering a custom objective.
var/default_custom_objective = "Cause chaos on the space station."
+ /// Whether we give a hardcore random bonus for greentexting as this antagonist while playing hardcore random
+ var/hardcore_random_bonus = FALSE
//ANTAG UI
diff --git a/code/modules/antagonists/abductor/equipment/glands/heal.dm b/code/modules/antagonists/abductor/equipment/glands/heal.dm
index a8e01a45eeecdf..2fa677cba0ef00 100644
--- a/code/modules/antagonists/abductor/equipment/glands/heal.dm
+++ b/code/modules/antagonists/abductor/equipment/glands/heal.dm
@@ -1,3 +1,5 @@
+#define REJECTION_VOMIT_FLAGS (MOB_VOMIT_BLOOD | MOB_VOMIT_STUN | MOB_VOMIT_KNOCKDOWN | MOB_VOMIT_FORCE)
+
/obj/item/organ/internal/heart/gland/heal
abductor_hint = "organic replicator. Forcibly ejects damaged and robotic organs from the abductee and regenerates them. Additionally, forcibly removes reagents (via vomit) from the abductee if they have moderate toxin damage or poison within the bloodstream, and regenerates blood to a healthy threshold if too low. The abductee will also reject implants such as mindshields."
cooldown_low = 200
@@ -78,19 +80,19 @@
/obj/item/organ/internal/heart/gland/heal/proc/reject_implant(obj/item/implant/implant)
owner.visible_message(span_warning("[owner] vomits up a tiny mangled implant!"), span_userdanger("You suddenly vomit up a tiny mangled implant!"))
- owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(REJECTION_VOMIT_FLAGS, lost_nutrition = 0)
implant.removed(owner)
qdel(implant)
/obj/item/organ/internal/heart/gland/heal/proc/reject_cyberimp(obj/item/organ/internal/cyberimp/implant)
owner.visible_message(span_warning("[owner] vomits up his [implant.name]!"), span_userdanger("You suddenly vomit up your [implant.name]!"))
- owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(REJECTION_VOMIT_FLAGS, lost_nutrition = 0)
implant.Remove(owner)
implant.forceMove(owner.drop_location())
/obj/item/organ/internal/heart/gland/heal/proc/replace_appendix(obj/item/organ/internal/appendix/appendix)
if(appendix)
- owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(REJECTION_VOMIT_FLAGS, lost_nutrition = 0)
appendix.Remove(owner)
appendix.forceMove(owner.drop_location())
owner.visible_message(span_warning("[owner] vomits up his [appendix.name]!"), span_userdanger("You suddenly vomit up your [appendix.name]!"))
@@ -106,7 +108,7 @@
/obj/item/organ/internal/heart/gland/heal/proc/replace_liver(obj/item/organ/internal/liver/liver)
if(liver)
owner.visible_message(span_warning("[owner] vomits up his [liver.name]!"), span_userdanger("You suddenly vomit up your [liver.name]!"))
- owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(REJECTION_VOMIT_FLAGS, lost_nutrition = 0)
liver.Remove(owner)
liver.forceMove(owner.drop_location())
else
@@ -121,7 +123,7 @@
/obj/item/organ/internal/heart/gland/heal/proc/replace_lungs(obj/item/organ/internal/lungs/lungs)
if(lungs)
owner.visible_message(span_warning("[owner] vomits up his [lungs.name]!"), span_userdanger("You suddenly vomit up your [lungs.name]!"))
- owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(REJECTION_VOMIT_FLAGS, lost_nutrition = 0)
lungs.Remove(owner)
lungs.forceMove(owner.drop_location())
else
@@ -136,7 +138,7 @@
/obj/item/organ/internal/heart/gland/heal/proc/replace_stomach(obj/item/organ/internal/stomach/stomach)
if(stomach)
owner.visible_message(span_warning("[owner] vomits up his [stomach.name]!"), span_userdanger("You suddenly vomit up your [stomach.name]!"))
- owner.vomit(0, TRUE, TRUE, 1, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(REJECTION_VOMIT_FLAGS, lost_nutrition = 0)
stomach.Remove(owner)
stomach.forceMove(owner.drop_location())
else
@@ -190,7 +192,7 @@
/obj/item/organ/internal/heart/gland/heal/proc/keep_replacing_blood()
var/keep_going = FALSE
- owner.vomit(0, TRUE, FALSE, 3, FALSE, FALSE, FALSE, TRUE)
+ owner.vomit(vomit_flags = (MOB_VOMIT_BLOOD | MOB_VOMIT_FORCE), lost_nutrition = 0, distance = 3)
owner.Stun(15)
owner.adjustToxLoss(-15, TRUE, TRUE)
@@ -226,3 +228,5 @@
var/obj/item/bodypart/chest/new_chest = new(null)
new_chest.replace_limb(owner, TRUE)
qdel(chest)
+
+#undef REJECTION_VOMIT_FLAGS
diff --git a/code/modules/antagonists/abductor/equipment/glands/plasma.dm b/code/modules/antagonists/abductor/equipment/glands/plasma.dm
index c167dd8a3291e1..0d709579cc8c5d 100644
--- a/code/modules/antagonists/abductor/equipment/glands/plasma.dm
+++ b/code/modules/antagonists/abductor/equipment/glands/plasma.dm
@@ -19,4 +19,4 @@
var/turf/open/T = get_turf(owner)
if(istype(T))
T.atmos_spawn_air("[GAS_PLASMA]=50;[TURF_TEMPERATURE(T20C)]")
- owner.vomit()
+ owner.vomit(VOMIT_CATEGORY_DEFAULT)
diff --git a/code/modules/antagonists/abductor/equipment/glands/slime.dm b/code/modules/antagonists/abductor/equipment/glands/slime.dm
index e3c966e3b6c61b..faebce9fc874f6 100644
--- a/code/modules/antagonists/abductor/equipment/glands/slime.dm
+++ b/code/modules/antagonists/abductor/equipment/glands/slime.dm
@@ -19,7 +19,7 @@
/obj/item/organ/internal/heart/gland/slime/activate()
to_chat(owner, span_warning("You feel nauseated!"))
- owner.vomit(20)
+ owner.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 20)
var/mob/living/simple_animal/slime/Slime = new(get_turf(owner), "grey")
Slime.set_friends(list(owner))
diff --git a/code/modules/antagonists/brother/brother.dm b/code/modules/antagonists/brother/brother.dm
index deef0a390cbd88..7a797273785d10 100644
--- a/code/modules/antagonists/brother/brother.dm
+++ b/code/modules/antagonists/brother/brother.dm
@@ -9,6 +9,7 @@
suicide_cry = "FOR MY BROTHER!!"
var/datum/team/brother_team/team
antag_moodlet = /datum/mood_event/focused
+ hardcore_random_bonus = TRUE
/datum/antagonist/brother/create_team(datum/team/brother_team/new_team)
if(!new_team)
diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm
index b46b05000d9e51..82c83ecf0ba899 100644
--- a/code/modules/antagonists/changeling/changeling.dm
+++ b/code/modules/antagonists/changeling/changeling.dm
@@ -13,6 +13,7 @@
suicide_cry = "FOR THE HIVE!!"
can_assign_self_objectives = TRUE
default_custom_objective = "Consume the station's most valuable genomes."
+ hardcore_random_bonus = TRUE
/// Whether to give this changeling objectives or not
var/give_objectives = TRUE
/// Weather we assign objectives which compete with other lings
@@ -282,13 +283,15 @@
/datum/antagonist/changeling/proc/on_life(datum/source, seconds_per_tick, times_fired)
SIGNAL_HANDLER
+ var/delta_time = DELTA_WORLD_TIME(SSmobs)
+
// If dead, we only regenerate up to half chem storage.
if(owner.current.stat == DEAD)
- adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * seconds_per_tick, total_chem_storage * 0.5)
+ adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * delta_time, total_chem_storage * 0.5)
// If we're not dead - we go up to the full chem cap.
else
- adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * seconds_per_tick)
+ adjust_chemicals((chem_recharge_rate - chem_recharge_slowdown) * delta_time)
/**
* Signal proc for [COMSIG_LIVING_POST_FULLY_HEAL], getting admin-healed restores our chemicals.
diff --git a/code/modules/antagonists/changeling/powers/lesserform.dm b/code/modules/antagonists/changeling/powers/lesserform.dm
index 854234af965f2d..87bd7c7c8b669e 100644
--- a/code/modules/antagonists/changeling/powers/lesserform.dm
+++ b/code/modules/antagonists/changeling/powers/lesserform.dm
@@ -40,7 +40,6 @@
user.humanize(species = chosen_species, instant = transform_instantly)
changeling.transform(user, chosen_form)
- user.regenerate_icons()
return TRUE
/// Returns the form to transform back into, automatically selects your only profile if you only have one
diff --git a/code/modules/antagonists/changeling/powers/mutations.dm b/code/modules/antagonists/changeling/powers/mutations.dm
index 309cffc0a75141..bf4f8c2b3da3e4 100644
--- a/code/modules/antagonists/changeling/powers/mutations.dm
+++ b/code/modules/antagonists/changeling/powers/mutations.dm
@@ -278,6 +278,7 @@
flags_1 = NONE
w_class = WEIGHT_CLASS_HUGE
slot_flags = NONE
+ antimagic_flags = NONE
pinless = TRUE
ammo_type = /obj/item/ammo_casing/magic/tentacle
fire_sound = 'sound/effects/splat.ogg'
diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm
index 683fe7e16b6fc1..25a267e03dfa9b 100644
--- a/code/modules/antagonists/changeling/powers/panacea.dm
+++ b/code/modules/antagonists/changeling/powers/panacea.dm
@@ -27,7 +27,7 @@
O.Remove(user)
if(iscarbon(user))
var/mob/living/carbon/C = user
- C.vomit(0)
+ C.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 0)
O.forceMove(get_turf(user))
//Skyrat Edit Start: Cortical Borer
var/mob/living/basic/cortical_borer/cb_inside = user.has_borer()
diff --git a/code/modules/antagonists/fugitive/hunters/hunter_gear.dm b/code/modules/antagonists/fugitive/hunters/hunter_gear.dm
index 71082f199e0753..013359e9a68fca 100644
--- a/code/modules/antagonists/fugitive/hunters/hunter_gear.dm
+++ b/code/modules/antagonists/fugitive/hunters/hunter_gear.dm
@@ -198,3 +198,22 @@
continue
return found_fugitive
+
+/obj/item/radio/headset/psyker
+ name = "psychic headset"
+ desc = "A headset designed to boost psychic waves. Protects ears from flashbangs."
+ icon_state = "psyker_headset"
+ worn_icon_state = "syndie_headset"
+
+/obj/item/radio/headset/psyker/Initialize(mapload)
+ . = ..()
+ AddComponent(/datum/component/wearertargeting/earprotection, list(ITEM_SLOT_EARS))
+
+/obj/item/radio/headset/psyker/equipped(mob/living/user, slot)
+ . = ..()
+ if(slot_flags & slot)
+ ADD_CLOTHING_TRAIT(user, TRAIT_ECHOLOCATION_EXTRA_RANGE)
+
+/obj/item/radio/headset/psyker/dropped(mob/user, silent)
+ . = ..()
+ REMOVE_CLOTHING_TRAIT(user, TRAIT_ECHOLOCATION_EXTRA_RANGE)
diff --git a/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm b/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
index 5865f76e9999ed..7df6818cdc44a4 100644
--- a/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
+++ b/code/modules/antagonists/fugitive/hunters/hunter_outfits.dm
@@ -187,7 +187,7 @@
name = "Psyker-Shikari Hunter"
glasses = null
head = null
- ears = /obj/item/radio/headset/syndicate/alt/psyker
+ ears = /obj/item/radio/headset/psyker
uniform = /obj/item/clothing/under/pants/track
gloves = /obj/item/clothing/gloves/fingerless
shoes = /obj/item/clothing/shoes/jackboots
diff --git a/code/modules/antagonists/greentext/greentext.dm b/code/modules/antagonists/greentext/greentext.dm
index d06977d2a63669..7133066f85694d 100644
--- a/code/modules/antagonists/greentext/greentext.dm
+++ b/code/modules/antagonists/greentext/greentext.dm
@@ -4,6 +4,7 @@
show_name_in_check_antagonists = TRUE //Not that it will be there for long
suicide_cry = "FOR THE GREENTEXT!!" // This can never actually show up, but not including it is a missed opportunity
count_against_dynamic_roll_chance = FALSE
+ hardcore_random_bonus = TRUE
/datum/antagonist/greentext/forge_objectives()
var/datum/objective/succeed_objective = new /datum/objective("Succeed")
diff --git a/code/modules/antagonists/heretic/heretic_antag.dm b/code/modules/antagonists/heretic/heretic_antag.dm
index 48c8dad7fa6bd4..0120c3b7ee3a86 100644
--- a/code/modules/antagonists/heretic/heretic_antag.dm
+++ b/code/modules/antagonists/heretic/heretic_antag.dm
@@ -25,6 +25,7 @@
preview_outfit = /datum/outfit/heretic
can_assign_self_objectives = TRUE
default_custom_objective = "Turn a department into a testament for your dark knowledge."
+ hardcore_random_bonus = TRUE
/// Whether we give this antagonist objectives on gain.
var/give_objectives = TRUE
/// Whether we've ascended! (Completed one of the final rituals)
@@ -63,6 +64,7 @@
PATH_VOID = "blue",
PATH_BLADE = "label", // my favorite color is label
PATH_COSMIC = "purple",
+ PATH_KNOCK = "yellow",
)
var/static/list/path_to_rune_color = list(
PATH_START = COLOR_LIME,
@@ -72,6 +74,7 @@
PATH_VOID = COLOR_CYAN,
PATH_BLADE = COLOR_SILVER,
PATH_COSMIC = COLOR_PURPLE,
+ PATH_KNOCK = COLOR_YELLOW,
)
/datum/antagonist/heretic/Destroy()
@@ -508,6 +511,7 @@
.["Remove Heart Target"] = CALLBACK(src, PROC_REF(remove_target))
.["Adjust Knowledge Points"] = CALLBACK(src, PROC_REF(admin_change_points))
+ .["Give Focus"] = CALLBACK(src, PROC_REF(admin_give_focus))
/**
* Admin proc for giving a heretic a Living Heart easily.
@@ -583,6 +587,18 @@
knowledge_points += change_num
+/**
+ * Admin proc for giving a heretic a focus.
+ */
+/datum/antagonist/heretic/proc/admin_give_focus(mob/admin)
+ if(!admin.client?.holder)
+ to_chat(admin, span_warning("You shouldn't be using this!"))
+ return
+
+ var/mob/living/pawn = owner.current
+ pawn.equip_to_slot_if_possible(new /obj/item/clothing/neck/heretic_focus(get_turf(pawn)), ITEM_SLOT_NECK, TRUE, TRUE)
+ to_chat(pawn, span_hypnophrase("The Mansus has manifested you a focus."))
+
/datum/antagonist/heretic/antag_panel_data()
var/list/string_of_knowledge = list()
diff --git a/code/modules/antagonists/heretic/items/heretic_blades.dm b/code/modules/antagonists/heretic/items/heretic_blades.dm
index c074e13c8d39b2..63493a5dc4bdbb 100644
--- a/code/modules/antagonists/heretic/items/heretic_blades.dm
+++ b/code/modules/antagonists/heretic/items/heretic_blades.dm
@@ -115,3 +115,14 @@
icon_state = "cosmic_blade"
inhand_icon_state = "cosmic_blade"
after_use_message = "The Stargazer hears your call..."
+
+// Path of Knock's blade
+/obj/item/melee/sickly_blade/knock
+ name = "\improper key blade"
+ desc = "A blade and a key, a key to what? \
+ What grand gates does it open?"
+ icon_state = "key_blade"
+ inhand_icon_state = "key_blade"
+ after_use_message = "The Mother of Ants hears your call..."
+ tool_behaviour = TOOL_CROWBAR
+ toolspeed = 1.3
diff --git a/code/modules/antagonists/heretic/items/keyring.dm b/code/modules/antagonists/heretic/items/keyring.dm
new file mode 100644
index 00000000000000..0498ba9e8a2aad
--- /dev/null
+++ b/code/modules/antagonists/heretic/items/keyring.dm
@@ -0,0 +1,186 @@
+/obj/effect/knock_portal
+ name = "crack in reality"
+ desc = "A crack in space, impossibly deep and painful to the eyes. Definitely not safe."
+ icon = 'icons/effects/eldritch.dmi'
+ icon_state = "realitycrack"
+ light_system = STATIC_LIGHT
+ light_power = 1
+ light_on = TRUE
+ light_color = COLOR_GREEN
+ light_range = 3
+ opacity = TRUE
+ density = FALSE //so we dont block doors closing
+ layer = OBJ_LAYER //under doors
+ ///The knock portal we teleport to
+ var/obj/effect/knock_portal/destination
+ ///The airlock we are linked to, we delete if it is destroyed
+ var/obj/machinery/door/our_airlock
+
+/obj/effect/knock_portal/Initialize(mapload, target)
+ . = ..()
+ if(target)
+ our_airlock = target
+ RegisterSignal(target, COMSIG_QDELETING, PROC_REF(delete_on_door_delete))
+
+ var/static/list/loc_connections = list(
+ COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
+ )
+ AddElement(/datum/element/connect_loc, loc_connections)
+
+///Deletes us and our destination portal if our_airlock is destroyed
+/obj/effect/knock_portal/proc/delete_on_door_delete(datum/source)
+ SIGNAL_HANDLER
+ qdel(src)
+
+///Signal handler for when our location is entered, calls teleport on the victim, if their old_loc didnt contain a portal already (to prevent loops)
+/obj/effect/knock_portal/proc/on_entered(datum/source, mob/living/loser, atom/old_loc)
+ SIGNAL_HANDLER
+ if(istype(loser) && !(locate(type) in old_loc))
+ teleport(loser)
+
+/obj/effect/knock_portal/Destroy()
+ QDEL_NULL(destination)
+ our_airlock = null
+ return ..()
+
+///Teleports the teleportee, to a random airlock if the teleportee isnt a heretic, or the other portal if they are one
+/obj/effect/knock_portal/proc/teleport(mob/living/teleportee)
+ if(isnull(destination)) //dumbass
+ qdel(src)
+ return
+
+ //get it?
+ var/obj/machinery/door/doorstination = IS_HERETIC_OR_MONSTER(teleportee) ? destination.our_airlock : find_random_airlock()
+ if(!do_teleport(teleportee, get_turf(doorstination), channel = TELEPORT_CHANNEL_MAGIC))
+ return
+
+ if(!IS_HERETIC_OR_MONSTER(teleportee))
+ teleportee.apply_damage(20, BRUTE) //so they dont roll it like a jackpot machine to see if they can land in the armory
+ to_chat(teleportee, span_userdanger("You stumble through [src], battered by forces beyond your comprehension, landing anywhere but where you thought you were going."))
+
+ INVOKE_ASYNC(src, PROC_REF(async_opendoor), doorstination)
+
+///Returns a random airlock on the same Z level as our portal, that isnt our airlock
+/obj/effect/knock_portal/proc/find_random_airlock()
+ var/list/turf/possible_destinations = list()
+ for(var/obj/airlock as anything in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/door/airlock))
+ if(airlock.z != z)
+ continue
+ if(airlock.loc == loc)
+ continue
+ possible_destinations += airlock
+ return pick(possible_destinations)
+
+///Asynchronous proc to unbolt, then open the passed door
+/obj/effect/knock_portal/proc/async_opendoor(obj/machinery/door/door)
+ if(istype(door, /obj/machinery/door/airlock)) //they can create portals on ANY door, but we should unlock airlocks so they can actually open
+ var/obj/machinery/door/airlock/as_airlock = door
+ as_airlock.unbolt()
+ door.open()
+
+///An ID card capable of shapeshifting to other IDs given by the Key Keepers Burden knowledge
+/obj/item/card/id/advanced/heretic
+ ///List of IDs this card consumed
+ var/list/obj/item/card/id/fused_ids = list()
+ ///The first portal in the portal pair, so we can clear it later
+ var/obj/effect/knock_portal/portal_one
+ ///The second portal in the portal pair, so we can clear it later
+ var/obj/effect/knock_portal/portal_two
+ ///The first door we are linking in the pair, so we can create a portal pair
+ var/datum/weakref/link
+
+/obj/item/card/id/advanced/heretic/examine(mob/user)
+ . = ..()
+ if(!IS_HERETIC_OR_MONSTER(user))
+ return
+ . += span_hypnophrase("Enchanted by the Mansus!")
+ . += span_hypnophrase("Using an ID on this will consume it and allow you to copy its accesses.")
+ . += span_hypnophrase("Using this in-hand allows you to change its appearance.")
+ . += span_hypnophrase("Using this on a pair of doors, allows you to link them together. Entering one door will transport you to the other, while heathens are instead teleported to a random airlock.")
+
+/obj/item/card/id/advanced/heretic/attack_self(mob/user)
+ . = ..()
+ if(!IS_HERETIC(user))
+ return
+ var/cardname = tgui_input_list(user, "Shapeshift into?", "Shapeshift", fused_ids)
+ if(!cardname)
+ balloon_alert(user, "no options!")
+ return ..()
+ var/obj/item/card/id/card = fused_ids[cardname]
+ shapeshift(card)
+
+///Changes our appearance to the passed ID card
+/obj/item/card/id/advanced/heretic/proc/shapeshift(obj/item/card/id/advanced/card)
+ trim = card.trim
+ assignment = card.assignment
+ registered_age = card.registered_age
+ registered_name = card.registered_name
+ icon_state = card.icon_state
+ inhand_icon_state = card.inhand_icon_state
+ assigned_icon_state = card.assigned_icon_state
+ name = card.name //not update_label because of the captains spare moment
+ update_icon()
+
+///Deletes and nulls our portal pair
+/obj/item/card/id/advanced/heretic/proc/clear_portals()
+ QDEL_NULL(portal_one)
+ QDEL_NULL(portal_two)
+
+///Clears portal references
+/obj/item/card/id/advanced/heretic/proc/clear_portal_refs()
+ SIGNAL_HANDLER
+ portal_one = null
+ portal_two = null
+
+///Creates a portal pair at door1 and door2, displays a balloon alert to user
+/obj/item/card/id/advanced/heretic/proc/make_portal(mob/user, obj/machinery/door/door1, obj/machinery/door/door2)
+ var/message = "linked"
+ if(portal_one || portal_two)
+ clear_portals()
+ message += ", previous cleared"
+
+ portal_one = new(get_turf(door2), door2)
+ portal_two = new(get_turf(door1), door1)
+ portal_one.destination = portal_two
+ RegisterSignal(portal_one, COMSIG_QDELETING, PROC_REF(clear_portal_refs)) //we only really need to register one because they already qdel both portals if one is destroyed
+ portal_two.destination = portal_one
+ balloon_alert(user, "[message]")
+
+/obj/item/card/id/advanced/heretic/attackby(obj/item/thing, mob/user, params)
+ if(!istype(thing, /obj/item/card/id/advanced) || !IS_HERETIC(user))
+ return ..()
+ var/obj/item/card/id/card = thing
+ fused_ids[card.name] = card
+ card.moveToNullspace()
+ playsound(drop_location(),'sound/items/eatfood.ogg', rand(10,50), TRUE)
+ access += card.access
+
+/obj/item/card/id/advanced/heretic/afterattack(atom/target, mob/user, proximity_flag, click_parameters)
+ . = ..()
+ if(!proximity_flag || !IS_HERETIC(user))
+ return
+ if(istype(target, /obj/effect/knock_portal))
+ clear_portals()
+ return
+
+ if(!istype(target, /obj/machinery/door))
+ return
+
+ var/reference_resolved = link?.resolve()
+ if(reference_resolved == target)
+ return
+
+ if(reference_resolved)
+ make_portal(user, reference_resolved, target)
+ to_chat(user, span_notice("You use [src], to link [link] and [target] together."))
+ link = null
+ balloon_alert(user, "link 2/2")
+ else
+ link = WEAKREF(target)
+ balloon_alert(user, "link 1/2")
+
+/obj/item/card/id/advanced/heretic/Destroy()
+ QDEL_LIST_ASSOC(fused_ids)
+ link = null
+ clear_portals()
+ return ..()
diff --git a/code/modules/antagonists/heretic/items/lintel.dm b/code/modules/antagonists/heretic/items/lintel.dm
new file mode 100644
index 00000000000000..140453842c090c
--- /dev/null
+++ b/code/modules/antagonists/heretic/items/lintel.dm
@@ -0,0 +1,64 @@
+/obj/effect/forcefield/wizard/heretic
+ name = "consecrated lintel"
+ desc = "A field of papers flying in the air, repulsing heathens with impossible force."
+ icon_state = "lintel"
+ initial_duration = 8 SECONDS
+
+/obj/effect/forcefield/wizard/heretic/Bumped(mob/living/bumpee)
+ . = ..()
+ if(!istype(bumpee) || IS_HERETIC_OR_MONSTER(bumpee))
+ return
+ var/throwtarget = get_edge_target_turf(loc, get_dir(loc, get_step_away(bumpee, loc)))
+ bumpee.safe_throw_at(throwtarget, 10, 1, force = MOVE_FORCE_EXTREMELY_STRONG)
+ visible_message(span_danger("[src] repulses [bumpee] in a storm of paper!"))
+
+///A heretic item that spawns a barrier at the clicked turf, 3 uses
+/obj/item/heretic_lintel
+ name = "consecrated book"
+ desc = "Some kind of book, its contents make your head hurt. The material is not known to you and it seems to shift and twist unnaturally."
+ icon = 'icons/obj/service/library.dmi'
+ icon_state = "hereticlintel"
+ force = 10
+ damtype = BURN
+ worn_icon_state = "book"
+ throw_speed = 1
+ throw_range = 5
+ w_class = WEIGHT_CLASS_NORMAL
+ attack_verb_continuous = list("bashes", "curses")
+ attack_verb_simple = list("bash", "curse")
+ resistance_flags = FLAMMABLE
+ drop_sound = 'sound/items/handling/book_drop.ogg'
+ pickup_sound = 'sound/items/handling/book_pickup.ogg'
+ ///what type of barrier do we spawn when used
+ var/barrier_type = /obj/effect/forcefield/wizard/heretic
+ ///how many uses do we have left
+ var/uses = 3
+
+/obj/item/heretic_lintel/examine(mob/user)
+ . = ..()
+ if(!IS_HERETIC_OR_MONSTER(user))
+ return
+ . += span_hypnophrase("Materializes a barrier upon any tile in sight, which only you can pass through. Lasts 8 seconds.")
+ . += span_hypnophrase("It has [uses] uses left.")
+
+/obj/item/heretic_lintel/afterattack(atom/target, mob/user, proximity_flag)
+ . = ..()
+ if(IS_HERETIC(user))
+ var/turf/turf_target = get_turf(target)
+ if(locate(barrier_type) in turf_target)
+ user.balloon_alert(user, "already occupied!")
+ return
+ turf_target.visible_message(span_warning("A storm of paper materializes!"))
+ new /obj/effect/temp_visual/paper_scatter(turf_target)
+ playsound(turf_target, 'sound/magic/smoke.ogg', 30)
+ new barrier_type(turf_target, user)
+ uses--
+ if(uses <= 0)
+ to_chat(user, span_warning("[src] falls apart, turning into ash and dust!"))
+ qdel(src)
+ return
+ var/mob/living/carbon/human/human_user = user
+ to_chat(human_user, span_userdanger("Your mind burns as you stare deep into the book, a headache setting in like your brain is on fire!"))
+ human_user.adjustOrganLoss(ORGAN_SLOT_BRAIN, 30, 190)
+ human_user.add_mood_event("gates_of_mansus", /datum/mood_event/gates_of_mansus)
+ human_user.dropItemToGround(src)
diff --git a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
index 26395396c06800..07fa27181859a2 100644
--- a/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
+++ b/code/modules/antagonists/heretic/knowledge/flesh_lore.dm
@@ -262,6 +262,8 @@
I finally began to understand. And then, blood rained from the heavens."
next_knowledge = list(/datum/heretic_knowledge/summon/stalker)
route = PATH_FLESH
+ ///What type of wound do we apply on hit
+ var/wound_type = /datum/wound/slash/flesh/severe
/datum/heretic_knowledge/blade_upgrade/flesh/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
if(!iscarbon(target) || source == target)
@@ -269,7 +271,7 @@
var/mob/living/carbon/carbon_target = target
var/obj/item/bodypart/bodypart = pick(carbon_target.bodyparts)
- var/datum/wound/slash/flesh/severe/crit_wound = new()
+ var/datum/wound/crit_wound = new wound_type()
crit_wound.apply_wound(bodypart, attack_direction = get_dir(source, target))
/datum/heretic_knowledge/summon/stalker
diff --git a/code/modules/antagonists/heretic/knowledge/knock_lore.dm b/code/modules/antagonists/heretic/knowledge/knock_lore.dm
new file mode 100644
index 00000000000000..fcb2c6ceb4c989
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/knock_lore.dm
@@ -0,0 +1,227 @@
+/**
+ * # The path of Knock.
+ *
+ * Goes as follows:
+ *
+ * A Locksmith’s Secret
+ * Grasp of Knock
+ * > Sidepaths:
+ * Ashen Eyes
+ * Codex Cicatrix
+ * Key Keeper’s Burden
+ *
+ * Rite Of Passage
+ * Mark Of Knock
+ * Ritual of Knowledge
+ * Burglar's Finesse
+ * > Sidepaths:
+ * Apetra Vulnera
+ * Opening Blast
+ *
+ * Opening Blade
+ * Caretaker’s Last Refuge
+ *
+ * Many secrets behind the Spider Door
+ */
+/datum/heretic_knowledge/limited_amount/starting/base_knock
+ name = "A Locksmith’s Secret"
+ desc = "Opens up the Path of Knock to you. \
+ Allows you to transmute a knife and a crowbar into a Key Blade. \
+ You can only create two at a time and they function as fast crowbars. \
+ In addition, they can fit into utility belts."
+ gain_text = "The Knock permits no seal and no isolation. It thrusts us gleefully out of the safety of ignorance."
+ next_knowledge = list(/datum/heretic_knowledge/knock_grasp)
+ required_atoms = list(
+ /obj/item/knife = 1,
+ /obj/item/crowbar = 1,
+ )
+ result_atoms = list(/obj/item/melee/sickly_blade/knock)
+ limit = 2
+ route = PATH_KNOCK
+
+/datum/heretic_knowledge/knock_grasp
+ name = "Grasp of Knock"
+ desc = "Your mansus grasp allows you to access anything! Right click on an airlock or a locker to force it open. \
+ DNA locks on mechs will be removed, and any pilot will be ejected. Works on consoles. \
+ Makes a distinctive knocking sound on use."
+ gain_text = "Nothing may remain closed from my touch."
+ next_knowledge = list(
+ /datum/heretic_knowledge/key_ring,
+ /datum/heretic_knowledge/medallion,
+ /datum/heretic_knowledge/codex_cicatrix,
+ )
+ cost = 1
+ route = PATH_KNOCK
+
+/datum/heretic_knowledge/knock_grasp/on_gain(mob/user, datum/antagonist/heretic/our_heretic)
+ RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, PROC_REF(on_secondary_mansus_grasp))
+ RegisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK, PROC_REF(on_mansus_grasp))
+
+/datum/heretic_knowledge/knock_grasp/on_lose(mob/user, datum/antagonist/heretic/our_heretic)
+ UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY)
+ UnregisterSignal(user, COMSIG_HERETIC_MANSUS_GRASP_ATTACK)
+
+/datum/heretic_knowledge/knock_grasp/proc/on_mansus_grasp(mob/living/source, mob/living/target)
+ SIGNAL_HANDLER
+ var/obj/item/clothing/under/suit = target.get_item_by_slot(ITEM_SLOT_ICLOTHING)
+ if(istype(suit) && suit.adjusted == NORMAL_STYLE)
+ suit.toggle_jumpsuit_adjust()
+ suit.update_appearance()
+
+/datum/heretic_knowledge/knock_grasp/proc/on_secondary_mansus_grasp(mob/living/source, atom/target)
+ SIGNAL_HANDLER
+
+ if(ismecha(target))
+ var/obj/vehicle/sealed/mecha/mecha = target
+ mecha.dna_lock = null
+ for(var/mob/living/occupant as anything in mecha.occupants)
+ if(isAI(occupant))
+ continue
+ mecha.mob_exit(occupant, randomstep = TRUE)
+ else if(istype(target,/obj/machinery/door/airlock))
+ var/obj/machinery/door/airlock/door = target
+ door.unbolt()
+ else if(istype(target, /obj/machinery/computer))
+ var/obj/machinery/computer/computer = target
+ computer.authenticated = TRUE
+ computer.balloon_alert(source, "unlocked")
+
+ var/turf/target_turf = get_turf(target)
+ SEND_SIGNAL(target_turf, COMSIG_ATOM_MAGICALLY_UNLOCKED, src, source)
+ playsound(target, 'sound/magic/hereticknock.ogg', 100, TRUE, -1)
+
+ return COMPONENT_USE_HAND
+
+/datum/heretic_knowledge/key_ring
+ name = "Key Keeper’s Burden"
+ desc = "Allows you to transmute a wallet, an iron rod, and an ID card to create an Eldritch Card. \
+ It functions the same as an ID Card, but attacking it with an ID card fuses it and gains its access. \
+ You can use it in-hand to change its form to a card you fused. \
+ Does not preserve the card used in the ritual."
+ gain_text = "Gateways shall open before me, my very will ensnaring reality."
+ required_atoms = list(
+ /obj/item/storage/wallet = 1,
+ /obj/item/stack/rods = 1,
+ /obj/item/card/id = 1,
+ )
+ result_atoms = list(/obj/item/card/id/advanced/heretic)
+ next_knowledge = list(/datum/heretic_knowledge/limited_amount/rite_of_passage)
+ cost = 1
+ route = PATH_KNOCK
+
+/datum/heretic_knowledge/limited_amount/rite_of_passage // item that creates 3 max at a time heretic only barriers, probably should limit to 1 only, holy people can also pass
+ name = "Rite Of Passage"
+ desc = "Allows you to transmute a white crayon, a wooden plank, and a multitool to create a Consecrated Book. \
+ It can materialize a barricade at range that only you and people resistant to magic can pass. 3 uses."
+ gain_text = "With this I can repel those that intend me harm."
+ required_atoms = list(
+ /obj/item/toy/crayon/white = 1,
+ /obj/item/stack/sheet/mineral/wood = 1,
+ /obj/item/multitool = 1,
+ )
+ result_atoms = list(/obj/item/heretic_lintel)
+ next_knowledge = list(/datum/heretic_knowledge/mark/knock_mark)
+ cost = 1
+ route = PATH_KNOCK
+
+/datum/heretic_knowledge/mark/knock_mark
+ name = "Mark of Knock"
+ desc = "Your Mansus Grasp now applies the Mark of Knock. \
+ Attack a marked person to bar them from all passages for the duration of the mark. \
+ This will make it so that they have no access whatsoever, even public access doors will reject them."
+ gain_text = "Their requests for passage will remain unheeded."
+ next_knowledge = list(/datum/heretic_knowledge/knowledge_ritual/knock)
+ route = PATH_KNOCK
+ mark_type = /datum/status_effect/eldritch/knock
+
+/datum/heretic_knowledge/knowledge_ritual/knock
+ next_knowledge = list(/datum/heretic_knowledge/spell/burglar_finesse)
+ route = PATH_KNOCK
+
+/datum/heretic_knowledge/spell/burglar_finesse
+ name = "Burglar's Finesse"
+ desc = "Grants you Burglar's Finesse, a single-target spell \
+ that puts a random item from the victims backpack into your hand."
+ gain_text = "Their trinkets will be mine, as will their lives in due time."
+ next_knowledge = list(
+ /datum/heretic_knowledge/spell/apetra_vulnera,
+ /datum/heretic_knowledge/spell/opening_blast,
+ /datum/heretic_knowledge/blade_upgrade/flesh/knock,
+ )
+ spell_to_add = /datum/action/cooldown/spell/pointed/burglar_finesse
+ cost = 2
+ route = PATH_KNOCK
+
+/datum/heretic_knowledge/blade_upgrade/flesh/knock //basically a chance-based weeping avulsion version of the former
+ name = "Opening Blade"
+ desc = "Your blade has a chance to cause a weeping avulsion on attack."
+ gain_text = "The power of my patron courses through my blade, willing their very flesh to part."
+ next_knowledge = list(/datum/heretic_knowledge/spell/caretaker_refuge)
+ route = PATH_KNOCK
+ wound_type = /datum/wound/slash/flesh/critical
+ var/chance = 35
+
+/datum/heretic_knowledge/blade_upgrade/flesh/knock/do_melee_effects(mob/living/source, mob/living/target, obj/item/melee/sickly_blade/blade)
+ if(prob(chance))
+ return ..()
+
+/datum/heretic_knowledge/spell/caretaker_refuge
+ name = "Caretaker’s Last Refuge"
+ desc = "Gives you a spell that makes you transparent and not dense. Cannot be used near living sentient beings. \
+ While in refuge, you cannot use your hands or spells, and you are immune to slowdown. \
+ You are invincible but unable to harm anything. Cancelled by being hit with an anti-magic item."
+ gain_text = "Then I saw my my own reflection cascaded mind-numbingly enough times that I was but a haze."
+ next_knowledge = list(/datum/heretic_knowledge/ultimate/knock_final)
+ route = PATH_KNOCK
+ spell_to_add = /datum/action/cooldown/spell/caretaker
+ cost = 1
+
+/datum/heretic_knowledge/ultimate/knock_final
+ name = "Many secrets behind the Spider Door"
+ desc = "The ascension ritual of the Path of Knock. \
+ Bring 3 corpses without organs in their torso to a transmutation rune to complete the ritual. \
+ When completed, you gain the ability to transform into empowered eldritch creatures \
+ and in addition, create a tear to the Spider Door; \
+ a tear in reality located at the site of this ritual. \
+ Eldritch creatures will endlessly pour from this rift \
+ who are bound to obey your instructions."
+ gain_text = "With her knowledge, and what I had seen, I knew what to do. \
+ I had to open the gates, with the holes in my foes as Ways! \
+ Reality will soon be torn, the Spider Gate opened! WITNESS ME!"
+ required_atoms = list(/mob/living/carbon/human = 3)
+ route = PATH_KNOCK
+
+/datum/heretic_knowledge/ultimate/knock_final/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
+ . = ..()
+ if(!.)
+ return FALSE
+
+ for(var/mob/living/carbon/human/body in atoms)
+ if(body.stat != DEAD)
+ continue
+ var/obj/item/bodypart/chest = body.get_bodypart(BODY_ZONE_CHEST)
+ if(LAZYLEN(chest.get_organs()))
+ to_chat(user, span_hierophant_warning("[body] has organs in their chest."))
+ continue
+
+ selected_atoms += body
+
+ if(!LAZYLEN(selected_atoms))
+ loc.balloon_alert(user, "ritual failed, not enough valid bodies!")
+ return FALSE
+ return TRUE
+
+/datum/heretic_knowledge/ultimate/knock_final/on_finished_recipe(mob/living/user, list/selected_atoms, turf/loc)
+ . = ..()
+ priority_announce("Delta-class dimensional anomaly detec[generate_heretic_text()] Reality rended, torn. Gates open, doors open, [user.real_name] has ascended! Fear the tide! [generate_heretic_text()]", "Centra[generate_heretic_text()]", ANNOUNCER_SPANOMALIES)
+ user.client?.give_award(/datum/award/achievement/misc/knock_ascension, user)
+
+ // buffs
+ var/datum/action/cooldown/spell/shapeshift/eldritch/ascension/transform_spell = new(user.mind)
+ transform_spell.Grant(user)
+
+ user.client?.give_award(/datum/award/achievement/misc/knock_ascension, user)
+ var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
+ var/datum/heretic_knowledge/blade_upgrade/flesh/knock/blade_upgrade = heretic_datum.get_knowledge(/datum/heretic_knowledge/blade_upgrade/flesh/knock)
+ blade_upgrade.chance += 30
+ new /obj/structure/knock_tear(loc, user.mind)
diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
index 375a1b785905bf..3e37c17392361f 100644
--- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_knowledge.dm
@@ -40,18 +40,17 @@
#ifndef UNIT_TESTS // This is a decently hefty thing to generate while unit testing, so we should skip it.
if(!heretic_level_generated)
heretic_level_generated = TRUE
- log_game("Generating z-level for heretic sacrifices...")
+ log_game("Loading heretic lazytemplate for heretic sacrifices...")
INVOKE_ASYNC(src, PROC_REF(generate_heretic_z_level))
#endif
/// Generate the sacrifice z-level.
/datum/heretic_knowledge/hunt_and_sacrifice/proc/generate_heretic_z_level()
- var/datum/map_template/heretic_sacrifice_level/new_level = new()
- if(!new_level.load_new_z())
- log_game("The heretic sacrifice z-level failed to load.")
- message_admins("The heretic sacrifice z-level failed to load. Heretic sacrifices won't be teleported to the shadow realm. \
+ if(!SSmapping.lazy_load_template(LAZY_TEMPLATE_KEY_HERETIC_SACRIFICE))
+ log_game("The heretic sacrifice template failed to load.")
+ message_admins("The heretic sacrifice lazy template failed to load. Heretic sacrifices won't be teleported to the shadow realm. \
If you want, you can spawn an /obj/effect/landmark/heretic somewhere to stop that from happening.")
- CRASH("Failed to initialize heretic sacrifice z-level!")
+ CRASH("Failed to lazy load heretic sacrifice template!")
/datum/heretic_knowledge/hunt_and_sacrifice/recipe_snowflake_check(mob/living/user, list/atoms, list/selected_atoms, turf/loc)
var/datum/antagonist/heretic/heretic_datum = IS_HERETIC(user)
diff --git a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm
index a31f0a7cc97ff9..983bbee32c600f 100644
--- a/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm
+++ b/code/modules/antagonists/heretic/knowledge/sacrifice_knowledge/sacrifice_map.dm
@@ -3,14 +3,6 @@
/// A global assoc list of all landmarks that denote a heretic sacrifice location. [string heretic path] = [landmark].
GLOBAL_LIST_EMPTY(heretic_sacrifice_landmarks)
-/**
- * A map template loaded in when heretics are created.
- * Hereteic sacrifices are sent here when completed.
- */
-/datum/map_template/heretic_sacrifice_level
- name = "Heretic Sacrifice Level"
- mappath = "_maps/templates/heretic_sacrifice_template.dmm"
-
/// Lardmarks meant to designate where heretic sacrifices are sent.
/obj/effect/landmark/heretic
name = "default heretic sacrifice landmark"
@@ -42,6 +34,10 @@ GLOBAL_LIST_EMPTY(heretic_sacrifice_landmarks)
name = "rust heretic sacrifice landmark"
for_heretic_path = PATH_RUST
+/obj/effect/landmark/heretic/knock
+ name = "knock heretic sacrifice landmark"
+ for_heretic_path = PATH_KNOCK
+
// A fluff signpost object that doesn't teleport you somewhere when you touch it.
/obj/structure/no_effect_signpost
name = "signpost"
@@ -114,3 +110,8 @@ GLOBAL_LIST_EMPTY(heretic_sacrifice_landmarks)
name = "Mansus Rust Gate"
ambience_index = AMBIENCE_REEBE
sound_environment = SOUND_ENVIRONMENT_SEWER_PIPE
+
+/area/centcom/heretic_sacrifice/knock
+ name = "Mansus Knock Gate"
+ ambience_index = AMBIENCE_DANGER
+ sound_environment = SOUND_ENVIRONMENT_PSYCHOTIC
diff --git a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
index 4a315575d61b71..6439840fed5d5c 100644
--- a/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
+++ b/code/modules/antagonists/heretic/knowledge/side_flesh_void.dm
@@ -26,6 +26,7 @@
Also has a chance to transfer wounds from you to the victim."
gain_text = "\"No matter the man, we bleed all the same.\" That's what the Marshal told me."
next_knowledge = list(
+ /datum/heretic_knowledge/spell/apetra_vulnera,
/datum/heretic_knowledge/spell/void_phase,
/datum/heretic_knowledge/summon/raw_prophet,
)
diff --git a/code/modules/antagonists/heretic/knowledge/side_knock_flesh.dm b/code/modules/antagonists/heretic/knowledge/side_knock_flesh.dm
new file mode 100644
index 00000000000000..97218ce5e94105
--- /dev/null
+++ b/code/modules/antagonists/heretic/knowledge/side_knock_flesh.dm
@@ -0,0 +1,28 @@
+// Sidepaths for knowledge between Knock and Flesh.
+
+/datum/heretic_knowledge/spell/apetra_vulnera
+ name = "Apetra Vulnera"
+ desc = "Grants you Apetra Vulnera, a spell \
+ which causes heavy bleeding on all bodyparts of the victim that have more than 15 brute damage. \
+ Wounds a random limb if no limb is sufficiently damaged."
+ gain_text = "Flesh opens, and blood spills. My master seeks sacrifice, and I shall appease."
+ next_knowledge = list(
+ /datum/heretic_knowledge/spell/blood_siphon,
+ /datum/heretic_knowledge/void_cloak,
+ )
+ spell_to_add = /datum/action/cooldown/spell/pointed/apetra_vulnera
+ cost = 1
+ route = PATH_SIDE
+
+/datum/heretic_knowledge/spell/opening_blast
+ name = "Wave Of Desperation"
+ desc = "Grants you Wave Of Desparation, a spell which can only be cast while restrained. \
+ It removes your restraints, repels and knocks down adjacent people, and applies the Mansus Grasp to everything nearby."
+ gain_text = "My shackles undone in dark fury, their feeble bindings crumble before my power."
+ next_knowledge = list(
+ /datum/heretic_knowledge/summon/ashy,
+ /datum/heretic_knowledge/void_cloak,
+ )
+ spell_to_add = /datum/action/cooldown/spell/aoe/wave_of_desperation
+ cost = 1
+ route = PATH_SIDE
diff --git a/code/modules/antagonists/heretic/magic/apetravulnera.dm b/code/modules/antagonists/heretic/magic/apetravulnera.dm
new file mode 100644
index 00000000000000..801104dddf9fc2
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/apetravulnera.dm
@@ -0,0 +1,59 @@
+/datum/action/cooldown/spell/pointed/apetra_vulnera
+ name = "Apetra Vulnera"
+ desc = "Causes severe bleeding on every limb of a target which has more than 15 brute damage. \
+ Wounds a random limb if no limb is sufficiently damaged."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "cleave"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 45 SECONDS
+
+ invocation = "AP'TRA VULN'RA!"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ cast_range = 4
+ /// What type of wound we apply
+ var/wound_type = /datum/wound/slash/flesh/critical/cleave
+
+/datum/action/cooldown/spell/pointed/apetra_vulnera/is_valid_target(atom/cast_on)
+ return ..() && ishuman(cast_on)
+
+/datum/action/cooldown/spell/pointed/apetra_vulnera/cast(mob/living/carbon/human/cast_on)
+ . = ..()
+
+ if(IS_HERETIC_OR_MONSTER(cast_on))
+ return FALSE
+
+ if(!cast_on.blood_volume)
+ return FALSE
+
+ if(cast_on.can_block_magic(antimagic_flags))
+ cast_on.visible_message(
+ span_danger("[cast_on]'s bruises briefly glow, but repels the effect!"),
+ span_danger("Your bruises sting a little, but you are protected!")
+ )
+ return FALSE
+
+ var/a_limb_got_damaged = FALSE
+ for(var/obj/item/bodypart/bodypart in cast_on.bodyparts)
+ if(bodypart.brute_dam < 15)
+ continue
+ a_limb_got_damaged = TRUE
+ var/datum/wound/slash/crit_wound = new wound_type()
+ crit_wound.apply_wound(bodypart)
+
+ if(!a_limb_got_damaged)
+ var/datum/wound/slash/crit_wound = new wound_type()
+ crit_wound.apply_wound(pick(cast_on.bodyparts))
+
+ cast_on.visible_message(
+ span_danger("[cast_on]'s scratches and bruises are torn open by an unholy force!"),
+ span_danger("Your scratches and bruises are torn open by some horrible unholy force!")
+ )
+
+ new /obj/effect/temp_visual/cleave(get_turf(cast_on))
+
+ return TRUE
diff --git a/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm
new file mode 100644
index 00000000000000..4395b4a54b340c
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/ascended_shapeshift.dm
@@ -0,0 +1,32 @@
+// Given to ascended knock heretics, is a form of shapeshift that can turn into all 4 common heretic summons, and is not limited to 1 selection.
+/datum/action/cooldown/spell/shapeshift/eldritch/ascension
+ name = "Ascended Shapechange"
+ desc = "A spell that allows you to take on the form of another eldritch creature, gaining their abilities. \
+ You can change your choice at any time, and if your form dies, you dont die."
+ cooldown_time = 20 SECONDS
+ die_with_shapeshifted_form = FALSE
+ possible_shapes = list(
+ /mob/living/simple_animal/hostile/heretic_summon/raw_prophet,
+ /mob/living/simple_animal/hostile/heretic_summon/rust_spirit,
+ /mob/living/simple_animal/hostile/heretic_summon/ash_spirit,
+ /mob/living/simple_animal/hostile/heretic_summon/stalker,
+ )
+
+/datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_shapeshift(mob/living/caster)
+ . = ..()
+ if(!.)
+ return
+ //buff our forms so this ascension ability isnt shit
+ playsound(caster, 'sound/magic/demon_consume.ogg', 50, TRUE)
+ var/mob/living/monster = .
+ monster.AddComponent(/datum/component/seethrough_mob)
+ monster.maxHealth *= 1.5
+ monster.health = monster.maxHealth
+ monster.melee_damage_lower = max((monster.melee_damage_lower * 2), 40)
+ monster.melee_damage_upper = monster.melee_damage_upper / 2
+ monster.transform *= 1.5
+ monster.AddElement(/datum/element/wall_smasher, strength_flag = ENVIRONMENT_SMASH_RWALLS)
+
+/datum/action/cooldown/spell/shapeshift/eldritch/ascension/do_unshapeshift(mob/living/caster)
+ . = ..()
+ shapeshift_type = null //pick another loser
diff --git a/code/modules/antagonists/heretic/magic/burglar_finesse.dm b/code/modules/antagonists/heretic/magic/burglar_finesse.dm
new file mode 100644
index 00000000000000..7bb6960354ec79
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/burglar_finesse.dm
@@ -0,0 +1,39 @@
+/datum/action/cooldown/spell/pointed/burglar_finesse
+ name = "Burglar's Finesse"
+ desc = "Steal a random item from the victim's backpack."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "burglarsfinesse"
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 40 SECONDS
+
+ invocation = "Y'O'K!"
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ cast_range = 4
+
+/datum/action/cooldown/spell/pointed/burglar_finesse/is_valid_target(atom/cast_on)
+ return ..() && ishuman(cast_on) && (locate(/obj/item/storage/backpack) in cast_on.contents)
+
+/datum/action/cooldown/spell/pointed/burglar_finesse/cast(mob/living/carbon/human/cast_on)
+ . = ..()
+ if(cast_on.can_block_magic(antimagic_flags))
+ to_chat(cast_on, span_danger("You feel a light tug, but are otherwise fine, you were protected by holiness!"))
+ to_chat(owner, span_danger("[cast_on] is protected by holy forces!"))
+ return FALSE
+
+ var/obj/storage_item = locate(/obj/item/storage/backpack) in cast_on.contents
+
+ if(isnull(storage_item))
+ return FALSE
+
+ var/item = pick(storage_item.contents)
+ if(isnull(item))
+ return FALSE
+
+ to_chat(cast_on, span_warning("Your [storage_item] feels lighter..."))
+ to_chat(owner, span_notice("With a blink, you pull [item] out of [cast_on][p_s()] [storage_item]."))
+ owner.put_in_active_hand(item)
diff --git a/code/modules/antagonists/heretic/magic/caretaker.dm b/code/modules/antagonists/heretic/magic/caretaker.dm
new file mode 100644
index 00000000000000..87f3a69dad1dc0
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/caretaker.dm
@@ -0,0 +1,39 @@
+/datum/action/cooldown/spell/caretaker
+ name = "Caretaker’s Last Refuge"
+ desc = "Shifts you into the Caretaker's Refuge, rendering you translucent and intangible. \
+ While in the Refuge your movement is unrestricted, but you cannot use your hands or cast any spells. \
+ You cannot enter the Refuge while near other sentient beings, \
+ and you can be removed from it upon contact with antimagical artifacts."
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_minor_antag.dmi'
+ button_icon_state = "ninja_cloak"
+ sound = 'sound/effects/curse2.ogg'
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 1 MINUTES
+
+ invocation_type = INVOCATION_NONE
+ spell_requirements = NONE
+
+/datum/action/cooldown/spell/caretaker/Remove(mob/living/remove_from)
+ if(remove_from.has_status_effect(/datum/status_effect/caretaker_refuge))
+ remove_from.remove_status_effect(/datum/status_effect/caretaker_refuge)
+ return ..()
+
+/datum/action/cooldown/spell/caretaker/is_valid_target(atom/cast_on)
+ return isliving(cast_on)
+
+/datum/action/cooldown/spell/caretaker/cast(atom/cast_on)
+ . = ..()
+ for(var/mob/living/alive in orange(5, owner))
+ if(alive.stat != DEAD && alive.client)
+ owner.balloon_alert(owner, "other minds nearby!")
+ return FALSE
+
+ var/mob/living/carbon/carbon_user = owner
+ if(carbon_user.has_status_effect(/datum/status_effect/caretaker_refuge))
+ carbon_user.remove_status_effect(/datum/status_effect/caretaker_refuge)
+ else
+ carbon_user.apply_status_effect(/datum/status_effect/caretaker_refuge)
+ return TRUE
diff --git a/code/modules/antagonists/heretic/magic/wave_of_desperation.dm b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
new file mode 100644
index 00000000000000..3b78b56ddc0ba1
--- /dev/null
+++ b/code/modules/antagonists/heretic/magic/wave_of_desperation.dm
@@ -0,0 +1,79 @@
+/datum/action/cooldown/spell/aoe/wave_of_desperation
+ name = "Wave Of Desperation"
+ desc = "Removes your restraints, repels and knocks down adjacent people, and applies certain effects of the Mansus Grasp upon everything nearby. \
+ Cannot be cast unless you are restrained, and the stress renders you unconscious 12 seconds later!"
+ background_icon_state = "bg_heretic"
+ overlay_icon_state = "bg_heretic_border"
+ button_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "uncuff"
+ sound = 'sound/magic/swap.ogg'
+
+ school = SCHOOL_FORBIDDEN
+ cooldown_time = 5 MINUTES
+
+ invocation = "F'K 'FF."
+ invocation_type = INVOCATION_WHISPER
+ spell_requirements = NONE
+
+ aoe_radius = 3
+
+/datum/action/cooldown/spell/aoe/wave_of_desperation/is_valid_target(mob/living/carbon/cast_on)
+ return ..() && istype(cast_on) && (cast_on.handcuffed || cast_on.legcuffed)
+
+// Before the cast, we do some small AOE damage around the caster
+/datum/action/cooldown/spell/aoe/wave_of_desperation/before_cast(mob/living/carbon/cast_on)
+ . = ..()
+ if(. & SPELL_CANCEL_CAST)
+ return
+
+ if(cast_on.handcuffed)
+ cast_on.visible_message(span_danger("[cast_on.handcuffed] on [cast_on] shatter!"))
+ QDEL_NULL(cast_on.handcuffed)
+ if(cast_on.legcuffed)
+ cast_on.visible_message(span_danger("[cast_on.legcuffed] on [cast_on] shatters!"))
+ QDEL_NULL(cast_on.legcuffed)
+
+ cast_on.apply_status_effect(/datum/status_effect/heretic_lastresort)
+ new /obj/effect/temp_visual/knockblast(get_turf(cast_on))
+
+ for(var/mob/living/victim in get_things_to_cast_on(cast_on, radius_override = 1))
+ victim.AdjustKnockdown(3 SECONDS)
+ victim.AdjustParalyzed(0.5 SECONDS)
+
+/datum/action/cooldown/spell/aoe/wave_of_desperation/get_things_to_cast_on(atom/center, radius_override)
+ . = list()
+ for(var/atom/nearby in orange(center, radius_override ? radius_override : aoe_radius))
+ if(nearby == owner || nearby == center || isarea(nearby))
+ continue
+ if(!ismob(nearby))
+ . += nearby
+ continue
+ var/mob/living/nearby_mob = nearby
+ if(!isturf(nearby_mob.loc))
+ continue
+ if(IS_HERETIC_OR_MONSTER(nearby_mob))
+ continue
+ if(nearby_mob.can_block_magic(antimagic_flags))
+ continue
+
+ . += nearby_mob
+
+/datum/action/cooldown/spell/aoe/wave_of_desperation/cast_on_thing_in_aoe(atom/victim, atom/caster)
+ if(!ismob(victim))
+ SEND_SIGNAL(owner, COMSIG_HERETIC_MANSUS_GRASP_ATTACK_SECONDARY, victim)
+
+ var/atom/movable/mover = victim
+ if(!istype(mover))
+ return
+
+ if(mover.anchored)
+ return
+ var/our_turf = get_turf(caster)
+ var/throwtarget = get_edge_target_turf(our_turf, get_dir(our_turf, get_step_away(mover, our_turf)))
+ mover.safe_throw_at(throwtarget, 3, 1, force = MOVE_FORCE_STRONG)
+
+/obj/effect/temp_visual/knockblast
+ icon = 'icons/effects/effects.dmi'
+ icon_state = "shield-flash"
+ alpha = 180
+ duration = 1 SECONDS
diff --git a/code/modules/antagonists/heretic/status_effects/buffs.dm b/code/modules/antagonists/heretic/status_effects/buffs.dm
index 743aa2c5a0144e..f1c96c51ad464c 100644
--- a/code/modules/antagonists/heretic/status_effects/buffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/buffs.dm
@@ -74,7 +74,8 @@
heal_amt = 3
if(WOUND_SEVERITY_CRITICAL)
heal_amt = 6
- if(wound.wound_type == WOUND_BURN)
+ var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound.type]
+ if (pregen_data.wounding_types_valid(list(WOUND_BURN)))
carbie.adjustFireLoss(-heal_amt)
else
carbie.adjustBruteLoss(-heal_amt)
@@ -235,3 +236,53 @@
return
addtimer(CALLBACK(src, PROC_REF(create_blade)), blade_recharge_time)
+
+/datum/status_effect/caretaker_refuge
+ id = "Caretaker’s Last Refuge"
+ status_type = STATUS_EFFECT_REFRESH
+ duration = -1
+ alert_type = null
+ var/static/list/caretaking_traits = list(TRAIT_HANDS_BLOCKED, TRAIT_IGNORESLOWDOWN)
+
+/datum/status_effect/caretaker_refuge/on_apply()
+ owner.add_traits(caretaking_traits, TRAIT_STATUS_EFFECT(id))
+ owner.status_flags |= GODMODE
+ animate(owner, alpha = 45,time = 0.5 SECONDS)
+ owner.density = FALSE
+ RegisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING), PROC_REF(on_focus_lost))
+ RegisterSignal(owner, COMSIG_MOB_BEFORE_SPELL_CAST, PROC_REF(prevent_spell_usage))
+ RegisterSignal(owner, COMSIG_ATOM_HOLYATTACK, PROC_REF(nullrod_handler))
+ return TRUE
+
+/datum/status_effect/caretaker_refuge/on_remove()
+ owner.remove_traits(caretaking_traits, TRAIT_STATUS_EFFECT(id))
+ owner.status_flags &= ~GODMODE
+ owner.alpha = initial(owner.alpha)
+ owner.density = initial(owner.density)
+ UnregisterSignal(owner, SIGNAL_REMOVETRAIT(TRAIT_ALLOW_HERETIC_CASTING))
+ UnregisterSignal(owner, COMSIG_MOB_BEFORE_SPELL_CAST)
+ UnregisterSignal(owner, COMSIG_ATOM_HOLYATTACK)
+ owner.visible_message(
+ span_warning("The haze around [owner] disappears, leaving them materialized!"),
+ span_notice("You exit the refuge."),
+ )
+
+/datum/status_effect/caretaker_refuge/get_examine_text()
+ return span_warning("[owner.p_Theyre()] enveloped in an unholy haze!")
+
+/datum/status_effect/caretaker_refuge/proc/nullrod_handler(datum/source, obj/item/weapon)
+ SIGNAL_HANDLER
+ playsound(get_turf(owner), 'sound/effects/curse1.ogg', 80, TRUE)
+ owner.visible_message(span_warning("[weapon] repels the haze around [owner]!"))
+ owner.remove_status_effect(type)
+
+/datum/status_effect/caretaker_refuge/proc/on_focus_lost()
+ SIGNAL_HANDLER
+ to_chat(owner, span_danger("Without a focus, your refuge weakens and dissipates!"))
+ owner.remove_status_effect(type)
+
+/datum/status_effect/caretaker_refuge/proc/prevent_spell_usage(datum/source, datum/spell)
+ SIGNAL_HANDLER
+ if(!istype(spell, /datum/action/cooldown/spell/caretaker))
+ owner.balloon_alert(owner, "may not cast spells in refuge!")
+ return SPELL_CANCEL_CAST
diff --git a/code/modules/antagonists/heretic/status_effects/debuffs.dm b/code/modules/antagonists/heretic/status_effects/debuffs.dm
index 3c7715111be2e5..c286c7b5092e6c 100644
--- a/code/modules/antagonists/heretic/status_effects/debuffs.dm
+++ b/code/modules/antagonists/heretic/status_effects/debuffs.dm
@@ -110,7 +110,7 @@
var/chance = rand(0, 100)
switch(chance)
if(0 to 10)
- human_owner.vomit()
+ human_owner.vomit(VOMIT_CATEGORY_DEFAULT)
if(20 to 30)
human_owner.set_timed_status_effect(100 SECONDS, /datum/status_effect/dizziness, only_if_higher = TRUE)
human_owner.set_timed_status_effect(100 SECONDS, /datum/status_effect/jitter, only_if_higher = TRUE)
@@ -192,3 +192,25 @@
/datum/status_effect/star_mark/extended
duration = 3 MINUTES
+
+// Last Resort
+/datum/status_effect/heretic_lastresort
+ id = "heretic_lastresort"
+ alert_type = /atom/movable/screen/alert/status_effect/heretic_lastresort
+ duration = 12 SECONDS
+ status_type = STATUS_EFFECT_REPLACE
+ tick_interval = -1
+
+/atom/movable/screen/alert/status_effect/heretic_lastresort
+ name = "Last Resort"
+ desc = "Your head spins, heart pumping as fast as it can, losing the fight with the ground. Run to safety!"
+ icon_state = "lastresort"
+
+/datum/status_effect/heretic_lastresort/on_apply()
+ ADD_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
+ to_chat(owner, span_userdanger("You are on the brink of losing consciousness, run!"))
+ return TRUE
+
+/datum/status_effect/heretic_lastresort/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_IGNORESLOWDOWN, TRAIT_STATUS_EFFECT(id))
+ owner.AdjustUnconscious(20 SECONDS, ignore_canstun = TRUE)
diff --git a/code/modules/antagonists/heretic/status_effects/mark_effects.dm b/code/modules/antagonists/heretic/status_effects/mark_effects.dm
index c454ebdc46289a..068570f76c7b17 100644
--- a/code/modules/antagonists/heretic/status_effects/mark_effects.dm
+++ b/code/modules/antagonists/heretic/status_effects/mark_effects.dm
@@ -61,8 +61,7 @@
if(ishuman(owner))
var/mob/living/carbon/human/human_owner = owner
var/obj/item/bodypart/bodypart = pick(human_owner.bodyparts)
- var/datum/wound/slash/flesh/severe/crit_wound = new()
- crit_wound.apply_wound(bodypart)
+ human_owner.cause_wound_of_type_and_severity(WOUND_SLASH, bodypart, WOUND_SEVERITY_SEVERE)
return ..()
@@ -247,3 +246,17 @@
new teleport_effect(get_turf(owner))
owner.Paralyze(2 SECONDS)
return ..()
+
+// MARK OF KNOCK
+
+/datum/status_effect/eldritch/knock
+ effect_icon_state = "emark7"
+ duration = 10 SECONDS
+
+/datum/status_effect/eldritch/knock/on_apply()
+ . = ..()
+ ADD_TRAIT(owner, TRAIT_ALWAYS_NO_ACCESS, STATUS_EFFECT_TRAIT)
+
+/datum/status_effect/eldritch/knock/on_remove()
+ REMOVE_TRAIT(owner, TRAIT_ALWAYS_NO_ACCESS, STATUS_EFFECT_TRAIT)
+ return ..()
diff --git a/code/modules/antagonists/heretic/structures/knock_final.dm b/code/modules/antagonists/heretic/structures/knock_final.dm
new file mode 100644
index 00000000000000..85face85609586
--- /dev/null
+++ b/code/modules/antagonists/heretic/structures/knock_final.dm
@@ -0,0 +1,67 @@
+/obj/structure/knock_tear
+ name = "???"
+ desc = "It stares back. Theres no reason to remain. Run."
+ max_integrity = INFINITE
+ resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ icon = 'icons/obj/anomaly.dmi'
+ icon_state = "bhole3"
+ color = "#53277E"
+ light_color = "#53277E" //cooler purple
+ light_range = 20
+ anchored = TRUE
+ density = FALSE
+ layer = HIGH_PIPE_LAYER //0.01 above sigil layer used by heretic runes
+ move_resist = INFINITY
+ var/datum/mind/ascendee
+ ///a static list of heretic summons, this shouldnt even matter enough to be static but whatever
+ var/static/list/monster_types
+
+/obj/structure/knock_tear/Initialize(mapload, ascendant)
+ . = ..()
+ transform *= 3
+ if(!monster_types)
+ monster_types = subtypesof(/mob/living/simple_animal/hostile/heretic_summon) - /mob/living/simple_animal/hostile/heretic_summon/armsy/prime
+ if(ascendant)
+ ascendee = ascendant
+ SSpoints_of_interest.make_point_of_interest(src)
+ INVOKE_ASYNC(src, PROC_REF(poll_ghosts))
+
+/obj/structure/knock_tear/proc/poll_ghosts()
+ var/list/candidates = poll_ghost_candidates("Would you like to be a random eldritch monster attacking the crew?", ROLE_SENTIENCE, ROLE_SENTIENCE, 10 SECONDS, POLL_IGNORE_HERETIC_MONSTER)
+ while(LAZYLEN(candidates))
+ var/mob/dead/observer/candidate = pick_n_take(candidates)
+ ghost_to_monster(candidate, should_ask = FALSE)
+
+/obj/structure/knock_tear/attack_ghost(mob/user)
+ . = ..()
+ if(.)
+ return
+ ghost_to_monster(user)
+
+/obj/structure/knock_tear/proc/ghost_to_monster(mob/dead/observer/user, should_ask = TRUE)
+ if(should_ask)
+ var/ask = tgui_alert(user, "Become a monster?", "Ascended Rift", list("Yes", "No"))
+ if(ask != "Yes" || QDELETED(src) || QDELETED(user))
+ return FALSE
+ var/monster_type = pick(monster_types)
+ var/mob/living/monster = new monster_type(loc)
+ monster.key = user.key
+ monster.set_name()
+ var/datum/antagonist/heretic_monster/woohoo_free_antag = new(src)
+ monster.mind.add_antag_datum(woohoo_free_antag)
+ if(ascendee)
+ monster.faction = ascendee.current.faction
+ woohoo_free_antag.set_owner(ascendee)
+ var/datum/objective/kill_all_your_friends = new()
+ kill_all_your_friends.owner = monster.mind
+ kill_all_your_friends.explanation_text = "The station's crew must be culled."
+ kill_all_your_friends.completed = TRUE
+ woohoo_free_antag.objectives += kill_all_your_friends
+
+/obj/structure/knock_tear/move_crushed(atom/movable/pusher, force = MOVE_FORCE_DEFAULT, direction)
+ return FALSE
+
+/obj/structure/knock_tear/Destroy(force) //this shouldnt happen but hey
+ if(ascendee)
+ ascendee = null
+ return ..()
diff --git a/code/modules/antagonists/nightmare/nightmare_species.dm b/code/modules/antagonists/nightmare/nightmare_species.dm
index 73f2f02f847d83..88331353657a3d 100644
--- a/code/modules/antagonists/nightmare/nightmare_species.dm
+++ b/code/modules/antagonists/nightmare/nightmare_species.dm
@@ -21,6 +21,8 @@
TRAIT_NOBLOOD,
TRAIT_NO_DNA_COPY,
TRAIT_NO_TRANSFORMATION_STING,
+ TRAIT_NODISMEMBER,
+ TRAIT_NEVER_WOUNDED,
)
mutantheart = /obj/item/organ/internal/heart/nightmare
diff --git a/code/modules/antagonists/ninja/ninja_explosive.dm b/code/modules/antagonists/ninja/ninja_explosive.dm
index b371f12c2e7661..4c014da1863204 100644
--- a/code/modules/antagonists/ninja/ninja_explosive.dm
+++ b/code/modules/antagonists/ninja/ninja_explosive.dm
@@ -66,12 +66,12 @@
qdel(src)
return
//Since we already did the checks in afterattack, the denonator must be a ninja with the bomb objective.
- if(!detonator)
+ if(isnull(detonator))
return
+ var/mob/ninja = detonator.resolve()
. = ..()
if(!.)
return
- var/mob/ninja = detonator.resolve()
if (isnull(ninja))
return
var/datum/antagonist/ninja/ninja_antag = ninja.mind.has_antag_datum(/datum/antagonist/ninja)
diff --git a/code/modules/antagonists/obsessed/obsessed.dm b/code/modules/antagonists/obsessed/obsessed.dm
index 63adacca1e82db..7c921fdd228564 100644
--- a/code/modules/antagonists/obsessed/obsessed.dm
+++ b/code/modules/antagonists/obsessed/obsessed.dm
@@ -10,6 +10,7 @@
silent = TRUE //not actually silent, because greet will be called by the trauma anyway.
suicide_cry = "FOR MY LOVE!!"
preview_outfit = /datum/outfit/obsessed
+ hardcore_random_bonus = TRUE
var/datum/brain_trauma/special/obsessed/trauma
/datum/antagonist/obsessed/admin_add(datum/mind/new_owner,mob/admin)
diff --git a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm
index ff9678ad519eae..57eb95a978c521 100644
--- a/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm
+++ b/code/modules/antagonists/pirate/pirate_shuttle_equipment.dm
@@ -183,8 +183,8 @@
/obj/machinery/piratepad/multitool_act(mob/living/user, obj/item/multitool/I)
. = ..()
if (istype(I))
- to_chat(user, span_notice("You register [src] in [I]s buffer."))
I.set_buffer(src)
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/obj/machinery/piratepad/screwdriver_act_secondary(mob/living/user, obj/item/screwdriver/screw)
diff --git a/code/modules/antagonists/revolution/revolution.dm b/code/modules/antagonists/revolution/revolution.dm
index 1857223df6b6cb..e88730f70267e9 100644
--- a/code/modules/antagonists/revolution/revolution.dm
+++ b/code/modules/antagonists/revolution/revolution.dm
@@ -176,6 +176,7 @@
job_rank = ROLE_REV_HEAD
preview_outfit = /datum/outfit/revolutionary
+ hardcore_random_bonus = TRUE
var/remove_clumsy = FALSE
var/give_flash = FALSE
diff --git a/code/modules/antagonists/survivalist/survivalist.dm b/code/modules/antagonists/survivalist/survivalist.dm
index 7cb9df6ed25ee6..2480b186600a6a 100644
--- a/code/modules/antagonists/survivalist/survivalist.dm
+++ b/code/modules/antagonists/survivalist/survivalist.dm
@@ -22,6 +22,7 @@
/datum/antagonist/survivalist/guns
greet_message = "Your own safety matters above all else, and the only way to ensure your safety is to stockpile weapons! Grab as many guns as possible, by any means necessary. Kill anyone who gets in your way."
+ hardcore_random_bonus = TRUE
/datum/antagonist/survivalist/guns/forge_objectives()
var/datum/objective/steal_n_of_type/summon_guns/guns = new
@@ -32,6 +33,7 @@
/datum/antagonist/survivalist/magic
name = "Amateur Magician"
greet_message = "Grow your newfound talent! Grab as many magical artefacts as possible, by any means necessary. Kill anyone who gets in your way."
+ hardcore_random_bonus = TRUE
/datum/antagonist/survivalist/magic/greet()
. = ..()
diff --git a/code/modules/antagonists/traitor/datum_traitor.dm b/code/modules/antagonists/traitor/datum_traitor.dm
index 3813009be42fb3..aaf162c15f8530 100644
--- a/code/modules/antagonists/traitor/datum_traitor.dm
+++ b/code/modules/antagonists/traitor/datum_traitor.dm
@@ -16,6 +16,7 @@
preview_outfit = /datum/outfit/traitor
can_assign_self_objectives = TRUE
default_custom_objective = "Perform an overcomplicated heist on valuable Nanotrasen assets."
+ hardcore_random_bonus = TRUE
var/give_objectives = TRUE
var/should_give_codewords = TRUE
///give this traitor an uplink?
diff --git a/code/modules/antagonists/traitor/objectives/eyesnatching.dm b/code/modules/antagonists/traitor/objectives/eyesnatching.dm
index d912be2384aad2..0540d83601ccfa 100644
--- a/code/modules/antagonists/traitor/objectives/eyesnatching.dm
+++ b/code/modules/antagonists/traitor/objectives/eyesnatching.dm
@@ -179,9 +179,10 @@
if(!do_after(user, eye_snatch_enthusiasm, target = target, extra_checks = CALLBACK(src, PROC_REF(eyeballs_exist), eyeballies, head, target)))
return
- var/datum/wound/blunt/bone/severe/severe_wound_type = /datum/wound/blunt/bone/severe
- var/datum/wound/blunt/bone/critical/critical_wound_type = /datum/wound/blunt/bone/critical
- target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10), attacking_item = src)
+ var/min_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 30, wound_source = src)
+ var/max_wound = head.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 50, wound_source = src)
+
+ target.apply_damage(20, BRUTE, BODY_ZONE_HEAD, wound_bonus = rand(min_wound, max_wound + 10), attacking_item = src)
target.visible_message(
span_danger("[src] pierces through [target]'s skull, horribly mutilating their eyes!"),
span_userdanger("Something penetrates your skull, horribly mutilating your eyes! Holy fuck!"),
diff --git a/code/modules/antagonists/wizard/equipment/artefact.dm b/code/modules/antagonists/wizard/equipment/artefact.dm
index fc15d0b014b094..39d9605c3ef047 100644
--- a/code/modules/antagonists/wizard/equipment/artefact.dm
+++ b/code/modules/antagonists/wizard/equipment/artefact.dm
@@ -150,7 +150,7 @@
/obj/tear_in_reality/proc/deranged(mob/living/carbon/C)
if(!C || C.stat == DEAD)
return
- C.vomit(0, TRUE, TRUE, 3, TRUE)
+ C.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 0, distance = 3)
C.spew_organ(3, 2)
C.investigate_log("has died from using telekinesis on a tear in reality.", INVESTIGATE_DEATHS)
C.death()
diff --git a/code/modules/antagonists/wizard/wizard.dm b/code/modules/antagonists/wizard/wizard.dm
index 45f7b671f12cbb..1e8df43cfbe766 100644
--- a/code/modules/antagonists/wizard/wizard.dm
+++ b/code/modules/antagonists/wizard/wizard.dm
@@ -14,6 +14,7 @@ GLOBAL_LIST_EMPTY(wizard_spellbook_purchases_by_key)
preview_outfit = /datum/outfit/wizard
can_assign_self_objectives = TRUE
default_custom_objective = "Demonstrate your incredible and destructive magical powers."
+ hardcore_random_bonus = TRUE
var/give_objectives = TRUE
var/strip = TRUE //strip before equipping
var/allow_rename = TRUE
diff --git a/code/modules/asset_cache/assets/fish.dm b/code/modules/asset_cache/assets/fish.dm
index 14258663d9b9aa..2fcf2b803e38c8 100644
--- a/code/modules/asset_cache/assets/fish.dm
+++ b/code/modules/asset_cache/assets/fish.dm
@@ -12,10 +12,3 @@
continue
id_list += id
Insert(id, fish_icon, fish_icon_state)
-
-
-/datum/asset/simple/fishing_minigame
- assets = list(
- "fishing_background_default" = 'icons/ui_icons/fishing/default.png',
- "fishing_background_lavaland" = 'icons/ui_icons/fishing/lavaland.png'
- )
diff --git a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
index 3ef5c9a4d33315..a11f439ec314bd 100644
--- a/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
+++ b/code/modules/atmospherics/machinery/air_alarm/_air_alarm.dm
@@ -124,6 +124,7 @@ GLOBAL_LIST_EMPTY_TYPED(air_alarms, /obj/machinery/airalarm)
GLOB.air_alarms += src
update_appearance()
+ find_and_hang_on_wall()
/obj/machinery/airalarm/process()
if(!COOLDOWN_FINISHED(src, warning_cooldown))
diff --git a/code/modules/atmospherics/machinery/bluespace_vendor.dm b/code/modules/atmospherics/machinery/bluespace_vendor.dm
index 7ea5b827d4a4c5..847533540180d5 100644
--- a/code/modules/atmospherics/machinery/bluespace_vendor.dm
+++ b/code/modules/atmospherics/machinery/bluespace_vendor.dm
@@ -69,6 +69,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/bluespace_vendor, 30)
/obj/machinery/bluespace_vendor/Initialize(mapload)
. = ..()
AddComponent(/datum/component/payment, tank_cost, SSeconomy.get_dep_account(ACCOUNT_ENG), PAYMENT_ANGRY)
+ find_and_hang_on_wall( FALSE)
/obj/machinery/bluespace_vendor/LateInitialize()
. = ..()
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm b/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm
index d4977eb6454f91..1685a027a5f285 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/bluespace_sender.dm
@@ -123,7 +123,7 @@ GLOBAL_LIST_EMPTY_TYPED(bluespace_senders, /obj/machinery/atmospherics/component
/obj/machinery/atmospherics/components/unary/bluespace_sender/multitool_act(mob/living/user, obj/item/item)
var/obj/item/multitool/multitool = item
multitool.set_buffer(src)
- to_chat(user, span_notice("You store linkage information in [item]'s buffer."))
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/obj/machinery/atmospherics/components/unary/bluespace_sender/wrench_act(mob/living/user, obj/item/tool)
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
index 269091d4bdfd1a..452d5d7b243585 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/outlet_injector.dm
@@ -43,16 +43,13 @@
. += span_notice("You can link it with an air sensor using a multitool.")
/obj/machinery/atmospherics/components/unary/outlet_injector/multitool_act(mob/living/user, obj/item/multitool/multi_tool)
- . = ..()
-
if(istype(multi_tool.buffer, /obj/machinery/air_sensor))
var/obj/machinery/air_sensor/sensor = multi_tool.buffer
- sensor.inlet_id = id_tag
- multi_tool.set_buffer(null)
- balloon_alert(user, "input linked to sensor")
+ multi_tool.set_buffer(src)
+ sensor.multitool_act(user, multi_tool)
return TOOL_ACT_TOOLTYPE_SUCCESS
- balloon_alert(user, "saved in buffer")
+ balloon_alert(user, "injector saved in buffer")
multi_tool.set_buffer(src)
return TOOL_ACT_TOOLTYPE_SUCCESS
diff --git a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
index 6449dc49357adf..3422f3e3adfa53 100644
--- a/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
+++ b/code/modules/atmospherics/machinery/components/unary_devices/vent_pump.dm
@@ -53,16 +53,13 @@
. += span_notice("You can link it with an air sensor using a multitool.")
/obj/machinery/atmospherics/components/unary/vent_pump/multitool_act(mob/living/user, obj/item/multitool/multi_tool)
- . = ..()
-
if(istype(multi_tool.buffer, /obj/machinery/air_sensor))
var/obj/machinery/air_sensor/sensor = multi_tool.buffer
- sensor.outlet_id = id_tag
- multi_tool.set_buffer(null)
- balloon_alert(user, "output linked to sensor")
+ multi_tool.set_buffer(src)
+ sensor.multitool_act(user, multi_tool)
return TOOL_ACT_TOOLTYPE_SUCCESS
- balloon_alert(user, "saved in buffer")
+ balloon_alert(user, "vent saved in buffer")
multi_tool.set_buffer(src)
return TOOL_ACT_TOOLTYPE_SUCCESS
diff --git a/code/modules/cards/cardhand.dm b/code/modules/cards/cardhand.dm
index b03a29a43f4827..9dab4e65b584b1 100644
--- a/code/modules/cards/cardhand.dm
+++ b/code/modules/cards/cardhand.dm
@@ -29,7 +29,7 @@
/obj/item/toy/cards/cardhand/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
if(istype(held_item, /obj/item/toy/cards/deck))
var/obj/item/toy/cards/deck/dealer_deck = held_item
- if(dealer_deck.wielded)
+ if(HAS_TRAIT(dealer_deck, TRAIT_WIELDED))
context[SCREENTIP_CONTEXT_LMB] = "Deal card"
context[SCREENTIP_CONTEXT_RMB] = "Deal card faceup"
return CONTEXTUAL_SCREENTIP_SET
@@ -77,7 +77,7 @@
if(istype(weapon, /obj/item/toy/cards/deck))
var/obj/item/toy/cards/deck/dealer_deck = weapon
- if(!dealer_deck.wielded) // recycle cardhand into deck (if unwielded)
+ if(!HAS_TRAIT(dealer_deck, TRAIT_WIELDED)) // recycle cardhand into deck (if unwielded)
dealer_deck.insert(src)
user.balloon_alert_to_viewers("puts card in deck")
return
diff --git a/code/modules/cards/deck/deck.dm b/code/modules/cards/deck/deck.dm
index 189d88822ecfb5..632b01d509d74d 100644
--- a/code/modules/cards/deck/deck.dm
+++ b/code/modules/cards/deck/deck.dm
@@ -21,8 +21,6 @@
var/decksize = INFINITY
/// The description of the cardgame that is played with this deck (used for memories)
var/cardgame_desc = "card game"
- /// Wielding status for holding with two hands
- var/wielded = FALSE
/// The holodeck computer used to spawn a holographic deck (see /obj/item/toy/cards/deck/syndicate/holographic)
var/obj/machinery/computer/holodeck/holodeck
/// If the cards in the deck have different card faces icons (blank and CAS decks do not)
@@ -33,8 +31,6 @@
/obj/item/toy/cards/deck/Initialize(mapload)
. = ..()
AddElement(/datum/element/drag_pickup)
- RegisterSignal(src, COMSIG_TWOHANDED_WIELD, PROC_REF(on_wield))
- RegisterSignal(src, COMSIG_TWOHANDED_UNWIELD, PROC_REF(on_unwield))
AddComponent(/datum/component/two_handed, attacksound='sound/items/cardflip.ogg')
register_context()
@@ -51,18 +47,6 @@
for(var/person in list("Jack", "Queen", "King"))
initial_cards += "[person] of [suit]"
-/// triggered on wield of two handed item
-/obj/item/toy/cards/deck/proc/on_wield(obj/item/source, mob/user)
- SIGNAL_HANDLER
-
- wielded = TRUE
-
-/// triggered on unwield of two handed item
-/obj/item/toy/cards/deck/proc/on_unwield(obj/item/source, mob/user)
- SIGNAL_HANDLER
-
- wielded = FALSE
-
/obj/item/toy/cards/deck/suicide_act(mob/living/carbon/user)
user.visible_message(span_suicide("[user] is slitting [user.p_their()] wrists with \the [src]! It looks like their luck ran out!"))
playsound(src, 'sound/items/cardshuffle.ogg', 50, TRUE)
@@ -87,7 +71,7 @@
/obj/item/toy/cards/deck/add_context(atom/source, list/context, obj/item/held_item, mob/living/user)
if(src == held_item)
var/obj/item/toy/cards/deck/dealer_deck = held_item
- context[SCREENTIP_CONTEXT_LMB] = dealer_deck.wielded ? "Recycle mode" : "Dealer mode"
+ context[SCREENTIP_CONTEXT_LMB] = HAS_TRAIT(dealer_deck, TRAIT_WIELDED) ? "Recycle mode" : "Dealer mode"
context[SCREENTIP_CONTEXT_ALT_LMB] = "Shuffle"
return CONTEXTUAL_SCREENTIP_SET
@@ -161,7 +145,7 @@
/obj/item/toy/cards/deck/AltClick(mob/living/user)
if(user.can_perform_action(src, NEED_DEXTERITY|FORBID_TELEKINESIS_REACH))
- if(wielded)
+ if(HAS_TRAIT(src, TRAIT_WIELDED))
shuffle_cards(user)
else
to_chat(user, span_notice("You must hold the [src] with both hands to shuffle."))
diff --git a/code/modules/cards/deck/tarot.dm b/code/modules/cards/deck/tarot.dm
index ced067c0c3979b..3dcdc8608c640c 100644
--- a/code/modules/cards/deck/tarot.dm
+++ b/code/modules/cards/deck/tarot.dm
@@ -36,8 +36,16 @@
/// ghost notification cooldown
COOLDOWN_DECLARE(ghost_alert_cooldown)
-/obj/item/toy/cards/deck/tarot/haunted/on_wield(obj/item/source, mob/living/carbon/user)
+/obj/item/toy/cards/deck/tarot/haunted/Initialize(mapload)
. = ..()
+ AddComponent( \
+ /datum/component/two_handed, \
+ attacksound = 'sound/items/cardflip.ogg', \
+ wield_callback = CALLBACK(src, PROC_REF(on_wield)), \
+ unwield_callback = CALLBACK(src, PROC_REF(on_unwield)), \
+ )
+
+/obj/item/toy/cards/deck/tarot/haunted/proc/on_wield(obj/item/source, mob/living/carbon/user)
ADD_TRAIT(user, TRAIT_SIXTHSENSE, MAGIC_TRAIT)
to_chat(user, span_notice("The veil to the underworld is opened. You can sense the dead souls calling out..."))
@@ -54,15 +62,8 @@
action = NOTIFY_ORBIT,
)
-/obj/item/toy/cards/deck/tarot/haunted/on_unwield(obj/item/source, mob/living/carbon/user)
- . = ..()
+/obj/item/toy/cards/deck/tarot/haunted/proc/on_unwield(obj/item/source, mob/living/carbon/user)
REMOVE_TRAIT(user, TRAIT_SIXTHSENSE, MAGIC_TRAIT)
to_chat(user, span_notice("The veil to the underworld closes shut. You feel your senses returning to normal."))
-/obj/item/toy/cards/deck/tarot/haunted/dropped(mob/living/carbon/user, silent)
- . = ..()
- if(wielded)
- REMOVE_TRAIT(user, TRAIT_SIXTHSENSE, MAGIC_TRAIT)
- to_chat(user, span_notice("The veil to the underworld closes shut. You feel your senses returning to normal."))
-
#undef TAROT_GHOST_TIMER
diff --git a/code/modules/cards/singlecard.dm b/code/modules/cards/singlecard.dm
index b918d7708ca898..169715c51d9093 100644
--- a/code/modules/cards/singlecard.dm
+++ b/code/modules/cards/singlecard.dm
@@ -78,7 +78,7 @@
if(istype(held_item, /obj/item/toy/cards/deck))
var/obj/item/toy/cards/deck/dealer_deck = held_item
- if(dealer_deck.wielded)
+ if(HAS_TRAIT(dealer_deck, TRAIT_WIELDED))
context[SCREENTIP_CONTEXT_LMB] = "Deal card"
context[SCREENTIP_CONTEXT_RMB] = "Deal card faceup"
return CONTEXTUAL_SCREENTIP_SET
@@ -151,7 +151,7 @@
if(istype(item, /obj/item/toy/cards/deck))
var/obj/item/toy/cards/deck/dealer_deck = item
- if(!dealer_deck.wielded) // recycle card into deck (if unwielded)
+ if(!HAS_TRAIT(dealer_deck, TRAIT_WIELDED)) // recycle card into deck (if unwielded)
dealer_deck.insert(src)
user.balloon_alert_to_viewers("puts card in deck")
return
diff --git a/code/modules/cargo/goodies.dm b/code/modules/cargo/goodies.dm
index f309f725774644..69677e87df56ad 100644
--- a/code/modules/cargo/goodies.dm
+++ b/code/modules/cargo/goodies.dm
@@ -271,3 +271,9 @@
desc = "A standard-sized coffeepot, for use with a coffeemaker."
cost = PAYCHECK_CREW
contains = list(/obj/item/reagent_containers/cup/coffeepot)
+
+/datum/supply_pack/goody/climbing_hook
+ name = "Climbing hook"
+ desc = "A less cheap imported climbing hook. Absolutely no use outside of planetary stations."
+ cost = PAYCHECK_CREW * 5
+ contains = list(/obj/item/climbing_hook)
diff --git a/code/modules/cargo/markets/market_items/weapons.dm b/code/modules/cargo/markets/market_items/weapons.dm
index f40e4fa1447ca7..6115b1e1557970 100644
--- a/code/modules/cargo/markets/market_items/weapons.dm
+++ b/code/modules/cargo/markets/market_items/weapons.dm
@@ -63,3 +63,13 @@
price_max = CARGO_CRATE_VALUE * 2
stock_max = 2
availability_prob = 50
+
+/datum/market_item/weapon/fisher
+ name = "SC/FISHER Saboteur Handgun"
+ desc = "A self-recharging, compact pistol that disrupts flashlights and security cameras, if only temporarily. Also usable in melee."
+ item = /obj/item/gun/energy/recharge/fisher
+
+ price_min = CARGO_CRATE_VALUE * 2
+ price_max = CARGO_CRATE_VALUE * 4
+ stock_max = 1
+ availability_prob = 50
diff --git a/code/modules/cargo/packs/emergency.dm b/code/modules/cargo/packs/emergency.dm
index 9c50372b6a8333..fca1a201ac103b 100644
--- a/code/modules/cargo/packs/emergency.dm
+++ b/code/modules/cargo/packs/emergency.dm
@@ -117,8 +117,6 @@
contains = list(/obj/item/clothing/head/utility/radiation = 2,
/obj/item/clothing/suit/utility/radiation = 2,
/obj/item/geiger_counter = 2,
- /obj/item/clothing/suit/utility/radiation,
- /obj/item/geiger_counter,
/obj/item/reagent_containers/cup/glass/bottle/vodka,
/obj/item/reagent_containers/cup/glass/drinkingglass/shotglass = 2,
)
diff --git a/code/modules/cargo/supplypod_beacon.dm b/code/modules/cargo/supplypod_beacon.dm
index 436a0ca6b93022..89c474635fa182 100644
--- a/code/modules/cargo/supplypod_beacon.dm
+++ b/code/modules/cargo/supplypod_beacon.dm
@@ -7,11 +7,17 @@
lefthand_file = 'icons/mob/inhands/items/devices_lefthand.dmi'
righthand_file = 'icons/mob/inhands/items/devices_righthand.dmi'
w_class = WEIGHT_CLASS_SMALL
+ armor_type = /datum/armor/supplypod_beacon
+ resistance_flags = FIRE_PROOF
var/obj/machinery/computer/cargo/express/express_console
var/linked = FALSE
var/ready = FALSE
var/launched = FALSE
+/datum/armor/supplypod_beacon
+ bomb = 100
+ fire = 100
+
/obj/item/supplypod_beacon/proc/update_status(consoleStatus)
switch(consoleStatus)
if (SP_LINKED)
@@ -49,6 +55,7 @@
/obj/item/supplypod_beacon/examine(user)
. = ..()
+ . += span_notice("It looks like it has a few anchoring bolts.")
if(!express_console)
. += span_notice("[src] is not currently linked to an Express Supply console.")
else
@@ -59,6 +66,11 @@
express_console.beacon = null
return ..()
+/obj/item/supplypod_beacon/wrench_act(mob/living/user, obj/item/tool)
+ . = ..()
+ default_unfasten_wrench(user, tool)
+ return TOOL_ACT_TOOLTYPE_SUCCESS
+
/obj/item/supplypod_beacon/proc/unlink_console()
if(express_console)
express_console.beacon = null
diff --git a/code/modules/client/client_defines.dm b/code/modules/client/client_defines.dm
index c3c6bd244254a8..2e655e91651eee 100644
--- a/code/modules/client/client_defines.dm
+++ b/code/modules/client/client_defines.dm
@@ -160,8 +160,6 @@
///Autoclick list of two elements, first being the clicked thing, second being the parameters.
var/list/atom/selected_target[2]
- ///Autoclick variable referencing the associated item.
- var/obj/item/active_mousedown_item = null
///Used in MouseDrag to preserve the original mouse click parameters
var/mouseParams = ""
///Used in MouseDrag to preserve the last mouse-entered location. Weakref
diff --git a/code/modules/client/client_procs.dm b/code/modules/client/client_procs.dm
index cbebcff555073e..9c7b5e3e9436a6 100644
--- a/code/modules/client/client_procs.dm
+++ b/code/modules/client/client_procs.dm
@@ -598,7 +598,6 @@ GLOBAL_LIST_INIT(blacklisted_builds, list(
send2adminchat("Server", "[cheesy_message] (No admins online)")
QDEL_LIST_ASSOC_VAL(char_render_holders)
- active_mousedown_item = null
SSambience.remove_ambience_client(src)
SSmouse_entered.hovers -= src
SSping.currentrun -= src
diff --git a/code/modules/clothing/belts/polymorph_belt.dm b/code/modules/clothing/belts/polymorph_belt.dm
index f6ccccae971dfe..e63e2c3bee36d0 100644
--- a/code/modules/clothing/belts/polymorph_belt.dm
+++ b/code/modules/clothing/belts/polymorph_belt.dm
@@ -60,8 +60,8 @@
if (!isanimal_or_basicmob(target_mob))
balloon_alert(user, "target too complex!")
return TRUE
- if (!(target_mob.mob_biotypes & MOB_ORGANIC))
- balloon_alert(user, "organic life only!")
+ if (target_mob.mob_biotypes & (MOB_HUMANOID|MOB_ROBOTIC|MOB_SPECIAL|MOB_SPIRIT|MOB_UNDEAD))
+ balloon_alert(user, "incompatible!")
return TRUE
if (isanimal_or_basicmob(target_mob))
if (!target_mob.compare_sentience_type(SENTIENCE_ORGANIC))
diff --git a/code/modules/clothing/glasses/_glasses.dm b/code/modules/clothing/glasses/_glasses.dm
index 688b63bddbb26e..fc665e57c8ffcb 100644
--- a/code/modules/clothing/glasses/_glasses.dm
+++ b/code/modules/clothing/glasses/_glasses.dm
@@ -195,6 +195,7 @@
base_icon_state = "eyepatch"
inhand_icon_state = null
actions_types = list(/datum/action/item_action/flip)
+ dog_fashion = /datum/dog_fashion/head/eyepatch
/obj/item/clothing/glasses/eyepatch/attack_self(mob/user, modifiers)
. = ..()
diff --git a/code/modules/clothing/head/jobs.dm b/code/modules/clothing/head/jobs.dm
index fd30bdd742f37e..4022e2595055ef 100644
--- a/code/modules/clothing/head/jobs.dm
+++ b/code/modules/clothing/head/jobs.dm
@@ -206,6 +206,116 @@
/obj/item/clothing/head/fedora/det_hat/minor
flask_path = /obj/item/reagent_containers/cup/glass/flask/det/minor
+///Detectives Fedora, but like Inspector Gadget. Not a subtype to not inherit candy corn stuff
+/obj/item/clothing/head/fedora/inspector_hat
+ name = "inspector's fedora"
+ desc = "There's only one man can try to stop an evil villian."
+ armor_type = /datum/armor/fedora_det_hat
+ icon_state = "detective"
+ inhand_icon_state = "det_hat"
+ dog_fashion = /datum/dog_fashion/head/detective
+ ///prefix our phrases must begin with
+ var/prefix = "go go gadget"
+ ///an assoc list of phrase = item (like gun = revolver)
+ var/list/items_by_phrase = list()
+ ///how many gadgets can we hold
+ var/max_items = 4
+ ///items above this weight cannot be put in the hat
+ var/max_weight = WEIGHT_CLASS_NORMAL
+
+/obj/item/clothing/head/fedora/inspector_hat/Initialize(mapload)
+ . = ..()
+ become_hearing_sensitive(ROUNDSTART_TRAIT)
+ QDEL_NULL(atom_storage)
+
+/obj/item/clothing/head/fedora/inspector_hat/examine(mob/user)
+ . = ..()
+ . += span_notice("You can put items inside, and get them out by saying a phrase, or using it in-hand!")
+ . += span_notice("The prefix is [prefix], and you can change it with alt-click!\n")
+ for(var/phrase in items_by_phrase)
+ var/obj/item/item = items_by_phrase[phrase]
+ . += span_notice("[icon2html(item, user)] You can remove [item] by saying \"[prefix] [phrase]\"!")
+
+/obj/item/clothing/head/fedora/inspector_hat/Hear(message, atom/movable/speaker, message_language, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range)
+ . = ..()
+ var/mob/living/carbon/wearer = loc
+ if(!istype(wearer) || speaker != wearer) //if we are worn
+ return FALSE
+
+ raw_message = htmlrendertext(raw_message)
+ var/prefix_index = findtext(raw_message, prefix)
+ if(prefix_index != 1)
+ return FALSE
+
+ var/the_phrase = trim_left(replacetext(raw_message, prefix, ""))
+ var/obj/item/result = items_by_phrase[the_phrase]
+ if(!result)
+ return FALSE
+
+ if(wearer.put_in_active_hand(result))
+ wearer.visible_message(span_warning("[src] drops [result] into the hands of [wearer]!"))
+ else
+ balloon_alert(wearer, "cant put in hands!")
+
+ return TRUE
+
+/obj/item/clothing/head/fedora/inspector_hat/attackby(obj/item/item, mob/user, params)
+ . = ..()
+
+ if(LAZYLEN(contents) >= max_items)
+ balloon_alert(user, "full!")
+ return
+ if(item.w_class > max_weight)
+ balloon_alert(user, "too big!")
+ return
+
+ var/input = tgui_input_text(user, "What is the activation phrase?", "Activation phrase", "gadget", max_length = 26)
+ if(!input)
+ return
+ if(input in items_by_phrase)
+ balloon_alert(user, "already used!")
+ return
+
+ if(item.loc != user || !user.transferItemToLoc(item, src))
+ return
+
+ to_chat(user, span_notice("You install [item] into the [thtotext(contents.len)] slot in [src]."))
+ playsound(src, 'sound/machines/click.ogg', 30, TRUE)
+ items_by_phrase[input] = item
+
+/obj/item/clothing/head/fedora/inspector_hat/attack_self(mob/user)
+ . = ..()
+ var/phrase = tgui_input_list(user, "What item do you want to remove by phrase?", "Item Removal", items_by_phrase)
+ if(!phrase)
+ return
+ user.put_in_inactive_hand(items_by_phrase[phrase])
+
+/obj/item/clothing/head/fedora/inspector_hat/AltClick(mob/user)
+ . = ..()
+ var/new_prefix = tgui_input_text(user, "What should be the new prefix?", "Activation prefix", prefix, max_length = 24)
+ if(!new_prefix)
+ return
+ prefix = new_prefix
+
+/obj/item/clothing/head/fedora/inspector_hat/Exited(atom/movable/gone, direction)
+ . = ..()
+ for(var/phrase in items_by_phrase)
+ var/obj/item/result = items_by_phrase[phrase]
+ if(gone == result)
+ items_by_phrase -= phrase
+ return
+
+/obj/item/clothing/head/fedora/inspector_hat/atom_destruction(damage_flag)
+ for(var/phrase in items_by_phrase)
+ var/obj/item/result = items_by_phrase[phrase]
+ result.forceMove(drop_location())
+ items_by_phrase = null
+ return ..()
+
+/obj/item/clothing/head/fedora/inspector_hat/Destroy()
+ QDEL_LIST_ASSOC(items_by_phrase)
+ return ..()
+
//Mime
/obj/item/clothing/head/beret
name = "beret"
diff --git a/code/modules/clothing/suits/ablativecoat.dm b/code/modules/clothing/suits/ablativecoat.dm
index 0a6967753e161c..8bc37aaba22b76 100644
--- a/code/modules/clothing/suits/ablativecoat.dm
+++ b/code/modules/clothing/suits/ablativecoat.dm
@@ -4,6 +4,7 @@
worn_icon = 'icons/mob/clothing/head/helmet.dmi'
desc = "Hood hopefully belonging to an ablative trenchcoat. Includes a visor for cool-o-vision."
icon_state = "ablativehood"
+ flags_inv = HIDEHAIR|HIDEEARS
armor_type = /datum/armor/hooded_ablative
strip_delay = 30
var/hit_reflect_chance = 50
diff --git a/code/modules/clothing/suits/jobs.dm b/code/modules/clothing/suits/jobs.dm
index 61f293a97fe60e..3ed9be8cb1da32 100644
--- a/code/modules/clothing/suits/jobs.dm
+++ b/code/modules/clothing/suits/jobs.dm
@@ -219,19 +219,21 @@
allowed = list(
/obj/item/stamp,
/obj/item/storage/bag/mail,
+ /obj/item/universal_scanner,
)
// Quartermaster
/obj/item/clothing/suit/jacket/quartermaster
- name = "quatermaster's overcoat"
- desc = "A luxury, brown double-breasted overcoat, made from kangaroo skin. It's gold cuffs linked are styled on the credits symbol. It makes you feel more important then you probably are."
+ name = "quartermaster's overcoat"
+ desc = "A luxury, brown double-breasted overcoat made from kangaroo skin. It's gold cuffs are linked and styled on the credits symbol. It makes you feel more important than you probably are."
icon_state = "qm_coat"
blood_overlay_type = "coat"
body_parts_covered = CHEST|GROIN|LEGS|ARMS
allowed = list(
/obj/item/stamp,
/obj/item/storage/bag/mail,
+ /obj/item/universal_scanner,
)
/obj/item/clothing/suit/toggle/lawyer/greyscale
diff --git a/code/modules/clothing/suits/wintercoats.dm b/code/modules/clothing/suits/wintercoats.dm
index a0f6e6d1047b2e..60cbcae1473351 100644
--- a/code/modules/clothing/suits/wintercoats.dm
+++ b/code/modules/clothing/suits/wintercoats.dm
@@ -590,6 +590,7 @@
allowed = list(
/obj/item/storage/bag/mail,
/obj/item/stamp,
+ /obj/item/universal_scanner,
)
/obj/item/clothing/head/hooded/winterhood/cargo
diff --git a/code/modules/events/brain_trauma.dm b/code/modules/events/brain_trauma.dm
index 1e5c09aff48e99..e063c89e152e32 100644
--- a/code/modules/events/brain_trauma.dm
+++ b/code/modules/events/brain_trauma.dm
@@ -2,6 +2,7 @@
name = "Spontaneous Brain Trauma"
typepath = /datum/round_event/brain_trauma
weight = 25
+ min_players = 13
category = EVENT_CATEGORY_HEALTH
description = "A crewmember gains a random trauma."
min_wizard_trigger_potency = 2
@@ -20,6 +21,10 @@
continue
if(!(H.mind.assigned_role.job_flags & JOB_CREW_MEMBER)) //please stop giving my centcom admin gimmicks full body paralysis
continue
+ // SKYRAT EDIT ADD START - Station/area event candidate filtering
+ if(engaged_role_play_check(H, station = TRUE, dorms = TRUE))
+ continue
+ // SKYRAT EDIT ADD END
traumatize(H)
announce_to_ghosts(H)
break
diff --git a/code/modules/events/disease_outbreak.dm b/code/modules/events/disease_outbreak.dm
index a323c7d83f00a5..4f251e871c7e83 100644
--- a/code/modules/events/disease_outbreak.dm
+++ b/code/modules/events/disease_outbreak.dm
@@ -61,6 +61,10 @@
continue
if(length(candidate.diseases)) //Is our candidate already sick?
continue
+ // SKYRAT EDIT ADD START - Station/area event candidate filtering
+ if(engaged_role_play_check(candidate, station = TRUE, dorms = TRUE))
+ continue
+ // SKYRAT EDIT ADD END
disease_candidates += candidate
///Handles checking and alerting admins about the number of valid candidates
diff --git a/code/modules/events/fake_virus.dm b/code/modules/events/fake_virus.dm
index fb6bfd5be97572..f690b1c4a8d5ee 100644
--- a/code/modules/events/fake_virus.dm
+++ b/code/modules/events/fake_virus.dm
@@ -10,6 +10,10 @@
for(var/mob/living/carbon/human/victim in shuffle(GLOB.player_list))
if(victim.stat == DEAD || HAS_TRAIT(victim, TRAIT_CRITICAL_CONDITION) || !(victim.mind.assigned_role.job_flags & JOB_CREW_MEMBER))
continue
+ // SKYRAT EDIT ADD START - Station/area event candidate filtering
+ if(engaged_role_play_check(fake_virus_victims, station = TRUE, dorms = TRUE))
+ continue
+ // SKYRAT EDIT ADD END
fake_virus_victims += victim
//first we do hard status effect victims
diff --git a/code/modules/events/heart_attack.dm b/code/modules/events/heart_attack.dm
index 8a8902d5724c3d..f073676c5b415a 100644
--- a/code/modules/events/heart_attack.dm
+++ b/code/modules/events/heart_attack.dm
@@ -34,6 +34,10 @@
continue
if(!(candidate.mind.assigned_role.job_flags & JOB_CREW_MEMBER))//only crewmembers can get one, a bit unfair for some ghost roles and it wastes the event
continue
+ // SKYRAT EDIT ADD START - Station/area event candidate filtering
+ if(engaged_role_play_check(candidate, station = TRUE, dorms = TRUE))
+ continue
+ // SKYRAT EDIT ADD END
if(candidate.satiety <= -60 && !candidate.has_status_effect(/datum/status_effect/exercised)) //Multiple junk food items recently //No foodmaxxing for the achievement
heart_attack_candidates[candidate] = 3
else
diff --git a/code/modules/events/shuttle_loan/shuttle_loan_datum.dm b/code/modules/events/shuttle_loan/shuttle_loan_datum.dm
index 3cc5a15a5077d3..424eb157efec08 100644
--- a/code/modules/events/shuttle_loan/shuttle_loan_datum.dm
+++ b/code/modules/events/shuttle_loan/shuttle_loan_datum.dm
@@ -17,6 +17,8 @@
. = ..()
if(!logging_desc)
stack_trace("No logging blurb set for [src.type]!")
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_LOANER_SHUTTLE))
+ bonus_points *= 1.15
/// Spawns paths added to `spawn_list`, and passes empty shuttle turfs so you can spawn more complicated things like dead bodies.
/datum/shuttle_loan_situation/proc/spawn_items(list/spawn_list, list/empty_shuttle_turfs)
diff --git a/code/modules/events/supermatter_surge.dm b/code/modules/events/supermatter_surge.dm
new file mode 100644
index 00000000000000..f8e1f2698f74a7
--- /dev/null
+++ b/code/modules/events/supermatter_surge.dm
@@ -0,0 +1,110 @@
+#define SURGE_DURATION_MIN 240 EVENT_SECONDS
+#define SURGE_DURATION_MAX 270 EVENT_SECONDS
+#define SURGE_SEVERITY_MIN 1
+#define SURGE_SEVERITY_MAX 4
+/// The amount of bullet energy we add for the duration of the SM surge
+#define SURGE_BULLET_ENERGY_ADDITION 5
+/// The amount of powerloss inhibition (energy retention) we add for the duration of the SM surge
+#define SURGE_BASE_POWERLOSS_INHIBITION 0.55
+/// The powerloss inhibition scaling based on surge severity
+#define SURGE_POWERLOSS_INHIBITION_MODIFIER 0.175
+/// The power generation scaling based on surge severity
+#define SURGE_POWER_GENERATION_MODIFIER 0.075
+/// The heat modifier scaling based on surge severity
+#define SURGE_HEAT_MODIFIER 0.25
+
+/**
+ * Supermatter Surge
+ *
+ * An engineering challenge event where the properties of the SM changes to be in a 'surge' of power.
+ * For the duration of the event a powerloss inhibition is added to nitrogen, causing the crystal to retain more of its internal energy.
+ * Heat modifier is lowered to generate some heat but not a high temp burn.
+ * Bullet energy from emitters is raised slightly to raise meV while turned on.
+ */
+
+/datum/round_event_control/supermatter_surge
+ name = "Supermatter Surge"
+ typepath = /datum/round_event/supermatter_surge
+ category = EVENT_CATEGORY_ENGINEERING
+ weight = 15
+ max_occurrences = 1
+ earliest_start = 20 MINUTES
+ description = "The supermatter will increase in power and heat by a random amount, and announce it."
+ min_wizard_trigger_potency = 4
+ max_wizard_trigger_potency = 7
+ admin_setup = list(
+ /datum/event_admin_setup/input_number/surge_spiciness,
+ )
+
+/datum/round_event_control/supermatter_surge/can_spawn_event(players_amt, allow_magic = FALSE)
+ . = ..()
+
+ if(!SSjob.has_minimum_jobs(crew_threshold = 3, jobs = JOB_GROUP_ENGINEERS, head_jobs = list(JOB_CHIEF_ENGINEER)))
+ return FALSE
+
+/datum/round_event/supermatter_surge
+ announce_when = 4
+ end_when = SURGE_DURATION_MIN
+ /// How powerful is the supermatter surge going to be?
+ var/surge_class = SURGE_SEVERITY_MIN
+ /// Typecasted reference to the supermatter chosen at event start
+ var/obj/machinery/power/supermatter_crystal/engine
+ /// Typecasted reference to the nitrogen properies in the SM chamber
+ var/datum/sm_gas/nitrogen/sm_gas
+
+/datum/event_admin_setup/input_number/surge_spiciness
+ input_text = "Set surge intensity. (Higher is more severe.)"
+ min_value = 1
+ max_value = 4
+
+/datum/event_admin_setup/input_number/surge_spiciness/prompt_admins()
+ default_value = rand(1, 4)
+ return ..()
+
+/datum/event_admin_setup/input_number/surge_spiciness/apply_to_event(datum/round_event/supermatter_surge/event)
+ event.surge_class = chosen_value
+
+/datum/round_event/supermatter_surge/setup()
+ engine = GLOB.main_supermatter_engine
+ if(isnull(engine))
+ stack_trace("SM surge event failed to find a supermatter engine!")
+ return
+
+ sm_gas = LAZYACCESS(engine.current_gas_behavior, /datum/gas/nitrogen)
+ if(isnull(sm_gas))
+ stack_trace("SM surge event failed to find gas properties for [engine].")
+ return
+
+ if(isnull(surge_class))
+ surge_class = rand(SURGE_SEVERITY_MIN, SURGE_SEVERITY_MAX)
+
+ end_when = rand(SURGE_DURATION_MIN, SURGE_DURATION_MAX)
+
+/datum/round_event/supermatter_surge/announce()
+ priority_announce("The Crystal Integrity Monitoring System has detected unusual atmospheric properties in the supermatter chamber and energy output from the supermatter crystal has increased significantly. Engineering intervention is required to stabilize the engine.", "Class [surge_class] Supermatter Surge Alert", 'sound/machines/engine_alert3.ogg')
+
+/datum/round_event/supermatter_surge/start()
+ engine.bullet_energy = surge_class + SURGE_BULLET_ENERGY_ADDITION
+ sm_gas.powerloss_inhibition = (surge_class * SURGE_POWERLOSS_INHIBITION_MODIFIER) + SURGE_BASE_POWERLOSS_INHIBITION
+ sm_gas.heat_power_generation = (surge_class * SURGE_POWER_GENERATION_MODIFIER) - 1
+ sm_gas.heat_modifier = (surge_class * SURGE_HEAT_MODIFIER) - 1
+
+
+/datum/round_event/supermatter_surge/end()
+ engine.bullet_energy = initial(engine.bullet_energy)
+ sm_gas.powerloss_inhibition = initial(sm_gas.powerloss_inhibition)
+ sm_gas.heat_power_generation = initial(sm_gas.heat_power_generation)
+ sm_gas.heat_modifier = initial(sm_gas.heat_modifier)
+ priority_announce("The supermatter surge has dissipated, crystal output readings have normalized.", "Anomaly Cleared")
+ engine = null
+ sm_gas = null
+
+#undef SURGE_DURATION_MIN
+#undef SURGE_DURATION_MAX
+#undef SURGE_SEVERITY_MIN
+#undef SURGE_SEVERITY_MAX
+#undef SURGE_BULLET_ENERGY_ADDITION
+#undef SURGE_BASE_POWERLOSS_INHIBITION
+#undef SURGE_POWERLOSS_INHIBITION_MODIFIER
+#undef SURGE_POWER_GENERATION_MODIFIER
+#undef SURGE_HEAT_MODIFIER
diff --git a/code/modules/fishing/fish/chasm_detritus.dm b/code/modules/fishing/fish/chasm_detritus.dm
index b3964eb401563e..8d6653781595f3 100644
--- a/code/modules/fishing/fish/chasm_detritus.dm
+++ b/code/modules/fishing/fish/chasm_detritus.dm
@@ -40,22 +40,21 @@ GLOBAL_LIST_INIT_TYPED(chasm_detritus_types, /datum/chasm_detritus, init_chasm_d
),
)
-/datum/chasm_detritus/proc/dispense_reward(turf/fishing_spot, turf/fisher_turf)
- if (prob(default_contents_chance))
+/datum/chasm_detritus/proc/dispense_detritus(mob/fisherman, turf/fishing_spot)
+ if(prob(default_contents_chance))
var/default_spawn = pick(default_contents[default_contents_key])
- return new default_spawn(fisher_turf)
- return find_chasm_contents(fishing_spot, fisher_turf)
+ return new default_spawn(get_turf(fisherman))
+ return find_chasm_contents(fishing_spot, get_turf(fisherman))
/// Returns the chosen detritus from the given list of things to choose from
/datum/chasm_detritus/proc/determine_detritus(list/chasm_stuff)
return pick(chasm_stuff)
-/// Returns an objected which is currently inside of a nearby chasm.
-/datum/chasm_detritus/proc/find_chasm_contents(datum/source, turf/fishing_spot, turf/fisher_turf)
- SIGNAL_HANDLER
+/// Returns an object which is currently inside of a nearby chasm.
+/datum/chasm_detritus/proc/find_chasm_contents(turf/fishing_spot, turf/fisher_turf)
var/list/chasm_contents = get_chasm_contents(fishing_spot)
- if (!length(chasm_contents))
+ if(!length(chasm_contents))
var/default_spawn = pick(default_contents[default_contents_key])
return new default_spawn(fisher_turf)
@@ -63,7 +62,7 @@ GLOBAL_LIST_INIT_TYPED(chasm_detritus_types, /datum/chasm_detritus, init_chasm_d
/datum/chasm_detritus/proc/get_chasm_contents(turf/fishing_spot)
. = list()
- for (var/obj/effect/abstract/chasm_storage/storage in range(5, fishing_spot))
+ for(var/obj/effect/abstract/chasm_storage/storage in range(5, fishing_spot))
for (var/thing as anything in storage.contents)
. += thing
@@ -76,7 +75,7 @@ GLOBAL_LIST_INIT_TYPED(chasm_detritus_types, /datum/chasm_detritus, init_chasm_d
/datum/chasm_detritus/restricted/get_chasm_contents(turf/fishing_spot)
. = list()
- for (var/obj/effect/abstract/chasm_storage/storage in range(5, fishing_spot))
+ for(var/obj/effect/abstract/chasm_storage/storage in range(5, fishing_spot))
for (var/thing as anything in storage.contents)
if(!istype(thing, chasm_storage_restricted_type))
continue
diff --git a/code/modules/fishing/fish/fish_traits.dm b/code/modules/fishing/fish/fish_traits.dm
index 6c7fb38e279bab..bec868ad24e77f 100644
--- a/code/modules/fishing/fish/fish_traits.dm
+++ b/code/modules/fishing/fish/fish_traits.dm
@@ -23,9 +23,9 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
SHOULD_CALL_PARENT(TRUE)
return list(ADDITIVE_FISHING_MOD = 0, MULTIPLICATIVE_FISHING_MOD = 1)
-/// Returns special minigame rules applied by this trait
-/datum/fish_trait/proc/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
- return list()
+/// Returns special minigame rules and effects applied by this trait
+/datum/fish_trait/proc/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
+ return
/// Applies some special qualities to the fish that has been spawned
/datum/fish_trait/proc/apply_to_fish(obj/item/fish/fish)
@@ -100,8 +100,8 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
name = "Heavy"
catalog_description = "This fish tends to stay near the waterbed.";
-/datum/fish_trait/heavy/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
- return list(FISHING_MINIGAME_RULE_HEAVY_FISH)
+/datum/fish_trait/heavy/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
+ minigame.fish_idle_velocity -= 10
/datum/fish_trait/carnivore
name = "Carnivore"
@@ -338,8 +338,9 @@ GLOBAL_LIST_INIT(fish_traits, init_subtypes_w_path_keys(/datum/fish_trait, list(
/datum/fish_trait/lubed/apply_to_fish(obj/item/fish/fish)
fish.AddComponent(/datum/component/slippery, 8 SECONDS, SLIDE|GALOSHES_DONT_HELP)
-/datum/fish_trait/lubed/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman)
- return list(FISHING_MINIGAME_RULE_LUBED_FISH)
+/datum/fish_trait/lubed/minigame_mod(obj/item/fishing_rod/rod, mob/fisherman, datum/fishing_challenge/minigame)
+ minigame.reeling_velocity *= 1.4
+ minigame.gravity_velocity *= 1.4
/datum/fish_trait/amphibious
name = "Amphibious"
diff --git a/code/modules/fishing/fish/fish_types.dm b/code/modules/fishing/fish/fish_types.dm
index e8752acb1de754..652b0aba8aa495 100644
--- a/code/modules/fishing/fish/fish_types.dm
+++ b/code/modules/fishing/fish/fish_types.dm
@@ -465,6 +465,7 @@
/obj/item/fish/holo/puffer
name = "holographic pufferfish"
desc ="A holographic representation of 100% safe-to-eat pufferfish... that is, if holographic fishes were even edible."
+ icon_state = "pufferfish"
sprite_width = 8
sprite_height = 8
average_size = 60
diff --git a/code/modules/fishing/fishing_equipment.dm b/code/modules/fishing/fishing_equipment.dm
index e94e13ed4e8c3c..6169b41fd88fce 100644
--- a/code/modules/fishing/fishing_equipment.dm
+++ b/code/modules/fishing/fishing_equipment.dm
@@ -162,14 +162,14 @@
/obj/item/fishing_hook/stabilized
name = "gyro-stabilized hook"
- desc = "A quirky hook that grants the user a better control of the tool, allowing them to move the hook both and up and down when reeling in, otherwise keeping it stabilized."
+ desc = "A quirky hook that grants the user a better control of the tool, allowing them to move the bait both and up and down when reeling in, otherwise keeping it in place."
icon_state = "gyro"
fishing_hook_traits = FISHING_HOOK_BIDIRECTIONAL
rod_overlay_icon_state = "hook_gyro_overlay"
/obj/item/fishing_hook/stabilized/examine(mob/user)
. = ..()
- . += span_notice("While fishing, you can hold the Ctrl key to move the bait down, rather than up.")
+ . += span_notice("While fishing, you can hold the Right Mouse Button to move the bait down, rather than up.")
/obj/item/fishing_hook/jaws
name = "jawed hook"
diff --git a/code/modules/fishing/fishing_minigame.dm b/code/modules/fishing/fishing_minigame.dm
index cdec5c54be6beb..478279749e406e 100644
--- a/code/modules/fishing/fishing_minigame.dm
+++ b/code/modules/fishing/fishing_minigame.dm
@@ -5,6 +5,35 @@
// UI minigame phase
#define MINIGAME_PHASE 3
+/// The height of the minigame slider. Not in pixels, but minigame units.
+#define FISHING_MINIGAME_AREA 1000
+/// Any lower than this, and the target position of the fish is considered null
+#define FISH_TARGET_MIN_DISTANCE 6
+/// The friction applied to fish jumps, so that it decelerates over time
+#define FISH_FRICTION_MULT 0.9
+/// Used to decide whether the fish can jump in a certain direction
+#define FISH_SHORT_JUMP_MIN_DISTANCE 100
+/// The maximum distance for a short jump
+#define FISH_SHORT_JUMP_MAX_DISTANCE 200
+// Acceleration mod when bait is over fish
+#define FISH_ON_BAIT_ACCELERATION_MULT 0.6
+/// The minimum velocity required for the bait to bounce
+#define BAIT_MIN_VELOCITY_BOUNCE 200
+/// The extra deceleration of velocity that happens when the bait switches direction
+#define BAIT_DECELERATION_MULT 2
+
+///Defines to know how the bait is moving on the minigame slider.
+#define REELING_STATE_IDLE 0
+#define REELING_STATE_UP 1
+#define REELING_STATE_DOWN 2
+
+///The pixel height of the minigame bar
+#define MINIGAME_SLIDER_HEIGHT 76
+///The standard pixel height of the bait
+#define MINIGAME_BAIT_HEIGHT 24
+///The standard pixel height of the fish (minus a pixel on each direction for the sake of a better looking sprite)
+#define MINIGAME_FISH_HEIGHT 4
+
/datum/fishing_challenge
/// When the ui minigame phase started
var/start_time
@@ -13,7 +42,7 @@
/// Fish AI type to use
var/fish_ai = FISH_AI_DUMB
/// Rule modifiers (eg weighted bait)
- var/list/special_effects = list()
+ var/special_effects = NONE
/// Did the game get past the baiting phase, used to track if bait should be consumed afterwards
var/bait_taken = FALSE
/// Result path
@@ -30,14 +59,67 @@
var/obj/item/fishing_rod/used_rod
/// Lure visual
var/obj/effect/fishing_lure/lure
- /// Background image from /datum/asset/simple/fishing_minigame
- var/background = "default"
+ /// Background icon state from fishing_hud.dmi
+ var/background = "background_default"
/// Fishing line visual
var/datum/beam/fishing_line
var/experience_multiplier = 1
+ /// How much space the fish takes on the minigame slider
+ var/fish_height = 50
+ /// How much space the bait takes on the minigame slider
+ var/bait_height = 320
+ /// The height in pixels of the bait bar
+ var/bait_pixel_height = MINIGAME_BAIT_HEIGHT
+ /// The height in pixels of the fish
+ var/fish_pixel_height = MINIGAME_FISH_HEIGHT
+ /// The position of the fish on the minigame slider
+ var/fish_position = 0
+ /// The position of the bait on the minigame slider
+ var/bait_position = 0
+ /// The current speed the fish is moving at
+ var/fish_velocity = 0
+ /// The current speed the bait is moving at
+ var/bait_velocity = 0
+
+ /// The completion score. If it reaches 100, it's a win. If it reaches 0, it's a loss.
+ var/completion = 30
+ /// How much completion is lost per second when the bait area is not intersecting with the fish's
+ var/completion_loss = 6
+ /// How much completion is gained per second when the bait area is intersecting with the fish's
+ var/completion_gain = 5
+
+ /// How likely the fish is to perform a standard jump, then multiplied by difficulty
+ var/short_jump_chance = 2.25
+ /// How likely the fish is to perform a long jump, then multiplied by difficulty
+ var/long_jump_chance = 0.0625
+ /// The speed limit for the short jump
+ var/short_jump_velocity_limit = 400
+ /// The speed limit for the long jump
+ var/long_jump_velocity_limit = 200
+ /// The current speed limit used
+ var/current_velocity_limit = 200
+ /// The base velocity of the fish, which may affect jump distances and falling speed.
+ var/fish_idle_velocity = 0
+ /// A position on the slider the fish wants to get to
+ var/target_position
+ /// If true, the fish can jump while a target position is set, thus overriding it
+ var/can_interrupt_move = TRUE
+
+ /// Whether the bait is idle or reeling up or down (left and right click)
+ var/reeling_state = REELING_STATE_IDLE
+ /// The acceleration of the bait while not reeling
+ var/gravity_velocity = -800
+ /// The acceleration of the bait while reeling
+ var/reeling_velocity = 1200
+ /// By how much the bait recoils back when hitting the bounds of the slider while idle
+ var/bait_bounce_mult = 0.6
+
+ ///The background as shown in the minigame, and the holder of the other visual overlays
+ var/atom/movable/screen/fishing_hud/fishing_hud
+
/datum/fishing_challenge/New(datum/component/fishing_spot/comp, reward_path, obj/item/fishing_rod/rod, mob/user)
src.user = user
src.reward_path = reward_path
@@ -52,32 +134,61 @@
if(ispath(reward_path,/obj/item/fish))
var/obj/item/fish/fish = reward_path
fish_ai = initial(fish.fish_ai_type)
+ switch(fish_ai)
+ if(FISH_AI_ZIPPY) // Keeps on jumping
+ short_jump_chance *= 3
+ if(FISH_AI_SLOW) // Only does long jump, and doesn't change direction until it gets there
+ short_jump_chance = 0
+ long_jump_chance = 1.5
+ long_jump_velocity_limit = 150
+ long_jump_velocity_limit = FALSE
// Apply fish trait modifiers
var/list/fish_list_properties = collect_fish_properties()
var/list/fish_traits = fish_list_properties[fish][NAMEOF(fish, fish_traits)]
for(var/fish_trait in fish_traits)
var/datum/fish_trait/trait = GLOB.fish_traits[fish_trait]
- special_effects += trait.minigame_mod(rod, user)
+ trait.minigame_mod(rod, user, src)
/// Enable special parameters
if(rod.line)
if(rod.line.fishing_line_traits & FISHING_LINE_BOUNCY)
- special_effects |= FISHING_MINIGAME_RULE_LIMIT_LOSS
+ completion_loss -= 2
if(rod.hook)
if(rod.hook.fishing_hook_traits & FISHING_HOOK_WEIGHTED)
- special_effects |= FISHING_MINIGAME_RULE_WEIGHTED_BAIT
+ bait_bounce_mult = 0.1
if(rod.hook.fishing_hook_traits & FISHING_HOOK_BIDIRECTIONAL)
special_effects |= FISHING_MINIGAME_RULE_BIDIRECTIONAL
if(rod.hook.fishing_hook_traits & FISHING_HOOK_NO_ESCAPE)
special_effects |= FISHING_MINIGAME_RULE_NO_ESCAPE
if(rod.hook.fishing_hook_traits & FISHING_HOOK_ENSNARE)
- special_effects |= FISHING_MINIGAME_RULE_LIMIT_LOSS
+ completion_loss -= 2
if(rod.hook.fishing_hook_traits & FISHING_HOOK_KILL)
special_effects |= FISHING_MINIGAME_RULE_KILL
- if((FISHING_MINIGAME_RULE_KILL in special_effects) && ispath(reward_path,/obj/item/fish))
+ if(special_effects & FISHING_MINIGAME_RULE_KILL && ispath(reward_path,/obj/item/fish))
RegisterSignal(user, COMSIG_MOB_FISHING_REWARD_DISPENSED, PROC_REF(hurt_fish))
difficulty += comp.fish_source.calculate_difficulty(reward_path, rod, user, src)
+ difficulty = round(difficulty)
+
+ /**
+ * If the chances are higher than 1% (100% at maximum difficulty), they'll scale
+ * less than proportionally (exponent less than 1) instead.
+ * This way we ensure fish with high jump chances won't get TOO jumpy until
+ * they near the maximum difficulty, at which they hit 100%
+ */
+ var/square_angle_rad = TORADIANS(90)
+ var/zero_one_difficulty = difficulty/100
+ if(short_jump_chance > 1)
+ short_jump_chance = (zero_one_difficulty**(square_angle_rad-TORADIANS(arctan(short_jump_chance * 1/square_angle_rad))))*100
+ else
+ short_jump_chance *= difficulty
+ if(long_jump_chance > 1)
+ long_jump_chance = (zero_one_difficulty**(square_angle_rad-TORADIANS(arctan(long_jump_chance * 1/square_angle_rad))))*100
+ else
+ long_jump_chance *= difficulty
+
+ bait_height -= difficulty
+ bait_pixel_height = round(MINIGAME_BAIT_HEIGHT * (bait_height/initial(bait_height)), 1)
/datum/fishing_challenge/Destroy(force, ...)
if(!completed)
@@ -86,6 +197,7 @@
QDEL_NULL(fishing_line)
if(lure)
QDEL_NULL(lure)
+ SStgui.close_uis(src)
user = null
used_rod = null
return ..()
@@ -119,13 +231,13 @@
/datum/fishing_challenge/proc/handle_click(mob/source, atom/target, modifiers)
SIGNAL_HANDLER
//You need to be holding the rod to use it.
- if(!source.get_active_held_item(used_rod) || LAZYACCESS(modifiers, SHIFT_CLICK))
+ if(!source.get_active_held_item(used_rod) || LAZYACCESS(modifiers, SHIFT_CLICK) || LAZYACCESS(modifiers, CTRL_CLICK) || LAZYACCESS(modifiers, ALT_CLICK))
return
if(phase == WAIT_PHASE) //Reset wait
send_alert("miss!")
start_baiting_phase()
else if(phase == BITING_PHASE)
- INVOKE_ASYNC(src, PROC_REF(start_minigame_phase))
+ start_minigame_phase()
return COMSIG_MOB_CANCEL_CLICKON
/// Challenge interrupted by something external
@@ -147,16 +259,20 @@
send_alert("stopped fishing")
complete(FALSE)
-/datum/fishing_challenge/proc/complete(win = FALSE, perfect_win = FALSE)
+/datum/fishing_challenge/proc/complete(win = FALSE)
+ if(completed)
+ return
deltimer(next_phase_timer)
completed = TRUE
+ if(phase == MINIGAME_PHASE)
+ remove_minigame_hud()
if(user)
REMOVE_TRAIT(user, TRAIT_GONE_FISHING, REF(src))
if(start_time)
var/seconds_spent = (world.time - start_time) * 0.1
- if(!(FISHING_MINIGAME_RULE_NO_EXP in special_effects))
+ if(!(special_effects & FISHING_MINIGAME_RULE_NO_EXP))
user.mind?.adjust_experience(/datum/skill/fishing, round(seconds_spent * FISHING_SKILL_EXP_PER_SECOND * experience_multiplier))
- if(win && user.mind?.get_skill_level(/datum/skill/fishing) >= SKILL_LEVEL_LEGENDARY)
+ if(user.mind?.get_skill_level(/datum/skill/fishing) >= SKILL_LEVEL_LEGENDARY)
user.client?.give_award(/datum/award/achievement/skill/legendary_fisher, user)
if(win)
if(reward_path != FISHING_DUD)
@@ -188,16 +304,10 @@
///The damage dealt per second to the fish when FISHING_MINIGAME_RULE_KILL is active.
#define FISH_DAMAGE_PER_SECOND 2
-/datum/fishing_challenge/proc/start_minigame_phase()
- phase = MINIGAME_PHASE
- deltimer(next_phase_timer)
- if((FISHING_MINIGAME_RULE_KILL in special_effects) && ispath(reward_path,/obj/item/fish))
- var/obj/item/fish/fish = reward_path
- var/wait_time = (initial(fish.health) / FISH_DAMAGE_PER_SECOND) SECONDS
- addtimer(CALLBACK(src, PROC_REF(win_anyway)), wait_time)
- start_time = world.time
- experience_multiplier += difficulty * FISHING_SKILL_DIFFIULTY_EXP_MULT
- ui_interact(user)
+///The player is no longer around to play the minigame, so we interrupt it.
+/datum/fishing_challenge/proc/on_user_logout(datum/source)
+ SIGNAL_HANDLER
+ interrupt(balloon_alert = FALSE)
/datum/fishing_challenge/proc/win_anyway()
if(!completed)
@@ -211,57 +321,251 @@
var/damage = CEILING((world.time - start_time)/10 * FISH_DAMAGE_PER_SECOND, 1)
reward.adjust_health(reward.health - damage)
-#undef FISH_DAMAGE_PER_SECOND
+/datum/fishing_challenge/proc/start_minigame_phase()
+ if(!prepare_minigame_hud())
+ return
+ phase = MINIGAME_PHASE
+ deltimer(next_phase_timer)
+ if((FISHING_MINIGAME_RULE_KILL in special_effects) && ispath(reward_path,/obj/item/fish))
+ var/obj/item/fish/fish = reward_path
+ var/wait_time = (initial(fish.health) / FISH_DAMAGE_PER_SECOND) SECONDS
+ addtimer(CALLBACK(src, PROC_REF(win_anyway)), wait_time)
+ start_time = world.time
+ experience_multiplier += difficulty * FISHING_SKILL_DIFFIULTY_EXP_MULT
-/datum/fishing_challenge/ui_interact(mob/user, datum/tgui/ui)
- ui = SStgui.try_update_ui(user, src, ui)
- if(!ui)
- ui = new(user, src, "Fishing")
- ui.set_autoupdate(FALSE)
- ui.set_mouse_hook(TRUE)
- ui.open()
+#undef FISH_DAMAGE_PER_SECOND
-/datum/fishing_challenge/ui_host(mob/user)
- return lure //Could be the target really
+///Initialize the minigame hud and register some signals to make it work.
+/datum/fishing_challenge/proc/prepare_minigame_hud()
+ if(!user.client || user.incapacitated())
+ return FALSE
+ . = TRUE
+ fishing_hud = new
+ fishing_hud.prepare_minigame(src)
+ RegisterSignal(user.client, COMSIG_CLIENT_MOUSEDOWN, PROC_REF(start_reeling))
+ RegisterSignal(user.client, COMSIG_CLIENT_MOUSEUP, PROC_REF(stop_reeling))
+ RegisterSignal(user, COMSIG_MOB_LOGOUT, PROC_REF(on_user_logout))
+ START_PROCESSING(SSfishing, src)
+
+///Stop processing and remove references to the minigame hud
+/datum/fishing_challenge/proc/remove_minigame_hud()
+ STOP_PROCESSING(SSfishing, src)
+ QDEL_NULL(fishing_hud)
+
+///While the mouse button is held down, the bait will be reeling up (or down on r-click if the bidirectional rule is enabled)
+/datum/fishing_challenge/proc/start_reeling(client/source, datum/object, location, control, params)
+ SIGNAL_HANDLER
+ var/bidirectional = special_effects & FISHING_MINIGAME_RULE_BIDIRECTIONAL
+ var/list/modifiers = params2list(params)
+ if(bidirectional && LAZYACCESS(modifiers, RIGHT_CLICK))
+ reeling_state = REELING_STATE_DOWN
+ else
+ reeling_state = REELING_STATE_UP
+
+///Reset the reeling state to idle once the mouse button is released
+/datum/fishing_challenge/proc/stop_reeling(client/source, datum/object, location, control, params)
+ SIGNAL_HANDLER
+ reeling_state = REELING_STATE_IDLE
+
+///Update the state of the fish, the bait and the hud
+/datum/fishing_challenge/process(seconds_per_tick)
+ move_fish(seconds_per_tick)
+ move_bait(seconds_per_tick)
+ if(!QDELETED(fishing_hud))
+ update_visuals()
+
+///The proc that moves the fish around, just like in the old TGUI, mostly.
+/datum/fishing_challenge/proc/move_fish(seconds_per_tick)
+ var/long_chance = long_jump_chance * seconds_per_tick * 10
+ var/short_chance = short_jump_chance * seconds_per_tick * 10
+
+ // If we have the target but we're close enough, mark as target reached
+ if(abs(target_position - fish_position) < FISH_TARGET_MIN_DISTANCE)
+ target_position = null
+
+ // Switching to new long jump target can interrupt any other
+ if((can_interrupt_move || isnull(target_position)) && prob(long_chance))
+ /**
+ * Move at least 0.75 to full of the availible bar in given direction,
+ * and more likely to move in the direction where there's more space
+ */
+ var/distance_from_top = FISHING_MINIGAME_AREA - fish_position - fish_height
+ var/distance_from_bottom = fish_position
+ var/top_chance
+ if(distance_from_top < FISH_SHORT_JUMP_MIN_DISTANCE)
+ top_chance = 0
+ else
+ top_chance = (distance_from_top/max(distance_from_bottom, 1)) * 100
+ var/new_target = fish_position
+ if(prob(top_chance))
+ new_target += distance_from_top * rand(75, 100)/100
+ else
+ new_target -= distance_from_bottom * rand(75, 100)/100
+ target_position = round(new_target)
+ current_velocity_limit = long_jump_velocity_limit
+
+ // Move towards target
+ if(!isnull(target_position))
+ var/distance = target_position - fish_position
+ // about 5 at diff 15 , 10 at diff 30, 30 at diff 100
+ var/acceleration_mult = 0.3 * difficulty + 0.5
+ var/target_acceleration = distance * acceleration_mult * seconds_per_tick
+
+ fish_velocity = fish_velocity * FISH_FRICTION_MULT + target_acceleration
+ else if(prob(short_chance))
+ var/distance_from_top = FISHING_MINIGAME_AREA - fish_position - fish_height
+ var/distance_from_bottom = fish_position
+ var/jump_length
+ if(distance_from_top >= FISH_SHORT_JUMP_MIN_DISTANCE)
+ jump_length = rand(FISH_SHORT_JUMP_MIN_DISTANCE, FISH_SHORT_JUMP_MAX_DISTANCE)
+ if(distance_from_bottom >= FISH_SHORT_JUMP_MIN_DISTANCE && (!jump_length || prob(50)))
+ jump_length = -rand(FISH_SHORT_JUMP_MIN_DISTANCE, FISH_SHORT_JUMP_MAX_DISTANCE)
+ target_position = clamp(fish_position + jump_length, 0, FISHING_MINIGAME_AREA - fish_height)
+ current_velocity_limit = short_jump_velocity_limit
+
+ fish_velocity = clamp(fish_velocity + fish_idle_velocity, -current_velocity_limit, current_velocity_limit)
+ fish_position = clamp(fish_position + fish_velocity * seconds_per_tick, 0, FISHING_MINIGAME_AREA - fish_height)
+
+///The proc that moves the bait around, just like in the old TGUI, mostly.
+/datum/fishing_challenge/proc/move_bait(seconds_per_tick)
+ var/should_bounce = abs(bait_velocity) > BAIT_MIN_VELOCITY_BOUNCE
+ bait_position += bait_velocity * seconds_per_tick
+ // Hitting the top bound
+ if(bait_position > FISHING_MINIGAME_AREA - bait_height)
+ bait_position = FISHING_MINIGAME_AREA - bait_height
+ if(reeling_state == REELING_STATE_UP || !should_bounce)
+ bait_velocity = 0
+ else
+ bait_velocity = -bait_velocity * bait_bounce_mult
+ // Hitting rock bottom
+ else if(bait_position < 0)
+ bait_position = 0
+ if(reeling_state == REELING_STATE_DOWN || !should_bounce)
+ bait_velocity = 0
+ else
+ bait_velocity = -bait_velocity * bait_bounce_mult
+
+ var/fish_on_bait = (fish_position + fish_height >= bait_position) && (bait_position + bait_height >= fish_position)
+
+ var/bidirectional = special_effects & FISHING_MINIGAME_RULE_BIDIRECTIONAL
+
+ var/velocity_change
+ switch(reeling_state)
+ if(REELING_STATE_UP)
+ velocity_change = reeling_velocity
+ if(REELING_STATE_DOWN)
+ velocity_change = -reeling_velocity
+ if(REELING_STATE_IDLE)
+ if(!bidirectional || bait_velocity > 0)
+ velocity_change = gravity_velocity
+ else
+ velocity_change = -gravity_velocity
+ velocity_change *= (fish_on_bait ? FISH_ON_BAIT_ACCELERATION_MULT : 1) * seconds_per_tick
+
+ velocity_change = round(velocity_change)
+
+ /**
+ * Pull the brake on the velocity if the current velocity and the acceleration
+ * have different directions, making the bait less slippery, thus easier to control
+ */
+ if(bait_velocity > 0 && velocity_change < 0)
+ bait_velocity += max(-bait_velocity, velocity_change * BAIT_DECELERATION_MULT)
+ else if(bait_velocity < 0 && velocity_change > 0)
+ bait_velocity += min(-bait_velocity, velocity_change * BAIT_DECELERATION_MULT)
+
+ ///bidirectional baits stay bouyant while idle
+ if(bidirectional && reeling_state == REELING_STATE_IDLE)
+ if(velocity_change < 0)
+ bait_velocity = max(bait_velocity + velocity_change, 0)
+ else if(velocity_change > 0)
+ bait_velocity = min(bait_velocity + velocity_change, 0)
+ else
+ bait_velocity += velocity_change
+
+ //check that the fish area is still intersecting the bait now that it has moved
+ fish_on_bait = (fish_position + fish_height >= bait_position) && (bait_position + bait_height >= fish_position)
+
+ if(fish_on_bait)
+ completion += completion_gain * seconds_per_tick
+ if(completion >= 100)
+ complete(TRUE)
+ else
+ completion -= completion_loss * seconds_per_tick
+ if(completion <= 0 && !(special_effects & FISHING_MINIGAME_RULE_NO_ESCAPE))
+ user.balloon_alert(user, "it got away!")
+ complete(FALSE)
+
+ completion = clamp(completion, 0, 100)
+
+///update the vertical pixel position of both fish and bait, and the icon state of the completion bar
+/datum/fishing_challenge/proc/update_visuals()
+ var/bait_offset_mult = bait_position/FISHING_MINIGAME_AREA
+ fishing_hud.hud_bait.pixel_y = round(MINIGAME_SLIDER_HEIGHT * bait_offset_mult, 1)
+ var/fish_offset_mult = fish_position/FISHING_MINIGAME_AREA
+ fishing_hud.hud_fish.pixel_y = round(MINIGAME_SLIDER_HEIGHT * fish_offset_mult, 1)
+ fishing_hud.hud_completion.icon_state = "completion_[FLOOR(completion, 5)]"
+
+///The screen object which bait, fish, and completion bar are visually attached to.
+/atom/movable/screen/fishing_hud
+ icon = 'icons/hud/fishing_hud.dmi'
+ screen_loc = "CENTER+1:8,CENTER:2"
+ name = "fishing minigame"
+ appearance_flags = APPEARANCE_UI|KEEP_TOGETHER
+ alpha = 230
+ ///The fish as shown in the minigame
+ var/atom/movable/screen/hud_fish/hud_fish
+ ///The bait as shown in the minigame
+ var/atom/movable/screen/hud_bait/hud_bait
+ ///The completion bar as shown in the minigame
+ var/atom/movable/screen/hud_completion/hud_completion
+
+///Initialize bait, fish and completion bar and add them to the visual appearance of this screen object.
+/atom/movable/screen/fishing_hud/proc/prepare_minigame(datum/fishing_challenge/challenge)
+ icon_state = challenge.background
+ add_overlay("frame")
+ hud_bait = new(null, null, challenge)
+ hud_fish = new
+ hud_completion = new(null, null, challenge)
+ vis_contents += list(hud_bait, hud_fish, hud_completion)
+ challenge.user.client.screen += src
+
+/atom/movable/screen/fishing_hud/Destroy()
+ QDEL_NULL(hud_fish)
+ QDEL_NULL(hud_bait)
+ QDEL_NULL(hud_completion)
+ return ..()
-// Manually closing the ui is treated as lose
-/datum/fishing_challenge/ui_close(mob/user)
- . = ..()
- if(!completed)
- send_alert("stopped fishing")
- complete(FALSE)
+/atom/movable/screen/hud_bait
+ icon = 'icons/hud/fishing_hud.dmi'
+ icon_state = "bait"
+ vis_flags = VIS_INHERIT_ID
-/datum/fishing_challenge/ui_static_data(mob/user)
- . = ..()
- .["difficulty"] = clamp(difficulty, 1, 100)
- .["fish_ai"] = fish_ai
- .["special_effects"] = special_effects
- .["background_image"] = background
-
-/datum/fishing_challenge/ui_assets(mob/user)
- return list(get_asset_datum(/datum/asset/simple/fishing_minigame)) //preset screens
-
-/datum/fishing_challenge/ui_status(mob/user, datum/ui_state/state)
- return min(
- get_dist(user, lure) > 5 ? UI_CLOSE : UI_INTERACTIVE,
- ui_status_user_has_free_hands(user),
- ui_status_user_is_abled(user, lure),
- )
-
-/datum/fishing_challenge/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
+/atom/movable/screen/hud_bait/Initialize(mapload, datum/hud/hud_owner, datum/fishing_challenge/challenge)
. = ..()
- if(.)
+ if(!challenge || challenge.bait_pixel_height == MINIGAME_BAIT_HEIGHT)
return
-
- if(phase != MINIGAME_PHASE)
- return
-
- switch(action)
- if("win")
- complete(win = TRUE)
- if("lose")
- send_alert("it got away")
- complete(win = FALSE)
+ var/static/icon_height
+ if(!icon_height)
+ var/list/icon_dimensions = get_icon_dimensions(icon)
+ icon_height = icon_dimensions["height"]
+ var/height_percent_diff = challenge.bait_pixel_height/MINIGAME_BAIT_HEIGHT
+ transform = transform.Scale(1, height_percent_diff)
+ pixel_z = -icon_height * (1 - height_percent_diff) * 0.5
+
+/atom/movable/screen/hud_fish
+ icon = 'icons/hud/fishing_hud.dmi'
+ icon_state = "fish"
+ vis_flags = VIS_INHERIT_ID
+
+/atom/movable/screen/hud_completion
+ icon = 'icons/hud/fishing_hud.dmi'
+ icon_state = "completion_0"
+ vis_flags = VIS_INHERIT_ID
+
+/atom/movable/screen/hud_completion/Initialize(mapload, datum/hud/hud_owner, datum/fishing_challenge/challenge)
+ . = ..()
+ if(challenge)
+ icon_state = "completion_[FLOOR(challenge.completion, 5)]"
/// The visual that appears over the fishing spot
/obj/effect/fishing_lure
@@ -280,3 +584,21 @@
#undef WAIT_PHASE
#undef BITING_PHASE
#undef MINIGAME_PHASE
+
+#undef FISHING_MINIGAME_AREA
+#undef FISH_TARGET_MIN_DISTANCE
+#undef FISH_FRICTION_MULT
+#undef FISH_SHORT_JUMP_MIN_DISTANCE
+#undef FISH_SHORT_JUMP_MAX_DISTANCE
+#undef FISH_ON_BAIT_ACCELERATION_MULT
+#undef BAIT_MIN_VELOCITY_BOUNCE
+#undef BAIT_DECELERATION_MULT
+
+#undef MINIGAME_SLIDER_HEIGHT
+#undef MINIGAME_BAIT_HEIGHT
+#undef MINIGAME_FISH_HEIGHT
+
+#undef REELING_STATE_IDLE
+#undef REELING_STATE_UP
+#undef REELING_STATE_DOWN
+
diff --git a/code/modules/fishing/fishing_rod.dm b/code/modules/fishing/fishing_rod.dm
index 58936d29534c13..3d1090447a3982 100644
--- a/code/modules/fishing/fishing_rod.dm
+++ b/code/modules/fishing/fishing_rod.dm
@@ -118,11 +118,14 @@
return hook?.reason_we_cant_fish(target_fish_source)
/obj/item/fishing_rod/proc/consume_bait(atom/movable/reward)
- if(!reward)
+ // catching things that aren't fish or alive mobs doesn't consume baits.
+ if(isnull(reward) || isnull(bait))
return
- var/mob/living/caught_mob = isliving(reward) ? reward : null
- // catching non-fish, non-mob movables, or dead mobs (probably from a chasm) doesn't consume baits.
- if(!bait || !isfish(reward) || !caught_mob || (caught_mob && caught_mob.stat == DEAD))
+ if(isliving(reward))
+ var/mob/living/caught_mob = reward
+ if(caught_mob.stat == DEAD)
+ return
+ else if(!isfish(reward))
return
QDEL_NULL(bait)
update_icon()
@@ -208,7 +211,7 @@
SIGNAL_HANDLER
. = NONE
- if(!CheckToolReach(src, source.target, cast_range))
+ if(!isturf(source.origin.loc) || !isturf(source.target.loc) || !CheckToolReach(src, source.target, cast_range))
SEND_SIGNAL(source, COMSIG_FISHING_LINE_SNAPPED) //Stepped out of range or los interrupted
return BEAM_CANCEL_DRAW
diff --git a/code/modules/fishing/sources/_fish_source.dm b/code/modules/fishing/sources/_fish_source.dm
index e8156f26cba73f..5a34db9ce5fbeb 100644
--- a/code/modules/fishing/sources/_fish_source.dm
+++ b/code/modules/fishing/sources/_fish_source.dm
@@ -16,7 +16,7 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour
/// How the spot type is described in fish catalog section about fish sources, will be skipped if null
var/catalog_description
/// Background image name from /datum/asset/simple/fishing_minigame
- var/background = "fishing_background_default"
+ var/background = "background_default"
/datum/fish_source/New()
if(!PERFORM_ALL_TESTS(focus_only/fish_sources_tables))
@@ -45,7 +45,7 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour
. += SETTLER_DIFFICULTY_MOD
// Difficulty modifier added by the fisher's skill level
- if(!challenge || !(FISHING_MINIGAME_RULE_NO_EXP in challenge.special_effects))
+ if(!challenge || !(challenge.special_effects & FISHING_MINIGAME_RULE_NO_EXP))
. += fisherman.mind?.get_skill_modifier(/datum/skill/fishing, SKILL_VALUE_MODIFIER)
// Difficulty modifier added by the rod
@@ -127,21 +127,24 @@ GLOBAL_LIST_INIT(preset_fish_sources, init_subtypes_w_path_keys(/datum/fish_sour
fish_table -= reward_path
var/atom/movable/reward = spawn_reward(reward_path, fisherman, fishing_spot)
- if(!reward) //baloon alert instead
+ if(!reward) //balloon alert instead
fisherman.balloon_alert(fisherman,pick(duds))
return
if(isitem(reward)) //Try to put it in hand
INVOKE_ASYNC(fisherman, TYPE_PROC_REF(/mob, put_in_hands), reward)
+ else // for fishing things like corpses, move them to the turf of the fisherman
+ INVOKE_ASYNC(reward, TYPE_PROC_REF(/atom/movable, forceMove), get_turf(fisherman))
fisherman.balloon_alert(fisherman, "caught [reward]!")
+
SEND_SIGNAL(fisherman, COMSIG_MOB_FISHING_REWARD_DISPENSED, reward)
return reward
/// Spawns a reward from a atom path right where the fisherman is. Part of the dispense_reward() logic.
-/datum/fish_source/proc/spawn_reward(reward_path, mob/fisherman, turf/fishing_spot)
+/datum/fish_source/proc/spawn_reward(reward_path, mob/fisherman, turf/fishing_spot)
if(reward_path == FISHING_DUD)
return
if(ispath(reward_path, /datum/chasm_detritus))
- return GLOB.chasm_detritus_types[reward_path].dispense_reward(fishing_spot, get_turf(fisherman))
+ return GLOB.chasm_detritus_types[reward_path].dispense_detritus(fisherman, fishing_spot)
if(!ispath(reward_path, /atom/movable))
CRASH("Unsupported /datum path [reward_path] passed to fish_source/proc/spawn_reward()")
var/atom/movable/reward = new reward_path(get_turf(fisherman))
diff --git a/code/modules/fishing/sources/source_types.dm b/code/modules/fishing/sources/source_types.dm
index cea97005f281ce..ffb37753881d56 100644
--- a/code/modules/fishing/sources/source_types.dm
+++ b/code/modules/fishing/sources/source_types.dm
@@ -24,9 +24,10 @@
/obj/item/fish/guppy = 10,
)
catalog_description = "Fish dimension (Fishing portal generator)"
+
/datum/fish_source/chasm
catalog_description = "Chasm depths"
- background = "fishing_background_lavaland"
+ background = "background_lavaland"
fish_table = list(
FISHING_DUD = 5,
/obj/item/fish/chasm_crab = 15,
@@ -45,7 +46,7 @@
/datum/fish_source/lavaland
catalog_description = "Lava vents"
- background = "fishing_background_lavaland"
+ background = "background_lavaland"
fish_table = list(
FISHING_DUD = 5,
/obj/item/stack/ore/slag = 20,
diff --git a/code/modules/food_and_drinks/machinery/monkeyrecycler.dm b/code/modules/food_and_drinks/machinery/monkeyrecycler.dm
index 5bb772c739855f..8978c975c5ecf1 100644
--- a/code/modules/food_and_drinks/machinery/monkeyrecycler.dm
+++ b/code/modules/food_and_drinks/machinery/monkeyrecycler.dm
@@ -98,6 +98,6 @@ GLOBAL_LIST_EMPTY(monkey_recyclers)
/obj/machinery/monkey_recycler/multitool_act(mob/living/user, obj/item/multitool/I)
. = ..()
if(istype(I))
- to_chat(user, span_notice("You log [src] in the multitool's buffer."))
I.set_buffer(src)
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
diff --git a/code/modules/food_and_drinks/machinery/processor.dm b/code/modules/food_and_drinks/machinery/processor.dm
index 5057dbfe08acb7..2c1b0b9d3b3475 100644
--- a/code/modules/food_and_drinks/machinery/processor.dm
+++ b/code/modules/food_and_drinks/machinery/processor.dm
@@ -68,11 +68,8 @@
var/list/cached_mats = recipe.preserve_materials && what.custom_materials
var/cached_multiplier = (recipe.food_multiplier * rating_amount)
for(var/i in 1 to cached_multiplier)
- var/atom/processed_food = new recipe.output(
- drop_location(),
- /* starting_reagent_purity = */ null,
- /* no_base_reagents = */ TRUE,
- )
+ var/atom/processed_food = new recipe.output(drop_location())
+ processed_food.reagents.clear_reagents()
what.reagents.copy_to(processed_food, what.reagents.total_volume, multiplier = 1 / cached_multiplier)
if(cached_mats)
processed_food.set_custom_materials(cached_mats, 1 / cached_multiplier)
diff --git a/code/modules/food_and_drinks/pizzabox.dm b/code/modules/food_and_drinks/pizzabox.dm
index a32928723623e8..1454f1e6917f19 100644
--- a/code/modules/food_and_drinks/pizzabox.dm
+++ b/code/modules/food_and_drinks/pizzabox.dm
@@ -100,7 +100,7 @@
pizza_overlay.pixel_y = -2
. += pizza_overlay
if(bomb)
- var/mutable_appearance/bomb_overlay = mutable_appearance(bomb.icon, bomb.icon_state)
+ var/mutable_appearance/bomb_overlay = mutable_appearance(bomb.icon, bomb.icon_state, layer = layer + 0.01)
bomb_overlay.pixel_y = 8
. += bomb_overlay
return
@@ -109,13 +109,13 @@
for(var/stacked_box in boxes)
box_offset += 3
var/obj/item/pizzabox/box = stacked_box
- var/mutable_appearance/box_overlay = mutable_appearance(box.icon, box.icon_state)
+ var/mutable_appearance/box_overlay = mutable_appearance(box.icon, box.icon_state, layer = layer + (box_offset * 0.01))
box_overlay.pixel_y = box_offset
. += box_overlay
var/obj/item/pizzabox/box = LAZYLEN(length(boxes)) ? boxes[length(boxes)] : src
if(box.boxtag != "")
- var/mutable_appearance/tag_overlay = mutable_appearance(icon, "pizzabox_tag")
+ var/mutable_appearance/tag_overlay = mutable_appearance(icon, "pizzabox_tag", layer = layer + (box_offset * 0.02))
tag_overlay.pixel_y = box_offset
. += tag_overlay
@@ -306,8 +306,9 @@
/obj/item/pizzabox/bomb/Initialize(mapload)
. = ..()
if(!pizza)
- var/randompizza = pick(subtypesof(/obj/item/food/pizza))
+ var/randompizza = pick(subtypesof(/obj/item/food/pizza) - /obj/item/food/pizza/flatbread) //also disincludes another base type
pizza = new randompizza(src)
+ update_appearance()
register_bomb(new /obj/item/bombcore/miniature/pizza(src))
set_wires(new /datum/wires/explosive/pizza(src))
diff --git a/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm b/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm
index 7e84fe4f5c07c6..1da96fc0aae706 100644
--- a/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm
+++ b/code/modules/food_and_drinks/recipes/drinks/drinks_alcoholic.dm
@@ -410,7 +410,7 @@
/datum/chemical_reaction/drink/squirt_cider
results = list(/datum/reagent/consumable/ethanol/squirt_cider = 4)
- required_reagents = list(/datum/reagent/water = 2, /datum/reagent/consumable/tomatojuice = 2, /datum/reagent/consumable/nutriment = 1, /datum/reagent/consumable/salt = 1)
+ required_reagents = list(/datum/reagent/water/salt = 2, /datum/reagent/consumable/tomatojuice = 2, /datum/reagent/consumable/nutriment = 1)
mix_message = "The mix swirls and turns a bright red that reminds you of an apple's skin."
reaction_tags = REACTION_TAG_DRINK | REACTION_TAG_EASY | REACTION_TAG_OTHER
diff --git a/code/modules/food_and_drinks/recipes/food_mixtures.dm b/code/modules/food_and_drinks/recipes/food_mixtures.dm
index cf1a1edfa63632..24cb557b239191 100644
--- a/code/modules/food_and_drinks/recipes/food_mixtures.dm
+++ b/code/modules/food_and_drinks/recipes/food_mixtures.dm
@@ -97,6 +97,10 @@
required_reagents = list(/datum/reagent/consumable/corn_starch = 1, /datum/reagent/toxin/acid = 1)
required_temp = 374
+/datum/chemical_reaction/food/rice_flour
+ results = list(/datum/reagent/consumable/rice_flour = 10)
+ required_reagents = list(/datum/reagent/consumable/flour = 5,/datum/reagent/consumable/rice = 5)
+
/datum/chemical_reaction/food/caramel
results = list(/datum/reagent/consumable/caramel = 1)
required_reagents = list(/datum/reagent/consumable/sugar = 1)
@@ -154,6 +158,12 @@
reaction_flags = REACTION_INSTANT
resulting_food_path = /obj/item/food/dough
+/datum/chemical_reaction/food/rice_dough
+ required_reagents = list(/datum/reagent/consumable/rice_flour = 20,/datum/reagent/water = 10)
+ mix_message = "The ingredients form a rice dough."
+ reaction_flags = REACTION_INSTANT
+ resulting_food_path = /obj/item/food/rice_dough
+
/datum/chemical_reaction/food/cakebatter
required_reagents = list(/datum/reagent/consumable/eggyolk = 6, /datum/reagent/consumable/eggwhite = 12, /datum/reagent/consumable/flour = 15, /datum/reagent/consumable/sugar = 5)
mix_message = "The ingredients form a cake batter."
@@ -247,7 +257,8 @@
reaction_flags = REACTION_INSTANT
/datum/chemical_reaction/food/olive_oil_upconvert
- required_reagents = list(/datum/reagent/consumable/nutriment/fat/oil/olive = 1, /datum/reagent/consumable/nutriment/fat/oil = 2)
+ required_catalysts = list(/datum/reagent/consumable/nutriment/fat/oil/olive = 1)
+ required_reagents = list( /datum/reagent/consumable/nutriment/fat/oil = 2)
results = list(/datum/reagent/consumable/nutriment/fat/oil/olive = 2)
mix_message = "The cooking oil dilutes the quality oil- how delightfully devilish..."
reaction_flags = REACTION_INSTANT
diff --git a/code/modules/food_and_drinks/recipes/soup_mixtures.dm b/code/modules/food_and_drinks/recipes/soup_mixtures.dm
index 6e78c0f5f2f563..ad2caa84ca6252 100644
--- a/code/modules/food_and_drinks/recipes/soup_mixtures.dm
+++ b/code/modules/food_and_drinks/recipes/soup_mixtures.dm
@@ -85,6 +85,34 @@
if(!length(required_ingredients))
return
+ // If a food item is supposed to be made, remove relevant ingredients from the pot, then make the item
+ if(!isnull(resulting_food_path))
+ var/list/tracked_ingredients
+ LAZYINITLIST(tracked_ingredients)
+ var/ingredient_max_multiplier = INFINITY
+ var/obj/item/reagent_containers/cup/soup_pot/pot = holder.my_atom
+
+ // Tracked ingredients are indexed by type and point to a list containing the actual items
+ for(var/obj/item/ingredient as anything in pot.added_ingredients)
+ if(is_type_in_list(ingredient, required_ingredients))
+ LAZYADD(tracked_ingredients[ingredient.type],ingredient)
+ // Find the max number of ingredients that may be used for making the food item
+ for(var/list/ingredient_type as anything in tracked_ingredients)
+ ingredient_max_multiplier = min(ingredient_max_multiplier,LAZYLEN(tracked_ingredients[ingredient_type]))
+ // Create the food items, removing the relavent ingredients at the same time
+ for(var/i in 1 to (min(created_volume,ingredient_max_multiplier)))
+ for(var/list/ingredient_type as anything in tracked_ingredients)
+ var/ingredient = tracked_ingredients[ingredient_type][i]
+ LAZYREMOVE(pot.added_ingredients,ingredient)
+ qdel(ingredient)
+ var/obj/item/created = new resulting_food_path(get_turf(pot))
+ created.pixel_y += 8
+ // Re-add required reagents that were not used in this step
+ if(created_volume > ingredient_max_multiplier)
+ for(var/reagent_path as anything in required_reagents)
+ holder.add_reagent(reagent_path,(required_reagents[reagent_path])*(created_volume-ingredient_max_multiplier))
+
+
// This only happens if we're being instant reacted so let's just skip to what we really want
if(isnull(reaction))
testing("Soup reaction of type [type] instant reacted, cleaning up.")
@@ -113,7 +141,6 @@
/datum/chemical_reaction/food/soup/reaction_step(datum/reagents/holder, datum/equilibrium/reaction, delta_t, delta_ph, step_reaction_vol)
if(!length(required_ingredients))
return
-
testing("Soup reaction step progressing with an increment volume of [step_reaction_vol] and delta_t of [delta_t].")
var/obj/item/reagent_containers/cup/soup_pot/pot = holder.my_atom
var/list/cached_ingredients = reaction.data["ingredients"]
@@ -171,7 +198,7 @@
/**
* Cleans up the ingredients and adds whatever leftover reagents to the mixture
*
- * * holder: The sou ppot
+ * * holder: The soup pot
* * reaction: The reaction being cleaned up, note this CAN be null if being instant reacted
* * react_vol: How much soup was produced
*/
@@ -180,29 +207,26 @@
reaction?.data["ingredients"] = null
- for(var/obj/item/ingredient as anything in pot.added_ingredients)
- // Let's not mess with indestructible items.
- // Chef doesn't need more ways to delete things with cooking.
- if(ingredient.resistance_flags & INDESTRUCTIBLE)
- continue
+ // If soup is made, remove ingredients as their reagents were added to the soup
+ if(react_vol)
+ for(var/obj/item/ingredient as anything in pot.added_ingredients)
+ // Let's not mess with indestructible items.
+ // Chef doesn't need more ways to delete things with cooking.
+ if(ingredient.resistance_flags & INDESTRUCTIBLE)
+ continue
- // Things that had reagents or ingredients in the soup will get deleted
- else if(!isnull(ingredient.reagents) || is_type_in_list(ingredient, required_ingredients))
+ // Everything else will just get fried
+ if(isnull(ingredient.reagents) && !is_type_in_list(ingredient, required_ingredients))
+ ingredient.AddElement(/datum/element/fried_item, 30)
+ continue
+
+ // Things that had reagents or ingredients in the soup will get deleted
LAZYREMOVE(pot.added_ingredients, ingredient)
// Send everything left behind
transfer_ingredient_reagents(ingredient, holder)
// Delete, it's done
qdel(ingredient)
- // Everything else will just get fried
- else
- ingredient.AddElement(/datum/element/fried_item, 30)
-
- // Spawning physical food results
- if(resulting_food_path)
- var/obj/item/created = new resulting_food_path(get_turf(pot))
- created.pixel_y += 8
-
// Anything left in the ingredient list will get dumped out
pot.dump_ingredients(get_turf(pot), y_offset = 8)
// Blackbox log the chemical reaction used, to account for soup reaction that don't produce typical results
@@ -560,8 +584,8 @@
/datum/chemical_reaction/food/soup/chili_sin_carne
required_reagents = list(
- /datum/reagent/water = 40,
- /datum/reagent/consumable/salt = 5,
+ /datum/reagent/water = 30,
+ /datum/reagent/water/salt = 10,
)
required_ingredients = list(
/obj/item/food/grown/chili = 1,
@@ -1097,8 +1121,8 @@
/datum/chemical_reaction/food/soup/electron
required_reagents = list(
- /datum/reagent/water = 45,
- /datum/reagent/consumable/salt = 5,
+ /datum/reagent/water = 40,
+ /datum/reagent/water/salt = 10,
)
required_ingredients = list(
/obj/item/food/grown/mushroom/jupitercup = 1,
@@ -1661,8 +1685,8 @@
/datum/chemical_reaction/food/soup/rice_porridge
required_reagents = list(
- /datum/reagent/water = 30,
- /datum/reagent/consumable/salt = 5,
+ /datum/reagent/water = 20,
+ /datum/reagent/water/salt = 10,
)
required_ingredients = list(
/obj/item/food/boiledrice = 1,
@@ -1803,7 +1827,7 @@
/obj/item/food/spaghetti/rawnoodles = 1
)
required_catalysts = list(
- /datum/reagent/water = 30
+ /datum/reagent/water/salt = 10,
)
resulting_food_path = /obj/item/food/spaghetti/boilednoodles
ingredient_reagent_multiplier = 0
diff --git a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
index ea2c1270303b97..47bd455cd858a1 100644
--- a/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
+++ b/code/modules/food_and_drinks/recipes/tablecraft/recipes_misc.dm
@@ -422,8 +422,7 @@
reqs = list(
/obj/item/reagent_containers/cup/beaker/large = 1,
/obj/item/food/grown/cucumber = 10,
- /datum/reagent/water = 10,
- /datum/reagent/consumable/salt = 10,
+ /datum/reagent/water/salt = 20,
)
result = /obj/item/storage/fancy/pickles_jar
category = CAT_MISCFOOD
diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm
index 8a8946fa836900..c2b1165f74c747 100644
--- a/code/modules/holiday/holidays.dm
+++ b/code/modules/holiday/holidays.dm
@@ -18,8 +18,8 @@
var/year_offset = 0
///Timezones this holiday is celebrated in (defaults to three timezones spanning a 50 hour window covering all timezones)
var/list/timezones = list(TIMEZONE_LINT, TIMEZONE_UTC, TIMEZONE_ANYWHERE_ON_EARTH)
- ///If this is defined, drones without a default hat will spawn with this one during the holiday; check drones_as_items.dm to see this used
- var/obj/item/drone_hat
+ ///If this is defined, drones/assistants without a default hat will spawn with this item in their head clothing slot.
+ var/obj/item/holiday_hat
///When this holiday is active, does this prevent mail from arriving to cargo? Try not to use this for longer holidays.
var/mail_holiday = FALSE
var/poster_name = "generic celebration poster"
@@ -109,6 +109,7 @@
name = "Fleet Day"
begin_month = JANUARY
begin_day = 19
+ holiday_hat = /obj/item/clothing/head/mothcap
/datum/holiday/fleet_day/greet()
return "This day commemorates another year of successful survival aboard the Mothic Grand Nomad Fleet. Moths galaxywide are encouraged to eat, drink, and be merry."
@@ -155,7 +156,7 @@
name = "Birthday of Space Station 13"
begin_day = 16
begin_month = FEBRUARY
- drone_hat = /obj/item/clothing/head/costume/festive
+ holiday_hat = /obj/item/clothing/head/costume/festive
poster_name = "station birthday poster"
poster_desc = "A poster celebrating another year of the station's operation. Why anyone would be happy to be here is byond you."
poster_icon = "holiday_cake" // is a lie
@@ -220,7 +221,7 @@
name = "St. Patrick's Day"
begin_day = 17
begin_month = MARCH
- drone_hat = /obj/item/clothing/head/soft/green
+ holiday_hat = /obj/item/clothing/head/soft/green
/datum/holiday/no_this_is_patrick/getStationPrefix()
return pick("Blarney","Green","Leprechaun","Booze")
@@ -235,6 +236,7 @@
begin_month = APRIL
begin_day = 1
end_day = 2
+ holiday_hat = /obj/item/clothing/head/chameleon/broken
/datum/holiday/april_fools/celebrate()
. = ..()
@@ -252,7 +254,7 @@
name = "Cosmonautics Day"
begin_day = 12
begin_month = APRIL
- drone_hat = /obj/item/clothing/head/syndicatefake
+ holiday_hat = /obj/item/clothing/head/syndicatefake
/datum/holiday/spess/greet()
return "On this day over 600 years ago, Comrade Yuri Gagarin first ventured into space!"
@@ -261,6 +263,7 @@
name = "Four-Twenty"
begin_day = 20
begin_month = APRIL
+ holiday_hat = /obj/item/clothing/head/rasta
/datum/holiday/fourtwenty/getStationPrefix()
return pick("Snoop","Blunt","Toke","Dank","Cheech","Chong")
@@ -283,7 +286,7 @@
timezones = list(TIMEZONE_TKT, TIMEZONE_TOT, TIMEZONE_NZST, TIMEZONE_NFT, TIMEZONE_LHST, TIMEZONE_AEST, TIMEZONE_ACST, TIMEZONE_ACWST, TIMEZONE_AWST, TIMEZONE_CXT, TIMEZONE_CCT, TIMEZONE_CKT, TIMEZONE_NUT)
begin_day = 25
begin_month = APRIL
- drone_hat = /obj/item/food/grown/poppy
+ holiday_hat = /obj/item/food/grown/poppy
/datum/holiday/anz/getStationPrefix()
return pick("Australian","New Zealand","Poppy", "Southern Cross")
@@ -294,7 +297,7 @@
name = "Labor Day"
begin_day = 1
begin_month = MAY
- drone_hat = /obj/item/clothing/head/utility/hardhat
+ holiday_hat = /obj/item/clothing/head/utility/hardhat
mail_holiday = TRUE
//Draconic Day is celebrated on May 3rd, the date on which the Draconic language was merged (#26780)
@@ -313,7 +316,7 @@
name = "Firefighter's Day"
begin_day = 4
begin_month = MAY
- drone_hat = /obj/item/clothing/head/utility/hardhat/red
+ holiday_hat = /obj/item/clothing/head/utility/hardhat/red
/datum/holiday/firefighter/getStationPrefix()
return pick("Burning","Blazing","Plasma","Fire")
@@ -322,7 +325,6 @@
name = "Bee Day"
begin_day = 20
begin_month = MAY
- drone_hat = /obj/item/clothing/mask/animal/small/bee
/datum/holiday/bee/getStationPrefix()
return pick("Bee","Honey","Hive","Africanized","Mead","Buzz")
@@ -355,6 +357,7 @@
name = "Summer Solstice"
begin_day = 21
begin_month = JUNE
+ holiday_hat = /obj/item/clothing/head/costume/garland
/datum/holiday/pride_week
name = PRIDE_WEEK
@@ -377,13 +380,13 @@
name = "Doctor's Day"
begin_day = 1
begin_month = JULY
- drone_hat = /obj/item/clothing/head/costume/nursehat
+ holiday_hat = /obj/item/clothing/head/costume/nursehat
/datum/holiday/ufo
name = "UFO Day"
begin_day = 2
begin_month = JULY
- drone_hat = /obj/item/clothing/mask/facehugger/dead
+ holiday_hat = /obj/item/clothing/head/collectable/xenom
/datum/holiday/ufo/getStationPrefix() //Is such a thing even possible?
return pick("Ayy","Truth","Tsoukalos","Mulder","Scully") //Yes it is!
@@ -394,6 +397,7 @@
begin_day = 4
begin_month = JULY
mail_holiday = TRUE
+ holiday_hat = /obj/item/clothing/head/cowboy/brown
/datum/holiday/usa/getStationPrefix()
return pick("Independent","American","Burger","Bald Eagle","Star-Spangled", "Fireworks")
@@ -408,7 +412,7 @@
timezones = list(TIMEZONE_CEST)
begin_day = 14
begin_month = JULY
- drone_hat = /obj/item/clothing/head/beret
+ holiday_hat = /obj/item/clothing/head/beret
mail_holiday = TRUE
/datum/holiday/france/getStationPrefix()
@@ -430,7 +434,7 @@
name = "Wizard's Day"
begin_month = JULY
begin_day = 27
- drone_hat = /obj/item/clothing/head/wizard
+ holiday_hat = /obj/item/clothing/head/wizard
/datum/holiday/wizards_day/getStationPrefix()
return pick("Dungeon", "Elf", "Magic", "D20", "Edition")
@@ -470,6 +474,7 @@
name = "Tiziran Unification Day"
begin_month = SEPTEMBER
begin_day = 1
+ holiday_hat = /obj/item/clothing/head/costume/lizard
/datum/holiday/tiziran_unification/greet()
return "On this day over 400 years ago, Lizardkind first united under a single banner, ready to face the stars as one unified people."
@@ -493,7 +498,7 @@
name = "Talk-Like-a-Pirate Day"
begin_day = 19
begin_month = SEPTEMBER
- drone_hat = /obj/item/clothing/head/costume/pirate
+ holiday_hat = /obj/item/clothing/head/costume/pirate
/datum/holiday/pirate/greet()
return "Ye be talkin' like a pirate today or else ye'r walkin' tha plank, matey!"
@@ -523,13 +528,13 @@
name = "Smiling Day"
begin_day = 7
begin_month = OCTOBER
- drone_hat = /obj/item/clothing/head/costume/papersack/smiley
+ holiday_hat = /obj/item/clothing/head/costume/papersack/smiley
/datum/holiday/boss
name = "Boss' Day"
begin_day = 16
begin_month = OCTOBER
- drone_hat = /obj/item/clothing/head/hats/tophat
+ holiday_hat = /obj/item/clothing/head/hats/tophat
/datum/holiday/un_day
name = "Anniversary of the Foundation of the United Nations"
@@ -578,7 +583,7 @@
name = "Remembrance Day"
begin_month = NOVEMBER
begin_day = 11
- drone_hat = /obj/item/food/grown/poppy
+ holiday_hat = /obj/item/food/grown/poppy
/datum/holiday/remembrance_day/getStationPrefix()
return pick("Peace", "Armistice", "Poppy")
@@ -600,7 +605,7 @@
name = "Flowers Day"
begin_day = 19
begin_month = NOVEMBER
- drone_hat = /obj/item/food/grown/moonflower
+ holiday_hat = /obj/item/food/grown/moonflower
/datum/holiday/hello
name = "Saying-'Hello' Day"
@@ -629,7 +634,7 @@
begin_day = 1
begin_month = DECEMBER
end_day = 31
- drone_hat = /obj/item/clothing/head/costume/santa
+ holiday_hat = /obj/item/clothing/head/costume/santa
/datum/holiday/festive_season/greet()
return "Have a nice festive season!"
@@ -643,20 +648,18 @@
name = MONKEYDAY
begin_day = 14
begin_month = DECEMBER
- drone_hat = /obj/item/clothing/mask/gas/monkeymask
/datum/holiday/doomsday
name = "Mayan Doomsday Anniversary"
begin_day = 21
begin_month = DECEMBER
- drone_hat = /obj/item/clothing/mask/animal/small/tribal
/datum/holiday/xmas
name = CHRISTMAS
begin_day = 23
begin_month = DECEMBER
end_day = 27
- drone_hat = /obj/item/clothing/head/costume/santa
+ holiday_hat = /obj/item/clothing/head/costume/santa
mail_holiday = TRUE
/datum/holiday/xmas/getStationPrefix()
@@ -693,7 +696,7 @@
begin_month = DECEMBER
end_day = 2
end_month = JANUARY
- drone_hat = /obj/item/clothing/head/costume/festive
+ holiday_hat = /obj/item/clothing/head/costume/festive
mail_holiday = TRUE
/datum/holiday/new_year/getStationPrefix()
@@ -808,7 +811,7 @@
/datum/holiday/easter
name = EASTER
- drone_hat = /obj/item/clothing/head/costume/rabbitears
+ holiday_hat = /obj/item/clothing/head/costume/rabbitears
var/const/days_early = 1 //to make editing the holiday easier
var/const/days_extra = 1
diff --git a/code/modules/holiday/nth_week.dm b/code/modules/holiday/nth_week.dm
index 55cfec74be6e2f..ef4815de066467 100644
--- a/code/modules/holiday/nth_week.dm
+++ b/code/modules/holiday/nth_week.dm
@@ -35,7 +35,7 @@
begin_week = 4
begin_month = NOVEMBER
begin_weekday = THURSDAY
- drone_hat = /obj/item/clothing/head/hats/tophat //This is the closest we can get to a pilgrim's hat
+ holiday_hat = /obj/item/clothing/head/hats/tophat //This is the closest we can get to a pilgrim's hat
/datum/holiday/nth_week/thanksgiving/canada
name = "Thanksgiving in Canada"
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index 86a8dbc409529e..098c6e81ce6a98 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -38,11 +38,14 @@
var/wine_power = 10
/// Color of the grown object, for use in coloring greyscale splats.
var/filling_color
- /// If the grown food has an alternaitve icon state to use in places.
+ /// If the grown food has an alternative icon state to use in places.
var/alt_icon
/// Should we pixel offset ourselves at init? for mapping
var/offset_at_init = TRUE
+/obj/item/food/grown/New(loc, obj/item/seeds/new_seed)
+ return ..()
+
/obj/item/food/grown/Initialize(mapload, obj/item/seeds/new_seed)
if(!tastes)
tastes = list("[name]" = 1) //This happens first else the component already inits
@@ -75,6 +78,7 @@
. = ..() //Only call it here because we want all the genes and shit to be applied before we add edibility. God this code is a mess.
+ reagents.clear_reagents()
seed.prepare_result(src)
transform *= TRANSFORM_USING_VARIABLE(seed.potency, 100) + 0.5 //Makes the resulting produce's sprite larger or smaller based on potency!
@@ -125,7 +129,7 @@
else
var/data = list()
data["names"] = list("[initial(name)]" = 1)
- data["color"] = filling_color
+ data["color"] = filling_color || reagent.color // filling_color is not guaranteed to be set for every plant. try to use it if we have it, otherwise use the reagent's color var
data["boozepwr"] = round(wine_power * reagent_purity * 2) // default boozepwr at 50% purity
data["quality"] = quality
if(wine_flavor)
diff --git a/code/modules/hydroponics/grown/banana.dm b/code/modules/hydroponics/grown/banana.dm
index fca26ebe97861b..1b9b16d485cb5e 100644
--- a/code/modules/hydroponics/grown/banana.dm
+++ b/code/modules/hydroponics/grown/banana.dm
@@ -169,6 +169,7 @@
/obj/item/food/grown/banana/bunch/Initialize(mapload, obj/item/seeds/new_seed)
. = ..()
+ reagents.clear_reagents()
reagents.add_reagent(/datum/reagent/consumable/monkey_energy, 10)
reagents.add_reagent(/datum/reagent/consumable/banana, 10)
diff --git a/code/modules/hydroponics/growninedible.dm b/code/modules/hydroponics/growninedible.dm
index 26fb90236a30d2..2b2556790e2b2c 100644
--- a/code/modules/hydroponics/growninedible.dm
+++ b/code/modules/hydroponics/growninedible.dm
@@ -10,6 +10,12 @@
var/obj/item/seeds/seed = null // type path, gets converted to item on New(). It's safe to assume it's always a seed item.
/// Should we pixel offset ourselves at init? for mapping
var/offset_at_init = TRUE
+ /// The reagent this plant distill to. If NULL, it uses a generic fruit_wine reagent and adjusts its variables.
+ var/distill_reagent
+
+// This may look like it's doing nothing but it's necessary, we do this to have kwargs work in New (for passing into Initialize)
+/obj/item/grown/New(loc, obj/item/seeds/new_seed)
+ return ..()
/obj/item/grown/Initialize(mapload, obj/item/seeds/new_seed)
. = ..()
diff --git a/code/modules/hydroponics/seeds.dm b/code/modules/hydroponics/seeds.dm
index 52be3e98f0c66b..36653ebafb9ffe 100644
--- a/code/modules/hydroponics/seeds.dm
+++ b/code/modules/hydroponics/seeds.dm
@@ -223,7 +223,7 @@
for(var/datum/plant_gene/trait/trait in parent.myseed.genes)
if((trait.mutability_flags & PLANT_GENE_MUTATABLE) && trait.can_add(mutated_seed))
mutated_seed.genes += trait.Copy()
- t_prod = new t_prod(output_loc, mutated_seed)
+ t_prod = new t_prod(output_loc, new_seed = mutated_seed)
t_prod.transform = initial(t_prod.transform)
t_prod.transform *= TRANSFORM_USING_VARIABLE(t_prod.seed.potency, 100) + 0.5
ADD_TRAIT(t_prod, TRAIT_PLANT_WILDMUTATE, INNATE_TRAIT)
@@ -232,7 +232,7 @@
t_prod.seed.set_instability(round(instability * 0.5))
continue
else
- t_prod = new product(output_loc, src)
+ t_prod = new product(output_loc, new_seed = src)
if(parent.myseed.plantname != initial(parent.myseed.plantname))
t_prod.name = lowertext(parent.myseed.plantname)
if(productdesc)
diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm
index 2a5a14b066d2bd..5b64770175cbef 100644
--- a/code/modules/jobs/access.dm
+++ b/code/modules/jobs/access.dm
@@ -5,6 +5,8 @@
return TRUE
if(result_bitflags & COMPONENT_OBJ_DISALLOW) // override all other checks
return FALSE
+ if(HAS_TRAIT(accessor, TRAIT_ALWAYS_NO_ACCESS))
+ return FALSE
//check if it doesn't require any access at all
if(check_access(null))
return TRUE
diff --git a/code/modules/jobs/job_types/assistant.dm b/code/modules/jobs/job_types/assistant.dm
index a5a68b7152aa4b..89e7d04742c7cd 100644
--- a/code/modules/jobs/job_types/assistant.dm
+++ b/code/modules/jobs/job_types/assistant.dm
@@ -48,6 +48,12 @@ Assistant
/datum/outfit/job/assistant/pre_equip(mob/living/carbon/human/target)
..()
+ for(var/holidayname in GLOB.holidays)
+ var/datum/holiday/holiday_today = GLOB.holidays[holidayname]
+ var/obj/item/special_hat = holiday_today.holiday_hat
+ if(prob(HOLIDAY_HAT_CHANCE) && !isnull(special_hat) && isnull(head))
+ head = special_hat
+
give_jumpsuit(target)
/datum/outfit/job/assistant/proc/give_jumpsuit(mob/living/carbon/human/target)
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm
index 15dd39861a4118..3e752910a29d76 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_vorpal_scythe.dm
@@ -21,7 +21,7 @@ If the scythe isn't empowered when you sheath it, you take a heap of damage and
return FALSE
var/obj/item/bodypart/part = hand
- if(isnull(part) || scythe.empowerment > SCYTHE_SATED)
+ if(isnull(part) || scythe.empowerment >= SCYTHE_SATED)
return ..()
to_chat(owner, span_userdanger("[scythe] tears into you for your unworthy display of arrogance!"))
diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm
index a0e5b26b5d94be..1916a65f7b17a2 100644
--- a/code/modules/mafia/controller.dm
+++ b/code/modules/mafia/controller.dm
@@ -369,7 +369,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment())
* * role: mafia_role datum to reward.
*/
/datum/mafia_controller/proc/award_role(award, datum/mafia_role/rewarded)
- var/client/role_client = GLOB.directory[rewarded.body.client]
+ var/client/role_client = rewarded.body.client
role_client?.give_award(award, rewarded.body)
/**
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm
index 6ab0ed2f3876ad..424e1db299ec57 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/hilbertshotel.dm
@@ -112,9 +112,10 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
if(!storageTurf) //Blame subsystems for not allowing this to be in Initialize
if(!GLOB.hhStorageTurf)
var/datum/map_template/hilbertshotelstorage/storageTemp = new()
- var/datum/turf_reservation/storageReservation = SSmapping.RequestBlockReservation(3, 3)
- storageTemp.load(locate(storageReservation.bottom_left_coords[1], storageReservation.bottom_left_coords[2], storageReservation.bottom_left_coords[3]))
- GLOB.hhStorageTurf = locate(storageReservation.bottom_left_coords[1]+1, storageReservation.bottom_left_coords[2]+1, storageReservation.bottom_left_coords[3])
+ var/datum/turf_reservation/storageReservation = SSmapping.request_turf_block_reservation(1, 1, 1)
+ var/turf/storage_turf = storageReservation.bottom_left_turfs[1]
+ storageTemp.load(storage_turf)
+ GLOB.hhStorageTurf = storage_turf
else
storageTurf = GLOB.hhStorageTurf
if(tryActiveRoom(chosenRoomNumber, target))
@@ -127,20 +128,30 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
if(activeRooms["[roomNumber]"])
var/datum/turf_reservation/roomReservation = activeRooms["[roomNumber]"]
do_sparks(3, FALSE, get_turf(user))
- user.forceMove(locate(roomReservation.bottom_left_coords[1] + hotelRoomTemp.landingZoneRelativeX, roomReservation.bottom_left_coords[2] + hotelRoomTemp.landingZoneRelativeY, roomReservation.bottom_left_coords[3]))
+ var/turf/room_bottom_left = roomReservation.bottom_left_turfs[1]
+ user.forceMove(locate(
+ room_bottom_left.x + hotelRoomTemp.landingZoneRelativeX,
+ room_bottom_left.y + hotelRoomTemp.landingZoneRelativeY,
+ room_bottom_left.z,
+ ))
return TRUE
return FALSE
/obj/item/hilbertshotel/proc/tryStoredRoom(roomNumber, mob/user)
if(storedRooms["[roomNumber]"])
- var/datum/turf_reservation/roomReservation = SSmapping.RequestBlockReservation(hotelRoomTemp.width, hotelRoomTemp.height)
- hotelRoomTempEmpty.load(locate(roomReservation.bottom_left_coords[1], roomReservation.bottom_left_coords[2], roomReservation.bottom_left_coords[3]))
+ var/datum/turf_reservation/roomReservation = SSmapping.request_turf_block_reservation(hotelRoomTemp.width, hotelRoomTemp.height, 1)
+ var/turf/room_turf = roomReservation.bottom_left_turfs[1]
+ hotelRoomTempEmpty.load(room_turf)
var/turfNumber = 1
for(var/x in 0 to hotelRoomTemp.width-1)
for(var/y in 0 to hotelRoomTemp.height-1)
for(var/atom/movable/A in storedRooms["[roomNumber]"][turfNumber])
if(istype(A.loc, /obj/item/abstracthotelstorage))//Don't want to recall something thats been moved
- A.forceMove(locate(roomReservation.bottom_left_coords[1] + x, roomReservation.bottom_left_coords[2] + y, roomReservation.bottom_left_coords[3]))
+ A.forceMove(locate(
+ room_turf.x + x,
+ room_turf.y + y,
+ room_turf.z,
+ ))
turfNumber++
for(var/obj/item/abstracthotelstorage/S in storageTurf)
if((S.roomNumber == roomNumber) && (S.parentSphere == src))
@@ -149,29 +160,39 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
activeRooms["[roomNumber]"] = roomReservation
linkTurfs(roomReservation, roomNumber)
do_sparks(3, FALSE, get_turf(user))
- user.forceMove(locate(roomReservation.bottom_left_coords[1] + hotelRoomTemp.landingZoneRelativeX, roomReservation.bottom_left_coords[2] + hotelRoomTemp.landingZoneRelativeY, roomReservation.bottom_left_coords[3]))
+ user.forceMove(locate(
+ room_turf.x + hotelRoomTemp.landingZoneRelativeX,
+ room_turf.y + hotelRoomTemp.landingZoneRelativeY,
+ room_turf.z,
+ ))
return TRUE
return FALSE
-/obj/item/hilbertshotel/proc/sendToNewRoom(roomNumber, mob/user, chosen_room) //SKYRAT EDIT ADDITION - GHOST HOTEL UPDATE. Was sendToNewRoom(chosenRoomNumber, target)
- var/datum/turf_reservation/roomReservation = SSmapping.RequestBlockReservation(hotelRoomTemp.width, hotelRoomTemp.height)
+/obj/item/hilbertshotel/proc/sendToNewRoom(roomNumber, mob/user, chosen_room) //SKYRAT EDIT ADDITION - GHOST HOTEL UPDATE. Was sendToNewRoom(roomNumber, mob/user)
+ var/datum/turf_reservation/roomReservation = SSmapping.request_turf_block_reservation(hotelRoomTemp.width, hotelRoomTemp.height, 1)
+ var/turf/bottom_left = roomReservation.bottom_left_turfs[1]
+ var/datum/map_template/load_from = hotelRoomTemp
+
if(ruinSpawned && roomNumber == GLOB.hhMysteryRoomNumber)
- hotelRoomTempLore.load(locate(roomReservation.bottom_left_coords[1], roomReservation.bottom_left_coords[2], roomReservation.bottom_left_coords[3]))
- else
- //SKYRAT EDIT ADDITION - GHOST HOTEL UPDATE
- switch(chosen_room)
- if("Apartment")
- ghost_cafe_rooms_apartment.load(locate(roomReservation.bottom_left_coords[1], roomReservation.bottom_left_coords[2], roomReservation.bottom_left_coords[3]))
- else
- //SKYRAT EDIT END
- hotelRoomTemp.load(locate(roomReservation.bottom_left_coords[1], roomReservation.bottom_left_coords[2], roomReservation.bottom_left_coords[3]))
+ load_from = hotelRoomTempLore
+ //SKYRAT EDIT ADDITION START - GHOST HOTEL UPDATE
+ else if(chosen_room == "Apartment")
+ load_from = ghost_cafe_rooms_apartment
+ //SKYRAT EDIT ADDITION END
+
+ load_from.load(bottom_left)
activeRooms["[roomNumber]"] = roomReservation
linkTurfs(roomReservation, roomNumber)
do_sparks(3, FALSE, get_turf(user))
- user.forceMove(locate(roomReservation.bottom_left_coords[1] + hotelRoomTemp.landingZoneRelativeX, roomReservation.bottom_left_coords[2] + hotelRoomTemp.landingZoneRelativeY, roomReservation.bottom_left_coords[3]))
+ user.forceMove(locate(
+ bottom_left.x + hotelRoomTemp.landingZoneRelativeX,
+ bottom_left.y + hotelRoomTemp.landingZoneRelativeY,
+ bottom_left.z,
+ ))
/obj/item/hilbertshotel/proc/linkTurfs(datum/turf_reservation/currentReservation, currentRoomnumber)
- var/area/misc/hilbertshotel/currentArea = get_area(locate(currentReservation.bottom_left_coords[1], currentReservation.bottom_left_coords[2], currentReservation.bottom_left_coords[3]))
+ var/turf/room_bottom_left = currentReservation.bottom_left_turfs[1]
+ var/area/misc/hilbertshotel/currentArea = get_area(room_bottom_left)
currentArea.name = "Hilbert's Hotel Room [currentRoomnumber]"
currentArea.parentSphere = src
currentArea.storageTurf = storageTurf
@@ -187,9 +208,10 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
if(activeRooms.len)
for(var/x in activeRooms)
var/datum/turf_reservation/room = activeRooms[x]
+ var/turf/room_bottom_left = room.bottom_left_turfs[1]
for(var/i in 0 to hotelRoomTemp.width-1)
for(var/j in 0 to hotelRoomTemp.height-1)
- for(var/atom/movable/A in locate(room.bottom_left_coords[1] + i, room.bottom_left_coords[2] + j, room.bottom_left_coords[3]))
+ for(var/atom/movable/A in locate(room_bottom_left.x + i, room_bottom_left.y + j, room_bottom_left.z))
if(ismob(A))
var/mob/M = A
if(M.mind)
@@ -271,7 +293,7 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
icon_state = "bluespace"
base_icon_state = "bluespace"
baseturfs = /turf/open/space/bluespace
- flags_1 = NOJAUNT
+ turf_flags = NOJAUNT
explosive_resistance = INFINITY
var/obj/item/hilbertshotel/parentSphere
@@ -437,7 +459,11 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
storeRoom()
/area/misc/hilbertshotel/proc/storeRoom()
- var/roomSize = (reservation.top_right_coords[1]-reservation.bottom_left_coords[1]+1)*(reservation.top_right_coords[2]-reservation.bottom_left_coords[2]+1)
+ var/turf/room_bottom_left = reservation.bottom_left_turfs[1]
+ var/turf/room_top_right = reservation.top_right_turfs[1]
+ var/roomSize = \
+ ((room_top_right.x - room_bottom_left.x) + 1) * \
+ ((room_top_right.y - room_bottom_left.y) + 1)
var/storage[roomSize]
var/turfNumber = 1
var/obj/item/abstracthotelstorage/storageObj = new(storageTurf)
@@ -447,7 +473,7 @@ GLOBAL_VAR_INIT(hhMysteryRoomNumber, rand(1, 999999))
for(var/x in 0 to parentSphere.hotelRoomTemp.width-1)
for(var/y in 0 to parentSphere.hotelRoomTemp.height-1)
var/list/turfContents = list()
- for(var/atom/movable/A in locate(reservation.bottom_left_coords[1] + x, reservation.bottom_left_coords[2] + y, reservation.bottom_left_coords[3]))
+ for(var/atom/movable/A in locate(room_bottom_left.x + x, room_bottom_left.y + y, room_bottom_left.z))
if(ismob(A) && !isliving(A))
continue //Don't want to store ghosts
turfContents += A
diff --git a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm
index ca2b6ec39c9f28..fc79c82e780efb 100644
--- a/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm
+++ b/code/modules/mapfluff/ruins/spaceruin_code/meateor.dm
@@ -13,8 +13,12 @@
/obj/effect/mob_spawn/corpse/human/tigercultist/perforated/special(mob/living/carbon/human/spawned_human)
. = ..()
- var/datum/wound/pierce/bleed/critical/exit_hole = new()
- exit_hole.apply_wound(spawned_human.get_bodypart(BODY_ZONE_CHEST))
+
+ var/obj/item/bodypart/chest/their_chest = spawned_human.get_bodypart(BODY_ZONE_CHEST)
+ if (!their_chest)
+ return
+
+ spawned_human.cause_wound_of_type_and_severity(WOUND_PIERCE, their_chest, WOUND_SEVERITY_CRITICAL)
/// A fun drink enjoyed by the tiger cooperative, might corrode your brain if you drink the whole bottle
/obj/item/reagent_containers/cup/glass/bottle/ritual_wine
diff --git a/code/modules/mapping/map_template.dm b/code/modules/mapping/map_template.dm
index 30477657ab5fcb..237ae9f2d52e4e 100644
--- a/code/modules/mapping/map_template.dm
+++ b/code/modules/mapping/map_template.dm
@@ -133,7 +133,15 @@
var/y = round((world.maxy - height) * 0.5) + 1
var/datum/space_level/level = SSmapping.add_new_zlevel(name, secret ? ZTRAITS_AWAY_SECRET : ZTRAITS_AWAY, contain_turfs = FALSE)
- var/datum/parsed_map/parsed = load_map(file(mappath), x, y, level.z_value, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=should_place_on_top, new_z = TRUE)
+ var/datum/parsed_map/parsed = load_map(
+ file(mappath),
+ x,
+ y,
+ level.z_value,
+ no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS),
+ place_on_top = should_place_on_top,
+ new_z = TRUE,
+ )
var/list/bounds = parsed.bounds
if(!bounds)
return FALSE
@@ -177,7 +185,14 @@
UNSETEMPTY(turf_blacklist)
parsed.turf_blacklist = turf_blacklist
- if(!parsed.load(T.x, T.y, T.z, cropMap=TRUE, no_changeturf=(SSatoms.initialized == INITIALIZATION_INSSATOMS), placeOnTop=should_place_on_top))
+ if(!parsed.load(
+ T.x,
+ T.y,
+ T.z,
+ crop_map = TRUE,
+ no_changeturf = (SSatoms.initialized == INITIALIZATION_INSSATOMS),
+ place_on_top = should_place_on_top,
+ ))
return
var/list/bounds = parsed.bounds
diff --git a/code/modules/mapping/mapping_helpers.dm b/code/modules/mapping/mapping_helpers.dm
index d1fc4a79cf7e27..cec7932f4f556c 100644
--- a/code/modules/mapping/mapping_helpers.dm
+++ b/code/modules/mapping/mapping_helpers.dm
@@ -1359,3 +1359,19 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava)
engraved_wall.AddComponent(/datum/component/engraved, engraving["story"], FALSE, engraving["story_value"])
qdel(src)
+
+/// Apply to a wall (or floor, technically) to ensure it is instantly destroyed by any explosion, even if usually invulnerable
+/obj/effect/mapping_helpers/bombable_wall
+ name = "bombable wall helper"
+ icon = 'icons/turf/overlays.dmi'
+ icon_state = "explodable"
+
+/obj/effect/mapping_helpers/bombable_wall/Initialize(mapload)
+ . = ..()
+ if(!mapload)
+ log_mapping("[src] spawned outside of mapload!")
+ return
+
+ var/turf/our_turf = get_turf(src) // In case a locker ate us or something
+ our_turf.AddElement(/datum/element/bombable_turf)
+ return INITIALIZE_HINT_QDEL
diff --git a/code/modules/mapping/reader.dm b/code/modules/mapping/reader.dm
index 96b555457d78df..14a8fdf6b94003 100644
--- a/code/modules/mapping/reader.dm
+++ b/code/modules/mapping/reader.dm
@@ -114,26 +114,72 @@
var/turfsSkipped = 0
#endif
+/datum/parsed_map/proc/copy()
+ // Avoids duped work just in case
+ build_cache()
+ var/datum/parsed_map/newfriend = new()
+ newfriend.original_path = original_path
+ newfriend.map_format = map_format
+ newfriend.key_len = key_len
+ newfriend.line_len = line_len
+ newfriend.grid_models = grid_models.Copy()
+ newfriend.gridSets = gridSets.Copy()
+ newfriend.modelCache = modelCache.Copy()
+ newfriend.parsed_bounds = parsed_bounds.Copy()
+ // Copy parsed bounds to reset to initial values
+ newfriend.bounds = parsed_bounds.Copy()
+ newfriend.turf_blacklist = turf_blacklist?.Copy()
+ return newfriend
+
//text trimming (both directions) helper macro
#define TRIM_TEXT(text) (trim_reduced(text))
-/// Shortcut function to parse a map and apply it to the world.
-///
-/// - `dmm_file`: A .dmm file to load (Required).
-/// - `x_offset`, `y_offset`, `z_offset`: Positions representign where to load the map (Optional).
-/// - `cropMap`: When true, the map will be cropped to fit the existing world dimensions (Optional).
-/// - `measureOnly`: When true, no changes will be made to the world (Optional).
-/// - `no_changeturf`: When true, [/turf/proc/AfterChange] won't be called on loaded turfs
-/// - `x_lower`, `x_upper`, `y_lower`, `y_upper`: Coordinates (relative to the map) to crop to (Optional).
-/// - `placeOnTop`: Whether to use [/turf/proc/PlaceOnTop] rather than [/turf/proc/ChangeTurf] (Optional).
-/proc/load_map(dmm_file as file, x_offset as num, y_offset as num, z_offset as num, cropMap as num, measureOnly as num, no_changeturf as num, x_lower = -INFINITY as num, x_upper = INFINITY as num, y_lower = -INFINITY as num, y_upper = INFINITY as num, placeOnTop = FALSE as num, new_z)
- var/datum/parsed_map/parsed = new(dmm_file, x_lower, x_upper, y_lower, y_upper, measureOnly)
- if(parsed.bounds && !measureOnly)
- parsed.load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z = new_z)
- return parsed
+/**
+ * Helper and recommened way to load a map file
+ * - dmm_file: The path to the map file
+ * - x_offset: The x offset to load the map at
+ * - y_offset: The y offset to load the map at
+ * - z_offset: The z offset to load the map at
+ * - crop_map: If true, the map will be cropped to the world bounds
+ * - measure_only: If true, the map will not be loaded, but the bounds will be calculated
+ * - no_changeturf: If true, the map will not call /turf/AfterChange
+ * - x_lower: The minimum x coordinate to load
+ * - x_upper: The maximum x coordinate to load
+ * - y_lower: The minimum y coordinate to load
+ * - y_upper: The maximum y coordinate to load
+ * - z_lower: The minimum z coordinate to load
+ * - z_upper: The maximum z coordinate to load
+ * - place_on_top: Whether to use /turf/proc/PlaceOnTop rather than /turf/proc/ChangeTurf
+ * - new_z: If true, a new z level will be created for the map
+ */
+/proc/load_map(
+ dmm_file,
+ x_offset = 0,
+ y_offset = 0,
+ z_offset = 0,
+ crop_map = FALSE,
+ measure_only = FALSE,
+ no_changeturf = FALSE,
+ x_lower = -INFINITY,
+ x_upper = INFINITY,
+ y_lower = -INFINITY,
+ y_upper = INFINITY,
+ z_lower = -INFINITY,
+ z_upper = INFINITY,
+ place_on_top = FALSE,
+ new_z = FALSE,
+)
+ if(!(dmm_file in GLOB.cached_maps))
+ GLOB.cached_maps[dmm_file] = new /datum/parsed_map(dmm_file)
+
+ var/datum/parsed_map/parsed_map = GLOB.cached_maps[dmm_file]
+ parsed_map = parsed_map.copy()
+ if(!measure_only && !isnull(parsed_map.bounds))
+ parsed_map.load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
+ return parsed_map
/// Parse a map, possibly cropping it.
-/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, measureOnly=FALSE)
+/datum/parsed_map/New(tfile, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper=INFINITY, z_lower = -INFINITY, z_upper=INFINITY, measureOnly=FALSE)
// This proc sleeps for like 6 seconds. why?
// Is it file accesses? if so, can those be done ahead of time, async to save on time here? I wonder.
// Love ya :)
@@ -184,20 +230,26 @@
CRASH("Coords before model definition in DMM")
var/curr_x = text2num(regexOutput[3])
-
if(curr_x < x_lower || curr_x > x_upper)
continue
+ var/curr_y = text2num(regexOutput[4])
+ if(curr_y < y_lower || curr_y > y_upper)
+ continue
+
+ var/curr_z = text2num(regexOutput[5])
+ if(curr_z < z_lower || curr_z > z_upper)
+ continue
+
var/datum/grid_set/gridSet = new
gridSet.xcrd = curr_x
- //position of the currently processed square
- gridSet.ycrd = text2num(regexOutput[4])
- gridSet.zcrd = text2num(regexOutput[5])
+ gridSet.ycrd = curr_y
+ gridSet.zcrd = curr_z
bounds[MAP_MINX] = min(bounds[MAP_MINX], curr_x)
- bounds[MAP_MINZ] = min(bounds[MAP_MINZ], gridSet.zcrd)
- bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], gridSet.zcrd)
+ bounds[MAP_MINZ] = min(bounds[MAP_MINZ], curr_y)
+ bounds[MAP_MAXZ] = max(bounds[MAP_MAXZ], curr_z)
var/list/gridLines = splittext(regexOutput[6], "\n")
gridSet.gridLines = gridLines
@@ -238,16 +290,29 @@
bounds[MAP_MAXX] = clamp(bounds[MAP_MAXX], x_lower, x_upper)
bounds[MAP_MINY] = clamp(bounds[MAP_MINY], y_lower, y_upper)
bounds[MAP_MAXY] = clamp(bounds[MAP_MAXY], y_lower, y_upper)
+ bounds[MAP_MINZ] = clamp(bounds[MAP_MINZ], z_lower, z_upper)
+ bounds[MAP_MAXZ] = clamp(bounds[MAP_MAXZ], z_lower, z_upper)
parsed_bounds = src.bounds
src.key_len = key_len
src.line_len = line_len
-/// Load the parsed map into the world. See [/proc/load_map] for arguments.
-/datum/parsed_map/proc/load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, whitelist = FALSE, new_z)
+/// Iterates over all grid sets and returns ones with z values within the given bounds. Inclusive
+/datum/parsed_map/proc/filter_grid_sets_based_on_z_bounds(lower_z, upper_z)
+ var/list/filtered_sets = list()
+ for(var/datum/grid_set/grid_set as anything in gridSets)
+ if(grid_set.zcrd < lower_z)
+ continue
+ if(grid_set.zcrd > upper_z)
+ continue
+ filtered_sets += grid_set
+ return filtered_sets
+
+/// Load the parsed map into the world. You probably want [/proc/load_map]. Keep the signature the same.
+/datum/parsed_map/proc/load(x_offset = 0, y_offset = 0, z_offset = 0, crop_map = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, z_lower = -INFINITY, z_upper = INFINITY, place_on_top = FALSE, new_z = FALSE)
//How I wish for RAII
Master.StartLoadingMap()
- . = _load_impl(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+ . = _load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
Master.StopLoadingMap()
#define MAPLOADING_CHECK_TICK \
@@ -262,7 +327,7 @@
}
// Do not call except via load() above.
-/datum/parsed_map/proc/_load_impl(x_offset = 1, y_offset = 1, z_offset = world.maxz + 1, cropMap = FALSE, no_changeturf = FALSE, x_lower = -INFINITY, x_upper = INFINITY, y_lower = -INFINITY, y_upper = INFINITY, placeOnTop = FALSE, new_z = FALSE)
+/datum/parsed_map/proc/_load_impl(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
PRIVATE_PROC(TRUE)
// Tell ss atoms that we're doing maploading
// We'll have to account for this in the following tick_checks so it doesn't overflow
@@ -275,9 +340,9 @@
var/sucessful = FALSE
switch(map_format)
if(MAP_TGM)
- sucessful = _tgm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+ sucessful = _tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
else
- sucessful = _dmm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+ sucessful = _dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
// And we are done lads, call it off
SSatoms.map_loader_stop(REF(src))
@@ -309,7 +374,7 @@
// In the tgm format, each gridset contains 255 lines, each line representing one tile, with 255 total gridsets
// In the dmm format, each gridset contains 255 lines, each line representing one row of tiles, containing 255 * line length characters, with one gridset per z
// You can think of dmm as storing maps in rows, whereas tgm stores them in columns
-/datum/parsed_map/proc/_tgm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+/datum/parsed_map/proc/_tgm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
// setup
var/list/modelCache = build_cache(no_changeturf)
var/space_key = modelCache[SPACE_KEY]
@@ -330,12 +395,12 @@
var/relative_y = first_column.ycrd
var/highest_y = relative_y + y_relative_to_absolute
- if(!cropMap && highest_y > world.maxy)
+ if(!crop_map && highest_y > world.maxy)
if(new_z)
// Need to avoid improperly loaded area/turf_contents
- world.increaseMaxY(highest_y, max_zs_to_load = z_offset - 1)
+ world.increase_max_y(highest_y, map_load_z_cutoff = z_offset - 1)
else
- world.increaseMaxY(highest_y)
+ world.increase_max_y(highest_y)
expanded_y = TRUE
// Skip Y coords that are above the smallest of the three params
@@ -345,7 +410,6 @@
var/y_starting_skip = relative_y - y_skip_above
highest_y -= y_starting_skip
-
// Y is the LOWEST it will ever be here, so we can easily set a threshold for how low to go
var/line_count = length(first_column.gridLines)
var/lowest_y = relative_y - (line_count - 1) // -1 because we decrement at the end of the loop, not the start
@@ -353,7 +417,7 @@
// X setup
var/x_delta_with = x_upper
- if(cropMap)
+ if(crop_map)
// Take our smaller crop threshold yes?
x_delta_with = min(x_delta_with, world.maxx)
@@ -367,33 +431,51 @@
// If our relative x is greater then X upper, well then we've gotta limit our expansion
var/delta = max(final_x - x_delta_with, 0)
final_x -= delta
- if(final_x > world.maxx && !cropMap)
+ if(final_x > world.maxx && !crop_map)
if(new_z)
// Need to avoid improperly loaded area/turf_contents
- world.increaseMaxX(final_x, max_zs_to_load = z_offset - 1)
+ world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1)
else
- world.increaseMaxX(final_x)
+ world.increase_max_x(final_x)
expanded_x = TRUE
var/lowest_x = max(x_lower, 1 - x_relative_to_absolute)
+ // Amount we offset the grid zcrd to get the true zcrd
+ var/grid_z_offset = z_offset - 1
+ var/z_upper_set = z_upper < INFINITY
+ var/z_lower_set = z_lower > -INFINITY
+
// We make the assumption that the last block of turfs will have the highest embedded z in it
- var/highest_z = last_column.zcrd + z_offset - 1 // Lets not just make a new z level each time we increment maxz
+ // true max zcrd
+ var/map_bounds_z_max = last_column.zcrd
+ var/z_upper_parsed = map_bounds_z_max + z_offset - 1
+ if(z_upper_set)
+ z_upper_parsed -= map_bounds_z_max - z_upper
+ if(z_lower_set)
+ var/offset_amount = z_lower - 1
+ z_upper_parsed -= offset_amount
+ grid_z_offset -= offset_amount
+
+ var/list/target_grid_sets = gridSets
+ if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want
+ target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper)
+
var/z_threshold = world.maxz
- if(highest_z > z_threshold && cropMap)
- for(var/i in z_threshold + 1 to highest_z) //create a new z_level if needed
+ if(z_upper_parsed > z_threshold && crop_map)
+ for(var/i in z_threshold + 1 to z_upper_parsed) //create a new z_level if needed
world.incrementMaxZ()
if(!no_changeturf)
WARNING("Z-level expansion occurred without no_changeturf set, this may cause problems when /turf/AfterChange is called")
- for(var/datum/grid_set/gset as anything in gridSets)
+ for(var/datum/grid_set/gset as anything in target_grid_sets)
var/true_xcrd = gset.xcrd + x_relative_to_absolute
// any cutoff of x means we just shouldn't iterate this gridset
if(final_x < true_xcrd || lowest_x > gset.xcrd)
continue
- var/zcrd = gset.zcrd + z_offset - 1
+ var/zcrd = gset.zcrd + grid_z_offset
// If we're using changeturf, we disable it if we load into a z level we JUST created
var/no_afterchange = no_changeturf || zcrd > z_threshold
@@ -420,7 +502,7 @@
if(!cache)
SSatoms.map_loader_stop(REF(src))
CRASH("Undefined model key in DMM: [gset.gridLines[i]]")
- build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z)
+ build_coordinate(cache, locate(true_xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z)
// only bother with bounds that actually exist
if(!first_found)
@@ -444,7 +526,7 @@
/// Stanrdard loading, not used in production
/// Doesn't take advantage of any tgm optimizations, which makes it slower but also more general
/// Use this if for some reason your map format is messy
-/datum/parsed_map/proc/_dmm_load(x_offset, y_offset, z_offset, cropMap, no_changeturf, x_lower, x_upper, y_lower, y_upper, placeOnTop, new_z)
+/datum/parsed_map/proc/_dmm_load(x_offset, y_offset, z_offset, crop_map, no_changeturf, x_lower, x_upper, y_lower, y_upper, z_lower, z_upper, place_on_top, new_z)
// setup
var/list/modelCache = build_cache(no_changeturf)
var/space_key = modelCache[SPACE_KEY]
@@ -455,23 +537,46 @@
var/y_relative_to_absolute = y_offset - 1
var/x_relative_to_absolute = x_offset - 1
var/line_len = src.line_len
- for(var/datum/grid_set/gset as anything in gridSets)
+
+ // Amount we offset the grid zcrd to get the true zcrd
+ var/grid_z_offset = z_offset - 1
+ var/z_upper_set = z_upper < INFINITY
+ var/z_lower_set = z_lower > -INFINITY
+
+ // we now need to find the maximum z, fun!
+ var/map_bounds_z_max = 1
+ for(var/datum/grid_set/grid_set as anything in gridSets)
+ map_bounds_z_max = max(map_bounds_z_max, grid_set.zcrd)
+
+ var/z_upper_parsed = map_bounds_z_max + z_offset - 1
+ if(z_upper_set)
+ z_upper_parsed -= map_bounds_z_max - z_upper
+ if(z_lower_set)
+ var/offset_amount = z_lower - 1
+ z_upper_parsed -= offset_amount
+ grid_z_offset -= offset_amount
+
+ var/list/target_grid_sets = gridSets
+ if(z_lower_set || z_upper_set) // bounds are set, filter out gridsets for z levels we don't want
+ target_grid_sets = filter_grid_sets_based_on_z_bounds(z_lower, z_upper)
+
+ for(var/datum/grid_set/gset as anything in target_grid_sets)
var/relative_x = gset.xcrd
var/relative_y = gset.ycrd
var/true_xcrd = relative_x + x_relative_to_absolute
var/ycrd = relative_y + y_relative_to_absolute
- var/zcrd = gset.zcrd + z_offset - 1
- if(!cropMap && ycrd > world.maxy)
+ var/zcrd = gset.zcrd + grid_z_offset
+ if(!crop_map && ycrd > world.maxy)
if(new_z)
// Need to avoid improperly loaded area/turf_contents
- world.increaseMaxY(ycrd, max_zs_to_load = z_offset - 1)
+ world.increase_max_y(ycrd, map_load_z_cutoff = z_offset - 1)
else
- world.increaseMaxY(ycrd)
+ world.increase_max_y(ycrd)
expanded_y = TRUE
var/zexpansion = zcrd > world.maxz
var/no_afterchange = no_changeturf
if(zexpansion)
- if(cropMap)
+ if(crop_map)
continue
else
while (zcrd > world.maxz) //create a new z_level if needed
@@ -508,7 +613,7 @@
var/x_step_count = ROUND_UP(x_target / key_len)
var/final_x = relative_x + (x_step_count - 1)
var/x_delta_with = x_upper
- if(cropMap)
+ if(crop_map)
// Take our smaller crop threshold yes?
x_delta_with = min(x_delta_with, world.maxx)
if(final_x > x_delta_with)
@@ -517,12 +622,12 @@
x_step_count -= delta
final_x -= delta
x_target = x_step_count * key_len
- if(final_x > world.maxx && !cropMap)
+ if(final_x > world.maxx && !crop_map)
if(new_z)
// Need to avoid improperly loaded area/turf_contents
- world.increaseMaxX(final_x, max_zs_to_load = z_offset - 1)
+ world.increase_max_x(final_x, map_load_z_cutoff = z_offset - 1)
else
- world.increaseMaxX(final_x)
+ world.increase_max_x(final_x)
expanded_x = TRUE
// We're gonna track the first and last pairs of coords we find
@@ -553,7 +658,7 @@
if(!cache)
SSatoms.map_loader_stop(REF(src))
CRASH("Undefined model key in DMM: [model_key]")
- build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, placeOnTop, new_z)
+ build_coordinate(cache, locate(xcrd, ycrd, zcrd), no_afterchange, place_on_top, new_z)
// only bother with bounds that actually exist
if(!first_found)
diff --git a/code/modules/mapping/ruins.dm b/code/modules/mapping/ruins.dm
index 01a91a10c2a11f..a7b9480a34e4af 100644
--- a/code/modules/mapping/ruins.dm
+++ b/code/modules/mapping/ruins.dm
@@ -51,10 +51,10 @@
return central_turf
/datum/map_template/ruin/proc/place_on_isolated_level(z)
- var/datum/turf_reservation/reservation = SSmapping.RequestBlockReservation(width, height, z) //Make the new level creation work with different traits.
+ var/datum/turf_reservation/reservation = SSmapping.request_turf_block_reservation(width, height, 1, z) //Make the new level creation work with different traits.
if(!reservation)
return
- var/turf/placement = locate(reservation.bottom_left_coords[1],reservation.bottom_left_coords[2],reservation.bottom_left_coords[3])
+ var/turf/placement = reservation.bottom_left_turfs[1]
load(placement)
loaded++
for(var/turf/T in get_affected_turfs(placement))
diff --git a/code/modules/mapping/space_management/multiz_helpers.dm b/code/modules/mapping/space_management/multiz_helpers.dm
index f2331eb514613d..b0e2ff7fa06568 100644
--- a/code/modules/mapping/space_management/multiz_helpers.dm
+++ b/code/modules/mapping/space_management/multiz_helpers.dm
@@ -1,10 +1,11 @@
/proc/get_step_multiz(ref, dir)
+ var/turf/us = get_turf(ref)
if(dir & UP)
dir &= ~UP
- return get_step(GET_TURF_ABOVE(get_turf(ref)), dir)
+ return get_step(GET_TURF_ABOVE(us), dir)
if(dir & DOWN)
dir &= ~DOWN
- return get_step(GET_TURF_BELOW(get_turf(ref)), dir)
+ return get_step(GET_TURF_BELOW(us), dir)
return get_step(ref, dir)
/proc/get_dir_multiz(turf/us, turf/them)
@@ -15,27 +16,21 @@
if(us.z == them.z)
return get_dir(us, them)
else
- var/turf/T = us.above()
+ var/turf/T = GET_TURF_ABOVE(us)
var/dir = NONE
if(T && (T.z == them.z))
dir = UP
else
- T = us.below()
+ T = GET_TURF_BELOW(us)
if(T && (T.z == them.z))
dir = DOWN
else
return get_dir(us, them)
return (dir | get_dir(us, them))
-/turf/proc/above()
- return GET_TURF_ABOVE(src)
-
-/turf/proc/below()
- return GET_TURF_BELOW(src)
-
/proc/get_lowest_turf(atom/ref)
var/turf/us = get_turf(ref)
- var/next = GET_TURF_BELOW(us)
+ var/turf/next = GET_TURF_BELOW(us)
while(next)
us = next
next = GET_TURF_BELOW(us)
@@ -44,7 +39,7 @@
// I wish this was lisp
/proc/get_highest_turf(atom/ref)
var/turf/us = get_turf(ref)
- var/next = GET_TURF_ABOVE(us)
+ var/turf/next = GET_TURF_ABOVE(us)
while(next)
us = next
next = GET_TURF_ABOVE(us)
diff --git a/code/modules/mapping/space_management/space_reservation.dm b/code/modules/mapping/space_management/space_reservation.dm
index 52ac76e343aa58..2809ae65e6c2e1 100644
--- a/code/modules/mapping/space_management/space_reservation.dm
+++ b/code/modules/mapping/space_management/space_reservation.dm
@@ -1,16 +1,33 @@
//Yes, they can only be rectangular.
//Yes, I'm sorry.
/datum/turf_reservation
+ /// All turfs that we've reserved
var/list/reserved_turfs = list()
- ///Turfs around the reservation for cordoning
+
+ /// Turfs around the reservation for cordoning
var/list/cordon_turfs = list()
- ///Area of turfs next to the cordon to fill with pre_cordon_area's
+
+ /// Area of turfs next to the cordon to fill with pre_cordon_area's
var/list/pre_cordon_turfs = list()
+
+ /// The width of the reservation
var/width = 0
+
+ /// The height of the reservation
var/height = 0
- var/bottom_left_coords[3]
- var/top_right_coords[3]
+
+ /// The z stack size of the reservation. Note that reservations are ALWAYS reserved from the bottom up
+ var/z_size = 0
+
+ /// List of the bottom left turfs. Indexed by what their z index for this reservation is
+ var/list/bottom_left_turfs = list()
+
+ /// List of the top right turfs. Indexed by what their z index for this reservation is
+ var/list/top_right_turfs = list()
+
+ /// The turf type the reservation is initially made with
var/turf_type = /turf/open/space
+
///Distance away from the cordon where we can put a "sort-cordon" and run some extra code (see make_repel). 0 makes nothing happen
var/pre_cordon_distance = 0
@@ -19,6 +36,9 @@
pre_cordon_distance = 7
/datum/turf_reservation/proc/Release()
+ bottom_left_turfs.Cut()
+ top_right_turfs.Cut()
+
var/list/reserved_copy = reserved_turfs.Copy()
SSmapping.used_turfs -= reserved_turfs
reserved_turfs = list()
@@ -36,20 +56,20 @@
INVOKE_ASYNC(SSmapping, TYPE_PROC_REF(/datum/controller/subsystem/mapping, reserve_turfs), release_turfs)
/// Attempts to calaculate and store a list of turfs around the reservation for cordoning. Returns whether a valid cordon was calculated
-/datum/turf_reservation/proc/calculate_cordon_turfs(turf/BL, turf/TR)
- if(BL.x < 2 || BL.y < 2 || TR.x > (world.maxx - 2) || TR.y > (world.maxy - 2))
+/datum/turf_reservation/proc/calculate_cordon_turfs(turf/bottom_left, turf/top_right)
+ if(bottom_left.x < 2 || bottom_left.y < 2 || top_right.x > (world.maxx - 2) || top_right.y > (world.maxy - 2))
return FALSE // no space for a cordon here
- var/list/possible_turfs = CORNER_OUTLINE(BL, width, height)
+ var/list/possible_turfs = CORNER_OUTLINE(bottom_left, width, height)
+ // if they're our cordon turfs, accept them
+ possible_turfs -= cordon_turfs
for(var/turf/cordon_turf as anything in possible_turfs)
- if(!(cordon_turf.flags_1 & UNUSED_RESERVATION_TURF))
+ if(!(cordon_turf.turf_flags & UNUSED_RESERVATION_TURF))
return FALSE
- cordon_turfs = possible_turfs
-
- pre_cordon_turfs.Cut()
+ cordon_turfs |= possible_turfs
if(pre_cordon_distance)
- var/turf/offset_turf = locate(BL.x + pre_cordon_distance, BL.y + pre_cordon_distance, BL.z)
+ var/turf/offset_turf = locate(bottom_left.x + pre_cordon_distance, bottom_left.y + pre_cordon_distance, bottom_left.z)
var/list/to_add = CORNER_OUTLINE(offset_turf, width - pre_cordon_distance * 2, height - pre_cordon_distance * 2) //we step-by-stop move inwards from the outer cordon
for(var/turf/turf_being_added as anything in to_add)
pre_cordon_turfs |= turf_being_added //add one by one so we can filter out duplicates
@@ -64,10 +84,11 @@
old_area.turfs_to_uncontain += cordon_turf
cordon_area.contained_turfs += cordon_turf
cordon_area.contents += cordon_turf
+ // Its no longer unused, but its also not "used"
+ cordon_turf.turf_flags &= ~UNUSED_RESERVATION_TURF
cordon_turf.ChangeTurf(/turf/cordon, /turf/cordon)
-
- cordon_turf.flags_1 &= ~UNUSED_RESERVATION_TURF
SSmapping.unused_turfs["[cordon_turf.z]"] -= cordon_turf
+ // still gets linked to us though
SSmapping.used_turfs[cordon_turf] = src
//swap the area with the pre-cordoning area
@@ -113,7 +134,8 @@
if(!HAS_TRAIT(enterer, TRAIT_FREE_HYPERSPACE_SOFTCORDON_MOVEMENT))
space_dump(source, enterer)
-/datum/turf_reservation/proc/Reserve(width, height, zlevel)
+/// Internal proc which handles reserving the area for the reservation.
+/datum/turf_reservation/proc/_reserve_area(width, height, zlevel)
src.width = width
src.height = height
if(width > world.maxx || height > world.maxy || width < 1 || height < 1)
@@ -126,12 +148,12 @@
for(var/i in avail)
CHECK_TICK
BL = i
- if(!(BL.flags_1 & UNUSED_RESERVATION_TURF))
+ if(!(BL.turf_flags & UNUSED_RESERVATION_TURF))
continue
if(BL.x + width > world.maxx || BL.y + height > world.maxy)
continue
TR = locate(BL.x + width - 1, BL.y + height - 1, BL.z)
- if(!(TR.flags_1 & UNUSED_RESERVATION_TURF))
+ if(!(TR.turf_flags & UNUSED_RESERVATION_TURF))
continue
final = block(BL, TR)
if(!final)
@@ -139,7 +161,7 @@
passing = TRUE
for(var/I in final)
var/turf/checking = I
- if(!(checking.flags_1 & UNUSED_RESERVATION_TURF))
+ if(!(checking.turf_flags & UNUSED_RESERVATION_TURF))
passing = FALSE
break
if(passing) // found a potentially valid area, now try to calculate its cordon
@@ -149,18 +171,94 @@
break
if(!passing || !istype(BL) || !istype(TR))
return FALSE
- bottom_left_coords = list(BL.x, BL.y, BL.z)
- top_right_coords = list(TR.x, TR.y, TR.z)
for(var/i in final)
var/turf/T = i
reserved_turfs |= T
- T.flags_1 &= ~UNUSED_RESERVATION_TURF
SSmapping.unused_turfs["[T.z]"] -= T
SSmapping.used_turfs[T] = src
+ T.turf_flags = (T.turf_flags | RESERVATION_TURF) & ~UNUSED_RESERVATION_TURF
T.ChangeTurf(turf_type, turf_type)
+
+ bottom_left_turfs += BL
+ top_right_turfs += TR
+ return TRUE
+
+/datum/turf_reservation/proc/reserve(width, height, z_size, z_reservation)
+ src.z_size = z_size
+ var/failed_reservation = FALSE
+ for(var/_ in 1 to z_size)
+ if(!_reserve_area(width, height, z_reservation))
+ failed_reservation = TRUE
+ break
+
+ if(failed_reservation)
+ Release()
+ return FALSE
+
generate_cordon()
return TRUE
+/// Calculates the effective bounds information for the given turf. Returns a list of the information, or null if not applicable.
+/datum/turf_reservation/proc/calculate_turf_bounds_information(turf/target)
+ for(var/z_idx in 1 to z_size)
+ var/turf/bottom_left = bottom_left_turfs[z_idx]
+ var/turf/top_right = top_right_turfs[z_idx]
+ var/bl_x = bottom_left.x
+ var/bl_y = bottom_left.y
+ var/tr_x = top_right.x
+ var/tr_y = top_right.y
+
+ if(target.x < bl_x)
+ continue
+
+ if(target.y < bl_y)
+ continue
+
+ if(target.x > tr_x)
+ continue
+
+ if(target.y > tr_y)
+ continue
+
+ var/list/return_information = list()
+ return_information["z_idx"] = z_idx
+ return_information["offset_x"] = target.x - bl_x
+ return_information["offset_y"] = target.y - bl_y
+ return return_information
+ return null
+
+/// Gets the turf below the given target. Returns null if there is no turf below the target
+/datum/turf_reservation/proc/get_turf_below(turf/target)
+ var/list/bounds_info = calculate_turf_bounds_information(target)
+ if(isnull(bounds_info))
+ return null
+
+ var/z_idx = bounds_info["z_idx"]
+ // check what z level, if its the max, then there is no turf below
+ if(z_idx == z_size)
+ return null
+
+ var/offset_x = bounds_info["offset_x"]
+ var/offset_y = bounds_info["offset_y"]
+ var/turf/bottom_left = bottom_left_turfs[z_idx + 1]
+ return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z)
+
+/// Gets the turf above the given target. Returns null if there is no turf above the target
+/datum/turf_reservation/proc/get_turf_above(turf/target)
+ var/list/bounds_info = calculate_turf_bounds_information(target)
+ if(isnull(bounds_info))
+ return null
+
+ var/z_idx = bounds_info["z_idx"]
+ // check what z level, if its the min, then there is no turf above
+ if(z_idx == 1)
+ return null
+
+ var/offset_x = bounds_info["offset_x"]
+ var/offset_y = bounds_info["offset_y"]
+ var/turf/bottom_left = bottom_left_turfs[z_idx - 1]
+ return locate(bottom_left.x + offset_x, bottom_left.y + offset_y, bottom_left.z)
+
/datum/turf_reservation/New()
LAZYADD(SSmapping.turf_reservations, src)
diff --git a/code/modules/mining/equipment/kinetic_crusher.dm b/code/modules/mining/equipment/kinetic_crusher.dm
index a1edd69ba3e6ac..caee1bbac8d2bb 100644
--- a/code/modules/mining/equipment/kinetic_crusher.dm
+++ b/code/modules/mining/equipment/kinetic_crusher.dm
@@ -38,6 +38,7 @@
)
//technically it's huge and bulky, but this provides an incentive to use it
AddComponent(/datum/component/two_handed, force_unwielded=0, force_wielded=20)
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
/obj/item/kinetic_crusher/Destroy()
QDEL_LIST(trophies)
@@ -165,6 +166,10 @@
playsound(user, 'sound/weapons/empty.ogg', 100, TRUE)
update_appearance()
+/obj/item/kinetic_crusher/proc/on_saboteur(datum/source, disrupt_duration)
+ set_light_on(FALSE)
+ playsound(src, 'sound/weapons/empty.ogg', 100, TRUE)
+ return COMSIG_SABOTEUR_SUCCESS
/obj/item/kinetic_crusher/update_icon_state()
inhand_icon_state = "crusher[HAS_TRAIT(src, TRAIT_WIELDED)]" // this is not icon_state and not supported by 2hcomponent
diff --git a/code/modules/mining/lavaland/megafauna_loot.dm b/code/modules/mining/lavaland/megafauna_loot.dm
index ba9b3548ab873c..65d3a1ad3b3b1b 100644
--- a/code/modules/mining/lavaland/megafauna_loot.dm
+++ b/code/modules/mining/lavaland/megafauna_loot.dm
@@ -643,9 +643,10 @@
spirits = list()
START_PROCESSING(SSobj, src)
SSpoints_of_interest.make_point_of_interest(src)
- AddComponent(/datum/component/butchering, \
- speed = 15 SECONDS, \
- effectiveness = 90, \
+ AddComponent(\
+ /datum/component/butchering, \
+ speed = 15 SECONDS, \
+ effectiveness = 90, \
)
/obj/item/melee/ghost_sword/Destroy()
diff --git a/code/modules/mining/machine_redemption.dm b/code/modules/mining/machine_redemption.dm
index 4c0ca13dcd2fa1..ce045a81298531 100644
--- a/code/modules/mining/machine_redemption.dm
+++ b/code/modules/mining/machine_redemption.dm
@@ -370,7 +370,7 @@
var/amount = round(min(text2num(params["sheets"]), 50, can_smelt_alloy(alloy)))
if(amount < 1) //no negative mats
return
- materials.use_materials(alloy.materials, action = "released", name = "sheets")
+ materials.use_materials(alloy.materials, multiplier = amount, action = "released", name = "sheets")
var/output
if(ispath(alloy.build_path, /obj/item/stack/sheet))
output = new alloy.build_path(src, amount)
diff --git a/code/modules/mining/machine_silo.dm b/code/modules/mining/machine_silo.dm
index dc15e28a9270ed..840c8e92900a89 100644
--- a/code/modules/mining/machine_silo.dm
+++ b/code/modules/mining/machine_silo.dm
@@ -165,8 +165,8 @@ GLOBAL_LIST_EMPTY(silo_access_logs)
/obj/machinery/ore_silo/multitool_act(mob/living/user, obj/item/multitool/I)
. = ..()
if (istype(I))
- to_chat(user, span_notice("You log [src] in the multitool's buffer."))
I.set_buffer(src)
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/**
diff --git a/code/modules/mining/machine_stacking.dm b/code/modules/mining/machine_stacking.dm
index d7381d48e66f3b..286317a5d74ebc 100644
--- a/code/modules/mining/machine_stacking.dm
+++ b/code/modules/mining/machine_stacking.dm
@@ -27,7 +27,7 @@
return
var/obj/item/multitool/M = I
M.set_buffer(src)
- to_chat(user, span_notice("You store linkage information in [I]'s buffer."))
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/obj/machinery/mineral/stacking_unit_console/ui_interact(mob/user, datum/tgui/ui)
diff --git a/code/modules/mob/dead/new_player/new_player.dm b/code/modules/mob/dead/new_player/new_player.dm
index 7cb099f6585a54..a38fe1711742ec 100644
--- a/code/modules/mob/dead/new_player/new_player.dm
+++ b/code/modules/mob/dead/new_player/new_player.dm
@@ -125,13 +125,15 @@
if(JOB_NOT_VETERAN)
return "You need to be veteran to join as [jobtitle]."
if(JOB_UNAVAILABLE_QUIRK)
- return "[jobtitle] is restricted from your quirks."
+ return "[jobtitle] is restricted due to your selected quirks."
if(JOB_UNAVAILABLE_LANGUAGE)
- return "[jobtitle] is restricted from your languages."
+ return "[jobtitle] is restricted due to your selected languages."
if(JOB_UNAVAILABLE_SPECIES)
- return "[jobtitle] is restricted from your species."
+ return "[jobtitle] is restricted due to your selected species."
if(JOB_UNAVAILABLE_FLAVOUR)
return "[jobtitle] requires you to have flavour text for your character."
+ if(JOB_UNAVAILABLE_AUGMENT)
+ return "[jobtitle] is restricted due to your selected body augments."
//SKYRAT EDIT END
if(JOB_UNAVAILABLE_ANTAG_INCOMPAT)
return "[jobtitle] is not compatible with some antagonist role assigned to you."
diff --git a/code/modules/mob/dead/new_player/preferences_setup.dm b/code/modules/mob/dead/new_player/preferences_setup.dm
index 9b8a192260fc34..a1c7c0371b0004 100644
--- a/code/modules/mob/dead/new_player/preferences_setup.dm
+++ b/code/modules/mob/dead/new_player/preferences_setup.dm
@@ -25,6 +25,7 @@
/datum/preferences/proc/hardcore_random_setup(mob/living/carbon/human/character)
var/next_hardcore_score = select_hardcore_quirks()
character.hardcore_survival_score = next_hardcore_score ** 1.2 //30 points would be about 60 score
+ log_admin("[character] started hardcore random with [english_list(all_quirks)], for a score of [next_hardcore_score].")
//Add a sixpack because honestly
var/obj/item/bodypart/chest/chest = character.get_bodypart(BODY_ZONE_CHEST)
diff --git a/code/modules/mob/emote.dm b/code/modules/mob/emote.dm
index ff2e414dd7013d..0688778b04840e 100644
--- a/code/modules/mob/emote.dm
+++ b/code/modules/mob/emote.dm
@@ -117,7 +117,7 @@
return
if(user.get_timed_status_effect_duration(/datum/status_effect/confusion) > BEYBLADE_PUKE_THRESHOLD)
- user.vomit(BEYBLADE_PUKE_NUTRIENT_LOSS, distance = 0)
+ user.vomit(VOMIT_CATEGORY_KNOCKDOWN, lost_nutrition = BEYBLADE_PUKE_NUTRIENT_LOSS, distance = 0)
return
if(prob(BEYBLADE_DIZZINESS_PROBABILITY))
diff --git a/code/modules/mob/living/basic/basic.dm b/code/modules/mob/living/basic/basic.dm
index 44c11bdc056707..155321805c621e 100644
--- a/code/modules/mob/living/basic/basic.dm
+++ b/code/modules/mob/living/basic/basic.dm
@@ -35,6 +35,8 @@
var/attack_vis_effect
///Played when someone punches the creature.
var/attacked_sound = SFX_PUNCH //This should be an element
+ /// How often can you melee attack?
+ var/melee_attack_cooldown = 2 SECONDS
/// Variable maintained for compatibility with attack_animal procs until simple animals can be refactored away. Use element instead of setting manually.
var/environment_smash = ENVIRONMENT_SMASH_STRUCTURES
@@ -191,8 +193,10 @@
return
. += span_deadsay("Upon closer examination, [p_they()] appear[p_s()] to be [HAS_TRAIT(user.mind, TRAIT_NAIVE) ? "asleep" : "dead"].")
-/mob/living/basic/proc/melee_attack(atom/target, list/modifiers)
+/mob/living/basic/proc/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
face_atom(target)
+ if (!ignore_cooldown)
+ changeNext_move(melee_attack_cooldown)
if(SEND_SIGNAL(src, COMSIG_HOSTILE_PRE_ATTACKINGTARGET, target) & COMPONENT_HOSTILE_NO_ATTACK)
return FALSE //but more importantly return before attack_animal called
var/result = target.attack_basic_mob(src, modifiers)
diff --git a/code/modules/mob/living/basic/farm_animals/pony.dm b/code/modules/mob/living/basic/farm_animals/pony.dm
index 434caa5ef51924..4bc09391cb7183 100644
--- a/code/modules/mob/living/basic/farm_animals/pony.dm
+++ b/code/modules/mob/living/basic/farm_animals/pony.dm
@@ -62,7 +62,7 @@
if (prob(33))
whinny_angrily()
-/mob/living/basic/pony/melee_attack(atom/target, list/modifiers)
+/mob/living/basic/pony/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
. = ..()
if (!.)
diff --git a/code/modules/mob/living/basic/heretic/star_gazer.dm b/code/modules/mob/living/basic/heretic/star_gazer.dm
index 099efd80172c34..b739da0831a210 100644
--- a/code/modules/mob/living/basic/heretic/star_gazer.dm
+++ b/code/modules/mob/living/basic/heretic/star_gazer.dm
@@ -6,7 +6,7 @@
icon_living = "star_gazer"
pixel_x = -32
base_pixel_x = -32
- mob_biotypes = MOB_HUMANOID | MOB_EPIC
+ mob_biotypes = MOB_HUMANOID | MOB_SPECIAL
response_help_continuous = "passes through"
response_help_simple = "pass through"
speed = -0.2
@@ -22,6 +22,7 @@
attack_verb_simple = "ravage"
attack_vis_effect = ATTACK_EFFECT_SLASH
attack_sound = 'sound/weapons/bladeslice.ogg'
+ melee_attack_cooldown = 0.6 SECONDS
speak_emote = list("growls")
damage_coeff = list(BRUTE = 1, BURN = 0.5, TOX = 0, CLONE = 0, STAMINA = 0, OXY = 0)
death_sound = 'sound/magic/cosmic_expansion.ogg'
@@ -57,6 +58,24 @@
ADD_TRAIT(src, TRAIT_NO_FLOATING_ANIM, INNATE_TRAIT)
set_light(4, l_color = "#dcaa5b")
+// Star gazer attacks everything around itself applies a spooky mark
+/mob/living/basic/heretic_summon/star_gazer/melee_attack(mob/living/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if (!. || !isliving(target))
+ return
+
+ target.apply_status_effect(/datum/status_effect/star_mark)
+ target.apply_damage(damage = 5, damagetype = CLONE)
+ var/datum/targetting_datum/target_confirmer = ai_controller.blackboard[BB_TARGETTING_DATUM]
+ for(var/mob/living/nearby_mob in range(1, src))
+ if(target == nearby_mob || !target_confirmer?.can_attack(src, nearby_mob))
+ continue
+ nearby_mob.apply_status_effect(/datum/status_effect/star_mark)
+ nearby_mob.apply_damage(10)
+ to_chat(nearby_mob, span_userdanger("\The [src] [attack_verb_continuous] you!"))
+ do_attack_animation(nearby_mob, ATTACK_EFFECT_SLASH)
+ log_combat(src, nearby_mob, "slashed")
+
/datum/ai_controller/basic_controller/star_gazer
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/star_gazer(),
@@ -70,39 +89,12 @@
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/star_gazer,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/targetting_datum/basic/star_gazer
stat_attack = HARD_CRIT
-/datum/ai_planning_subtree/basic_melee_attack_subtree/star_gazer
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/star_gazer
-
-/datum/ai_behavior/basic_melee_attack/star_gazer
- action_cooldown = 0.6 SECONDS
-
-/datum/ai_behavior/basic_melee_attack/star_gazer/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
- . = ..()
- var/atom/target = controller.blackboard[target_key]
- var/mob/living/living_pawn = controller.pawn
-
- if(!isliving(target))
- return
- var/mob/living/living_target = target
- living_target.apply_status_effect(/datum/status_effect/star_mark)
- living_target.apply_damage_type(damage = 5, damagetype = CLONE)
- if(living_target.pulledby != living_pawn)
- if(living_pawn.Adjacent(living_target) && isturf(living_target.loc) && living_target.stat == SOFT_CRIT)
- living_target.grabbedby(living_pawn)
- for(var/mob/living/nearby_mob in range(1, living_pawn))
- if(nearby_mob.stat == DEAD || living_target == nearby_mob || faction_check(nearby_mob.faction, list(FACTION_HERETIC)))
- continue
- nearby_mob.apply_status_effect(/datum/status_effect/star_mark)
- nearby_mob.adjustBruteLoss(10)
- living_pawn.do_attack_animation(nearby_mob, ATTACK_EFFECT_SLASH)
- log_combat(living_pawn, nearby_mob, "slashed")
-
/datum/ai_planning_subtree/attack_obstacle_in_path/star_gazer
attack_behaviour = /datum/ai_behavior/attack_obstructions/star_gazer
@@ -119,4 +111,4 @@
command_feedback = "stares!"
pointed_reaction = "stares intensely!"
refuse_reaction = "..."
- attack_behaviour = /datum/ai_behavior/basic_melee_attack/star_gazer
+ attack_behaviour = /datum/ai_behavior/basic_melee_attack
diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm
index 465d724944b2f9..292766be07bf90 100644
--- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm
+++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp.dm
@@ -69,6 +69,7 @@
INVOKE_ASYNC(src, PROC_REF(cannibalize_victim), victim)
return COMPONENT_HOSTILE_NO_ATTACK
+/// Carve a stone into a beautiful self-portrait
/mob/living/basic/mining/ice_whelp/proc/create_sculpture(atom/target)
balloon_alert(src, "sculpting...")
if(!do_after(src, 5 SECONDS, target = target))
@@ -80,7 +81,9 @@
dragon_statue.set_anchored(TRUE)
qdel(target)
+/// Gib and consume our fellow ice drakes
/mob/living/basic/mining/ice_whelp/proc/cannibalize_victim(mob/living/target)
+ start_pulling(target)
balloon_alert(src, "devouring...")
if(!do_after(src, 5 SECONDS, target))
return
diff --git a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm
index 08c5fda3fd89e0..9885ba3da590f6 100644
--- a/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm
+++ b/code/modules/mob/living/basic/icemoon/ice_whelp/ice_whelp_ai.dm
@@ -58,7 +58,6 @@
finish_action(controller, FALSE)
return
- living_pawn.start_pulling(target)
living_pawn.melee_attack(target)
finish_action(controller, TRUE)
diff --git a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm
index 83369de8862d61..c433ec936ad5ba 100644
--- a/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm
+++ b/code/modules/mob/living/basic/lavaland/goliath/goliath_ai.dm
@@ -9,7 +9,7 @@
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
- /datum/ai_planning_subtree/target_retaliate,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/find_food,
/datum/ai_planning_subtree/targeted_mob_ability/goliath_tentacles,
diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm
index 6463fc94979840..18cd73219362e9 100644
--- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm
+++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity.dm
@@ -18,6 +18,7 @@
attack_verb_simple = "snip"
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE // Closer than a scratch to a crustacean pinching effect
+ melee_attack_cooldown = 1 SECONDS
butcher_results = list(
/obj/item/food/meat/crab = 2,
/obj/item/stack/sheet/bone = 2,
@@ -35,6 +36,7 @@
/mob/living/basic/mining/lobstrosity/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_SNOWSTORM_IMMUNE, INNATE_TRAIT)
+ AddElement(/datum/element/mob_grabber)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_CLAW)
AddElement(/datum/element/basic_eating, food_types = target_foods)
AddElement(\
@@ -73,7 +75,7 @@
return
var/mob/living/basic/basic_source = source
var/mob/living/living_target = target
- basic_source.melee_attack(living_target)
+ basic_source.melee_attack(living_target, ignore_cooldown = TRUE)
basic_source.ai_controller?.set_blackboard_key(BB_BASIC_MOB_FLEEING, FALSE)
basic_source.start_pulling(living_target)
diff --git a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm
index dcbeb1e670ca6d..8e4dfe9e29463f 100644
--- a/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm
+++ b/code/modules/mob/living/basic/lavaland/lobstrosity/lobstrosity_ai.dm
@@ -36,7 +36,6 @@
return ..()
/datum/ai_behavior/basic_melee_attack/lobster
- action_cooldown = 1 SECONDS
/datum/ai_behavior/basic_melee_attack/lobster/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
var/mob/living/target = controller.blackboard[target_key]
@@ -53,9 +52,6 @@
if (controller.blackboard[BB_BASIC_MOB_FLEEING])
finish_action(controller = controller, succeeded = TRUE, target_key = target_key) // We don't want to clear our target
return
- var/mob/living/living_pawn = controller.pawn
- if (target.stat != CONSCIOUS)
- living_pawn.start_pulling(target) // No crawling away
return ..()
/datum/ai_planning_subtree/flee_target/lobster
@@ -75,6 +71,10 @@
finish_action(controller, succeeded = FALSE)
return
+ var/mob/living/us = controller.pawn
+ if (us.pulling == target)
+ us.stop_pulling() // If we're running away from someone, best not to bring them with us
+
return ..()
/// Don't use charge ability on an adjacent target, and make sure you're visible before you start
diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm
index b94ba9142980c9..28ed712d06127a 100644
--- a/code/modules/mob/living/basic/lavaland/watcher/watcher.dm
+++ b/code/modules/mob/living/basic/lavaland/watcher/watcher.dm
@@ -1,4 +1,4 @@
-/// A floating eyeball which keeps its distance and plays red light/green light with you.
+/// A floating eyeball which keeps its distance and sometimes make you look away.
/mob/living/basic/mining/watcher
name = "watcher"
desc = "A levitating, monocular creature held aloft by wing-like veins. A sharp spine of crystal protrudes from its body."
@@ -59,14 +59,10 @@
)
update_appearance(UPDATE_OVERLAYS)
- var/datum/action/cooldown/mob_cooldown/watcher_overwatch/overwatch = new(src)
- overwatch.Grant(src)
- overwatch.projectile_type = projectile_type
- ai_controller.set_blackboard_key(BB_WATCHER_OVERWATCH, overwatch)
-
var/datum/action/cooldown/mob_cooldown/watcher_gaze/gaze = new gaze_attack(src)
gaze.Grant(src)
- ai_controller.set_blackboard_key(BB_WATCHER_GAZE, gaze)
+ ai_controller.set_blackboard_key(BB_GENERIC_ACTION, gaze)
+ AddComponent(/datum/component/revenge_ability, gaze, targetting = ai_controller.blackboard[BB_TARGETTING_DATUM])
/mob/living/basic/mining/watcher/update_overlays()
. = ..()
diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm
index b08245963f43b9..9b2972a398fb3f 100644
--- a/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm
+++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_ai.dm
@@ -6,41 +6,23 @@
ai_movement = /datum/ai_movement/basic_avoidance
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
- /datum/ai_planning_subtree/target_retaliate,
+ /datum/ai_planning_subtree/target_retaliate/check_faction,
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/use_mob_ability/gaze,
- /datum/ai_planning_subtree/targeted_mob_ability/overwatch,
/datum/ai_planning_subtree/ranged_skirmish/watcher,
/datum/ai_planning_subtree/maintain_distance,
)
-/datum/ai_planning_subtree/targeted_mob_ability/overwatch
- ability_key = BB_WATCHER_OVERWATCH
- operational_datums = list(/datum/component/ai_target_timer)
-
-/datum/ai_planning_subtree/targeted_mob_ability/overwatch/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
- var/mob/living/living_pawn = controller.pawn
- if (living_pawn.do_after_count())
- return // Don't interrupt our other ability
- var/atom/target = controller.blackboard[target_key]
- if (QDELETED(target) || HAS_TRAIT(target, TRAIT_OVERWATCH_IMMUNE))
- return // We should probably let miners move sometimes
- var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0
- if (time_on_target < 5 SECONDS)
- return // We need to spend some time acquiring our target first
- return ..()
-
/datum/ai_planning_subtree/use_mob_ability/gaze
- ability_key = BB_WATCHER_GAZE
finish_planning = TRUE
/datum/ai_planning_subtree/use_mob_ability/gaze/SelectBehaviors(datum/ai_controller/controller, seconds_per_tick)
- var/mob/living/watcher = controller.pawn
- if (watcher.health > watcher.maxHealth * 0.66) // When we're a little hurt
- return
var/mob/living/target = controller.blackboard[BB_BASIC_MOB_CURRENT_TARGET]
if (!isliving(target))
return // Don't do this if there's nothing hostile around or if our target is a mech
+ var/time_on_target = controller.blackboard[BB_BASIC_MOB_HAS_TARGET_TIME] || 0
+ if (time_on_target < 5 SECONDS)
+ return // We need to spend some time acquiring our target first
return ..()
/datum/ai_planning_subtree/ranged_skirmish/watcher
diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm
index e4eb9562f5336e..9426db41cca60d 100644
--- a/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm
+++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_gaze.dm
@@ -8,7 +8,7 @@
button_icon_state = "gaze"
background_icon_state = "bg_demon"
overlay_icon_state = "bg_demon_border"
- cooldown_time = 30 SECONDS
+ cooldown_time = 20 SECONDS
check_flags = AB_CHECK_CONSCIOUS | AB_CHECK_INCAPACITATED
click_to_activate = FALSE
shared_cooldown = NONE
diff --git a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
index 36ad2d61b4cf64..0c8194c524a938 100644
--- a/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
+++ b/code/modules/mob/living/basic/lavaland/watcher/watcher_overwatch.dm
@@ -1,5 +1,6 @@
/**
* Automatically shoot at a target if they do anything while this is active on them.
+ * Currently not given to any mob, but retained so admins can use it.
*/
/datum/action/cooldown/mob_cooldown/watcher_overwatch
name = "Overwatch"
diff --git a/code/modules/mob/living/basic/pets/dog/_dog.dm b/code/modules/mob/living/basic/pets/dog/_dog.dm
index 93947173cd922d..dd3b3b3a4634c6 100644
--- a/code/modules/mob/living/basic/pets/dog/_dog.dm
+++ b/code/modules/mob/living/basic/pets/dog/_dog.dm
@@ -33,6 +33,7 @@
attack_verb_simple = "bite"
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
+ melee_attack_cooldown = 0.8 SECONDS
/// Instructions you can give to dogs
var/static/list/pet_commands = list(
/datum/pet_command/idle,
diff --git a/code/modules/mob/living/basic/pets/dog/corgi.dm b/code/modules/mob/living/basic/pets/dog/corgi.dm
index d072a1c897e18e..cb0df08d198b9e 100644
--- a/code/modules/mob/living/basic/pets/dog/corgi.dm
+++ b/code/modules/mob/living/basic/pets/dog/corgi.dm
@@ -44,27 +44,29 @@
/mob/living/basic/pet/dog/corgi/Exited(atom/movable/gone, direction)
. = ..()
+ var/dropped_something = FALSE
if(gone == inventory_head)
+ dropped_something = TRUE
inventory_head = null
- update_corgi_fluff()
- update_appearance(UPDATE_OVERLAYS)
if(gone == inventory_back)
+ dropped_something = TRUE
inventory_back = null
+ if(dropped_something)
update_corgi_fluff()
update_appearance(UPDATE_OVERLAYS)
/mob/living/basic/pet/dog/corgi/gib()
- if(inventory_head)
- inventory_head.forceMove(drop_location())
- inventory_head = null
- if(inventory_back)
- inventory_back.forceMove(drop_location())
- inventory_back = null
+ undress_dog()
if(access_card)
access_card.forceMove(drop_location())
access_card = null
return ..()
+/// Removes the hat and shirt (but not ID) of this corgi
+/mob/living/basic/pet/dog/corgi/proc/undress_dog()
+ inventory_head?.forceMove(drop_location())
+ inventory_back?.forceMove(drop_location())
+
/mob/living/basic/pet/dog/corgi/examine(mob/user)
. = ..()
if(access_card)
@@ -194,44 +196,42 @@
/mob/living/basic/pet/dog/corgi/proc/place_on_head(obj/item/item_to_add, mob/living/user)
if(inventory_head)
if(user)
- to_chat(user, span_warning("You can't put more than one hat on [src]!"))
- return
- if(!item_to_add)
- user.visible_message(span_notice("[user] pets [src]."), span_notice("You rest your hand on [src]'s head for a moment."))
- if(flags_1 & HOLOGRAM_1)
- return
- user.add_mood_event(REF(src), /datum/mood_event/pet_animal, src)
- return
+ balloon_alert(user, "already wearing a hat!")
+ return FALSE
+
+ if(isnull(item_to_add))
+ if (!isnull(user))
+ user.visible_message(span_notice("[user] pets [src]."), span_notice("You rest your hand on [src]'s head for a moment."))
+ if(flags_1 & HOLOGRAM_1)
+ return
+ user.add_mood_event(REF(src), /datum/mood_event/pet_animal, src)
+ return FALSE
if(user && !user.temporarilyRemoveItemFromInventory(item_to_add))
to_chat(user, span_warning("\The [item_to_add] is stuck to your hand, you cannot put it on [src]'s head!"))
- return
-
- var/valid = FALSE
- if(ispath(item_to_add.dog_fashion, /datum/dog_fashion/head))
- valid = TRUE
+ return FALSE
//Various hats and items (worn on his head) change Ian's behaviour. His attributes are reset when a hat is removed.
-
- if(valid)
- if(user && (stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH)))
- to_chat(user, span_notice("There is merely a dull, lifeless look in [real_name]'s eyes as you put \the [item_to_add] on [p_them()]."))
- else if(user)
- user.visible_message(span_notice("[user] puts [item_to_add] on [real_name]'s head. [src] looks at [user] and barks once."),
- span_notice("You put [item_to_add] on [real_name]'s head. [src] gives you a peculiar look, then wags [p_their()] tail once and barks."),
- span_hear("You hear a friendly-sounding bark."))
- item_to_add.forceMove(src)
- inventory_head = item_to_add
- update_corgi_fluff()
- update_appearance(UPDATE_OVERLAYS)
- else
+ if(!ispath(item_to_add.dog_fashion, /datum/dog_fashion/head))
to_chat(user, span_warning("You set [item_to_add] on [src]'s head, but it falls off!"))
item_to_add.forceMove(drop_location())
if(prob(25))
step_rand(item_to_add)
dance_rotate(src, set_original_dir = TRUE)
+ return FALSE
- return valid
+ if (user)
+ if(stat == DEAD || HAS_TRAIT(src, TRAIT_FAKEDEATH))
+ to_chat(user, span_notice("There is merely a dull, lifeless look in [real_name]'s eyes as you put \the [item_to_add] on [p_them()]."))
+ else
+ user.visible_message(span_notice("[user] puts [item_to_add] on [real_name]'s head. [src] looks at [user] and barks once."),
+ span_notice("You put [item_to_add] on [real_name]'s head. [src] gives you a peculiar look, then wags [p_their()] tail once and barks."),
+ span_hear("You hear a friendly-sounding bark."))
+ item_to_add.forceMove(src)
+ inventory_head = item_to_add
+ update_corgi_fluff()
+ update_appearance(UPDATE_OVERLAYS)
+ return TRUE
/mob/living/basic/pet/dog/corgi/proc/update_corgi_fluff()
// First, change back to defaults
@@ -380,6 +380,14 @@
Write_Memory(TRUE)
return ..()
+/mob/living/basic/pet/dog/corgi/ian/revive(full_heal_flags, excess_healing, force_grab_ghost)
+ . = ..()
+ if (!.)
+ return
+ if (!istype(inventory_head, /obj/item/clothing/glasses/eyepatch))
+ inventory_head?.forceMove(drop_location())
+ place_on_head(new /obj/item/clothing/glasses/eyepatch/medical)
+
/mob/living/basic/pet/dog/corgi/ian/narsie_act()
playsound(src, 'sound/magic/demon_dies.ogg', 75, TRUE)
var/mob/living/basic/pet/dog/corgi/narsie/narsIan = new(loc)
@@ -453,30 +461,30 @@
can_be_shaved = FALSE
unique_pet = TRUE
held_state = "narsian"
+ /// Mobs we will consume in the name of Nar'Sie
+ var/static/list/edible_types = list(/mob/living/simple_animal/pet, /mob/living/basic/pet)
-//this could maybe be turned into an element
-/mob/living/basic/pet/dog/corgi/narsie/Life(seconds_per_tick = SSMOBS_DT, times_fired)
+/mob/living/basic/pet/dog/corgi/narsie/Initialize(mapload)
. = ..()
- //consume simple_animal pets
- for(var/mob/living/simple_animal/pet/simple_pet in range(1, src))
- if(simple_pet != src && !istype(simple_pet, /mob/living/basic/pet/dog/corgi/narsie))
- visible_message(span_warning("Dark magic resonating from [src] devours [simple_pet]!"), \
- "DELICIOUS SOULS")
- playsound(src, 'sound/magic/demon_attack1.ogg', 75, TRUE)
- new /obj/effect/temp_visual/cult/sac(get_turf(simple_pet))
- narsie_act()
- simple_pet.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS)
- simple_pet.gib()
- //consume basic pets
- for(var/mob/living/basic/pet/basic_pet in range(1, src))
- if(basic_pet != src && !istype(basic_pet, /mob/living/basic/pet/dog/corgi/narsie))
- visible_message(span_warning("Dark magic resonating from [src] devours [basic_pet]!"), \
- "DELICIOUS SOULS")
- playsound(src, 'sound/magic/demon_attack1.ogg', 75, TRUE)
- new /obj/effect/temp_visual/cult/sac(get_turf(basic_pet))
- narsie_act()
- basic_pet.investigate_log("has been gibbed by [src].", INVESTIGATE_DEATHS)
- basic_pet.gib()
+ var/static/list/connections = list(COMSIG_ATOM_ENTERED = PROC_REF(on_prey_approached))
+ AddComponent(/datum/component/connect_range, tracked = src, connections = connections, range = 1, works_in_containers = FALSE)
+
+/// Attempt to eat a pet we get near
+/mob/living/basic/pet/dog/corgi/narsie/proc/on_prey_approached(atom/movable/dog, atom/movable/prey)
+ SIGNAL_HANDLER
+ if (!is_type_in_list(prey, edible_types) || istype(prey, type))
+ return
+ visible_message(span_warning("Dark magic resonating from [src] devours [prey]!"), \
+ "DELICIOUS SOULS")
+ playsound(src, 'sound/magic/demon_attack1.ogg', 75, TRUE)
+ new /obj/effect/temp_visual/cult/sac(get_turf(prey))
+ narsie_act()
+ prey.investigate_log("has been sacrificed by [src].", INVESTIGATE_DEATHS)
+ if (isliving(prey))
+ var/mob/living/living_sacrifice = prey
+ living_sacrifice.gib()
+ else
+ qdel(prey)
/mob/living/basic/pet/dog/corgi/narsie/update_corgi_fluff()
. = ..()
diff --git a/code/modules/mob/living/basic/ruin_defender/stickman.dm b/code/modules/mob/living/basic/ruin_defender/stickman.dm
index 8d0a5ab0cdedfc..107973135c78c1 100644
--- a/code/modules/mob/living/basic/ruin_defender/stickman.dm
+++ b/code/modules/mob/living/basic/ruin_defender/stickman.dm
@@ -13,6 +13,7 @@
attack_verb_simple = "punch"
melee_damage_lower = 10
melee_damage_upper = 10
+ melee_attack_cooldown = 1.5 SECONDS
attack_sound = 'sound/weapons/punch1.ogg'
combat_mode = TRUE
faction = list(FACTION_STICKMAN)
@@ -39,15 +40,9 @@
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/stickman
+ /datum/ai_planning_subtree/basic_melee_attack_subtree
)
-/datum/ai_planning_subtree/basic_melee_attack_subtree/stickman
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/stickman
-
-/datum/ai_behavior/basic_melee_attack/stickman
- action_cooldown = 1.5 SECONDS
-
/mob/living/basic/stickman/dog
name = "Angry Stick Dog"
desc = "Stickman's best friend, if he could see him at least."
diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm
index 6e716ddf411b25..7c57349524fec0 100644
--- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm
+++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_behavior.dm
@@ -1,6 +1,3 @@
-/datum/ai_behavior/basic_melee_attack/bear
- action_cooldown = 2 SECONDS
-
/datum/ai_behavior/find_hunt_target/find_hive
/datum/ai_behavior/find_hunt_target/find_hive/valid_dinner(mob/living/source, obj/structure/beebox/hive, radius)
diff --git a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm
index 244c600d89fb43..851c0bb80290b2 100644
--- a/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm
+++ b/code/modules/mob/living/basic/space_fauna/bear/bear_ai_subtree.dm
@@ -8,16 +8,13 @@
planning_subtrees = list(
/datum/ai_planning_subtree/target_retaliate,
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/bear,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/climb_trees,
/datum/ai_planning_subtree/find_and_hunt_target/find_hive,
/datum/ai_planning_subtree/find_and_hunt_target/find_honeycomb,
/datum/ai_planning_subtree/random_speech/bear,
)
-/datum/ai_planning_subtree/basic_melee_attack_subtree/bear
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/bear
-
/datum/ai_planning_subtree/find_and_hunt_target/find_hive
target_key = BB_FOUND_HONEY
hunting_behavior = /datum/ai_behavior/hunt_target/find_hive
diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp.dm b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
index 6ce40d8c1a3b38..9a4b149fd1a003 100644
--- a/code/modules/mob/living/basic/space_fauna/carp/carp.dm
+++ b/code/modules/mob/living/basic/space_fauna/carp/carp.dm
@@ -32,6 +32,7 @@
attack_vis_effect = ATTACK_EFFECT_BITE
attack_verb_continuous = "bites"
attack_verb_simple = "bite"
+ melee_attack_cooldown = 1.5 SECONDS
response_help_continuous = "pets"
response_help_simple = "pet"
response_disarm_continuous = "gently pushes aside"
@@ -57,7 +58,7 @@
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/follow,
- /datum/pet_command/point_targetting/attack/carp
+ /datum/pet_command/point_targetting/attack
)
/// Carp want to eat raw meat
var/static/list/desired_food = list(/obj/item/food/meat/slab, /obj/item/food/meat/rawcutlet)
diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm
index c0f5143f18c22f..b2dc866ce7cec7 100644
--- a/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm
+++ b/code/modules/mob/living/basic/space_fauna/carp/carp_ai_actions.dm
@@ -1,17 +1,8 @@
#define MAGICARP_SPELL_TARGET_SEEK_RANGE 4
-/datum/pet_command/point_targetting/attack/carp
- attack_behaviour = /datum/ai_behavior/basic_melee_attack/carp
-
/datum/pet_command/point_targetting/use_ability/magicarp
pet_ability_key = BB_MAGICARP_SPELL
-/datum/ai_planning_subtree/basic_melee_attack_subtree/carp
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/carp
-
-/datum/ai_behavior/basic_melee_attack/carp
- action_cooldown = 1.5 SECONDS
-
/datum/ai_planning_subtree/attack_obstacle_in_path/carp
attack_behaviour = /datum/ai_behavior/attack_obstructions/carp
@@ -20,12 +11,12 @@
/// As basic attack tree but interrupt if your health gets low or if your spell is off cooldown
/datum/ai_planning_subtree/basic_melee_attack_subtree/magicarp
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/carp/magic
+ melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/magicarp
/// Interrupt your attack chain if: you have a spell, it's not on cooldown, and it has a target
-/datum/ai_behavior/basic_melee_attack/carp/magic
+/datum/ai_behavior/basic_melee_attack/magicarp
-/datum/ai_behavior/basic_melee_attack/carp/magic/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, health_ratio_key)
+/datum/ai_behavior/basic_melee_attack/magicarp/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key, health_ratio_key)
var/datum/action/cooldown/using_action = controller.blackboard[BB_MAGICARP_SPELL]
if (QDELETED(using_action))
return ..()
diff --git a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm
index f4c446d088b960..b30970145352bc 100644
--- a/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm
+++ b/code/modules/mob/living/basic/space_fauna/carp/carp_controllers.dm
@@ -25,7 +25,7 @@
/datum/ai_planning_subtree/attack_obstacle_in_path/carp,
/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift,
/datum/ai_planning_subtree/make_carp_rift/aggressive_teleport,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/carp,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/carp_migration,
)
@@ -48,7 +48,7 @@
/datum/ai_planning_subtree/attack_obstacle_in_path/carp,
/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift,
/datum/ai_planning_subtree/make_carp_rift/aggressive_teleport,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/carp,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/**
@@ -91,6 +91,6 @@
/datum/ai_planning_subtree/attack_obstacle_in_path/carp,
/datum/ai_planning_subtree/shortcut_to_target_through_carp_rift,
/datum/ai_planning_subtree/make_carp_rift/aggressive_teleport,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/carp,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/carp_migration,
)
diff --git a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm
index 5051335a5571e3..32f02b880dba40 100644
--- a/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm
+++ b/code/modules/mob/living/basic/space_fauna/carp/magicarp.dm
@@ -56,7 +56,7 @@ GLOBAL_LIST_INIT(magicarp_spell_colours, list(
/datum/pet_command/idle,
/datum/pet_command/free,
/datum/pet_command/follow,
- /datum/pet_command/point_targetting/attack/carp,
+ /datum/pet_command/point_targetting/attack,
/datum/pet_command/point_targetting/use_ability/magicarp,
)
/// List of all projectiles we can fire.
diff --git a/code/modules/mob/living/basic/space_fauna/faithless.dm b/code/modules/mob/living/basic/space_fauna/faithless.dm
index b279856412c3b7..c1dc297ea46a6e 100644
--- a/code/modules/mob/living/basic/space_fauna/faithless.dm
+++ b/code/modules/mob/living/basic/space_fauna/faithless.dm
@@ -18,6 +18,7 @@
attack_verb_continuous = "grips"
attack_verb_simple = "grip"
attack_sound = 'sound/hallucinations/growl1.ogg'
+ melee_attack_cooldown = 1 SECONDS
speak_emote = list("growls")
unsuitable_atmos_damage = 0
@@ -29,12 +30,29 @@
ai_controller = /datum/ai_controller/basic_controller/faithless
+ /// What are the odds we paralyze a target on attack
+ var/paralyze_chance = 12
+ /// How long do we paralyze a target for if we attack them
+ var/paralyze_duration = 2 SECONDS
+
/mob/living/basic/faithless/Initialize(mapload)
. = ..()
ADD_TRAIT(src, TRAIT_SPACEWALK, INNATE_TRAIT)
AddElement(/datum/element/footstep, FOOTSTEP_MOB_SHOE)
+ AddElement(/datum/element/mob_grabber, steal_from_others = FALSE)
AddComponent(/datum/component/pry_open_door)
+/mob/living/basic/faithless/melee_attack(atom/target, list/modifiers, ignore_cooldown)
+ . = ..()
+ if (!. || !isliving(target))
+ return
+
+ var/mob/living/living_target = target
+ if (prob(paralyze_chance))
+ living_target.Paralyze(paralyze_duration)
+ living_target.visible_message(span_danger("\The [src] knocks \the [target] down!"), \
+ span_userdanger("\The [src] knocks you down!"))
+
/datum/ai_controller/basic_controller/faithless
blackboard = list(
BB_TARGETTING_DATUM = new /datum/targetting_datum/basic/faithless(),
@@ -47,37 +65,10 @@
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
/datum/ai_planning_subtree/attack_obstacle_in_path/low_priority_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/faithless,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures,
/datum/ai_planning_subtree/random_speech/faithless,
)
/datum/targetting_datum/basic/faithless
stat_attack = UNCONSCIOUS
-
-/datum/ai_planning_subtree/basic_melee_attack_subtree/faithless
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/faithless
-
-/datum/ai_behavior/basic_melee_attack/faithless
- action_cooldown = 1 SECONDS
- /// What are the odds we paralyze a target
- var/paralyze_chance = 12
- /// How long do we paralyze a target for if we attack them
- var/paralyze_duration = 2 SECONDS
-
-/datum/ai_behavior/basic_melee_attack/faithless/perform(seconds_per_tick, datum/ai_controller/controller, target_key, targetting_datum_key, hiding_location_key)
- . = ..()
- var/atom/target = controller.blackboard[target_key]
- var/mob/living/living_pawn = controller.pawn
-
- if(!isliving(target))
- return
- var/mob/living/living_target = target
- if(living_target.pulledby != living_pawn && !HAS_AI_CONTROLLER_TYPE(living_target.pulledby, /datum/ai_controller/basic_controller/faithless)) //Dont steal from my fellow faithless.
- if(living_pawn.Adjacent(living_target) && isturf(living_target.loc) && living_target.stat == SOFT_CRIT)
- living_target.grabbedby(living_pawn) //Drag their bodies around as a menace.
- if(prob(paralyze_chance) && iscarbon(target))
- var/mob/living/carbon/carbon_target = target
- carbon_target.Paralyze(paralyze_duration)
- carbon_target.visible_message(span_danger("\The [living_pawn] knocks down \the [carbon_target]!"), \
- span_userdanger("\The [living_pawn] knocks you down!"))
diff --git a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm
index 9b57eba8fe233e..d9dfc3c5343286 100644
--- a/code/modules/mob/living/basic/space_fauna/garden_gnome.dm
+++ b/code/modules/mob/living/basic/space_fauna/garden_gnome.dm
@@ -17,6 +17,7 @@
attack_verb_continuous = "punches"
attack_verb_simple = "punch"
attack_sound = 'sound/weapons/punch1.ogg'
+ melee_attack_cooldown = 1.2 SECONDS
damage_coeff = list(BRUTE = 1, BURN = 1, TOX = 1, CLONE = 1, STAMINA = 0, OXY = 1)
speak_emote = list("announces")
@@ -132,12 +133,6 @@
planning_subtrees = list(
/datum/ai_planning_subtree/target_retaliate,
/datum/ai_planning_subtree/attack_obstacle_in_path,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/garden_gnome,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/random_speech/garden_gnome,
)
-
-/datum/ai_planning_subtree/basic_melee_attack_subtree/garden_gnome
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/garden_gnome
-
-/datum/ai_behavior/basic_melee_attack/garden_gnome
- action_cooldown = 1.2 SECONDS
diff --git a/code/modules/mob/living/basic/space_fauna/headslug.dm b/code/modules/mob/living/basic/space_fauna/headslug.dm
index b0fba4fadc1534..a417b6c13942b8 100644
--- a/code/modules/mob/living/basic/space_fauna/headslug.dm
+++ b/code/modules/mob/living/basic/space_fauna/headslug.dm
@@ -18,6 +18,7 @@
attack_verb_simple = "chomp"
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
+ mob_biotypes = MOB_ORGANIC|MOB_SPECIAL
faction = list(FACTION_CREATURE)
obj_damage = 0
environment_smash = ENVIRONMENT_SMASH_NONE
@@ -39,10 +40,11 @@
/mob/living/basic/headslug/examine(mob/user)
. = ..()
- if(isnull(client))
- . += span_notice("It appears to be moving around listlessly.")
- else
- . += span_warning("It's moving around intelligently!")
+ if(stat != DEAD)
+ if(isnull(client))
+ . += span_notice("It appears to be moving around listlessly.")
+ else
+ . += span_warning("It's moving around intelligently!")
if (egg_lain)
. += span_notice("Its reproductive equipment appears to have withered.")
diff --git a/code/modules/mob/living/basic/space_fauna/lightgeist.dm b/code/modules/mob/living/basic/space_fauna/lightgeist.dm
index 28debb6c0a1301..c70588f4502b45 100644
--- a/code/modules/mob/living/basic/space_fauna/lightgeist.dm
+++ b/code/modules/mob/living/basic/space_fauna/lightgeist.dm
@@ -21,6 +21,7 @@
health = 2
melee_damage_lower = 5
melee_damage_upper = 5
+ melee_attack_cooldown = 5 SECONDS
friendly_verb_continuous = "taps"
friendly_verb_simple = "tap"
density = FALSE
@@ -65,10 +66,10 @@
complete_text = "%TARGET%'s wounds mend together.",\
)
-/mob/living/basic/lightgeist/melee_attack(atom/target, list/modifiers)
- if (isliving(target))
+/mob/living/basic/lightgeist/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
+ . = ..()
+ if (. && isliving(target))
faction |= REF(target) // Anyone we heal will treat us as a friend
- return ..()
/mob/living/basic/lightgeist/ghost()
. = ..()
@@ -86,7 +87,7 @@
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/lightgeist, // We heal things by attacking them
+ /datum/ai_planning_subtree/basic_melee_attack_subtree, // We heal things by attacking them
)
/// Attack only mobs who have damage that we can heal, I think this is specific enough not to be a generic type
@@ -111,9 +112,3 @@
continue
return TRUE
return FALSE
-
-/datum/ai_planning_subtree/basic_melee_attack_subtree/lightgeist
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/lightgeist
-
-/datum/ai_behavior/basic_melee_attack/lightgeist
- action_cooldown = 5 SECONDS
diff --git a/code/modules/mob/living/basic/space_fauna/mushroom.dm b/code/modules/mob/living/basic/space_fauna/mushroom.dm
index 5e52dabae7511f..e6d47e2db5cc9e 100644
--- a/code/modules/mob/living/basic/space_fauna/mushroom.dm
+++ b/code/modules/mob/living/basic/space_fauna/mushroom.dm
@@ -64,7 +64,7 @@
idle_behavior = /datum/idle_behavior/idle_random_walk
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/mushroom,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/mushroom_food,
)
@@ -76,13 +76,6 @@
/datum/targetting_datum/basic/mushroom/faction_check(mob/living/living_mob, mob/living/the_target)
return !living_mob.faction_check_mob(the_target, exact_match = check_factions_exactly)
-
-/datum/ai_planning_subtree/basic_melee_attack_subtree/mushroom
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/mushroom
-
-/datum/ai_behavior/basic_melee_attack/mushroom
- action_cooldown = 2 SECONDS
-
/datum/ai_planning_subtree/find_and_hunt_target/mushroom_food
target_key = BB_LOW_PRIORITY_HUNTING_TARGET
hunting_behavior = /datum/ai_behavior/hunt_target/unarmed_attack_target/mushroom_food
diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm
index 35d597e53ca9ae..5a7bb075f19c9d 100644
--- a/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm
+++ b/code/modules/mob/living/basic/space_fauna/netherworld/blankbody.dm
@@ -14,6 +14,7 @@
attack_verb_simple = "punch"
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
+ melee_attack_cooldown = 1 SECONDS
faction = list(FACTION_NETHER)
speak_emote = list("screams")
death_message = "falls apart into a fine dust."
@@ -42,5 +43,5 @@
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/average_speed,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm
index 5fabbf2afb2e42..b38ada0f6e14a4 100644
--- a/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm
+++ b/code/modules/mob/living/basic/space_fauna/netherworld/creature.dm
@@ -15,6 +15,7 @@
gold_core_spawnable = HOSTILE_SPAWN
attack_sound = 'sound/weapons/bite.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
+ melee_attack_cooldown = 1 SECONDS
faction = list(FACTION_NETHER)
speak_emote = list("screams")
death_message = "gets his head split open."
@@ -111,5 +112,5 @@
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/average_speed,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
diff --git a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm
index 57d90da264ab09..18dca95013e47f 100644
--- a/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm
+++ b/code/modules/mob/living/basic/space_fauna/netherworld/migo.dm
@@ -12,6 +12,7 @@
speed = 1
attack_verb_continuous = "lacerates"
attack_verb_simple = "lacerate"
+ melee_attack_cooldown = 1 SECONDS
gold_core_spawnable = HOSTILE_SPAWN
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
@@ -81,5 +82,5 @@
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/average_speed,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
diff --git a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm
index e423b5ff852d0f..7a30f88b4c27b4 100644
--- a/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm
+++ b/code/modules/mob/living/basic/space_fauna/regal_rat/regal_rat_actions.dm
@@ -161,6 +161,7 @@
nearby_roach.melee_damage_upper += 4
nearby_roach.obj_damage += 5
nearby_roach.ai_controller = new /datum/ai_controller/basic_controller/cockroach/sewer(nearby_roach)
+ nearby_roach.melee_attack_cooldown = 0.8 SECONDS
nearby_roach.icon_state += "_sewer"
nearby_roach.maxHealth += 1
@@ -232,7 +233,7 @@
if (istype(victim) && !(FACTION_RAT in victim.faction))
to_chat(victim, span_userdanger("With this last sip, you feel your body convulsing horribly from the contents you've ingested. As you contemplate your actions, you sense an awakened kinship with rat-kind and their newly risen leader!"))
victim.faction |= FACTION_RAT
- victim.vomit()
+ victim.vomit(VOMIT_CATEGORY_DEFAULT)
metabolization_rate = 10 * REAGENTS_METABOLISM
/datum/reagent/rat_spit/on_mob_life(mob/living/carbon/C)
@@ -243,7 +244,7 @@
to_chat(C, span_warning("That food does not sit up well!"))
C.adjust_disgust(5)
else if(prob(5))
- C.vomit()
+ C.vomit(VOMIT_CATEGORY_DEFAULT)
return ..()
/datum/pet_command/protect_owner/glockroach
diff --git a/code/modules/mob/living/basic/space_fauna/statue/statue.dm b/code/modules/mob/living/basic/space_fauna/statue/statue.dm
index 7d13ee9a1abff8..bce35146ecfc69 100644
--- a/code/modules/mob/living/basic/space_fauna/statue/statue.dm
+++ b/code/modules/mob/living/basic/space_fauna/statue/statue.dm
@@ -27,6 +27,7 @@
attack_verb_simple = "claw"
attack_sound = 'sound/hallucinations/growl1.ogg'
attack_vis_effect = ATTACK_EFFECT_CLAW
+ melee_attack_cooldown = 1 SECONDS
faction = list(FACTION_STATUE)
speak_emote = list("screams")
@@ -147,16 +148,10 @@
ai_movement = /datum/ai_movement/basic_avoidance
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/statue,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/look_for_light_fixtures,
)
-/datum/ai_planning_subtree/basic_melee_attack_subtree/statue
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/statue
-
-/datum/ai_behavior/basic_melee_attack/statue
- action_cooldown = 1 SECONDS
-
/mob/living/basic/statue/frosty
name = "Frosty"
desc = "Just a snowman. Just a snowman. Oh god, it's just a snowman."
diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm
index f632a38dc1b20c..9d3a09c5348f2c 100644
--- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm
+++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_ai.dm
@@ -1,5 +1,3 @@
-#define WUMBO_ATTACK_COOLDOWN 2.5 SECONDS
-
/// Cowardly when small, aggressive when big. Tries to transform whenever possible.
/datum/ai_controller/basic_controller/wumborian_fugu
blackboard = list(
@@ -15,23 +13,15 @@
/datum/ai_planning_subtree/targeted_mob_ability/inflate,
/datum/ai_planning_subtree/flee_target,
/datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/wumborian_fugu,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
-/datum/ai_planning_subtree/basic_melee_attack_subtree/wumborian_fugu
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/wumborian_fugu
-
-/datum/ai_behavior/basic_melee_attack/wumborian_fugu
- action_cooldown = WUMBO_ATTACK_COOLDOWN
-
/datum/ai_planning_subtree/attack_obstacle_in_path/wumborian_fugu
attack_behaviour = /datum/ai_behavior/attack_obstructions/wumborian_fugu
/datum/ai_behavior/attack_obstructions/wumborian_fugu
can_attack_turfs = TRUE
- action_cooldown = WUMBO_ATTACK_COOLDOWN
+ action_cooldown = 2.5 SECONDS
/datum/ai_planning_subtree/targeted_mob_ability/inflate
ability_key = BB_FUGU_INFLATE
-
-#undef WUMBO_ATTACK_COOLDOWN
diff --git a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm
index e13bef354823ea..bf8be2051d8c14 100644
--- a/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm
+++ b/code/modules/mob/living/basic/space_fauna/wumborian_fugu/wumborian_fugu.dm
@@ -29,6 +29,7 @@
melee_damage_upper = 0
attack_sound = 'sound/weapons/punch1.ogg'
attack_vis_effect = ATTACK_EFFECT_BITE
+ melee_attack_cooldown = 2.5 SECONDS
attack_verb_continuous = "chomps"
attack_verb_simple = "chomp"
friendly_verb_continuous = "floats near"
diff --git a/code/modules/mob/living/basic/syndicate/syndicate.dm b/code/modules/mob/living/basic/syndicate/syndicate.dm
index c11d592cd3b544..a4fd0981198de9 100644
--- a/code/modules/mob/living/basic/syndicate/syndicate.dm
+++ b/code/modules/mob/living/basic/syndicate/syndicate.dm
@@ -15,6 +15,7 @@
attack_verb_continuous = "punches"
attack_verb_simple = "punch"
attack_sound = 'sound/weapons/punch1.ogg'
+ melee_attack_cooldown = 1.2 SECONDS
combat_mode = TRUE
unsuitable_atmos_damage = 7.5
unsuitable_cold_damage = 7.5
diff --git a/code/modules/mob/living/basic/syndicate/syndicate_ai.dm b/code/modules/mob/living/basic/syndicate/syndicate_ai.dm
index be24b37441a8c3..393ef19287e1f4 100644
--- a/code/modules/mob/living/basic/syndicate/syndicate_ai.dm
+++ b/code/modules/mob/living/basic/syndicate/syndicate_ai.dm
@@ -8,18 +8,12 @@
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
/datum/ai_planning_subtree/attack_obstacle_in_path/syndicate,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/syndicate
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
/datum/targetting_datum/basic/syndicate
stat_attack = HARD_CRIT
-/datum/ai_planning_subtree/basic_melee_attack_subtree/syndicate
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/syndicate
-
-/datum/ai_behavior/basic_melee_attack/syndicate
- action_cooldown = 1.2 SECONDS
-
/datum/ai_planning_subtree/attack_obstacle_in_path/syndicate
attack_behaviour = /datum/ai_behavior/attack_obstructions/syndicate
diff --git a/code/modules/mob/living/basic/tree.dm b/code/modules/mob/living/basic/tree.dm
index f93ab14a37f4ce..2a0806b105c983 100644
--- a/code/modules/mob/living/basic/tree.dm
+++ b/code/modules/mob/living/basic/tree.dm
@@ -74,7 +74,7 @@
our_turf.air.gases[/datum/gas/carbon_dioxide][MOLES] -= amt
our_turf.atmos_spawn_air("[GAS_O2]=[amt]")
-/mob/living/basic/tree/melee_attack(atom/target, list/modifiers)
+/mob/living/basic/tree/melee_attack(atom/target, list/modifiers, ignore_cooldown = FALSE)
. = ..()
if(!.)
@@ -107,12 +107,6 @@
idle_behavior = /datum/idle_behavior/idle_random_walk/less_walking
planning_subtrees = list(
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/tree,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/random_speech/tree,
)
-
-/datum/ai_planning_subtree/basic_melee_attack_subtree/tree
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/tree
-
-/datum/ai_behavior/basic_melee_attack/tree
- action_cooldown = 2 SECONDS
diff --git a/code/modules/mob/living/basic/vermin/cockroach.dm b/code/modules/mob/living/basic/vermin/cockroach.dm
index 7aff4a844d3da2..5c69ad904474f0 100644
--- a/code/modules/mob/living/basic/vermin/cockroach.dm
+++ b/code/modules/mob/living/basic/vermin/cockroach.dm
@@ -131,6 +131,7 @@
melee_damage_lower = 2.5
melee_damage_upper = 10
obj_damage = 10
+ melee_attack_cooldown = 1 SECONDS
gold_core_spawnable = HOSTILE_SPAWN
attack_sound = 'sound/weapons/bladeslice.ogg'
attack_vis_effect = ATTACK_EFFECT_SLASH
@@ -165,31 +166,19 @@
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/random_speech/insect,
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/hauberoach, //If we are attacking someone, this will prevent us from hunting
+ /datum/ai_planning_subtree/basic_melee_attack_subtree, //If we are attacking someone, this will prevent us from hunting
/datum/ai_planning_subtree/find_and_hunt_target/roach,
)
-/datum/ai_planning_subtree/basic_melee_attack_subtree/hauberoach
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/hauberoach
-
-/datum/ai_behavior/basic_melee_attack/hauberoach //Slightly slower, as this is being made in feature freeze ;)
- action_cooldown = 1 SECONDS
-
/datum/ai_controller/basic_controller/cockroach/sewer
planning_subtrees = list(
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/random_speech/insect,
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/sewer,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
/datum/ai_planning_subtree/find_and_hunt_target/roach,
)
-/datum/ai_planning_subtree/basic_melee_attack_subtree/sewer
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/sewer
-
-/datum/ai_behavior/basic_melee_attack/sewer
- action_cooldown = 0.8 SECONDS
-
/mob/living/basic/cockroach/glockroach/mobroach
name = "mobroach"
desc = "WE'RE FUCKED, THAT GLOCKROACH HAS A TOMMYGUN!"
diff --git a/code/modules/mob/living/basic/vermin/frog.dm b/code/modules/mob/living/basic/vermin/frog.dm
index 282ed17b00c261..191ea12b4df33c 100644
--- a/code/modules/mob/living/basic/vermin/frog.dm
+++ b/code/modules/mob/living/basic/vermin/frog.dm
@@ -17,6 +17,7 @@
obj_damage = 10
attack_verb_continuous = "bites"
attack_verb_simple = "bite"
+ melee_attack_cooldown = 2.5 SECONDS
response_help_continuous = "pets"
response_help_simple = "pet"
response_disarm_continuous = "pokes"
@@ -85,19 +86,13 @@
planning_subtrees = list(
/datum/ai_planning_subtree/target_retaliate,
/datum/ai_planning_subtree/random_speech/frog,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/frog,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
-/datum/ai_planning_subtree/basic_melee_attack_subtree/frog
- melee_attack_behavior = /datum/ai_behavior/basic_melee_attack/frog
-
-/datum/ai_behavior/basic_melee_attack/frog
- action_cooldown = 2.5 SECONDS
-
/datum/ai_controller/basic_controller/frog/trash
planning_subtrees = list(
/datum/ai_planning_subtree/pet_planning,
/datum/ai_planning_subtree/random_speech/frog,
/datum/ai_planning_subtree/simple_find_target,
- /datum/ai_planning_subtree/basic_melee_attack_subtree/frog,
+ /datum/ai_planning_subtree/basic_melee_attack_subtree,
)
diff --git a/code/modules/mob/living/basic/vermin/mouse.dm b/code/modules/mob/living/basic/vermin/mouse.dm
index 1f2a5c7e41e3f3..0b3645b3e0f639 100644
--- a/code/modules/mob/living/basic/vermin/mouse.dm
+++ b/code/modules/mob/living/basic/vermin/mouse.dm
@@ -57,6 +57,7 @@
)
AddElement(/datum/element/connect_loc, loc_connections)
make_tameable()
+ AddComponent(/datum/component/swarming, 16, 16) //max_x, max_y
/mob/living/basic/mouse/proc/make_tameable()
if (tame)
@@ -116,7 +117,8 @@
. = ..(TRUE)
// Now if we were't ACTUALLY gibbed, spawn the dead mouse
if(!gibbed)
- var/obj/item/food/deadmouse/mouse = new(loc, /* starting_reagent_purity = */ null, /* no_base_reagents = */ FALSE, /* dead_critter = */ src)
+ var/obj/item/food/deadmouse/mouse = new(loc)
+ mouse.copy_corpse(src)
if(HAS_TRAIT(src, TRAIT_BEING_SHOCKED))
mouse.desc = "They're toast."
mouse.add_atom_colour("#3A3A3A", FIXED_COLOUR_PRIORITY)
@@ -301,16 +303,18 @@
var/body_color = "gray"
var/critter_type = /mob/living/basic/mouse
-/obj/item/food/deadmouse/Initialize(mapload, starting_reagent_purity, no_base_reagents, mob/living/basic/mouse/dead_critter)
+/obj/item/food/deadmouse/Initialize(mapload)
. = ..()
- if(dead_critter)
- body_color = dead_critter.body_color
- critter_type = dead_critter.type
- name = dead_critter.name
- icon_state = dead_critter.icon_dead
AddElement(/datum/element/swabable, CELL_LINE_TABLE_MOUSE, CELL_VIRUS_TABLE_GENERIC_MOB, 1, 10)
RegisterSignal(src, COMSIG_ATOM_ON_LAZARUS_INJECTOR, PROC_REF(use_lazarus))
+/// Copy properties from an imminently dead mouse
+/obj/item/food/deadmouse/proc/copy_corpse(mob/living/basic/mouse/dead_critter)
+ body_color = dead_critter.body_color
+ critter_type = dead_critter.type
+ name = dead_critter.name
+ icon_state = dead_critter.icon_dead
+
/obj/item/food/deadmouse/examine(mob/user)
. = ..()
if (reagents?.has_reagent(/datum/reagent/yuck) || reagents?.has_reagent(/datum/reagent/fuel))
diff --git a/code/modules/mob/living/blood.dm b/code/modules/mob/living/blood.dm
index 9608db4d4c613a..9354f02e2b2df9 100644
--- a/code/modules/mob/living/blood.dm
+++ b/code/modules/mob/living/blood.dm
@@ -88,7 +88,7 @@
//Makes a blood drop, leaking amt units of blood from the mob
/mob/living/carbon/proc/bleed(amt)
- if(!blood_volume)
+ if(!blood_volume || (status_flags & GODMODE))
return
blood_volume = max(blood_volume - amt, 0)
diff --git a/code/modules/mob/living/carbon/alien/organs.dm b/code/modules/mob/living/carbon/alien/organs.dm
index 00c8a617c64fe4..5ef9b23c418212 100644
--- a/code/modules/mob/living/carbon/alien/organs.dm
+++ b/code/modules/mob/living/carbon/alien/organs.dm
@@ -51,21 +51,24 @@
actions_types = list(/datum/action/cooldown/alien/transfer)
/obj/item/organ/internal/alien/plasmavessel/on_life(seconds_per_tick, times_fired)
+ var/delta_time = DELTA_WORLD_TIME(SSmobs)
+ //Instantly healing to max health in a single tick would be silly. If it takes 8 seconds to fire, then something's fucked.
+ var/delta_time_capped = min(delta_time, 8)
//If there are alien weeds on the ground then heal if needed or give some plasma
if(locate(/obj/structure/alien/weeds) in owner.loc)
if(owner.health >= owner.maxHealth)
- owner.adjustPlasma(plasma_rate * seconds_per_tick)
+ owner.adjustPlasma(plasma_rate * delta_time)
else
var/heal_amt = heal_rate
if(!isalien(owner))
heal_amt *= 0.2
- owner.adjustPlasma(0.5 * plasma_rate * seconds_per_tick)
- owner.adjustBruteLoss(-heal_amt * seconds_per_tick)
- owner.adjustFireLoss(-heal_amt * seconds_per_tick)
- owner.adjustOxyLoss(-heal_amt * seconds_per_tick)
- owner.adjustCloneLoss(-heal_amt * seconds_per_tick)
+ owner.adjustPlasma(0.5 * plasma_rate * delta_time_capped)
+ owner.adjustBruteLoss(-heal_amt * delta_time_capped)
+ owner.adjustFireLoss(-heal_amt * delta_time_capped)
+ owner.adjustOxyLoss(-heal_amt * delta_time_capped)
+ owner.adjustCloneLoss(-heal_amt * delta_time_capped)
else
- owner.adjustPlasma(0.1 * plasma_rate * seconds_per_tick)
+ owner.adjustPlasma(0.1 * plasma_rate * delta_time)
/obj/item/organ/internal/alien/plasmavessel/on_insert(mob/living/carbon/organ_owner)
. = ..()
diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm
index 36399dbc71d4ff..b0a2f8018cc056 100644
--- a/code/modules/mob/living/carbon/carbon.dm
+++ b/code/modules/mob/living/carbon/carbon.dm
@@ -416,53 +416,76 @@
return 0
return ..()
-/mob/living/carbon/proc/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, vomit_type = VOMIT_TOXIC, harm = TRUE, force = FALSE, purge_ratio = 0.1)
+/// Proc that compels the mob to throw up. Returns TRUE if the mob actually threw up.
+/mob/living/carbon/proc/vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, vomit_type = /obj/effect/decal/cleanable/vomit/toxic, lost_nutrition = 10, distance = 1, purge_ratio = 0.1)
+ var/force = (vomit_flags & MOB_VOMIT_FORCE)
if((HAS_TRAIT(src, TRAIT_NOHUNGER) || HAS_TRAIT(src, TRAIT_TOXINLOVER)) && !force)
return TRUE
SEND_SIGNAL(src, COMSIG_CARBON_VOMITED, distance, force)
+
+ // cache some stuff that we'll need later (at least multiple times)
var/starting_dir = dir
- if(nutrition < 100 && !blood && !force)
+ var/message = (vomit_flags & MOB_VOMIT_MESSAGE)
+ var/stun = (vomit_flags & MOB_VOMIT_STUN)
+ var/knockdown = (vomit_flags & MOB_VOMIT_KNOCKDOWN)
+ var/blood = (vomit_flags & MOB_VOMIT_BLOOD)
+
+ if(!force && !blood && (nutrition < 100))
if(message)
- visible_message(span_warning("[src] dry heaves!"), \
- span_userdanger("You try to throw up, but there's nothing in your stomach!"))
+ visible_message(
+ span_warning("[src] dry heaves!"),
+ span_userdanger("You try to throw up, but there's nothing in your stomach!"),
+ )
if(stun)
Stun(20 SECONDS)
+ if(knockdown)
+ Knockdown(20 SECONDS)
return TRUE
if(is_mouth_covered()) //make this add a blood/vomit overlay later it'll be hilarious
if(message)
- visible_message(span_danger("[src] throws up all over [p_them()]self!"), \
- span_userdanger("You throw up all over yourself!"))
+ visible_message(
+ span_danger("[src] throws up all over [p_them()]self!"),
+ span_userdanger("You throw up all over yourself!"),
+ )
add_mood_event("vomit", /datum/mood_event/vomitself)
distance = 0
else
if(message)
- visible_message(span_danger("[src] throws up!"), span_userdanger("You throw up!"))
+ visible_message(
+ span_danger("[src] throws up!"),
+ span_userdanger("You throw up!"),
+ )
if(!isflyperson(src))
add_mood_event("vomit", /datum/mood_event/vomit)
if(stun)
Stun(8 SECONDS)
+ if(knockdown)
+ Knockdown(8 SECONDS)
playsound(get_turf(src), 'sound/effects/splat.ogg', 50, TRUE)
- var/turf/T = get_turf(src)
+
+ var/turf/location = get_turf(src)
if(!blood)
adjust_nutrition(-lost_nutrition)
adjustToxLoss(-3)
- for(var/i=0 to distance)
+ for(var/i = 0 to distance)
if(blood)
- if(T)
- add_splatter_floor(T)
- if(harm)
+ if(location)
+ add_splatter_floor(location)
+ if(vomit_flags & MOB_VOMIT_HARM)
adjustBruteLoss(3)
else
- if(T)
- T.add_vomit_floor(src, vomit_type, purge_ratio) //toxic barf looks different || call purge when doing detoxicfication to pump more chems out of the stomach.
- T = get_step(T, starting_dir)
- if (T?.is_blocked_turf())
+ if(location)
+ location.add_vomit_floor(src, vomit_type, vomit_flags, purge_ratio) // call purge when doing detoxicfication to pump more chems out of the stomach.
+
+ location = get_step(location, starting_dir)
+ if (location?.is_blocked_turf())
break
+
return TRUE
/**
@@ -1180,7 +1203,7 @@
/mob/living/carbon/proc/hypnosis_vulnerable()
if(HAS_TRAIT(src, TRAIT_MINDSHIELD))
return FALSE
- if(has_status_effect(/datum/status_effect/hallucination))
+ if(has_status_effect(/datum/status_effect/hallucination) || has_status_effect(/datum/status_effect/drugginess))
return TRUE
if(IsSleeping() || IsUnconscious())
return TRUE
diff --git a/code/modules/mob/living/carbon/carbon_defense.dm b/code/modules/mob/living/carbon/carbon_defense.dm
index 3ab34e70230295..8a704a277b7dcc 100644
--- a/code/modules/mob/living/carbon/carbon_defense.dm
+++ b/code/modules/mob/living/carbon/carbon_defense.dm
@@ -31,7 +31,7 @@
/mob/living/carbon/is_mouth_covered(check_flags = ALL)
if((check_flags & ITEM_SLOT_HEAD) && head && (head.flags_cover & HEADCOVERSMOUTH))
return head
- if((check_flags & ITEM_SLOT_MASK) && wear_mask && (wear_mask.flags_cover & HEADCOVERSMOUTH))
+ if((check_flags & ITEM_SLOT_MASK) && wear_mask && (wear_mask.flags_cover & MASKCOVERSMOUTH))
return wear_mask
return null
@@ -102,7 +102,7 @@
if(I.force)
var/attack_direction = get_dir(user, src)
apply_damage(I.force, I.damtype, affecting, wound_bonus = I.wound_bonus, bare_wound_bonus = I.bare_wound_bonus, sharpness = I.get_sharpness(), attack_direction = attack_direction, attacking_item = I)
- if(I.damtype == BRUTE && IS_ORGANIC_LIMB(affecting))
+ if(I.damtype == BRUTE && affecting.can_bleed())
if(prob(33))
I.add_mob_blood(src)
var/turf/location = get_turf(src)
@@ -140,22 +140,35 @@
//SKYRAT EDIT ADDITION END
var/extra_wound_details = ""
+
if(I.damtype == BRUTE && hit_bodypart.can_dismember())
+
var/mangled_state = hit_bodypart.get_mangled_state()
- var/bio_state = hit_bodypart.biological_state
- if((mangled_state & BODYPART_MANGLED_FLESH) && (mangled_state & BODYPART_MANGLED_BONE))
+
+ var/bio_status = hit_bodypart.get_bio_state_status()
+
+ var/has_exterior = ((bio_status & ANATOMY_EXTERIOR))
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
+
+ var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR)))
+ var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR)))
+
+ var/dismemberable = ((hit_bodypart.dismemberable_by_wound()) || hit_bodypart.dismemberable_by_total_damage())
+ if (dismemberable)
extra_wound_details = ", threatening to sever it entirely"
- else if((mangled_state & BODYPART_MANGLED_FLESH && I.get_sharpness()) || ((mangled_state & BODYPART_MANGLED_BONE) && (bio_state & BIO_BONE) && !(bio_state & BIO_FLESH)))
- extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the bone"
- else if((mangled_state & BODYPART_MANGLED_BONE && I.get_sharpness()) || ((mangled_state & BODYPART_MANGLED_FLESH) && (bio_state & BIO_FLESH) && !(bio_state & BIO_BONE)))
- extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining tissue"
+ else if((has_interior && (has_exterior && exterior_ready_to_dismember) && I.get_sharpness()))
+ var/bone_text = hit_bodypart.get_internal_description()
+ extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] through to the [bone_text]"
+ else if(has_exterior && ((has_interior && interior_ready_to_dismember) && I.get_sharpness()))
+ var/tissue_text = hit_bodypart.get_external_description()
+ extra_wound_details = ", [I.get_sharpness() == SHARP_EDGED ? "slicing" : "piercing"] at the remaining [tissue_text]"
var/message_hit_area = ""
if(hit_area)
message_hit_area = " in the [hit_area]"
var/attack_message_spectator = "[src] [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!"
var/attack_message_victim = "You're [message_verb_continuous][message_hit_area] with [I][extra_wound_details]!"
- var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I]!"
+ var/attack_message_attacker = "You [message_verb_simple] [src][message_hit_area] with [I][extra_wound_details]!"
if(user in viewers(src, null))
attack_message_spectator = "[user] [message_verb_continuous] [src][message_hit_area] with [I][extra_wound_details]!"
attack_message_victim = "[user] [message_verb_continuous] you[message_hit_area] with [I][extra_wound_details]!"
@@ -776,14 +789,16 @@
var/obj/item/bodypart/grasped_part = get_bodypart(zone_selected)
/*
- if(!grasped_part?.get_modified_bleed_rate())
+ if(!grasped_part?.can_be_grasped())
return
var/starting_hand_index = active_hand_index
if(starting_hand_index == grasped_part.held_index)
to_chat(src, span_danger("You can't grasp your [grasped_part.name] with itself!"))
return
- to_chat(src, span_warning("You try grasping at your [grasped_part.name], trying to stop the bleeding..."))
+ var/bleed_rate = grasped_part.get_modified_bleed_rate()
+ var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "")
+ to_chat(src, span_warning("You try grasping at your [grasped_part.name][bleeding_text]..."))
if(!do_after(src, 0.75 SECONDS))
to_chat(src, span_danger("You fail to grasp your [grasped_part.name]."))
return
@@ -797,6 +812,17 @@
*/ // SKYRAT EDIT REMOVAL - MODULARIZED INTO grasp.dm's self_grasp_bleeding_limb !! IF THIS PROC IS UPDATED, PUT IT IN THERE !!
self_grasp_bleeding_limb(grasped_part, supress_message)
+/// If TRUE, the owner of this bodypart can try grabbing it to slow bleeding, as well as various other effects.
+/obj/item/bodypart/proc/can_be_grasped()
+ if (get_modified_bleed_rate())
+ return TRUE
+
+ for (var/datum/wound/iterated_wound as anything in wounds)
+ if (iterated_wound.wound_flags & CAN_BE_GRASPED)
+ return TRUE
+
+ return FALSE
+
/// an abstract item representing you holding your own limb to staunch the bleeding, see [/mob/living/carbon/proc/grabbedby] will probably need to find somewhere else to put this.
/obj/item/hand_item/self_grasp
name = "self-grasp"
@@ -841,7 +867,9 @@
RegisterSignal(user, COMSIG_QDELETING, PROC_REF(qdel_void))
RegisterSignals(grasped_part, list(COMSIG_CARBON_REMOVE_LIMB, COMSIG_QDELETING), PROC_REF(qdel_void))
- user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name], trying to stop the bleeding."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE)
+ var/bleed_rate = grasped_part.get_modified_bleed_rate()
+ var/bleeding_text = (bleed_rate ? ", trying to stop the bleeding" : "")
+ user.visible_message(span_danger("[user] grasps at [user.p_their()] [grasped_part.name][bleeding_text]."), span_notice("You grab hold of your [grasped_part.name] tightly."), vision_distance=COMBAT_MESSAGE_RANGE)
playsound(get_turf(src), 'sound/weapons/thudswoosh.ogg', 50, TRUE, -1)
return TRUE
diff --git a/code/modules/mob/living/carbon/carbon_defines.dm b/code/modules/mob/living/carbon/carbon_defines.dm
index ec44dcec13eca0..bcf5f495715a48 100644
--- a/code/modules/mob/living/carbon/carbon_defines.dm
+++ b/code/modules/mob/living/carbon/carbon_defines.dm
@@ -104,7 +104,7 @@
/// All of the scars a carbon has afflicted throughout their limbs
var/list/all_scars
- /// Assoc list of BODY_ZONE -> WOUND_TYPE. Set when a limb is dismembered, unset when one is attached. Used for determining what scar to add when it comes time to generate them.
+ /// Assoc list of BODY_ZONE -> wounding_type. Set when a limb is dismembered, unset when one is attached. Used for determining what scar to add when it comes time to generate them.
var/list/body_zone_dismembered_by
/// Simple modifier for whether this mob can handle greater or lesser skillchip complexity. See /datum/mutation/human/biotechcompat/ for example.
diff --git a/code/modules/mob/living/carbon/damage_procs.dm b/code/modules/mob/living/carbon/damage_procs.dm
index 98827ae3ce3d33..01551ac0743b9f 100644
--- a/code/modules/mob/living/carbon/damage_procs.dm
+++ b/code/modules/mob/living/carbon/damage_procs.dm
@@ -1,4 +1,4 @@
-/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
+/mob/living/carbon/apply_damage(damage, damagetype = BRUTE, def_zone = null, blocked = 0, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone)
var/hit_percent = (100-blocked)/100
if(!damage || (!forced && hit_percent <= 0))
diff --git a/code/modules/mob/living/carbon/human/_species.dm b/code/modules/mob/living/carbon/human/_species.dm
index eb4bb54ea8098c..6e9f61cdb8c24d 100644
--- a/code/modules/mob/living/carbon/human/_species.dm
+++ b/code/modules/mob/living/carbon/human/_species.dm
@@ -511,6 +511,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
C.grant_language(language, SPOKEN_LANGUAGE, LANGUAGE_SPECIES)
for(var/language in gaining_holder.blocked_languages)
C.add_blocked_language(language, LANGUAGE_SPECIES)
+ C.regenerate_icons()
SEND_SIGNAL(C, COMSIG_SPECIES_GAIN, src, old_species)
@@ -1086,7 +1087,7 @@ GLOBAL_LIST_EMPTY(features_by_species)
to_chat(source, span_danger("You feel weak."))
if(time_since_irradiated > RAD_MOB_VOMIT && SPT_PROB(RAD_MOB_VOMIT_PROB, seconds_per_tick))
- source.vomit(10, TRUE)
+ source.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 10)
if(time_since_irradiated > RAD_MOB_MUTATE && SPT_PROB(RAD_MOB_MUTATE_PROB, seconds_per_tick))
to_chat(source, span_danger("You mutate!"))
@@ -1706,19 +1707,26 @@ GLOBAL_LIST_EMPTY(features_by_species)
// Lets pick a random body part and check for an existing burn
var/obj/item/bodypart/bodypart = pick(humi.bodyparts)
- var/datum/wound/burn/flesh/existing_burn = locate(/datum/wound/burn) in bodypart.wounds
-
+ var/datum/wound/existing_burn
+ for (var/datum/wound/iterated_wound as anything in bodypart.wounds)
+ var/datum/wound_pregen_data/pregen_data = iterated_wound.get_pregen_data()
+ if (pregen_data.wound_series in GLOB.wounding_types_to_series[WOUND_BURN])
+ existing_burn = iterated_wound
+ break
// If we have an existing burn try to upgrade it
+ var/severity
if(existing_burn)
switch(existing_burn.severity)
if(WOUND_SEVERITY_MODERATE)
if(humi.bodytemperature > BODYTEMP_HEAT_WOUND_LIMIT + 400) // 800k
- bodypart.force_wound_upwards(/datum/wound/burn/flesh/severe, wound_source = "hot temperatures")
+ severity = WOUND_SEVERITY_SEVERE
if(WOUND_SEVERITY_SEVERE)
if(humi.bodytemperature > BODYTEMP_HEAT_WOUND_LIMIT + 2800) // 3200k
- bodypart.force_wound_upwards(/datum/wound/burn/flesh/critical, wound_source = "hot temperatures")
+ severity = WOUND_SEVERITY_CRITICAL
else // If we have no burn apply the lowest level burn
- bodypart.force_wound_upwards(/datum/wound/burn/flesh/moderate, wound_source = "hot temperatures")
+ severity = WOUND_SEVERITY_MODERATE
+
+ humi.cause_wound_of_type_and_severity(WOUND_BURN, bodypart, severity, wound_source = "hot temperatures")
// always take some burn damage
var/burn_damage = HEAT_DAMAGE_LEVEL_1
diff --git a/code/modules/mob/living/carbon/human/damage_procs.dm b/code/modules/mob/living/carbon/human/damage_procs.dm
index 47cbbe12188e51..d4fc0b403656a3 100644
--- a/code/modules/mob/living/carbon/human/damage_procs.dm
+++ b/code/modules/mob/living/carbon/human/damage_procs.dm
@@ -1,4 +1,4 @@
/// depending on the species, it will run the corresponding apply_damage code there
-/mob/living/carbon/human/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
+/mob/living/carbon/human/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = 0, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
return dna.species.apply_damage(damage, damagetype, def_zone, blocked, src, forced, spread_damage, wound_bonus, bare_wound_bonus, sharpness, attack_direction, attacking_item)
diff --git a/code/modules/mob/living/carbon/human/examine.dm b/code/modules/mob/living/carbon/human/examine.dm
index 2601aed8731aa4..1fc148e7b640a2 100644
--- a/code/modules/mob/living/carbon/human/examine.dm
+++ b/code/modules/mob/living/carbon/human/examine.dm
@@ -514,10 +514,10 @@
//Temporary flavor text addition:
if(temporary_flavor_text)
- if(length_char(temporary_flavor_text) <= 40)
+ if(length_char(temporary_flavor_text) < TEMPORARY_FLAVOR_PREVIEW_LIMIT)
. += span_notice("They look different than usual: [temporary_flavor_text]")
else
- . += span_notice("They look different than usual: [copytext_char(temporary_flavor_text, 1, 37)]... More...")
+ . += span_notice("They look different than usual: [copytext_char(temporary_flavor_text, 1, TEMPORARY_FLAVOR_PREVIEW_LIMIT)]... More...")
. += ""
SEND_SIGNAL(src, COMSIG_ATOM_EXAMINE, user, .)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 427d9cf1003995..40e4bf095d507b 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -741,15 +741,21 @@
return ..()
-/mob/living/carbon/human/vomit(lost_nutrition = 10, blood = FALSE, stun = TRUE, distance = 1, message = TRUE, vomit_type = VOMIT_TOXIC, harm = TRUE, force = FALSE, purge_ratio = 0.1)
- if(blood && HAS_TRAIT(src, TRAIT_NOBLOOD) && !HAS_TRAIT(src, TRAIT_TOXINLOVER))
- if(message)
- visible_message(span_warning("[src] dry heaves!"), \
- span_userdanger("You try to throw up, but there's nothing in your stomach!"))
- if(stun)
- Stun(20 SECONDS)
- return 1
- ..()
+/mob/living/carbon/human/vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, vomit_type = /obj/effect/decal/cleanable/vomit/toxic, lost_nutrition = 10, distance = 1, purge_ratio = 0.1)
+ if(!((vomit_flags & MOB_VOMIT_BLOOD) && HAS_TRAIT(src, TRAIT_NOBLOOD) && !HAS_TRAIT(src, TRAIT_TOXINLOVER)))
+ return ..()
+
+ if(vomit_flags & MOB_VOMIT_MESSAGE)
+ visible_message(
+ span_warning("[src] dry heaves!"),
+ span_userdanger("You try to throw up, but there's nothing in your stomach!"),
+ )
+ if(vomit_flags & MOB_VOMIT_STUN)
+ Stun(20 SECONDS)
+ if(vomit_flags & MOB_VOMIT_KNOCKDOWN)
+ Knockdown(20 SECONDS)
+
+ return TRUE
/mob/living/carbon/human/vv_edit_var(var_name, var_value)
if(var_name == NAMEOF(src, mob_height))
diff --git a/code/modules/mob/living/carbon/human/life.dm b/code/modules/mob/living/carbon/human/life.dm
index c468d9b750c18b..c91fc7d6638de5 100644
--- a/code/modules/mob/living/carbon/human/life.dm
+++ b/code/modules/mob/living/carbon/human/life.dm
@@ -279,7 +279,7 @@
lastpuke += SPT_PROB(30, seconds_per_tick)
if(lastpuke >= 50) // about 25 second delay I guess // This is actually closer to 150 seconds
- vomit(20)
+ vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 20)
lastpuke = 0
diff --git a/code/modules/mob/living/carbon/human/species_types/ethereal.dm b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
index 9546a659e41da4..42c718477bce25 100644
--- a/code/modules/mob/living/carbon/human/species_types/ethereal.dm
+++ b/code/modules/mob/living/carbon/human/species_types/ethereal.dm
@@ -68,6 +68,7 @@
RegisterSignal(ethereal, COMSIG_ATOM_EMAG_ACT, PROC_REF(on_emag_act))
RegisterSignal(ethereal, COMSIG_ATOM_EMP_ACT, PROC_REF(on_emp_act))
RegisterSignal(ethereal, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater))
+ RegisterSignal(ethereal, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
ethereal_light = ethereal.mob_light(light_type = /obj/effect/dummy/lighting_obj/moblight/species)
spec_updatehealth(ethereal)
new_ethereal.set_safe_hunger_level()
@@ -84,6 +85,7 @@
UnregisterSignal(former_ethereal, COMSIG_ATOM_EMAG_ACT)
UnregisterSignal(former_ethereal, COMSIG_ATOM_EMP_ACT)
UnregisterSignal(former_ethereal, COMSIG_LIGHT_EATER_ACT)
+ UnregisterSignal(former_ethereal, COMSIG_HIT_BY_SABOTEUR)
QDEL_NULL(ethereal_light)
return ..()
@@ -147,6 +149,16 @@
if(EMP_HEAVY)
addtimer(CALLBACK(src, PROC_REF(stop_emp), H), 20 SECONDS, TIMER_UNIQUE|TIMER_OVERRIDE) //We're out for 20 seconds
+/datum/species/ethereal/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ var/mob/living/carbon/human/our_target = source
+ EMPeffect = TRUE
+ spec_updatehealth(our_target)
+ to_chat(our_target, span_warning("Something inside of you crackles in a bad way."))
+ our_target.take_bodypart_damage(burn = 3, wound_bonus = CANT_WOUND)
+ addtimer(CALLBACK(src, PROC_REF(stop_emp), our_target), disrupt_duration, TIMER_UNIQUE|TIMER_OVERRIDE)
+ return COMSIG_SABOTEUR_SUCCESS
+
/datum/species/ethereal/proc/on_emag_act(mob/living/carbon/human/H, mob/user)
SIGNAL_HANDLER
if(emageffect)
diff --git a/code/modules/mob/living/carbon/life.dm b/code/modules/mob/living/carbon/life.dm
index 45c441a13c2012..e66d72a2c44367 100644
--- a/code/modules/mob/living/carbon/life.dm
+++ b/code/modules/mob/living/carbon/life.dm
@@ -375,13 +375,13 @@
if(prob(5))
to_chat(src, span_warning("The stench of rotting carcasses is unbearable!"))
add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench)
- vomit()
+ vomit(VOMIT_CATEGORY_DEFAULT)
if(30 to INFINITY)
//Higher chance to vomit. Let the horror start
if(prob(25))
to_chat(src, span_warning("The stench of rotting carcasses is unbearable!"))
add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench)
- vomit()
+ vomit(VOMIT_CATEGORY_DEFAULT)
else
clear_mood_event("smell")
diff --git a/code/modules/mob/living/damage_procs.dm b/code/modules/mob/living/damage_procs.dm
index 439101151c61aa..786e781a58433c 100644
--- a/code/modules/mob/living/damage_procs.dm
+++ b/code/modules/mob/living/damage_procs.dm
@@ -14,7 +14,7 @@
*
* Returns TRUE if damage applied
*/
-/mob/living/proc/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
+/mob/living/proc/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = 0, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
SEND_SIGNAL(src, COMSIG_MOB_APPLY_DAMAGE, damage, damagetype, def_zone)
var/hit_percent = (100-blocked)/100
if(!damage || (!forced && hit_percent <= 0))
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 6c3f397f6bac9a..c157603ea8305e 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -131,6 +131,8 @@
for (var/law in laws.inherent)
lawcheck += law
+ create_eye()
+
if(target_ai.mind)
target_ai.mind.transfer_to(src)
if(mind.special_role)
@@ -152,8 +154,6 @@
job = "AI"
- create_eye()
-
create_modularInterface()
// /mob/living/silicon/ai/apply_prefs_job() uses these to set these procs at mapload
@@ -205,10 +205,6 @@
interaction_range = 1
sprint = 5
-/mob/living/silicon/ai/weak_syndie/Initialize(mapload, datum/ai_laws/L, mob/target_ai)
- . = ..()
- laws = new /datum/ai_laws/syndicate_override
-
/mob/living/silicon/ai/key_down(_key, client/user)
if(findtext(_key, "numpad")) //if it's a numpad number, we can convert it to just the number
_key = _key[7] //strings, lists, same thing really
@@ -866,24 +862,25 @@
/mob/living/silicon/ai/transfer_ai(interaction, mob/user, mob/living/silicon/ai/AI, obj/item/aicard/card)
if(!..())
return
- if(interaction == AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card.
- if(!can_be_carded)
- to_chat(user, span_boldwarning("Transfer failed."))
- return
- disconnect_shell() //If the AI is controlling a borg, force the player back to core!
- if(!mind)
- to_chat(user, span_warning("No intelligence patterns detected."))
- return
- ShutOffDoomsdayDevice()
- var/obj/structure/ai_core/new_core = new /obj/structure/ai_core/deactivated(loc, posibrain_inside)//Spawns a deactivated terminal at AI location.
- new_core.circuit.battery = battery
- ai_restore_power()//So the AI initially has power.
- control_disabled = TRUE //Can't control things remotely if you're stuck in a card!
- radio_enabled = FALSE //No talking on the built-in radio for you either!
- forceMove(card)
- card.AI = src
- to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.")
- to_chat(user, "[span_boldnotice("Transfer successful")]: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.")
+ if(interaction != AI_TRANS_TO_CARD)//The only possible interaction. Upload AI mob to a card.
+ return
+ if(!can_be_carded)
+ balloon_alert(user, "transfer failed!")
+ return
+ disconnect_shell() //If the AI is controlling a borg, force the player back to core!
+ if(!mind)
+ balloon_alert(user, "no intelligence detected!") // average tg coder am i right
+ return
+ ShutOffDoomsdayDevice()
+ var/obj/structure/ai_core/new_core = new /obj/structure/ai_core/deactivated(loc, posibrain_inside)//Spawns a deactivated terminal at AI location.
+ new_core.circuit.battery = battery
+ ai_restore_power()//So the AI initially has power.
+ control_disabled = TRUE //Can't control things remotely if you're stuck in a card!
+ radio_enabled = FALSE //No talking on the built-in radio for you either!
+ forceMove(card)
+ card.AI = src
+ to_chat(src, "You have been downloaded to a mobile storage device. Remote device connection severed.")
+ to_chat(user, "[span_boldnotice("Transfer successful")]: [name] ([rand(1000,9999)].exe) removed from host terminal and stored within local memory.")
/mob/living/silicon/ai/can_perform_action(atom/movable/target, action_bitflags)
if(control_disabled)
diff --git a/code/modules/mob/living/silicon/ai/multicam.dm b/code/modules/mob/living/silicon/ai/multicam.dm
index e5bcd813b7e071..45924e2fe981f4 100644
--- a/code/modules/mob/living/silicon/ai/multicam.dm
+++ b/code/modules/mob/living/silicon/ai/multicam.dm
@@ -86,7 +86,7 @@
name = ""
icon = 'icons/misc/pic_in_pic.dmi'
icon_state = "room_background"
- flags_1 = NOJAUNT
+ turf_flags = NOJAUNT
/turf/open/ai_visible/Initialize(mapload)
. = ..()
diff --git a/code/modules/mob/living/silicon/damage_procs.dm b/code/modules/mob/living/silicon/damage_procs.dm
index 59db1264a30a31..8ee146508a4149 100644
--- a/code/modules/mob/living/silicon/damage_procs.dm
+++ b/code/modules/mob/living/silicon/damage_procs.dm
@@ -1,5 +1,5 @@
-/mob/living/silicon/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = FALSE, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
+/mob/living/silicon/apply_damage(damage = 0, damagetype = BRUTE, def_zone = null, blocked = 0, forced = FALSE, spread_damage = FALSE, wound_bonus = 0, bare_wound_bonus = 0, sharpness = NONE, attack_direction = null, attacking_item)
var/hit_percent = (100-blocked)/100
if((!damage || (!forced && hit_percent <= 0)))
return 0
diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm
index 9732222f0cf5eb..30559e616b569d 100644
--- a/code/modules/mob/living/silicon/robot/robot.dm
+++ b/code/modules/mob/living/silicon/robot/robot.dm
@@ -19,6 +19,7 @@
AddElement(/datum/element/ridable, /datum/component/riding/creature/cyborg)
RegisterSignal(src, COMSIG_PROCESS_BORGCHARGER_OCCUPANT, PROC_REF(charge))
RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater))
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
robot_modules_background = new()
robot_modules_background.icon_state = "block"
@@ -470,6 +471,13 @@
smash_headlamp()
return COMPONENT_BLOCK_LIGHT_EATER
+/// special handling for getting shot with a light disruptor/saboteur e.g. the fisher
+/mob/living/silicon/robot/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(lamp_enabled)
+ toggle_headlamp(TRUE)
+ to_chat(src, span_warning("Your headlamp was forcibly turned off. Restarting it should fix it, though."))
+ return COMSIG_SABOTEUR_SUCCESS
/**
* Handles headlamp smashing
diff --git a/code/modules/mob/living/simple_animal/bot/cleanbot.dm b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
index b4b4dd80f7fa69..710085884658af 100644
--- a/code/modules/mob/living/simple_animal/bot/cleanbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/cleanbot.dm
@@ -92,7 +92,7 @@
)
/mob/living/simple_animal/bot/cleanbot/autopatrol
- bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT
+ bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION
/mob/living/simple_animal/bot/cleanbot/medbay
name = "Scrubs, MD"
diff --git a/code/modules/mob/living/simple_animal/bot/construction.dm b/code/modules/mob/living/simple_animal/bot/construction.dm
index d01e36c334bbbc..12f33dec723ff9 100644
--- a/code/modules/mob/living/simple_animal/bot/construction.dm
+++ b/code/modules/mob/living/simple_animal/bot/construction.dm
@@ -273,6 +273,10 @@
switch(build_step)
if(ASSEMBLY_FIRST_STEP)
if(istype(W, /obj/item/healthanalyzer))
+ var/obj/item/healthanalyzer/analyzer = W // SKYRAT EDIT ADDITION BEGIN -- EXTRA ROBOTICS HEALTH ANALYZERS
+ if (!analyzer.can_be_used_in_medibot())
+ user?.balloon_alert(user, "no attachment ports!")
+ return // SKYRAT EDIT ADDITION END
if(!user.temporarilyRemoveItemFromInventory(W))
return
healthanalyzer = W.type
diff --git a/code/modules/mob/living/simple_animal/bot/honkbot.dm b/code/modules/mob/living/simple_animal/bot/honkbot.dm
index 8c46d8bec9c810..220c4a5cccea12 100644
--- a/code/modules/mob/living/simple_animal/bot/honkbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/honkbot.dm
@@ -9,7 +9,7 @@
radio_key = /obj/item/encryptionkey/headset_service //doesn't have security key
radio_channel = RADIO_CHANNEL_SERVICE //Doesn't even use the radio anyway.
bot_type = HONK_BOT
- bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_AUTOPATROL
+ bot_mode_flags = BOT_MODE_ON | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_AUTOPATROL | BOT_MODE_ROUNDSTART_POSSESSION
hackables = "sound control systems"
path_image_color = "#FF69B4"
data_hud_type = DATA_HUD_SECURITY_BASIC //show jobs
diff --git a/code/modules/mob/living/simple_animal/bot/medbot.dm b/code/modules/mob/living/simple_animal/bot/medbot.dm
index 5af3e6ebad471f..9c06465b8a5655 100644
--- a/code/modules/mob/living/simple_animal/bot/medbot.dm
+++ b/code/modules/mob/living/simple_animal/bot/medbot.dm
@@ -102,6 +102,8 @@
var/tipped_status = MEDBOT_PANIC_NONE
///The name we got when we were tipped
var/tipper_name
+ ///The trim type that will grant additional access to this medibot
+ var/datum/id_trim/additional_access = /datum/id_trim/job/paramedic
///Last announced healing a person in critical condition
COOLDOWN_DECLARE(last_patient_message)
@@ -111,7 +113,7 @@
COOLDOWN_DECLARE(last_tipping_action_voice)
/mob/living/simple_animal/bot/medbot/autopatrol
- bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT
+ bot_mode_flags = BOT_MODE_ON | BOT_MODE_AUTOPATROL | BOT_MODE_REMOTE_ENABLED | BOT_MODE_CAN_BE_SAPIENT | BOT_MODE_ROUNDSTART_POSSESSION
/mob/living/simple_animal/bot/medbot/stationary
medical_mode_flags = MEDBOT_DECLARE_CRIT | MEDBOT_STATIONARY_MODE | MEDBOT_SPEAK_MODE
@@ -144,6 +146,7 @@
damagetype_healer = "all"
heal_threshold = 0
heal_amount = 5
+ additional_access = /datum/id_trim/syndicom/crew
/mob/living/simple_animal/bot/medbot/nukie/Initialize(mapload, new_skin)
. = ..()
@@ -152,6 +155,7 @@
RegisterSignal(SSdcs, COMSIG_GLOB_NUKE_DEVICE_DETONATING, PROC_REF(nuke_detonate))
internal_radio.set_frequency(FREQ_SYNDICATE)
internal_radio.freqlock = RADIO_FREQENCY_LOCKED
+ faction += ROLE_SYNDICATE //one of us
/mob/living/simple_animal/bot/medbot/nukie/proc/nuke_disarm()
SIGNAL_HANDLER
@@ -204,14 +208,21 @@
. = ..()
// Doing this hurts my soul, but simplebot access reworks are for another day.
- var/datum/id_trim/job/para_trim = SSid_access.trim_singletons_by_path[/datum/id_trim/job/paramedic]
- access_card.add_access(para_trim.access + para_trim.wildcard_access)
+ var/datum/id_trim/additional_trim = SSid_access.trim_singletons_by_path[additional_access]
+ access_card.add_access(additional_trim.access + additional_trim.wildcard_access)
prev_access = access_card.access.Copy()
if(!isnull(new_skin))
skin = new_skin
update_appearance()
+ if(HAS_TRAIT(SSstation, STATION_TRAIT_MEDBOT_MANIA) && mapload && is_station_level(z))
+ skin = "advanced"
+ update_appearance(UPDATE_OVERLAYS)
+ damagetype_healer = "all"
+ if(prob(50))
+ name += ", PhD."
+
AddComponent(/datum/component/tippable, \
tip_time = 3 SECONDS, \
untip_time = 3 SECONDS, \
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
index b095016827fe6f..a0f078acd92e85 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/_drone.dm
@@ -74,7 +74,7 @@
/// Default [/mob/living/simple_animal/drone/var/internal_storage] item
var/obj/item/default_storage = /obj/item/storage/drone_tools
/// Default [/mob/living/simple_animal/drone/var/head] item
- var/obj/item/default_hatmask
+ var/obj/item/default_headwear
/**
* icon_state of drone from icons/mobs/drone.dmi
*
@@ -181,9 +181,16 @@
if(default_storage)
var/obj/item/I = new default_storage(src)
equip_to_slot_or_del(I, ITEM_SLOT_DEX_STORAGE)
- if(default_hatmask)
- var/obj/item/I = new default_hatmask(src)
- equip_to_slot_or_del(I, ITEM_SLOT_HEAD)
+
+ for(var/holiday_name in GLOB.holidays)
+ var/datum/holiday/holiday_today = GLOB.holidays[holiday_name]
+ var/obj/item/potential_hat = holiday_today.holiday_hat
+ if(!isnull(potential_hat) && isnull(default_headwear)) //If our drone type doesn't start with a hat, we take the holiday one.
+ default_headwear = potential_hat
+
+ if(default_headwear)
+ var/obj/item/new_hat = new default_headwear(src)
+ equip_to_slot_or_del(new_hat, ITEM_SLOT_HEAD)
ADD_TRAIT(access_card, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
diff --git a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
index 38c3585271ab4b..d4353c95c824ad 100644
--- a/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
+++ b/code/modules/mob/living/simple_animal/friendly/drone/extra_drone_types.dm
@@ -27,7 +27,7 @@
"2. Kill.\n"+\
"3. Destroy."
default_storage = /obj/item/uplink
- default_hatmask = /obj/item/clothing/head/helmet/swat
+ default_headwear = /obj/item/clothing/head/helmet/swat
hacked = TRUE
shy = FALSE
flavortext = null
@@ -49,7 +49,7 @@
W.implant(src, force = TRUE)
/mob/living/simple_animal/drone/snowflake
- default_hatmask = /obj/item/clothing/head/chameleon/drone
+ default_headwear = /obj/item/clothing/head/chameleon/drone
/mob/living/simple_animal/drone/snowflake/Initialize(mapload)
. = ..()
@@ -87,7 +87,7 @@
/mob/living/simple_animal/drone/polymorphed
default_storage = null
- default_hatmask = null
+ default_headwear = null
picked = TRUE
flavortext = null
@@ -132,7 +132,7 @@
/mob/living/simple_animal/drone/derelict
name = "derelict drone"
- default_hatmask = /obj/item/clothing/head/costume/ushanka
+ default_headwear = /obj/item/clothing/head/costume/ushanka
laws = \
"1. You may not involve yourself in the matters of another sentient being outside the station that housed your activation, even if such matters conflict with Law Two or Law Three, unless the other being is another Drone.\n"+\
"2. You may not harm any sentient being, regardless of intent or circumstance.\n"+\
diff --git a/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm b/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm
index b57ae45e1b7ad2..ebd5658f07f4b3 100644
--- a/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm
+++ b/code/modules/mob/living/simple_animal/guardian/guardian_creator.dm
@@ -87,9 +87,10 @@ GLOBAL_LIST_INIT(guardian_radial_images, setup_guardian_radial())
if(LAZYLEN(candidates))
var/mob/dead/observer/candidate = pick(candidates)
spawn_guardian(user, candidate, guardian_path)
+ used = TRUE
+ SEND_SIGNAL(src, COMSIG_TRAITOR_ITEM_USED(type))
else
to_chat(user, failure_message)
- used = FALSE
/obj/item/guardiancreator/proc/spawn_guardian(mob/living/user, mob/dead/candidate, guardian_path)
if(QDELETED(user) || user.stat == DEAD)
diff --git a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm
index 4269f847d924d6..2c0b9ba983a09f 100644
--- a/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm
+++ b/code/modules/mob/living/simple_animal/hostile/heretic_monsters.dm
@@ -145,7 +145,7 @@
mob_size = MOB_SIZE_HUGE
sentience_type = SENTIENCE_BOSS
environment_smash = ENVIRONMENT_SMASH_RWALLS
- mob_biotypes = MOB_ORGANIC|MOB_EPIC
+ mob_biotypes = MOB_ORGANIC|MOB_SPECIAL
obj_damage = 200
ranged_cooldown_time = 5
ranged = TRUE
diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
index 360381c9c1c602..3484c27375da46 100644
--- a/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
+++ b/code/modules/mob/living/simple_animal/hostile/megafauna/_megafauna.dm
@@ -6,7 +6,7 @@
combat_mode = TRUE
sentience_type = SENTIENCE_BOSS
environment_smash = ENVIRONMENT_SMASH_RWALLS
- mob_biotypes = MOB_ORGANIC|MOB_EPIC
+ mob_biotypes = MOB_ORGANIC|MOB_SPECIAL
obj_damage = 400
light_range = 3
faction = list(FACTION_MINING, FACTION_BOSS)
diff --git a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
index faabb633104c9d..2d92ef88a65d63 100644
--- a/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mining_mobs/hivelord.dm
@@ -1,11 +1,11 @@
/mob/living/simple_animal/hostile/asteroid/hivelord
name = "hivelord"
- desc = "A truly alien creature, it is a mass of unknown organic material, constantly fluctuating. When attacking, pieces of it split off and attack in tandem with the original."
+ desc = "A levitating swarm of tiny creatures which act as a single individual. When threatened or hunting they rapidly replicate additional short-lived bodies."
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
- icon_state = "Hivelord"
- icon_living = "Hivelord"
- icon_aggro = "Hivelord_alert"
- icon_dead = "Hivelord_dead"
+ icon_state = "hivelord"
+ icon_living = "hivelord"
+ icon_aggro = "hivelord_alert"
+ icon_dead = "hivelord_dead"
icon_gib = "syndicate_gib"
mob_biotypes = MOB_ORGANIC
move_to_delay = 14
@@ -18,11 +18,11 @@
harm_intent_damage = 5
melee_damage_lower = 0
melee_damage_upper = 0
- attack_verb_continuous = "lashes out at"
- attack_verb_simple = "lash out at"
+ attack_verb_continuous = "weakly tackles"
+ attack_verb_simple = "weakly tackles"
speak_emote = list("telepathically cries")
attack_sound = 'sound/weapons/pierce.ogg'
- throw_message = "falls right through the strange body of the"
+ throw_message = "passes between the bodies of the"
ranged_cooldown = 0
ranged_cooldown_time = 20
obj_damage = 0
@@ -40,14 +40,14 @@
AddComponent(/datum/component/clickbox, icon_state = "hivelord", max_scale = INFINITY, dead_state = "hivelord_dead") //they writhe so much.
/mob/living/simple_animal/hostile/asteroid/hivelord/OpenFire(the_target)
- if(world.time >= ranged_cooldown)
- var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/A = new brood_type(src.loc)
-
- A.flags_1 |= (flags_1 & ADMIN_SPAWNED_1)
- A.GiveTarget(target)
- A.friends = friends
- A.faction = faction.Copy()
- ranged_cooldown = world.time + ranged_cooldown_time
+ if(world.time < ranged_cooldown)
+ return
+ var/mob/living/simple_animal/hostile/asteroid/hivelordbrood/brood = new brood_type(src.loc)
+ brood.flags_1 |= (flags_1 & ADMIN_SPAWNED_1)
+ brood.GiveTarget(target)
+ brood.friends = friends
+ brood.faction = faction.Copy()
+ ranged_cooldown = world.time + ranged_cooldown_time
/mob/living/simple_animal/hostile/asteroid/hivelord/AttackingTarget()
OpenFire()
@@ -55,17 +55,17 @@
/mob/living/simple_animal/hostile/asteroid/hivelord/death(gibbed)
mouse_opacity = MOUSE_OPACITY_ICON
- ..(gibbed)
+ return ..()
//A fragile but rapidly produced creature
/mob/living/simple_animal/hostile/asteroid/hivelordbrood
name = "hivelord brood"
- desc = "A fragment of the original Hivelord, rallying behind its original. One isn't much of a threat, but..."
+ desc = "Short-lived attack form of the hivelord. One isn't much of a threat, but..."
icon = 'icons/mob/simple/lavaland/lavaland_monsters.dmi'
- icon_state = "Hivelordbrood"
- icon_living = "Hivelordbrood"
- icon_aggro = "Hivelordbrood"
- icon_dead = "Hivelordbrood"
+ icon_state = "hivelord_brood"
+ icon_living = "hivelord_brood"
+ icon_aggro = "hivelord_brood"
+ icon_dead = "hivelord_brood"
icon_gib = "syndicate_gib"
move_to_delay = 1
friendly_verb_continuous = "buzzes near"
@@ -93,11 +93,16 @@
/mob/living/simple_animal/hostile/asteroid/hivelordbrood/Initialize(mapload)
. = ..()
- addtimer(CALLBACK(src, PROC_REF(death)), 100)
+ addtimer(CALLBACK(src, PROC_REF(death)), 10 SECONDS)
AddElement(/datum/element/simple_flying)
AddComponent(/datum/component/swarming)
AddComponent(/datum/component/clickbox, icon_state = clickbox_state, max_scale = clickbox_max_scale)
+/mob/living/simple_animal/hostile/asteroid/hivelordbrood/death(gibbed)
+ if (!gibbed)
+ new /obj/effect/temp_visual/hive_spawn_wither(get_turf(src), /* copy_from = */ src)
+ return ..()
+
//Legion
/mob/living/simple_animal/hostile/asteroid/hivelord/legion
name = "legion"
diff --git a/code/modules/mob/living/simple_animal/revenant.dm b/code/modules/mob/living/simple_animal/revenant.dm
index 82c2be6920abc8..6e2ec11afeac8a 100644
--- a/code/modules/mob/living/simple_animal/revenant.dm
+++ b/code/modules/mob/living/simple_animal/revenant.dm
@@ -132,6 +132,7 @@
/mob/living/simple_animal/revenant/Life(seconds_per_tick = SSMOBS_DT, times_fired)
if(stasis)
return
+ var/delta_time = DELTA_WORLD_TIME(SSmobs)
if(revealed && essence <= 0)
death()
if(unreveal_time && world.time >= unreveal_time)
@@ -145,7 +146,7 @@
REMOVE_TRAIT(src, TRAIT_NO_TRANSFORM, REVENANT_STUNNED_TRAIT)
to_chat(src, span_revenboldnotice("You can move again!"))
if(essence_regenerating && !inhibited && essence < essence_regen_cap) //While inhibited, essence will not regenerate
- essence = min(essence + (essence_regen_amount * seconds_per_tick), essence_regen_cap)
+ essence = min(essence + (essence_regen_amount * delta_time), essence_regen_cap)
update_mob_action_buttons() //because we update something required by our spells in life, we need to update our buttons
update_spooky_icon()
update_health_hud()
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index cefd88c72ef001..09ce3b3c65c8ce 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -36,7 +36,6 @@
set_name()
SEND_SIGNAL(src, COMSIG_HUMAN_MONKEYIZE)
uncuff()
- regenerate_icons()
return src
////////////////////////// Humanize //////////////////////////////
@@ -71,7 +70,6 @@
invisibility = 0
set_species(species)
SEND_SIGNAL(src, COMSIG_MONKEY_HUMANIZE)
- regenerate_icons()
return src
/mob/proc/AIize(client/preference_source, move = TRUE)
diff --git a/code/modules/mod/mod_actions.dm b/code/modules/mod/mod_actions.dm
index d82912055e2c80..111ea425b6a6e5 100644
--- a/code/modules/mod/mod_actions.dm
+++ b/code/modules/mod/mod_actions.dm
@@ -125,6 +125,8 @@
var/obj/item/mod/module/module
/// A reference to the mob we are pinned to.
var/mob/pinner
+ /// Timer until we remove our cooldown overlay
+ var/cooldown_timer
/datum/action/item_action/mod/pinned_module/New(Target, obj/item/mod/module/linked_module, mob/user)
var/obj/item/mod/control/mod = Target
@@ -141,11 +143,22 @@
check_flags = NONE
name = "Activate [capitalize(linked_module.name)]"
desc = "Quickly activate [linked_module]."
- RegisterSignals(linked_module, list(COMSIG_MODULE_ACTIVATED, COMSIG_MODULE_DEACTIVATED, COMSIG_MODULE_USED), PROC_REF(module_interacted_with))
+ RegisterSignals(linked_module, list(
+ COMSIG_MODULE_ACTIVATED,
+ COMSIG_MODULE_DEACTIVATED,
+ COMSIG_MODULE_USED,
+ ), PROC_REF(module_interacted_with))
+ RegisterSignal(linked_module, COMSIG_MODULE_COOLDOWN_STARTED, PROC_REF(cooldown_started))
RegisterSignal(user, COMSIG_QDELETING, PROC_REF(pinner_deleted))
/datum/action/item_action/mod/pinned_module/Destroy()
- UnregisterSignal(module, list(COMSIG_MODULE_ACTIVATED, COMSIG_MODULE_DEACTIVATED, COMSIG_MODULE_USED))
+ deltimer(cooldown_timer)
+ UnregisterSignal(module, list(
+ COMSIG_MODULE_ACTIVATED,
+ COMSIG_MODULE_DEACTIVATED,
+ COMSIG_MODULE_COOLDOWN_STARTED,
+ COMSIG_MODULE_USED,
+ ))
module.pinned_to -= REF(pinner)
module = null
pinner = null
@@ -178,12 +191,19 @@
else if(module.active)
current_button.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_active", layer = FLOAT_LAYER-0.1))
if(!COOLDOWN_FINISHED(module, cooldown_timer))
- var/image/cooldown_image = image(icon = 'icons/hud/radial.dmi', icon_state = "module_cooldown")
- current_button.add_overlay(cooldown_image)
- addtimer(CALLBACK(current_button, TYPE_PROC_REF(/image, cut_overlay), cooldown_image), COOLDOWN_TIMELEFT(module, cooldown_timer))
+ current_button.add_overlay(image(icon = 'icons/hud/radial.dmi', icon_state = "module_cooldown"))
return ..()
/datum/action/item_action/mod/pinned_module/proc/module_interacted_with(datum/source)
SIGNAL_HANDLER
build_all_button_icons(UPDATE_BUTTON_OVERLAY|UPDATE_BUTTON_STATUS)
+
+/datum/action/item_action/mod/pinned_module/proc/cooldown_started(datum/source, cooldown_time)
+ SIGNAL_HANDLER
+
+ deltimer(cooldown_timer)
+ build_all_button_icons(UPDATE_BUTTON_OVERLAY)
+ if (cooldown_time == 0)
+ return
+ cooldown_timer = addtimer(CALLBACK(src, PROC_REF(build_all_button_icons), UPDATE_BUTTON_OVERLAY), cooldown_time + 1, TIMER_STOPPABLE)
diff --git a/code/modules/mod/mod_theme.dm b/code/modules/mod/mod_theme.dm
index 521dd6f024011a..9adaaf4f14680e 100644
--- a/code/modules/mod/mod_theme.dm
+++ b/code/modules/mod/mod_theme.dm
@@ -949,6 +949,7 @@
default_skin = "syndicate"
armor_type = /datum/armor/mod_theme_syndicate
atom_flags = PREVENT_CONTENTS_EXPLOSION_1
+ complexity_max = DEFAULT_MAX_COMPLEXITY + 3
max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT
siemens_coefficient = 0
slowdown_inactive = 1
@@ -1043,6 +1044,7 @@
resistance_flags = FIRE_PROOF|ACID_PROOF
atom_flags = PREVENT_CONTENTS_EXPLOSION_1
max_heat_protection_temperature = FIRE_IMMUNITY_MAX_TEMP_PROTECT
+ complexity_max = DEFAULT_MAX_COMPLEXITY + 3
siemens_coefficient = 0
slowdown_inactive = 1
slowdown_active = 0.5
diff --git a/code/modules/mod/mod_types.dm b/code/modules/mod/mod_types.dm
index 2f2be1dc86e479..f11c6b9b4b904b 100644
--- a/code/modules/mod/mod_types.dm
+++ b/code/modules/mod/mod_types.dm
@@ -231,12 +231,14 @@
/obj/item/mod/module/emp_shield,
/obj/item/mod/module/magnetic_harness,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/dna_lock,
)
default_pins = list(
/obj/item/mod/module/armor_booster,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
)
/obj/item/mod/control/pre_equipped/nuclear
@@ -249,11 +251,13 @@
/obj/item/mod/module/emp_shield,
/obj/item/mod/module/magnetic_harness,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
)
default_pins = list(
/obj/item/mod/module/armor_booster,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
)
/obj/item/mod/control/pre_equipped/nuclear/plasmaman
@@ -275,11 +279,13 @@
/obj/item/mod/module/emp_shield,
/obj/item/mod/module/magnetic_harness,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
)
default_pins = list(
/obj/item/mod/module/armor_booster,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
)
/obj/item/mod/control/pre_equipped/elite/flamethrower
@@ -289,12 +295,14 @@
/obj/item/mod/module/magnetic_harness,
/obj/item/mod/module/thermal_regulator,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
/obj/item/mod/module/flashlight,
/obj/item/mod/module/flamethrower,
)
default_pins = list(
/obj/item/mod/module/armor_booster,
/obj/item/mod/module/jetpack/advanced,
+ /obj/item/mod/module/jump_jet,
/obj/item/mod/module/flamethrower,
)
diff --git a/code/modules/mod/modules/_module.dm b/code/modules/mod/modules/_module.dm
index d5699986ee9405..a3067e0c150540 100644
--- a/code/modules/mod/modules/_module.dm
+++ b/code/modules/mod/modules/_module.dm
@@ -89,6 +89,13 @@
on_use()
SEND_SIGNAL(mod, COMSIG_MOD_MODULE_SELECTED, src)
+/// Apply a cooldown until this item can be used again
+/obj/item/mod/module/proc/start_cooldown(applied_cooldown)
+ if (isnull(applied_cooldown))
+ applied_cooldown = cooldown_time
+ COOLDOWN_START(src, cooldown_timer, applied_cooldown)
+ SEND_SIGNAL(src, COMSIG_MODULE_COOLDOWN_STARTED, applied_cooldown)
+
/// Called when the module is activated
/obj/item/mod/module/proc/on_activation()
if(!COOLDOWN_FINISHED(src, cooldown_timer))
@@ -129,8 +136,8 @@
update_signal(used_button)
balloon_alert(mod.wearer, "[src] activated, [used_button]-click to use")
active = TRUE
- COOLDOWN_START(src, cooldown_timer, cooldown_time)
mod.wearer.update_clothing(mod.slot_flags)
+ start_cooldown()
SEND_SIGNAL(src, COMSIG_MODULE_ACTIVATED)
return TRUE
@@ -166,7 +173,7 @@
return FALSE
if(SEND_SIGNAL(src, COMSIG_MODULE_TRIGGERED, mod.wearer) & MOD_ABORT_USE)
return FALSE
- COOLDOWN_START(src, cooldown_timer, cooldown_time)
+ start_cooldown()
addtimer(CALLBACK(mod.wearer, TYPE_PROC_REF(/mob, update_clothing), mod.slot_flags), cooldown_time+1) //need to run it a bit after the cooldown starts to avoid conflicts
mod.wearer.update_clothing(mod.slot_flags)
SEND_SIGNAL(src, COMSIG_MODULE_USED)
diff --git a/code/modules/mod/modules/modules_general.dm b/code/modules/mod/modules/modules_general.dm
index f64d808b7a7301..6d47a5b60ba3ba 100644
--- a/code/modules/mod/modules/modules_general.dm
+++ b/code/modules/mod/modules/modules_general.dm
@@ -169,12 +169,53 @@
/obj/item/mod/module/jetpack/advanced
name = "MOD advanced ion jetpack module"
desc = "An improvement on the previous model of electric thrusters. This one achieves higher speeds through \
- mounting of more jets and a red paint applied on it."
+ mounting of more jets and application of red paint."
icon_state = "jetpack_advanced"
overlay_state_inactive = "module_jetpackadv"
overlay_state_active = "module_jetpackadv_on"
full_speed = TRUE
+/// Cooldown to use if we didn't actually launch a jump jet
+#define FAILED_ACTIVATION_COOLDOWN 3 SECONDS
+
+///Jump Jet - Briefly removes the effect of gravity and pushes you up one z-level if possible.
+/obj/item/mod/module/jump_jet
+ name = "MOD ionic jump jet module"
+ desc = "A specialised ionic thruster which provides a short but powerful boost capable of pushing against gravity, \
+ after which time it needs to recharge."
+ icon_state = "jump_jet"
+ module_type = MODULE_USABLE
+ complexity = 3
+ cooldown_time = 30 SECONDS
+ use_power_cost = DEFAULT_CHARGE_DRAIN * 5
+ incompatible_modules = list(/obj/item/mod/module/jump_jet)
+
+/obj/item/mod/module/jump_jet/on_use()
+ . = ..()
+ if (!.)
+ return FALSE
+ if (DOING_INTERACTION(mod.wearer, mod.wearer))
+ balloon_alert(mod.wearer, "busy!")
+ return
+ balloon_alert(mod.wearer, "launching...")
+ mod.wearer.Shake(duration = 1 SECONDS)
+ if (!do_after(mod.wearer, 1 SECONDS, target = mod.wearer))
+ start_cooldown(FAILED_ACTIVATION_COOLDOWN) // Don't go on full cooldown if we failed to launch
+ return FALSE
+ playsound(mod.wearer, 'sound/vehicles/rocketlaunch.ogg', 100, TRUE)
+ mod.wearer.apply_status_effect(/datum/status_effect/jump_jet)
+ var/turf/launch_from = get_turf(mod.wearer)
+ if (mod.wearer.zMove(UP, z_move_flags = ZMOVE_CHECK_PULLS))
+ launch_from.visible_message(span_warning("[mod.wearer] rockets into the air!"))
+ new /obj/effect/temp_visual/jet_plume(launch_from)
+
+ var/obj/item/mod/module/jetpack/linked_jetpack = locate() in mod.modules
+ if (!isnull(linked_jetpack) && !linked_jetpack.active)
+ linked_jetpack.on_activation()
+ return TRUE
+
+#undef FAILED_ACTIVATION_COOLDOWN
+
///Status Readout - Puts a lot of information including health, nutrition, fingerprints, temperature to the suit TGUI.
/obj/item/mod/module/status_readout
name = "MOD status readout module"
diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm
index da83445a641087..21d2b8352fb925 100644
--- a/code/modules/modular_computers/computers/item/computer.dm
+++ b/code/modules/modular_computers/computers/item/computer.dm
@@ -65,6 +65,8 @@
///If the computer has a flashlight/LED light built-in.
var/has_light = FALSE
+ /// If the computer's flashlight/LED light has forcibly disabled for a temporary amount of time.
+ COOLDOWN_DECLARE(disabled_time)
/// How far the computer's light can reach, is not editable by players.
var/comp_light_luminosity = 3
/// The built-in light's color, editable by players.
@@ -127,6 +129,7 @@
UpdateDisplay()
if(has_light)
add_item_action(/datum/action/item_action/toggle_computer_light)
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
if(inserted_disk)
inserted_disk = new inserted_disk(src)
if(internal_cell)
@@ -657,7 +660,7 @@
/obj/item/modular_computer/ui_action_click(mob/user, actiontype)
if(istype(actiontype, /datum/action/item_action/toggle_computer_light))
- toggle_flashlight()
+ toggle_flashlight(user)
return
return ..()
@@ -668,14 +671,32 @@
* Called from ui_act(), does as the name implies.
* It is separated from ui_act() to be overwritten as needed.
*/
-/obj/item/modular_computer/proc/toggle_flashlight()
+/obj/item/modular_computer/proc/toggle_flashlight(mob/user)
if(!has_light)
return FALSE
+ if(!COOLDOWN_FINISHED(src, disabled_time))
+ balloon_alert(user, "disrupted!")
+ return FALSE
set_light_on(!light_on)
update_appearance()
update_item_action_buttons(force = TRUE) //force it because we added an overlay, not changed its icon
return TRUE
+/**
+ * Disables the computer's flashlight/LED light, if it has one, for a given disrupt_duration.
+ *
+ * Called when sent COMSIG_HIT_BY_SABOTEUR.
+ */
+/obj/item/modular_computer/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ if(!has_light)
+ return
+ set_light_on(FALSE)
+ update_appearance()
+ update_item_action_buttons(force = TRUE) //force it because we added an overlay, not changed its icon
+ COOLDOWN_START(src, disabled_time, disrupt_duration)
+ return COMSIG_SABOTEUR_SUCCESS
+
/**
* Sets the computer's light color, if it has a light.
*
diff --git a/code/modules/modular_computers/computers/item/pda.dm b/code/modules/modular_computers/computers/item/pda.dm
index bc57f3597846f4..a4b08f2d90f6db 100644
--- a/code/modules/modular_computers/computers/item/pda.dm
+++ b/code/modules/modular_computers/computers/item/pda.dm
@@ -379,7 +379,7 @@
.["comp_light_color"] = robo.lamp_color
//Makes the flashlight button affect the borg rather than the tablet
-/obj/item/modular_computer/pda/silicon/toggle_flashlight()
+/obj/item/modular_computer/pda/silicon/toggle_flashlight(mob/user)
if(!silicon_owner || QDELETED(silicon_owner))
return FALSE
if(iscyborg(silicon_owner))
diff --git a/code/modules/modular_computers/file_system/programs/notepad.dm b/code/modules/modular_computers/file_system/programs/notepad.dm
index 2e1eb52add5e8d..01afaa08c19e07 100644
--- a/code/modules/modular_computers/file_system/programs/notepad.dm
+++ b/code/modules/modular_computers/file_system/programs/notepad.dm
@@ -10,7 +10,14 @@
usage_flags = PROGRAM_TABLET
var/written_note = "Congratulations on your station upgrading to the new NtOS and Thinktronic based collaboration effort, \
- bringing you the best in electronics and software since 2467!"
+ bringing you the best in electronics and software since 2467!\n\
+ To help with navigation, we have provided the following definitions:\n\
+ Fore - Toward front of ship\n\
+ Aft - Toward back of ship\n\
+ Port - Left side of ship\n\
+ Starboard - Right side of ship\n\
+ Quarter - Either sides of Aft\n\
+ Bow - Either sides of Fore"
/datum/computer_file/program/notepad/ui_act(action, list/params, datum/tgui/ui)
switch(action)
diff --git a/code/modules/modular_computers/laptop_vendor.dm b/code/modules/modular_computers/laptop_vendor.dm
deleted file mode 100644
index 28a6e12843f1a6..00000000000000
--- a/code/modules/modular_computers/laptop_vendor.dm
+++ /dev/null
@@ -1,152 +0,0 @@
-// A vendor machine for modular computer portable devices - Laptops and Tablets
-
-/obj/machinery/lapvend
- name = "computer vendor"
- desc = "A vending machine with microfabricator capable of dispensing various NT-branded computers."
- icon = 'icons/obj/machines/vending.dmi'
- icon_state = "robotics"
- layer = 2.9
- density = TRUE
-
- // The actual laptop/tablet
- var/obj/item/modular_computer/laptop/fabricated_laptop
- var/obj/item/modular_computer/pda/fabricated_tablet
-
- // Utility vars
- var/state = 0 // 0: Select device type, 1: Select loadout, 2: Payment, 3: Thankyou screen
- var/devtype = 0 // 0: None(unselected), 1: Laptop, 2: Tablet
- var/total_price = 0 // Price of currently vended device.
- var/credits = 0
-
-// Removes all traces of old order and allows you to begin configuration from scratch.
-/obj/machinery/lapvend/proc/reset_order()
- state = 0
- devtype = 0
- if(fabricated_laptop)
- qdel(fabricated_laptop)
- fabricated_laptop = null
- if(fabricated_tablet)
- qdel(fabricated_tablet)
- fabricated_tablet = null
-
-// Recalculates the price and optionally even fabricates the device.
-/obj/machinery/lapvend/proc/fabricate_and_recalc_price(fabricate = FALSE)
- total_price = 0
- if(devtype == 1) // Laptop, generally cheaper to make it accessible for most station roles
- if(fabricate)
- fabricated_laptop = new /obj/item/modular_computer/laptop/buildable(src)
- total_price = 99
-
- return total_price
- else if(devtype == 2) // Tablet, more expensive, not everyone could probably afford this.
- if(fabricate)
- fabricated_tablet = new(src)
- total_price = 199
- return FALSE
-
-/obj/machinery/lapvend/ui_act(action, params)
- . = ..()
- if(.)
- return
-
- switch(action)
- if("pick_device")
- if(state) // We've already picked a device type
- return FALSE
- devtype = text2num(params["pick"])
- state = 1
- fabricate_and_recalc_price(FALSE)
- return TRUE
- if("clean_order")
- reset_order()
- return TRUE
- if("purchase")
- try_purchase()
- return TRUE
- if((state != 1) && devtype) // Following IFs should only be usable when in the Select Loadout mode
- return FALSE
- switch(action)
- if("confirm_order")
- state = 2 // Wait for ID swipe for payment processing
- fabricate_and_recalc_price(FALSE)
- return TRUE
- return FALSE
-
-/obj/machinery/lapvend/ui_interact(mob/user, datum/tgui/ui)
- if(machine_stat & (BROKEN | NOPOWER | MAINT))
- if(ui)
- ui.close()
- return FALSE
-
- ui = SStgui.try_update_ui(user, src, ui)
- if (!ui)
- ui = new(user, src, "ComputerFabricator")
- ui.open()
-
-/obj/machinery/lapvend/attackby(obj/item/I, mob/user)
- if(istype(I, /obj/item/stack/spacecash))
- var/obj/item/stack/spacecash/c = I
- if(!user.temporarilyRemoveItemFromInventory(c))
- return
- credits += c.value
- visible_message(span_info("[span_name("[user]")] inserts [c.value] cr into [src]."))
- qdel(c)
- return
- else if(istype(I, /obj/item/holochip))
- var/obj/item/holochip/HC = I
- credits += HC.credits
- visible_message(span_info("[user] inserts a [HC.credits] cr holocredit chip into [src]."))
- qdel(HC)
- return
- else if(isidcard(I))
- if(state != 2)
- return
- var/obj/item/card/id/ID = I
- var/datum/bank_account/account = ID.registered_account
- var/target_credits = total_price - credits
- if(!account.adjust_money(-target_credits, "Vending: Laptop Vendor"))
- say("Insufficient credits on card to purchase!")
- return
- credits += target_credits
- say("[target_credits] cr have been withdrawn from your account.")
- return
- return ..()
-
-// Simplified payment processing, returns 1 on success.
-/obj/machinery/lapvend/proc/process_payment()
- if(total_price > credits)
- say("Insufficient credits.")
- return FALSE
- else
- return TRUE
-
-/obj/machinery/lapvend/ui_data(mob/user)
-
- var/list/data = list()
- data["state"] = state
- if(state == 1)
- data["devtype"] = devtype
- if(state == 1 || state == 2)
- data["totalprice"] = total_price
- data["credits"] = credits
-
- return data
-
-
-/obj/machinery/lapvend/proc/try_purchase()
- // Awaiting payment state
- if(state == 2)
- if(process_payment())
- fabricate_and_recalc_price(1)
- if((devtype == 1) && fabricated_laptop)
- fabricated_laptop.forceMove(src.loc)
- fabricated_laptop = null
- else if((devtype == 2) && fabricated_tablet)
- fabricated_tablet.forceMove(src.loc)
- fabricated_tablet = null
- credits -= total_price
- say("Enjoy your new product!")
- state = 3
- addtimer(CALLBACK(src, PROC_REF(reset_order)), 100)
- return TRUE
- return FALSE
diff --git a/code/modules/paperwork/paper_cutter.dm b/code/modules/paperwork/paper_cutter.dm
index 31cbe153f9e862..9586ec6e861841 100644
--- a/code/modules/paperwork/paper_cutter.dm
+++ b/code/modules/paperwork/paper_cutter.dm
@@ -171,8 +171,9 @@
to_chat(user, span_userdanger("You neatly cut [stored_paper][clumsy ? "... and your finger in the process!" : "."]"))
if(clumsy)
var/obj/item/bodypart/finger = user.get_active_hand()
- var/datum/wound/slash/flesh/moderate/papercut = new
- papercut.apply_wound(finger, wound_source = "paper cut")
+ if (iscarbon(user))
+ var/mob/living/carbon/carbon_user = user
+ carbon_user.cause_wound_of_type_and_severity(WOUND_SLASH, finger, WOUND_SEVERITY_MODERATE, wound_source = "paper cut")
stored_paper = null
qdel(stored_paper)
new /obj/item/paper/paperslip(get_turf(src))
diff --git a/code/modules/paperwork/ticketmachine.dm b/code/modules/paperwork/ticketmachine.dm
index 8d62bf784cbd90..a5902a9df5a20a 100644
--- a/code/modules/paperwork/ticketmachine.dm
+++ b/code/modules/paperwork/ticketmachine.dm
@@ -31,6 +31,7 @@
/obj/machinery/ticket_machine/Initialize(mapload)
. = ..()
update_appearance()
+ find_and_hang_on_wall()
/obj/machinery/ticket_machine/Destroy()
for(var/obj/item/ticket_machine_ticket/ticket in tickets)
@@ -55,7 +56,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/ticket_machine, 32)
return
var/obj/item/multitool/M = I
M.set_buffer(src)
- to_chat(user, span_notice("You store linkage information in [I]'s buffer."))
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/obj/machinery/ticket_machine/emag_act(mob/user, obj/item/card/emag/emag_card) //Emag the ticket machine to dispense burning tickets, as well as randomize its number to destroy the HoP's mind.
diff --git a/code/modules/photography/camera/camera.dm b/code/modules/photography/camera/camera.dm
index 33c22c66aef16b..1c0e360ed75853 100644
--- a/code/modules/photography/camera/camera.dm
+++ b/code/modules/photography/camera/camera.dm
@@ -187,7 +187,7 @@
var/list/turfs = list()
var/list/mobs = list()
var/blueprints = FALSE
- var/clone_area = SSmapping.RequestBlockReservation(size_x * 2 + 1, size_y * 2 + 1)
+ var/clone_area = SSmapping.request_turf_block_reservation(size_x * 2 + 1, size_y * 2 + 1, 1)
var/width = size_x * 2 + 1
var/height = size_y * 2 + 1
diff --git a/code/modules/photography/camera/camera_image_capturing.dm b/code/modules/photography/camera/camera_image_capturing.dm
index 6b48e29da52ed3..d928164ff014df 100644
--- a/code/modules/photography/camera/camera_image_capturing.dm
+++ b/code/modules/photography/camera/camera_image_capturing.dm
@@ -16,13 +16,14 @@
var/wipe_atoms = FALSE
if(istype(clone_area) && total_x == clone_area.width && total_y == clone_area.height && size_x >= 0 && size_y > 0)
- var/cloned_center_x = round(clone_area.bottom_left_coords[1] + ((total_x - 1) / 2))
- var/cloned_center_y = round(clone_area.bottom_left_coords[2] + ((total_y - 1) / 2))
+ var/turf/bottom_left = clone_area.bottom_left_turfs[1]
+ var/cloned_center_x = round(bottom_left.x + ((total_x - 1) / 2))
+ var/cloned_center_y = round(bottom_left.y + ((total_y - 1) / 2))
for(var/t in turfs)
var/turf/T = t
var/offset_x = T.x - center.x
var/offset_y = T.y - center.y
- var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, clone_area.bottom_left_coords[3])
+ var/turf/newT = locate(cloned_center_x + offset_x, cloned_center_y + offset_y, bottom_left.z)
if(!(newT in clone_area.reserved_turfs)) //sanity check so we don't overwrite other areas somehow
continue
atoms += new /obj/effect/appearance_clone(newT, T)
@@ -34,7 +35,7 @@
atoms += new /obj/effect/appearance_clone(newT, A)
skip_normal = TRUE
wipe_atoms = TRUE
- center = locate(cloned_center_x, cloned_center_y, clone_area.bottom_left_coords[3])
+ center = locate(cloned_center_x, cloned_center_y, bottom_left.z)
if(!skip_normal)
for(var/i in turfs)
diff --git a/code/modules/plumbing/plumbers/grinder_chemical.dm b/code/modules/plumbing/plumbers/grinder_chemical.dm
index 5e3c3b0f5d3ab9..e70977bc1f4450 100644
--- a/code/modules/plumbing/plumbers/grinder_chemical.dm
+++ b/code/modules/plumbing/plumbers/grinder_chemical.dm
@@ -1,6 +1,6 @@
/obj/machinery/plumbing/grinder_chemical
name = "chemical grinder"
- desc = "chemical grinder."
+ desc = "Chemical grinder. Can either grind or juice stuff you put in."
icon_state = "grinder_chemical"
layer = ABOVE_ALL_MOB_LAYER
plane = ABOVE_GAME_PLANE
@@ -8,7 +8,6 @@
reagent_flags = TRANSPARENT | DRAINABLE
buffer = 400
active_power_usage = BASE_MACHINE_ACTIVE_CONSUMPTION * 2
- var/eat_dir = SOUTH
/obj/machinery/plumbing/grinder_chemical/Initialize(mapload, bolt, layer)
. = ..()
@@ -18,21 +17,35 @@
)
AddElement(/datum/element/connect_loc, loc_connections)
-/obj/machinery/plumbing/grinder_chemical/setDir(newdir)
- . = ..()
- eat_dir = newdir
+/obj/machinery/plumbing/grinder_chemical/attackby(obj/item/weapon, mob/user, params)
+ if(istype(weapon, /obj/item/storage/bag))
+ to_chat(user, span_notice("You dump items from [weapon] into the grinder."))
+ for(var/obj/item/obj_item in weapon.contents)
+ grind(obj_item)
+ else
+ to_chat(user, span_notice("You attempt to grind [weapon]."))
+ grind(weapon)
+
+ return TRUE
/obj/machinery/plumbing/grinder_chemical/CanAllowThrough(atom/movable/mover, border_dir)
. = ..()
if(!anchored)
return
- if(border_dir == eat_dir)
- return TRUE
+ if(!istype(mover, /obj/item))
+ return FALSE
+ return TRUE
/obj/machinery/plumbing/grinder_chemical/proc/on_entered(datum/source, atom/movable/AM)
SIGNAL_HANDLER
+
grind(AM)
+/**
+ * Grinds/Juices the atom
+ * Arguments
+ * * [AM][atom] - the atom to grind or juice
+ */
/obj/machinery/plumbing/grinder_chemical/proc/grind(atom/AM)
if(machine_stat & NOPOWER)
return
@@ -40,11 +53,14 @@
return
if(!isitem(AM))
return
+
var/obj/item/I = AM
+ var/result
if(I.grind_results || I.juice_typepath)
use_power(active_power_usage)
if(I.grind_results)
- I.grind(src, src)
+ result = I.grind(reagents, usr)
else if (I.juice_typepath)
- I.juice(src, src)
- qdel(I)
+ result = I.juice(reagents, usr)
+ if(result)
+ qdel(I)
diff --git a/code/modules/plumbing/plumbers/teleporter.dm b/code/modules/plumbing/plumbers/teleporter.dm
index 7b3a62c9939862..a8e6e7ae3ac554 100644
--- a/code/modules/plumbing/plumbers/teleporter.dm
+++ b/code/modules/plumbing/plumbers/teleporter.dm
@@ -73,7 +73,7 @@
var/obj/item/multitool/M = I
M.set_buffer(src)
- to_chat(user, span_notice("You store linkage information in [I]'s buffer."))
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/obj/machinery/plumbing/receiver/process(seconds_per_tick)
diff --git a/code/modules/power/apc/apc_main.dm b/code/modules/power/apc/apc_main.dm
index b9f60aea016883..fb9fd389c1777f 100644
--- a/code/modules/power/apc/apc_main.dm
+++ b/code/modules/power/apc/apc_main.dm
@@ -212,6 +212,7 @@
AddElement(/datum/element/contextual_screentip_bare_hands, rmb_text = "Toggle interface lock")
AddElement(/datum/element/contextual_screentip_mob_typechecks, hovering_mob_typechecks)
+ find_and_hang_on_wall()
/obj/machinery/power/apc/Destroy()
if(malfai && operating)
diff --git a/code/modules/power/floodlight.dm b/code/modules/power/floodlight.dm
index 3ab8ece250f071..f36c9b1303866d 100644
--- a/code/modules/power/floodlight.dm
+++ b/code/modules/power/floodlight.dm
@@ -144,6 +144,7 @@
/obj/machinery/power/floodlight/Initialize(mapload)
. = ..()
RegisterSignal(src, COMSIG_OBJ_PAINTED, TYPE_PROC_REF(/obj/machinery/power/floodlight, on_color_change)) //update light color when color changes
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
register_context()
/obj/machinery/power/floodlight/proc/on_color_change(obj/machinery/power/flood_light, mob/user, obj/item/toy/crayon/spraycan/spraycan, is_dark_color)
@@ -289,6 +290,11 @@
/obj/machinery/power/floodlight/attack_ai(mob/user)
return attack_hand(user)
+/obj/machinery/power/floodlight/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ atom_break(ENERGY) // technically,
+ return COMSIG_SABOTEUR_SUCCESS
+
/obj/machinery/power/floodlight/atom_break(damage_flag)
. = ..()
if(!.)
@@ -297,7 +303,8 @@
var/obj/structure/floodlight_frame/floodlight_frame = new(loc)
floodlight_frame.state = FLOODLIGHT_NEEDS_LIGHTS
- new /obj/item/light/tube(loc)
+ var/obj/item/light/tube/our_light = new(loc)
+ our_light.shatter()
qdel(src)
diff --git a/code/modules/power/lighting/light.dm b/code/modules/power/lighting/light.dm
index 2486a6500c148d..ed4b2159cab6fc 100644
--- a/code/modules/power/lighting/light.dm
+++ b/code/modules/power/lighting/light.dm
@@ -114,7 +114,9 @@
// Light projects out backwards from the dir of the light
set_light(l_dir = REVERSE_DIR(dir))
RegisterSignal(src, COMSIG_LIGHT_EATER_ACT, PROC_REF(on_light_eater))
+ RegisterSignal(src, COMSIG_HIT_BY_SABOTEUR, PROC_REF(on_saboteur))
AddElement(/datum/element/atmos_sensitive, mapload)
+ find_and_hang_on_wall(custom_drop_callback = CALLBACK(src, PROC_REF(knock_down)))
return INITIALIZE_HINT_LATELOAD
/obj/machinery/light/LateInitialize()
@@ -706,6 +708,11 @@
tube?.burn()
return
+/obj/machinery/light/proc/on_saboteur(datum/source, disrupt_duration)
+ SIGNAL_HANDLER
+ break_light_tube()
+ return COMSIG_SABOTEUR_SUCCESS
+
/obj/machinery/light/proc/grey_tide(datum/source, list/grey_tide_areas)
SIGNAL_HANDLER
@@ -714,6 +721,20 @@
continue
INVOKE_ASYNC(src, PROC_REF(flicker))
+/**
+ * All the effects that occur when a light falls off a wall that it was hung onto.
+ */
+/obj/machinery/light/proc/knock_down()
+ new /obj/item/wallframe/light_fixture(drop_location())
+ new /obj/item/stack/cable_coil(drop_location(), 1, "red")
+ if(status != LIGHT_BROKEN)
+ break_light_tube(FALSE)
+ if(status != LIGHT_EMPTY)
+ drop_light_tube()
+ if(cell)
+ cell.forceMove(drop_location())
+ qdel(src)
+
/obj/machinery/light/floor
name = "floor light"
desc = "A lightbulb you can walk on without breaking it, amazing."
diff --git a/code/modules/power/lighting/light_construct.dm b/code/modules/power/lighting/light_construct.dm
index 5a8f406b86c7b4..05d9533c79ea76 100644
--- a/code/modules/power/lighting/light_construct.dm
+++ b/code/modules/power/lighting/light_construct.dm
@@ -33,6 +33,7 @@
. = ..()
if(building)
setDir(ndir)
+ find_and_hang_on_wall()
/obj/structure/light_construct/Destroy()
QDEL_NULL(cell)
diff --git a/code/modules/power/singularity/singularity.dm b/code/modules/power/singularity/singularity.dm
index d3e4fe9e808ef7..0a797a2d7d6816 100644
--- a/code/modules/power/singularity/singularity.dm
+++ b/code/modules/power/singularity/singularity.dm
@@ -14,6 +14,7 @@
/// the prepended string to the icon state (singularity_s1, dark_matter_s1, etc)
var/singularity_icon_variant = "singularity"
+
/// The singularity component itself.
/// A weak ref in case an admin removes the component to preserve the functionality.
var/datum/weakref/singularity_component
@@ -55,6 +56,8 @@
/obj/singularity/Initialize(mapload, starting_energy = 50)
. = ..()
+ energy = starting_energy
+
START_PROCESSING(SSsinguloprocess, src)
SSpoints_of_interest.make_point_of_interest(src)
@@ -212,7 +215,7 @@
if(STAGE_TWO)
if(check_cardinals_range(1, TRUE))
current_size = STAGE_TWO
- icon = 'icons/obj/machines/engine/singularity.dmi'
+ icon = 'icons/effects/96x96.dmi'
icon_state = "[singularity_icon_variant]_s3"
pixel_x = -32
pixel_y = -32
@@ -224,7 +227,7 @@
if(STAGE_THREE)
if(check_cardinals_range(2, TRUE))
current_size = STAGE_THREE
- icon = 'icons/obj/machines/engine/singularity.dmi'
+ icon = 'icons/effects/160x160.dmi'
icon_state = "[singularity_icon_variant]_s5"
pixel_x = -64
pixel_y = -64
@@ -236,7 +239,7 @@
if(STAGE_FOUR)
if(check_cardinals_range(3, TRUE))
current_size = STAGE_FOUR
- icon = 'icons/obj/machines/engine/singularity.dmi'
+ icon = 'icons/effects/224x224.dmi'
icon_state = "[singularity_icon_variant]_s7"
pixel_x = -96
pixel_y = -96
@@ -247,7 +250,7 @@
dissipate_strength = 10
if(STAGE_FIVE)//this one also lacks a check for gens because it eats everything
current_size = STAGE_FIVE
- icon = 'icons/obj/machines/engine/singularity.dmi'
+ icon = 'icons/effects/288x288.dmi'
icon_state = "[singularity_icon_variant]_s9"
pixel_x = -128
pixel_y = -128
diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm
index 15fba3cc8fe6e6..654ea8054400fe 100644
--- a/code/modules/power/supermatter/supermatter.dm
+++ b/code/modules/power/supermatter/supermatter.dm
@@ -39,6 +39,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
var/absorption_ratio = 0.15
/// The gasmix we just recently absorbed. Tile's air multiplied by absorption_ratio
var/datum/gas_mixture/absorbed_gasmix
+ /// The current gas behaviors for this particular crystal
+ var/list/current_gas_behavior
///Refered to as EER on the monitor. This value effects gas output, damage, and power generation.
var/internal_energy = 0
@@ -109,7 +111,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
///The cutoff for a bolt jumping, grows with heat, lowers with higher mol count,
var/zap_cutoff = 1500
///How much the bullets damage should be multiplied by when it is added to the internal variables
- var/bullet_energy = 2
+ var/bullet_energy = SUPERMATTER_DEFAULT_BULLET_ENERGY
///How much hallucination should we produce per unit of power?
var/hallucination_power = 0.1
@@ -172,11 +174,14 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
/// Lazy list of the crazy engineers who managed to turn a cascading engine around.
var/list/datum/weakref/saviors = null
+ /// If a sliver of the supermatter has been removed. Almost certainly by a traitor. Lowers the delamination countdown time.
+ var/supermatter_sliver_removed = FALSE
/// Cooldown for sending emergency alerts to the common radio channel
COOLDOWN_DECLARE(common_radio_cooldown)
/obj/machinery/power/supermatter_crystal/Initialize(mapload)
. = ..()
+ current_gas_behavior = init_sm_gas()
gas_percentage = list()
absorbed_gasmix = new()
uid = gl_uid++
@@ -265,22 +270,25 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
// Some extra effects like [/datum/sm_gas/carbon_dioxide/extra_effects]
// needs more than one gas and rely on a fully parsed gas_percentage.
for (var/gas_path in absorbed_gasmix.gases)
- var/datum/sm_gas/sm_gas = GLOB.sm_gas_behavior[gas_path]
+ var/datum/sm_gas/sm_gas = current_gas_behavior[gas_path]
sm_gas?.extra_effects(src)
// PART 3: POWER PROCESSING
internal_energy_factors = calculate_internal_energy()
zap_factors = calculate_zap_multiplier()
- if(internal_energy && (last_power_zap + 4 SECONDS - (internal_energy * 0.001)) < world.time)
+ if(internal_energy && (last_power_zap + (4 - internal_energy * 0.001) SECONDS) < world.time)
playsound(src, 'sound/weapons/emitter2.ogg', 70, TRUE)
hue_angle_shift = clamp(903 * log(10, (internal_energy + 8000)) - 3590, -50, 240)
var/zap_color = color_matrix_rotate_hue(hue_angle_shift)
+ //Scale the strength of the zap with the world's time elapsed between zaps in seconds.
+ //Capped at 16 seconds to prevent a crazy burst of energy if atmos was halted for a long time.
+ var/delta_time = min((world.time - last_power_zap) * 0.1, 16)
supermatter_zap(
zapstart = src,
range = 3,
- zap_str = 5 * internal_energy * zap_multiplier,
+ zap_str = 1.25 * internal_energy * zap_multiplier * delta_time,
zap_flags = ZAP_SUPERMATTER_FLAGS,
- zap_cutoff = 300,
+ zap_cutoff = 300 * delta_time,
power_level = internal_energy,
color = zap_color,
)
@@ -505,10 +513,23 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
radio.talk_into(
src,
count_down_messages[1],
- emergency_channel
+ emergency_channel,
+ list(SPAN_COMMAND)
)
- for(var/i in SUPERMATTER_COUNTDOWN_TIME to 0 step -10)
+ var/delamination_countdown_time = SUPERMATTER_COUNTDOWN_TIME
+ // If a sliver was removed from the supermatter, the countdown time is significantly decreased
+ if (supermatter_sliver_removed == TRUE)
+ delamination_countdown_time = SUPERMATTER_SLIVER_REMOVED_COUNTDOWN_TIME
+ radio.talk_into(
+ src,
+ "WARNING: Projected time until full crystal delamination significantly lower than expected. \
+ Please inspect crystal for structural abnormalities or sabotage!",
+ emergency_channel,
+ list(SPAN_COMMAND)
+ )
+
+ for(var/i in delamination_countdown_time to 0 step -10)
if(last_delamination_strategy != delamination_strategy)
count_down_messages = delamination_strategy.count_down_messages()
last_delamination_strategy = delamination_strategy
@@ -583,7 +604,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
if(mole_count < MINIMUM_MOLE_COUNT) //save processing power from small amounts like these
continue
gas_percentage[gas_path] = mole_count / total_moles
- var/datum/sm_gas/sm_gas = GLOB.sm_gas_behavior[gas_path]
+ var/datum/sm_gas/sm_gas = current_gas_behavior[gas_path]
if(!sm_gas)
continue
gas_power_transmission += sm_gas.power_transmission * gas_percentage[gas_path]
@@ -919,7 +940,7 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal)
multi = 8
if(zap_flags & ZAP_SUPERMATTER_FLAGS)
var/remaining_power = target.zap_act(zap_str * multi, zap_flags)
- zap_str = remaining_power * 0.5 //Coils should take a lot out of the power of the zap
+ zap_str = remaining_power / multi //Coils should take a lot out of the power of the zap
else
zap_str /= 3
diff --git a/code/modules/power/supermatter/supermatter_hit_procs.dm b/code/modules/power/supermatter/supermatter_hit_procs.dm
index 5c68669e6e245e..452b37e054100e 100644
--- a/code/modules/power/supermatter/supermatter_hit_procs.dm
+++ b/code/modules/power/supermatter/supermatter_hit_procs.dm
@@ -67,6 +67,7 @@
if (scalpel.usesLeft)
to_chat(user, span_danger("You extract a sliver from \the [src]. \The [src] begins to react violently!"))
new /obj/item/nuke_core/supermatter_sliver(src.drop_location())
+ supermatter_sliver_removed = TRUE
external_power_trickle += 800
log_activation(who = user, how = scalpel)
scalpel.usesLeft--
diff --git a/code/modules/procedural_mapping/mapGenerators/repair.dm b/code/modules/procedural_mapping/mapGenerators/repair.dm
index c9df8496389d15..505dc36f02c12a 100644
--- a/code/modules/procedural_mapping/mapGenerators/repair.dm
+++ b/code/modules/procedural_mapping/mapGenerators/repair.dm
@@ -28,7 +28,18 @@
var/z_offset = SSmapping.station_start
var/list/bounds
for (var/path in SSmapping.config.GetFullMapPaths())
- var/datum/parsed_map/parsed = load_map(file(path), 1, 1, z_offset, measureOnly = FALSE, no_changeturf = FALSE, cropMap=TRUE, x_lower = mother1.x_low, y_lower = mother1.y_low, x_upper = mother1.x_high, y_upper = mother1.y_high)
+ var/datum/parsed_map/parsed = load_map(
+ file(path),
+ 1,
+ 1,
+ z_offset,
+ no_changeturf = FALSE,
+ crop_map = TRUE,
+ x_lower = mother1.x_low,
+ y_lower = mother1.y_low,
+ x_upper = mother1.x_high,
+ y_upper = mother1.y_high,
+ )
bounds = parsed?.bounds
z_offset += bounds[MAP_MAXZ] - bounds[MAP_MINZ] + 1
diff --git a/code/modules/projectiles/ammunition/_ammunition.dm b/code/modules/projectiles/ammunition/_ammunition.dm
index 9641adc29b56e7..e492afb776b752 100644
--- a/code/modules/projectiles/ammunition/_ammunition.dm
+++ b/code/modules/projectiles/ammunition/_ammunition.dm
@@ -30,8 +30,6 @@
var/click_cooldown_override = 0
///the visual effect appearing when the ammo is fired.
var/firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect
- ///Does this leave a casing behind?
- var/is_cased_ammo = TRUE
///pacifism check for boolet, set to FALSE if bullet is non-lethal
var/harmful = TRUE
@@ -148,8 +146,6 @@
return ..()
/obj/item/ammo_casing/proc/bounce_away(still_warm = FALSE, bounce_delay = 3)
- if(!is_cased_ammo)
- return
update_appearance()
SpinAnimation(10, 1)
var/turf/T = get_turf(src)
diff --git a/code/modules/projectiles/ammunition/ballistic/shotgun.dm b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
index e1f233d51f862e..53ff1f8a350419 100644
--- a/code/modules/projectiles/ammunition/ballistic/shotgun.dm
+++ b/code/modules/projectiles/ammunition/ballistic/shotgun.dm
@@ -106,12 +106,12 @@
/obj/item/ammo_casing/shotgun/improvised
name = "improvised shell"
- desc = "An extremely weak shotgun shell with multiple small pellets made out of metal shards."
+ desc = "A homemade shotgun casing filled with crushed glass, used to commmit vandalism and property damage."
icon_state = "improvshell"
projectile_type = /obj/projectile/bullet/pellet/shotgun_improvised
- custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*2.5)
- pellets = 10
- variance = 25
+ custom_materials = list(/datum/material/iron=SMALL_MATERIAL_AMOUNT*2, /datum/material/glass=SMALL_MATERIAL_AMOUNT*1)
+ pellets = 6
+ variance = 30
/obj/item/ammo_casing/shotgun/ion
name = "ion shell"
diff --git a/code/modules/projectiles/ammunition/energy/_energy.dm b/code/modules/projectiles/ammunition/energy/_energy.dm
index 788c0f7810ddc8..808cbdfbfe77c9 100644
--- a/code/modules/projectiles/ammunition/energy/_energy.dm
+++ b/code/modules/projectiles/ammunition/energy/_energy.dm
@@ -8,8 +8,4 @@
var/select_name = CALIBER_ENERGY
fire_sound = 'sound/weapons/laser.ogg'
firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/energy
- is_cased_ammo = FALSE
-
- //SKYRAT EDIT ADD - CELL LOADED GUNS
- var/select_color = FALSE //This is the color that shows up when selecting an ammo type. Disabled by default
- //SKYRAT EDIT ADD END
+ var/select_color = FALSE //SKYRAT EDIT ADDITION - This is the color that shows up when selecting an ammo type. Disabled by default
diff --git a/code/modules/projectiles/ammunition/energy/special.dm b/code/modules/projectiles/ammunition/energy/special.dm
index bedfab7b23574a..24fba4b9ba492a 100644
--- a/code/modules/projectiles/ammunition/energy/special.dm
+++ b/code/modules/projectiles/ammunition/energy/special.dm
@@ -74,3 +74,9 @@
select_name = "marksman nanoshot"
e_cost = 0
fire_sound = 'sound/weapons/gun/revolver/shot_alt.ogg'
+
+/obj/item/ammo_casing/energy/fisher
+ projectile_type = /obj/projectile/energy/fisher
+ select_name = "light-buster"
+ e_cost = 250
+ fire_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg' // fwip fwip fwip fwip
diff --git a/code/modules/projectiles/ammunition/special/magic.dm b/code/modules/projectiles/ammunition/special/magic.dm
index c6737fd3cabbbe..9135e3ec5b9475 100644
--- a/code/modules/projectiles/ammunition/special/magic.dm
+++ b/code/modules/projectiles/ammunition/special/magic.dm
@@ -4,7 +4,6 @@
slot_flags = null
projectile_type = /obj/projectile/magic
firing_effect_type = /obj/effect/temp_visual/dir_setting/firing_effect/magic
- is_cased_ammo = FALSE
/obj/item/ammo_casing/magic/change
projectile_type = /obj/projectile/magic/change
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 969004589228b5..e76af741a1840b 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -26,6 +26,7 @@
var/vary_fire_sound = TRUE
var/fire_sound_volume = 50
var/dry_fire_sound = 'sound/weapons/gun/general/dry_fire.ogg'
+ var/dry_fire_sound_volume = 30
var/suppressed = null //whether or not a message is displayed when fired
var/can_suppress = FALSE
var/suppressed_sound = 'sound/weapons/gun/general/heavy_shot_suppressed.ogg'
@@ -44,7 +45,7 @@
var/weapon_weight = WEAPON_LIGHT
var/dual_wield_spread = 24 //additional spread when dual wielding
///Can we hold up our target with this? Default to yes
- var/can_hold_up = TRUE
+ var/can_hold_up = FALSE // SKYRAT EDIT - DISABLED ORIGINAL: TRUE
/// Just 'slightly' snowflakey way to modify projectile damage for projectiles fired from this gun.
var/projectile_damage_multiplier = 1
@@ -169,7 +170,7 @@
/obj/item/gun/proc/shoot_with_empty_chamber(mob/living/user as mob|obj)
balloon_alert_to_viewers("*click*")
- playsound(src, dry_fire_sound, 30, TRUE)
+ playsound(src, dry_fire_sound, dry_fire_sound_volume, TRUE)
/obj/item/gun/proc/fire_sounds()
if(suppressed)
diff --git a/code/modules/projectiles/guns/ballistic.dm b/code/modules/projectiles/guns/ballistic.dm
index 61dcb1748e4528..576ba6395be72e 100644
--- a/code/modules/projectiles/guns/ballistic.dm
+++ b/code/modules/projectiles/guns/ballistic.dm
@@ -441,9 +441,6 @@
if (sawn_off)
bonus_spread += SAWN_OFF_ACC_PENALTY
- if(magazine && !chambered.is_cased_ammo)
- magazine.stored_ammo -= chambered
-
return ..()
/obj/item/gun/ballistic/shoot_live_shot(mob/living/user, pointblank = 0, atom/pbtarget = null, message = 1)
diff --git a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
index d9443719a7e3ef..5d33b3fce5170f 100644
--- a/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
+++ b/code/modules/projectiles/guns/ballistic/bows/bow_arrows.dm
@@ -11,7 +11,6 @@
throwforce = 1
firing_effect_type = null
caliber = CALIBER_ARROW
- is_cased_ammo = FALSE
///Whether the bullet type spawns another casing of the same type or not.
var/reusable = TRUE
diff --git a/code/modules/projectiles/guns/energy/beam_rifle.dm b/code/modules/projectiles/guns/energy/beam_rifle.dm
index 68479abbf47d7a..8869da14a59e33 100644
--- a/code/modules/projectiles/guns/energy/beam_rifle.dm
+++ b/code/modules/projectiles/guns/energy/beam_rifle.dm
@@ -30,7 +30,6 @@
ammo_type = list(/obj/item/ammo_casing/energy/beam_rifle/hitscan)
actions_types = list(/datum/action/item_action/zoom_lock_action)
cell_type = /obj/item/stock_parts/cell/beam_rifle
- canMouseDown = TRUE
var/aiming = FALSE
var/aiming_time = 12
var/aiming_time_fire_threshold = 5
@@ -72,8 +71,6 @@
var/current_zoom_x = 0
var/current_zoom_y = 0
- var/mob/listeningTo
-
/obj/item/gun/energy/beam_rifle/apply_fantasy_bonuses(bonus)
. = ..()
delay = modify_fantasy_variable("delay", delay, -bonus * 2)
@@ -179,7 +176,6 @@
STOP_PROCESSING(SSfastprocess, src)
set_user(null)
QDEL_LIST(current_tracers)
- listeningTo = null
return ..()
/obj/item/gun/energy/beam_rifle/emp_act(severity)
@@ -232,30 +228,28 @@
if(!istype(current_user) || !isturf(current_user.loc) || !(src in current_user.held_items) || current_user.incapacitated()) //Doesn't work if you're not holding it!
if(automatic_cleanup)
stop_aiming()
- set_user(null)
return FALSE
return TRUE
-/obj/item/gun/energy/beam_rifle/proc/process_aim()
- if(istype(current_user) && current_user.client && current_user.client.mouseParams)
- var/angle = mouse_angle_from_client(current_user.client)
- current_user.setDir(angle2dir_cardinal(angle))
- var/difference = abs(closer_angle_difference(lastangle, angle))
- delay_penalty(difference * aiming_time_increase_angle_multiplier)
- lastangle = angle
+/obj/item/gun/energy/beam_rifle/proc/process_aim(params)
+ var/angle = mouse_angle_from_client(current_user?.client, params)
+ current_user.setDir(angle2dir_cardinal(angle))
+ var/difference = abs(closer_angle_difference(lastangle, angle))
+ delay_penalty(difference * aiming_time_increase_angle_multiplier)
+ lastangle = angle
/obj/item/gun/energy/beam_rifle/proc/on_mob_move()
SIGNAL_HANDLER
check_user()
if(aiming)
delay_penalty(aiming_time_increase_user_movement)
- process_aim()
+ process_aim(current_user?.client?.mouseParams)
INVOKE_ASYNC(src, PROC_REF(aiming_beam), TRUE)
-/obj/item/gun/energy/beam_rifle/proc/start_aiming()
+/obj/item/gun/energy/beam_rifle/proc/start_aiming(params)
aiming_time_left = aiming_time
aiming = TRUE
- process_aim()
+ process_aim(params)
aiming_beam(TRUE)
zooming_angle = lastangle
start_zooming()
@@ -271,47 +265,65 @@
if(user == current_user)
return
stop_aiming(current_user)
- if(listeningTo)
- UnregisterSignal(listeningTo, COMSIG_MOVABLE_MOVED)
- listeningTo = null
if(istype(current_user))
+ unregister_client_signals(current_user)
+ UnregisterSignal(current_user, list(COMSIG_MOVABLE_MOVED, COMSIG_MOB_LOGIN, COMSIG_MOB_LOGOUT))
current_user = null
- if(istype(user))
- current_user = user
- RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_mob_move))
- listeningTo = user
+ if(!istype(user))
+ return
+ current_user = user
+ RegisterSignal(user, COMSIG_MOVABLE_MOVED, PROC_REF(on_mob_move))
+ RegisterSignal(user, COMSIG_MOB_LOGIN, PROC_REF(register_client_signals))
+ RegisterSignal(user, COMSIG_MOB_LOGOUT, PROC_REF(unregister_client_signals))
+ if(user.client)
+ register_client_signals(user)
+
+/obj/item/gun/energy/beam_rifle/proc/register_client_signals(mob/source)
+ SIGNAL_HANDLER
+ RegisterSignal(source.client, COMSIG_CLIENT_MOUSEDOWN, PROC_REF(on_mouse_down))
+
+/obj/item/gun/energy/beam_rifle/proc/unregister_client_signals(mob/source)
+ SIGNAL_HANDLER
+ stop_aiming()
+ if(QDELETED(source.client))
+ return
+ UnregisterSignal(source.client, list(COMSIG_CLIENT_MOUSEDOWN, COMSIG_CLIENT_MOUSEUP, COMSIG_CLIENT_MOUSEDRAG))
-/obj/item/gun/energy/beam_rifle/onMouseDrag(src_object, over_object, src_location, over_location, params, mob)
+///change the aiming beam angle to that of the mouse cursor.
+/obj/item/gun/energy/beam_rifle/proc/on_mouse_drag(client/source, src_object, over_object, src_location, over_location, src_control, over_control, params)
+ SIGNAL_HANDLER
if(aiming)
- process_aim()
- aiming_beam()
+ process_aim(params)
+ INVOKE_ASYNC(src, PROC_REF(aiming_beam))
if(zoom_lock == ZOOM_LOCK_AUTOZOOM_FREEMOVE)
zooming_angle = lastangle
set_autozoom_pixel_offsets_immediate(zooming_angle)
- return ..()
-/obj/item/gun/energy/beam_rifle/onMouseDown(object, location, params, mob/mob)
- if(istype(mob))
- set_user(mob)
- if(istype(object, /atom/movable/screen) && !istype(object, /atom/movable/screen/click_catcher))
+///Start aiming and charging the beam
+/obj/item/gun/energy/beam_rifle/proc/on_mouse_down(client/source, atom/movable/object, location, control, params)
+ SIGNAL_HANDLER
+ if(source.mob.get_active_held_item() != src)
return
- if((object in mob.contents) || (object == mob))
+ if(!object.IsAutoclickable() || (object in source.mob.contents) || (object == source.mob))
return
- start_aiming()
- return ..()
+ INVOKE_ASYNC(src, PROC_REF(start_aiming), params)
+ RegisterSignal(source, COMSIG_CLIENT_MOUSEDRAG, PROC_REF(on_mouse_drag))
+ RegisterSignal(source, COMSIG_CLIENT_MOUSEUP, PROC_REF(on_mouse_up))
-/obj/item/gun/energy/beam_rifle/onMouseUp(object, location, params, mob/M)
- if(istype(object, /atom/movable/screen) && !istype(object, /atom/movable/screen/click_catcher))
+///Stop aiming and fire the beam if charged enough
+/obj/item/gun/energy/beam_rifle/proc/on_mouse_up(client/source, atom/movable/object, location, control, params)
+ SIGNAL_HANDLER
+ if(!object.IsAutoclickable())
return
- process_aim()
+ process_aim(params)
+ UnregisterSignal(source, list(COMSIG_CLIENT_MOUSEDRAG, COMSIG_CLIENT_MOUSEUP))
if(aiming_time_left <= aiming_time_fire_threshold && check_user())
sync_ammo()
- var/atom/target = M.client.mouse_object_ref?.resolve()
+ var/atom/target = source.mouse_object_ref?.resolve()
if(target)
- afterattack(target, M, FALSE, M.client.mouseParams, passthrough = TRUE)
+ INVOKE_ASYNC(src, PROC_REF(afterattack), target, source.mob, FALSE, source.mouseParams, passthrough = TRUE)
stop_aiming()
QDEL_LIST(current_tracers)
- return ..()
/obj/item/gun/energy/beam_rifle/afterattack(atom/target, mob/living/user, flag, params, passthrough = FALSE)
. |= AFTERATTACK_PROCESSED_ITEM
diff --git a/code/modules/projectiles/guns/energy/recharge.dm b/code/modules/projectiles/guns/energy/recharge.dm
index 6644accd8a579c..eed27478755480 100644
--- a/code/modules/projectiles/guns/energy/recharge.dm
+++ b/code/modules/projectiles/guns/energy/recharge.dm
@@ -135,3 +135,36 @@
custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT*2)
suppressed = null
ammo_type = list(/obj/item/ammo_casing/energy/bolt/large)
+
+/// A silly gun that does literally zero damage, but disrupts electrical sources of light, like flashlights.
+/obj/item/gun/energy/recharge/fisher
+ name = "\improper SC/FISHER disruptor"
+ desc = "A self-recharging, permanently suppressed, and very haphazardly modified accelerator handgun that does literally nothing to anything except light fixtures and cameras. \
+ Can fire twice before requiring a recharge, with bolts passing through machinery, but demands precision."
+ icon_state = "fisher"
+ base_icon_state = "fisher"
+ dry_fire_sound_volume = 10
+ w_class = WEIGHT_CLASS_SMALL
+ holds_charge = TRUE
+ suppressed = TRUE
+ recharge_time = 1.2 SECONDS
+ ammo_type = list(/obj/item/ammo_casing/energy/fisher)
+
+/obj/item/gun/energy/recharge/fisher/examine_more(mob/user)
+ . = ..()
+ . += span_notice("The SC/FISHER is an illegally-modified kinetic accelerator cut down and refit into a disassembled miniature energy gun chassis, with its pressure chamber \
+ attenuated to launch kinetic bolts that disrupt flashlights and cameras, if only temporarily. This effect also works on cyborg headlamps, and works longer in melee.
\
+ While some would argue that this is a really terrible design choice, others argue that it is very funny to be able to shoot at light sources. Caveat emptor.")
+
+/obj/item/gun/energy/recharge/fisher/afterattack(atom/target, mob/living/user, flag, params)
+ // you should just shoot them, but in case you can't/wont
+ . = ..()
+ if(user.Adjacent(target))
+ var/obj/projectile/energy/fisher/melee/simulated_hit = new
+ simulated_hit.on_hit(target)
+
+/obj/item/gun/energy/recharge/fisher/throw_impact(atom/hit_atom, datum/thrownthing/throwingdatum)
+ // ...you reeeeeally just shoot them, but in case you can't/won't
+ . = ..()
+ var/obj/projectile/energy/fisher/melee/simulated_hit = new
+ simulated_hit.on_hit(hit_atom)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index 60bec6ac3b0803..2f5fa84c4419c5 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -231,12 +231,12 @@
SEND_SIGNAL(src, COMSIG_PROJECTILE_RANGE_OUT)
qdel(src)
-//to get the correct limb (if any) for the projectile hit message
-/mob/living/proc/check_limb_hit(hit_zone)
+/// Returns the string form of the def_zone we have hit.
+/mob/living/proc/check_hit_limb_zone_name(hit_zone)
if(has_limbs)
return hit_zone
-/mob/living/carbon/check_limb_hit(hit_zone)
+/mob/living/carbon/check_hit_limb_zone_name(hit_zone)
if(get_bodypart(hit_zone))
return hit_zone
else //when a limb is missing the damage is actually passed to the chest
@@ -250,21 +250,20 @@
* blocked - percentage of hit blocked
* pierce_hit - are we piercing through or regular hitting
*/
-/* SKYRAT EDIT REMOVAL - MOVED TO MASTER_FILES PROJECTILE.DM
/obj/projectile/proc/on_hit(atom/target, blocked = FALSE, pierce_hit)
// i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
// maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM
- var/obj/item/bodypart/hit_limb
+ var/hit_limb_zone
if(isliving(target))
var/mob/living/L = target
- hit_limb = L.check_limb_hit(def_zone)
+ hit_limb_zone = L.check_hit_limb_zone_name(def_zone)
if(fired_from)
- SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle, hit_limb)
- SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb)
+ SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle, hit_limb_zone)
+ SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb_zone)
if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason
return
- var/turf/target_loca = get_turf(target)
+ var/turf/target_turf = get_turf(target)
var/hitx
var/hity
@@ -275,66 +274,89 @@
hitx = target.pixel_x + rand(-8, 8)
hity = target.pixel_y + rand(-8, 8)
- if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75))
- var/turf/closed/wall/W = target_loca
+ // SKYRAT EDIT ADDITION BEGIN - IMPACT SOUNDS
+ var/impact_sound
+ if(hitsound)
+ impact_sound = hitsound
+ else
+ impact_sound = target.impact_sound
+ get_sfx()
+ playsound(src, get_sfx_skyrat(impact_sound), vol_by_damage(), TRUE, -1)
+ // SKYRAT EDIT ADDITION END
+
+ if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_turf) && prob(75))
+ var/turf/closed/wall/target_wall = target_turf
if(impact_effect_type && !hitscan)
- new impact_effect_type(target_loca, hitx, hity)
+ new impact_effect_type(target_wall, hitx, hity)
- W.add_dent(WALL_DENT_SHOT, hitx, hity)
+ target_wall.add_dent(WALL_DENT_SHOT, hitx, hity)
return BULLET_ACT_HIT
if(!isliving(target))
if(impact_effect_type && !hitscan)
- new impact_effect_type(target_loca, hitx, hity)
+ new impact_effect_type(target_turf, hitx, hity)
+ /* SKYRAT EDIT REMOVAL - IMPACT SOUNDS
if(isturf(target) && hitsound_wall)
var/volume = clamp(vol_by_damage() + 20, 0, 100)
if(suppressed)
volume = 5
playsound(loc, hitsound_wall, volume, TRUE, -1)
+ SKYRAT EDIT REMOVAL END */
return BULLET_ACT_HIT
- var/mob/living/L = target
+ var/mob/living/living_target = target
if(blocked != 100) // not completely blocked
- if(damage && L.blood_volume && damage_type == BRUTE)
- var/splatter_dir = dir
- if(starting)
- splatter_dir = get_dir(starting, target_loca)
- if(isalien(L))
- new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
- else
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
- if(prob(33))
- L.add_splatter_floor(target_loca)
+ var/obj/item/bodypart/hit_bodypart = living_target.get_bodypart(hit_limb_zone)
+ if (damage)
+ if (living_target.blood_volume && damage_type == BRUTE && (isnull(hit_bodypart) || hit_bodypart.can_bleed()))
+ var/splatter_dir = dir
+ if(starting)
+ splatter_dir = get_dir(starting, target_turf)
+ if(isalien(living_target))
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_turf, splatter_dir)
+ else
+ new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_turf, splatter_dir)
+ if(prob(33))
+ living_target.add_splatter_floor(target_turf)
+ else if (!isnull(hit_bodypart) && (hit_bodypart.biological_state & (BIO_METAL|BIO_WIRED)))
+ var/random_damage_mult = RANDOM_DECIMAL(0.85, 1.15) // SOMETIMES you can get more or less sparks
+ var/damage_dealt = ((damage / (1 - (blocked / 100))) * random_damage_mult)
+
+ var/spark_amount = round((damage_dealt / PROJECTILE_DAMAGE_PER_ROBOTIC_SPARK))
+ if (spark_amount > 0)
+ do_sparks(spark_amount, FALSE, living_target)
+
else if(impact_effect_type && !hitscan)
- new impact_effect_type(target_loca, hitx, hity)
+ new impact_effect_type(target_turf, hitx, hity)
var/organ_hit_text = ""
- var/limb_hit = hit_limb
- if(limb_hit)
- organ_hit_text = " in \the [parse_zone(limb_hit)]"
+ if(hit_limb_zone)
+ organ_hit_text = " in \the [parse_zone(hit_limb_zone)]"
if(suppressed == SUPPRESSED_VERY)
- playsound(loc, hitsound, 5, TRUE, -1)
+ //playsound(loc, hitsound, 5, TRUE, -1) SKYRAT EDIT REMOVAL - IMPACT SOUNDS
else if(suppressed)
- playsound(loc, hitsound, 5, TRUE, -1)
- to_chat(L, span_userdanger("You're shot by \a [src][organ_hit_text]!"))
+ //playsound(loc, hitsound, 5, TRUE, -1) SKYRAT EDIT REMOVAL - IMPACT SOUNDS
+ to_chat(living_target, span_userdanger("You're shot by \a [src][organ_hit_text]!"))
else
+ /* SKYRAT EDIT REMOVAL - IMPACT SOUNDS
if(hitsound)
var/volume = vol_by_damage()
playsound(src, hitsound, volume, TRUE, -1)
- L.visible_message(span_danger("[L] is hit by \a [src][organ_hit_text]!"), \
+ SKYRAT EDIT REMOVAL END */
+ living_target.visible_message(span_danger("[living_target] is hit by \a [src][organ_hit_text]!"), \
span_userdanger("You're hit by \a [src][organ_hit_text]!"), null, COMBAT_MESSAGE_RANGE)
- if(L.is_blind())
- to_chat(L, span_userdanger("You feel something hit you[organ_hit_text]!"))
- L.on_hit(src)
+ if(living_target.is_blind())
+ to_chat(living_target, span_userdanger("You feel something hit you[organ_hit_text]!"))
+ living_target.on_hit(src)
var/reagent_note
if(reagents?.reagent_list)
reagent_note = "REAGENTS: [pretty_string_from_reagent_list(reagents.reagent_list)]"
if(ismob(firer))
- log_combat(firer, L, "shot", src, reagent_note)
+ log_combat(firer, living_target, "shot", src, reagent_note)
return BULLET_ACT_HIT
if(isvehicle(firer))
@@ -344,12 +366,12 @@
if(!LAZYLEN(logging_mobs))
logging_mobs = firing_vehicle.return_drivers()
for(var/mob/logged_mob as anything in logging_mobs)
- log_combat(logged_mob, L, "shot", src, "from inside [firing_vehicle][logging_mobs.len > 1 ? " with multiple occupants" : null][reagent_note ? " and contained [reagent_note]" : null]")
+ log_combat(logged_mob, living_target, "shot", src, "from inside [firing_vehicle][logging_mobs.len > 1 ? " with multiple occupants" : null][reagent_note ? " and contained [reagent_note]" : null]")
return BULLET_ACT_HIT
- L.log_message("has been shot by [firer] with [src][reagent_note ? " containing [reagent_note]" : null]", LOG_VICTIM, color="orange")
+ living_target.log_message("has been shot by [firer] with [src][reagent_note ? " containing [reagent_note]" : null]", LOG_ATTACK, color="orange")
return BULLET_ACT_HIT
-*/
+
/obj/projectile/proc/vol_by_damage()
if(src.damage)
return clamp((src.damage) * 0.67, 30, 100)// Multiply projectile damage by 0.67, then CLAMP the value between 30 and 100
diff --git a/code/modules/projectiles/projectile/bullets/shotgun.dm b/code/modules/projectiles/projectile/bullets/shotgun.dm
index 446654a3489b7d..639939e150fb78 100644
--- a/code/modules/projectiles/projectile/bullets/shotgun.dm
+++ b/code/modules/projectiles/projectile/bullets/shotgun.dm
@@ -118,14 +118,13 @@
embedding = null
/obj/projectile/bullet/pellet/shotgun_improvised
- tile_dropoff = 0.35 //Come on it does 6 damage don't be like that.
- damage = 6
- wound_bonus = 0
- bare_wound_bonus = 7.5
+ damage = 5
+ wound_bonus = -5
+ demolition_mod = 3 //Very good at acts of vandalism
/obj/projectile/bullet/pellet/shotgun_improvised/Initialize(mapload)
. = ..()
- range = rand(1, 8)
+ range = rand(3, 8)
/obj/projectile/bullet/pellet/shotgun_improvised/on_range()
do_sparks(1, TRUE, src)
diff --git a/code/modules/projectiles/projectile/special/lightbreaker.dm b/code/modules/projectiles/projectile/special/lightbreaker.dm
new file mode 100644
index 00000000000000..fd7d3d89e7a97e
--- /dev/null
+++ b/code/modules/projectiles/projectile/special/lightbreaker.dm
@@ -0,0 +1,35 @@
+/obj/projectile/energy/fisher
+ name = "attenuated kinetic force"
+ alpha = 0
+ damage = 0
+ damage_type = BRUTE
+ armor_flag = BOMB
+ range = 14
+ projectile_phasing = PASSTABLE | PASSMOB | PASSMACHINE | PASSSTRUCTURE
+ hitscan = TRUE
+ var/disrupt_duration = 10 SECONDS
+
+/obj/projectile/energy/fisher/on_hit(atom/target, blocked, pierce_hit)
+ . = ..()
+ var/lights_flickered = 0
+ if(SEND_SIGNAL(target, COMSIG_HIT_BY_SABOTEUR, disrupt_duration) & COMSIG_SABOTEUR_SUCCESS)
+ lights_flickered++
+ if(!isliving(target))
+ return
+ var/list/things_to_disrupt = list()
+ if(ishuman(target))
+ var/mob/living/carbon/human/human_target = target
+ things_to_disrupt = human_target.get_all_gear()
+ else
+ var/mob/living/living_target = target // i guess this covers borgs too?
+ things_to_disrupt = living_target.get_equipped_items(include_pockets = TRUE, include_accessories = TRUE)
+ for(var/obj/item/thingy as anything in things_to_disrupt)
+ if(SEND_SIGNAL(thingy, COMSIG_HIT_BY_SABOTEUR, disrupt_duration) & COMSIG_SABOTEUR_SUCCESS)
+ lights_flickered++
+ if(lights_flickered)
+ to_chat(target, span_warning("Your light [lights_flickered > 1 ? "sources flick" : "source flicks"] off."))
+
+/obj/projectile/energy/fisher/melee
+ range = 1
+ suppressed = SUPPRESSED_VERY
+ disrupt_duration = 20 SECONDS
diff --git a/code/modules/reagents/chemistry/holder.dm b/code/modules/reagents/chemistry/holder.dm
index cb8a30f61b83c9..902ccf35e6ac43 100644
--- a/code/modules/reagents/chemistry/holder.dm
+++ b/code/modules/reagents/chemistry/holder.dm
@@ -82,10 +82,10 @@
if(reagent_to_react_count[reagent_id] < reagent_to_react_count[preferred_id])
preferred_id = reagent_id
continue
-
- if(!reaction_lookup[preferred_id])
- reaction_lookup[preferred_id] = list()
- reaction_lookup[preferred_id] += reaction
+ if (preferred_id != null)
+ if(!reaction_lookup[preferred_id])
+ reaction_lookup[preferred_id] = list()
+ reaction_lookup[preferred_id] += reaction
for(var/datum/chemical_reaction/reaction as anything in reactions)
var/list/product_ids = list()
@@ -1391,9 +1391,7 @@
/// Is this holder full or not
/datum/reagents/proc/holder_full()
- if(total_volume >= maximum_volume)
- return TRUE
- return FALSE
+ return total_volume >= maximum_volume
/// Get the amount of this reagent
/datum/reagents/proc/get_reagent_amount(reagent, include_subtypes = FALSE)
@@ -1420,6 +1418,12 @@
return round(cached_reagent.purity, 0.01)
return 0
+/// Directly set the purity of all contained reagents to a new value
+/datum/reagents/proc/set_all_reagents_purity(new_purity = 0)
+ var/list/cached_reagents = reagent_list
+ for(var/datum/reagent/cached_reagent as anything in cached_reagents)
+ cached_reagent.purity = max(0, new_purity)
+
/// Get the average purity of all reagents (or all subtypes of provided typepath)
/datum/reagents/proc/get_average_purity(parent_type = null)
var/total_amount
diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
index dc0c51edd533a8..26ebfa1d18d9fc 100644
--- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
+++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
@@ -99,11 +99,9 @@
. = ..()
if(. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN)
return
- if(!can_interact(user) || !user.can_perform_action(src, ALLOW_SILICON_REACH|FORBID_TELEKINESIS_REACH))
+ if(operating || !user.can_perform_action(src, ALLOW_SILICON_REACH | FORBID_TELEKINESIS_REACH))
return
- if(operating)
- return
- replace_beaker(user)
+ eject(user)
return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
/obj/machinery/reagentgrinder/attack_robot_secondary(mob/user, list/modifiers)
@@ -146,24 +144,28 @@
default_unfasten_wrench(user, tool)
return TOOL_ACT_TOOLTYPE_SUCCESS
-/obj/machinery/reagentgrinder/attackby(obj/item/I, mob/living/user, params)
- //You can only screw open empty grinder
- if(!beaker && !length(holdingitems) && default_deconstruction_screwdriver(user, icon_state, icon_state, I))
- return
+/obj/machinery/reagentgrinder/screwdriver_act(mob/living/user, obj/item/tool)
+ . = TOOL_ACT_TOOLTYPE_SUCCESS
+ if(!beaker && !length(holdingitems))
+ return default_deconstruction_screwdriver(user, icon_state, icon_state, tool)
- if(default_deconstruction_crowbar(I))
- return
+/obj/machinery/reagentgrinder/crowbar_act(mob/living/user, obj/item/tool)
+ return default_deconstruction_crowbar(tool)
+/obj/machinery/reagentgrinder/attackby(obj/item/weapon, mob/living/user, params)
if(panel_open) //Can't insert objects when its screwed open
return TRUE
- if (is_reagent_container(I) && !(I.item_flags & ABSTRACT) && I.is_open_container())
- var/obj/item/reagent_containers/B = I
+ if(!weapon.grind_requirements(src)) //Error messages should be in the objects' definitions
+ return
+
+ if (is_reagent_container(weapon) && !(weapon.item_flags & ABSTRACT) && weapon.is_open_container())
+ var/obj/item/reagent_containers/container = weapon
. = TRUE //no afterattack
- if(!user.transferItemToLoc(B, src))
+ if(!user.transferItemToLoc(container, src))
return
- replace_beaker(user, B)
- to_chat(user, span_notice("You add [B] to [src]."))
+ replace_beaker(user, container)
+ to_chat(user, span_notice("You add [container] to [src]."))
update_appearance()
return TRUE //no afterattack
@@ -172,39 +174,36 @@
return TRUE
//Fill machine with a bag!
- if(istype(I, /obj/item/storage/bag))
- if(!I.contents.len)
- to_chat(user, span_notice("[I] is empty!"))
+ if(istype(weapon, /obj/item/storage/bag))
+ if(!weapon.contents.len)
+ to_chat(user, span_notice("[weapon] is empty!"))
return TRUE
var/list/inserted = list()
- if(I.atom_storage.remove_type(/obj/item/food/grown, src, limit - length(holdingitems), TRUE, FALSE, user, inserted))
+ if(weapon.atom_storage.remove_type(/obj/item/food/grown, src, limit - length(holdingitems), TRUE, FALSE, user, inserted))
for(var/i in inserted)
holdingitems[i] = TRUE
inserted = list()
- if(I.atom_storage.remove_type(/obj/item/food/honeycomb, src, limit - length(holdingitems), TRUE, FALSE, user, inserted))
+ if(weapon.atom_storage.remove_type(/obj/item/food/honeycomb, src, limit - length(holdingitems), TRUE, FALSE, user, inserted))
for(var/i in inserted)
holdingitems[i] = TRUE
- if(!I.contents.len)
- to_chat(user, span_notice("You empty [I] into [src]."))
+ if(!weapon.contents.len)
+ to_chat(user, span_notice("You empty [weapon] into [src]."))
else
to_chat(user, span_notice("You fill [src] to the brim."))
return TRUE
- if(!I.grind_results && !I.juice_typepath)
+ if(!weapon.grind_results && !weapon.juice_typepath)
if(user.combat_mode)
return ..()
else
- to_chat(user, span_warning("You cannot grind [I] into reagents!"))
+ to_chat(user, span_warning("You cannot grind/juice [weapon] into reagents!"))
return TRUE
- if(!I.grind_requirements(src)) //Error messages should be in the objects' definitions
- return
-
- if(user.transferItemToLoc(I, src))
- to_chat(user, span_notice("You add [I] to [src]."))
- holdingitems[I] = TRUE
+ if(user.transferItemToLoc(weapon, src))
+ to_chat(user, span_notice("You add [weapon] to [src]."))
+ holdingitems[weapon] = TRUE
return FALSE
/obj/machinery/reagentgrinder/ui_interact(mob/user) // The microwave Menu //I am reasonably certain that this is not a microwave
@@ -261,9 +260,9 @@
if(beaker)
replace_beaker(user)
-/obj/machinery/reagentgrinder/proc/remove_object(obj/item/O)
- holdingitems -= O
- qdel(O)
+/obj/machinery/reagentgrinder/proc/remove_object(obj/item/weapon)
+ holdingitems -= weapon
+ qdel(weapon)
/obj/machinery/reagentgrinder/proc/start_shaking()
var/static/list/transforms
@@ -306,11 +305,11 @@
/obj/machinery/reagentgrinder/proc/juice(mob/user)
power_change()
- if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.total_volume >= beaker.reagents.maximum_volume)
+ if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.holder_full())
return
operate_for(50, juicing = TRUE)
for(var/obj/item/i in holdingitems)
- if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume)
+ if(beaker.reagents.holder_full())
break
var/obj/item/I = i
if(I.juice_typepath)
@@ -324,12 +323,12 @@
/obj/machinery/reagentgrinder/proc/grind(mob/user)
power_change()
- if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.total_volume >= beaker.reagents.maximum_volume)
+ if(!beaker || machine_stat & (NOPOWER|BROKEN) || beaker.reagents.holder_full())
return
operate_for(60)
warn_of_dust() // don't breathe this.
for(var/i in holdingitems)
- if(beaker.reagents.total_volume >= beaker.reagents.maximum_volume)
+ if(beaker.reagents.holder_full())
break
var/obj/item/I = i
if(I.grind_results)
@@ -337,7 +336,10 @@
/obj/machinery/reagentgrinder/proc/grind_item(obj/item/I, mob/user) //Grind results can be found in respective object definitions
if(!I.grind(beaker.reagents, user))
- to_chat(usr, span_danger("[src] shorts out as it tries to grind up [I], and transfers it back to storage."))
+ if(isstack(I))
+ to_chat(usr, span_notice("[src] attempts to grind as many pieces of [I] as possible."))
+ else
+ to_chat(usr, span_danger("[src] shorts out as it tries to grind up [I], and transfers it back to storage."))
return
remove_object(I)
@@ -347,19 +349,21 @@
if(!beaker || machine_stat & (NOPOWER|BROKEN))
return
operate_for(50, juicing = TRUE)
- addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/machinery/reagentgrinder, mix_complete)), 50)
+ addtimer(CALLBACK(src, PROC_REF(mix_complete)), 50 / speed)
/obj/machinery/reagentgrinder/proc/mix_complete()
- if(beaker?.reagents.total_volume)
- //Recipe to make Butter
- var/butter_amt = FLOOR(beaker.reagents.get_reagent_amount(/datum/reagent/consumable/milk) / MILK_TO_BUTTER_COEFF, 1)
- var/purity = beaker.reagents.get_reagent_purity(/datum/reagent/consumable/milk)
- beaker.reagents.remove_reagent(/datum/reagent/consumable/milk, MILK_TO_BUTTER_COEFF * butter_amt)
- for(var/i in 1 to butter_amt)
- new /obj/item/food/butter(/* loc = */ drop_location(), /* starting_reagent_purity = */ purity)
- //Recipe to make Mayonnaise
- if (beaker.reagents.has_reagent(/datum/reagent/consumable/eggyolk))
- beaker.reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise)
- //Recipe to make whipped cream
- if (beaker.reagents.has_reagent(/datum/reagent/consumable/cream))
- beaker.reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream)
+ if(beaker?.reagents.total_volume <= 0)
+ return
+ //Recipe to make Butter
+ var/butter_amt = FLOOR(beaker.reagents.get_reagent_amount(/datum/reagent/consumable/milk) / MILK_TO_BUTTER_COEFF, 1)
+ var/purity = beaker.reagents.get_reagent_purity(/datum/reagent/consumable/milk)
+ beaker.reagents.remove_reagent(/datum/reagent/consumable/milk, MILK_TO_BUTTER_COEFF * butter_amt)
+ for(var/i in 1 to butter_amt)
+ var/obj/item/food/butter/tasty_butter = new(drop_location())
+ tasty_butter.reagents.set_all_reagents_purity(purity)
+ //Recipe to make Mayonnaise
+ if (beaker.reagents.has_reagent(/datum/reagent/consumable/eggyolk))
+ beaker.reagents.convert_reagent(/datum/reagent/consumable/eggyolk, /datum/reagent/consumable/mayonnaise)
+ //Recipe to make whipped cream
+ if (beaker.reagents.has_reagent(/datum/reagent/consumable/cream))
+ beaker.reagents.convert_reagent(/datum/reagent/consumable/cream, /datum/reagent/consumable/whipped_cream)
diff --git a/code/modules/reagents/chemistry/reagents.dm b/code/modules/reagents/chemistry/reagents.dm
index 016253baa3e900..8de1d98cc2e4e3 100644
--- a/code/modules/reagents/chemistry/reagents.dm
+++ b/code/modules/reagents/chemistry/reagents.dm
@@ -162,6 +162,10 @@ GLOBAL_LIST_INIT(name2reagent, build_name2reagent())
return
holder.remove_reagent(type, metabolization_rate * M.metabolism_efficiency * seconds_per_tick) //By default it slowly disappears.
+/// Called in burns.dm *if* the reagent has the REAGENT_AFFECTS_WOUNDS process flag
+/datum/reagent/proc/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound)
+ return
+
/*
Used to run functions before a reagent is transferred. Returning TRUE will block the transfer attempt.
Primarily used in reagents/reaction_agents
diff --git a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
index 2299cab54933e8..817e5ed98bfe55 100644
--- a/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/atmos_gas_reagents.dm
@@ -52,7 +52,8 @@
breather.adjustFireLoss(-2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype)
breather.adjustToxLoss(-5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
breather.adjustBruteLoss(-2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype)
- return ..()
+ . = ..()
+ return TRUE
/datum/reagent/hypernoblium
name = "Hyper-Noblium"
@@ -90,7 +91,8 @@
/datum/reagent/nitrium_high_metabolization/on_mob_life(mob/living/carbon/breather, seconds_per_tick, times_fired)
breather.adjustStaminaLoss(-2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
breather.adjustToxLoss(0.1 * current_cycle * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) // 1 toxin damage per cycle at cycle 10
- return ..()
+ . = ..()
+ return TRUE
/datum/reagent/nitrium_low_metabolization
name = "Nitrium"
@@ -123,13 +125,15 @@
if(!HAS_TRAIT(breather, TRAIT_KNOCKEDOUT))
return ..()
+ . = ..()
for(var/obj/item/organ/organ_being_healed as anything in breather.organs)
if(!organ_being_healed.damage)
continue
organ_being_healed.apply_organ_damage(-0.5 * REM * seconds_per_tick, required_organ_flag = ORGAN_ORGANIC)
+ . = TRUE
- return ..()
+ return .
/datum/reagent/zauker
name = "Zauker"
@@ -147,4 +151,5 @@
breather.adjustOxyLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type)
breather.adjustFireLoss(2 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype)
breather.adjustToxLoss(2 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
- return ..()
+ ..()
+ return TRUE
diff --git a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
index 665ecb2577e2a5..693404431faa10 100644
--- a/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/cat2_medicine_reagents.dm
@@ -46,12 +46,11 @@
if(good_kind_of_healing && !reaping && SPT_PROB(0.00005, seconds_per_tick)) //janken with the grim reaper!
notify_ghosts("[affected_mob] has entered a game of rock-paper-scissors with death!", source = affected_mob, action = NOTIFY_ORBIT, header = "Who Will Win?")
reaping = TRUE
- var/list/RockPaperScissors = list("rock" = "paper", "paper" = "scissors", "scissors" = "rock") //choice = loses to
if(affected_mob.apply_status_effect(/datum/status_effect/necropolis_curse, CURSE_BLINDING))
helbent = TRUE
to_chat(affected_mob, span_hierophant("Malevolent spirits appear before you, bartering your life in a 'friendly' game of rock, paper, scissors. Which do you choose?"))
var/timeisticking = world.time
- var/RPSchoice = tgui_alert(affected_mob, "Janken Time! You have 60 Seconds to Choose!", "Rock Paper Scissors", RockPaperScissors, 60)
+ var/RPSchoice = tgui_alert(affected_mob, "Janken Time! You have 60 Seconds to Choose!", "Rock Paper Scissors", list("rock" , "paper" , "scissors"), 60)
if(QDELETED(affected_mob) || (timeisticking+(1.1 MINUTES) < world.time))
reaping = FALSE
return //good job, you ruined it
@@ -59,21 +58,21 @@
to_chat(affected_mob, span_hierophant("You decide to not press your luck, but the spirits remain... hopefully they'll go away soon."))
reaping = FALSE
return
- var/grim = pick(RockPaperScissors)
- if(grim == RPSchoice) //You Tied!
- to_chat(affected_mob, span_hierophant("You tie, and the malevolent spirits disappear... for now."))
- reaping = FALSE
- else if(RockPaperScissors[RPSchoice] == grim) //You lost!
- to_chat(affected_mob, span_hierophant("You lose, and the malevolent spirits smirk eerily as they surround your body."))
- affected_mob.investigate_log("has lost rock paper scissors with the grim reaper and been dusted.", INVESTIGATE_DEATHS)
- affected_mob.dust()
- return
- else //VICTORY ROYALE
- to_chat(affected_mob, span_hierophant("You win, and the malevolent spirits fade away as well as your wounds."))
- affected_mob.client.give_award(/datum/award/achievement/jobs/helbitaljanken, affected_mob)
- affected_mob.revive(HEAL_ALL)
- holder.del_reagent(type)
- return
+ switch(rand(1,3))
+ if(1) //You Tied!
+ to_chat(affected_mob, span_hierophant("You tie, and the malevolent spirits disappear... for now."))
+ reaping = FALSE
+ if(2) //You lost!
+ to_chat(affected_mob, span_hierophant("You lose, and the malevolent spirits smirk eerily as they surround your body."))
+ affected_mob.investigate_log("has lost rock paper scissors with the grim reaper and been dusted.", INVESTIGATE_DEATHS)
+ affected_mob.dust()
+ return
+ if(3) //VICTORY ROYALE
+ to_chat(affected_mob, span_hierophant("You win, and the malevolent spirits fade away as well as your wounds."))
+ affected_mob.client.give_award(/datum/award/achievement/jobs/helbitaljanken, affected_mob)
+ affected_mob.revive(HEAL_ALL)
+ holder.del_reagent(type)
+ return
..()
return
@@ -554,6 +553,7 @@
H.set_heartattack(TRUE)
volume = 0
. = ..()
+ return TRUE
/datum/reagent/medicine/c2/penthrite/on_mob_end_metabolize(mob/living/user)
user.clear_alert("penthrite")
diff --git a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
index d40ca4fd85b15d..f67b813be0ccf1 100644
--- a/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drinks/alcohol_reagents.dm
@@ -8,6 +8,7 @@
nutriment_factor = 0
taste_description = "alcohol"
metabolization_rate = 0.5 * REAGENTS_METABOLISM
+ creation_purity = 1 // impure base reagents are a big no-no
ph = 7.33
burning_temperature = 2193//ethanol burns at 1970C (at it's peak)
burning_volume = 0.1
@@ -2143,7 +2144,7 @@
if(SPT_PROB(5, seconds_per_tick))
stored_teleports += rand(2, 6)
if(prob(70))
- drinker.vomit(vomit_type = VOMIT_PURPLE)
+ drinker.vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, vomit_type = /obj/effect/decal/cleanable/vomit/purple)
return ..()
/datum/reagent/consumable/ethanol/planet_cracker
diff --git a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
index 83aa18eebb51c1..36444d6229b812 100644
--- a/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drinks/drink_reagents.dm
@@ -296,10 +296,39 @@
affected_mob.AdjustSleeping(-20 * REM * seconds_per_tick)
if(affected_mob.getToxLoss() && SPT_PROB(10, seconds_per_tick))
affected_mob.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype)
+ var/to_chatted = FALSE
+ for(var/datum/wound/iter_wound as anything in affected_mob.all_wounds)
+ if(SPT_PROB(10, seconds_per_tick))
+ var/helped = iter_wound.tea_life_process()
+ if(!to_chatted && helped)
+ to_chat(affected_mob, span_notice("A calm, relaxed feeling suffuses you. Your wounds feel a little healthier."))
+ to_chatted = TRUE
affected_mob.adjust_bodytemperature(20 * REM * TEMPERATURE_DAMAGE_COEFFICIENT * seconds_per_tick, 0, affected_mob.get_body_temp_normal())
..()
. = TRUE
+// Different handling, different name.
+// Returns FALSE by default so broken bones and 'loss' wounds don't give a false message
+/datum/wound/proc/tea_life_process()
+ return FALSE
+
+// Slowly increase (gauzed) clot rate
+/datum/wound/pierce/bleed/tea_life_process()
+ gauzed_clot_rate += 0.1
+ return TRUE
+
+// Slowly increase clot rate
+/datum/wound/slash/flesh/tea_life_process()
+ clot_rate += 0.2
+ return TRUE
+
+// There's a designated burn process, but I felt this would be better for consistency with the rest of the reagent's procs
+/datum/wound/burn/flesh/tea_life_process()
+ // Sanitizes and heals, but with a limit
+ flesh_healing = (flesh_healing > 0.1) ? flesh_healing : flesh_healing + 0.02
+ infestation_rate = max(infestation_rate - 0.005, 0)
+ return TRUE
+
/datum/reagent/consumable/lemonade
name = "Lemonade"
description = "Sweet, tangy lemonade. Good for the soul."
diff --git a/code/modules/reagents/chemistry/reagents/drug_reagents.dm b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
index 060351a8c88ca1..23d6010f1a94bb 100644
--- a/code/modules/reagents/chemistry/reagents/drug_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/drug_reagents.dm
@@ -122,6 +122,7 @@
affected_human.set_hairstyle("Bald", update = FALSE)
affected_mob.set_species(/datum/species/human/krokodil_addict)
affected_mob.adjustBruteLoss(50 * REM, FALSE, required_bodytype = affected_bodytype) // holy shit your skin just FELL THE FUCK OFF
+ . = TRUE
..()
/datum/reagent/drug/krokodil/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
@@ -324,17 +325,17 @@
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
addiction_types = list(/datum/addiction/stimulants = 6) //2.6 per 2 seconds
-/datum/reagent/drug/pumpup/on_mob_metabolize(mob/living/carbon/L)
- ..()
- ADD_TRAIT(L, TRAIT_BATON_RESISTANCE, type)
- var/obj/item/organ/internal/liver/liver = L.get_organ_slot(ORGAN_SLOT_LIVER)
- if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM))
- L.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high)
+/datum/reagent/drug/pumpup/on_mob_metabolize(mob/living/carbon/affected_mob)
+ . = ..()
+ ADD_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
+ var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER)
+ if(liver && HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM))
+ affected_mob.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high)
metabolization_rate *= 0.8
-/datum/reagent/drug/pumpup/on_mob_end_metabolize(mob/living/L)
- REMOVE_TRAIT(L, TRAIT_BATON_RESISTANCE, type)
- ..()
+/datum/reagent/drug/pumpup/on_mob_end_metabolize(mob/living/affected_mob)
+ REMOVE_TRAIT(affected_mob, TRAIT_BATON_RESISTANCE, type)
+ return ..()
/datum/reagent/drug/pumpup/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.set_jitter_if_lower(10 SECONDS * REM * seconds_per_tick)
@@ -344,8 +345,9 @@
if(SPT_PROB(7.5, seconds_per_tick))
affected_mob.losebreath++
affected_mob.adjustToxLoss(2, FALSE, required_biotype = affected_biotype)
+ . = TRUE
..()
- . = TRUE
+
/datum/reagent/drug/pumpup/overdose_start(mob/living/affected_mob)
to_chat(affected_mob, span_userdanger("You can't stop shaking, your heart beats faster and faster..."))
@@ -367,7 +369,7 @@
name = "Maintenance Drugs"
chemical_flags = NONE
-/datum/reagent/drug/pumpup/on_mob_metabolize(mob/living/carbon/L)
+/datum/reagent/drug/maint/on_mob_metabolize(mob/living/carbon/L)
var/obj/item/organ/internal/liver/liver = L.get_organ_slot(ORGAN_SLOT_LIVER)
if(HAS_TRAIT(liver, TRAIT_MAINTENANCE_METABOLISM))
L.add_mood_event("maintenance_fun", /datum/mood_event/maintenance_high)
@@ -418,6 +420,7 @@
/datum/reagent/drug/maint/sludge/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
affected_mob.adjustToxLoss(0.5 * REM * seconds_per_tick, required_biotype = affected_biotype)
+ return TRUE
/datum/reagent/drug/maint/sludge/on_mob_end_metabolize(mob/living/affected_mob)
. = ..()
@@ -432,7 +435,7 @@
carbie.adjustToxLoss(1 * REM * seconds_per_tick, required_biotype = affected_biotype)
if(SPT_PROB(5, seconds_per_tick))
carbie.adjustToxLoss(5, required_biotype = affected_biotype)
- carbie.vomit()
+ carbie.vomit(VOMIT_CATEGORY_DEFAULT)
/datum/reagent/drug/maint/tar
name = "Maintenance Tar"
@@ -445,13 +448,13 @@
/datum/reagent/drug/maint/tar/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
-
affected_mob.AdjustStun(-10 * REM * seconds_per_tick)
affected_mob.AdjustKnockdown(-10 * REM * seconds_per_tick)
affected_mob.AdjustUnconscious(-10 * REM * seconds_per_tick)
affected_mob.AdjustParalyzed(-10 * REM * seconds_per_tick)
affected_mob.AdjustImmobilized(-10 * REM * seconds_per_tick)
affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, 1.5 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)
+ return TRUE
/datum/reagent/drug/maint/tar/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
. = ..()
@@ -603,6 +606,7 @@
if(SPT_PROB(BLASTOFF_DANCE_MOVE_CHANCE_PER_UNIT * volume, seconds_per_tick))
dancer.emote("flip")
+ return TRUE
/datum/reagent/drug/blastoff/overdose_process(mob/living/dancer, seconds_per_tick, times_fired)
. = ..()
@@ -670,6 +674,7 @@
/datum/reagent/drug/saturnx/on_mob_life(mob/living/carbon/invisible_man, seconds_per_tick, times_fired)
. = ..()
invisible_man.adjustOrganLoss(ORGAN_SLOT_LIVER, 0.3 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)
+ return TRUE
/datum/reagent/drug/saturnx/on_mob_metabolize(mob/living/invisible_man)
. = ..()
@@ -785,7 +790,7 @@
//I wish i could give it some kind of bonus when smoked, but we don't have an INHALE method.
/datum/reagent/drug/kronkaine/on_mob_life(mob/living/carbon/kronkaine_fiend, seconds_per_tick, times_fired)
- . = ..()
+ . = ..() || TRUE
kronkaine_fiend.add_mood_event("tweaking", /datum/mood_event/stimulant_medium, name)
kronkaine_fiend.adjustOrganLoss(ORGAN_SLOT_HEART, 0.4 * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)
kronkaine_fiend.set_jitter_if_lower(20 SECONDS * REM * seconds_per_tick)
diff --git a/code/modules/reagents/chemistry/reagents/food_reagents.dm b/code/modules/reagents/chemistry/reagents/food_reagents.dm
index 3d9e8c40f82200..1e0559d60345ad 100644
--- a/code/modules/reagents/chemistry/reagents/food_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/food_reagents.dm
@@ -273,6 +273,7 @@
taste_mult = 1.5 // stop sugar drowning out other flavours
nutriment_factor = 2
metabolization_rate = 2 * REAGENTS_METABOLISM
+ creation_purity = 1 // impure base reagents are a big no-no
overdose_threshold = 100 // Hyperglycaemic shock
taste_description = "sweetness"
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
@@ -448,7 +449,7 @@
if(prob(10))
victim.set_dizzy_if_lower(2 SECONDS)
if(prob(5))
- victim.vomit()
+ victim.vomit(VOMIT_CATEGORY_DEFAULT)
/datum/reagent/consumable/condensedcapsaicin/on_mob_life(mob/living/carbon/M, seconds_per_tick, times_fired)
if(!holder.has_reagent(/datum/reagent/consumable/milk))
@@ -472,6 +473,40 @@
return
exposed_turf.spawn_unique_cleanable(/obj/effect/decal/cleanable/food/salt)
+/datum/reagent/consumable/salt/expose_mob(mob/living/exposed_mob, methods, reac_volume)
+ . = ..()
+ var/mob/living/carbon/carbies = exposed_mob
+ if(!(methods & (PATCH|TOUCH|VAPOR)))
+ return
+ for(var/datum/wound/iter_wound as anything in carbies.all_wounds)
+ iter_wound.on_salt(reac_volume, carbies)
+
+// Salt can help with wounds by soaking up fluid, but undiluted salt will also cause irritation from the loose crystals, and it might soak up the body's water as well!
+// A saltwater mixture would be best, but we're making improvised chems here, not real ones.
+/datum/wound/proc/on_salt(reac_volume, mob/living/carbon/carbies)
+ return
+
+/datum/wound/pierce/bleed/on_salt(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.06 * reac_volume, initial_flow * 0.6) // 20u of a salt shacker * 0.1 = -1.6~ blood flow, but is always clamped to, at best, third blood loss from that wound.
+ // Crystal irritation worsening recovery.
+ gauzed_clot_rate *= 0.65
+ to_chat(carbies, span_notice("The salt bits seep in and stick to [lowertext(src)], painfully irritating the skin but soaking up most of the blood."))
+
+/datum/wound/slash/flesh/on_salt(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.1 * reac_volume, initial_flow * 0.5) // 20u of a salt shacker * 0.1 = -2~ blood flow, but is always clamped to, at best, halve blood loss from that wound.
+ // Crystal irritation worsening recovery.
+ clot_rate *= 0.75
+ to_chat(carbies, span_notice("The salt bits seep in and stick to [lowertext(src)], painfully irritating the skin but soaking up most of the blood."))
+
+/datum/wound/burn/flesh/on_salt(reac_volume)
+ // Slightly sanitizes and disinfects, but also increases infestation rate (some bacteria are aided by salt), and decreases flesh healing (can damage the skin from moisture absorption)
+ sanitization += VALUE_PER(0.4, 30) * reac_volume
+ infestation -= max(VALUE_PER(0.3, 30) * reac_volume, 0)
+ infestation_rate += VALUE_PER(0.12, 30) * reac_volume
+ flesh_healing -= max(VALUE_PER(5, 30) * reac_volume, 0)
+ to_chat(victim, span_notice("The salt bits seep in and stick to [lowertext(src)], painfully irritating the skin! After a few moments, it feels marginally better."))
+
+
/datum/reagent/consumable/blackpepper
name = "Black Pepper"
description = "A powder ground from peppercorns. *AAAACHOOO*"
@@ -610,9 +645,38 @@
reagent_state = SOLID
color = "#FFFFFF" // rgb: 0, 0, 0
taste_description = "chalky wheat"
- chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS
default_container = /obj/item/reagent_containers/condiment/flour
+/datum/reagent/consumable/flour/expose_mob(mob/living/exposed_mob, methods, reac_volume)
+ . = ..()
+ var/mob/living/carbon/carbies = exposed_mob
+ if(!(methods & (PATCH|TOUCH|VAPOR)))
+ return
+ for(var/datum/wound/iter_wound as anything in carbies.all_wounds)
+ iter_wound.on_flour(reac_volume, carbies)
+
+/datum/wound/proc/on_flour(reac_volume, mob/living/carbon/carbies)
+ return
+
+/datum/wound/pierce/bleed/on_flour(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.015 * reac_volume) // 30u of a flour sack * 0.015 = -0.45~ blood flow, prettay good
+ to_chat(carbies, span_notice("The flour seeps into [lowertext(src)], painfully drying it up and absorbing some of the blood."))
+ // When some nerd adds infection for wounds, make this increase the infection
+
+/datum/wound/slash/flesh/on_flour(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.04 * reac_volume) // 30u of a flour sack * 0.04 = -1.25~ blood flow, pretty good!
+ to_chat(carbies, span_notice("The flour seeps into [lowertext(src)], painfully drying some of it up and absorbing a little blood."))
+ // When some nerd adds infection for wounds, make this increase the infection
+
+// Don't pour flour onto burn wounds, it increases infection risk! Very unwise. Backed up by REAL info from REAL professionals.
+// https://www.reuters.com/article/uk-factcheck-flour-burn-idUSKCN26F2N3
+/datum/wound/burn/flesh/on_flour(reac_volume)
+ to_chat(victim, span_notice("The flour seeps into [lowertext(src)], spiking you with intense pain! That probably wasn't a good idea..."))
+ sanitization -= min(0, 1)
+ infestation += 0.2
+ return
+
/datum/reagent/consumable/flour/expose_turf(turf/exposed_turf, reac_volume)
. = ..()
if(isspaceturf(exposed_turf))
@@ -647,6 +711,14 @@
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
default_container = /obj/item/reagent_containers/condiment/rice
+/datum/reagent/consumable/rice_flour
+ name = "Rice Flour"
+ description = "Flour mixed with Rice"
+ reagent_state = SOLID
+ color = "#FFFFFF" // rgb: 0, 0, 0
+ taste_description = "chalky wheat with rice"
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+
/datum/reagent/consumable/vanilla
name = "Vanilla Powder"
description = "A fatty, bitter paste made from vanilla pods."
@@ -677,6 +749,37 @@
description = "A slippery solution."
color = "#DBCE95"
taste_description = "slime"
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS
+
+// Starch has similar absorbing properties to flour (Stronger here because it's rarer)
+/datum/reagent/consumable/corn_starch/expose_mob(mob/living/exposed_mob, methods, reac_volume)
+ . = ..()
+ var/mob/living/carbon/carbies = exposed_mob
+ if(!(methods & (PATCH|TOUCH|VAPOR)))
+ return
+ for(var/datum/wound/iter_wound as anything in carbies.all_wounds)
+ iter_wound.on_starch(reac_volume, carbies)
+
+/datum/wound/proc/on_starch(reac_volume, mob/living/carbon/carbies)
+ return
+
+/datum/wound/pierce/bleed/on_starch(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.03 * reac_volume)
+ to_chat(carbies, span_notice("The slimey starch seeps into [lowertext(src)], painfully drying some of it up and absorbing a little blood."))
+ // When some nerd adds infection for wounds, make this increase the infection
+ return
+
+/datum/wound/slash/flesh/on_starch(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.06 * reac_volume)
+ to_chat(carbies, span_notice("The slimey starch seeps into [lowertext(src)], painfully drying it up and absorbing some of the blood."))
+ // When some nerd adds infection for wounds, make this increase the infection
+ return
+
+/datum/wound/burn/flesh/on_starch(reac_volume, mob/living/carbon/carbies)
+ to_chat(carbies, span_notice("The slimey starch seeps into [lowertext(src)], spiking you with intense pain! That probably wasn't a good idea..."))
+ sanitization -= min(0, 0.5)
+ infestation += 0.1
+ return
/datum/reagent/consumable/corn_syrup
name = "Corn Syrup"
@@ -716,6 +819,7 @@
M.adjustFireLoss(-1, FALSE, required_bodytype = affected_bodytype)
M.adjustOxyLoss(-1, FALSE, required_biotype = affected_biotype)
M.adjustToxLoss(-1, FALSE, required_biotype = affected_biotype)
+ . = TRUE
..()
/datum/reagent/consumable/honey/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)
diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents.dm
index d14df37ce965ec..f7eaba3c211bac 100644
--- a/code/modules/reagents/chemistry/reagents/impure_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/impure_reagents.dm
@@ -15,12 +15,12 @@
var/liver_damage = 0.5
/datum/reagent/impurity/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
+ . = ..()
var/obj/item/organ/internal/liver/liver = affected_mob.get_organ_slot(ORGAN_SLOT_LIVER)
- if(!liver)//Though, lets be safe
- affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)//Incase of no liver!
- return ..()
+ if(isnull(liver)) //Though, lets be safe
+ return affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype) //Incase of no liver!
affected_mob.adjustOrganLoss(ORGAN_SLOT_LIVER, liver_damage * REM * seconds_per_tick, required_organ_flag = affected_organ_flags)
- return ..()
+ return TRUE
//Basically just so people don't forget to adjust metabolization_rate
/datum/reagent/inverse
@@ -35,8 +35,8 @@
/datum/reagent/inverse/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
- affected_mob.adjustToxLoss(tox_damage * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
- return ..()
+ . = ..()
+ return affected_mob.adjustToxLoss(tox_damage * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
//Failed chems - generally use inverse if you want to use a impure subtype for it
//technically not a impure chem, but it's here because it can only be made with a failed impure reaction
diff --git a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
index 8e09b318d1cd03..db98ed9d622573 100644
--- a/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/impure_reagents/impure_medicine_reagents.dm
@@ -47,6 +47,7 @@
if("oxy")
owner.adjustOxyLoss(-0.5, required_biotype = affected_biotype, required_respiration_type = affected_respiration_type)
..()
+ return TRUE
// C2 medications
// Helbital
@@ -198,7 +199,8 @@ Basically, we fill the time between now and 2s from now with hands based off the
/datum/reagent/peptides_failed/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired)
owner.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.25 * seconds_per_tick, 170)
owner.adjust_nutrition(-5 * REAGENTS_METABOLISM * seconds_per_tick)
- . = ..()
+ ..()
+ return TRUE
//Lenturi
//impure
@@ -233,6 +235,7 @@ Basically, we fill the time between now and 2s from now with hands based off the
//Just the removed itching mechanism - omage to it's origins.
/datum/reagent/inverse/ichiyuri/on_mob_life(mob/living/carbon/owner, seconds_per_tick, times_fired)
if(prob(resetting_probability) && !(HAS_TRAIT(owner, TRAIT_RESTRAINED) || owner.incapacitated()))
+ . = TRUE
if(spammer < world.time)
to_chat(owner,span_warning("You can't help but itch yourself."))
spammer = world.time + (10 SECONDS)
@@ -242,7 +245,7 @@ Basically, we fill the time between now and 2s from now with hands based off the
resetting_probability = 0
resetting_probability += (5*(current_cycle/10) * seconds_per_tick) // 10 iterations = >51% to itch
..()
- return TRUE
+ return .
//Aiuri
//impure
@@ -441,7 +444,7 @@ Basically, we fill the time between now and 2s from now with hands based off the
if (time_until_next_poison <= 0)
time_until_next_poison = poison_interval
owner.adjustToxLoss(creation_purity * 1, required_biotype = affected_biotype)
-
+ . = TRUE
..()
//Kind of a healing effect, Presumably you're using syrinver to purge so this helps that
@@ -561,7 +564,8 @@ Basically, we fill the time between now and 2s from now with hands based off the
if(!heart || heart.organ_flags & ORGAN_FAILING)
remove_buffs(affected_mob)
..()
-
+ return TRUE
+
/datum/reagent/inverse/penthrite/on_mob_delete(mob/living/carbon/affected_mob)
remove_buffs(affected_mob)
var/obj/item/organ/internal/heart/heart = affected_mob.get_organ_slot(ORGAN_SLOT_HEART)
diff --git a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
index 86978985ff8c3b..6e2b12a95eb031 100644
--- a/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/medicine_reagents.dm
@@ -70,7 +70,8 @@
affected_mob.adjustToxLoss(-5 * REM * seconds_per_tick, FALSE, TRUE, affected_biotype)
// Heal everything! That we want to. But really don't heal reagents. Otherwise we'll lose ... us.
affected_mob.fully_heal(full_heal_flags & ~HEAL_ALL_REAGENTS)
- return ..()
+ ..()
+ return TRUE
/datum/reagent/medicine/adminordrazine/quantum_heal
name = "Quantum Medicine"
@@ -136,6 +137,7 @@
if(prob(30))
SEND_SOUND(affected_mob, sound('sound/weapons/flash_ring.ogg'))
..()
+ return TRUE
/datum/reagent/medicine/cryoxadone
name = "Cryoxadone"
@@ -346,7 +348,7 @@
color = "#6D6374"
metabolization_rate = 0.4 * REAGENTS_METABOLISM
ph = 2.6
- chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS
/datum/reagent/medicine/mine_salve/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.adjustBruteLoss(-0.25 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype)
@@ -380,6 +382,10 @@
. = ..()
metabolizer.remove_status_effect(/datum/status_effect/grouped/screwy_hud/fake_healthy, type)
+/datum/reagent/medicine/mine_salve/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound)
+ burn_wound.sanitization += 0.3
+ burn_wound.flesh_healing += 0.5
+
/datum/reagent/medicine/omnizine
name = "Omnizine"
description = "Slowly heals all damage types. Overdose will cause damage in all types instead."
@@ -496,6 +502,7 @@
affected_mob.adjustToxLoss(-1 * REM * seconds_per_tick, required_biotype = affected_biotype)
..()
+ return TRUE
/datum/reagent/medicine/pen_acid
name = "Pentetic Acid"
@@ -726,20 +733,22 @@
affected_mob.adjust_temp_blindness(-4 SECONDS * REM * seconds_per_tick * normalized_purity)
affected_mob.adjust_eye_blur(-4 SECONDS * REM * seconds_per_tick * normalized_purity)
var/obj/item/organ/internal/eyes/eyes = affected_mob.get_organ_slot(ORGAN_SLOT_EYES)
- if(eyes)
- // Healing eye damage will cure nearsightedness and blindness from ... eye damage
- eyes.apply_organ_damage(-2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)
- // If our eyes are seriously damaged, we have a probability of causing eye blur while healing depending on purity
- if(eyes.damaged && IS_ORGANIC_ORGAN(eyes) && SPT_PROB(16 - min(normalized_purity * 6, 12), seconds_per_tick))
- // While healing, gives some eye blur
- if(affected_mob.is_blind_from(EYE_DAMAGE))
- to_chat(affected_mob, span_warning("Your vision slowly returns..."))
- affected_mob.adjust_eye_blur(20 SECONDS)
- else if(affected_mob.is_nearsighted_from(EYE_DAMAGE))
- to_chat(affected_mob, span_warning("The blackness in your peripheral vision begins to fade."))
- affected_mob.adjust_eye_blur(5 SECONDS)
+ if(isnull(eyes))
+ return ..()
- return ..()
+ // Healing eye damage will cure nearsightedness and blindness from ... eye damage
+ eyes.apply_organ_damage(-2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)
+ // If our eyes are seriously damaged, we have a probability of causing eye blur while healing depending on purity
+ if(eyes.damaged && IS_ORGANIC_ORGAN(eyes) && SPT_PROB(16 - min(normalized_purity * 6, 12), seconds_per_tick))
+ // While healing, gives some eye blur
+ if(affected_mob.is_blind_from(EYE_DAMAGE))
+ to_chat(affected_mob, span_warning("Your vision slowly returns..."))
+ affected_mob.adjust_eye_blur(20 SECONDS)
+ else if(affected_mob.is_nearsighted_from(EYE_DAMAGE))
+ to_chat(affected_mob, span_warning("The blackness in your peripheral vision begins to fade."))
+ affected_mob.adjust_eye_blur(5 SECONDS)
+
+ return ..() || TRUE
/datum/reagent/medicine/oculine/on_mob_delete(mob/living/affected_mob)
var/obj/item/organ/internal/eyes/eyes = affected_mob.get_organ_slot(ORGAN_SLOT_EYES)
@@ -779,6 +788,7 @@
return ..()
ears.adjustEarDamage(-4 * REM * seconds_per_tick * normalise_creation_purity(), -4 * REM * seconds_per_tick * normalise_creation_purity())
..()
+ return TRUE
/datum/reagent/medicine/inacusiate/on_mob_delete(mob/living/affected_mob)
. = ..()
@@ -851,7 +861,7 @@
if(SPT_PROB(10, seconds_per_tick))
holder.add_reagent(/datum/reagent/toxin/histamine, 4)
..()
- return
+ return FALSE
if(affected_mob.health <= affected_mob.crit_threshold)
affected_mob.adjustToxLoss(-0.5 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
affected_mob.adjustBruteLoss(-0.5 * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype)
@@ -867,7 +877,7 @@
affected_mob.adjustStaminaLoss(-0.5 * REM * seconds_per_tick, 0)
if(SPT_PROB(10, seconds_per_tick))
affected_mob.AdjustAllImmobility(-20)
- ..()
+ return ..()
/datum/reagent/medicine/epinephrine/overdose_process(mob/living/affected_mob, seconds_per_tick, times_fired)
if(SPT_PROB(18, REM * seconds_per_tick))
@@ -983,7 +993,8 @@
var/damage_at_random = rand(0, 250)/100 //0 to 2.5
affected_mob.adjustBruteLoss(damage_at_random * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype)
affected_mob.adjustFireLoss(damage_at_random * REM * seconds_per_tick, FALSE, required_bodytype = affected_bodytype)
- return ..()
+ ..()
+ return TRUE
/datum/reagent/medicine/mannitol
name = "Mannitol"
@@ -1000,6 +1011,7 @@
/datum/reagent/medicine/mannitol/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, -2 * REM * seconds_per_tick * normalise_creation_purity(), required_organ_flag = affected_organ_flags)
..()
+ return TRUE
//Having mannitol in you will pause the brain damage from brain tumor (so it heals an even 2 brain damage instead of 1.8)
/datum/reagent/medicine/mannitol/on_mob_metabolize(mob/living/carbon/affected_mob)
@@ -1110,6 +1122,14 @@
..()
. = TRUE
+/datum/reagent/medicine/antihol/expose_mob(mob/living/carbon/exposed_carbon, methods=TOUCH, reac_volume)
+ . = ..()
+ if(!(methods & (TOUCH|VAPOR|PATCH)))
+ return
+
+ for(var/datum/surgery/surgery as anything in exposed_carbon.surgeries)
+ surgery.speed_modifier = max(surgery.speed_modifier - 0.1, -0.9)
+
/datum/reagent/medicine/stimulants
name = "Stimulants"
description = "Increases resistance to batons and movement speed in addition to restoring minor damage and weakness. Overdose causes weakness and toxin damage."
@@ -1228,7 +1248,7 @@
/datum/reagent/medicine/syndicate_nanites/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired) //wtb flavortext messages that hint that you're vomitting up robots
if(SPT_PROB(13, seconds_per_tick))
affected_mob.reagents.remove_reagent(type, metabolization_rate*15) // ~5 units at a rate of 0.4 but i wanted a nice number in code
- affected_mob.vomit(20) // nanite safety protocols make your body expel them to prevent harmies
+ affected_mob.vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, vomit_type = /obj/effect/decal/cleanable/vomit/nanites, lost_nutrition = 20) // nanite safety protocols make your body expel them to prevent harmies
..()
. = TRUE
@@ -1539,7 +1559,7 @@
/datum/reagent/medicine/metafactor/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
if(SPT_PROB(13, seconds_per_tick))
- affected_mob.vomit()
+ affected_mob.vomit(VOMIT_CATEGORY_DEFAULT)
..()
/datum/reagent/medicine/silibinin
@@ -1731,4 +1751,5 @@
M.adjust_drowsiness(2 SECONDS * REM * seconds_per_tick)
if(SPT_PROB(15, seconds_per_tick) && !M.getStaminaLoss())
M.adjustStaminaLoss(10)
+ . = TRUE
M.adjust_disgust(-10 * REM * seconds_per_tick)
diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm
index 54fa4391790959..2a46d537233004 100644
--- a/code/modules/reagents/chemistry/reagents/other_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm
@@ -299,6 +299,53 @@
//You don't belong in this world, monster!
mytray.reagents.remove_reagent(type, volume)
+/datum/reagent/water/salt
+ name = "Saltwater"
+ description = "Water, but salty. Smells like... the station infirmary?"
+ color = "#aaaaaa9d" // rgb: 170, 170, 170, 77 (alpha)
+ taste_description = "the sea"
+ cooling_temperature = 3
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS
+ default_container = /obj/item/reagent_containers/cup/glass/waterbottle
+
+/datum/glass_style/shot_glass/water/salt
+ required_drink_type = /datum/reagent/water/salt
+ icon_state = "shotglassclear"
+
+/datum/glass_style/drinking_glass/water/salt
+ required_drink_type = /datum/reagent/water/salt
+ name = "glass of saltwater"
+ desc = "If you have a sore throat, gargle some saltwater and watch the pain go away. Can be used as a very improvised topical medicine against wounds."
+ icon_state = "glass_clear"
+
+/datum/reagent/water/salt/expose_mob(mob/living/exposed_mob, methods, reac_volume)
+ . = ..()
+ var/mob/living/carbon/carbies = exposed_mob
+ if(!(methods & (PATCH|TOUCH|VAPOR)))
+ return
+ for(var/datum/wound/iter_wound as anything in carbies.all_wounds)
+ iter_wound.on_saltwater(reac_volume, carbies)
+
+// Mixed salt with water! All the help of salt with none of the irritation. Plus increased volume.
+/datum/wound/proc/on_saltwater(reac_volume, mob/living/carbon/carbies)
+ return
+
+/datum/wound/pierce/bleed/on_saltwater(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.06 * reac_volume, initial_flow * 0.6)
+ to_chat(carbies, span_notice("The salt water splashes over [lowertext(src)], soaking up the blood."))
+
+/datum/wound/slash/flesh/on_saltwater(reac_volume, mob/living/carbon/carbies)
+ adjust_blood_flow(-0.1 * reac_volume, initial_flow * 0.5)
+ to_chat(carbies, span_notice("The salt water splashes over [lowertext(src)], soaking up the blood."))
+
+/datum/wound/burn/flesh/on_saltwater(reac_volume)
+ // Similar but better stats from normal salt.
+ sanitization += VALUE_PER(0.6, 30) * reac_volume
+ infestation -= max(VALUE_PER(0.5, 30) * reac_volume, 0)
+ infestation_rate += VALUE_PER(0.07, 30) * reac_volume
+ to_chat(victim, span_notice("The salt water splashes over [lowertext(src)], soaking up the... miscellaneous fluids. It feels somewhat better afterwards."))
+ return
+
/datum/reagent/water/holywater
name = "Holy Water"
description = "Water blessed by some deity."
@@ -429,6 +476,7 @@
name = "Unholy Water"
description = "Something that shouldn't exist on this plane of existence."
taste_description = "suffering"
+ self_consuming = TRUE //unholy intervention won't be limited by the lack of a liver
metabolization_rate = 2.5 * REAGENTS_METABOLISM //0.5u/second
penetrates_skin = TOUCH|VAPOR
ph = 6.5
@@ -452,6 +500,7 @@
affected_mob.adjustOxyLoss(1 * REM * seconds_per_tick, 0)
affected_mob.adjustBruteLoss(1 * REM * seconds_per_tick, 0)
..()
+ return TRUE
/datum/reagent/hellwater //if someone has this in their system they've really pissed off an eldrich god
name = "Hell Water"
@@ -467,6 +516,7 @@
affected_mob.adjustFireLoss(0.5*seconds_per_tick, 0) //Hence the other damages... ain't I a bastard?
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2.5*seconds_per_tick, 150)
holder.remove_reagent(type, 0.5*seconds_per_tick)
+ return TRUE
/datum/reagent/medicine/omnizine/godblood
name = "Godblood"
@@ -933,6 +983,7 @@
affected_mob.emote(pick("twitch","drool","moan"))
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5*seconds_per_tick)
..()
+ return TRUE
/datum/reagent/sulfur
name = "Sulfur"
@@ -1058,7 +1109,7 @@
color = "#D0EFEE" // space cleaner but lighter
taste_description = "bitterness"
ph = 10.5
- chemical_flags = REAGENT_CAN_BE_SYNTHESIZED
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_AFFECTS_WOUNDS
/datum/reagent/space_cleaner/sterilizine/expose_mob(mob/living/carbon/exposed_carbon, methods=TOUCH, reac_volume)
. = ..()
@@ -1068,6 +1119,9 @@
for(var/datum/surgery/surgery as anything in exposed_carbon.surgeries)
surgery.speed_modifier = max(0.2, surgery.speed_modifier)
+/datum/reagent/space_cleaner/sterilizine/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound)
+ burn_wound.sanitization += 0.9
+
/datum/reagent/iron
name = "Iron"
description = "Pure iron is a metal."
@@ -1117,6 +1171,7 @@
/datum/reagent/uranium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.adjustToxLoss(tox_damage * seconds_per_tick * REM)
..()
+ return TRUE
/datum/reagent/uranium/expose_turf(turf/exposed_turf, reac_volume)
. = ..()
@@ -1231,14 +1286,14 @@
/datum/reagent/space_cleaner
name = "Space Cleaner"
- description = "A compound used to clean things. Now with 50% more sodium hypochlorite!"
+ description = "A compound used to clean things. Now with 50% more sodium hypochlorite! Can be used to clean wounds, but it's not really meant for that."
color = "#A5F0EE" // rgb: 165, 240, 238
taste_description = "sourness"
reagent_weight = 0.6 //so it sprays further
- penetrates_skin = NONE
+ penetrates_skin = VAPOR
var/clean_types = CLEAN_WASH
ph = 5.5
- chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS
+ chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_CLEANS|REAGENT_AFFECTS_WOUNDS
/datum/reagent/space_cleaner/expose_obj(obj/exposed_obj, reac_volume)
. = ..()
@@ -1264,6 +1319,13 @@
if(methods & (TOUCH|VAPOR))
exposed_mob.wash(clean_types)
+/datum/reagent/space_cleaner/on_burn_wound_processing(datum/wound/burn/flesh/burn_wound)
+ burn_wound.sanitization += 0.3
+ if(prob(5))
+ to_chat(burn_wound.victim, span_notice("Your [burn_wound] stings and burns from the [src] covering it! It does look pretty clean though."))
+ burn_wound.victim.adjustToxLoss(0.5)
+ burn_wound.limb.receive_damage(burn = 0.5, wound_bonus = CANT_WOUND)
+
/datum/reagent/space_cleaner/ez_clean
name = "EZ Clean"
description = "A powerful, acidic cleaner sold by Waffle Co. Affects organic matter while leaving other objects unaffected."
@@ -1278,6 +1340,7 @@
affected_mob.adjustFireLoss(1.665*seconds_per_tick)
affected_mob.adjustToxLoss(1.665*seconds_per_tick)
..()
+ return TRUE
/datum/reagent/space_cleaner/ez_clean/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)
. = ..()
@@ -1319,8 +1382,10 @@
/datum/reagent/impedrezene/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.adjust_jitter(-5 SECONDS * seconds_per_tick)
+ . = FALSE
if(SPT_PROB(55, seconds_per_tick))
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 2)
+ . = TRUE
if(SPT_PROB(30, seconds_per_tick))
affected_mob.adjust_drowsiness(6 SECONDS)
if(SPT_PROB(5, seconds_per_tick))
@@ -2485,8 +2550,10 @@
/datum/reagent/peaceborg/tire/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
var/healthcomp = (100 - affected_mob.health) //DOES NOT ACCOUNT FOR ADMINBUS THINGS THAT MAKE YOU HAVE MORE THAN 200/210 HEALTH, OR SOMETHING OTHER THAN A HUMAN PROCESSING THIS.
+ . = FALSE
if(affected_mob.getStaminaLoss() < (45 - healthcomp)) //At 50 health you would have 200 - 150 health meaning 50 compensation. 60 - 50 = 10, so would only do 10-19 stamina.)
affected_mob.adjustStaminaLoss(10 * REM * seconds_per_tick)
+ . = TRUE
if(SPT_PROB(16, seconds_per_tick))
to_chat(affected_mob, "You should sit down and take a rest...")
..()
@@ -2546,7 +2613,10 @@
if(yuck_cycles % YUCK_PUKE_CYCLES == 0)
if(yuck_cycles >= YUCK_PUKE_CYCLES * YUCK_PUKES_TO_STUN)
holder.remove_reagent(type, 5)
- affected_mob.vomit(rand(14, 26), stun = yuck_cycles >= YUCK_PUKE_CYCLES * YUCK_PUKES_TO_STUN)
+ var/passable_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM)
+ if(yuck_cycles >= (YUCK_PUKE_CYCLES * YUCK_PUKES_TO_STUN))
+ passable_flags |= MOB_VOMIT_STUN
+ affected_mob.vomit(vomit_flags = passable_flags, lost_nutrition = rand(14, 26))
if(holder)
return ..()
#undef YUCK_PUKE_CYCLES
@@ -2711,6 +2781,7 @@
It re-energizes and heals those who can see beyond this fragile reality, \
but is incredibly harmful to the closed-minded. It metabolizes very quickly."
taste_description = "Ag'hsj'saje'sh"
+ self_consuming = TRUE //eldritch intervention won't be limited by the lack of a liver
color = "#1f8016"
metabolization_rate = 2.5 * REAGENTS_METABOLISM //0.5u/second
chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE
@@ -2792,7 +2863,8 @@
victim.emote("scream")
if(SPT_PROB(2, seconds_per_tick)) // Stuns, but purges ants.
victim.vomit(rand(5,10), FALSE, TRUE, 1, TRUE, FALSE, purge_ratio = 1)
- return ..()
+ ..()
+ return TRUE
/datum/reagent/ants/on_mob_end_metabolize(mob/living/living_anthill)
ant_damage = 0
@@ -2842,8 +2914,9 @@
metabolization_rate = 0.4 * REAGENTS_METABOLISM
/datum/reagent/lead/on_mob_life(mob/living/carbon/victim)
- . = ..()
victim.adjustOrganLoss(ORGAN_SLOT_BRAIN, 0.5)
+ ..()
+ return TRUE
//The main feedstock for kronkaine production, also a shitty stamina healer.
/datum/reagent/kronkus_extract
@@ -2855,9 +2928,10 @@
addiction_types = list(/datum/addiction/stimulants = 5)
/datum/reagent/kronkus_extract/on_mob_life(mob/living/carbon/kronkus_enjoyer)
- . = ..()
+ ..()
kronkus_enjoyer.adjustOrganLoss(ORGAN_SLOT_HEART, 0.1)
kronkus_enjoyer.adjustStaminaLoss(-2, FALSE)
+ return TRUE
/datum/reagent/brimdust
name = "Brimdust"
@@ -2869,7 +2943,7 @@
/datum/reagent/brimdust/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
. = ..()
- affected_mob.adjustFireLoss((ispodperson(affected_mob) ? -1 : 1) * seconds_per_tick)
+ return affected_mob.adjustFireLoss((ispodperson(affected_mob) ? -1 : 1) * seconds_per_tick)
/datum/reagent/brimdust/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user)
mytray.adjust_weedlevel(-1)
@@ -2951,3 +3025,4 @@
if(SPT_PROB(10, seconds_per_tick))
affected_mob.emote(pick("twitch","choke","shiver","gag"))
..()
+ return TRUE
diff --git a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
index 0d52ca4e82545b..6f99273ad4e937 100644
--- a/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/pyrotechnic_reagents.dm
@@ -203,6 +203,7 @@
/datum/reagent/napalm/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.adjust_fire_stacks(1 * REM * seconds_per_tick)
..()
+ return TRUE
/datum/reagent/napalm/expose_mob(mob/living/exposed_mob, methods=TOUCH, reac_volume)
. = ..()
diff --git a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
index a9735cd7e755a6..d655698646ca28 100644
--- a/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
+++ b/code/modules/reagents/chemistry/reagents/toxin_reagents.dm
@@ -64,8 +64,8 @@
exposed_mob.domutcheck()
/datum/reagent/toxin/mutagen/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
- affected_mob.adjustToxLoss(0.5 * seconds_per_tick * REM, required_biotype = affected_biotype)
- return ..()
+ . = affected_mob.adjustToxLoss(0.5 * seconds_per_tick * REM, required_biotype = affected_biotype)
+ return ..() || .
/datum/reagent/toxin/mutagen/on_hydroponics_apply(obj/machinery/hydroponics/mytray, mob/user)
mytray.mutation_roll(user)
@@ -513,7 +513,8 @@
if(51 to INFINITY)
affected_mob.Sleeping(40 * REM * seconds_per_tick)
affected_mob.adjustToxLoss(1 * (current_cycle - 50) * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
- return ..()
+ . = TRUE
+ return ..() || .
/datum/reagent/toxin/coffeepowder
name = "Coffee Grounds"
@@ -559,7 +560,7 @@
/datum/reagent/toxin/mutetoxin/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
// Gain approximately 12 seconds * creation purity seconds of silence every metabolism tick.
affected_mob.set_silence_if_lower(6 SECONDS * REM * normalise_creation_purity() * seconds_per_tick)
- ..()
+ return ..()
/datum/reagent/toxin/staminatoxin
name = "Tirizene"
@@ -590,8 +591,8 @@
affected_mob.AddComponent(/datum/component/irradiated)
else
affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, required_biotype = affected_biotype)
-
- ..()
+ . = TRUE
+ return ..() || .
/datum/reagent/toxin/histamine
name = "Histamine"
@@ -904,9 +905,10 @@
/datum/reagent/toxin/lipolicide/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
if(affected_mob.nutrition <= NUTRITION_LEVEL_STARVING)
affected_mob.adjustToxLoss(1 * REM * seconds_per_tick, FALSE, required_biotype = affected_biotype)
+ . = TRUE
affected_mob.adjust_nutrition(-3 * REM * normalise_creation_purity() * seconds_per_tick) // making the chef more valuable, one meme trap at a time
affected_mob.overeatduration = 0
- return ..()
+ return ..() || .
/datum/reagent/toxin/coniine
name = "Coniine"
@@ -936,7 +938,12 @@
/datum/reagent/toxin/spewium/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
.=..()
if(current_cycle >= 11 && SPT_PROB(min(30, current_cycle), seconds_per_tick))
- affected_mob.vomit(10, prob(10), prob(50), rand(0,4), TRUE)
+ var/constructed_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM)
+ if(prob(10))
+ constructed_flags |= MOB_VOMIT_BLOOD
+ if(prob(50))
+ constructed_flags |= MOB_VOMIT_STUN
+ affected_mob.vomit(vomit_flags = constructed_flags, distance = rand(0,4))
for(var/datum/reagent/toxin/R in affected_mob.reagents.reagent_list)
if(R != src)
affected_mob.reagents.remove_reagent(R.type,1)
@@ -945,7 +952,7 @@
. = ..()
if(current_cycle >= 33 && SPT_PROB(7.5, seconds_per_tick))
affected_mob.spew_organ()
- affected_mob.vomit(0, TRUE, TRUE, 4)
+ affected_mob.vomit(VOMIT_CATEGORY_BLOOD, lost_nutrition = 0, distance = 4)
to_chat(affected_mob, span_userdanger("You feel something lumpy come up as you vomit."))
/datum/reagent/toxin/curare
@@ -1193,7 +1200,7 @@
affected_mob.manual_emote(pick("oofs silently.", "looks like [affected_mob.p_their()] bones hurt.", "grimaces, as though [affected_mob.p_their()] bones hurt."))
if(3)
to_chat(affected_mob, span_warning("Your bones hurt!"))
- return ..()
+ return ..() || TRUE
/datum/reagent/toxin/bonehurtingjuice/overdose_process(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
if(SPT_PROB(2, seconds_per_tick) && iscarbon(affected_mob)) //big oof
@@ -1247,11 +1254,11 @@
/datum/reagent/toxin/leadacetate/on_mob_life(mob/living/carbon/affected_mob, seconds_per_tick, times_fired)
affected_mob.adjustOrganLoss(ORGAN_SLOT_EARS, 1 * REM * seconds_per_tick)
affected_mob.adjustOrganLoss(ORGAN_SLOT_BRAIN, 1 * REM * seconds_per_tick)
+ . = TRUE
if(SPT_PROB(0.5, seconds_per_tick))
to_chat(affected_mob, span_notice("Ah, what was that? You thought you heard something..."))
affected_mob.adjust_confusion(5 SECONDS)
- return ..()
-
+ return ..() || .
/datum/reagent/toxin/hunterspider
name = "Spider Toxin"
description = "A toxic chemical produced by spiders to weaken prey."
diff --git a/code/modules/reagents/chemistry/recipes/medicine.dm b/code/modules/reagents/chemistry/recipes/medicine.dm
index cd755532fb3057..e3f1fd1acef9ac 100644
--- a/code/modules/reagents/chemistry/recipes/medicine.dm
+++ b/code/modules/reagents/chemistry/recipes/medicine.dm
@@ -95,7 +95,7 @@
/datum/chemical_reaction/medicine/salglu_solution
results = list(/datum/reagent/medicine/salglu_solution = 3)
- required_reagents = list(/datum/reagent/consumable/salt = 1, /datum/reagent/water = 1, /datum/reagent/consumable/sugar = 1)
+ required_reagents = list(/datum/reagent/water/salt = 2, /datum/reagent/consumable/sugar = 1)
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_ORGAN
/datum/chemical_reaction/medicine/mine_salve
@@ -143,8 +143,8 @@
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OTHER
/datum/chemical_reaction/medicine/pen_acid
- results = list(/datum/reagent/medicine/pen_acid = 6)
- required_reagents = list(/datum/reagent/fuel = 1, /datum/reagent/chlorine = 1, /datum/reagent/ammonia = 1, /datum/reagent/toxin/formaldehyde = 1, /datum/reagent/sodium = 1, /datum/reagent/toxin/cyanide = 1)
+ results = list(/datum/reagent/medicine/pen_acid = 5)
+ required_reagents = list(/datum/reagent/fuel = 1, /datum/reagent/ammonia = 1, /datum/reagent/toxin/formaldehyde = 1, /datum/reagent/consumable/salt = 1, /datum/reagent/toxin/cyanide = 1)
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_HEALING | REACTION_TAG_OTHER
/datum/chemical_reaction/medicine/sal_acid
diff --git a/code/modules/reagents/chemistry/recipes/others.dm b/code/modules/reagents/chemistry/recipes/others.dm
index 1c77ff5f6c1059..b632bc7b6c86e9 100644
--- a/code/modules/reagents/chemistry/recipes/others.dm
+++ b/code/modules/reagents/chemistry/recipes/others.dm
@@ -35,8 +35,8 @@
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_EXPLOSIVE
/datum/chemical_reaction/sodiumchloride
- results = list(/datum/reagent/consumable/salt = 3)
- required_reagents = list(/datum/reagent/water = 1, /datum/reagent/sodium = 1, /datum/reagent/chlorine = 1)
+ results = list(/datum/reagent/consumable/salt = 2)
+ required_reagents = list(/datum/reagent/sodium = 1, /datum/reagent/chlorine = 1) // That's what I said! Sodium Chloride!
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_FOOD
/datum/chemical_reaction/stable_plasma
@@ -586,15 +586,19 @@
if(ismonkey(M))
M.gib()
else
- M.vomit(blood = TRUE, stun = TRUE) //not having a redo of itching powder (hopefully)
+ M.vomit(VOMIT_CATEGORY_BLOOD)
new /mob/living/carbon/human/species/monkey(location, TRUE)
//water electrolysis
/datum/chemical_reaction/electrolysis
- results = list(/datum/reagent/oxygen = 1.5, /datum/reagent/hydrogen = 3)
- required_reagents = list(/datum/reagent/consumable/liquidelectricity/enriched = 1, /datum/reagent/water = 5)
+ results = list(/datum/reagent/oxygen = 2.5, /datum/reagent/hydrogen = 5)
+ required_reagents = list(/datum/reagent/consumable/liquidelectricity = 1, /datum/reagent/water = 5)
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_CHEMICAL
+/datum/chemical_reaction/electrolysis2
+ results = list(/datum/reagent/oxygen = 2.5, /datum/reagent/hydrogen = 5)
+ required_reagents = list(/datum/reagent/consumable/liquidelectricity/enriched = 1, /datum/reagent/water = 5)
+ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_CHEMICAL
//butterflium
/datum/chemical_reaction/butterflium
required_reagents = list(/datum/reagent/colorful_reagent = 1, /datum/reagent/medicine/omnizine = 1, /datum/reagent/medicine/strange_reagent = 1, /datum/reagent/consumable/nutriment = 1)
@@ -770,6 +774,11 @@
required_catalysts = list(/datum/reagent/water/holywater = 1)
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_UNIQUE | REACTION_TAG_PLANT | REACTION_TAG_OTHER
+/datum/chemical_reaction/saltwater
+ results = list(/datum/reagent/water/salt = 2)
+ required_reagents = list(/datum/reagent/water = 1, /datum/reagent/consumable/salt = 1)
+ reaction_tags = REACTION_TAG_EASY | REACTION_TAG_DRINK | REACTION_TAG_ORGAN
+
/datum/chemical_reaction/exotic_stabilizer
results = list(/datum/reagent/exotic_stabilizer = 2)
required_reagents = list(/datum/reagent/plasma_oxide = 1,/datum/reagent/stabilizing_agent = 1)
diff --git a/code/modules/reagents/chemistry/recipes/special.dm b/code/modules/reagents/chemistry/recipes/special.dm
index 990ace10830a8b..592a281ed7301f 100644
--- a/code/modules/reagents/chemistry/recipes/special.dm
+++ b/code/modules/reagents/chemistry/recipes/special.dm
@@ -217,6 +217,9 @@ GLOBAL_LIST_INIT(medicine_reagents, build_medicine_reagents())
return FALSE
required_reagents = req_reag
+ if (required_reagents.len == 0)
+ return FALSE
+
var/req_catalysts = unwrap_reagent_list(recipe_data["required_catalysts"])
if(!req_catalysts)
return FALSE
diff --git a/code/modules/reagents/chemistry/recipes/toxins.dm b/code/modules/reagents/chemistry/recipes/toxins.dm
index b4e80e01cd02b7..0fcae783d89936 100644
--- a/code/modules/reagents/chemistry/recipes/toxins.dm
+++ b/code/modules/reagents/chemistry/recipes/toxins.dm
@@ -307,8 +307,8 @@
reaction_tags = REACTION_TAG_EASY | REACTION_TAG_DAMAGING | REACTION_TAG_OTHER
/datum/chemical_reaction/heparin
- results = list(/datum/reagent/toxin/heparin = 4)
- required_reagents = list(/datum/reagent/toxin/formaldehyde = 1, /datum/reagent/sodium = 1, /datum/reagent/chlorine = 1, /datum/reagent/lithium = 1)
+ results = list(/datum/reagent/toxin/heparin = 3)
+ required_reagents = list(/datum/reagent/toxin/formaldehyde = 1, /datum/reagent/consumable/salt = 1, /datum/reagent/lithium = 1)
mix_message = "The mixture thins and loses all color."
is_cold_recipe = FALSE
required_temp = 100
diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm
index ad83d2c439c95b..b5930955a3143e 100644
--- a/code/modules/reagents/reagent_containers/cups/_cup.dm
+++ b/code/modules/reagents/reagent_containers/cups/_cup.dm
@@ -536,15 +536,18 @@
to_chat(user, span_warning("You can't grind this!"))
/obj/item/reagent_containers/cup/mortar/proc/grind_item(obj/item/item, mob/living/carbon/human/user)
- if(!item.grind(src, user))
- to_chat(user, span_notice("You fail to grind [item]."))
+ if(!item.grind(reagents, user))
+ if(isstack(item))
+ to_chat(usr, span_notice("[src] attempts to grind as many pieces of [item] as possible."))
+ else
+ to_chat(user, span_danger("You fail to grind [item]."))
return
to_chat(user, span_notice("You grind [item] into a nice powder."))
grinded = null
QDEL_NULL(item)
/obj/item/reagent_containers/cup/mortar/proc/juice_item(obj/item/item, mob/living/carbon/human/user)
- if(!item.juice(src, user))
+ if(!item.juice(reagents, user))
to_chat(user, span_notice("You fail to juice [item]."))
return
to_chat(user, span_notice("You juice [item] into a fine liquid."))
diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm
index a19e7fbe7f32fb..28c26c5d3568a6 100644
--- a/code/modules/reagents/reagent_containers/spray.dm
+++ b/code/modules/reagents/reagent_containers/spray.dm
@@ -442,6 +442,7 @@
/obj/item/reagent_containers/spray/hercuri
name = "medical spray (hercuri)"
- desc = "A medical spray bottle.This one contains hercuri, a medicine used to negate the effects of dangerous high-temperature environments. Careful not to freeze the patient!"
- icon_state = "sprayer_large"
+ desc = "A medical spray bottle. This one contains hercuri, a medicine used to negate the effects of dangerous high-temperature environments. Careful not to freeze the patient!"
+ icon = 'icons/obj/medical/chemical.dmi'
+ icon_state = "sprayer_med_yellow"
list_reagents = list(/datum/reagent/medicine/c2/hercuri = 100)
diff --git a/code/modules/reagents/reagent_dispenser.dm b/code/modules/reagents/reagent_dispenser.dm
index 5f8ebecd990246..ef48a50af21052 100644
--- a/code/modules/reagents/reagent_dispenser.dm
+++ b/code/modules/reagents/reagent_dispenser.dm
@@ -1,3 +1,5 @@
+#define REAGENT_SPILL_DIVISOR 200
+
/obj/structure/reagent_dispensers
name = "Dispenser"
desc = "..."
@@ -206,6 +208,15 @@
return TRUE
return FALSE
+/obj/structure/reagent_dispensers/proc/knock_down()
+ var/datum/effect_system/fluid_spread/smoke/chem/smoke = new ()
+ var/range = reagents.total_volume / REAGENT_SPILL_DIVISOR
+ smoke.attach(drop_location())
+ smoke.set_up(round(range), holder = drop_location(), location = drop_location(), carry = reagents, silent = FALSE)
+ smoke.start(log = TRUE)
+ reagents.clear_reagents()
+ qdel(src)
+
/obj/structure/reagent_dispensers/wrench_act(mob/living/user, obj/item/tool)
. = ..()
if(!openable)
@@ -341,6 +352,7 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/peppertank, 3
. = ..()
if(prob(1))
desc = "IT'S PEPPER TIME, BITCH!"
+ find_and_hang_on_wall()
/obj/structure/reagent_dispensers/water_cooler//SKYRAT EDIT - ICON OVERRIDEN BY AESTHETICS - SEE MODULE
name = "liquid cooler"
@@ -392,6 +404,10 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/peppertank, 3
MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/virusfood, 30)
+/obj/structure/reagent_dispensers/wall/virusfood/Initialize(mapload)
+ . = ..()
+ find_and_hang_on_wall()
+
/obj/structure/reagent_dispensers/cooking_oil
name = "vat of cooking oil"
desc = "A huge metal vat with a tap on the front. Filled with cooking oil for use in frying food."
@@ -454,3 +470,5 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/structure/reagent_dispensers/wall/virusfood, 30
desc = "A stationary, plumbed, fuel tank."
reagent_id = /datum/reagent/fuel
accepts_rig = TRUE
+
+#undef REAGENT_SPILL_DIVISOR
diff --git a/code/modules/recycling/conveyor.dm b/code/modules/recycling/conveyor.dm
index 8417338851826c..b68230686a8aa0 100644
--- a/code/modules/recycling/conveyor.dm
+++ b/code/modules/recycling/conveyor.dm
@@ -42,6 +42,7 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
AddElement(/datum/element/footstep_override, priority = STEP_SOUND_CONVEYOR_PRIORITY)
var/static/list/give_turf_traits = list(TRAIT_TURF_IGNORE_SLOWDOWN)
AddElement(/datum/element/give_turf_traits, give_turf_traits)
+ register_context()
/obj/machinery/conveyor/examine(mob/user)
. = ..()
@@ -50,6 +51,20 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
. += "\nLeft-click with a wrench to rotate."
. += "Left-click with a screwdriver to invert its direction."
. += "Right-click with a screwdriver to flip its belt around."
+ . += "Using another conveyor belt assembly on this will place a new conveyor belt in the direction this one is pointing."
+
+/obj/machinery/conveyor/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+ if(istype(held_item, /obj/item/stack/conveyor))
+ context[SCREENTIP_CONTEXT_LMB] = "Extend current conveyor belt"
+ return CONTEXTUAL_SCREENTIP_SET
+ if(held_item.tool_behaviour == TOOL_WRENCH)
+ context[SCREENTIP_CONTEXT_LMB] = "Rotate conveyor belt"
+ return CONTEXTUAL_SCREENTIP_SET
+ if(held_item.tool_behaviour == TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "Invert conveyor belt"
+ context[SCREENTIP_CONTEXT_RMB] = "Flip conveyor belt"
+ return CONTEXTUAL_SCREENTIP_SET
/obj/machinery/conveyor/centcom_auto
id = "round_end_belt"
@@ -282,6 +297,19 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
update_move_direction()
to_chat(user, span_notice("You set [src]'s direction [inverted ? "backwards" : "back to default"]."))
+ else if(istype(attacking_item, /obj/item/stack/conveyor))
+ // We should place a new conveyor belt machine on the output turf the conveyor is pointing to.
+ var/turf/target_turf = get_step(get_turf(src), forwards)
+ if(!target_turf)
+ return ..()
+ for(var/obj/machinery/conveyor/belt in target_turf)
+ to_chat(user, span_warning("You cannot place a conveyor belt on top of another conveyor belt."))
+ return ..()
+
+ var/obj/item/stack/conveyor/belt_item = attacking_item
+ belt_item.use(1)
+ new /obj/machinery/conveyor(target_turf, forwards, id)
+
else if(!user.combat_mode)
user.transferItemToLoc(attacking_item, drop_location())
else
@@ -534,6 +562,14 @@ GLOBAL_LIST_EMPTY(conveyors_by_id)
/obj/item/stack/conveyor/update_weight()
return FALSE
+/obj/item/stack/conveyor/examine(mob/user)
+ . = ..()
+ . += span_notice("Use a conveyor switch assembly on this before placing to connect to a lever.")
+
+/obj/item/stack/conveyor/use(used, transfer, check)
+ . = ..()
+ playsound(src, 'sound/weapons/genhit.ogg', 30, TRUE)
+
/obj/item/stack/conveyor/thirty
amount = 30
diff --git a/code/modules/recycling/disposal/multiz.dm b/code/modules/recycling/disposal/multiz.dm
index a4b914d66b8f51..06f4e52a31ce2b 100644
--- a/code/modules/recycling/disposal/multiz.dm
+++ b/code/modules/recycling/disposal/multiz.dm
@@ -21,11 +21,11 @@
return ..()
//Are we a trunk that goes up? Or down?
- var/turf/target = null
+ var/turf/target = get_turf(src)
if(multiz_dir == MULTIZ_PIPE_UP)
- target = GET_TURF_ABOVE(get_turf(src))
+ target = GET_TURF_ABOVE(target)
if(multiz_dir == MULTIZ_PIPE_DOWN)
- target = GET_TURF_BELOW(get_turf(src))
+ target = GET_TURF_BELOW(target)
if(!target) //Nothing located.
return
diff --git a/code/modules/religion/rites.dm b/code/modules/religion/rites.dm
index c7e38145349e0b..d907191c33ddd2 100644
--- a/code/modules/religion/rites.dm
+++ b/code/modules/religion/rites.dm
@@ -221,7 +221,7 @@
user.add_mood_event("maint_adaptation", /datum/mood_event/maintenance_adaptation)
if(iscarbon(user))
var/mob/living/carbon/vomitorium = user
- vomitorium.vomit()
+ vomitorium.vomit(VOMIT_CATEGORY_DEFAULT)
var/datum/dna/dna = vomitorium.has_dna()
dna?.add_mutation(/datum/mutation/human/stimmed) //some fluff mutations
dna?.add_mutation(/datum/mutation/human/strong)
diff --git a/code/modules/research/designs/machine_designs.dm b/code/modules/research/designs/machine_designs.dm
index 6597c12fbec8bb..1c44e8bc4fb268 100644
--- a/code/modules/research/designs/machine_designs.dm
+++ b/code/modules/research/designs/machine_designs.dm
@@ -51,6 +51,16 @@
)
departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
+/datum/design/board/mass_driver
+ name = "Mass Driver Board"
+ desc = "The circuit board for a mass driver."
+ id = "mass_driver"
+ build_path = /obj/item/circuitboard/machine/mass_driver
+ category = list(
+ RND_CATEGORY_MACHINE + RND_SUBCATEGORY_MACHINE_ENGINEERING
+ )
+ departmental_flags = DEPARTMENT_BITFLAG_ENGINEERING
+
/datum/design/board/turbine_compressor
name = "Turbine Compressor Board"
desc = "The circuit board for a turbine compressor."
diff --git a/code/modules/research/designs/mechfabricator_designs.dm b/code/modules/research/designs/mechfabricator_designs.dm
index a9afd6b3a08002..0c1e3498dc1e0b 100644
--- a/code/modules/research/designs/mechfabricator_designs.dm
+++ b/code/modules/research/designs/mechfabricator_designs.dm
@@ -1173,6 +1173,17 @@
RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE
)
+/datum/design/borg_upgrade_drink_apparatus
+ name = "Drink Apparatus"
+ id = "borg_upgrade_drink_apparatus"
+ build_type = MECHFAB
+ build_path = /obj/item/borg/upgrade/drink_app
+ materials = list(/datum/material/iron = HALF_SHEET_MATERIAL_AMOUNT, /datum/material/glass = SHEET_MATERIAL_AMOUNT)
+ construction_time = 4 SECONDS
+ category = list(
+ RND_CATEGORY_MECHFAB_CYBORG_MODULES + RND_SUBCATEGORY_MECHFAB_CYBORG_MODULES_SERVICE
+ )
+
/datum/design/borg_upgrade_service_apparatus
name = "Service Apparatus"
id = "borg_upgrade_service_apparatus"
diff --git a/code/modules/research/server.dm b/code/modules/research/server.dm
index b79bf3f8e02cb8..45a0a520fa0d4c 100644
--- a/code/modules/research/server.dm
+++ b/code/modules/research/server.dm
@@ -109,7 +109,7 @@
if(!stored_research)
return
tool.set_buffer(stored_research)
- to_chat(user, span_notice("Stored [src]'s techweb information in [tool]."))
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/// Master R&D server. As long as this still exists and still holds the HDD for the theft objective, research points generate at normal speed. Destroy it or an antag steals the HDD? Half research speed.
diff --git a/code/modules/research/stock_parts.dm b/code/modules/research/stock_parts.dm
index bdeeec6df4abd4..86b9f2dfafe4da 100644
--- a/code/modules/research/stock_parts.dm
+++ b/code/modules/research/stock_parts.dm
@@ -100,23 +100,21 @@ If you create T5+ please take a pass at mech_fabricator.dm. The parts being good
*/
/obj/item/storage/part_replacer/bluespace/proc/on_part_entered(datum/source, obj/item/inserted_component)
SIGNAL_HANDLER
+
+ if(istype(inserted_component, /obj/item/stock_parts/cell))
+ var/obj/item/stock_parts/cell/inserted_cell = inserted_component
+ if(inserted_cell.rigged || inserted_cell.corrupted)
+ message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted rigged/corrupted [inserted_cell] into [src].")
+ usr.log_message("has inserted rigged/corrupted [inserted_cell] into [src].", LOG_GAME)
+ usr.log_message("inserted rigged/corrupted [inserted_cell] into [src]", LOG_ATTACK)
+ return
+
if(inserted_component.reagents)
if(length(inserted_component.reagents.reagent_list))
inserted_component.reagents.clear_reagents()
to_chat(usr, span_notice("[src] churns as [inserted_component] has its reagents emptied into bluespace."))
RegisterSignal(inserted_component.reagents, COMSIG_REAGENTS_PRE_ADD_REAGENT, PROC_REF(on_insered_component_reagent_pre_add))
-
- if(!istype(inserted_component, /obj/item/stock_parts/cell))
- return
-
- var/obj/item/stock_parts/cell/inserted_cell = inserted_component
-
- if(inserted_cell.rigged || inserted_cell.corrupted)
- message_admins("[ADMIN_LOOKUPFLW(usr)] has inserted rigged/corrupted [inserted_cell] into [src].")
- usr.log_message("has inserted rigged/corrupted [inserted_cell] into [src].", LOG_GAME)
- usr.log_message("inserted rigged/corrupted [inserted_cell] into [src]", LOG_ATTACK)
-
/**
* Signal handler for when the reagents datum of an inserted part has reagents added to it.
*
diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm
index e3fcdba9d64a1a..3a818ea940300c 100644
--- a/code/modules/research/techweb/all_nodes.dm
+++ b/code/modules/research/techweb/all_nodes.dm
@@ -596,6 +596,7 @@
"emergency_oxygen_engi",
"emergency_oxygen",
"emitter",
+ "mass_driver",
"firealarm_electronics",
"firelock_board",
"generic_tank",
@@ -979,6 +980,7 @@
"borg_upgrade_condiment_synthesizer",
"borg_upgrade_silicon_knife",
"borg_upgrade_service_apparatus",
+ "borg_upgrade_drink_apparatus",
"borg_upgrade_service_cookbook",
)
research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2000)
diff --git a/code/modules/research/xenobiology/crossbreeding/_misc.dm b/code/modules/research/xenobiology/crossbreeding/_misc.dm
index 8fc4d50a98cddf..8eb166bf8207fc 100644
--- a/code/modules/research/xenobiology/crossbreeding/_misc.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_misc.dm
@@ -209,9 +209,13 @@ Slimecrossing Items
else
to_chat(user, span_warning("The device is empty..."))
-/obj/item/capturedevice/proc/store(mob/living/M)
- M.forceMove(src)
+/obj/item/capturedevice/proc/store(mob/living/pokemon)
+ pokemon.forceMove(src)
+ pokemon.add_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), ABSTRACT_ITEM_TRAIT)
+ pokemon.cancel_camera()
/obj/item/capturedevice/proc/release()
- for(var/atom/movable/M in contents)
- M.forceMove(get_turf(loc))
+ for(var/mob/living/pokemon in contents)
+ pokemon.forceMove(get_turf(loc))
+ pokemon.remove_traits(list(TRAIT_IMMOBILIZED, TRAIT_HANDS_BLOCKED), ABSTRACT_ITEM_TRAIT)
+ pokemon.cancel_camera()
diff --git a/code/modules/research/xenobiology/crossbreeding/_weapons.dm b/code/modules/research/xenobiology/crossbreeding/_weapons.dm
index a728705579429c..61f4e7a72e0c89 100644
--- a/code/modules/research/xenobiology/crossbreeding/_weapons.dm
+++ b/code/modules/research/xenobiology/crossbreeding/_weapons.dm
@@ -96,6 +96,7 @@ Slimecrossing Weapons
item_flags = ABSTRACT | DROPDEL
w_class = WEIGHT_CLASS_HUGE
slot_flags = NONE
+ antimagic_flags = NONE
force = 5
max_charges = 1 //Recharging costs blood.
recharge_rate = 1
diff --git a/code/modules/research/xenobiology/crossbreeding/charged.dm b/code/modules/research/xenobiology/crossbreeding/charged.dm
index cb7070c83a493c..8941057453ba19 100644
--- a/code/modules/research/xenobiology/crossbreeding/charged.dm
+++ b/code/modules/research/xenobiology/crossbreeding/charged.dm
@@ -235,21 +235,23 @@ Charged extracts:
effect_desc = "Randomizes the user's species."
/obj/item/slimecross/charged/black/do_effect(mob/user)
- var/mob/living/carbon/human/H = user
- if(!istype(H))
- to_chat(user, span_warning("You have to be able to have a species to get your species changed."))
+ var/mob/living/carbon/human/experiment_subject = user
+ if(!istype(experiment_subject))
+ balloon_alert(experiment_subject, "incompatible biology!")
return
var/list/allowed_species = list()
for(var/stype in subtypesof(/datum/species))
- var/datum/species/X = stype
- if(initial(X.changesource_flags) & SLIME_EXTRACT)
+ var/datum/species/try_species = stype
+ if(initial(try_species.changesource_flags) & SLIME_EXTRACT)
allowed_species += stype
var/datum/species/changed = pick(allowed_species)
- if(changed)
- H.set_species(changed, icon_update = 1)
- to_chat(H, span_danger("You feel very different!"))
- ..()
+ if(isnull(changed))
+ visible_message(span_notice("[src] fizzes uselessly."))
+ return
+ experiment_subject.set_species(changed, icon_update = TRUE)
+ to_chat(experiment_subject, span_danger("You feel very different!"))
+ return ..()
/obj/item/slimecross/charged/lightpink
colour = SLIME_TYPE_LIGHT_PINK
diff --git a/code/modules/research/xenobiology/crossbreeding/consuming.dm b/code/modules/research/xenobiology/crossbreeding/consuming.dm
index 43bfd2dbe8abe3..007bacf8bb785f 100644
--- a/code/modules/research/xenobiology/crossbreeding/consuming.dm
+++ b/code/modules/research/xenobiology/crossbreeding/consuming.dm
@@ -355,7 +355,7 @@ Consuming extracts:
/obj/item/slime_cookie/green/do_effect(mob/living/M, mob/user)
if(ishuman(M))
var/mob/living/carbon/human/H = M
- H.vomit(25)
+ H.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 25)
M.reagents.remove_all()
/obj/item/slimecross/consuming/pink
diff --git a/code/modules/security_levels/security_level_datums.dm b/code/modules/security_levels/security_level_datums.dm
index 8ed94e50411380..175b79d1c8771a 100644
--- a/code/modules/security_levels/security_level_datums.dm
+++ b/code/modules/security_levels/security_level_datums.dm
@@ -45,7 +45,7 @@
sound = 'sound/misc/notice2.ogg' // Friendly beep
number_level = SEC_LEVEL_GREEN
lowering_to_configuration_key = /datum/config_entry/string/alert_green
- shuttle_call_time_mod = 2
+ shuttle_call_time_mod = ALERT_COEFF_GREEN
/**
* BLUE
@@ -58,7 +58,7 @@
number_level = SEC_LEVEL_BLUE
lowering_to_configuration_key = /datum/config_entry/string/alert_blue_downto
elevating_to_configuration_key = /datum/config_entry/string/alert_blue_upto
- shuttle_call_time_mod = 1
+ shuttle_call_time_mod = ALERT_COEFF_BLUE
/**
* RED
@@ -71,7 +71,7 @@
number_level = SEC_LEVEL_RED
lowering_to_configuration_key = /datum/config_entry/string/alert_red_downto
elevating_to_configuration_key = /datum/config_entry/string/alert_red_upto
- shuttle_call_time_mod = 0.5
+ shuttle_call_time_mod = ALERT_COEFF_RED
/**
* DELTA
@@ -83,4 +83,4 @@
sound = 'sound/misc/airraid.ogg' // Air alarm to signify importance
number_level = SEC_LEVEL_DELTA
elevating_to_configuration_key = /datum/config_entry/string/alert_delta
- shuttle_call_time_mod = 0.25
+ shuttle_call_time_mod = ALERT_COEFF_DELTA
diff --git a/code/modules/shuttle/emergency.dm b/code/modules/shuttle/emergency.dm
index 3542102b32930d..ab2be975307576 100644
--- a/code/modules/shuttle/emergency.dm
+++ b/code/modules/shuttle/emergency.dm
@@ -339,15 +339,9 @@
/obj/docking_port/mobile/emergency/request(obj/docking_port/stationary/S, area/signal_origin, reason, red_alert, set_coefficient=null, silent=FALSE) //SKYRAT EDIT CHANGE - AUTOTRANSFER
if(!isnum(set_coefficient))
- var/security_num = SSsecurity_level.get_current_level_as_number()
- switch(security_num)
- if(SEC_LEVEL_GREEN)
- set_coefficient = 2
- if(SEC_LEVEL_BLUE)
- set_coefficient = 1
- else
- set_coefficient = 0.5
- var/call_time = SSshuttle.emergency_call_time * set_coefficient * engine_coeff
+ set_coefficient = SSsecurity_level.current_security_level.shuttle_call_time_mod
+ alert_coeff = set_coefficient
+ var/call_time = SSshuttle.emergency_call_time * alert_coeff * engine_coeff
switch(mode)
// The shuttle can not normally be called while "recalling", so
// if this proc is called, it's via admin fiat
diff --git a/code/modules/shuttle/shuttle.dm b/code/modules/shuttle/shuttle.dm
index 2ef513bb01552d..4ae1c241d06f37 100644
--- a/code/modules/shuttle/shuttle.dm
+++ b/code/modules/shuttle/shuttle.dm
@@ -382,8 +382,17 @@
/// This should be a unit test, but too much of our other code breaks during shuttle movement, so not yet, not yet.
/proc/test_whiteship_sizes()
var/obj/docking_port/stationary/port_type = /obj/docking_port/stationary/picked/whiteship
- var/datum/turf_reservation/docking_yard = SSmapping.RequestBlockReservation(initial(port_type.width), initial(port_type.height))
- var/turf/spawnpoint = locate(docking_yard.bottom_left_coords[1] + initial(port_type.dwidth), docking_yard.bottom_left_coords[2] + initial(port_type.dheight), docking_yard.bottom_left_coords[3])
+ var/datum/turf_reservation/docking_yard = SSmapping.request_turf_block_reservation(
+ initial(port_type.width),
+ initial(port_type.height),
+ 1,
+ )
+ var/turf/bottom_left = docking_yard.bottom_left_turfs[1]
+ var/turf/spawnpoint = locate(
+ bottom_left.x + initial(port_type.dwidth),
+ bottom_left.y + initial(port_type.dheight),
+ bottom_left.z,
+ )
var/obj/docking_port/stationary/picked/whiteship/port = new(spawnpoint)
var/list/ids = port.shuttlekeys
@@ -439,7 +448,8 @@
var/current_engine_power = 0
///How much engine power (thrust) the shuttle starts with at mapload.
var/initial_engine_power = 0
-
+ ///Speed multiplier based on station alert level
+ var/alert_coeff = ALERT_COEFF_BLUE
///used as a timer (if you want time left to complete move, use timeLeft proc)
var/timer
var/last_timer_length
@@ -943,6 +953,20 @@
last_timer_length *= multiple
setTimer(time_remaining)
+/obj/docking_port/mobile/proc/alert_coeff_change(new_coeff)
+ if(isnull(new_coeff))
+ return
+
+ var/time_multiplier = new_coeff / alert_coeff
+ var/time_remaining = timer - world.time
+ if(time_remaining < 0 || !last_timer_length)
+ return
+
+ time_remaining *= time_multiplier
+ last_timer_length *= time_multiplier
+ alert_coeff = new_coeff
+ setTimer(time_remaining)
+
/obj/docking_port/mobile/proc/invertTimer()
if(!last_timer_length)
return
diff --git a/code/modules/spells/spell.dm b/code/modules/spells/spell.dm
index aad6135c45a579..966f618376d781 100644
--- a/code/modules/spells/spell.dm
+++ b/code/modules/spells/spell.dm
@@ -209,12 +209,16 @@
// Otherwise, we can check for contents if they have wizardly apparel. This isn't *quite* perfect, but it'll do, especially since many of the edge cases (gorilla holding a wizard hat) still more or less make sense.
if(spell_requirements & SPELL_REQUIRES_WIZARD_GARB)
- for(var/atom/movable/item in owner.contents)
- var/obj/item/clothing/clothem = item
- if(istype(clothem) && clothem.clothing_flags & CASTING_CLOTHES)
- return TRUE
- to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
- return FALSE
+ var/any_casting = FALSE
+ for(var/obj/item/clothing/item in owner)
+ if(item.clothing_flags & CASTING_CLOTHES)
+ any_casting = TRUE
+ break
+
+ if(!any_casting)
+ if(feedback)
+ to_chat(owner, span_warning("You don't feel strong enough without your hat!"))
+ return FALSE
if(!(spell_requirements & SPELL_CASTABLE_AS_BRAIN) && isbrain(owner))
if(feedback)
@@ -298,6 +302,31 @@
/datum/action/cooldown/spell/proc/before_cast(atom/cast_on)
SHOULD_CALL_PARENT(TRUE)
+ // Bonus invocation check done here:
+ // If the caster has no tongue and it's a verbal spell,
+ // Or has no hands and is a gesture spell - cancel it,
+ // and show a funny message that they tried
+ if(ishuman(owner) && !(spell_requirements & SPELL_CASTABLE_WITHOUT_INVOCATION))
+ var/mob/living/carbon/human/caster = owner
+ switch(invocation_type)
+ if(INVOCATION_WHISPER, INVOCATION_SHOUT)
+ if(!caster.get_organ_slot(ORGAN_SLOT_TONGUE))
+ invocation(caster)
+ to_chat(caster, span_warning("Your lack of tongue is making it difficult to say the correct words to cast [src]..."))
+ StartCooldown(2 SECONDS)
+ return SPELL_CANCEL_CAST
+
+ if(INVOCATION_EMOTE)
+ if(caster.usable_hands <= 0)
+ var/arm_describer = (caster.num_hands >= 2 ? "arms limply" : (caster.num_hands == 1 ? "arm wildly" : "arm stumps"))
+ caster.visible_message(
+ span_warning("[caster] wiggles around [caster.p_their()] [arm_describer]."),
+ ignored_mobs = caster,
+ )
+ to_chat(caster, span_warning("You can't position your hands correctly to invoke [src][caster.num_hands > 0 ? "" : ", as you have none"]..."))
+ StartCooldown(2 SECONDS)
+ return SPELL_CANCEL_CAST
+
var/sig_return = SEND_SIGNAL(src, COMSIG_SPELL_BEFORE_CAST, cast_on)
if(owner)
sig_return |= SEND_SIGNAL(owner, COMSIG_MOB_BEFORE_SPELL_CAST, src, cast_on)
diff --git a/code/modules/spells/spell_types/touch/scream_for_me.dm b/code/modules/spells/spell_types/touch/scream_for_me.dm
index e10bdaebcc5ac7..231b6927e504be 100644
--- a/code/modules/spells/spell_types/touch/scream_for_me.dm
+++ b/code/modules/spells/spell_types/touch/scream_for_me.dm
@@ -21,7 +21,7 @@
span_userdanger("The spell bounces from [victim]'s skin back into your arm!"),
)
var/obj/item/bodypart/to_wound = caster.get_holding_bodypart_of_item(hand)
- to_wound.force_wound_upwards(/datum/wound/slash/flesh/critical)
+ caster.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL)
/datum/action/cooldown/spell/touch/scream_for_me/cast_on_hand_hit(obj/item/melee/touch_attack/hand, mob/living/victim, mob/living/carbon/caster)
if(!ishuman(victim))
@@ -29,7 +29,7 @@
var/mob/living/carbon/human/human_victim = victim
human_victim.emote("scream")
for(var/obj/item/bodypart/to_wound as anything in human_victim.bodyparts)
- to_wound.force_wound_upwards(/datum/wound/slash/flesh/critical)
+ human_victim.cause_wound_of_type_and_severity(WOUND_SLASH, to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_CRITICAL)
return TRUE
/obj/item/melee/touch_attack/scream_for_me
diff --git a/code/modules/station_goals/bsa.dm b/code/modules/station_goals/bsa.dm
index 9ac99c86b3c315..ec29b326ae9fc4 100644
--- a/code/modules/station_goals/bsa.dm
+++ b/code/modules/station_goals/bsa.dm
@@ -57,7 +57,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE)
return
var/obj/item/multitool/M = I
M.set_buffer(src)
- to_chat(user, span_notice("You store linkage information in [I]'s buffer."))
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/obj/machinery/bsa/front
@@ -74,7 +74,7 @@ GLOBAL_VAR_INIT(bsa_unlock, FALSE)
return
var/obj/item/multitool/M = I
M.set_buffer(src)
- to_chat(user, span_notice("You store linkage information in [I]'s buffer."))
+ balloon_alert(user, "saved to multitool buffer")
return TRUE
/obj/machinery/bsa/middle
diff --git a/code/modules/surgery/bodyparts/_bodyparts.dm b/code/modules/surgery/bodyparts/_bodyparts.dm
index 5967e3502dc959..4b9c0114024456 100644
--- a/code/modules/surgery/bodyparts/_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/_bodyparts.dm
@@ -1,6 +1,3 @@
-#define AUGGED_LIMB_EMP_BRUTE_DAMAGE 3
-#define AUGGED_LIMB_EMP_BURN_DAMAGE 2
-
/obj/item/bodypart
name = "limb"
desc = "Why is it detached..."
@@ -31,9 +28,9 @@
/**
* A bitfield of biological states, exclusively used to determine which wounds this limb will get,
* as well as how easily it will happen.
- * Set to BIO_STANDARD because most species have both flesh bone and blood in their limbs.
+ * Set to BIO_STANDARD_UNJOINTED because most species have both flesh bone and blood in their limbs.
*/
- var/biological_state = BIO_STANDARD
+ var/biological_state = BIO_STANDARD_UNJOINTED
///A bitfield of bodytypes for clothing, surgery, and misc information
var/bodytype = BODYTYPE_HUMANOID | BODYTYPE_ORGANIC
///Defines when a bodypart should not be changed. Example: BP_BLOCK_CHANGE_SPECIES prevents the limb from being overwritten on species gain
@@ -195,10 +192,12 @@
var/hp_percent_to_dismemberable = 0.8
/// If true, we will use [hp_percent_to_dismemberable] even if we are dismemberable via wounds. Useful for things with extreme wound resistance.
var/use_alternate_dismemberment_calc_even_if_mangleable = FALSE
- /// If false, no wound that can be applied to us can mangle our flesh. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment.
- var/any_existing_wound_can_mangle_our_flesh
- /// If false, no wound that can be applied to us can mangle our bone. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment.
- var/any_existing_wound_can_mangle_our_bone
+ /// If false, no wound that can be applied to us can mangle our exterior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment.
+ var/any_existing_wound_can_mangle_our_exterior
+ /// If false, no wound that can be applied to us can mangle our interior. Used for determining if we should use [hp_percent_to_dismemberable] instead of normal dismemberment.
+ var/any_existing_wound_can_mangle_our_interior
+ /// get_damage() / total_damage must surpass this to allow our limb to be disabled, even temporarily, by an EMP.
+ var/robotic_emp_paralyze_damage_percent_threshold = 0.3
/obj/item/bodypart/apply_fantasy_bonuses(bonus)
. = ..()
@@ -499,73 +498,32 @@
var/mangled_state = get_mangled_state()
var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
- var/has_exterior = FALSE
- var/has_interior = FALSE
-
- for (var/state as anything in GLOB.bio_state_states)
- var/flag = text2num(state)
- if (!(biological_state & flag))
- continue
-
- var/value = GLOB.bio_state_states[state]
- if (value & BIO_EXTERIOR)
- has_exterior = TRUE
- if (value & BIO_INTERIOR)
- has_interior = TRUE
-
- if (has_exterior && has_interior)
- break
-
- // We put this here so we dont increase init time by doing this all at once on initialization
- // Effectively, we "lazy load"
- if (isnull(any_existing_wound_can_mangle_our_bone) || isnull(any_existing_wound_can_mangle_our_flesh))
- any_existing_wound_can_mangle_our_bone = FALSE
- any_existing_wound_can_mangle_our_flesh = FALSE
- for (var/datum/wound/wound_type as anything in GLOB.all_wound_pregen_data)
- var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_type]
- if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific
- continue
- if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_FLESH)
- any_existing_wound_can_mangle_our_flesh = TRUE
- if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_BONE)
- any_existing_wound_can_mangle_our_bone = TRUE
-
- if (any_existing_wound_can_mangle_our_bone && any_existing_wound_can_mangle_our_flesh)
- break
+ var/bio_status = get_bio_state_status()
- var/can_theoretically_be_dismembered = (any_existing_wound_can_mangle_our_bone || (any_existing_wound_can_mangle_our_flesh && !has_exterior))
+ var/has_exterior = ((bio_status & ANATOMY_EXTERIOR))
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
- var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_BONE) == BODYPART_MANGLED_BONE))
- var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_FLESH) == BODYPART_MANGLED_FLESH))
+ var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR)))
// if we're bone only, all cutting attacks go straight to the bone
- if(has_exterior && interior_ready_to_dismember)
+ if(!has_exterior && has_interior)
if(wounding_type == WOUND_SLASH)
wounding_type = WOUND_BLUNT
wounding_dmg *= (easy_dismember ? 1 : 0.6)
else if(wounding_type == WOUND_PIERCE)
wounding_type = WOUND_BLUNT
wounding_dmg *= (easy_dismember ? 1 : 0.75)
- if(exterior_ready_to_dismember && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
- return
else
// if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
// So a big sharp weapon is still all you need to destroy a limb
- if(has_exterior && interior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_BONE) && sharpness)
- playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
+ if(has_interior && exterior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_INTERIOR) && sharpness)
if(wounding_type == WOUND_SLASH && !easy_dismember)
wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area
if(wounding_type == WOUND_PIERCE && !easy_dismember)
wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
wounding_type = WOUND_BLUNT
- else if(interior_ready_to_dismember && exterior_ready_to_dismember && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
- return
- if (use_alternate_dismemberment_calc_even_if_mangleable || !can_theoretically_be_dismembered)
- var/percent_to_total_max = (get_damage() / max_damage)
- if (percent_to_total_max >= hp_percent_to_dismemberable)
- if (try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
- return
-
+ if ((dismemberable_by_wound() || dismemberable_by_total_damage()) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
+ return
// now we have our wounding_type and are ready to carry on with wounds and dealing the actual damage
if(wounding_dmg >= WOUND_MINIMUM_DAMAGE && wound_bonus != CANT_WOUND)
//SKYRAT EDIT ADDITION - MEDICAL
@@ -611,6 +569,83 @@
owner.updatehealth()
return update_bodypart_damage_state() || .
+/// Returns a bitflag using ANATOMY_EXTERIOR or ANATOMY_INTERIOR. Used to determine if we as a whole have a interior or exterior biostate, or both.
+/obj/item/bodypart/proc/get_bio_state_status()
+ SHOULD_BE_PURE(TRUE)
+
+ var/bio_status = NONE
+
+ for (var/state as anything in GLOB.bio_state_anatomy)
+ var/flag = text2num(state)
+ if (!(biological_state & flag))
+ continue
+
+ var/value = GLOB.bio_state_anatomy[state]
+ if (value & ANATOMY_EXTERIOR)
+ bio_status |= ANATOMY_EXTERIOR
+ if (value & ANATOMY_INTERIOR)
+ bio_status |= ANATOMY_INTERIOR
+
+ if ((bio_status & ANATOMY_EXTERIOR_AND_INTERIOR) == ANATOMY_EXTERIOR_AND_INTERIOR)
+ break
+
+ return bio_status
+
+/// Returns if our current mangling status allows us to be dismembered. Requires both no exterior/mangled exterior and no interior/mangled interior.
+/obj/item/bodypart/proc/dismemberable_by_wound()
+ SHOULD_BE_PURE(TRUE)
+
+ var/mangled_state = get_mangled_state()
+
+ var/bio_status = get_bio_state_status()
+
+ var/has_exterior = ((bio_status & ANATOMY_EXTERIOR))
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
+
+ var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR)))
+ var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_INTERIOR)))
+
+ return (exterior_ready_to_dismember && interior_ready_to_dismember)
+
+/// Returns TRUE if our total percent damage is more or equal to our dismemberable percentage, but FALSE if a wound can cause us to be dismembered.
+/obj/item/bodypart/proc/dismemberable_by_total_damage()
+
+ update_wound_theory()
+
+ var/bio_status = get_bio_state_status()
+
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
+ var/can_theoretically_be_dismembered_by_wound = (any_existing_wound_can_mangle_our_interior || (any_existing_wound_can_mangle_our_exterior && has_interior))
+
+ var/wound_dismemberable = dismemberable_by_wound()
+ var/ready_to_use_alternate_formula = (use_alternate_dismemberment_calc_even_if_mangleable || (!wound_dismemberable && !can_theoretically_be_dismembered_by_wound))
+
+ if (ready_to_use_alternate_formula)
+ var/percent_to_total_max = (get_damage() / max_damage)
+ if (percent_to_total_max >= hp_percent_to_dismemberable)
+ return TRUE
+
+ return FALSE
+
+/// Updates our "can be theoretically dismembered by wounds" variables by iterating through all wound static data.
+/obj/item/bodypart/proc/update_wound_theory()
+ // We put this here so we dont increase init time by doing this all at once on initialization
+ // Effectively, we "lazy load"
+ if (isnull(any_existing_wound_can_mangle_our_interior) || isnull(any_existing_wound_can_mangle_our_exterior))
+ any_existing_wound_can_mangle_our_interior = FALSE
+ any_existing_wound_can_mangle_our_exterior = FALSE
+ for (var/datum/wound/wound_type as anything in GLOB.all_wound_pregen_data)
+ var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_type]
+ if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific
+ continue
+ if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_EXTERIOR)
+ any_existing_wound_can_mangle_our_exterior = TRUE
+ if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_INTERIOR)
+ any_existing_wound_can_mangle_our_interior = TRUE
+
+ if (any_existing_wound_can_mangle_our_interior && any_existing_wound_can_mangle_our_exterior)
+ break
+
//Heals brute and burn damage for the organ. Returns 1 if the damage-icon states changed at all.
//Damage cannot go below zero.
//Cannot remove negative damage (i.e. apply damage)
@@ -1201,9 +1236,6 @@
for(var/datum/wound/iter_wound as anything in wounds)
cached_bleed_rate += iter_wound.blood_flow
- if(!cached_bleed_rate)
- QDEL_NULL(grasped_by)
-
// Our bleed overlay is based directly off bleed_rate, so go aheead and update that would you?
if(cached_bleed_rate != old_bleed_rate)
update_part_wound_overlay()
@@ -1355,13 +1387,13 @@
. = ..()
if(. & EMP_PROTECT_WIRES || !IS_ROBOTIC_LIMB(src))
return FALSE
- owner.visible_message(span_danger("[owner]'s [src.name] seems to malfunction!"))
-
- // with defines at the time of writing, this is 3 brute and 2 burn
- // 3 + 2 = 5, with 6 limbs thats 30, on a heavy 60
- // 60 * 0.8 = 48
- var/time_needed = 10 SECONDS
+ // with defines at the time of writing, this is 2 brute and 1.5 burn
+ // 2 + 1.5 = 3,5, with 6 limbs thats 21, on a heavy 42
+ // 42 * 0.8 = 33.6
+ // 3 hits to crit with an ion rifle on someone fully augged at a total of 100.8 damage, although im p sure mood can boost max hp above 100
+ // dont forget emps pierce armor, debilitate augs, and usually comes with splash damage e.g. ion rifles or grenades
+ var/time_needed = AUGGED_LIMB_EMP_PARALYZE_TIME
var/brute_damage = AUGGED_LIMB_EMP_BRUTE_DAMAGE
var/burn_damage = AUGGED_LIMB_EMP_BURN_DAMAGE
if(severity == EMP_HEAVY)
@@ -1371,9 +1403,30 @@
receive_damage(brute_damage, burn_damage)
do_sparks(number = 1, cardinal_only = FALSE, source = owner)
- ADD_TRAIT(src, TRAIT_PARALYSIS, EMP_TRAIT)
- addtimer(CALLBACK(src, PROC_REF(un_paralyze)), time_needed)
+ var/damage_percent_to_max = (get_damage() / max_damage)
+ if (time_needed && (damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold))
+ owner.visible_message(span_danger("[owner]'s [src] seems to malfunction!"))
+ ADD_TRAIT(src, TRAIT_PARALYSIS, EMP_TRAIT)
+ addtimer(CALLBACK(src, PROC_REF(un_paralyze)), time_needed)
return TRUE
/obj/item/bodypart/proc/un_paralyze()
REMOVE_TRAITS_IN(src, EMP_TRAIT)
+
+/// Returns the generic description of our BIO_EXTERNAL feature(s), prioritizing certain ones over others. Returns error on failure.
+/obj/item/bodypart/proc/get_external_description()
+ if (biological_state & BIO_FLESH)
+ return "flesh"
+ if (biological_state & BIO_WIRED)
+ return "wiring"
+
+ return "error"
+
+/// Returns the generic description of our BIO_INTERNAL feature(s), prioritizing certain ones over others. Returns error on failure.
+/obj/item/bodypart/proc/get_internal_description()
+ if (biological_state & BIO_BONE)
+ return "bone"
+ if (biological_state & BIO_METAL)
+ return "metal"
+
+ return "error"
diff --git a/code/modules/surgery/bodyparts/dismemberment.dm b/code/modules/surgery/bodyparts/dismemberment.dm
index 22a325f1974a00..5a7343f8a0b3a1 100644
--- a/code/modules/surgery/bodyparts/dismemberment.dm
+++ b/code/modules/surgery/bodyparts/dismemberment.dm
@@ -5,7 +5,7 @@
return TRUE
///Remove target limb from it's owner, with side effects.
-/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE, wound_type)
+/obj/item/bodypart/proc/dismember(dam_type = BRUTE, silent=TRUE, wounding_type)
if(!owner || (bodypart_flags & BODYPART_UNREMOVABLE))
return FALSE
var/mob/living/carbon/limb_owner = owner
@@ -23,14 +23,14 @@
limb_owner.add_mood_event("dismembered_[body_zone]", /datum/mood_event/dismembered, src)
limb_owner.add_mob_memory(/datum/memory/was_dismembered, lost_limb = src)
- if (wound_type)
- LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wound_type)
+ if (wounding_type)
+ LAZYSET(limb_owner.body_zone_dismembered_by, body_zone, wounding_type)
drop_limb()
limb_owner.update_equipment_speed_mods() // Update in case speed affecting item unequipped by dismemberment
var/turf/owner_location = limb_owner.loc
- if(wound_type != WOUND_BURN && istype(owner_location) && can_bleed())
+ if(wounding_type != WOUND_BURN && istype(owner_location) && can_bleed())
limb_owner.add_splatter_floor(owner_location)
if(QDELETED(src)) //Could have dropped into lava/explosion/chasm/whatever
@@ -55,7 +55,7 @@
return TRUE
-/obj/item/bodypart/chest/dismember(dam_type = BRUTE, silent=TRUE, wound_type)
+/obj/item/bodypart/chest/dismember(dam_type = BRUTE, silent=TRUE, wounding_type)
if(!owner)
return FALSE
var/mob/living/carbon/chest_owner = owner
@@ -64,7 +64,7 @@
if(HAS_TRAIT(chest_owner, TRAIT_NODISMEMBER))
return FALSE
. = list()
- if(wound_type != WOUND_BURN && isturf(chest_owner.loc) && can_bleed())
+ if(wounding_type != WOUND_BURN && isturf(chest_owner.loc) && can_bleed())
chest_owner.add_splatter_floor(chest_owner.loc)
playsound(get_turf(chest_owner), 'sound/misc/splort.ogg', 80, TRUE)
for(var/obj/item/organ/organ as anything in chest_owner.organs)
@@ -158,16 +158,16 @@
* Dismemberment for flesh and bone requires the victim to have the skin on their bodypart destroyed (either a critical cut or piercing wound), and at least a hairline fracture
* (severe bone), at which point we can start rolling for dismembering. The attack must also deal at least 10 damage, and must be a brute attack of some kind (sorry for now, cakehat, maybe later)
*
- * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_FLESH if our skin is broken, BODYPART_MANGLED_BONE if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering
+ * Returns: BODYPART_MANGLED_NONE if we're fine, BODYPART_MANGLED_EXTERIOR if our skin is broken, BODYPART_MANGLED_INTERIOR if our bone is broken, or BODYPART_MANGLED_BOTH if both are broken and we're up for dismembering
*/
/obj/item/bodypart/proc/get_mangled_state()
. = BODYPART_MANGLED_NONE
for(var/datum/wound/iter_wound as anything in wounds)
- if((iter_wound.wound_flags & MANGLES_BONE))
- . |= BODYPART_MANGLED_BONE
- if((iter_wound.wound_flags & MANGLES_FLESH))
- . |= BODYPART_MANGLED_FLESH
+ if((iter_wound.wound_flags & MANGLES_INTERIOR))
+ . |= BODYPART_MANGLED_INTERIOR
+ if((iter_wound.wound_flags & MANGLES_EXTERIOR))
+ . |= BODYPART_MANGLED_EXTERIOR
/**
* try_dismember() is used, once we've confirmed that a flesh and bone bodypart has both the skin and bone mangled, to actually roll for it
@@ -445,8 +445,8 @@
if (LAZYLEN(dismembered_by_copy))
var/datum/scar/scaries = new
var/datum/wound/loss/phantom_loss = new // stolen valor, really
- phantom_loss.loss_wound_type = dismembered_by_copy?[limb_zone]
- if (phantom_loss.loss_wound_type)
+ phantom_loss.loss_wounding_type = dismembered_by_copy?[limb_zone]
+ if (phantom_loss.loss_wounding_type)
scaries.generate(limb, phantom_loss)
LAZYREMOVE(dismembered_by_copy, limb_zone) // in case we're using a passed list
else
diff --git a/code/modules/surgery/bodyparts/parts.dm b/code/modules/surgery/bodyparts/parts.dm
index d022e003f2f95c..4207f3541f6415 100644
--- a/code/modules/surgery/bodyparts/parts.dm
+++ b/code/modules/surgery/bodyparts/parts.dm
@@ -53,7 +53,7 @@
if(cavity_item)
cavity_item.forceMove(drop_location())
cavity_item = null
- ..()
+ return ..()
/obj/item/bodypart/chest/monkey
icon = 'icons/mob/human/species/monkey/bodyparts.dmi'
@@ -114,7 +114,7 @@
/// Datum describing how to offset things held in the hands of this arm, the x offset IS functional here
var/datum/worn_feature_offset/held_hand_offset
- biological_state = (BIO_STANDARD|BIO_JOINTED)
+ biological_state = BIO_STANDARD_JOINTED
/obj/item/bodypart/arm/Destroy()
QDEL_NULL(worn_glove_offset)
@@ -346,7 +346,7 @@
/// Datum describing how to offset things worn on the foot of this leg, note that an x offset won't do anything here
var/datum/worn_feature_offset/worn_foot_offset
- biological_state = (BIO_STANDARD|BIO_JOINTED)
+ biological_state = BIO_STANDARD_JOINTED
/obj/item/bodypart/leg/Destroy()
QDEL_NULL(worn_foot_offset)
diff --git a/code/modules/surgery/bodyparts/robot_bodyparts.dm b/code/modules/surgery/bodyparts/robot_bodyparts.dm
index 0382f4b048f4f0..37b6cef9897507 100644
--- a/code/modules/surgery/bodyparts/robot_bodyparts.dm
+++ b/code/modules/surgery/bodyparts/robot_bodyparts.dm
@@ -1,3 +1,4 @@
+
#define ROBOTIC_LIGHT_BRUTE_MSG "marred"
#define ROBOTIC_MEDIUM_BRUTE_MSG "dented"
#define ROBOTIC_HEAVY_BRUTE_MSG "falling apart"
@@ -112,10 +113,13 @@
. = ..()
if(!.)
return
- owner.Knockdown(severity == EMP_HEAVY ? 20 SECONDS : 10 SECONDS)
+ var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME
+ if (severity == EMP_HEAVY)
+ knockdown_time *= 2
+ owner.Knockdown(knockdown_time)
if(owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways.
return
- to_chat(owner, span_danger("As your [src.name] unexpectedly malfunctions, it causes you to fall to the ground!"))
+ to_chat(owner, span_danger("As your [src] unexpectedly malfunctions, it causes you to fall to the ground!"))
/obj/item/bodypart/leg/right/robot
name = "cyborg right leg"
@@ -154,10 +158,13 @@
. = ..()
if(!.)
return
- owner.Knockdown(severity == EMP_HEAVY ? 20 SECONDS : 10 SECONDS)
+ var/knockdown_time = AUGGED_LEG_EMP_KNOCKDOWN_TIME
+ if (severity == EMP_HEAVY)
+ knockdown_time *= 2
+ owner.Knockdown(knockdown_time)
if(owner.incapacitated(IGNORE_RESTRAINTS|IGNORE_GRAB)) // So the message isn't duplicated. If they were stunned beforehand by something else, then the message not showing makes more sense anyways.
return
- to_chat(owner, span_danger("As your [src.name] unexpectedly malfunctions, it causes you to fall to the ground!"))
+ to_chat(owner, span_danger("As your [src] unexpectedly malfunctions, it causes you to fall to the ground!"))
/obj/item/bodypart/chest/robot
name = "cyborg torso"
@@ -192,18 +199,29 @@
var/wired = FALSE
var/obj/item/stock_parts/cell/cell = null
+ robotic_emp_paralyze_damage_percent_threshold = 0.6
+
/obj/item/bodypart/chest/robot/emp_act(severity)
. = ..()
if(!.)
return
- to_chat(owner, span_danger("Your [src.name]'s logic boards temporarily become unresponsive!"))
+
+ var/stun_time = 0
+ var/shift_x = 3
+ var/shift_y = 0
+ var/shake_duration = AUGGED_CHEST_EMP_SHAKE_TIME
+
if(severity == EMP_HEAVY)
- owner.Stun(6 SECONDS)
- owner.Shake(pixelshiftx = 5, pixelshifty = 2, duration = 4 SECONDS)
- return
+ stun_time = AUGGED_CHEST_EMP_STUN_TIME
+
+ shift_x = 5
+ shift_y = 2
- owner.Stun(3 SECONDS)
- owner.Shake(pixelshiftx = 3, pixelshifty = 0, duration = 2.5 SECONDS)
+ var/damage_percent_to_max = (get_damage() / max_damage)
+ if (stun_time && (damage_percent_to_max >= robotic_emp_paralyze_damage_percent_threshold))
+ to_chat(owner, span_danger("Your [src]'s logic boards temporarily become unresponsive!"))
+ owner.Stun(stun_time)
+ owner.Shake(pixelshiftx = shift_x, pixelshifty = shift_y, duration = shake_duration)
/obj/item/bodypart/chest/robot/get_cell()
return cell
@@ -322,9 +340,11 @@
. = ..()
if(!.)
return
- to_chat(owner, span_danger("Your [src.name]'s optical transponders glitch out and malfunction!"))
+ to_chat(owner, span_danger("Your [src]'s optical transponders glitch out and malfunction!"))
- var/glitch_duration = severity == EMP_HEAVY ? 15 SECONDS : 7.5 SECONDS
+ var/glitch_duration = AUGGED_HEAD_EMP_GLITCH_DURATION
+ if (severity == EMP_HEAVY)
+ glitch_duration *= 2
owner.add_client_colour(/datum/client_colour/malfunction)
@@ -408,7 +428,7 @@
icon = 'icons/mob/augmentation/surplus_augments.dmi'
burn_modifier = 1
brute_modifier = 1
- max_damage = 20
+ max_damage = PROSTHESIS_MAX_HP
biological_state = (BIO_METAL|BIO_JOINTED)
@@ -419,7 +439,7 @@
icon = 'icons/mob/augmentation/surplus_augments.dmi'
burn_modifier = 1
brute_modifier = 1
- max_damage = 20
+ max_damage = PROSTHESIS_MAX_HP
biological_state = (BIO_METAL|BIO_JOINTED)
@@ -430,7 +450,7 @@
icon = 'icons/mob/augmentation/surplus_augments.dmi'
brute_modifier = 1
burn_modifier = 1
- max_damage = 20
+ max_damage = PROSTHESIS_MAX_HP
biological_state = (BIO_METAL|BIO_JOINTED)
@@ -441,7 +461,7 @@
icon = 'icons/mob/augmentation/surplus_augments.dmi'
brute_modifier = 1
burn_modifier = 1
- max_damage = 20
+ max_damage = PROSTHESIS_MAX_HP
biological_state = (BIO_METAL|BIO_JOINTED)
diff --git a/code/modules/surgery/bodyparts/wounds.dm b/code/modules/surgery/bodyparts/wounds.dm
index db1407953b656e..1b50dbc8fd1698 100644
--- a/code/modules/surgery/bodyparts/wounds.dm
+++ b/code/modules/surgery/bodyparts/wounds.dm
@@ -1,81 +1,40 @@
/// Allows us to roll for and apply a wound without actually dealing damage. Used for aggregate wounding power with pellet clouds
-/obj/item/bodypart/proc/painless_wound_roll(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=NONE)
+/obj/item/bodypart/proc/painless_wound_roll(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus, sharpness=NONE)
SHOULD_CALL_PARENT(TRUE)
- if(!owner || phantom_wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND || (owner.status_flags & GODMODE))
+ if(!owner || wounding_dmg <= WOUND_MINIMUM_DAMAGE || wound_bonus == CANT_WOUND || (owner.status_flags & GODMODE))
return
var/mangled_state = get_mangled_state()
var/easy_dismember = HAS_TRAIT(owner, TRAIT_EASYDISMEMBER) // if we have easydismember, we don't reduce damage when redirecting damage to different types (slashing weapons on mangled/skinless limbs attack at 100% instead of 50%)
- var/has_exterior = FALSE
- var/has_interior = FALSE
+ var/bio_status = get_bio_state_status()
- for (var/state as anything in GLOB.bio_state_states)
- var/flag = text2num(state)
- if (!(biological_state & flag))
- continue
-
- var/value = GLOB.bio_state_states[state]
- if (value & BIO_EXTERIOR)
- has_exterior = TRUE
- if (value & BIO_INTERIOR)
- has_interior = TRUE
-
- if (has_exterior && has_interior)
- break
-
- // We put this here so we dont increase init time by doing this all at once on initialization
- // Effectively, we "lazy load"
- if (isnull(any_existing_wound_can_mangle_our_bone) || isnull(any_existing_wound_can_mangle_our_flesh))
- any_existing_wound_can_mangle_our_bone = FALSE
- any_existing_wound_can_mangle_our_flesh = FALSE
- for (var/datum/wound/wound_type as anything in GLOB.all_wound_pregen_data)
- var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_type]
- if (!pregen_data.can_be_applied_to(src, random_roll = TRUE)) // we only consider randoms because non-randoms are usually really specific
- continue
- if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_FLESH)
- any_existing_wound_can_mangle_our_flesh = TRUE
- if (initial(pregen_data.wound_path_to_generate.wound_flags) & MANGLES_BONE)
- any_existing_wound_can_mangle_our_bone = TRUE
-
- if (any_existing_wound_can_mangle_our_bone && any_existing_wound_can_mangle_our_flesh)
- break
+ var/has_exterior = ((bio_status & ANATOMY_EXTERIOR))
+ var/has_interior = ((bio_status & ANATOMY_INTERIOR))
- var/can_theoretically_be_dismembered = (any_existing_wound_can_mangle_our_bone || (any_existing_wound_can_mangle_our_flesh && !has_exterior))
-
- var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_BONE) == BODYPART_MANGLED_BONE))
- var/interior_ready_to_dismember = (!has_interior || ((mangled_state & BODYPART_MANGLED_FLESH) == BODYPART_MANGLED_FLESH))
+ var/exterior_ready_to_dismember = (!has_exterior || ((mangled_state & BODYPART_MANGLED_EXTERIOR)))
// if we're bone only, all cutting attacks go straight to the bone
- if(has_exterior && interior_ready_to_dismember)
+ if(!has_exterior && has_interior)
if(wounding_type == WOUND_SLASH)
wounding_type = WOUND_BLUNT
- phantom_wounding_dmg *= (easy_dismember ? 1 : 0.6)
+ wounding_dmg *= (easy_dismember ? 1 : 0.6)
else if(wounding_type == WOUND_PIERCE)
wounding_type = WOUND_BLUNT
- phantom_wounding_dmg *= (easy_dismember ? 1 : 0.75)
- if(exterior_ready_to_dismember && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
- return
+ wounding_dmg *= (easy_dismember ? 1 : 0.75)
else
// if we've already mangled the skin (critical slash or piercing wound), then the bone is exposed, and we can damage it with sharp weapons at a reduced rate
// So a big sharp weapon is still all you need to destroy a limb
- if(has_exterior && interior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_BONE) && sharpness)
- playsound(src, "sound/effects/wounds/crackandbleed.ogg", 100)
+ if(has_interior && exterior_ready_to_dismember && !(mangled_state & BODYPART_MANGLED_INTERIOR) && sharpness)
if(wounding_type == WOUND_SLASH && !easy_dismember)
- phantom_wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area
+ wounding_dmg *= 0.6 // edged weapons pass along 60% of their wounding damage to the bone since the power is spread out over a larger area
if(wounding_type == WOUND_PIERCE && !easy_dismember)
- phantom_wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
+ wounding_dmg *= 0.75 // piercing weapons pass along 75% of their wounding damage to the bone since it's more concentrated
wounding_type = WOUND_BLUNT
- else if(interior_ready_to_dismember && exterior_ready_to_dismember && try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
+ if ((dismemberable_by_wound() || dismemberable_by_total_damage()) && try_dismember(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus))
return
- if (use_alternate_dismemberment_calc_even_if_mangleable || !can_theoretically_be_dismembered)
- var/percent_to_total_max = (get_damage() / max_damage)
- if (percent_to_total_max >= hp_percent_to_dismemberable)
- if (try_dismember(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus))
- return
-
- return check_wounding(wounding_type, phantom_wounding_dmg, wound_bonus, bare_wound_bonus)
+ return check_wounding(wounding_type, wounding_dmg, wound_bonus, bare_wound_bonus)
/**
* check_wounding() is where we handle rolling for, selecting, and applying a wound if we meet the criteria
@@ -111,6 +70,8 @@
var/base_roll = rand(1, round(damage ** WOUND_DAMAGE_EXPONENT))
var/injury_roll = base_roll
injury_roll += check_woundings_mods(woundtype, damage, wound_bonus, bare_wound_bonus)
+ var/list/series_wounding_mods = check_series_wounding_mods()
+
if(injury_roll > WOUND_DISMEMBER_OUTRIGHT_THRESH && prob(get_damage() / max_damage * 100))
var/datum/wound/loss/dismembering = new
dismembering.apply_dismember(src, woundtype, outright = TRUE, attack_direction = attack_direction)
@@ -119,8 +80,8 @@
var/list/datum/wound/possible_wounds = list()
for (var/datum/wound/type as anything in GLOB.all_wound_pregen_data)
var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[type]
- if (pregen_data.can_be_applied_to(src, woundtype, random_roll = TRUE))
- possible_wounds += type
+ if (pregen_data.can_be_applied_to(src, list(woundtype), random_roll = TRUE))
+ possible_wounds[type] = pregen_data.get_weight(src, woundtype, damage, attack_direction, damage_source)
// quick re-check to see if bare_wound_bonus applies, for the benefit of log_wound(), see about getting the check from check_woundings_mods() somehow
if(ishuman(owner))
var/mob/living/carbon/human/human_wearer = owner
@@ -131,39 +92,137 @@
bare_wound_bonus = 0
break
- //cycle through the wounds of the relevant category from the most severe down
- for(var/datum/wound/possible_wound as anything in possible_wounds)
+ for (var/datum/wound/iterated_path as anything in possible_wounds)
+ for (var/datum/wound/existing_wound as anything in wounds)
+ if (iterated_path == existing_wound.type)
+ possible_wounds -= iterated_path
+ break // breaks out of the nested loop
+
+ var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_path]
+ var/specific_injury_roll = (injury_roll + series_wounding_mods[pregen_data.wound_series])
+ if (pregen_data.get_threshold_for(src, attack_direction, damage_source) > specific_injury_roll)
+ possible_wounds -= iterated_path
+ continue
+
+ if (pregen_data.compete_for_wounding)
+ for (var/datum/wound/other_path as anything in possible_wounds)
+ if (other_path == iterated_path)
+ continue
+ if (initial(iterated_path.severity) == initial(other_path.severity) && pregen_data.overpower_wounds_of_even_severity)
+ possible_wounds -= other_path
+ continue
+ else if (pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_LESSERS)
+ if (initial(iterated_path.severity) > initial(other_path.severity))
+ possible_wounds -= other_path
+ continue
+ else if (pregen_data.competition_mode == WOUND_COMPETITION_OVERPOWER_GREATERS)
+ if (initial(iterated_path.severity) < initial(other_path.severity))
+ possible_wounds -= other_path
+ continue
+
+ while (length(possible_wounds))
+ var/datum/wound/possible_wound = pick_weight(possible_wounds)
+ var/datum/wound_pregen_data/possible_pregen_data = GLOB.all_wound_pregen_data[possible_wound]
+ possible_wounds -= possible_wound
+
var/datum/wound/replaced_wound
for(var/datum/wound/existing_wound as anything in wounds)
- if(existing_wound.wound_series == initial(possible_wound.wound_series))
+ var/datum/wound_pregen_data/existing_pregen_data = GLOB.all_wound_pregen_data[existing_wound.type]
+ if(existing_pregen_data.wound_series == possible_pregen_data.wound_series)
if(existing_wound.severity >= initial(possible_wound.severity))
- return
+ continue
else
- replaced_wound = existing_wound // if we find something we keep iterating untilw e're done or we find we're outclassed by something in our series
-
- if(initial(possible_wound.threshold_minimum) < injury_roll)
- var/datum/wound/new_wound
- if(replaced_wound)
- new_wound = replaced_wound.replace_wound(new possible_wound, attack_direction = attack_direction)
- else
- new_wound = new possible_wound
- new_wound.apply_wound(src, attack_direction = attack_direction, wound_source = damage_source)
- log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned
- return new_wound
+ replaced_wound = existing_wound
+ // if we get through this whole loop without continuing, we found our winner
+
+ var/datum/wound/new_wound = new possible_wound
+ if(replaced_wound)
+ new_wound = replaced_wound.replace_wound(new_wound, attack_direction = attack_direction)
+ else
+ new_wound.apply_wound(src, attack_direction = attack_direction, wound_source = damage_source)
+ log_wound(owner, new_wound, damage, wound_bonus, bare_wound_bonus, base_roll) // dismembering wounds are logged in the apply_wound() for loss wounds since they delete themselves immediately, these will be immediately returned
+ return new_wound
// try forcing a specific wound, but only if there isn't already a wound of that severity or greater for that type on this bodypart
-/obj/item/bodypart/proc/force_wound_upwards(specific_woundtype, smited = FALSE, wound_source)
+/obj/item/bodypart/proc/force_wound_upwards(datum/wound/potential_wound, smited = FALSE, wound_source)
SHOULD_NOT_OVERRIDE(TRUE)
- var/datum/wound/potential_wound = specific_woundtype
+ if (isnull(potential_wound))
+ return
+
+ var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[potential_wound]
for(var/datum/wound/existing_wound as anything in wounds)
- if (existing_wound.wound_series == initial(potential_wound.wound_series))
+ var/datum/wound_pregen_data/existing_pregen_data = existing_wound.get_pregen_data()
+ if (existing_pregen_data.wound_series == pregen_data.wound_series)
if(existing_wound.severity < initial(potential_wound.severity)) // we only try if the existing one is inferior to the one we're trying to force
existing_wound.replace_wound(new potential_wound, smited)
return
var/datum/wound/new_wound = new potential_wound
new_wound.apply_wound(src, smited = smited, wound_source = wound_source)
+ return new_wound
+
+/**
+ * A simple proc to force a type of wound onto this mob. If you just want to force a specific mainline (fractures, bleeding, etc.) wound, you only need to care about the first 3 args.
+ *
+ * Args:
+ * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list.
+ * * obj/item/bodypart/limb: The limb we wil be applying the wound to. If null, a random bodypart will be picked.
+ * * min_severity: The minimum severity that will be considered.
+ * * max_severity: The maximum severity that will be considered.
+ * * severity_pick_mode: The "pick mode" to be used. See get_corresponding_wound_type's documentation
+ * * wound_source: The source of the wound to be applied. Nullable.
+ *
+ * For the rest of the args, refer to get_corresponding_wound_type().
+ *
+ * Returns:
+ * A new wound instance if the application was successful, null otherwise.
+*/
+/mob/living/carbon/proc/cause_wound_of_type_and_severity(wounding_type, obj/item/bodypart/limb, min_severity, max_severity = min_severity, severity_pick_mode = WOUND_PICK_HIGHEST_SEVERITY, wound_source)
+ if (isnull(limb))
+ limb = pick(bodyparts)
+
+ var/list/type_list = wounding_type
+ if (!islist(type_list))
+ type_list = list(type_list)
+
+ var/datum/wound/corresponding_typepath = get_corresponding_wound_type(type_list, limb, min_severity, max_severity, severity_pick_mode)
+ if (corresponding_typepath)
+ return limb.force_wound_upwards(corresponding_typepath, wound_source = wound_source)
+
+/// Limb is nullable, but picks a random one. Defers to limb.get_wound_threshold_of_wound_type, see it for documentation.
+/mob/living/carbon/proc/get_wound_threshold_of_wound_type(wounding_type, severity, default, obj/item/bodypart/limb, wound_source)
+ if (isnull(limb))
+ limb = pick(bodyparts)
+
+ if (!limb)
+ return default
+
+ return limb.get_wound_threshold_of_wound_type(wounding_type, severity, default, wound_source)
+
+/**
+ * A simple proc that gets the best wound to fit the criteria laid out, then returns its wound threshold.
+ *
+ * Args:
+ * * wounding_type: The wounding_type, e.g. WOUND_BLUNT, WOUND_SLASH to force onto the mob. Can be a list of wounding_types.
+ * * severity: The severity that will be considered.
+ * * return_value_if_no_wound: If no wound is found, we will return this instead. (It is reccomended to use named args for this one, as its unclear what it is without)
+ * * wound_source: The theoretical source of the wound. Nullable.
+ *
+ * Returns:
+ * return_value_if_no_wound if no wound is found - if one IS found, the wound threshold for that wound.
+ */
+/obj/item/bodypart/proc/get_wound_threshold_of_wound_type(wounding_type, severity, return_value_if_no_wound, wound_source)
+ var/list/type_list = wounding_type
+ if (!islist(type_list))
+ type_list = list(type_list)
+
+ var/datum/wound/wound_path = get_corresponding_wound_type(type_list, src, severity, duplicates_allowed = TRUE, care_about_existing_wounds = FALSE)
+ if (wound_path)
+ var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[wound_path]
+ return pregen_data.get_threshold_for(src, damage_source = wound_source)
+
+ return return_value_if_no_wound
/**
* check_wounding_mods() is where we handle the various modifiers of a wound roll
@@ -209,7 +268,20 @@
return injury_mod
- /// Get whatever wound of the given type is currently attached to this limb, if any
+/// Should return an assoc list of (wound_series -> penalty). Will be used in determining series-specific penalties for wounding.
+/obj/item/bodypart/proc/check_series_wounding_mods()
+ RETURN_TYPE(/list)
+
+ var/list/series_mods = list()
+
+ for (var/datum/wound/iterated_wound as anything in wounds)
+ var/datum/wound_pregen_data/pregen_data = GLOB.all_wound_pregen_data[iterated_wound.type]
+
+ series_mods[pregen_data.wound_series] += iterated_wound.series_threshold_penalty
+
+ return series_mods
+
+/// Get whatever wound of the given type is currently attached to this limb, if any
/obj/item/bodypart/proc/get_wound_type(checking_type)
RETURN_TYPE(checking_type)
SHOULD_NOT_OVERRIDE(TRUE)
@@ -232,11 +304,11 @@
/obj/item/bodypart/proc/update_wounds(replaced = FALSE)
SHOULD_CALL_PARENT(TRUE)
- var/dam_mul = 1 //initial(wound_damage_multiplier)
+ var/dam_mul = 1
// we can (normally) only have one wound per type, but remember there's multiple types (smites like :B:loodless can generate multiple cuts on a limb)
for(var/datum/wound/iter_wound as anything in wounds)
- dam_mul *= iter_wound.damage_mulitplier_penalty
+ dam_mul *= iter_wound.damage_multiplier_penalty
if(!LAZYLEN(wounds) && current_gauze && !replaced) // no more wounds = no need for the gauze anymore
owner.visible_message(span_notice("\The [current_gauze.name] on [owner]'s [name] falls away."), span_notice("The [current_gauze.name] on your [parse_zone(body_zone)] falls away."))
diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm
index d28ffa4062c731..0987df92bd94e1 100644
--- a/code/modules/surgery/organs/autosurgeon.dm
+++ b/code/modules/surgery/organs/autosurgeon.dm
@@ -157,9 +157,6 @@
/obj/item/autosurgeon/syndicate/anti_stun
starting_organ = /obj/item/organ/internal/cyberimp/brain/anti_stun
-/obj/item/autosurgeon/syndicate/anti_drop
- starting_organ = /obj/item/organ/internal/cyberimp/brain/anti_drop
-
/obj/item/autosurgeon/syndicate/reviver
starting_organ = /obj/item/organ/internal/cyberimp/chest/reviver
diff --git a/code/modules/surgery/organs/internal/appendix/_appendix.dm b/code/modules/surgery/organs/internal/appendix/_appendix.dm
index 835d7e62bed79a..a52479c10a7321 100644
--- a/code/modules/surgery/organs/internal/appendix/_appendix.dm
+++ b/code/modules/surgery/organs/internal/appendix/_appendix.dm
@@ -65,7 +65,7 @@
organ_owner.adjustToxLoss(1, updating_health = TRUE, forced = TRUE)
if(3)
if(SPT_PROB(0.5, seconds_per_tick))
- organ_owner.vomit(95)
+ organ_owner.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 95)
organ_owner.adjustOrganLoss(ORGAN_SLOT_APPENDIX, 15)
diff --git a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
index 55624e88bf1a45..0a7332c0dd893a 100644
--- a/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
+++ b/code/modules/surgery/organs/internal/cyberimp/augments_internal.dm
@@ -172,4 +172,3 @@
new /obj/item/autosurgeon/syndicate/xray_eyes(src)
new /obj/item/autosurgeon/syndicate/anti_stun(src)
new /obj/item/autosurgeon/syndicate/reviver(src)
- new /obj/item/autosurgeon/syndicate/anti_drop(src)
diff --git a/code/modules/surgery/organs/internal/ears/_ears.dm b/code/modules/surgery/organs/internal/ears/_ears.dm
index 54d35628f85fa4..52f5d7405201c0 100644
--- a/code/modules/surgery/organs/internal/ears/_ears.dm
+++ b/code/modules/surgery/organs/internal/ears/_ears.dm
@@ -153,4 +153,4 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
- apply_organ_damage(40/severity)
+ apply_organ_damage(20 / severity)
diff --git a/code/modules/surgery/organs/internal/liver/_liver.dm b/code/modules/surgery/organs/internal/liver/_liver.dm
index f871fbd84db588..fe5ca01df4f016 100644
--- a/code/modules/surgery/organs/internal/liver/_liver.dm
+++ b/code/modules/surgery/organs/internal/liver/_liver.dm
@@ -172,18 +172,18 @@
to_chat(owner, span_userdanger("You feel stabbing pain in your abdomen!"))
if(2)
to_chat(owner, span_userdanger("You feel a burning sensation in your gut!"))
- owner.vomit()
+ owner.vomit(VOMIT_CATEGORY_DEFAULT)
if(3)
to_chat(owner, span_userdanger("You feel painful acid in your throat!"))
- owner.vomit(blood = TRUE)
+ owner.vomit(VOMIT_CATEGORY_BLOOD)
if(4)
to_chat(owner, span_userdanger("Overwhelming pain knocks you out!"))
- owner.vomit(blood = TRUE, distance = rand(1,2))
+ owner.vomit(VOMIT_CATEGORY_BLOOD, distance = rand(1,2))
owner.emote("Scream")
owner.AdjustUnconscious(2.5 SECONDS)
if(5)
to_chat(owner, span_userdanger("You feel as if your guts are about to melt!"))
- owner.vomit(blood = TRUE,distance = rand(1,3))
+ owner.vomit(VOMIT_CATEGORY_BLOOD, distance = rand(1,3))
owner.emote("Scream")
owner.AdjustUnconscious(5 SECONDS)
diff --git a/code/modules/surgery/organs/internal/lungs/_lungs.dm b/code/modules/surgery/organs/internal/lungs/_lungs.dm
index ba66721433f44a..5e4e0648067513 100644
--- a/code/modules/surgery/organs/internal/lungs/_lungs.dm
+++ b/code/modules/surgery/organs/internal/lungs/_lungs.dm
@@ -481,13 +481,13 @@
if(prob(5))
to_chat(breather, span_warning("The stench of rotting carcasses is unbearable!"))
breather.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench)
- breather.vomit()
+ breather.vomit(VOMIT_CATEGORY_DEFAULT)
if(30 to INFINITY)
//Higher chance to vomit. Let the horror start
if(prob(15))
to_chat(breather, span_warning("The stench of rotting carcasses is unbearable!"))
breather.add_mood_event("smell", /datum/mood_event/disgust/nauseating_stench)
- breather.vomit()
+ breather.vomit(VOMIT_CATEGORY_DEFAULT)
else
breather.clear_mood_event("smell")
// In a full miasma atmosphere with 101.34 pKa, about 10 disgust per breath, is pretty low compared to threshholds
diff --git a/code/modules/surgery/organs/internal/stomach/_stomach.dm b/code/modules/surgery/organs/internal/stomach/_stomach.dm
index 4f83b7844a4aea..bebeaacf110a33 100644
--- a/code/modules/surgery/organs/internal/stomach/_stomach.dm
+++ b/code/modules/surgery/organs/internal/stomach/_stomach.dm
@@ -110,13 +110,13 @@
//The stomach is damage has nutriment but low on theshhold, lo prob of vomit
if(SPT_PROB(0.0125 * damage * nutri_vol * nutri_vol, seconds_per_tick))
- body.vomit(damage)
+ body.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = damage)
to_chat(body, span_warning("Your stomach reels in pain as you're incapable of holding down all that food!"))
return
// the change of vomit is now high
if(damage > high_threshold && SPT_PROB(0.05 * damage * nutri_vol * nutri_vol, seconds_per_tick))
- body.vomit(damage)
+ body.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = damage)
to_chat(body, span_warning("Your stomach reels in pain as you're incapable of holding down all that food!"))
/obj/item/organ/internal/stomach/proc/handle_hunger(mob/living/carbon/human/human, seconds_per_tick, times_fired)
@@ -234,7 +234,7 @@
if(SPT_PROB(pukeprob, seconds_per_tick)) //iT hAndLeS mOrE ThaN PukInG
disgusted.adjust_confusion(2.5 SECONDS)
disgusted.adjust_stutter(2 SECONDS)
- disgusted.vomit(10, distance = 0, vomit_type = NONE)
+ disgusted.vomit(VOMIT_CATEGORY_DEFAULT, distance = 0)
disgusted.set_dizzy_if_lower(10 SECONDS)
if(disgust >= DISGUST_LEVEL_DISGUSTED)
if(SPT_PROB(13, seconds_per_tick))
@@ -300,7 +300,7 @@
if(. & EMP_PROTECT_SELF)
return
if(!COOLDOWN_FINISHED(src, severe_cooldown)) //So we cant just spam emp to kill people.
- owner.vomit(stun = FALSE)
+ owner.vomit(vomit_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM))
COOLDOWN_START(src, severe_cooldown, 10 SECONDS)
if(prob(emp_vulnerability/severity)) //Chance of permanent effects
organ_flags |= ORGAN_EMP //Starts organ faliure - gonna need replacing soon.
diff --git a/code/modules/surgery/revival.dm b/code/modules/surgery/revival.dm
index 1f26a63cc6ac64..e199df0ffd9ec9 100644
--- a/code/modules/surgery/revival.dm
+++ b/code/modules/surgery/revival.dm
@@ -3,7 +3,8 @@
desc = "An experimental surgical procedure which involves reconstruction and reactivation of the patient's brain even long after death. \
The body must still be able to sustain life."
requires_bodypart_type = NONE
- possible_locs = list(BODY_ZONE_HEAD)
+ possible_locs = list(BODY_ZONE_CHEST)
+ target_mobtypes = list(/mob/living)
surgery_flags = SURGERY_REQUIRE_RESTING | SURGERY_REQUIRE_LIMB | SURGERY_MORBID_CURIOSITY
steps = list(
/datum/surgery_step/incise,
@@ -15,15 +16,22 @@
/datum/surgery_step/close,
)
-/datum/surgery/revival/can_start(mob/user, mob/living/carbon/target)
+/datum/surgery/revival/can_start(mob/user, mob/living/target)
if(!..())
return FALSE
if(target.stat != DEAD)
return FALSE
if(HAS_TRAIT(target, TRAIT_SUICIDED) || HAS_TRAIT(target, TRAIT_HUSK) || HAS_TRAIT(target, TRAIT_DEFIB_BLACKLISTED))
return FALSE
- var/obj/item/organ/internal/brain/target_brain = target.get_organ_slot(ORGAN_SLOT_BRAIN)
- if(!target_brain)
+ if(!is_valid_target(target))
+ return FALSE
+ return TRUE
+
+/// Extra checks which can be overridden
+/datum/surgery/revival/proc/is_valid_target(mob/living/patient)
+ if (iscarbon(patient))
+ return FALSE
+ if (!(patient.mob_biotypes & (MOB_ORGANIC|MOB_HUMANOID)))
return FALSE
return TRUE
@@ -59,7 +67,7 @@
to_chat(user, span_warning("You need an electrode for this!"))
return FALSE
-/datum/surgery_step/revive/preop(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+/datum/surgery_step/revive/preop(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
display_results(
user,
target,
@@ -69,13 +77,13 @@
)
target.notify_ghost_cloning("Someone is trying to zap your brain.", source = target)
-/datum/surgery_step/revive/play_preop_sound(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+/datum/surgery_step/revive/play_preop_sound(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
if(istype(tool, /obj/item/shockpaddles))
playsound(tool, 'sound/machines/defib_charge.ogg', 75, 0)
else
..()
-/datum/surgery_step/revive/success(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
+/datum/surgery_step/revive/success(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery, default_display_results)
display_results(
user,
target,
@@ -87,24 +95,27 @@
target.adjustOxyLoss(-50, 0)
target.updatehealth()
if(target.revive())
- target.visible_message(span_notice("...[target] wakes up, alive and aware!"))
- target.emote("gasp")
- target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 50, 199) //MAD SCIENCE
- to_chat(target, "[CONFIG_GET(string/blackoutpolicy)]") //SKYRAT EDIT ADDITION - BLACKOUT POLICY
- if(HAS_MIND_TRAIT(user, TRAIT_MORBID) && ishuman(user)) //Contrary to their typical hatred of resurrection, it wouldn't be very thematic if morbid people didn't love playing god
- var/mob/living/carbon/human/morbid_weirdo = user
- morbid_weirdo.add_mood_event("morbid_revival_success", /datum/mood_event/morbid_revival_success)
+ on_revived(user, target)
return TRUE
+
+ //SKYRAT EDIT CHANGE - DNR TRAIT - need this so that people don't just keep spamming the revival surgery; it runs success just bc the surgery steps are done
+ if(HAS_TRAIT(target, TRAIT_DNR))
+ target.visible_message(span_warning("...[target.p_they()] lie[target.p_s()] still, unaffected. Further attempts are futile, target.p_theyre() gone."))
else
- //SKYRAT EDIT ADDITION - DNR TRAIT - need this so that people dont just keep spamming the revival surgery; it runs success just bc the surgery steps are done
- if(HAS_TRAIT(target, TRAIT_DNR))
- target.visible_message(span_warning("...[target.p_they()] lies still, unaffected. Further attempts are futile, they're gone."))
- else
- target.visible_message(span_warning("...[target.p_they()] convulses, then lies still."))
- //SKYRAT EDIT ADDITION END - DNR TRAIT - ORIGINAL: target.visible_message(span_warning("...[target.p_they()] convulses, then lies still."))
- return FALSE
+ target.visible_message(span_warning("...[target.p_they()] convulse[target.p_s()], then lie[target.p_s()] still."))
+ //SKYRAT EDIT CHANGE END - DNR TRAIT - ORIGINAL: target.visible_message(span_warning("...[target.p_they()] convulse[target.p_s()], then lie[target.p_s()] still."))
+ return FALSE
-/datum/surgery_step/revive/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+/// Called when you have been successfully raised from the dead
+/datum/surgery_step/revive/proc/on_revived(mob/surgeon, mob/living/patient)
+ patient.visible_message(span_notice("...[patient] wakes up, alive and aware!"))
+ patient.emote("gasp")
+ to_chat(patient, "[CONFIG_GET(string/blackoutpolicy)]") //SKYRAT EDIT ADDITION - BLACKOUT POLICY
+ if(HAS_MIND_TRAIT(surgeon, TRAIT_MORBID) && ishuman(surgeon)) // Contrary to their typical hatred of resurrection, it wouldn't be very thematic if morbid people didn't love playing god
+ var/mob/living/carbon/human/morbid_weirdo = surgeon
+ morbid_weirdo.add_mood_event("morbid_revival_success", /datum/mood_event/morbid_revival_success)
+
+/datum/surgery_step/revive/failure(mob/user, mob/living/target, target_zone, obj/item/tool, datum/surgery/surgery)
display_results(
user,
target,
@@ -112,5 +123,23 @@
span_notice("[user] send a powerful shock to [target]'s brain with [tool], but [target.p_they()] doesn't react."),
span_notice("[user] send a powerful shock to [target]'s brain with [tool], but [target.p_they()] doesn't react."),
)
- target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15, 180)
return FALSE
+
+/// Additional revival effects if the target has a brain
+/datum/surgery/revival/carbon
+ possible_locs = list(BODY_ZONE_HEAD)
+ target_mobtypes = list(/mob/living/carbon)
+
+/datum/surgery/revival/carbon/is_valid_target(mob/living/carbon/patient)
+ var/obj/item/organ/internal/brain/target_brain = patient.get_organ_slot(ORGAN_SLOT_BRAIN)
+ return !isnull(target_brain)
+
+/datum/surgery_step/revive/carbon
+
+/datum/surgery_step/revive/carbon/on_revived(mob/surgeon, mob/living/patient)
+ . = ..()
+ patient.adjustOrganLoss(ORGAN_SLOT_BRAIN, 50, 199) // MAD SCIENCE
+
+/datum/surgery_step/revive/carbon/failure(mob/user, mob/living/carbon/target, target_zone, obj/item/tool, datum/surgery/surgery)
+ . = ..()
+ target.adjustOrganLoss(ORGAN_SLOT_BRAIN, 15, 180)
diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm
index e469f16740a8b4..a89d4524170d57 100644
--- a/code/modules/unit_tests/_unit_tests.dm
+++ b/code/modules/unit_tests/_unit_tests.dm
@@ -104,7 +104,6 @@
#include "bloody_footprints.dm"
#include "breath.dm"
#include "burning.dm"
-#include "byond_status.dm"
#include "cable_powernets.dm"
#include "card_mismatch.dm"
#include "cardboard_cutouts.dm"
diff --git a/code/modules/unit_tests/byond_status.dm b/code/modules/unit_tests/byond_status.dm
deleted file mode 100644
index e27888315565d2..00000000000000
--- a/code/modules/unit_tests/byond_status.dm
+++ /dev/null
@@ -1,9 +0,0 @@
-/// Tests the SUPPOSED TO BE TEMPORARY byond_status() proc for a useful format
-/datum/unit_test/byond_status
-
-/datum/unit_test/byond_status/Run()
- if (world.system_type != UNIX)
- return
-
- var/status = byond_status()
- TEST_ASSERT(findtext(status, "Sleeping procs"), "Invalid byond_status: [status]")
diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm
index 86b0cc69d1cf4e..b767b010495c8c 100644
--- a/code/modules/unit_tests/chain_pull_through_space.dm
+++ b/code/modules/unit_tests/chain_pull_through_space.dm
@@ -11,15 +11,16 @@
..()
//reserve a tile that is always empty for our z destination
- reserved = SSmapping.RequestBlockReservation(5,5)
+ reserved = SSmapping.request_turf_block_reservation(5, 5, 1)
// Create a space tile that goes to another z-level
claimed_tile = run_loc_floor_bottom_left.type
space_tile = run_loc_floor_bottom_left.ChangeTurf(/turf/open/space)
- space_tile.destination_x = round(reserved.bottom_left_coords[1] + (reserved.width-1) / 2)
- space_tile.destination_y = round(reserved.bottom_left_coords[2] + (reserved.height-1) / 2)
- space_tile.destination_z = reserved.bottom_left_coords[3]
+ var/turf/bottom_left = reserved.bottom_left_turfs[1]
+ space_tile.destination_x = round(bottom_left.x + (reserved.width-1) / 2)
+ space_tile.destination_y = round(bottom_left.y + (reserved.height-1) / 2)
+ space_tile.destination_z = bottom_left.z
// Create our list of humans, all adjacent to one another
alice = new(locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
diff --git a/code/modules/unit_tests/fish_unit_tests.dm b/code/modules/unit_tests/fish_unit_tests.dm
index 6c4a278a39649d..1ef15f8d0f5126 100644
--- a/code/modules/unit_tests/fish_unit_tests.dm
+++ b/code/modules/unit_tests/fish_unit_tests.dm
@@ -123,5 +123,79 @@
. = ..()
probability = 0 //works around the global list initialization skipping abstract/impossible evolutions.
+// we want no default spawns in this unit test
+/datum/chasm_detritus/restricted/bodies/no_defaults
+ default_contents_chance = 0
+
+/// Checks that we are able to fish people out of chasms with priority and that they end up in the right location
+/datum/unit_test/fish_rescue_hook
+ priority = TEST_LONGER
+ var/original_turf_type
+ var/original_turf_baseturfs
+ var/list/mobs_spawned
+
+/datum/unit_test/fish_rescue_hook/Run()
+ // create our human dummies to be dropped into the chasm
+ var/mob/living/carbon/human/consistent/get_in_the_hole = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/basic/mining/lobstrosity/you_too = allocate(/mob/living/basic/mining/lobstrosity)
+ var/mob/living/carbon/human/consistent/mindless = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/consistent/no_brain = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/consistent/empty = allocate(/mob/living/carbon/human/consistent)
+ var/mob/living/carbon/human/consistent/dummy = allocate(/mob/living/carbon/human/consistent)
+
+ mobs_spawned = list(
+ get_in_the_hole,
+ you_too,
+ mindless,
+ no_brain,
+ empty,
+ dummy,
+ )
+
+ // create our chasm and remember the previous turf so we can change it back once we're done
+ original_turf_type = run_loc_floor_bottom_left.type
+ original_turf_baseturfs = islist(run_loc_floor_bottom_left.baseturfs) ? run_loc_floor_bottom_left.baseturfs.Copy() : run_loc_floor_bottom_left.baseturfs
+ run_loc_floor_bottom_left.ChangeTurf(/turf/open/chasm)
+ var/turf/open/chasm/the_hole = run_loc_floor_bottom_left
+
+ // into the hole they go
+ for(var/mob/mob_spawned in mobs_spawned)
+ the_hole.drop(mob_spawned)
+ sleep(0.2 SECONDS) // we have to WAIT because the drop() proc sleeps.
+
+ // our 'fisherman' where we expect the item to be moved to after fishing it up
+ var/mob/living/carbon/human/consistent/a_fisherman = allocate(/mob/living/carbon/human/consistent, run_loc_floor_top_right)
+
+ // pretend like this mob has a mind. they should be fished up first
+ no_brain.mind_initialize()
+
+ SEND_SIGNAL(the_hole, COMSIG_PRE_FISHING) // we need to do this for the fishing spot component to be attached
+ var/datum/component/fishing_spot/the_hole_fishing_spot = the_hole.GetComponent(/datum/component/fishing_spot)
+ var/datum/fish_source/fishing_source = the_hole_fishing_spot.fish_source
+ var/obj/item/fishing_hook/rescue/the_hook = allocate(/obj/item/fishing_hook/rescue, run_loc_floor_top_right)
+ the_hook.chasm_detritus_type = /datum/chasm_detritus/restricted/bodies/no_defaults
+
+ // try to fish up our minded victim
+ var/atom/movable/reward = fishing_source.dispense_reward(the_hook.chasm_detritus_type, a_fisherman, the_hole)
+
+ // mobs with minds (aka players) should have precedence over any other mobs that are in the chasm
+ TEST_ASSERT_EQUAL(reward, no_brain, "Fished up [reward] ([REF(reward)]) with a rescue hook; expected to fish up [no_brain]([REF(no_brain)])")
+ // it should end up on the same turf as the fisherman
+ TEST_ASSERT_EQUAL(get_turf(reward), get_turf(a_fisherman), "[reward] was fished up with the rescue hook and ended up at [get_turf(reward)]; expected to be at [get_turf(a_fisherman)]")
+
+ // let's further test that by giving a second mob a mind. they should be fished up immediately..
+ empty.mind_initialize()
+
+ reward = fishing_source.dispense_reward(the_hook.chasm_detritus_type, a_fisherman, the_hole)
+
+ TEST_ASSERT_EQUAL(reward, empty, "Fished up [reward]([REF(reward)]) with a rescue hook; expected to fish up [empty]([REF(empty)])")
+ TEST_ASSERT_EQUAL(get_turf(reward), get_turf(a_fisherman), "[reward] was fished up with the rescue hook and ended up at [get_turf(reward)]; expected to be at [get_turf(a_fisherman)]")
+
+// clean up so we don't mess up subsequent tests
+/datum/unit_test/fish_rescue_hook/Destroy()
+ QDEL_LIST(mobs_spawned)
+ run_loc_floor_bottom_left.ChangeTurf(original_turf_type, original_turf_baseturfs)
+ return ..()
+
#undef TRAIT_FISH_TESTING
diff --git a/code/modules/unit_tests/medical_wounds.dm b/code/modules/unit_tests/medical_wounds.dm
index 0838b2d18be852..161492a726a923 100644
--- a/code/modules/unit_tests/medical_wounds.dm
+++ b/code/modules/unit_tests/medical_wounds.dm
@@ -19,10 +19,11 @@
TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
var/datum/wound/iter_test_wound
+ var/datum/wound_pregen_data/iter_pregen_data = GLOB.all_wound_pregen_data[iter_test_wound]
var/threshold_penalty = 0
for(iter_test_wound in iter_test_wound_list)
- var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
if(dam_types[i] == BRUTE)
tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
else if(dam_types[i] == BURN)
@@ -59,10 +60,11 @@
TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test")
var/datum/wound/iter_test_wound
+ var/datum/wound_pregen_data/iter_pregen_data = GLOB.all_wound_pregen_data[iter_test_wound]
var/threshold_penalty = 0
for(iter_test_wound in iter_test_wound_list)
- var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
+ var/threshold = iter_pregen_data.threshold_minimum - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty
if(dam_types[i] == BRUTE)
tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i])
else if(dam_types[i] == BURN)
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_assaultoperative.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_assaultoperative.png
index 06ef0119d1c558..fefeec4ad1dcd3 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_assaultoperative.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_assaultoperative.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png
index 2bba94849140c9..e53e0a86697708 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_bloodbrother.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png
index 38dc445797362e..e1bdbea1058a48 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_changeling.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png b/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png
index 38dc445797362e..e1bdbea1058a48 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png and b/code/modules/unit_tests/screenshots/screenshot_antag_icons_stowawaychangeling.png differ
diff --git a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard.png b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard.png
index 1bddc8f501137d..766941523231da 100644
Binary files a/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard.png and b/code/modules/unit_tests/screenshots/screenshot_humanoids__datum_species_lizard.png differ
diff --git a/code/modules/unit_tests/teleporters.dm b/code/modules/unit_tests/teleporters.dm
index 2cb047304fbb50..e1af0a71a351d9 100644
--- a/code/modules/unit_tests/teleporters.dm
+++ b/code/modules/unit_tests/teleporters.dm
@@ -1,10 +1,18 @@
-/datum/unit_test/auto_teleporter_linking/Run()
+/datum/unit_test/teleporter/Run()
// Put down the teleporter machinery
var/obj/machinery/teleport/hub/hub = allocate(/obj/machinery/teleport/hub)
var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z))
+ var/obj/item/beacon/beacon = allocate(/obj/item/beacon)
TEST_ASSERT_EQUAL(hub.power_station, station, "Hub didn't link to the station")
TEST_ASSERT_EQUAL(station.teleporter_console, computer, "Station didn't link to the teleporter console")
TEST_ASSERT_EQUAL(station.teleporter_hub, hub, "Station didn't link to the hub")
TEST_ASSERT_EQUAL(computer.power_station, station, "Teleporter console didn't link to the hub")
+
+ computer.set_teleport_target(beacon)
+ TEST_ASSERT_EQUAL(computer.target_ref, beacon.weak_reference, "Teleporter didn't target beacon correctly")
+
+ computer.set_teleport_target(beacon)
+ beacon.turn_off()
+ TEST_ASSERT_NULL(computer.target_ref, "Teleporter beacon isn't properly turned off.")
diff --git a/code/modules/uplink/uplink_items/device_tools.dm b/code/modules/uplink/uplink_items/device_tools.dm
index 20792cfae708e1..8a83e7450c5563 100644
--- a/code/modules/uplink/uplink_items/device_tools.dm
+++ b/code/modules/uplink/uplink_items/device_tools.dm
@@ -66,13 +66,14 @@
cost = 1
surplus = 20
-/* /datum/uplink_item/device_tools/briefcase_launchpad // SKYRAT EDIT REMOVAL
+/* // SKYRAT EDIT REMOVAL
+/datum/uplink_item/device_tools/briefcase_launchpad
name = "Briefcase Launchpad"
desc = "A briefcase containing a launchpad, a device able to teleport items and people to and from targets up to eight tiles away from the briefcase. \
Also includes a remote control, disguised as an ordinary folder. Touch the briefcase with the remote to link it."
surplus = 0
item = /obj/item/storage/briefcase/launchpad
- cost = 6 */
+ cost = 6
/datum/uplink_item/device_tools/syndicate_teleporter
name = "Experimental Syndicate Teleporter"
@@ -82,6 +83,7 @@
Comes with 4 charges, recharges randomly. Warranty null and void if exposed to an electromagnetic pulse."
item = /obj/item/storage/box/syndie_kit/syndicate_teleporter
cost = 8
+*/ //END SKYRAT EDIT
/datum/uplink_item/device_tools/camera_app
name = "SyndEye Program"
@@ -265,3 +267,9 @@
bright lights. Effective, affordable, and nigh undetectable."
item = /obj/item/syndicate_contacts
cost = 3
+
+/datum/uplink_item/device_tools/syndicate_climbing_hook
+ name = "Syndicate Climbing Hook"
+ desc = "High-tech rope, a refined hook structure, the peak of climbing technology. Only useful for climbing up holes, provided the operation site has any."
+ item = /obj/item/climbing_hook/syndicate
+ cost = 1
diff --git a/code/modules/uplink/uplink_items/nukeops.dm b/code/modules/uplink/uplink_items/nukeops.dm
index e25763350a8758..632d4cc2717f44 100644
--- a/code/modules/uplink/uplink_items/nukeops.dm
+++ b/code/modules/uplink/uplink_items/nukeops.dm
@@ -468,7 +468,7 @@
name = "Cybernetic Implants Bundle"
desc = "A box containing x-ray eyes, a CNS Rebooter and Reviver implant. Comes with an autosurgeon for each."
item = /obj/item/storage/box/cyber_implants
- cost = 25 //worth around 32 TC
+ cost = 20 //worth 24 TC
purchasable_from = UPLINK_NUKE_OPS
/datum/uplink_item/bundles_tc/medical
@@ -732,13 +732,6 @@
item = /obj/item/autosurgeon/syndicate/anti_stun
cost = 8
-/datum/uplink_item/implants/nuclear/antidrop
- name = "Anti-Drop Implant"
- desc = "This implant will keep you from dropping things from your hands. Be sure to hold onto the item before activating, and \
- activate it again to turn it off. Comes with an autosurgeon."
- item = /obj/item/autosurgeon/syndicate/anti_drop
- cost = 8
-
// Badass (meme items)
/datum/uplink_item/badass/costumes
diff --git a/code/modules/vehicles/cars/clowncar.dm b/code/modules/vehicles/cars/clowncar.dm
index f08e30a7554eac..1fd230bb47a82e 100644
--- a/code/modules/vehicles/cars/clowncar.dm
+++ b/code/modules/vehicles/cars/clowncar.dm
@@ -121,11 +121,11 @@
if(prob(35)) //Note: The randomstep on dump_mobs throws occupants into each other and often causes wounds regardless.
continue
for(var/obj/item/bodypart/head/head_to_wound as anything in carbon_occupant.bodyparts)
- var/type_wound = pick(list(
- /datum/wound/blunt/bone/moderate,
- /datum/wound/blunt/bone/severe,
- ))
- head_to_wound.force_wound_upwards(type_wound, wound_source = src)
+ var/pick_mode = text2num(pick(list(
+ "[WOUND_PICK_LOWEST_SEVERITY]",
+ "[WOUND_PICK_HIGHEST_SEVERITY]"
+ )))
+ carbon_occupant.cause_wound_of_type_and_severity(WOUND_BLUNT, head_to_wound, WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, pick_mode)
carbon_occupant.playsound_local(src, 'sound/weapons/flash_ring.ogg', 50)
carbon_occupant.set_eye_blur_if_lower(rand(10 SECONDS, 20 SECONDS))
diff --git a/code/modules/vending/_vending.dm b/code/modules/vending/_vending.dm
index 298af4a7cf9bdb..ab882bf7997924 100644
--- a/code/modules/vending/_vending.dm
+++ b/code/modules/vending/_vending.dm
@@ -194,8 +194,6 @@
///Items that the players have loaded into the vendor
var/list/vending_machine_input = list()
- ///Display header on the input view
- var/input_display_header = "Custom Vendor"
//The type of refill canisters used by this machine.
var/obj/item/vending_refill/refill_canister = null
@@ -954,13 +952,9 @@
return FALSE
var/mob/living/carbon/carbon_target = atom_target
for(var/obj/item/bodypart/squish_part in carbon_target.bodyparts)
- var/type_wound
- if (squish_part.biological_state & BIO_BONE)
- type_wound = pick(list(/datum/wound/blunt/bone/critical, /datum/wound/blunt/bone/severe, /datum/wound/blunt/bone/moderate))
- else
- squish_part.receive_damage(brute=30)
- if (type_wound)
- squish_part.force_wound_upwards(type_wound, wound_source = "crushed by [src]")
+ var/severity = pick(WOUND_SEVERITY_MODERATE, WOUND_SEVERITY_SEVERE, WOUND_SEVERITY_CRITICAL)
+ if (!carbon_target.cause_wound_of_type_and_severity(WOUND_BLUNT, squish_part, severity, wound_source = "crushed by [src]"))
+ squish_part.receive_damage(brute = 30)
carbon_target.visible_message(span_danger("[carbon_target]'s body is maimed underneath the mass of [src]!"), span_userdanger("Your body is maimed underneath the mass of [src]!"))
return TRUE
if(CRUSH_CRIT_HEADGIB) // skull squish!
@@ -1042,7 +1036,7 @@
to_chat(user, span_notice("You insert [inserted_item] into [src]'s input compartment."))
for(var/datum/data/vending_product/product_datum in product_records + coin_records + hidden_records)
- if(ispath(inserted_item.type, product_datum.product_path))
+ if(inserted_item.type == product_datum.product_path)
product_datum.amount++
LAZYADD(product_datum.returned_products, inserted_item)
return
@@ -1536,7 +1530,7 @@
* * user - the user doing the loading
*/
/obj/machinery/vending/proc/canLoadItem(obj/item/loaded_item, mob/user)
- if((loaded_item.type in products) || (loaded_item.type in premium) || (loaded_item.type in contraband))
+ if(!length(loaded_item.contents) && ((loaded_item.type in products) || (loaded_item.type in premium) || (loaded_item.type in contraband)))
return TRUE
to_chat(user, span_warning("[src] does not accept [loaded_item]!"))
return FALSE
diff --git a/code/modules/vending/snack.dm b/code/modules/vending/snack.dm
index ec633084fd8360..23b1bcf2c71e34 100644
--- a/code/modules/vending/snack.dm
+++ b/code/modules/vending/snack.dm
@@ -44,7 +44,6 @@
default_price = PAYCHECK_CREW * 0.6
extra_price = PAYCHECK_CREW
payment_department = ACCOUNT_SRV
- input_display_header = "Chef's Food Selection"
/obj/item/vending_refill/snack
machine_name = "Getmore Chocolate Corp"
diff --git a/code/modules/vending/wardrobes.dm b/code/modules/vending/wardrobes.dm
index d12f43a880d09e..da2c08e04c9ad8 100644
--- a/code/modules/vending/wardrobes.dm
+++ b/code/modules/vending/wardrobes.dm
@@ -5,7 +5,6 @@
default_price = PAYCHECK_CREW
extra_price = PAYCHECK_COMMAND
payment_department = NO_FREEBIES
- input_display_header = "Returned Clothing"
panel_type = "panel19"
light_mask = "wardrobe-light-mask"
diff --git a/config/config.txt b/config/config.txt
index d714caf92c32d6..8d28b656c30c1b 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -395,7 +395,7 @@ CLIENT_ERROR_BUILD 1421
## Set to 0 or comment out to disable.
SECOND_TOPIC_LIMIT 10
-MINUTE_TOPIC_LIMIT 100
+MINUTE_TOPIC_LIMIT 200
## CLICK RATE LIMITING
diff --git a/config/spaceruinblacklist.txt b/config/spaceruinblacklist.txt
index 7b951c28e467bc..6ac8f8f08c266c 100644
--- a/config/spaceruinblacklist.txt
+++ b/config/spaceruinblacklist.txt
@@ -35,6 +35,7 @@
#_maps/RandomRuins/SpaceRuins/derelict6.dmm
#_maps/RandomRuins/SpaceRuins/derelict7.dmm
#_maps/RandomRuins/SpaceRuins/derelict8.dmm
+#_maps/RandomRuins/SpaceRuins/derelict9.dmm
#_maps/RandomRuins/SpaceRuins/dj_station.dmm
#_maps/RandomRuins/SpaceRuins/emptyshell.dmm
#_maps/RandomRuins/SpaceRuins/fasttravel.dmm
diff --git a/html/changelogs/AutoChangeLog-pr-23528.yml b/html/changelogs/AutoChangeLog-pr-23528.yml
new file mode 100644
index 00000000000000..2b0c89d8c7f7e6
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-23528.yml
@@ -0,0 +1,6 @@
+author: "jjpark-kb"
+delete-after: True
+changes:
+ - qol: "you can now construct, deconstruct, and anchor/unanchor the millstone"
+ - rscadd: "rough stones will now occasionally drop from mineral walls (the mining kind)"
+ - bugfix: "you can now cut/process rough stones"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23560.yml b/html/changelogs/AutoChangeLog-pr-23560.yml
deleted file mode 100644
index d5bef4bd17620a..00000000000000
--- a/html/changelogs/AutoChangeLog-pr-23560.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "LT3"
-delete-after: True
-changes:
- - bugfix: "Votes play an alert sound again"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23595.yml b/html/changelogs/AutoChangeLog-pr-23595.yml
deleted file mode 100644
index 45cc56938222dd..00000000000000
--- a/html/changelogs/AutoChangeLog-pr-23595.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-author: "SkyratBot"
-delete-after: True
-changes:
- - qol: "adds some more traitor objective brainwashing default objectives."
- - spellcheck: "fixes the double-punctuation on traitor objective brainwashing broadcasts."
- - spellcheck: "brainwashing deadchat broadcasts will now auto-punctuate."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23596.yml b/html/changelogs/AutoChangeLog-pr-23596.yml
deleted file mode 100644
index 04863e880946ac..00000000000000
--- a/html/changelogs/AutoChangeLog-pr-23596.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-author: "SkyratBot"
-delete-after: True
-changes:
- - bugfix: "Golems can eat"
- - bugfix: "Cooked and crafted food should be edible"
- - bugfix: "Medborgs can now spawn vanilla ice cream instead of nothing ice cream"
- - bugfix: "Ghosts can examine food"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23597.yml b/html/changelogs/AutoChangeLog-pr-23597.yml
deleted file mode 100644
index ee4a97c4445ea9..00000000000000
--- a/html/changelogs/AutoChangeLog-pr-23597.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "SkyratBot"
-delete-after: True
-changes:
- - bugfix: "Exploration drones can't be used to reach Centcom anymore."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23824.yml b/html/changelogs/AutoChangeLog-pr-23824.yml
new file mode 100644
index 00000000000000..63758f49096d51
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-23824.yml
@@ -0,0 +1,5 @@
+author: "SkyratBot"
+delete-after: True
+changes:
+ - bugfix: "Projectiles no longer cause a blood graphic or blood splatters if they hit a limb that cant bleed"
+ - rscadd: "Prosthetics/Augments now spark when shot"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23833.yml b/html/changelogs/AutoChangeLog-pr-23833.yml
new file mode 100644
index 00000000000000..04f0b925c93e96
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-23833.yml
@@ -0,0 +1,4 @@
+author: "SkyratBot"
+delete-after: True
+changes:
+ - image: "resprites t-ray scanner."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23834.yml b/html/changelogs/AutoChangeLog-pr-23834.yml
new file mode 100644
index 00000000000000..43c91f27ba9874
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-23834.yml
@@ -0,0 +1,4 @@
+author: "GoldenAlpharex"
+delete-after: True
+changes:
+ - bugfix: "The green pin is no longer unremovable for those that had it on before they hit the 100 hours threshold. It will now be automatically disabled for them."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-23835.yml b/html/changelogs/AutoChangeLog-pr-23835.yml
new file mode 100644
index 00000000000000..9c26357d5173d3
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-23835.yml
@@ -0,0 +1,4 @@
+author: "SkyratBot"
+delete-after: True
+changes:
+ - admin: "Admins can transform misbehaving players into arbitrary objects at will."
\ No newline at end of file
diff --git a/html/changelogs/archive/2023-09.yml b/html/changelogs/archive/2023-09.yml
index 5d9abd2c928b3e..7fc3abe8ebc66a 100644
--- a/html/changelogs/archive/2023-09.yml
+++ b/html/changelogs/archive/2023-09.yml
@@ -321,3 +321,559 @@
shuttle will begin with more reputation and likely be able to immediately access
most of the uplink catalogue.
- image: updates medigel sprites
+2023-09-10:
+ A.C.M.O.:
+ - bugfix: Fixed Poly's voice commands. Poly will now perch on the Chief Engineer's
+ shoulder when expected.
+ LT3:
+ - image: Delam emergency procedure wall biscuit replaced with SAFETY MOTH
+ - code_imp: Delam panic button works when APC is dead
+ - code_imp: Removed extra calls to nightshift subsystem during delam
+ - bugfix: Votes play an alert sound again
+ SkyratBot:
+ - bugfix: Golems can eat
+ - bugfix: Cooked and crafted food should be edible
+ - bugfix: Medborgs can now spawn vanilla ice cream instead of nothing ice cream
+ - bugfix: Ghosts can examine food
+ - bugfix: Exploration drones can't be used to reach Centcom anymore.
+ - admin: Admins can add/remove the spawner component from arbitrary items again.
+ - qol: adds some more traitor objective brainwashing default objectives.
+ - spellcheck: fixes the double-punctuation on traitor objective brainwashing broadcasts.
+ - spellcheck: brainwashing deadchat broadcasts will now auto-punctuate.
+ - admin: The "Create Command Report" verb now has the option to not print report
+ papers at communications consoles.
+ - bugfix: It is now possible to smoke cigarettes even if you aren't wearing a safety
+ helmet
+ StrangeWeirdKitten:
+ - bugfix: Fixes Interdyne mining reward boards making golem reward vendors
+ vinylspiders:
+ - bugfix: fixed a bug that was preventing large mortar from getting any of the extra
+ reagents from grinding/juicing certain items
+2023-09-11:
+ Hatterhat:
+ - rscdel: Thanks to lobbying from other factions within the Syndicate, the black
+ markets accessible by telecrystal-based uplinks are no longer stocking modified
+ hand teleporters, citing a new "stand and deliver" doctrine established by more
+ violent, militant arms of the organization.
+ Melbert:
+ - qol: Haunted 8-ball no longer requires the ghost orbit the petitioner to submit
+ votes
+ - qol: Haunted 8-ball ghosts can now change their vote after submitting it
+ - bugfix: Haunted 8-ball no longer always reports "yes"
+ - bugfix: Haunted 8-ball no longer always reports default "yes", "no", or "maybe"
+ and now gives a proper eight ball response
+ - bugfix: Haunted 8-ball can be picked up via the stat panel
+ RatFromTheJungle:
+ - rscdel: Guns, collectively, can no longer right-click holdup people
+ SkyratBot:
+ - rscadd: 'Added the service borg "drink apparatus" upgrade, which adds an extra
+ drinking apparatus to the borg, up to a maximum of 5 extra.
+
+ :cl:'
+ - bugfix: Changeling tentacle and bloodchiller from xenobio will no longer stop
+ working if you have antimagic
+ - qol: Rice Dough may be made in beaker instead of being crafted, but the rice and
+ flour must be added first
+ - rscadd: humpback emergency shuttle
+ - image: Hivelords have a new sprite.
+ - image: Hivelord and Legion brood have a death animation.
+ - bugfix: Mortar and pestle can grind stuff again
+ Wallem, MTandi:
+ - image: Updates chem factory tank sprites
+ jjpark-kb:
+ - bugfix: revive mob ritual works on basic mobs now
+ - bugfix: ashwalkers have the ashwalker faction
+ softcerv:
+ - bugfix: ghost cafe NIFs no longer have access to hivemind
+2023-09-12:
+ Adelphon:
+ - rscadd: new underwear
+ GoldenAlpharex:
+ - bugfix: All potted plants should now be visible again.
+ - admin: Player ranks are now displayed in the Player Panel of each user.
+ - code_imp: Player ranks can now be checked without taking into account the admin
+ bypass while in-game.
+ - bugfix: Fixed the rounding errors that caused decimals to wrongly appear when
+ hitting the Shift Layer Upwards or the Shift Layer Downwards verbs.
+ - bugfix: The message sent to admins when a new admin has been added via the Permissions
+ Panel will now properly show the new admin's ckey.
+ - qol: Character Previews are now always displayed in the Examine panel, rather
+ than disappearing when the flavor text would otherwise be hidden.
+ LT3:
+ - code_imp: Status display shuttle timer no longer scrolls
+ - bugfix: Fixed Void Raptor cargo door labels and IDs
+ - bugfix: Singularities are no longer invisible. You can again see your impending
+ doom
+ Melbert:
+ - rscdel: Spacers are slightly shorter. They're still taller than other people,
+ just not as much.
+ OrionTheFox:
+ - bugfix: fixed being unable to re-select the "Quartermaster" title in job prefs
+ after selecting an alt-title
+ - image: updated the greyscale Cattleman Hat to be not-bad. It now isn't 1px too
+ low, and actually looks like a cattleman rather than a lump of butter on a plate!
+ SkyratBot:
+ - bugfix: The vorpal scythe is no longer as greedy about you murdering people, and
+ will once again accept striking any living creature to be sated.
+ - bugfix: Fix capture devices allowing mob actions while inside
+ - bugfix: Your clothes and such should correctly reposition themselves if a black
+ charged slime extract turns you into a monkey.
+ - bugfix: Ninjas should be correctly credited for using their spider bombs
+ - balance: Supermatter zap power generation scales with the delta time between its
+ last zaps, preventing faster zapping from scaling power generation to extreme
+ levels.
+ - bugfix: Fixes supermatter zap rate not scaling properly. It should zap much faster
+ at higher energy levels as intended.
+ - qol: Changeling chemical generation scales with the world's delta time, making
+ its rate independent of subsystem lag.
+ - qol: Revenant essence generation scales with the world's delta time, making its
+ rate independent of subsystem lag.
+ - qol: Xenomorph plasma generation and resin healing scales with the world's delta
+ time, making their rates independent of subsystem lag.
+ - balance: polymorph belt now blacklists mobs that are undead, humanoid, robotic
+ or spiritual (in nature, not religiously), as well as megafauna
+ - balance: however, this means that it works with more mobs that it should logically
+ work with, like slimes/bugs/lightgeists etc
+ - bugfix: fixed headslug shenanigans with the polymorph belt hopefully for good
+ this time
+ - bugfix: fixed headslug description mentioning its movement despite the slug being
+ dead
+ sergeirocks100:
+ - bugfix: Briefs now make use of their digitigrade sprite.
+2023-09-13:
+ CoiledLamb, Time-Green:
+ - image: resprites the radioactive nebula shielding
+ Ghommie (Based on an old PR by Trilbyspaceclone from Citadel):
+ - qol: The notepad app now includes basic nautical directions in its default message.
+ - qol: A tip about nautical directions, too.
+ LT3:
+ - bugfix: All missing surgery trays have been found. Don't ask where.
+ Literallynotpickles:
+ - qol: Adjusted medical tongue bounties to mention that cybernetic ones _do_ work
+ for them.
+ Melbert:
+ - bugfix: Birdboat's Augment Theater is named less odd now
+ Rhials:
+ - bugfix: The psyker headset is no longer a syndicate headset subtype, and no longer
+ has syndie comms.
+ SkyratBot:
+ - rscadd: The SC/FISHER disruptor pistol, a very compact, permanently silenced energy
+ gun, is now stocked in Nanotrasen-accessible black markets with a price generally
+ somewhere between 400 and 800 credits. Aspiring users are warned that it's really
+ bad for trying to actually kill people. Caveat emptor.
+ - rscadd: Guns now have a dry_fire_sound_volume variable, allowing for guns to be
+ less loud when trying to fire while empty.
+ - bugfix: Closets and crates now properly count as structures for pass flags again.
+ - balance: Add hypnosis vulnerability for drugged victims
+ - rscadd: Operative MODsuits now have an attached "jump jet" which sends you upwards
+ and allows you to use your jetpack under gravity for a few seconds, perfect
+ for navigating the pits and valleys of Icebox Station.
+ - qol: Shuttle engines now tell you how to install them in their screentips and
+ their examine text.
+ - image: resprites pestkiller, weedkiller, and nolabel sprays
+ - image: updates shading on medigels
+ - image: resprites all spray bottles
+ - bugfix: Medicine allergy can no longer roll quantum medicine
+ - bugfix: fixed grown food items not getting the right seed type passed to them
+ upon creation
+ - bugfix: The holographic pufferfish from the holographic beach from the holodeck
+ no longer looks like a goldfish.
+ - bugfix: the ablative coat's hood now hides the wearer's hair and ear
+ - bugfix: Soup recipes, that make items, spawn the correct number of items per reaction
+ instead of just one item
+ - bugfix: Soup recipes, that make items, consumes the correct number of reagents
+ instead of the largest multiple of the reagents
+ - balance: removed anti-drop implants from the nuclear operative uplink
+ - balance: removed anti-drop implant from the nukie implants bundle and changed
+ its cost to 20 TC
+ SuicidalPickles:
+ - qol: Cargo Coats/Jackets can now equip universal scanners on their suit-slots.
+ burgerenergy:
+ - balance: Buffed MCR damage in line with an upstream generic laser buff
+ vinylspiders:
+ - bugfix: Headsets now have their old worn sprites back, did you ever notice it?
+2023-09-14:
+ LT3:
+ - bugfix: Emergency shuttle should correctly scale timer up/down when changing security
+ levels
+ OrionTheFox:
+ - rscdel: Removed the now-obsolete Skyrat Cargo Gorka-Jacket; TG now has one!
+ - image: updated all of the cargo sprites to match TG's, including digitigrade sprites!
+ - bugfix: the QM's Formal Skirt now actually uses the Skirt icon rather than the
+ Suit
+ Rhials:
+ - rscadd: Shuttle Firesale positive station trait. Some emergency shuttle options
+ have been put on sale!
+ - rscadd: Misplaced Wallet positive station trait. You wouldn't steal from a missing
+ wallet, would you??
+ - rscadd: Wisdom Cow Invasion positive station trait.
+ - rscadd: Advanced Medbots positive station trait. Better roundstart medbots!
+ - rscadd: Loaner Shuttle positive station trait. More shuttle loan offers and more
+ payout!
+ - qol: Station Trait titles are now italicized for easier reading.
+ - spellcheck: Fixes a "prerequisites" typo in the shuttle purchase menu.
+ Seven:
+ - balance: The supermatter delamination countdown has been lowered from 30 to 13
+ seconds
+ - balance: Removing a sliver from the supermatter further lowers that down to 3
+ seconds
+ - balance: Supermatter panic button warning reduced from 5 to 3 seconds
+ - balance: Supermatter suppression system healing runtime reduced from 9 to 7 seconds
+ - balance: The supermatter crystal uses bigger text on its final countdown
+ - spellcheck: Some supermatter delamination related mood descriptions have been
+ edited to explain the mood effect better
+ SkyratBot:
+ - bugfix: You can no longer teleport to disabled beacon if the teleporter was previously
+ locked-on to it.
+ - bugfix: Dynamic now biases less heavily towards the exact average.
+ - bugfix: Nightmares can no longer receive wounds
+ - bugfix: Nightmares can no longer have limbs dismembered
+ - qol: Conveyor belts now have screentips and a better examine tip to teach you
+ how to set one up properly.
+ - qol: Using a conveyor belt stack on a placed conveyor belt will extend the conveyor
+ belt to the output of that conveyor belt.. You can use this to place fully integrated
+ conveyor belts much easier now.
+ - image: When you throw up nanites, your vomit should now be appropriately nanite-colored.
+ - bugfix: Fix poor dynamic threat distribution at lower population levels, causing
+ dynamic to generate better threat curves at lower population levels than it
+ did before.
+ - bugfix: Roundstart medbots and cleanbots are now more likely to be able to be
+ possessed by observers.
+ - admin: It's easier to modify the properties of bots to stop them from being possessed
+ or depossessed.
+ Wallem:
+ - qol: Examine a Dish Drive to see all the items inside of it, as well as the item
+ you'll pull out when you interact with it.
+ - qol: Dish Drive servo tier increases suction range
+ honkpocket:
+ - bugfix: Players who run the DNR quirk no longer appear as revivable when examined
+ nikothedude:
+ - qol: 'Temporary flavor text preview character limit: From 37 to 110'
+ tf-4:
+ - rscadd: You can now craft ammo pouches from four leather.
+ - spellcheck: Fixed typo in cocaine snorting message.
+2023-09-15:
+ LovliestPlant:
+ - rscadd: '[Void Raptor] Adds an exam room and charting office to medbay, just opposite
+ of the escape pod.'
+ - rscadd: '[Void Raptor] Adds a refrigeration system to medbay''s cold storage room.'
+ - qol: '[Void Raptor] Removes some clutter from medbay (storage plants, extra anesthetic
+ closets), adds blankets to TC beds.'
+ - qol: '[Void Raptor] Access to the Southern entrance to the Security office now
+ matches that of the North entrance.'
+ - qol: '[Void Raptor] Overhauls medbay''s cold storage room. Adds a coldroom freezer
+ system; adds a handful of emergency oxygen tanks, emergency nitrogen tanks,
+ and masks to the internals crate; replaces the empty medical crate with a spare
+ robotics limbs crate; and replaces the Oxygen canisters with anesthetic mix
+ canisters.'
+ - qol: '[Void Raptor] Adds towel bin to perma, replaces the linen bin in the public
+ pool with a towel bin.'
+ - bugfix: '[Void Raptor] Replaces flooring with bare plating in sections of maints,
+ should fix mouse spawning.'
+ - bugfix: '[Void Raptor] Fixes the back entrance to the medbay''s treatment center
+ requiring morgue access.'
+ - bugfix: '[Void Raptor] CO''s can now open the brig officer locker in their office.'
+ - bugfix: '[Void Raptor] Fixes a minor clipping issue with the records console in
+ the CO office.'
+ Lunar248:
+ - qol: Gave Freighter a few things to reduce some tediousness, such as a welding
+ tank in engineering, water tank in botany, and a proper sink in the kitchen.
+ - bugfix: Added missing lights, and light switches to Freighter.
+ SkyratBot:
+ - bugfix: you can no longer bypass html sanitization using the table element. >:(
+2023-09-16:
+ LT3:
+ - code_imp: Engaging in Role Play on the Interlink should no longer lead to heart
+ attack and death
+ LT3, unit0016:
+ - qol: You can now choose between limb, prosthetic, or no limb at all
+ Literallynotpickles:
+ - balance: Changeling Horror-Form no longer has reduced click-delay.
+ RatFromTheJungle:
+ - balance: The captains sabre now does five more damage totaling to 20 (while losing
+ a bit of wound chance!), while the shamshir does the same with equally less
+ wound chance, less armor pen, and worse block.
+ SkyratBot:
+ - bugfix: fixes a bug that would cause grown inedible plant seeds (like tower cap)
+ to vanish from existence upon being added to the seed extractor
+ - bugfix: fixes a issue that would cause fruit wine to bug out when trying to blend
+ its reagent color
+ - bugfix: The nuclear operative MODsuit intellicard now actually downloads an AI
+ rather than simply kicking candidates from the game.
+ - bugfix: Fixed a race condition that made fishing yield no reward way too often.
+ - bugfix: The legendary fisher achievement is awarded even if you don't win the
+ minigame.
+ - bugfix: Fixed a fish hook exploit.
+ - bugfix: Baits are now properly consumed by caught fish and (alive) mobs.
+ - bugfix: Fixes tesla coils duplicating the power of >7GeV supermatter zaps.
+ - bugfix: Space ruin Anomaly Research - Fixes stacked windows and underplating
+ - rscadd: There's a new space ruin in town, be on the lookout for a hidden supply
+ cache.
+ - rscadd: Added a new type of wall which can only be destroyed by blowing it up.
+ - balance: Pulling embedded items e.g. shrapnel with hemostats is now a lot faster,
+ and scales appropriately with toolspeed.
+ - balance: You can now pull embedded items with wirecutters, at a speed penalty.
+ - bugfix: Unary vents & Injectors now link properly with air sensors via multitool
+ both ways
+ - balance: Watchers will no longer put you at gunpoint.
+ - balance: The spontaneous brain trauma event will no longer occur if there are
+ fewer than 13 players.
+ - bugfix: Flares and candles no longer sound like flashlights when being turned
+ on.
+ - bugfix: Getting shot by an SC/FISHER now disables PDA lights for consistency's
+ sake.
+ - spellcheck: Replaced an irrelevant tip of the round about scars with a better
+ one
+ - balance: You will be knocked down again on certain vomits. Don't worry, you'll
+ deserve it when it happens.
+ - qol: The supply beacon will no longer delete itself due to explosions, and you
+ can now anchor it with a wrench.
+ - spellcheck: Express console now correctly states that it needs cargo access instead
+ of QM access.
+ - bugfix: returning items to vendors works correctly
+ - bugfix: you can't return items that has stuff in it for e.g. a serving tray with
+ food in it
+ - bugfix: fixed fishing.
+ - bugfix: the bank machine cannot print holochips worth 0 credits now
+ - bugfix: adds the bolted and welded helper to the bar backroom/kitchen coldroom
+ airlock on birdshot, as to prevent chefs from being able to access armor and
+ sunglasses roundstart with barely any work involved
+ nikothedude:
+ - rscadd: 2 Bonesetters, 4 stacks of gauze, 2 health analyzers that cant be used
+ for medibots, all in the robodrobe
+2023-09-17:
+ SkyratBot:
+ - refactor: Refactored wounds yet again
+ - bugfix: Wounds are now picked from the most severe down again, meaning eswords
+ can jump to avulsions
+ - bugfix: Scar descs are now properly applied
+ honkpocket:
+ - rscadd: The bullet-drive machine can once again be bought from the cargo-imports
+ menu
+ nikothedude:
+ - bugfix: Msucle scars no longer cause general disfigurement
+2023-09-18:
+ Majkl-J:
+ - bugfix: laser magazines can now be reloaded correctly
+ SkyratBot:
+ - bugfix: Monkeys have their tails back.
+ - bugfix: Fixed a resource dupe in the ORM.
+ - rscadd: added the inspectors hat to the detectives cabinet, a special hat that
+ allows the wearer to say a phrase to dispense a stored item
+ - rscadd: climbing hooks that allow you to go up holes for multiz, found in internals
+ boxes (on planetary maps), the uplink, cargo and nukie personal lockers
+ - rscadd: A new ruin has appeared on lavaland, featuring the site of an ancient
+ battle.
+ - qol: '[Deltastation, Icebox, Metastation, Tramstation] Adds cell timers to isolation
+ cells. (they do not auto-open the doors)'
+ - qol: '[Birdshot, Deltastation, Icebox, Metastation, Northstar, Tramstation] Adds
+ translator glove modules to the stacks of "accessibility" (e.g. plasma fixation
+ / thermal regulator) modules found in security, medical, and engineering storage
+ rooms.'
+ - qol: '[Birdshot] Adds a roll of packaging paper to the cargo office.'
+ - qol: '[Icebox] Adds a hand labeler to security''s gear room.'
+ - qol: '[Northstar] Nudges the set of binoculars covering the mass driver controls
+ in ordnance over a few inches.'
+ - bugfix: '[Birdshot] Remaps the janitor''s closet such that the recycling machine
+ will now work.'
+ - bugfix: '[Icebox] Removes a duplicated hand labeler from the rack near security''s
+ brig cells.'
+ - bugfix: '[Metastation] Patches a broken corpse disposal pipe running from aux
+ surgery to the morgue.'
+ - bugfix: '[Northstar] Fixes the SM being hotwired at round-start (partially rewires
+ the SM room, moves the APC to the North wall).'
+ - code_imp: added some null checks for general juicing & grinding items
+ - bugfix: grinding stacks now grinds as many pieces/sheets from the stack as possible
+ that can fit in a beaker/container without wasting the whole stack
+ - bugfix: plumbing chemical grinder now actually works again
+ - bugfix: the plumbing chemical grinder allows stuff to enter from any direction
+ but not mobs and also accepts items put inside it via hand including bags
+ - bugfix: You can remove the beaker from the all in 1 grinder when power is off
+ via right click
+ - bugfix: All in 1 grinder now mixes faster with upgraded parts
+ - refactor: you can no longer walk into a plumbing chemical grinder
+ - bugfix: adds a few firelocks and alarms around IceBox
+ - bugfix: the nukie medibot (oppenheimer) has access to the doors of the infiltrator
+ and is not shot at by the turrets
+ - refactor: heretic sacrifice room is now lazyloaded
+ - bugfix: Lipolicide and other chems now puts you in crit, even if it is the only
+ source of damage
+ - bugfix: made the radiation protection crate's contents match it's description
+ - rscadd: Ghosts (observers) can eat ghost burgers and booberry muffins.
+ - balance: Ghost burgers will not decay or pick up germs due to the fact that they
+ moved themselves off a table.
+ - rscadd: NanoTrasen improved the quality of the local durathread strain; resulting
+ in it now being twice as filling!
+ - image: Hercuri spray now uses the same sprite as the yellow medical spray
+ - spellcheck: Added a missing space to hercuri spray's description
+ - bugfix: Manually constructed windoors have correct unrestricted accesses applied
+ to them
+ - bugfix: Windoors created via RCD now actually have electronics inside them
+ - bugfix: Airlocks constructed via RCD have the shell component correctly installed
+ inside them and have no other missing variables
+ - rscadd: Wall mounted objects (Things like APCs, Air Alarms, Light switches, Signs,
+ Posters, Newscasters, you name it) will now fall to the ground and break or
+ deconstruct when their attaching wall is changed or broken.
+ - spellcheck: Fixed some typos on QM's Overcoat
+ - bugfix: Forgetting to take dough out of the oven no longer progresses the server
+ to a crash-worthy state with infinite bread and ash and burned food products
+ for all.
+ - bugfix: Recipe paper in the ruins now shows a normal recipe for Metalgen and Secret
+ sauce.
+ - code_imp: Cleans up some unnecessary code left over from caseless ammo.
+ - qol: the recycler can now be rotated
+ - qol: Machines now transfer their local materials to silo during linking with multitool
+ SpaceLove:
+ - balance: Bluespace Miners have been made cheaper by Nanotrasen as they have found
+ a huge mine of materials recently!
+ TheOneAndOnlyCreeperJoe:
+ - bugfix: Hydra quirk now properly works instead of making you British.
+ Thebleh:
+ - bugfix: Bluespace RPEDs can now be rigged again
+ nikothedude:
+ - rscadd: Several common 'household' reagents can be used as improvised medicine
+ treatment.
+ - rscadd: Drinking tea will help mend (non-bone) wounds over time.
+ - rscadd: Flour and corn starch may be splashed onto wounds to help dry them up,
+ though they'll have a negative effect on burn wounds.
+ - rscadd: Added a new reagent, saltwater, made by combining table salt with water.
+ - rscadd: Table salt and saltwater can be splashed onto wounds as well, reducing
+ bleeding and improving sanitization and disinfection significantly. However,
+ the coarse undiluted salt will irritate the wounds, reducing clot rate and flesh
+ healing, and both of the reagents will increase a burn wound's infestation rate.
+ - rscadd: Altered Table Salt's recipe to just need sodium and chloride. Changed
+ the recipe of Pentetic Acid and Heparin to need table salt (sodium x chloride)
+ and thus slightly altered the total output of those reagents (pentacid went
+ from 5u per reaction to 4u, heparin 4u->3u)
+ - rscadd: Saline-Glucose Solution now needs 2u of saltwater and 1u of sugar, meaning
+ the overall recipe should be completely unchanged in practice. Contact me on
+ discord if any issues arise from these chemical changes!
+ - qol: First aid analyzers now give easy-to-understand direct information, with
+ the specific recommended treatments bolded in the analysis text. They also have
+ a 'unique' extra bit of info, telling you about improvised ways to remedy your
+ wound.
+ tf-4:
+ - bugfix: fixes being able to eat, use pills etc through gas masks and such
+2023-09-19:
+ Ghommie (Thanks Sealed101):
+ - refactor: Reworked the fishing minigame into a game screen object from a TGUI
+ interface
+ Melbert:
+ - balance: Humanoids without tongues cannot cast spells with vocal components
+ - balance: Humanoids without arms cannot cast spells with emotive components
+ SkyratBot:
+ - bugfix: Makes sure pump-up properly grants the baton resistance trait.
+ - bugfix: The Nightmare's Light Eater can no longer suck the light out of space
+ tiles.
+ - bugfix: Fix wooden barricade description "This looks like it can be barricaded
+ with planks of wood" being spammed on objects.
+ - bugfix: basic mobs retaliate targetting now selects targets they can attack
+ - bugfix: Makes ethanol and sugar pure by default.
+ - balance: Player-controlled basic mobs attack as fast as those mobs can when controlled
+ by the AI
+ - balance: Player-controlled Faithless can paralyse people they attack, like the
+ AI does
+ - balance: Player-controlled Star Gazers (if an admin felt like making one) apply
+ the star mark on attack and deal damage to everything around them, like the
+ AI does
+ - rscadd: Many kinds of mobs can now be brought back to life through revival surgery.
+ - rscadd: Dogs can wear eyepatches.
+ - code_imp: Scars now stack trace if they fail to get a valid description
+ - rscdel: Removes the computer vendor.
+ - bugfix: pancake stack layering
+ - bugfix: pizzabox stack layering
+ - bugfix: pizzabox bombs that spawn unarmed now label their pizza correctly and
+ cannot spawn a spriteless pizza
+ - bugfix: Fixes a misplaced status display in Meta's medical storage.
+ - rscadd: mass drivers are now buildable, you activate them by attaching a signaler
+ to their launch wire, and can increase their power by pulsing the safeties wire,
+ and reset it back to normal by cutting the safeties wire.
+ - server: Default-configuration MINUTE_TOPIC_LIMIT has been increased
+ - bugfix: Fix a runtime when trying to cycle move intents with a hotkey as a dead
+ mob.
+ - bugfix: Nanotrasen has finally recalled their faulty multitools and replaced them
+ with working ones! The multitool's buffer now properly clears itself.
+ - qol: Moved multitool link messages to balloon alerts
+ - bugfix: added some missing firealarms on icebox in the hall towards departures
+ and the upper section of chapel
+ - bugfix: normal ethereal blood now works for electrolysis, the hydrogen and oxygen
+ output of the electrolysis recipe has been increased.
+ - balance: Only traitor, changeling, heretic, blood brother, headrev, wizard, obsessed,
+ magic/gun survivalists and greentext book holders can now double their hardcore
+ random score
+ - qol: Redtexting as antag with hardcore random score will pay you default points,
+ instead of none (normal survival rules)
+ - bugfix: End report screen will properly report hardcore random survival in case
+ of station destruction
+2023-09-20:
+ LT3:
+ - rscadd: 'New random event: Supermatter Surge'
+ - refactor: It's different than the Supermatter Surge you're used to
+ - refactor: 'The scale is now 1: low severity to 4: most severe. Keep that in mind!'
+ - rscdel: Removed Skyrat version of Supermatter Surge
+ - code_imp: Individual supermatter crystals can have custom gas properties
+ SkyratBot:
+ - image: resprites t-ray scanner, gas analyzer, geiger counter and hand drill.
+ - qol: Surgery trays can now be crafted via the crafting menu (two rods, one silver),
+ and deconstructed via secondary click with a screwdriver!
+ - spellcheck: Unreverted and improved resonance cascade message.
+ - qol: Crafting R&D guns from gun kits no longer requires tools or cable coil. The
+ decloner and energy crossbow still need reagents.
+ - qol: Halved R&D gun crafting time. 20->10 seconds.
+ - bugfix: Fixed Mafia achievements
+ - balance: Unholy and Eldritch water are self-consuming like holy water! They don't
+ need a liver to be processed.
+ - bugfix: Fixes a selection window in the game rock-paper-scissors with death.
+ - bugfix: fixes inedible grown items (such as tower caps) becoming unclickable when
+ harvested, fixes their seeds disappearing when inserted into the seed machine
+ - refactor: Refactors the camera console UI.
+ - rscadd: heretic knock path and its respective items and award
+ - bugfix: Lava can no longer occasionally generate inside of previously loaded templates
+ and breach and/or destroy shit
+ - qol: mice and rats now are visually spaced out from eachother for visual clarity
+ - balance: Supermatter now takes 15 seconds to delaminate normally and 5 if a sliver
+ has been taken from it. Gives a little more time to escape in the case of the
+ sliver and also evens out the times to please perfectionists.
+ - bugfix: Supermatter now accurately reports it's detonation time.
+ - spellcheck: Supermatter mood descriptions have been reverted back to their old,
+ more flavorful selves.
+ Zergspower:
+ - qol: reworks the verb panel to be less of a CVS receipt
+ vinylspiders:
+ - bugfix: removed a deprecated loadout item 'Black Two-Piece Suit' which had an
+ invalid item path. The old item has been replaced with 'Black Suit'.
+2023-09-21:
+ LT3:
+ - bugfix: Interdyne scientists get their Interdyne labcoat
+ Rhials:
+ - qol: Restores holiday hats for drones.
+ - qol: Extends holiday hat behavior to assistants. Get festive!
+ SkyratBot:
+ - bugfix: RCD Construction effects will no longer fall into chasms.
+ - bugfix: Gauze no longer falls off if a wound is demoted or promoted
+ - bugfix: The caller in a holopad call should now be able to hear people on the
+ other end.
+ - bugfix: wall mounted objects air alarms, fire alarms etc now actually falls off/gets
+ destroyed when their attached wall is deconstructed
+ - bugfix: wall mounts crafted in game also properly falls off/gets destroyed when
+ their attached wall is deconstructed
+ - bugfix: Fixes a bug allowing holopara injectors to be refundable when used.
+ - balance: Improvised shotgun shells now deal half as much damage to humans and
+ cause less wounds, but do 50% more damage to structures and machines. They also
+ require a glass shard for crafting.
+ - balance: 'EMP damage on augs: 2/1.5 from 3/2'
+ - balance: Augs now only get paralyzed by EMP for 3/6 seconds if they are damaged
+ below 70% HP,
+ - balance: Aug EMP knockdown reduced to 3 seconds
+ - balance: Synthetic ears now take far less EMP damage
+ - bugfix: rescue hooks will once again drop the mob next to the fisherman instead
+ of just displaying a balloon alert and doing nothing
+ - bugfix: Recipe that converts Vegetable Oil into Olive Oil works properly
+ - rscadd: Splashing antihol on a patient before surgery will make it to go slower.
+ honkpocket:
+ - rscadd: Adds garment bags to the blueshield and NTC lockers
+ - rscadd: Adds turtlenecks and the intern outfit to the NTC garment bag
+ vinylspiders:
+ - bugfix: spiritual quirk is no longer missing from the game
+ zydras:
+ - bugfix: properly paths NT Consultant and Blueshield areas
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index f7615fc045bf4b..29e59dc6c7148d 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/effects/eldritch.dmi b/icons/effects/eldritch.dmi
index 40d4ea80df2d66..8b7738f3b46a0d 100644
Binary files a/icons/effects/eldritch.dmi and b/icons/effects/eldritch.dmi differ
diff --git a/icons/hud/fishing_hud.dmi b/icons/hud/fishing_hud.dmi
new file mode 100644
index 00000000000000..b68acee09b76af
Binary files /dev/null and b/icons/hud/fishing_hud.dmi differ
diff --git a/icons/hud/screen_alert.dmi b/icons/hud/screen_alert.dmi
index 944337b18e552b..a1fc01434e4564 100755
Binary files a/icons/hud/screen_alert.dmi and b/icons/hud/screen_alert.dmi differ
diff --git a/icons/mob/actions/actions_ecult.dmi b/icons/mob/actions/actions_ecult.dmi
index a684dd1fbe6274..747b57949be822 100644
Binary files a/icons/mob/actions/actions_ecult.dmi and b/icons/mob/actions/actions_ecult.dmi differ
diff --git a/icons/mob/inhands/64x64_lefthand.dmi b/icons/mob/inhands/64x64_lefthand.dmi
index 26c177f7293ab7..8a6bab69776678 100644
Binary files a/icons/mob/inhands/64x64_lefthand.dmi and b/icons/mob/inhands/64x64_lefthand.dmi differ
diff --git a/icons/mob/inhands/64x64_righthand.dmi b/icons/mob/inhands/64x64_righthand.dmi
index 79c30f5cb19787..251458590bdd48 100644
Binary files a/icons/mob/inhands/64x64_righthand.dmi and b/icons/mob/inhands/64x64_righthand.dmi differ
diff --git a/icons/mob/simple/corgi_head.dmi b/icons/mob/simple/corgi_head.dmi
index b582d0406f99d8..2e14a3ed0bb5dd 100644
Binary files a/icons/mob/simple/corgi_head.dmi and b/icons/mob/simple/corgi_head.dmi differ
diff --git a/icons/mob/simple/lavaland/lavaland_monsters.dmi b/icons/mob/simple/lavaland/lavaland_monsters.dmi
index 1d546bd17d45d6..13c37dca594f0c 100644
Binary files a/icons/mob/simple/lavaland/lavaland_monsters.dmi and b/icons/mob/simple/lavaland/lavaland_monsters.dmi differ
diff --git a/icons/obj/clothing/modsuit/mod_modules.dmi b/icons/obj/clothing/modsuit/mod_modules.dmi
index d548f615c44286..f1d19c29da119e 100644
Binary files a/icons/obj/clothing/modsuit/mod_modules.dmi and b/icons/obj/clothing/modsuit/mod_modules.dmi differ
diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi
index b6ccf1b8c41697..d89ee6e5d6408f 100644
Binary files a/icons/obj/device.dmi and b/icons/obj/device.dmi differ
diff --git a/icons/obj/machines/floor.dmi b/icons/obj/machines/floor.dmi
index 38ea510f37bb34..6f858465dcdcb1 100644
Binary files a/icons/obj/machines/floor.dmi and b/icons/obj/machines/floor.dmi differ
diff --git a/icons/obj/machines/nebula_shielding.dmi b/icons/obj/machines/nebula_shielding.dmi
index 32aedcc1b7af5c..1da2b5f2a9b2a1 100644
Binary files a/icons/obj/machines/nebula_shielding.dmi and b/icons/obj/machines/nebula_shielding.dmi differ
diff --git a/icons/obj/machines/recycling.dmi b/icons/obj/machines/recycling.dmi
index de419374151e60..9a6dffaaee2d5b 100644
Binary files a/icons/obj/machines/recycling.dmi and b/icons/obj/machines/recycling.dmi differ
diff --git a/icons/obj/medical/chemical.dmi b/icons/obj/medical/chemical.dmi
index d41fc3b167ae7e..b4b26e4f848c62 100644
Binary files a/icons/obj/medical/chemical.dmi and b/icons/obj/medical/chemical.dmi differ
diff --git a/icons/obj/mining.dmi b/icons/obj/mining.dmi
index 6c5560438c60c3..54b19553b84028 100644
Binary files a/icons/obj/mining.dmi and b/icons/obj/mining.dmi differ
diff --git a/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi b/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi
index 792868a8ae3445..19775b6eff8a22 100644
Binary files a/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi and b/icons/obj/pipes_n_cables/hydrochem/plumbers.dmi differ
diff --git a/icons/obj/service/hydroponics/equipment.dmi b/icons/obj/service/hydroponics/equipment.dmi
index dc164c5b1c9431..afcc4de523512a 100644
Binary files a/icons/obj/service/hydroponics/equipment.dmi and b/icons/obj/service/hydroponics/equipment.dmi differ
diff --git a/icons/obj/service/janitor.dmi b/icons/obj/service/janitor.dmi
index 8c73a99b416424..0e814ca4545899 100644
Binary files a/icons/obj/service/janitor.dmi and b/icons/obj/service/janitor.dmi differ
diff --git a/icons/obj/service/library.dmi b/icons/obj/service/library.dmi
index dfd8aca6b89fa9..8229c1fc3947e7 100644
Binary files a/icons/obj/service/library.dmi and b/icons/obj/service/library.dmi differ
diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi
index a04a34edd319a0..e6679c8c51848a 100644
Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ
diff --git a/icons/obj/weapons/guns/energy.dmi b/icons/obj/weapons/guns/energy.dmi
index 07ac342a36d76c..97b75335b91a0e 100644
Binary files a/icons/obj/weapons/guns/energy.dmi and b/icons/obj/weapons/guns/energy.dmi differ
diff --git a/icons/obj/weapons/khopesh.dmi b/icons/obj/weapons/khopesh.dmi
index 4bbd4d5f0f9c92..ab7a0c252cbf77 100644
Binary files a/icons/obj/weapons/khopesh.dmi and b/icons/obj/weapons/khopesh.dmi differ
diff --git a/icons/turf/overlays.dmi b/icons/turf/overlays.dmi
index 83309e4d18ebbb..c9decbc5a3af30 100644
Binary files a/icons/turf/overlays.dmi and b/icons/turf/overlays.dmi differ
diff --git a/icons/ui_icons/achievements/achievements.dmi b/icons/ui_icons/achievements/achievements.dmi
index ad34dfd16f0ce1..b3e64c9d09f835 100644
Binary files a/icons/ui_icons/achievements/achievements.dmi and b/icons/ui_icons/achievements/achievements.dmi differ
diff --git a/icons/ui_icons/fishing/default.png b/icons/ui_icons/fishing/default.png
deleted file mode 100644
index 20dabae9210ed9..00000000000000
Binary files a/icons/ui_icons/fishing/default.png and /dev/null differ
diff --git a/icons/ui_icons/fishing/lavaland.png b/icons/ui_icons/fishing/lavaland.png
deleted file mode 100644
index b6e2487bdc4b8f..00000000000000
Binary files a/icons/ui_icons/fishing/lavaland.png and /dev/null differ
diff --git a/libbyond_sleeping_procs.so b/libbyond_sleeping_procs.so
deleted file mode 100644
index 8596115fd9624a..00000000000000
Binary files a/libbyond_sleeping_procs.so and /dev/null differ
diff --git a/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm b/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm
index 620317122b22e9..b74721c63ae260 100644
--- a/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm
+++ b/modular_skyrat/master_files/code/game/objects/items/stacks/sheets/sheet_types.dm
@@ -102,6 +102,7 @@ GLOBAL_LIST_INIT(skyrat_leather_belt_recipes, list(
new/datum/stack_recipe("xenoarch belt", /obj/item/storage/belt/utility/xenoarch, 4, check_density = FALSE, category = CAT_CONTAINERS),
new/datum/stack_recipe("medical bandolier", /obj/item/storage/belt/medbandolier, 5, check_density = FALSE, category = CAT_CONTAINERS),
new/datum/stack_recipe("gear harness", /obj/item/clothing/under/misc/skyrat/gear_harness, 6, check_density = FALSE, category = CAT_CLOTHING),
+ new/datum/stack_recipe("ammo pouch", /obj/item/storage/pouch/ammo, 4, check_density = FALSE, category = CAT_CONTAINERS),
))
/obj/item/stack/sheet/leather/get_main_recipes()
diff --git a/modular_skyrat/master_files/code/modules/cargo/bounties/medical.dm b/modular_skyrat/master_files/code/modules/cargo/bounties/medical.dm
new file mode 100644
index 00000000000000..e1b032bbc332fa
--- /dev/null
+++ b/modular_skyrat/master_files/code/modules/cargo/bounties/medical.dm
@@ -0,0 +1,2 @@
+/datum/bounty/item/medical/tongue
+ description = "A recent attack by Mime extremists has left staff at Station 23 speechless. Ship some spare tongues. We'll accept cybernetic variants if need be."
\ No newline at end of file
diff --git a/modular_skyrat/master_files/code/modules/client/playtime.dm b/modular_skyrat/master_files/code/modules/client/playtime.dm
index c640b4b95d7cee..b1d301120e777f 100644
--- a/modular_skyrat/master_files/code/modules/client/playtime.dm
+++ b/modular_skyrat/master_files/code/modules/client/playtime.dm
@@ -20,6 +20,10 @@
return preferences?.parent?.is_green()
/datum/preference/toggle/green_pin/apply_to_human(mob/living/carbon/human/wearer, value)
+ if(value && wearer.client && !wearer.client?.is_green())
+ // This way, it doesn't stick for those that had it set to TRUE before they got their 100 hours in.
+ wearer.client?.prefs?.write_preference(GLOB.preference_entries[/datum/preference/toggle/green_pin], FALSE)
+
return
/client/proc/is_green()
diff --git a/modular_skyrat/master_files/code/modules/clothing/under/jobs/cargo.dm b/modular_skyrat/master_files/code/modules/clothing/under/jobs/cargo.dm
index adbbbd0dc5b0d6..8fe26c3b34260f 100644
--- a/modular_skyrat/master_files/code/modules/clothing/under/jobs/cargo.dm
+++ b/modular_skyrat/master_files/code/modules/clothing/under/jobs/cargo.dm
@@ -103,7 +103,7 @@
/obj/item/clothing/under/rank/cargo/qm/skyrat/formal/skirt
name = "quartermaster's formal jumpskirt"
desc = "A western-like alternate uniform for the old fashioned QM. Skirt included!"
- icon_state = "supply_chief"
+ icon_state = "supply_chief_skirt"
can_adjust = FALSE
body_parts_covered = CHEST|GROIN|ARMS
dying_key = DYE_REGISTRY_JUMPSKIRT
diff --git a/modular_skyrat/master_files/code/modules/mob/living/carbon/human_helpers.dm b/modular_skyrat/master_files/code/modules/mob/living/carbon/human_helpers.dm
index 4cffb1a204590f..b13a1ec0e3eeed 100644
--- a/modular_skyrat/master_files/code/modules/mob/living/carbon/human_helpers.dm
+++ b/modular_skyrat/master_files/code/modules/mob/living/carbon/human_helpers.dm
@@ -4,7 +4,7 @@
var/t_He = p_They()
var/t_is = p_are()
//This checks to see if the body is revivable
- if(key || !get_organ_by_type(/obj/item/organ/internal/brain) || ghost?.can_reenter_corpse)
+ if((key || !get_organ_by_type(/obj/item/organ/internal/brain) || ghost?.can_reenter_corpse) && (!HAS_TRAIT(src, TRAIT_DNR)))
return span_deadsay("[t_He] [t_is] limp and unresponsive; they're still twitching on occasion, perhaps [p_they()] can still be saved..!")
else
return span_deadsay("[t_He] [t_is] limp and unresponsive; there are no signs of life and they've degraded beyond revival...")
diff --git a/modular_skyrat/master_files/code/modules/surgery/organs/internal/appendix/_appendix.dm b/modular_skyrat/master_files/code/modules/surgery/organs/internal/appendix/_appendix.dm
new file mode 100644
index 00000000000000..296696d559f94f
--- /dev/null
+++ b/modular_skyrat/master_files/code/modules/surgery/organs/internal/appendix/_appendix.dm
@@ -0,0 +1,8 @@
+/obj/item/organ/internal/appendix/become_inflamed()
+ if(engaged_role_play_check(owner, station = TRUE, dorms = TRUE))
+ return
+
+ if(!(owner.mind.assigned_role.job_flags & JOB_CREW_MEMBER))
+ return
+
+ ..()
diff --git a/modular_skyrat/master_files/icons/mob/clothing/head/cowboy.dmi b/modular_skyrat/master_files/icons/mob/clothing/head/cowboy.dmi
index 835a46c9d16b15..94d19052982a55 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/head/cowboy.dmi and b/modular_skyrat/master_files/icons/mob/clothing/head/cowboy.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/suit.dmi b/modular_skyrat/master_files/icons/mob/clothing/suit.dmi
index cf43274be0564b..c00b68f50cfac0 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/suit.dmi and b/modular_skyrat/master_files/icons/mob/clothing/suit.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/under/cargo.dmi b/modular_skyrat/master_files/icons/mob/clothing/under/cargo.dmi
index 6f988460699695..9a8042cf0a0f6e 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/under/cargo.dmi and b/modular_skyrat/master_files/icons/mob/clothing/under/cargo.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/under/cargo_digi.dmi b/modular_skyrat/master_files/icons/mob/clothing/under/cargo_digi.dmi
index b975af5de8c257..c4ecb9e4f81203 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/under/cargo_digi.dmi and b/modular_skyrat/master_files/icons/mob/clothing/under/cargo_digi.dmi differ
diff --git a/modular_skyrat/master_files/icons/mob/clothing/underwear.dmi b/modular_skyrat/master_files/icons/mob/clothing/underwear.dmi
index bb41c5562b69c5..09b9161fadec48 100644
Binary files a/modular_skyrat/master_files/icons/mob/clothing/underwear.dmi and b/modular_skyrat/master_files/icons/mob/clothing/underwear.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/head/cowboy.dmi b/modular_skyrat/master_files/icons/obj/clothing/head/cowboy.dmi
index bd26b776ee56b2..fb582bf495ce59 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/head/cowboy.dmi and b/modular_skyrat/master_files/icons/obj/clothing/head/cowboy.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/suits.dmi b/modular_skyrat/master_files/icons/obj/clothing/suits.dmi
index e2a568e2c918f0..45b23619f8976e 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/suits.dmi and b/modular_skyrat/master_files/icons/obj/clothing/suits.dmi differ
diff --git a/modular_skyrat/master_files/icons/obj/clothing/under/cargo.dmi b/modular_skyrat/master_files/icons/obj/clothing/under/cargo.dmi
index bc6d0043e132fe..fccbf88fbc9e86 100644
Binary files a/modular_skyrat/master_files/icons/obj/clothing/under/cargo.dmi and b/modular_skyrat/master_files/icons/obj/clothing/under/cargo.dmi differ
diff --git a/modular_skyrat/modules/alternative_job_titles/code/alt_job_titles.dm b/modular_skyrat/modules/alternative_job_titles/code/alt_job_titles.dm
index e62c81552d59cc..d7abb4e00f0116 100644
--- a/modular_skyrat/modules/alternative_job_titles/code/alt_job_titles.dm
+++ b/modular_skyrat/modules/alternative_job_titles/code/alt_job_titles.dm
@@ -300,6 +300,7 @@
/datum/job/quartermaster
alt_titles = list(
+ "Quartermaster",
"Union Requisitions Officer",
"Deck Chief",
"Warehouse Supervisor",
diff --git a/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm b/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm
index b49156bd0a13f4..ae8176bffb5908 100644
--- a/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm
+++ b/modular_skyrat/modules/ashwalkers/code/effects/ash_rituals.dm
@@ -331,23 +331,47 @@
/datum/ash_ritual/revive_animal/ritual_success(obj/effect/ash_rune/success_rune)
. = ..()
+ if(!revive_simple(success_rune))
+ revive_basic(success_rune)
+
+/datum/ash_ritual/revive_animal/proc/revive_simple(obj/effect/ash_rune/success_rune)
var/turf/src_turf = get_turf(success_rune)
var/mob/living/simple_animal/find_animal = locate() in src_turf
if(!find_animal)
- return
+ return FALSE
if(find_animal.stat != DEAD)
- return
+ return FALSE
if(find_animal.sentience_type != SENTIENCE_ORGANIC)
- return
+ return FALSE
- find_animal.faction = list(FACTION_NEUTRAL)
+ find_animal.faction = list(FACTION_ASHWALKER)
if(ishostile(find_animal))
var/mob/living/simple_animal/hostile/hostile_animal = find_animal
hostile_animal.attack_same = FALSE
find_animal.revive(HEAL_ALL)
+ return TRUE
+
+/datum/ash_ritual/revive_animal/proc/revive_basic(obj/effect/ash_rune/success_rune)
+ var/turf/src_turf = get_turf(success_rune)
+
+ var/mob/living/basic/find_animal = locate() in src_turf
+
+ if(!find_animal)
+ return FALSE
+
+ if(find_animal.health > 0)
+ return FALSE
+
+ if(find_animal.sentience_type != SENTIENCE_ORGANIC)
+ return FALSE
+
+ find_animal.faction = list(FACTION_ASHWALKER)
+
+ find_animal.revive()
+ return TRUE
diff --git a/modular_skyrat/modules/ashwalkers/code/species/Ashwalkers.dm b/modular_skyrat/modules/ashwalkers/code/species/Ashwalkers.dm
index d24aa290ec0686..0e60d2ed7e90ce 100644
--- a/modular_skyrat/modules/ashwalkers/code/species/Ashwalkers.dm
+++ b/modular_skyrat/modules/ashwalkers/code/species/Ashwalkers.dm
@@ -13,10 +13,12 @@
. = ..()
RegisterSignal(carbon_target, COMSIG_MOB_ITEM_ATTACK, PROC_REF(mob_attack))
carbon_target.AddComponent(/datum/component/ash_age)
+ carbon_target.faction |= FACTION_ASHWALKER
/datum/species/lizard/ashwalker/on_species_loss(mob/living/carbon/carbon_target)
. = ..()
UnregisterSignal(carbon_target, COMSIG_MOB_ITEM_ATTACK)
+ carbon_target.faction &= FACTION_ASHWALKER
/datum/species/lizard/ashwalker/proc/mob_attack(datum/source, mob/mob_target, mob/user)
SIGNAL_HANDLER
diff --git a/modular_skyrat/modules/blueshield/code/closet.dm b/modular_skyrat/modules/blueshield/code/closet.dm
index 54e5665135eeac..1ceaf37325b6f2 100644
--- a/modular_skyrat/modules/blueshield/code/closet.dm
+++ b/modular_skyrat/modules/blueshield/code/closet.dm
@@ -1,3 +1,19 @@
+/obj/item/storage/bag/garment/blueshield
+ name = "Blueshield's garment bag"
+ desc = "A bag for storing extra clothes and shoes. This one belongs to the blueshield."
+
+/obj/item/storage/bag/garment/blueshield/PopulateContents()
+ new /obj/item/clothing/suit/hooded/wintercoat/skyrat/blueshield(src)
+ new /obj/item/clothing/head/beret/blueshield(src)
+ new /obj/item/clothing/head/beret/blueshield/navy(src)
+ new /obj/item/clothing/under/rank/blueshield(src)
+ new /obj/item/clothing/under/rank/blueshield/skirt(src)
+ new /obj/item/clothing/under/rank/blueshield/turtleneck(src)
+ new /obj/item/clothing/under/rank/blueshield/turtleneck/skirt(src)
+ new /obj/item/clothing/suit/armor/vest/blueshield(src)
+ new /obj/item/clothing/suit/armor/vest/blueshield/jacket(src)
+ new /obj/item/clothing/neck/mantle/bsmantle(src)
+
/obj/structure/closet/secure_closet/blueshield
name = "\the blueshield's locker"
icon_state = "bs"
@@ -12,5 +28,5 @@
new /obj/item/assembly/flash/handheld(src)
new /obj/item/restraints/handcuffs(src)
new /obj/item/clothing/glasses/hud/security/sunglasses(src)
- new /obj/item/clothing/suit/hooded/wintercoat/skyrat/blueshield(src)
new /obj/item/storage/medkit/tactical/blueshield(src)
+ new /obj/item/storage/bag/garment/blueshield(src)
diff --git a/modular_skyrat/modules/bluespace_miner/code/bluespace_miner.dm b/modular_skyrat/modules/bluespace_miner/code/bluespace_miner.dm
index 6b45258c05108d..e082ddabb42ae7 100644
--- a/modular_skyrat/modules/bluespace_miner/code/bluespace_miner.dm
+++ b/modular_skyrat/modules/bluespace_miner/code/bluespace_miner.dm
@@ -151,7 +151,7 @@
/datum/supply_pack/misc/bluespace_miner
name = "Bluespace Miner"
desc = "Nanotrasen has revolutionized the procuring of materials with bluespace-- featuring the Bluespace Miner!"
- cost = CARGO_CRATE_VALUE * 150 // 30,000
+ cost = CARGO_CRATE_VALUE * 50 // 10,000
contains = list(/obj/item/circuitboard/machine/bluespace_miner)
crate_name = "Bluespace Miner Circuitboard Crate"
crate_type = /obj/structure/closet/crate
diff --git a/modular_skyrat/modules/cell_component/code/flashlight.dm b/modular_skyrat/modules/cell_component/code/flashlight.dm
index 01de50266e3e57..6e585781a838ee 100644
--- a/modular_skyrat/modules/cell_component/code/flashlight.dm
+++ b/modular_skyrat/modules/cell_component/code/flashlight.dm
@@ -18,13 +18,10 @@
/obj/item/flashlight/Initialize(mapload)
. = ..()
- if(icon_state == "[initial(icon_state)]-on")
- turn_on(FALSE)
- update_brightness()
- register_context()
+ update_item_action_buttons()
if(uses_battery)
- AddComponent(/datum/component/cell, cell_override, CALLBACK(src, PROC_REF(turn_off)), _has_cell_overlays = FALSE)
+ AddComponent(/datum/component/cell, cell_override, CALLBACK(src, PROC_REF(quietly_turn_off)), _has_cell_overlays = FALSE)
/obj/item/flashlight/examine(mob/user)
. = ..()
@@ -55,46 +52,37 @@
set_light_power(initial(light_power))
to_chat(user, span_notice("You set [src] to low."))
-/obj/item/flashlight/proc/toggle_light()
+/obj/item/flashlight/toggle_light(noisy = TRUE)
+ . = ..()
if(on)
- turn_off()
- else
+ after_turn_on()
if(uses_battery && !(item_use_power(power_use_amount, TRUE) & COMPONENT_POWER_SUCCESS))
return
- turn_on(makes_noise_when_lit)
- playsound(src, on ? sound_on : sound_off, 40, TRUE)
- return TRUE
-
-/obj/item/flashlight/attack_self(mob/user)
- . = ..()
- toggle_light()
+ if(noisy)
+ playsound(src, on ? sound_on : sound_off, 40, TRUE)
/**
* Handles turning on the flashlight.
* Parameters:
* * noisy - Boolean on whether the flashlight should make an additional noise from being turned on or not. Defaults to TRUE.
*/
-/obj/item/flashlight/proc/turn_on(noisy = TRUE)
- on = TRUE
- if (uses_battery)
+/obj/item/flashlight/proc/after_turn_on(noisy = TRUE)
+ if(uses_battery)
START_PROCESSING(SSobj, src)
- update_brightness()
if(noisy)
playsound(src, 'modular_skyrat/master_files/sound/effects/flashlight.ogg', 40, TRUE) //Credits to ERIS for the sound
- update_item_action_buttons()
-/// Handles turning off the flashlight.
-/obj/item/flashlight/proc/turn_off()
- on = FALSE
- update_brightness()
- update_item_action_buttons()
+/// Quietly turns the flashlight off if it was on (called by the battery running out of charge).
+/obj/item/flashlight/proc/quietly_turn_off()
+ if(on)
+ toggle_light(noisy = FALSE)
/obj/item/flashlight/process(seconds_per_tick)
if(!on)
STOP_PROCESSING(SSobj, src)
return
if(uses_battery && !(item_use_power(power_use_amount) & COMPONENT_POWER_SUCCESS))
- turn_off()
+ quietly_turn_off()
/obj/item/flashlight/update_icon_state()
. = ..()
diff --git a/modular_skyrat/modules/company_imports/code/armament_datums/vitezstvi_ammo.dm b/modular_skyrat/modules/company_imports/code/armament_datums/vitezstvi_ammo.dm
index 27a3a48920fb23..3e6df12d1246e8 100644
--- a/modular_skyrat/modules/company_imports/code/armament_datums/vitezstvi_ammo.dm
+++ b/modular_skyrat/modules/company_imports/code/armament_datums/vitezstvi_ammo.dm
@@ -15,6 +15,10 @@
item_type = /obj/item/disk/ammo_workbench/advanced
cost = PAYCHECK_COMMAND * 5
+/datum/armament_entry/company_import/vitezstvi/ammo_bench/bullet_drive
+ item_type = /obj/item/circuitboard/machine/dish_drive/bullet
+ cost = PAYCHECK_COMMAND * 2
+
// Boxes of non-shotgun ammo
/datum/armament_entry/company_import/vitezstvi/ammo_boxes
diff --git a/modular_skyrat/modules/contractor/code/datums/contract.dm b/modular_skyrat/modules/contractor/code/datums/contract.dm
index 47e7906254d1ba..7d57f029f844e5 100644
--- a/modular_skyrat/modules/contractor/code/datums/contract.dm
+++ b/modular_skyrat/modules/contractor/code/datums/contract.dm
@@ -213,9 +213,9 @@
for(var/i in 1 to 2)
var/obj/item/bodypart/limb = target.get_bodypart(pick_n_take(parts_to_fuck_up))
- var/datum/wound/blunt/bone/severe/severe_wound_type = /datum/wound/blunt/bone/severe
- var/datum/wound/blunt/bone/critical/critical_wound_type = /datum/wound/blunt/bone/critical
- limb.receive_damage(brute = WOUND_MINIMUM_DAMAGE, wound_bonus = rand(initial(severe_wound_type.threshold_minimum), initial(critical_wound_type.threshold_minimum) + 10))
+ var/min_wound = limb.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_SEVERE, return_value_if_no_wound = 40)
+ var/max_wound = limb.get_wound_threshold_of_wound_type(WOUND_BLUNT, WOUND_SEVERITY_CRITICAL, return_value_if_no_wound = 60)
+ limb.receive_damage(brute = WOUND_MINIMUM_DAMAGE, wound_bonus = rand(min_wound, max_wound))
target.update_damage_overlays()
addtimer(CALLBACK(src, PROC_REF(victim_stage_three), target), 6 SECONDS)
diff --git a/modular_skyrat/modules/customization/modules/client/augment/limbs.dm b/modular_skyrat/modules/customization/modules/client/augment/limbs.dm
index e1bc30db944485..e5f8f4f14e7beb 100644
--- a/modular_skyrat/modules/customization/modules/client/augment/limbs.dm
+++ b/modular_skyrat/modules/customization/modules/client/augment/limbs.dm
@@ -67,6 +67,12 @@
path = /obj/item/bodypart/arm/left/plasmaman
uses_robotic_styles = FALSE
+/datum/augment_item/limb/l_arm/self_destruct
+ name = "No Left Arm"
+ path = /obj/item/bodypart/arm/left/self_destruct
+ cost = -3
+ uses_robotic_styles = FALSE
+
//RIGHT ARMS
/datum/augment_item/limb/r_arm
slot = AUGMENT_SLOT_R_ARM
@@ -85,6 +91,12 @@
path = /obj/item/bodypart/arm/right/plasmaman
uses_robotic_styles = FALSE
+/datum/augment_item/limb/r_arm/self_destruct
+ name = "No Right Arm"
+ path = /obj/item/bodypart/arm/right/self_destruct
+ cost = -3
+ uses_robotic_styles = FALSE
+
//LEFT LEGS
/datum/augment_item/limb/l_leg
slot = AUGMENT_SLOT_L_LEG
@@ -103,6 +115,12 @@
path = /obj/item/bodypart/leg/left/plasmaman
uses_robotic_styles = FALSE
+/datum/augment_item/limb/l_leg/self_destruct
+ name = "No Left Leg"
+ path = /obj/item/bodypart/leg/left/self_destruct
+ cost = -3
+ uses_robotic_styles = FALSE
+
//RIGHT LEGS
/datum/augment_item/limb/r_leg
slot = AUGMENT_SLOT_R_LEG
@@ -120,3 +138,9 @@
name = "Plasmaman right leg"
path = /obj/item/bodypart/leg/right/plasmaman
uses_robotic_styles = FALSE
+
+/datum/augment_item/limb/r_leg/self_destruct
+ name = "No Right Leg"
+ path = /obj/item/bodypart/leg/right/self_destruct
+ cost = -3
+ uses_robotic_styles = FALSE
diff --git a/modular_skyrat/modules/customization/modules/clothing/under/utility_port/suits_port.dm b/modular_skyrat/modules/customization/modules/clothing/under/utility_port/suits_port.dm
index 07ce022f2a4602..bb88ca325dbf63 100644
--- a/modular_skyrat/modules/customization/modules/clothing/under/utility_port/suits_port.dm
+++ b/modular_skyrat/modules/customization/modules/clothing/under/utility_port/suits_port.dm
@@ -53,16 +53,6 @@
desc = "A comfortable jacket in a neutral black"
icon_state = "off_dep_jacket"
-/obj/item/clothing/suit/gorka //THIS WILL BE MOVED IN THE NEXT PR ADDING PROPER GORKAS (not cargo related so not in this PR), BUT FOR NOW ITS HERE FOR THE SUBTYPE'S FILE LINKS
- icon = 'modular_skyrat/master_files/icons/obj/clothing/suits.dmi'
- worn_icon = 'modular_skyrat/master_files/icons/mob/clothing/suit.dmi'
- supports_variations_flags = CLOTHING_DIGITIGRADE_VARIATION_NO_NEW_ICON
-
-/obj/item/clothing/suit/gorka/supply //Put here for sorting purposes, considering the cargo gorkas are in the utility file too. The base Gorka and Jacket (to be added later) will most likely be elsewhere
- name = "supply gorka jacket"
- desc = "A snug jacket to wear over your gorka. This one matches with supply's colors."
- icon_state = "gorka_jacket_supply"
-
/obj/item/clothing/suit/toggle/jacket/supply/head
name = "quartermaster's jacket"
desc = "Even if people refuse to recognize you as a head, they can recognize you as a badass."
diff --git a/modular_skyrat/modules/customization/modules/jobs/_job.dm b/modular_skyrat/modules/customization/modules/jobs/_job.dm
index af223cbc67cc68..e6be489f524f51 100644
--- a/modular_skyrat/modules/customization/modules/jobs/_job.dm
+++ b/modular_skyrat/modules/customization/modules/jobs/_job.dm
@@ -5,6 +5,8 @@
var/loadout = TRUE
//List of banned quirks in their names(dont blame me, that's how they're stored), players can't join as the job if they have the quirk. Associative for the purposes of performance
var/list/banned_quirks
+ /// List of banned augments
+ var/list/banned_augments
///A list of slots that can't have loadout items assigned to them if no_dresscode is applied, used for important items such as ID, PDA, backpack and headset
var/list/blacklist_dresscode_slots
//Whitelist of allowed species for this job. If not specified then all roundstart races can be used. Associative with TRUE
@@ -41,6 +43,20 @@
return TRUE
return FALSE
+/datum/job/proc/has_banned_augment(datum/preferences/pref)
+ if(!pref)
+ return FALSE
+
+ if(!banned_augments)
+ return FALSE
+
+ var/list/player_augments = pref.augments
+ for(var/key in player_augments)
+ if(player_augments[key] in banned_augments)
+ return TRUE
+
+ return FALSE
+
// Misc
/datum/job/assistant
no_dresscode = TRUE
@@ -53,43 +69,56 @@
//Security
/datum/job/security_officer
banned_quirks = list(SEC_RESTRICTED_QUIRKS)
+ banned_augments = list(SEC_RESTRICTED_AUGMENTS)
/datum/job/detective
banned_quirks = list(SEC_RESTRICTED_QUIRKS)
+ banned_augments = list(SEC_RESTRICTED_AUGMENTS)
/datum/job/warden
banned_quirks = list(SEC_RESTRICTED_QUIRKS)
+ banned_augments = list(SEC_RESTRICTED_AUGMENTS)
/datum/job/blueshield
banned_quirks = list(SEC_RESTRICTED_QUIRKS)
+ banned_augments = list(SEC_RESTRICTED_AUGMENTS)
/datum/job/corrections_officer
banned_quirks = list(SEC_RESTRICTED_QUIRKS)
+ banned_augments = list(SEC_RESTRICTED_AUGMENTS)
// Command
/datum/job/captain
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/nanotrasen_consultant
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/head_of_security
banned_quirks = list(SEC_RESTRICTED_QUIRKS, HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(SEC_RESTRICTED_AUGMENTS)
/datum/job/chief_medical_officer
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/chief_engineer
banned_quirks = list(HEAD_RESTRICTED_QUIRKS, "Paraplegic" = TRUE)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/research_director
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/head_of_personnel
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/quartermaster
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
//Silicon
/datum/job/ai
@@ -147,12 +176,16 @@
// Nanotrasen Fleet
/datum/job/fleetmaster
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/operations_inspector
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/deck_crew
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
/datum/job/bridge_officer
banned_quirks = list(HEAD_RESTRICTED_QUIRKS)
+ banned_augments = list(HEAD_RESTRICTED_AUGMENTS)
diff --git a/modular_skyrat/modules/customization/modules/mob/dead/new_player/sprite_accessories.dm b/modular_skyrat/modules/customization/modules/mob/dead/new_player/sprite_accessories.dm
index cd22694da794c6..33a5ba2487f30b 100644
--- a/modular_skyrat/modules/customization/modules/mob/dead/new_player/sprite_accessories.dm
+++ b/modular_skyrat/modules/customization/modules/mob/dead/new_player/sprite_accessories.dm
@@ -579,6 +579,9 @@ GLOBAL_LIST_EMPTY(cached_mutant_icon_files)
/*
End of adding hides_breasts to TG stuff, start of adding has_digitigrade to TG stuff
*/
+/datum/sprite_accessory/underwear/male_briefs
+ has_digitigrade = TRUE
+
/datum/sprite_accessory/underwear/male_boxers
has_digitigrade = TRUE
@@ -655,6 +658,12 @@ GLOBAL_LIST_EMPTY(cached_mutant_icon_files)
gender = FEMALE
use_static = null
+/datum/sprite_accessory/undershirt/hi_vis_bra
+ name = "Safekini"
+ icon_state = "hi_vis_bra"
+ gender = FEMALE
+ use_static = TRUE
+
/datum/sprite_accessory/undershirt/bra_alt
name = "Bra - Alt"
icon_state = "bra_alt"
diff --git a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/_hemophage_defines.dm b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/_hemophage_defines.dm
index 50fcc84f3f7346..7471839e1dbfae 100644
--- a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/_hemophage_defines.dm
+++ b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/_hemophage_defines.dm
@@ -10,6 +10,8 @@
/// Minimum amount of blood that you can reach via blood regeneration, regeneration will stop below this.
#define MINIMUM_VOLUME_FOR_REGEN (BLOOD_VOLUME_BAD + 1) // We do this to avoid any jankiness, and because we want to ensure that they don't fall into a state where they're constantly passing out in a locker.
+/// Vomit flags for hemophages who eat food
+#define HEMOPHAGE_VOMIT_FLAGS (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM | MOB_VOMIT_FORCE)
/// The ratio of reagents that get purged while a Hemophage vomits from trying to eat/drink something that their tumor doesn't like.
#define HEMOPHAGE_VOMIT_PURGE_RATIO 0.95
/// How much disgust we're at after eating/drinking something the tumor doesn't like.
diff --git a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_liver.dm b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_liver.dm
index 5210b20977539f..bf731c941aae7b 100644
--- a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_liver.dm
+++ b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_liver.dm
@@ -49,7 +49,7 @@
to_chat(body, span_warning("That tasted awful..."))
// We don't lose nutrition because we don't even use nutrition as Hemopahges. It WILL however purge nearly all of what's in their stomach.
- body.vomit(lost_nutrition = 0, stun = FALSE, distance = 1, force = TRUE, purge_ratio = HEMOPHAGE_VOMIT_PURGE_RATIO)
+ body.vomit(vomit_flags = HEMOPHAGE_VOMIT_FLAGS, lost_nutrition = 0, distance = 1, purge_ratio = HEMOPHAGE_VOMIT_PURGE_RATIO)
#undef MINIMUM_BLOOD_REGENING_REAGENT_RATIO
diff --git a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_stomach.dm b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_stomach.dm
index 801f65fb7e02aa..3af433695a9af8 100644
--- a/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_stomach.dm
+++ b/modular_skyrat/modules/customization/modules/mob/living/carbon/human/species/hemophage/corrupted_stomach.dm
@@ -1,4 +1,3 @@
-
/datum/component/organ_corruption/stomach
corruptable_organ_type = /obj/item/organ/internal/stomach
corrupted_icon_state = "stomach"
@@ -43,4 +42,4 @@
to_chat(body, span_warning("That tasted awful..."))
// We don't lose nutrition because we don't even use nutrition as hemopahges. It WILL however purge nearly all of what's in their stomach.
- body.vomit(lost_nutrition = 0, stun = FALSE, distance = 1, force = TRUE, purge_ratio = HEMOPHAGE_VOMIT_PURGE_RATIO)
+ body.vomit(vomit_flags = HEMOPHAGE_VOMIT_FLAGS, lost_nutrition = 0, distance = 1, purge_ratio = HEMOPHAGE_VOMIT_PURGE_RATIO)
diff --git a/modular_skyrat/modules/customization/modules/reagents/chemistry/reagents/alcohol_reagents.dm b/modular_skyrat/modules/customization/modules/reagents/chemistry/reagents/alcohol_reagents.dm
index 25af43017e76aa..32391bd465e3c0 100644
--- a/modular_skyrat/modules/customization/modules/reagents/chemistry/reagents/alcohol_reagents.dm
+++ b/modular_skyrat/modules/customization/modules/reagents/chemistry/reagents/alcohol_reagents.dm
@@ -31,7 +31,7 @@
if(!(C.mob_biotypes & MOB_ROBOTIC))
C.reagents.remove_reagent(type, 3.6) //gets removed from organics very fast
if(prob(25))
- C.vomit(5, FALSE, FALSE)
+ C.vomit(VOMIT_CATEGORY_DEFAULT, lost_nutrition = 5)
return ..()
/datum/reagent/consumable/ethanol/synthanol/expose_mob(mob/living/carbon/C, method=TOUCH, volume)
diff --git a/modular_skyrat/modules/customization/modules/surgery/bodyparts/parts.dm b/modular_skyrat/modules/customization/modules/surgery/bodyparts/parts.dm
new file mode 100644
index 00000000000000..85b251c08b6a1e
--- /dev/null
+++ b/modular_skyrat/modules/customization/modules/surgery/bodyparts/parts.dm
@@ -0,0 +1,32 @@
+/// Self Destructing Bodyparts, For Augmentation. I'm leaving out heads + chests as, while it would be cool for synths, I also don't want people to start the round unrevivable sans botany because they're dumb as heck. You know who and what you are.
+/obj/item/bodypart/arm/left/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special)
+ . = ..()
+ drop_limb()
+ qdel(src)
+
+/obj/item/bodypart/arm/left/self_destruct/set_icon_static(new_icon)
+ return
+
+/obj/item/bodypart/arm/right/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special)
+ . = ..()
+ drop_limb()
+ qdel(src)
+
+/obj/item/bodypart/arm/right/self_destruct/set_icon_static(new_icon)
+ return
+
+/obj/item/bodypart/leg/left/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special)
+ . = ..()
+ drop_limb()
+ qdel(src)
+
+/obj/item/bodypart/leg/left/self_destruct/set_icon_static(new_icon)
+ return
+
+/obj/item/bodypart/leg/right/self_destruct/try_attach_limb(mob/living/carbon/limb_owner, special)
+ . = ..()
+ drop_limb()
+ qdel(src)
+
+/obj/item/bodypart/leg/right/self_destruct/set_icon_static(new_icon)
+ return
diff --git a/modular_skyrat/modules/delam_emergency_stop/code/scram.dm b/modular_skyrat/modules/delam_emergency_stop/code/scram.dm
index 1cc11c47ef9ef2..5e6fefa8b1f5e7 100644
--- a/modular_skyrat/modules/delam_emergency_stop/code/scram.dm
+++ b/modular_skyrat/modules/delam_emergency_stop/code/scram.dm
@@ -1,4 +1,3 @@
-#define DELAM_MACHINE_POWER_CONSUMPTION 375
#define SM_PREVENT_EXPLOSION_THRESHOLD 100
#define SM_COOLING_MIXTURE_MOLES 64000
#define SM_COOLING_MIXTURE_TEMP 120
@@ -18,8 +17,9 @@
#define SHATTER_FLASH_RANGE 5
#define SHATTER_MIN_TIME 13 SECONDS
#define SHATTER_MAX_TIME 15 SECONDS
-#define POWER_CUT_MIN_DURATION 19 SECONDS
-#define POWER_CUT_MAX_DURATION 21 SECONDS
+#define EVAC_WARNING_TIMER 3 SECONDS
+#define POWER_CUT_MIN_DURATION_SECONDS 19
+#define POWER_CUT_MAX_DURATION_SECONDS 21
#define AIR_INJECT_RATE 33
#define BUTTON_SOUND_RANGE 7
#define BUTTON_SOUND_FALLOFF_DISTANCE 7
@@ -176,11 +176,12 @@
notify_volume = 75,
)
- radio.talk_into(src, "DELAMINATION SUPPRESSION SYSTEM FIRING IN 5 SECONDS. EVACUATE THE SUPERMATTER ENGINE ROOM!", emergency_channel)
+ radio.talk_into(src, "DELAMINATION SUPPRESSION SYSTEM FIRING. EVACUATE THE SUPERMATTER ENGINE ROOM!", emergency_channel)
// fight power with power
- addtimer(CALLBACK(src, PROC_REF(put_on_a_show)), 5 SECONDS)
- INVOKE_ASYNC(SSnightshift, TYPE_PROC_REF(/datum/controller/subsystem/nightshift, suck_light_power))
+ addtimer(CALLBACK(src, PROC_REF(put_on_a_show)), EVAC_WARNING_TIMER)
+ playsound(src, 'sound/misc/bloblarm.ogg', 100, FALSE, MACHINE_RUMBLE_SOUND_RANGE, ignore_walls = TRUE, use_reverb = TRUE, falloff_distance = MACHINE_SOUND_FALLOFF_DISTANCE)
+ power_fail((EVAC_WARNING_TIMER / 10) + POWER_CUT_MAX_DURATION_SECONDS, (EVAC_WARNING_TIMER / 10) + POWER_CUT_MAX_DURATION_SECONDS)
/// Stop the delamination. Let the fireworks begin
/obj/machinery/atmospherics/components/unary/delam_scram/proc/put_on_a_show()
@@ -190,7 +191,6 @@
// Fire bell close, that nice 'are we gonna die?' rumble out far
on = TRUE
- playsound(src, 'sound/machines/hypertorus/HFR_critical_explosion.ogg', 100, FALSE, MACHINE_RUMBLE_SOUND_RANGE, ignore_walls = TRUE, use_reverb = TRUE, falloff_distance = MACHINE_SOUND_FALLOFF_DISTANCE)
alert_sound_to_playing('sound/misc/earth_rumble_distant3.ogg', override_volume = TRUE)
update_appearance()
@@ -217,10 +217,7 @@
addtimer(CALLBACK(fucked_window, TYPE_PROC_REF(/obj/structure/window/reinforced/plasma, shatter_window)), rand(SHATTER_MIN_TIME, SHATTER_MAX_TIME))
// Let the gas work for a few seconds to cool the crystal. If it has damage beyond repair, heal it a bit
- addtimer(CALLBACK(src, PROC_REF(prevent_explosion)), 9 SECONDS)
-
- // Restore the power that we were 'channelling' to the SM
- addtimer(CALLBACK(SSnightshift, TYPE_PROC_REF(/datum/controller/subsystem/nightshift, restore_light_power)), rand(POWER_CUT_MIN_DURATION, POWER_CUT_MAX_DURATION))
+ addtimer(CALLBACK(src, PROC_REF(prevent_explosion)), 7 SECONDS)
/// Shatter the supermatter chamber windows
/obj/structure/window/reinforced/plasma/proc/shatter_window()
@@ -261,24 +258,6 @@
delam_juice.temperature = SM_COOLING_MIXTURE_TEMP
airs[1] = delam_juice
-/// Dims the lights and consumes a bit of APC power, summoning that electricity to stop the delam... somehow
-/datum/controller/subsystem/nightshift/proc/suck_light_power()
- SSnightshift.can_fire = FALSE
- for(var/obj/machinery/power/apc/light_to_suck as anything in SSmachines.get_machines_by_type(/obj/machinery/power/apc))
- light_to_suck.cell.charge -= DELAM_MACHINE_POWER_CONSUMPTION
- light_to_suck.lighting = APC_CHANNEL_OFF
- light_to_suck.nightshift_lights = TRUE
- light_to_suck.update_appearance()
- light_to_suck.update()
-
-/// Restores the lighting after the delam suppression
-/datum/controller/subsystem/nightshift/proc/restore_light_power()
- SSnightshift.can_fire = TRUE
- for(var/obj/machinery/power/apc/light_to_restore as anything in SSmachines.get_machines_by_type(/obj/machinery/power/apc))
- light_to_restore.lighting = APC_CHANNEL_AUTO_ON
- light_to_restore.update_appearance()
- light_to_restore.update()
-
/// A big red button you can smash to stop the supermatter engine, oh how tempting!
/obj/machinery/button/delam_scram
name = "\improper supermatter emergency stop button"
@@ -289,6 +268,7 @@
can_alter_skin = FALSE
silicon_access_disabled = TRUE
resistance_flags = FREEZE_PROOF | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF
+ use_power = NO_POWER_USE
light_color = LIGHT_COLOR_INTENSE_RED
light_power = 0.7
///one use only!
@@ -319,7 +299,6 @@
/// Proc for arming the red button, it hasn't been pushed yet
/obj/machinery/button/delam_scram/attack_hand(mob/user, list/modifiers)
. = ..()
-
if((machine_stat & BROKEN))
return
@@ -338,8 +317,8 @@
// Give them a cheeky instructions card. But only one! If you lost it, question your engineering prowess in this moment
if(button_stage == BUTTON_IDLE)
- visible_message(span_danger("A biscuit card falls out of [src]!"))
- user.put_in_hands(new /obj/item/folder/biscuit/confidential/delam(get_turf(user)))
+ visible_message(span_danger("A plastic card falls out of [src]!"))
+ user.put_in_hands(new /obj/item/paper/paperslip/corporate/fluff/delam_procedure(get_turf(user)))
button_stage = BUTTON_AWAKE
return
@@ -424,32 +403,69 @@
active = FALSE
update_appearance()
-/obj/item/folder/biscuit/confidential/delam
- name = "NT-approved delam emergency procedure"
- contained_slip = /obj/item/paper/paperslip/corporate/fluff/delam_procedure
- layer = SIGN_LAYER
-
/obj/item/paper/paperslip/corporate/fluff/delam_procedure/Initialize(mapload)
- name = "delam emergency procedure"
+ name = "NT-approved delam emergency procedure"
desc = "Now you're a REAL engineer!"
- default_raw_text = "EMERGENCY PROCEDURE: SUPERMATTER DELAMINATION
\
- So you've found yourself in a bit of a pickle with a delamination of a supermatter reactor. Don't worry, saving the day is just a few steps away!
\
- - Locate the ever-elusive red emergency stop button. It's probably hiding in plain sight, so take your time, have a laugh, and enjoy the anticipation. Remember, it's like a treasure hunt, only with the added bonus of preventing a nuclear disaster.
\
- - Once you've uncovered the button, muster all your courage and push it like there's no tomorrow. Well, actually, you're pushing it to ensure there is a tomorrow. But hey, who doesn't love a little paradoxical button-pushing?
\
- - Prepare for the impending suppression of the supermatter engine room, because things are about to get real quiet. Just make sure everyone has evacuated, or else they'll be in for a surprise. The system needs its space, and it's not known for being the friendliest neighbour.
\
- - After the delamination is successfully suppressed, take a moment to appreciate the delicate beauty of crystal-based electricity. Take a look around and fix any damage to those fragile glass components. Feel free to put on your finest overalls and channel your inner engiborg while doing so.
\
- - Keep an eye out for fires and the infamous air mix. It's always an adventure trying to strike the perfect balance between breathable air and potential suffocation. Remember, oxygen plus a spark equals fireworks – the kind you definitely don't want inside a reactor.
\
- - To avoid singeing your eyebrows off, consider enlisting the help of a synth or a trusty borg. After all, nothing says \"safety first\" like outsourcing your firefighting to non-living, non-breathing assistants.
\
- - Clear out any lightly radioactive debris (The cargo department will probably love to dispose it for you.)
\
- - Finally, revel in the satisfaction of knowing that you've single-handedly prevented a delamination. But, of course, don't forget to feel guilty because SAFETY MOTH Knows. SAFETY MOTH knows everything. It's always watching, judging, and probably taking notes for its next safety briefing. So bask in the glory of your heroism, but know that the all-knowing Moff is onto you.
\
- (Optional step, for the true daredevils out there)
\
- - When it comes time for your second attempt at starting the SM: Fold these instructions into a paper plane, give it a good toss towards the crystal, and watch it soar through the air. Because nothing says \"I'm dealing with a potentially catastrophic situation\" like engaging in some whimsical paper airplane shenanigans.
\
- Hopefully you'll never need to use this. However, good luck!"
+ return ..()
+
+/obj/item/paper/paperslip/corporate/fluff/delam_procedure/examine(mob/user)
+ . = ..()
+ ui_interact(user)
+
+/obj/item/paper/paperslip/corporate/fluff/delam_procedure/attackby(obj/item/attacking_item, mob/living/user, params)
+ if(burn_paper_product_attackby_check(attacking_item, user))
+ SStgui.close_uis(src)
+ return
+
+ // Enable picking paper up by clicking on it with the clipboard or folder
+ if(istype(attacking_item, /obj/item/clipboard) || istype(attacking_item, /obj/item/folder) || istype(attacking_item, /obj/item/paper_bin))
+ attacking_item.attackby(src, user)
+ return
+
+ ui_interact(user)
+ return ..()
+
+/obj/item/paper/paperslip/corporate/fluff/delam_procedure/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "DelamProcedure")
+ ui.autoupdate = FALSE
+ ui.open()
+
+/obj/structure/sign/delam_procedure
+ name = "Safety Moth - Delamination Emergency Procedure"
+ desc = "This informational sign uses Safety Moth™ to tell the viewer how to use the emergency stop button if the Supermatter Crystal is delaminating."
+ icon = 'modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi'
+ icon_state = "moff-poster"
+ pixel_y = 4
+ armor_type = /datum/armor/sign_delam
+ anchored = TRUE
+
+/datum/armor/sign_delam
+ melee = 60
+ acid = 70
+ fire = 90
+
+/obj/structure/sign/delam_procedure/examine(mob/user)
+ . = ..()
+ ui_interact(user)
+
+/obj/structure/sign/delam_procedure/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "DelamProcedure")
+ ui.autoupdate = FALSE
+ ui.open()
+
+/obj/structure/sign/delam_procedure/ui_status(mob/user)
+ if(user.is_blind())
+ return UI_CLOSE
+
return ..()
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/atmospherics/components/unary/delam_scram, 0)
+MAPPING_DIRECTIONAL_HELPERS(/obj/structure/sign/delam_procedure, 32)
-#undef DELAM_MACHINE_POWER_CONSUMPTION
#undef DAMAGED_SUPERMATTER_COLOR
#undef SM_PREVENT_EXPLOSION_THRESHOLD
#undef SM_COOLING_MIXTURE_MOLES
@@ -469,8 +485,9 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/atmospherics/components/unary/delam_s
#undef SHATTER_FLASH_RANGE
#undef SHATTER_MIN_TIME
#undef SHATTER_MAX_TIME
-#undef POWER_CUT_MIN_DURATION
-#undef POWER_CUT_MAX_DURATION
+#undef EVAC_WARNING_TIMER
+#undef POWER_CUT_MIN_DURATION_SECONDS
+#undef POWER_CUT_MAX_DURATION_SECONDS
#undef AIR_INJECT_RATE
#undef BUTTON_SOUND_RANGE
#undef BUTTON_SOUND_FALLOFF_DISTANCE
diff --git a/modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi b/modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi
index 8828ba8410b0a2..b654175abd4852 100644
Binary files a/modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi and b/modular_skyrat/modules/delam_emergency_stop/icons/scram.dmi differ
diff --git a/modular_skyrat/modules/gunsgalore/code/projectile.dm b/modular_skyrat/modules/gunsgalore/code/projectile.dm
deleted file mode 100644
index 9de14ea56075f3..00000000000000
--- a/modular_skyrat/modules/gunsgalore/code/projectile.dm
+++ /dev/null
@@ -1,94 +0,0 @@
-
-/**
- * Called when the projectile hits something
- *
- * @params
- * target - thing hit
- * blocked - percentage of hit blocked
- * pierce_hit - are we piercing through or regular hitting
- */
-/obj/projectile/proc/on_hit(atom/target, blocked = FALSE, pierce_hit)
- if(fired_from)
- SEND_SIGNAL(fired_from, COMSIG_PROJECTILE_ON_HIT, firer, target, Angle)
- // i know that this is probably more with wands and gun mods in mind, but it's a bit silly that the projectile on_hit signal doesn't ping the projectile itself.
- // maybe we care what the projectile thinks! See about combining these via args some time when it's not 5AM
- var/obj/item/bodypart/hit_limb
- if(isliving(target))
- var/mob/living/L = target
- hit_limb = L.check_limb_hit(def_zone)
- SEND_SIGNAL(src, COMSIG_PROJECTILE_SELF_ON_HIT, firer, target, Angle, hit_limb)
-
- if(QDELETED(src)) // in case one of the above signals deleted the projectile for whatever reason
- return
- var/turf/target_loca = get_turf(target)
-
- var/hitx
- var/hity
- if(target == original)
- hitx = target.pixel_x + p_x - 16
- hity = target.pixel_y + p_y - 16
- else
- hitx = target.pixel_x + rand(-8, 8)
- hity = target.pixel_y + rand(-8, 8)
-
- var/impact_sound
- if(hitsound)
- impact_sound = hitsound
- else
- impact_sound = target.impact_sound
- get_sfx()
- playsound(src, get_sfx_skyrat(impact_sound), vol_by_damage(), TRUE, -1)
-
- if(damage > 0 && (damage_type == BRUTE || damage_type == BURN) && iswallturf(target_loca) && prob(75))
- var/turf/closed/wall/W = target_loca
- if(impact_effect_type && !hitscan)
- new impact_effect_type(target_loca, hitx, hity)
-
- W.add_dent(WALL_DENT_SHOT, hitx, hity)
-
- return BULLET_ACT_HIT
-
- if(!isliving(target))
- if(impact_effect_type && !hitscan)
- new impact_effect_type(target_loca, hitx, hity)
- return BULLET_ACT_HIT
-
- var/mob/living/L = target
-
- if(blocked != 100) // not completely blocked
- if(damage && L.blood_volume && damage_type == BRUTE)
- var/splatter_dir = dir
- if(starting)
- splatter_dir = get_dir(starting, target_loca)
- if(isalien(L))
- new /obj/effect/temp_visual/dir_setting/bloodsplatter/xenosplatter(target_loca, splatter_dir)
- else
- new /obj/effect/temp_visual/dir_setting/bloodsplatter(target_loca, splatter_dir)
- if(prob(33))
- L.add_splatter_floor(target_loca)
- else if(impact_effect_type && !hitscan)
- new impact_effect_type(target_loca, hitx, hity)
-
- var/organ_hit_text = ""
- var/limb_hit = hit_limb
- if(limb_hit)
- organ_hit_text = " in \the [parse_zone(limb_hit)]"
- else if(suppressed && suppressed != SUPPRESSED_VERY)
- to_chat(L, span_userdanger("You're shot by \a [src][organ_hit_text]!"))
- else
- L.visible_message(span_danger("[L] is hit by \a [src][organ_hit_text]!"), \
- span_userdanger("You're hit by \a [src][organ_hit_text]!"), null, COMBAT_MESSAGE_RANGE)
- L.on_hit(src)
-
- var/reagent_note
- if(reagents?.reagent_list)
- reagent_note = " REAGENTS:"
- for(var/datum/reagent/R in reagents.reagent_list)
- reagent_note += "[R.name] ([num2text(R.volume)])"
-
- if(ismob(firer))
- log_combat(firer, L, "shot", src, reagent_note)
- else
- L.log_message("has been shot by [firer] with [src]", LOG_ATTACK, color="orange")
-
- return BULLET_ACT_HIT
diff --git a/modular_skyrat/modules/hev_suit/code/hev_suit.dm b/modular_skyrat/modules/hev_suit/code/hev_suit.dm
index 081b4a0853fa5c..fd8e5462a6dd90 100644
--- a/modular_skyrat/modules/hev_suit/code/hev_suit.dm
+++ b/modular_skyrat/modules/hev_suit/code/hev_suit.dm
@@ -621,16 +621,18 @@
var/sound_to_play
- var/wound_series = new_wound.wound_series
- var/wound_type = new_wound.wound_type
+ var/datum/wound_pregen_data/pregen_data = new_wound.get_pregen_data()
var/wound_severity = new_wound.severity
- if (wound_type == WOUND_SLASH || wound_type == WOUND_PIERCE)
+ var/is_laceration = pregen_data.wounding_types_valid(list(WOUND_SLASH, WOUND_PIERCE))
+ var/is_fracture = pregen_data.wounding_types_valid(list(WOUND_BLUNT))
+
+ if (is_laceration)
if (wound_severity >= WOUND_SEVERITY_SEVERE)
sound_to_play = major_lacerations_sound
else
sound_to_play = minor_lacerations_sound
- else if (wound_type == WOUND_BLUNT || wound_series == WOUND_SERIES_MUSCLE_DAMAGE)
+ else if (is_fracture)
if (wound_severity >= WOUND_SEVERITY_SEVERE)
sound_to_play = major_fracture_sound
else
diff --git a/modular_skyrat/modules/horrorform/code/true_changeling.dm b/modular_skyrat/modules/horrorform/code/true_changeling.dm
index bc51c730b7df63..4765f9f9df6ebb 100644
--- a/modular_skyrat/modules/horrorform/code/true_changeling.dm
+++ b/modular_skyrat/modules/horrorform/code/true_changeling.dm
@@ -32,7 +32,6 @@
attack_verb_continuous = "rips into"
attack_verb_simple = "rip into"
attack_sound = 'sound/effects/blobattack.ogg'
- next_move_modifier = 0.5 //Faster attacks
butcher_results = list(/obj/item/food/meat/slab/human = 15) //It's a pretty big dude. Actually killing one is a feat.
gold_core_spawnable = FALSE //Should stay exclusive to changelings tbh, otherwise makes it much less significant to sight one
var/datum/action/innate/turn_to_human
diff --git a/modular_skyrat/modules/hydra/code/neutral.dm b/modular_skyrat/modules/hydra/code/neutral.dm
index e87de1a4bf25bc..aea55dcc14736e 100644
--- a/modular_skyrat/modules/hydra/code/neutral.dm
+++ b/modular_skyrat/modules/hydra/code/neutral.dm
@@ -16,8 +16,6 @@
spell.owner = hydra
resetspell.Grant(hydra)
resetspell.owner = hydra
- hydra.name_archive = hydra.real_name
-
/datum/action/innate/hydra
name = "Switch head"
@@ -33,12 +31,16 @@
/datum/action/innate/hydrareset/Activate()
var/mob/living/carbon/human/hydra = owner
+ if(!hydra.name_archive) // sets the archived 'real' name if not set.
+ hydra.name_archive = hydra.real_name
hydra.real_name = hydra.name_archive
hydra.visible_message(span_notice("[hydra.name] pushes all three heads forwards; they seem to be talking as a collective."), \
span_notice("You are now talking as [hydra.name_archive]!"), ignored_mobs=owner)
/datum/action/innate/hydra/Activate() //Oops, all hydra!
var/mob/living/carbon/human/hydra = owner
+ if(!hydra.name_archive) // sets the archived 'real' name if not set.
+ hydra.name_archive = hydra.real_name
var/list/names = splittext(hydra.name_archive,"-")
var/selhead = input("Who would you like to speak as?","Heads:") in names
hydra.real_name = selhead
diff --git a/modular_skyrat/modules/ices_events/code/ICES_event_config.dm b/modular_skyrat/modules/ices_events/code/ICES_event_config.dm
index 7f4e2bfde6797f..bd49fc1de1753e 100644
--- a/modular_skyrat/modules/ices_events/code/ICES_event_config.dm
+++ b/modular_skyrat/modules/ices_events/code/ICES_event_config.dm
@@ -576,7 +576,7 @@
* Supermatter Surge
*/
/datum/round_event_control/supermatter_surge
- max_occurrences = 2
+ max_occurrences = 1
weight = MED_EVENT_FREQ
/**
diff --git a/modular_skyrat/modules/ices_events/code/events/ev_roleplay_check.dm b/modular_skyrat/modules/ices_events/code/events/ev_roleplay_check.dm
new file mode 100644
index 00000000000000..d55fd75aeb3967
--- /dev/null
+++ b/modular_skyrat/modules/ices_events/code/events/ev_roleplay_check.dm
@@ -0,0 +1,13 @@
+/**
+ * Checks if a player meets certain conditions to exclude them from event selection.
+ */
+/proc/engaged_role_play_check(mob/living/carbon/human/player, station = TRUE, dorms = TRUE)
+ var/turf/player_turf = get_turf(player)
+ var/area/player_area = get_area(player_turf)
+ if(!is_station_level(player_turf.z) && station)
+ return TRUE
+
+ if(istype(player_area, /area/station/commons/dorms) && dorms)
+ return TRUE
+
+ return FALSE
diff --git a/modular_skyrat/modules/interaction_menu/code/interaction_component.dm b/modular_skyrat/modules/interaction_menu/code/interaction_component.dm
index fe3b6fea50d507..b6122decd070ee 100644
--- a/modular_skyrat/modules/interaction_menu/code/interaction_component.dm
+++ b/modular_skyrat/modules/interaction_menu/code/interaction_component.dm
@@ -77,6 +77,7 @@
var/list/data = list()
var/list/descriptions = list()
var/list/categories = list()
+ var/list/display_categories = list()
var/list/colors = list()
for(var/datum/interaction/interaction in interactions)
if(!can_interact(interaction, user))
@@ -85,13 +86,15 @@
categories[interaction.category] = list(interaction.name)
else
categories[interaction.category] += interaction.name
+ var/list/sorted_category = sort_list(categories[interaction.category])
+ categories[interaction.category] = sorted_category
descriptions[interaction.name] = interaction.description
colors[interaction.name] = interaction.color
- data["categories"] = list()
data["descriptions"] = descriptions
data["colors"] = colors
for(var/category in categories)
- data["categories"] += category
+ display_categories += category
+ data["categories"] = sort_list(display_categories)
data["ref_user"] = REF(user)
data["ref_self"] = REF(self)
data["self"] = self.name
diff --git a/modular_skyrat/modules/layer_shift/code/mob_movement.dm b/modular_skyrat/modules/layer_shift/code/mob_movement.dm
index 5725a349e52e08..2bc2ed4cdee594 100644
--- a/modular_skyrat/modules/layer_shift/code/mob_movement.dm
+++ b/modular_skyrat/modules/layer_shift/code/mob_movement.dm
@@ -1,7 +1,10 @@
-#define MOB_LAYER_SHIFT_INCREMENT 0.01
-#define MOB_LAYER_SHIFT_MIN 3.95
-//#define MOB_LAYER 4 // This is a byond standard define
-#define MOB_LAYER_SHIFT_MAX 4.05
+#define MOB_LAYER_SHIFT_INCREMENT 1
+/// The amount by which layers are multiplied before being modified.
+/// Helps avoiding floating point errors.
+#define MOB_LAYER_MULTIPLIER 100
+#define MOB_LAYER_SHIFT_MIN 3.95
+//#define MOB_LAYER 4 // This is a byond standard define
+#define MOB_LAYER_SHIFT_MAX 4.05
/mob/living/verb/layershift_up()
set name = "Shift Layer Upwards"
@@ -15,8 +18,8 @@
to_chat(src, span_warning("You cannot increase your layer priority any further."))
return
- layer += MOB_LAYER_SHIFT_INCREMENT
- var/layer_priority = (layer - MOB_LAYER) * 100 // Just for text feedback
+ layer = min(((layer * MOB_LAYER_MULTIPLIER) + MOB_LAYER_SHIFT_INCREMENT) / MOB_LAYER_MULTIPLIER, MOB_LAYER_SHIFT_MAX)
+ var/layer_priority = round(layer * MOB_LAYER_MULTIPLIER - MOB_LAYER * MOB_LAYER_MULTIPLIER, MOB_LAYER_SHIFT_INCREMENT) // Just for text feedback
to_chat(src, span_notice("Your layer priority is now [layer_priority]."))
/mob/living/verb/layershift_down()
@@ -31,6 +34,6 @@
to_chat(src, span_warning("You cannot decrease your layer priority any further."))
return
- layer -= MOB_LAYER_SHIFT_INCREMENT
- var/layer_priority = (layer - MOB_LAYER) * 100 // Just for text feedback
+ layer = max(((layer * MOB_LAYER_MULTIPLIER) - MOB_LAYER_SHIFT_INCREMENT) / MOB_LAYER_MULTIPLIER, MOB_LAYER_SHIFT_MIN)
+ var/layer_priority = round(layer * MOB_LAYER_MULTIPLIER - MOB_LAYER * MOB_LAYER_MULTIPLIER, MOB_LAYER_SHIFT_INCREMENT) // Just for text feedback
to_chat(src, span_notice("Your layer priority is now [layer_priority]."))
diff --git a/modular_skyrat/modules/liquids/code/ocean_turfs.dm b/modular_skyrat/modules/liquids/code/ocean_turfs.dm
index dc25e69d7dd48a..35066f098574b3 100644
--- a/modular_skyrat/modules/liquids/code/ocean_turfs.dm
+++ b/modular_skyrat/modules/liquids/code/ocean_turfs.dm
@@ -9,7 +9,7 @@
for(var/obj/structure/flora/plant in contents)
qdel(plant)
- var/turf/T = below()
+ var/turf/T = GET_TURF_BELOW(src)
if(T)
if(T.turf_flags & NO_RUINS)
ChangeTurf(replacement_turf, null, CHANGETURF_IGNORE_AIR)
diff --git a/modular_skyrat/modules/loadouts/loadout_items/loadout_datum_suit.dm b/modular_skyrat/modules/loadouts/loadout_items/loadout_datum_suit.dm
index 8f788249efc2be..27bad2580397ab 100644
--- a/modular_skyrat/modules/loadouts/loadout_items/loadout_datum_suit.dm
+++ b/modular_skyrat/modules/loadouts/loadout_items/loadout_datum_suit.dm
@@ -640,11 +640,6 @@ GLOBAL_LIST_INIT(loadout_exosuits, generate_loadout_items(/datum/loadout_item/su
item_path = /obj/item/clothing/suit/toggle/jacket/supply
restricted_roles = list(JOB_QUARTERMASTER, JOB_CARGO_TECHNICIAN, JOB_SHAFT_MINER, JOB_CUSTOMS_AGENT)
-/datum/loadout_item/suit/supply_gorka_jacket
- name = "Supply Gorka Jacket"
- item_path = /obj/item/clothing/suit/gorka/supply
- restricted_roles = list(JOB_QUARTERMASTER, JOB_CARGO_TECHNICIAN, JOB_SHAFT_MINER, JOB_CUSTOMS_AGENT)
-
/datum/loadout_item/suit/cargo_gorka_jacket
name = "Cargo Gorka Jacket"
item_path = /obj/item/clothing/suit/toggle/cargo_tech
diff --git a/modular_skyrat/modules/loadouts/loadout_items/under/loadout_datum_under.dm b/modular_skyrat/modules/loadouts/loadout_items/under/loadout_datum_under.dm
index 21ef8c9a2906ad..ad748c17e57d5a 100644
--- a/modular_skyrat/modules/loadouts/loadout_items/under/loadout_datum_under.dm
+++ b/modular_skyrat/modules/loadouts/loadout_items/under/loadout_datum_under.dm
@@ -672,10 +672,6 @@ GLOBAL_LIST_INIT(loadout_miscunders, generate_loadout_items(/datum/loadout_item/
name = "Black Suitskirt"
item_path = /obj/item/clothing/under/suit/black/skirt
-/datum/loadout_item/under/formal/black_twopiece
- name = "Black Two-Piece Suit"
- item_path = /obj/item/clothing/under/suit/blacktwopiece
-
/datum/loadout_item/under/formal/black_lawyer_suit
name = "Black Lawyer Suit"
item_path = /obj/item/clothing/under/rank/civilian/lawyer/black
diff --git a/modular_skyrat/modules/mapping/code/areas/station.dm b/modular_skyrat/modules/mapping/code/areas/station.dm
index 8a310ece882613..8c24b2dcbec2e7 100644
--- a/modular_skyrat/modules/mapping/code/areas/station.dm
+++ b/modular_skyrat/modules/mapping/code/areas/station.dm
@@ -26,13 +26,13 @@
icon_state = "secure_bunker"
// NT Consultant area
-/area/command/heads_quarters/captain/private/nt_rep
+/area/station/command/heads_quarters/nt_rep
name = "Nanotrasen Consultant's Office"
icon = 'modular_skyrat/modules/mapping/icons/areas/areas_station.dmi'
icon_state = "nt_rep"
-// BlueShield area
-/area/blueshield
+// Blueshield area
+/area/station/command/heads_quarters/blueshield
name = "Blueshield's Office"
icon = 'modular_skyrat/modules/mapping/icons/areas/areas_station.dmi'
icon_state = "blueshield"
diff --git a/modular_skyrat/modules/mapping/code/interdyne_mining.dm b/modular_skyrat/modules/mapping/code/interdyne_mining.dm
index 522b5cabe5bb73..860d17d104bde1 100644
--- a/modular_skyrat/modules/mapping/code/interdyne_mining.dm
+++ b/modular_skyrat/modules/mapping/code/interdyne_mining.dm
@@ -1,6 +1,6 @@
/obj/item/circuitboard/computer/order_console/mining/interdyne
name = "Interdyne Mining Equipment Vendor Console"
- build_path = /obj/machinery/computer/order_console/mining/golem
+ build_path = /obj/machinery/computer/order_console/mining/interdyne
// Interdyne/DS-2 mining equipment vendor that doesn't need a cargo shuttle to work
diff --git a/modular_skyrat/modules/mapping/code/lavaland_ruin_code.dm b/modular_skyrat/modules/mapping/code/lavaland_ruin_code.dm
index 24b1c9abd96d71..c66faed0810c45 100644
--- a/modular_skyrat/modules/mapping/code/lavaland_ruin_code.dm
+++ b/modular_skyrat/modules/mapping/code/lavaland_ruin_code.dm
@@ -23,6 +23,7 @@
/datum/outfit/lavaland_syndicate
name = "Interdyne Bioweapon Scientist"
uniform = /obj/item/clothing/under/rank/rnd/scientist/skyrat/utility/syndicate
+ suit = /obj/item/clothing/suit/toggle/labcoat/interdyne
ears = /obj/item/radio/headset/interdyne
/datum/outfit/lavaland_syndicate/post_equip(mob/living/carbon/human/syndicate, visualsOnly = FALSE)
diff --git a/modular_skyrat/modules/mapping/code/wardrobes.dm b/modular_skyrat/modules/mapping/code/wardrobes.dm
index 81a1d86f681c42..bfb7e2621d3757 100644
--- a/modular_skyrat/modules/mapping/code/wardrobes.dm
+++ b/modular_skyrat/modules/mapping/code/wardrobes.dm
@@ -37,12 +37,17 @@
/obj/item/clothing/under/syndicate/skyrat/maid = 5,
/obj/item/clothing/gloves/combat/maid = 5,
/obj/item/clothing/head/costume/maidheadband/syndicate = 5,
- /obj/item/storage/box/nif_ghost_box = 10,
+ /obj/item/storage/box/nif_ghost_box/ghost_role = 10,
)
refill_canister = /obj/item/vending_refill/wardrobe/syndie_wardrobe
light_color = COLOR_MOSTLY_PURE_RED
+/obj/machinery/vending/wardrobe/syndie_wardrobe/ghost_cafe
+ excluded_products = list(
+ /obj/item/storage/box/nif_ghost_box/ghost_role,
+ )
+
/obj/item/vending_refill/wardrobe/syndie_wardrobe
machine_name = "SynDrobe"
diff --git a/modular_skyrat/modules/marines/code/smartgun.dm b/modular_skyrat/modules/marines/code/smartgun.dm
index a31cf1a6bc0292..1927413f51b018 100644
--- a/modular_skyrat/modules/marines/code/smartgun.dm
+++ b/modular_skyrat/modules/marines/code/smartgun.dm
@@ -118,7 +118,6 @@
/obj/item/ammo_casing/smart
firing_effect_type = null
- is_cased_ammo = FALSE
/obj/item/ammo_casing/smart/Initialize(mapload)
. = ..()
diff --git a/modular_skyrat/modules/medical/code/grasp.dm b/modular_skyrat/modules/medical/code/grasp.dm
index 8fbc3c407bed23..808732c5572aa8 100644
--- a/modular_skyrat/modules/medical/code/grasp.dm
+++ b/modular_skyrat/modules/medical/code/grasp.dm
@@ -1,5 +1,5 @@
/mob/living/carbon/proc/self_grasp_bleeding_limb(obj/item/bodypart/grasped_part, supress_message = FALSE)
- if(!grasped_part?.get_modified_bleed_rate())
+ if(!grasped_part?.can_be_grasped())
return
var/starting_hand_index = active_hand_index
if(starting_hand_index == grasped_part.held_index)
diff --git a/modular_skyrat/modules/medical/code/health_analyzer.dm b/modular_skyrat/modules/medical/code/health_analyzer.dm
new file mode 100644
index 00000000000000..1d7eba6198f3e7
--- /dev/null
+++ b/modular_skyrat/modules/medical/code/health_analyzer.dm
@@ -0,0 +1,11 @@
+/// If TRUE, this analyzer can be used for medibot construction. If FALSE, it cannot. Returns TRUE by default.
+/obj/item/healthanalyzer/proc/can_be_used_in_medibot()
+ return TRUE
+
+/obj/item/healthanalyzer/no_medibot
+ name = "surplus health analyzer"
+ desc = "A hand-held body scanner capable of distinguishing vital signs of the subject. Has a side button to scan for chemicals, and can be toggled to scan wounds. \
+ This one seems to lack the mounting braces usually found on medibot-compatable analyzers..."
+
+/obj/item/healthanalyzer/no_medibot/can_be_used_in_medibot()
+ return FALSE
diff --git a/modular_skyrat/modules/medical/code/wounds/muscle.dm b/modular_skyrat/modules/medical/code/wounds/muscle.dm
index 7c6b0e11b3b42a..c1f9c3fc4c3515 100644
--- a/modular_skyrat/modules/medical/code/wounds/muscle.dm
+++ b/modular_skyrat/modules/medical/code/wounds/muscle.dm
@@ -6,66 +6,33 @@
/datum/wound/muscle
name = "Muscle Wound"
sound_effect = 'sound/effects/wounds/blood1.ogg'
- wound_type = WOUND_BLUNT
wound_flags = (ACCEPTS_GAUZE | SPLINT_OVERLAY)
- wound_series = WOUND_SERIES_MUSCLE_DAMAGE
-
processes = TRUE
/// How much do we need to regen. Will regen faster if we're splinted and or laying down
var/regen_ticks_needed
/// Our current counter for healing
var/regen_ticks_current = 0
+ can_scar = FALSE
+
/datum/wound_pregen_data/muscle
abstract = TRUE
viable_zones = list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_LEG)
required_limb_biostate = BIO_FLESH
-/datum/wound_pregen_data/muscle/can_be_applied_to(obj/item/bodypart/limb, wound_type, datum/wound/old_wound, random_roll)
- if (!istype(limb) || !limb.owner)
- return FALSE
-
- if (random_roll && !can_be_randomly_generated)
- return FALSE
-
- if (HAS_TRAIT(limb.owner, TRAIT_NEVER_WOUNDED) || (limb.owner.status_flags & GODMODE))
- return FALSE
- // THIS IS HIGHLY TEMPORARY A PROC WILL COME FROM UPSTREAM THAT SHOULD REPLACE THIS!! REPLACE IT!!!!!!!!!!!! REMOVE THE OVERRIDE 8/31/23 ~Niko
- if (wound_type != (WOUND_BLUNT) && wound_type != (WOUND_SLASH) && wound_type != (WOUND_PIERCE))
- return FALSE
- else
- for (var/datum/wound/preexisting_wound as anything in limb.wounds)
- if (preexisting_wound.wound_series == initial(wound_path_to_generate.wound_series))
- if (preexisting_wound.severity >= initial(wound_path_to_generate.severity))
- return FALSE
+ required_wounding_types = list(WOUND_BLUNT, WOUND_SLASH, WOUND_PIERCE)
+ match_all_wounding_types = FALSE
- if (!ignore_cannot_bleed && ((required_limb_biostate & BIO_BLOODED) && !limb.can_bleed()))
- return FALSE
-
- if (!biostate_valid(limb.biological_state))
- return FALSE
-
- if (!(limb.body_zone in viable_zones))
- return FALSE
+ wound_series = WOUND_SERIES_MUSCLE_DAMAGE
- // we accept promotions and demotions, but no point in redundancy. This should have already been checked wherever the wound was rolled and applied for (see: bodypart damage code), but we do an extra check
- // in case we ever directly add wounds
- if (!duplicates_allowed)
- for (var/datum/wound/preexisting_wound as anything in limb.wounds)
- if (preexisting_wound.type == wound_path_to_generate && (preexisting_wound != old_wound))
- return FALSE
- return TRUE
+ weight = 3 // very low chance to replace a normal wound. this is about 4.5%
/*
Overwriting of base procs
*/
/datum/wound/muscle/wound_injury(datum/wound/old_wound = null, attack_direction)
- // hook into gaining/losing gauze so crit muscle wounds can re-enable/disable depending if they're slung or not
- RegisterSignals(limb, list(COMSIG_BODYPART_SPLINTED, COMSIG_BODYPART_SPLINT_DESTROYED), PROC_REF(update_inefficiencies))
-
- RegisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand))
if(limb.held_index && victim.get_item_for_held_index(limb.held_index) && (disabling || prob(30 * severity)))
var/obj/item/I = victim.get_item_for_held_index(limb.held_index)
if(istype(I, /obj/item/offhand))
@@ -74,14 +41,19 @@
if(I && victim.dropItemToGround(I))
victim.visible_message(span_danger("[victim] drops [I] in shock!"), span_warning("The force on your [parse_zone(limb.body_zone)] causes you to drop [I]!"), vision_distance=COMBAT_MESSAGE_RANGE)
- update_inefficiencies()
+ return ..()
+
+/datum/wound/muscle/set_victim(new_victim)
+ if (victim)
+ UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
+
+ if (new_victim)
+ RegisterSignal(new_victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK, PROC_REF(attack_with_hurt_hand))
+
+ return ..()
/datum/wound/muscle/remove_wound(ignore_limb, replaced)
limp_slowdown = 0
- if(limb)
- UnregisterSignal(limb, list(COMSIG_BODYPART_GAUZED, COMSIG_BODYPART_GAUZE_DESTROYED))
- if(victim)
- UnregisterSignal(victim, COMSIG_HUMAN_EARLY_UNARMED_ATTACK)
return ..()
/datum/wound/muscle/handle_process()
@@ -149,43 +121,17 @@
return "[msg.Join()]"
-/*
- Common procs mostly copied from bone wounds, as their behaviour is very similar
-*/
-
-/datum/wound/muscle/proc/update_inefficiencies()
- SIGNAL_HANDLER
- if(limb.body_zone in list(BODY_ZONE_L_LEG, BODY_ZONE_R_LEG))
- if(limb.current_gauze)
- limp_slowdown = initial(limp_slowdown) * limb.current_gauze.splint_factor
- else
- limp_slowdown = initial(limp_slowdown)
- victim.apply_status_effect(/datum/status_effect/limp)
- else if(limb.body_zone in list(BODY_ZONE_L_ARM, BODY_ZONE_R_ARM))
- if(limb.current_gauze)
- interaction_efficiency_penalty = 1 + ((interaction_efficiency_penalty - 1) * limb.current_gauze.splint_factor)
- else
- interaction_efficiency_penalty = interaction_efficiency_penalty
-
- if(initial(disabling))
- if(limb.current_gauze)
- set_disabling(FALSE)
- else
- set_disabling(TRUE)
-
- limb.update_wounds()
-
/// Moderate (Muscle Tear)
/datum/wound/muscle/moderate
name = "Muscle Tear"
desc = "Patient's muscle has torn, causing serious pain and reduced limb functionality."
- treat_text = "Recommended rest and sleep, or splinting the limb."
+ treat_text = "A tight splint on the affected limb, as well as plenty of rest and sleep."
examine_desc = "appears unnaturallly red and swollen"
occur_text = "swells up, it's skin turning red"
severity = WOUND_SEVERITY_MODERATE
interaction_efficiency_penalty = 1.5
limp_slowdown = 2
- threshold_minimum = 35
+ limp_chance = 30
threshold_penalty = 15
status_effect_type = /datum/status_effect/wound/muscle/moderate
regen_ticks_needed = 90
@@ -194,6 +140,7 @@
abstract = FALSE
wound_path_to_generate = /datum/wound/muscle/moderate
+ threshold_minimum = 35
/*
Severe (Ruptured Tendon)
@@ -203,13 +150,13 @@
name = "Ruptured Tendon"
sound_effect = 'sound/effects/wounds/blood2.ogg'
desc = "Patient's tendon has been severed, causing significant pain and near uselessness of limb."
- treat_text = "Recommended rest and sleep aswell as splinting the limb."
+ treat_text = "A tight splint on the affected limb, as well as plenty of rest and sleep."
examine_desc = "is limp and awkwardly twitching, skin swollen and red"
occur_text = "twists in pain and goes limp, it's tendon ruptured"
severity = WOUND_SEVERITY_SEVERE
interaction_efficiency_penalty = 2
limp_slowdown = 5
- threshold_minimum = 80
+ limp_chance = 40
threshold_penalty = 35
disabling = TRUE
status_effect_type = /datum/status_effect/wound/muscle/severe
@@ -219,42 +166,10 @@
abstract = FALSE
wound_path_to_generate = /datum/wound/muscle/severe
-
-/datum/status_effect/wound/muscle
-
-/datum/status_effect/wound/muscle/on_apply()
- . = ..()
- RegisterSignal(owner, COMSIG_MOB_SWAP_HANDS, PROC_REF(on_swap_hands))
- on_swap_hands()
-
-/datum/status_effect/wound/muscle/on_remove()
- . = ..()
- UnregisterSignal(owner, COMSIG_MOB_SWAP_HANDS)
- var/mob/living/carbon/wound_owner = owner
- wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/muscle_wound)
-
-/datum/status_effect/wound/muscle/proc/on_swap_hands()
- SIGNAL_HANDLER
-
- var/mob/living/carbon/wound_owner = owner
- if(wound_owner.get_active_hand() == linked_limb)
- wound_owner.add_actionspeed_modifier(/datum/actionspeed_modifier/muscle_wound, (linked_wound.interaction_efficiency_penalty - 1))
- else
- wound_owner.remove_actionspeed_modifier(/datum/actionspeed_modifier/muscle_wound)
-
-/datum/status_effect/wound/muscle/nextmove_modifier()
- var/mob/living/carbon/C = owner
-
- if(C.get_active_hand() == linked_limb)
- return linked_wound.interaction_efficiency_penalty
-
- return 1
+ threshold_minimum = 80
// muscle
/datum/status_effect/wound/muscle/moderate
id = "torn muscle"
/datum/status_effect/wound/muscle/severe
id = "ruptured tendon"
-
-/datum/actionspeed_modifier/muscle_wound
- variable = TRUE
diff --git a/modular_skyrat/modules/mentor/code/client_procs.dm b/modular_skyrat/modules/mentor/code/client_procs.dm
index af6ecae38e4a6d..8c83b8c615ef8d 100644
--- a/modular_skyrat/modules/mentor/code/client_procs.dm
+++ b/modular_skyrat/modules/mentor/code/client_procs.dm
@@ -44,6 +44,13 @@
cmd_mentor_dementor()
-/client/proc/is_mentor() // admins are mentors too.
- if(mentor_datum || check_rights_for(src, R_ADMIN))
+/**
+ * Returns whether or not the user is qualified as a mentor.
+ *
+ * Arguments:
+ * * admin_bypass - Whether or not admins can succeed this check, even if they
+ * do not actually possess the role. Defaults to `TRUE`.
+ */
+/client/proc/is_mentor(admin_bypass = TRUE)
+ if(mentor_datum || (admin_bypass && check_rights_for(src, R_ADMIN)))
return TRUE
diff --git a/modular_skyrat/modules/microfusion/code/projectiles.dm b/modular_skyrat/modules/microfusion/code/projectiles.dm
index c1f0a5f908ea99..6e90ebfda46888 100644
--- a/modular_skyrat/modules/microfusion/code/projectiles.dm
+++ b/modular_skyrat/modules/microfusion/code/projectiles.dm
@@ -16,7 +16,7 @@
/obj/projectile/beam/laser/microfusion
name = "microfusion laser"
icon = 'modular_skyrat/modules/microfusion/icons/projectiles.dmi'
- damage = 20
+ damage = 25
/obj/projectile/beam/microfusion_disabler
name = "microfusion disabler laser"
@@ -36,7 +36,7 @@
/obj/projectile/beam/laser/microfusion/superheated
name = "superheated microfusion laser"
icon_state = "laser_greyscale"
- damage = 15 //Trading damage for fire stacks
+ damage = 20 //Trading damage for fire stacks
color = LIGHT_COLOR_FIRE
light_color = LIGHT_COLOR_FIRE
@@ -51,7 +51,7 @@
name = "hellfire microfusion laser"
icon_state = "laser_greyscale"
wound_bonus = 0
- damage = 15 // You are trading damage for a significant wound bonus and speed increase
+ damage = 20 // You are trading damage for a significant wound bonus and speed increase
speed = 0.6
color = LIGHT_COLOR_FLARE
light_color = LIGHT_COLOR_FLARE
@@ -63,16 +63,16 @@
name = "scatter microfusion laser"
/obj/projectile/beam/laser/microfusion/repeater
- damage = 10
+ damage = 12.5
/obj/projectile/beam/laser/microfusion/penetrator
name = "focused microfusion laser"
- damage = 15
+ damage = 20
armour_penetration = 50
/obj/projectile/beam/laser/microfusion/lance
name = "lance microfusion laser"
- damage = 40 // We're turning the gun into a heavylaser
+ damage = 50 // We're turning the gun into a heavylaser
tracer_type = /obj/effect/projectile/tracer/heavy_laser
muzzle_type = /obj/effect/projectile/muzzle/heavy_laser
impact_type = /obj/effect/projectile/impact/heavy_laser
@@ -91,7 +91,7 @@
color = COLOR_VIVID_YELLOW
light_color = COLOR_VIVID_YELLOW
damage_type = STAMINA
- damage = 20
+ damage = 25
armor_flag = ENERGY
hitsound = 'sound/misc/slip.ogg'
impact_type = /obj/effect/projectile/impact/disabler
diff --git a/modular_skyrat/modules/modular_implants/code/nifs.dm b/modular_skyrat/modules/modular_implants/code/nifs.dm
index 18944df55f70b5..69c0a581c3594c 100644
--- a/modular_skyrat/modules/modular_implants/code/nifs.dm
+++ b/modular_skyrat/modules/modular_implants/code/nifs.dm
@@ -497,13 +497,16 @@
/obj/item/storage/box/nif_ghost_box/PopulateContents()
new /obj/item/autosurgeon/organ/nif/ghost_role(src)
- new /obj/item/disk/nifsoft_uploader/hivemind(src)
new /obj/item/disk/nifsoft_uploader/shapeshifter(src)
new /obj/item/disk/nifsoft_uploader/summoner(src)
new /obj/item/disk/nifsoft_uploader/money_sense(src)
new /obj/item/disk/nifsoft_uploader/dorms(src)
new /obj/item/disk/nifsoft_uploader/soulcatcher(src)
+/obj/item/storage/box/nif_ghost_box/ghost_role/PopulateContents()
+ . = ..()
+ new /obj/item/disk/nifsoft_uploader/hivemind(src)
+
#undef NIF_CALIBRATION_STAGE_1
#undef NIF_CALIBRATION_STAGE_1_END
#undef NIF_CALIBRATION_STAGE_2
diff --git a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm
index ce6aa14dfb2290..c861b924d6f3f6 100644
--- a/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm
+++ b/modular_skyrat/modules/modular_items/lewd_items/code/lewd_helpers/human.dm
@@ -209,6 +209,21 @@
else
return TRUE
+/// Returns true if the human has accessible tail for the parameter. Accepts any of the `REQUIRE_GENITAL_` defines.
+/mob/living/carbon/human/proc/has_tail(required_state = REQUIRE_GENITAL_ANY)
+ var/obj/item/organ/genital = get_organ_slot(ORGAN_SLOT_TAIL)
+ if(!genital)
+ return FALSE
+
+ switch(required_state)
+ if(REQUIRE_GENITAL_ANY)
+ return TRUE
+ if(REQUIRE_GENITAL_EXPOSED)
+ return !get_item_by_slot(ORGAN_SLOT_TAIL)
+ if(REQUIRE_GENITAL_UNEXPOSED)
+ return get_item_by_slot(ORGAN_SLOT_TAIL)
+ else
+ return TRUE
/*
* This code needed for changing character's gender by chems
@@ -363,3 +378,7 @@
if(wear_suit && istype(wear_suit, /obj/item/clothing/suit/straight_jacket/kinky_sleepbag))
return FALSE
..()
+
+/// Checks if the tail is exposed.
+/obj/item/organ/external/tail/proc/is_exposed()
+ return TRUE // your tail is always exposed, dummy! why are you checking this
diff --git a/modular_skyrat/modules/modular_vending/code/wardrobes.dm b/modular_skyrat/modules/modular_vending/code/wardrobes.dm
index 8cf3fb0f6da247..e5f3e0210e7c9d 100644
--- a/modular_skyrat/modules/modular_vending/code/wardrobes.dm
+++ b/modular_skyrat/modules/modular_vending/code/wardrobes.dm
@@ -43,7 +43,6 @@
/obj/item/clothing/under/rank/cargo/tech/skyrat/turtleneck/skirt = 3,
/obj/item/clothing/under/rank/cargo/tech/skyrat/utility = 3,
/obj/item/clothing/under/rank/cargo/tech/skyrat/casualman = 3,
- /obj/item/clothing/suit/gorka/supply = 3,
/obj/item/clothing/suit/toggle/jacket/supply = 3,
/obj/item/clothing/glasses/hud/gun_permit = 5, //from company imports module
/obj/item/storage/backpack/messenger = 3,
@@ -62,6 +61,9 @@
/obj/item/clothing/mask/breath = 2,
/obj/item/reagent_containers/cup/bottle/morphine = 2,
/obj/item/reagent_containers/syringe = 2,
+ /obj/item/bonesetter = 2, // for dislocations
+ /obj/item/stack/medical/gauze = 4, // for ALL wounds
+ /obj/item/healthanalyzer/no_medibot = 2, // disallows medibot use so its not wasted immediately on medibots
/obj/item/storage/backpack/science/robo = 2,
/obj/item/storage/backpack/satchel/science/robo = 2,
/obj/item/storage/backpack/duffelbag/science/robo = 2,
diff --git a/modular_skyrat/modules/modular_weapons/code/energy.dm b/modular_skyrat/modules/modular_weapons/code/energy.dm
index 247a9a82ba5e86..550af2c130eff8 100644
--- a/modular_skyrat/modules/modular_weapons/code/energy.dm
+++ b/modular_skyrat/modules/modular_weapons/code/energy.dm
@@ -219,7 +219,7 @@
icon = 'modular_skyrat/modules/modular_weapons/icons/obj/ammo.dmi'
icon_state = "plasma_shell"
worn_icon_state = "shell"
- caliber = "Beam Shell"
+ caliber = CALIBER_LASER
custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT * 2,/datum/material/plasma=HALF_SHEET_MATERIAL_AMOUNT)
projectile_type = /obj/projectile/beam/laser/single
@@ -228,7 +228,6 @@
desc = "A chemical mixture that once triggered, creates a deadly projectile, melting it's own casing in the process."
icon_state = "plasma_shell2"
worn_icon_state = "shell"
- caliber = "Beam Shell"
custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT * 2,/datum/material/plasma=HALF_SHEET_MATERIAL_AMOUNT)
projectile_type = /obj/projectile/beam/laser/double
@@ -237,7 +236,6 @@
desc = "A chemical mixture that once triggered, creates a deadly bouncing projectile, melting it's own casing in the process."
icon_state = "bounce_shell"
worn_icon_state = "shell"
- caliber = "Beam Shell"
custom_materials = list(/datum/material/iron=SHEET_MATERIAL_AMOUNT * 2,/datum/material/plasma=HALF_SHEET_MATERIAL_AMOUNT)
projectile_type = /obj/projectile/beam/laser/bounce
diff --git a/modular_skyrat/modules/modular_weapons/code/melee.dm b/modular_skyrat/modules/modular_weapons/code/melee.dm
index e2b633a9c1eacb..8165a798a6fd79 100644
--- a/modular_skyrat/modules/modular_weapons/code/melee.dm
+++ b/modular_skyrat/modules/modular_weapons/code/melee.dm
@@ -1,4 +1,5 @@
-// Cargo Sabres
+// Sabres, including the cargo variety
+
/obj/item/storage/belt/sabre/cargo
name = "authentic shamshir leather sheath"
desc = "A good-looking sheath that is advertised as being made of real Venusian black leather. It feels rather plastic-like to the touch, and it looks like it's made to fit a British cavalry sabre."
@@ -9,6 +10,11 @@
new /obj/item/melee/sabre/cargo(src)
update_appearance()
+/obj/item/melee/sabre
+ force = 20 // Original: 15
+ wound_bonus = 5 // Original: 10
+ bare_wound_bonus = 20 // Original: 25 Both down slightly, to make up for the damage buff, since it'd get a bit wacky ontop of the armor pen.
+
/obj/item/melee/sabre/cargo
name = "authentic shamshir sabre"
desc = "An expertly crafted historical human sword once used by the Persians which has recently gained traction due to Venusian historal recreation sports. One small flaw, the Taj-based company who produces these has mistaken them for British cavalry sabres akin to those used by high ranking Nanotrasen officials. Atleast it cuts the same way!"
@@ -18,6 +24,7 @@
block_chance = 20
armour_penetration = 25
+
// This is here so that people can't buy the Sabres and craft them into powercrepes
/datum/crafting_recipe/food/powercrepe
blacklist = list(/obj/item/melee/sabre/cargo)
diff --git a/modular_skyrat/modules/mold/code/mold_disease.dm b/modular_skyrat/modules/mold/code/mold_disease.dm
index 4e008aa3a54930..8778e469becbd0 100644
--- a/modular_skyrat/modules/mold/code/mold_disease.dm
+++ b/modular_skyrat/modules/mold/code/mold_disease.dm
@@ -63,7 +63,7 @@
to_chat(affected_mob, span_danger("[pick("You feel uncomfortably hot...", "You feel like unzipping your jumpsuit", "You feel like taking off some clothes...")]"))
affected_mob.adjust_bodytemperature(30)
if(SPT_PROB(5, seconds_per_tick))
- affected_mob.vomit(20)
+ affected_mob.vomit(vomit_flags = VOMIT_CATEGORY_DEFAULT, lost_nutrition = 20)
/datum/reagent/cryptococcus_spores
name = "Cryptococcus gattii microbes"
diff --git a/modular_skyrat/modules/morenarcotics/code/cocaine_item.dm b/modular_skyrat/modules/morenarcotics/code/cocaine_item.dm
index 4673264c92db24..2a4da011635738 100644
--- a/modular_skyrat/modules/morenarcotics/code/cocaine_item.dm
+++ b/modular_skyrat/modules/morenarcotics/code/cocaine_item.dm
@@ -52,7 +52,7 @@
if(covered)
to_chat(user, span_warning("You have to remove your [covered] first!"))
return
- user.visible_message(span_notice("'[user] starts snorting the [src]."))
+ user.visible_message(span_notice("[user] starts snorting the [src]."))
if(do_after(user, 30))
to_chat(user, span_notice("You finish snorting the [src]."))
if(reagents.total_volume)
diff --git a/modular_skyrat/modules/moretraitoritems/code/mafioso.dm b/modular_skyrat/modules/moretraitoritems/code/mafioso.dm
index bc58e789153099..e07039ded74b84 100644
--- a/modular_skyrat/modules/moretraitoritems/code/mafioso.dm
+++ b/modular_skyrat/modules/moretraitoritems/code/mafioso.dm
@@ -13,10 +13,10 @@
acid = 30
wound = 20
-/obj/item/clothing/under/suit/blacktwopiece/armoured
- armor_type = /datum/armor/clothing_under/blacktwopiece_armoured
+/obj/item/clothing/under/suit/black/armoured
+ armor_type = /datum/armor/clothing_under/black_armoured
-/datum/armor/clothing_under/blacktwopiece_armoured
+/datum/armor/clothing_under/black_armoured
melee = 10
bullet = 10
laser = 10
diff --git a/modular_skyrat/modules/moretraitoritems/code/syndicate_loadout.dm b/modular_skyrat/modules/moretraitoritems/code/syndicate_loadout.dm
index 8a49d1b6cf582d..2df1919fbbffe8 100644
--- a/modular_skyrat/modules/moretraitoritems/code/syndicate_loadout.dm
+++ b/modular_skyrat/modules/moretraitoritems/code/syndicate_loadout.dm
@@ -188,7 +188,7 @@
new /obj/item/gun/ballistic/automatic/tommygun(src)
new /obj/item/ammo_box/magazine/tommygunm45(src)
new /obj/item/clothing/suit/jacket/det_suit/noir/mafioso(src)
- new /obj/item/clothing/under/suit/blacktwopiece/armoured(src)
+ new /obj/item/clothing/under/suit/black/armoured(src)
new /obj/item/clothing/mask/fakemoustache/italian(src)
new /obj/item/switchblade(src)
new /obj/item/clothing/shoes/laceup(src)
diff --git a/modular_skyrat/modules/nanotrasen_rep/code/nanotrasen_consultant.dm b/modular_skyrat/modules/nanotrasen_rep/code/nanotrasen_consultant.dm
index 12d2406d0b7d22..b9126f3a3a5940 100644
--- a/modular_skyrat/modules/nanotrasen_rep/code/nanotrasen_consultant.dm
+++ b/modular_skyrat/modules/nanotrasen_rep/code/nanotrasen_consultant.dm
@@ -73,14 +73,14 @@
id_trim = /datum/id_trim/job/nanotrasen_consultant
/obj/item/radio/headset/heads/nanotrasen_consultant
- name = "\proper the nanotrasen consultant's headset"
+ name = "\proper the Nanotrasen consultant's headset"
desc = "An official Central Command headset."
icon_state = "cent_headset"
keyslot = new /obj/item/encryptionkey/headset_com
keyslot2 = new /obj/item/encryptionkey/headset_cent
/obj/item/radio/headset/heads/nanotrasen_consultant/alt
- name = "\proper the nanotrasen consultant's bowman headset"
+ name = "\proper the Nanotrasen consultant's bowman headset"
desc = "An official Central Command headset. Protects ears from flashbangs."
icon_state = "cent_headset_alt"
@@ -126,6 +126,27 @@
new /obj/item/ammo_box/magazine/m45a5(src)
new /obj/item/ammo_box/magazine/m45a5(src)
+/obj/item/storage/bag/garment/nanotrasen_consultant
+ name = "Nanotrasen consultant's garment bag"
+ desc = "A bag for storing extra clothes and shoes. This one belongs to the Nanotrasen consultant."
+
+/obj/item/storage/bag/garment/nanotrasen_consultant/PopulateContents()
+ new /obj/item/clothing/shoes/sneakers/brown(src)
+ new /obj/item/clothing/glasses/sunglasses/gar/giga(src)
+ new /obj/item/clothing/gloves/combat(src)
+ new /obj/item/clothing/gloves/combat/naval/nanotrasen_consultant(src)
+ new /obj/item/clothing/suit/hooded/wintercoat/centcom/nt_consultant(src)
+ new /obj/item/clothing/under/rank/nanotrasen_consultant(src)
+ new /obj/item/clothing/under/rank/nanotrasen_consultant/skirt(src)
+ new /obj/item/clothing/under/rank/centcom/officer(src)
+ new /obj/item/clothing/under/rank/centcom/officer_skirt(src)
+ new /obj/item/clothing/head/nanotrasen_consultant(src)
+ new /obj/item/clothing/head/nanotrasen_consultant/beret(src)
+ new /obj/item/clothing/head/beret/centcom_formal/nt_consultant(src)
+ new /obj/item/clothing/head/hats/centhat(src)
+ new /obj/item/clothing/suit/armor/centcom_formal/nt_consultant(src)
+ new /obj/item/clothing/under/rank/centcom/intern(src)
+ new /obj/item/clothing/head/hats/intern(src)
/obj/structure/closet/secure_closet/nanotrasen_consultant/station
name = "\proper nanotrasen consultant's locker"
@@ -139,14 +160,10 @@
new /obj/item/storage/backpack/satchel/leather(src)
new /obj/item/clothing/neck/petcollar(src)
new /obj/item/pet_carrier(src)
- new /obj/item/clothing/shoes/sneakers/brown(src)
new /obj/item/clothing/suit/armor/vest(src)
new /obj/item/computer_disk/command/captain(src)
new /obj/item/radio/headset/heads/nanotrasen_consultant/alt(src)
new /obj/item/radio/headset/heads/nanotrasen_consultant(src)
- new /obj/item/clothing/glasses/sunglasses/gar/giga(src)
- new /obj/item/clothing/gloves/combat(src)
- new /obj/item/clothing/gloves/combat/naval/nanotrasen_consultant(src)
new /obj/item/storage/photo_album/personal(src)
new /obj/item/bedsheet/centcom(src)
- new /obj/item/clothing/suit/hooded/wintercoat/centcom/nt_consultant(src)
+ new /obj/item/storage/bag/garment/nanotrasen_consultant(src)
diff --git a/modular_skyrat/modules/player_ranks/code/subsystem/player_ranks.dm b/modular_skyrat/modules/player_ranks/code/subsystem/player_ranks.dm
index 8e20fcda771360..264d9f8bedce84 100644
--- a/modular_skyrat/modules/player_ranks/code/subsystem/player_ranks.dm
+++ b/modular_skyrat/modules/player_ranks/code/subsystem/player_ranks.dm
@@ -46,15 +46,17 @@ SUBSYSTEM_DEF(player_ranks)
*
* Arguments:
* * user - The client to verify the donator status of.
+ * * admin_bypass - Whether or not admins can succeed this check, even if they
+ * do not actually possess the role. Defaults to `TRUE`.
*/
-/datum/controller/subsystem/player_ranks/proc/is_donator(client/user)
+/datum/controller/subsystem/player_ranks/proc/is_donator(client/user, admin_bypass = TRUE)
if(!istype(user))
CRASH("Invalid user type provided to is_donator(), expected 'client' and obtained '[user ? user.type : "null"]'.")
if(GLOB.donator_list[user.ckey])
return TRUE
- if(is_admin(user))
+ if(admin_bypass && is_admin(user))
return TRUE
return FALSE
@@ -66,12 +68,14 @@ SUBSYSTEM_DEF(player_ranks)
*
* Arguments:
* * user - The client to verify the mentor status of.
+ * * admin_bypass - Whether or not admins can succeed this check, even if they
+ * do not actually possess the role. Defaults to `TRUE`.
*/
-/datum/controller/subsystem/player_ranks/proc/is_mentor(client/user)
+/datum/controller/subsystem/player_ranks/proc/is_mentor(client/user, admin_bypass = TRUE)
if(!istype(user))
CRASH("Invalid user type provided to is_mentor(), expected 'client' and obtained '[user ? user.type : "null"]'.")
- return user.is_mentor()
+ return user.is_mentor(admin_bypass)
/**
@@ -79,15 +83,17 @@ SUBSYSTEM_DEF(player_ranks)
*
* Arguments:
* * user - The client to verify the veteran status of.
+ * * admin_bypass - Whether or not admins can succeed this check, even if they
+ * do not actually possess the role. Defaults to `TRUE`.
*/
-/datum/controller/subsystem/player_ranks/proc/is_veteran(client/user)
+/datum/controller/subsystem/player_ranks/proc/is_veteran(client/user, admin_bypass = TRUE)
if(!istype(user))
CRASH("Invalid user type provided to is_veteran(), expected 'client' and obtained '[user ? user.type : "null"]'.")
if(GLOB.veteran_list[user.ckey])
return TRUE
- if(is_admin(user))
+ if(admin_bypass && is_admin(user))
return TRUE
return FALSE
diff --git a/modular_skyrat/modules/poly_commands/parrot.dm b/modular_skyrat/modules/poly_commands/parrot.dm
index df8c73ef882634..f522edf7da11bf 100644
--- a/modular_skyrat/modules/poly_commands/parrot.dm
+++ b/modular_skyrat/modules/poly_commands/parrot.dm
@@ -12,7 +12,7 @@
/mob/living/simple_animal/parrot/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, list/message_mods = list(), message_range)
. = ..()
- if(check_command(message, speaker))
+ if(check_command(raw_message, speaker))
return
if(speaker != src && prob(50)) //Dont imitate ourselves
if(!radio_freq || prob(10))
@@ -20,7 +20,7 @@
speech_buffer -= pick(speech_buffer)
speech_buffer |= html_decode(raw_message)
if(speaker == src && !client) //If a parrot squawks in the woods and no one is around to hear it, does it make a sound? This code says yes!
- return message
+ return raw_message
/mob/living/simple_animal/parrot/proc/perch_on_human(mob/living/carbon/human/human_target)
if(!human_target)
@@ -61,30 +61,30 @@
/mob/living/simple_animal/parrot/poly/proc/command_perch(mob/living/carbon/human/human_target)
if (!buckled)
buckled_to_human = FALSE
- if(human_target.buckled_mobs?.len >= human_target.max_buckled_mobs)
+ if(LAZYLEN(human_target.buckled_mobs) >= human_target.max_buckled_mobs)
return
if(buckled_to_human)
- emote("me", EMOTE_VISIBLE, "gives [human_target] a confused look, squawking softly.")
+ manual_emote("gives [human_target] a confused look, squawking softly.")
return
if(get_dist(src, human_target) > 1 || buckled) // Only adjacent
- emote("me", EMOTE_VISIBLE, "tilts their head at [human_target], before bawking loudly and staying put.")
+ manual_emote("tilts their head at [human_target], before bawking loudly and staying put.")
return
- emote("me", EMOTE_VISIBLE, "obediently hops up onto [human_target]'s shoulder, spreading their wings for a moment before settling down.")
+ manual_emote("obediently hops up onto [human_target]'s shoulder, spreading their wings for a moment before settling down.")
perch_on_human(human_target)
/mob/living/simple_animal/parrot/poly/proc/command_hop_off(mob/living/carbon/human/human_target)
if (!buckled)
buckled_to_human = FALSE
if(!buckled_to_human || !buckled)
- emote("me", EMOTE_VISIBLE, "gives [human_target] a confused look, squawking softly.")
+ manual_emote("gives [human_target] a confused look, squawking softly.")
return
icon_state = icon_living
parrot_state = PARROT_WANDER
if(buckled)
- to_chat(src, span_notice("You are no longer sitting on [buckled]."))
+ to_chat(src, span_notice("You are no longer sitting on [human_target]."))
buckled.unbuckle_mob(src, TRUE)
- emote("me", EMOTE_VISIBLE, "squawks and hops off of [buckled], flying away.")
+ manual_emote("squawks and hops off of [human_target], flying away.")
buckled = null
buckled_to_human = FALSE
pixel_x = initial(pixel_x)
diff --git a/modular_skyrat/modules/primitive_cooking_additions/code/big_mortar.dm b/modular_skyrat/modules/primitive_cooking_additions/code/big_mortar.dm
index 3639e7005db5b5..3c2fc629a79619 100644
--- a/modular_skyrat/modules/primitive_cooking_additions/code/big_mortar.dm
+++ b/modular_skyrat/modules/primitive_cooking_additions/code/big_mortar.dm
@@ -138,14 +138,14 @@
///Juices the passed target item, and transfers any contained chems to the mortar as well
/obj/structure/large_mortar/proc/juice_target_item(obj/item/to_be_juiced, mob/living/carbon/human/user)
- to_be_juiced.juice(src, user)
+ to_be_juiced.juice(src.reagents, user)
to_chat(user, span_notice("You juice [to_be_juiced] into a liquid."))
QDEL_NULL(to_be_juiced)
///Grinds the passed target item, and transfers any contained chems to the mortar as well
/obj/structure/large_mortar/proc/grind_target_item(obj/item/to_be_ground, mob/living/carbon/human/user)
- to_be_ground.grind(src, user)
+ to_be_ground.grind(src.reagents, user)
to_chat(user, span_notice("You break [to_be_ground] into a fine powder."))
QDEL_NULL(to_be_ground)
diff --git a/modular_skyrat/modules/primitive_cooking_additions/code/millstone.dm b/modular_skyrat/modules/primitive_cooking_additions/code/millstone.dm
index d477528acd47ec..314390a7f54e22 100644
--- a/modular_skyrat/modules/primitive_cooking_additions/code/millstone.dm
+++ b/modular_skyrat/modules/primitive_cooking_additions/code/millstone.dm
@@ -88,6 +88,22 @@
return TRUE
+ if(attacking_item.tool_behaviour == TOOL_WRENCH)
+ attacking_item.play_tool_sound(src)
+ anchored = !anchored
+ balloon_alert(user, "[src] [anchored ? "anchored" : "unanchored"]")
+ return TRUE
+
+ if(attacking_item.tool_behaviour == TOOL_SCREWDRIVER)
+ attacking_item.play_tool_sound(src)
+
+ for(var/i in 1 to 6)
+ var/obj/item/stack/sheet/mineral/stone = new (get_turf(src))
+ transfer_fingerprints_to(stone)
+
+ qdel(src)
+ return TRUE
+
if(!((istype(attacking_item, /obj/item/food/grown/)) || (istype(attacking_item, /obj/item/grown))))
balloon_alert(user, "can only mill plants")
return ..()
diff --git a/modular_skyrat/modules/sec_haul/code/misc/bullet_drive.dm b/modular_skyrat/modules/sec_haul/code/misc/bullet_drive.dm
index 747601cb1f1a7f..7abf5585e086b3 100644
--- a/modular_skyrat/modules/sec_haul/code/misc/bullet_drive.dm
+++ b/modular_skyrat/modules/sec_haul/code/misc/bullet_drive.dm
@@ -6,7 +6,7 @@
density = TRUE
circuit = /obj/item/circuitboard/machine/dish_drive/bullet
collectable_items = list(/obj/item/ammo_casing)
- succrange = 10
+ suck_distance = 8
binrange = 10
/obj/item/circuitboard/machine/dish_drive/bullet
@@ -52,7 +52,7 @@
do_the_dishes()
if(!suction_enabled)
return
- for(var/obj/item/I in view(succrange, src))
+ for(var/obj/item/I in view(2 + suck_distance, src))
if(istype(I, /obj/machinery/dish_drive/bullet))
visible_message(span_userdanger("[src] has detected another bullet drive nearby, and is sad!"))
break
diff --git a/modular_skyrat/modules/stone/code/stone.dm b/modular_skyrat/modules/stone/code/stone.dm
index fb7ecd3f43a093..e41ac38f34448a 100644
--- a/modular_skyrat/modules/stone/code/stone.dm
+++ b/modular_skyrat/modules/stone/code/stone.dm
@@ -20,6 +20,7 @@
GLOBAL_LIST_INIT(stone_recipes, list ( \
new/datum/stack_recipe("stone brick wall", /turf/closed/wall/mineral/stone, 5, one_per_turf = 1, on_solid_ground = 1, applies_mats = TRUE, category = CAT_STRUCTURE), \
new/datum/stack_recipe("stone brick tile", /obj/item/stack/tile/mineral/stone, 1, 4, 20, check_density = FALSE, category = CAT_TILES),
+ new/datum/stack_recipe("millstone", /obj/structure/millstone, 6, one_per_turf = 1, on_solid_ground = 1, category = CAT_STRUCTURE),
))
/obj/item/stack/sheet/mineral/stone/get_main_recipes()
@@ -54,7 +55,7 @@ GLOBAL_LIST_INIT(stone_recipes, list ( \
. += span_notice("With a chisel or even a pickaxe of some kind, you could cut this into blocks.")
/obj/item/stack/stone/attackby(obj/item/attacking_item, mob/user, params)
- if((attacking_item.tool_behaviour != TOOL_MINING) || !(istype(attacking_item, /obj/item/chisel)))
+ if((attacking_item.tool_behaviour != TOOL_MINING) && !(istype(attacking_item, /obj/item/chisel)))
return ..()
playsound(src, 'sound/effects/picaxe1.ogg', 50, TRUE)
balloon_alert_to_viewers("cutting...")
@@ -127,3 +128,9 @@ GLOBAL_LIST_INIT(stone_recipes, list ( \
smoothing_flags = SMOOTH_BITMASK
smoothing_groups = SMOOTH_GROUP_STONE_WALLS + SMOOTH_GROUP_WALLS + SMOOTH_GROUP_CLOSED_TURFS
canSmoothWith = SMOOTH_GROUP_STONE_WALLS
+
+/turf/closed/mineral/gets_drilled(mob/user, give_exp = FALSE)
+ if(prob(5))
+ new /obj/item/stack/stone(src)
+
+ return ..()
diff --git a/modular_skyrat/modules/supermatter_surge/code/supermatter_surge.dm b/modular_skyrat/modules/supermatter_surge/code/supermatter_surge.dm
deleted file mode 100644
index 351903a3e49cd9..00000000000000
--- a/modular_skyrat/modules/supermatter_surge/code/supermatter_surge.dm
+++ /dev/null
@@ -1,70 +0,0 @@
-#define SUPERMATTER_SURGE_BULLET_ENERGY_FACTOR_UPPER 300
-#define SUPERMATTER_SURGE_BULLET_ENERGY_FACTOR_LOWER 100
-#define SUPERMATTER_SURGE_TIME_UPPER 360 * 0.5
-#define SUPERMATTER_SURGE_TIME_LOWER 180 * 0.5
-#define SUPERMATTER_SURGE_ANNOUNCE_THRESHOLD 25
-#define LOWER_SURGE_LIMIT 100 to 150
-#define MIDDLE_SURGE_LIMIT 151 to 200
-#define UPPER_SURGE_LIMIT 201 to 250
-/**
- * Supermatter Surge
- *
- * A very simple event designed to give engineering a challenge. It simply increases the supermatters power by a set amount, and announces it.
- *
- * This should be entirely fine for a powerful setup, but will require intervention on a lower power setup.
- */
-
-/datum/round_event_control/supermatter_surge
- name = "Supermatter Surge"
- typepath = /datum/round_event/supermatter_surge
- category = EVENT_CATEGORY_ENGINEERING
- max_occurrences = 4
- earliest_start = 30 MINUTES
- description = "The supermatter will increase in power by a random amount, and announce it."
-
-/datum/round_event/supermatter_surge
- announce_when = 1
- end_when = SUPERMATTER_SURGE_TIME_LOWER
- /// How powerful is the supermatter surge going to be? Set in setup.
- var/surge_power = SUPERMATTER_SURGE_BULLET_ENERGY_FACTOR_LOWER
- var/starting_surge_power = 0
- /// Typecasted reference to the supermatter chosen at the events start. Prevents the engine from going AWOL if it changes for some reason.
- var/obj/machinery/power/supermatter_crystal/our_main_engine
-
-/datum/round_event/supermatter_surge/setup()
- our_main_engine = GLOB.main_supermatter_engine
- surge_power = rand(SUPERMATTER_SURGE_BULLET_ENERGY_FACTOR_LOWER, SUPERMATTER_SURGE_BULLET_ENERGY_FACTOR_UPPER)
- starting_surge_power = our_main_engine.bullet_energy
- end_when = rand(SUPERMATTER_SURGE_TIME_LOWER, SUPERMATTER_SURGE_TIME_UPPER)
-
-/datum/round_event/supermatter_surge/announce()
- if(surge_power > SUPERMATTER_SURGE_ANNOUNCE_THRESHOLD || prob(round(surge_power)))
- priority_announce("Class [get_surge_level()] supermatter surge detected. Intervention may be required.", "Anomaly Alert", ANNOUNCER_KLAXON)
-
-/datum/round_event/supermatter_surge/proc/get_surge_level()
- switch(surge_power)
- if(LOWER_SURGE_LIMIT)
- return 4
- if(MIDDLE_SURGE_LIMIT)
- return 3
- if(UPPER_SURGE_LIMIT)
- return 2
- else
- return 1
-
-/datum/round_event/supermatter_surge/start()
- our_main_engine?.bullet_energy *= surge_power
-
-/datum/round_event/supermatter_surge/end()
- our_main_engine?.bullet_energy = starting_surge_power
- priority_announce("The supermatter surge has dissipated.", "Anomaly Cleared")
- our_main_engine = null
-
-#undef SUPERMATTER_SURGE_BULLET_ENERGY_FACTOR_UPPER
-#undef SUPERMATTER_SURGE_BULLET_ENERGY_FACTOR_LOWER
-#undef SUPERMATTER_SURGE_TIME_UPPER
-#undef SUPERMATTER_SURGE_TIME_LOWER
-#undef SUPERMATTER_SURGE_ANNOUNCE_THRESHOLD
-#undef LOWER_SURGE_LIMIT
-#undef MIDDLE_SURGE_LIMIT
-#undef UPPER_SURGE_LIMIT
diff --git a/modular_skyrat/modules/synths/code/reagents/reagents.dm b/modular_skyrat/modules/synths/code/reagents/reagents.dm
index b99291084f24d9..ef48f0bf9479ae 100644
--- a/modular_skyrat/modules/synths/code/reagents/reagents.dm
+++ b/modular_skyrat/modules/synths/code/reagents/reagents.dm
@@ -78,7 +78,7 @@
return ..()
affected_mob.reagents.remove_reagent(type, NANITE_SLURRY_ORGANIC_PURGE_RATE) //gets removed from organics very fast
if(prob(NANITE_SLURRY_ORGANIC_VOMIT_CHANCE))
- affected_mob.vomit(vomit_type = VOMIT_NANITE)
+ affected_mob.vomit(vomit_flags = (MOB_VOMIT_MESSAGE | MOB_VOMIT_HARM), vomit_type = /obj/effect/decal/cleanable/vomit/nanites)
return TRUE
#undef NANITE_SLURRY_ORGANIC_PURGE_RATE
diff --git a/sound/machines/engine_alert3.ogg b/sound/machines/engine_alert3.ogg
new file mode 100644
index 00000000000000..394bfed2a138de
Binary files /dev/null and b/sound/machines/engine_alert3.ogg differ
diff --git a/sound/magic/hereticknock.ogg b/sound/magic/hereticknock.ogg
new file mode 100644
index 00000000000000..87ca57302a285f
Binary files /dev/null and b/sound/magic/hereticknock.ogg differ
diff --git a/strings/tips.txt b/strings/tips.txt
index 514a9dada7a50b..12248bb4124355 100644
--- a/strings/tips.txt
+++ b/strings/tips.txt
@@ -13,9 +13,9 @@ As a Botanist, you should look into increasing the potency of your plants. This
As a Cargo Technician, you can earn more cargo points by shipping back crates from maintenance, liquid containers, plasma sheets, rare seeds from hydroponics, and more!
As a Cargo Technician, you can hack MULEbots to make them faster, run over people in their way, and even let you ride them!
As a Cargo Technician, you can order contraband items from the supply shuttle console by de-constructing it and using a multitool on the circuit board, the re-assembling it.
+As a Changeling, taking on someone else's appearance will also give you all of their scars. You can use Fleshmend to get rid of all scars.
As a Changeling, the Extract DNA sting counts for your genome absorb objective, but does not let you respec your powers.
As a Changeling, you can absorb someone by strangling them and using the Absorb verb; this gives you the ability to rechoose your powers, the DNA of whoever you absorbed, the memory of the absorbed, and some samples of things the absorbed said.
-As a Changeling, your Regenerate Limbs power will quickly heal all of your wounds, but they'll still leave scars. Changelings can use Fleshmend to get rid of scars, or you can ingest Carpotoxin to get rid of them like a normal person.
As a Chemist, some chemicals can only be synthesized by heating up the contents with a chemical heater or manually with lighters and similar tools.
As a Chemist, there are dozens of chemicals that can heal, and even more that can cause harm. Experiment!
As a Chemist, Water and Potassium mixed together will create an explosion, with power scaling by amount used. Don't do it.
@@ -267,3 +267,4 @@ You can spray a fire extinguisher, throw items or fire a gun while floating thro
You can swap floor tiles by holding a crowbar in one hand and a stack of tiles in the other.
You can use a machine in the vault to deposit cash or rob Cargo's department funds.
You'll quickly lose your interest in the game if you play to win and kill. If you find yourself doing this, take a step back and talk to people - it's a much better experience!
+Some areas of the station use simple nautical directions to indicate their respective locations, like Fore (Front of the ship), Aft (Back), Port (Left side), Starboard (Right), Quarter and Bow (Either sides of Aft and Fore, respectively). You can review these terms on the Notepad App of your PDA.
\ No newline at end of file
diff --git a/strings/wounds/bone_scar_desc.json b/strings/wounds/bone_scar_desc.json
index b1eb84bb8b79b0..2a89f0220021f0 100644
--- a/strings/wounds/bone_scar_desc.json
+++ b/strings/wounds/bone_scar_desc.json
@@ -1,6 +1,11 @@
{
"generic": ["general disfigurement"],
+ "dislocate": [
+ "the bone equivalent of a faded bruise",
+ "a series of tiny chip marks"
+ ],
+
"bluntmoderate": [
"the bone equivalent of a faded bruise",
"a series of tiny chip marks"
diff --git a/strings/wounds/flesh_scar_desc.json b/strings/wounds/flesh_scar_desc.json
index d8c253873cc38a..0fd78bec8e4f18 100644
--- a/strings/wounds/flesh_scar_desc.json
+++ b/strings/wounds/flesh_scar_desc.json
@@ -1,6 +1,12 @@
{
"generic": ["general disfigurement"],
+ "dislocate": [
+ "light discoloring",
+ "a slight blue tint",
+ "a slightly deadened tint"
+ ],
+
"bluntmoderate": [
"light discoloring",
"a slight blue tint",
diff --git a/tgstation.dme b/tgstation.dme
index 17bda6dc3eabce..40c8f89ab3abcb 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -104,6 +104,7 @@
#include "code\__DEFINES\ghost.dm"
#include "code\__DEFINES\gravity.dm"
#include "code\__DEFINES\guardian_defines.dm"
+#include "code\__DEFINES\holiday.dm"
#include "code\__DEFINES\holopads.dm"
#include "code\__DEFINES\hud.dm"
#include "code\__DEFINES\icon_smoothing.dm"
@@ -300,6 +301,7 @@
#include "code\__DEFINES\dcs\signals\signals_janitor.dm"
#include "code\__DEFINES\dcs\signals\signals_key.dm"
#include "code\__DEFINES\dcs\signals\signals_ladder.dm"
+#include "code\__DEFINES\dcs\signals\signals_lazy_templates.dm"
#include "code\__DEFINES\dcs\signals\signals_leash.dm"
#include "code\__DEFINES\dcs\signals\signals_lift.dm"
#include "code\__DEFINES\dcs\signals\signals_light_eater.dm"
@@ -319,6 +321,7 @@
#include "code\__DEFINES\dcs\signals\signals_radiation.dm"
#include "code\__DEFINES\dcs\signals\signals_reagent.dm"
#include "code\__DEFINES\dcs\signals\signals_restaurant.dm"
+#include "code\__DEFINES\dcs\signals\signals_saboteur.dm"
#include "code\__DEFINES\dcs\signals\signals_scangate.dm"
#include "code\__DEFINES\dcs\signals\signals_screentips.dm"
#include "code\__DEFINES\dcs\signals\signals_spatial_grid.dm"
@@ -453,7 +456,6 @@
#include "code\__HELPERS\atoms.dm"
#include "code\__HELPERS\auxtools.dm"
#include "code\__HELPERS\bitflag_lists.dm"
-#include "code\__HELPERS\byond_status.dm"
#include "code\__HELPERS\cameras.dm"
#include "code\__HELPERS\chat.dm"
#include "code\__HELPERS\chat_filter.dm"
@@ -761,6 +763,7 @@
#include "code\controllers\subsystem\processing\digital_clock.dm"
#include "code\controllers\subsystem\processing\fastprocess.dm"
#include "code\controllers\subsystem\processing\fire_burning.dm"
+#include "code\controllers\subsystem\processing\fishing.dm"
#include "code\controllers\subsystem\processing\greyscale.dm"
#include "code\controllers\subsystem\processing\instruments.dm"
#include "code\controllers\subsystem\processing\obj.dm"
@@ -1193,6 +1196,7 @@
#include "code\datums\components\uplink.dm"
#include "code\datums\components\usb_port.dm"
#include "code\datums\components\vacuum.dm"
+#include "code\datums\components\wall_mounted.dm"
#include "code\datums\components\wearertargeting.dm"
#include "code\datums\components\weatherannouncer.dm"
#include "code\datums\components\wet_floor.dm"
@@ -1224,6 +1228,7 @@
#include "code\datums\components\food\decomposition.dm"
#include "code\datums\components\food\edible.dm"
#include "code\datums\components\food\germ_sensitive.dm"
+#include "code\datums\components\food\ghost_edible.dm"
#include "code\datums\components\food\golem_food.dm"
#include "code\datums\components\food\ice_cream_holder.dm"
#include "code\datums\components\material\material_container.dm"
@@ -1325,6 +1330,7 @@
#include "code\datums\elements\bed_tucking.dm"
#include "code\datums\elements\befriend_petting.dm"
#include "code\datums\elements\blocks_explosives.dm"
+#include "code\datums\elements\bombable_turf.dm"
#include "code\datums\elements\bonus_damage.dm"
#include "code\datums\elements\bsa_blocker.dm"
#include "code\datums\elements\bugkiller_reagent.dm"
@@ -1388,6 +1394,7 @@
#include "code\datums\elements\light_eater.dm"
#include "code\datums\elements\loomable.dm"
#include "code\datums\elements\mirage_border.dm"
+#include "code\datums\elements\mob_grabber.dm"
#include "code\datums\elements\mob_killed_tally.dm"
#include "code\datums\elements\movement_turf_changer.dm"
#include "code\datums\elements\movetype_handler.dm"
@@ -1576,10 +1583,89 @@
#include "code\datums\proximity_monitor\fields\projectile_dampener.dm"
#include "code\datums\proximity_monitor\fields\timestop.dm"
#include "code\datums\quirks\_quirk.dm"
-#include "code\datums\quirks\negative_quirks\negative_quirks.dm"
-#include "code\datums\quirks\neutral_quirks\neutral_quirks.dm"
-#include "code\datums\quirks\positive_quirks\positive_quirks.dm"
+#include "code\datums\quirks\negative_quirks\allergic.dm"
+#include "code\datums\quirks\negative_quirks\bad_back.dm"
+#include "code\datums\quirks\negative_quirks\bad_touch.dm"
+#include "code\datums\quirks\negative_quirks\big_hands.dm"
+#include "code\datums\quirks\negative_quirks\blindness.dm"
+#include "code\datums\quirks\negative_quirks\blood_deficiency.dm"
+#include "code\datums\quirks\negative_quirks\body_purist.dm"
+#include "code\datums\quirks\negative_quirks\brain_problems.dm"
+#include "code\datums\quirks\negative_quirks\chronic_illness.dm"
+#include "code\datums\quirks\negative_quirks\claustrophobia.dm"
+#include "code\datums\quirks\negative_quirks\clumsy.dm"
+#include "code\datums\quirks\negative_quirks\cursed.dm"
+#include "code\datums\quirks\negative_quirks\deafness.dm"
+#include "code\datums\quirks\negative_quirks\depression.dm"
+#include "code\datums\quirks\negative_quirks\family_heirloom.dm"
+#include "code\datums\quirks\negative_quirks\frail.dm"
+#include "code\datums\quirks\negative_quirks\glass_jaw.dm"
+#include "code\datums\quirks\negative_quirks\heavy_sleeper.dm"
+#include "code\datums\quirks\negative_quirks\hemiplegic.dm"
+#include "code\datums\quirks\negative_quirks\hypersensitive.dm"
+#include "code\datums\quirks\negative_quirks\illiterate.dm"
+#include "code\datums\quirks\negative_quirks\indebted.dm"
+#include "code\datums\quirks\negative_quirks\insanity.dm"
+#include "code\datums\quirks\negative_quirks\junkie.dm"
+#include "code\datums\quirks\negative_quirks\light_drinker.dm"
+#include "code\datums\quirks\negative_quirks\mute.dm"
+#include "code\datums\quirks\negative_quirks\nearsighted.dm"
+#include "code\datums\quirks\negative_quirks\non_violent.dm"
+#include "code\datums\quirks\negative_quirks\numb.dm"
+#include "code\datums\quirks\negative_quirks\nyctophobia.dm"
+#include "code\datums\quirks\negative_quirks\paraplegic.dm"
+#include "code\datums\quirks\negative_quirks\photophobia.dm"
+#include "code\datums\quirks\negative_quirks\poor_aim.dm"
+#include "code\datums\quirks\negative_quirks\prosopagnosia.dm"
+#include "code\datums\quirks\negative_quirks\prosthetic_limb.dm"
+#include "code\datums\quirks\negative_quirks\prosthetic_organ.dm"
+#include "code\datums\quirks\negative_quirks\pushover.dm"
+#include "code\datums\quirks\negative_quirks\quadruple_amputee.dm"
+#include "code\datums\quirks\negative_quirks\social_anxiety.dm"
+#include "code\datums\quirks\negative_quirks\softspoken.dm"
+#include "code\datums\quirks\negative_quirks\tin_man.dm"
+#include "code\datums\quirks\negative_quirks\unstable.dm"
+#include "code\datums\quirks\neutral_quirks\bald.dm"
+#include "code\datums\quirks\neutral_quirks\colorist.dm"
+#include "code\datums\quirks\neutral_quirks\deviant_tastes.dm"
+#include "code\datums\quirks\neutral_quirks\extrovert.dm"
+#include "code\datums\quirks\neutral_quirks\foreigner.dm"
+#include "code\datums\quirks\neutral_quirks\gamer.dm"
+#include "code\datums\quirks\neutral_quirks\heretochromatic.dm"
+#include "code\datums\quirks\neutral_quirks\introvert.dm"
+#include "code\datums\quirks\neutral_quirks\monochromatic.dm"
+#include "code\datums\quirks\neutral_quirks\no_taste.dm"
+#include "code\datums\quirks\neutral_quirks\phobia.dm"
+#include "code\datums\quirks\neutral_quirks\photographer.dm"
+#include "code\datums\quirks\neutral_quirks\pineapple_hater.dm"
+#include "code\datums\quirks\neutral_quirks\pineapple_liker.dm"
+#include "code\datums\quirks\neutral_quirks\pride_pin.dm"
+#include "code\datums\quirks\neutral_quirks\shifty_eyes.dm"
+#include "code\datums\quirks\neutral_quirks\snob.dm"
+#include "code\datums\quirks\neutral_quirks\vegetarian.dm"
+#include "code\datums\quirks\positive_quirks\alcohol_tolerance.dm"
+#include "code\datums\quirks\positive_quirks\apathetic.dm"
+#include "code\datums\quirks\positive_quirks\bilingual.dm"
+#include "code\datums\quirks\positive_quirks\clown_enjoyer.dm"
+#include "code\datums\quirks\positive_quirks\drunk_healing.dm"
+#include "code\datums\quirks\positive_quirks\empath.dm"
+#include "code\datums\quirks\positive_quirks\freerunning.dm"
+#include "code\datums\quirks\positive_quirks\friendly.dm"
+#include "code\datums\quirks\positive_quirks\jolly.dm"
+#include "code\datums\quirks\positive_quirks\light_step.dm"
+#include "code\datums\quirks\positive_quirks\mime_fan.dm"
+#include "code\datums\quirks\positive_quirks\musician.dm"
+#include "code\datums\quirks\positive_quirks\night_vision.dm"
+#include "code\datums\quirks\positive_quirks\poster_boy.dm"
+#include "code\datums\quirks\positive_quirks\self_aware.dm"
+#include "code\datums\quirks\positive_quirks\settler.dm"
+#include "code\datums\quirks\positive_quirks\signer.dm"
+#include "code\datums\quirks\positive_quirks\skittish.dm"
#include "code\datums\quirks\positive_quirks\spacer.dm"
+#include "code\datums\quirks\positive_quirks\spiritual.dm"
+#include "code\datums\quirks\positive_quirks\tagger.dm"
+#include "code\datums\quirks\positive_quirks\throwing_arm.dm"
+#include "code\datums\quirks\positive_quirks\voracious.dm"
#include "code\datums\records\crime.dm"
#include "code\datums\records\data.dm"
#include "code\datums\records\manifest.dm"
@@ -1692,6 +1778,7 @@
#include "code\datums\wires\emitter.dm"
#include "code\datums\wires\explosive.dm"
#include "code\datums\wires\fax.dm"
+#include "code\datums\wires\mass_driver.dm"
#include "code\datums\wires\mecha.dm"
#include "code\datums\wires\microwave.dm"
#include "code\datums\wires\mod.dm"
@@ -2070,6 +2157,7 @@
#include "code\game\objects\items\choice_beacon.dm"
#include "code\game\objects\items\chromosome.dm"
#include "code\game\objects\items\cigs_lighters.dm"
+#include "code\game\objects\items\climbingrope.dm"
#include "code\game\objects\items\clown_items.dm"
#include "code\game\objects\items\control_wand.dm"
#include "code\game\objects\items\cosmetics.dm"
@@ -2524,6 +2612,7 @@
#include "code\game\turfs\change_turf.dm"
#include "code\game\turfs\turf.dm"
#include "code\game\turfs\closed\_closed.dm"
+#include "code\game\turfs\closed\indestructible.dm"
#include "code\game\turfs\closed\minerals.dm"
#include "code\game\turfs\closed\walls.dm"
#include "code\game\turfs\closed\wall\material_walls.dm"
@@ -2568,6 +2657,7 @@
#include "code\modules\actionspeed\modifiers\drugs.dm"
#include "code\modules\actionspeed\modifiers\mood.dm"
#include "code\modules\actionspeed\modifiers\status_effects.dm"
+#include "code\modules\actionspeed\modifiers\wound.dm"
#include "code\modules\admin\admin.dm"
#include "code\modules\admin\admin_fax_panel.dm"
#include "code\modules\admin\admin_investigate.dm"
@@ -2607,11 +2697,11 @@
#include "code\modules\admin\whitelist.dm"
#include "code\modules\admin\callproc\callproc.dm"
#include "code\modules\admin\smites\bad_luck.dm"
+#include "code\modules\admin\smites\become_object.dm"
#include "code\modules\admin\smites\berforate.dm"
#include "code\modules\admin\smites\bloodless.dm"
#include "code\modules\admin\smites\boneless.dm"
#include "code\modules\admin\smites\brain_damage.dm"
-#include "code\modules\admin\smites\bread.dm"
#include "code\modules\admin\smites\bsa.dm"
#include "code\modules\admin\smites\curse_of_babel.dm"
#include "code\modules\admin\smites\dock_pay.dm"
@@ -2853,17 +2943,21 @@
#include "code\modules\antagonists\heretic\items\heretic_blades.dm"
#include "code\modules\antagonists\heretic\items\heretic_necks.dm"
#include "code\modules\antagonists\heretic\items\hunter_rifle.dm"
+#include "code\modules\antagonists\heretic\items\keyring.dm"
+#include "code\modules\antagonists\heretic\items\lintel.dm"
#include "code\modules\antagonists\heretic\items\madness_mask.dm"
#include "code\modules\antagonists\heretic\knowledge\ash_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\blade_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\cosmic_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\flesh_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\general_side.dm"
+#include "code\modules\antagonists\heretic\knowledge\knock_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\rust_lore.dm"
#include "code\modules\antagonists\heretic\knowledge\side_ash_flesh.dm"
#include "code\modules\antagonists\heretic\knowledge\side_blade_rust.dm"
#include "code\modules\antagonists\heretic\knowledge\side_cosmos_ash.dm"
#include "code\modules\antagonists\heretic\knowledge\side_flesh_void.dm"
+#include "code\modules\antagonists\heretic\knowledge\side_knock_flesh.dm"
#include "code\modules\antagonists\heretic\knowledge\side_rust_cosmos.dm"
#include "code\modules\antagonists\heretic\knowledge\side_void_blade.dm"
#include "code\modules\antagonists\heretic\knowledge\starting_lore.dm"
@@ -2873,10 +2967,14 @@
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_map.dm"
#include "code\modules\antagonists\heretic\knowledge\sacrifice_knowledge\sacrifice_moodlets.dm"
#include "code\modules\antagonists\heretic\magic\aggressive_spread.dm"
+#include "code\modules\antagonists\heretic\magic\apetravulnera.dm"
+#include "code\modules\antagonists\heretic\magic\ascended_shapeshift.dm"
#include "code\modules\antagonists\heretic\magic\ash_ascension.dm"
#include "code\modules\antagonists\heretic\magic\ash_jaunt.dm"
#include "code\modules\antagonists\heretic\magic\blood_cleave.dm"
#include "code\modules\antagonists\heretic\magic\blood_siphon.dm"
+#include "code\modules\antagonists\heretic\magic\burglar_finesse.dm"
+#include "code\modules\antagonists\heretic\magic\caretaker.dm"
#include "code\modules\antagonists\heretic\magic\cosmic_expansion.dm"
#include "code\modules\antagonists\heretic\magic\cosmic_runes.dm"
#include "code\modules\antagonists\heretic\magic\eldritch_blind.dm"
@@ -2904,12 +3002,14 @@
#include "code\modules\antagonists\heretic\magic\void_cold_cone.dm"
#include "code\modules\antagonists\heretic\magic\void_phase.dm"
#include "code\modules\antagonists\heretic\magic\void_pull.dm"
+#include "code\modules\antagonists\heretic\magic\wave_of_desperation.dm"
#include "code\modules\antagonists\heretic\mobs\maid_in_mirror.dm"
#include "code\modules\antagonists\heretic\status_effects\buffs.dm"
#include "code\modules\antagonists\heretic\status_effects\debuffs.dm"
#include "code\modules\antagonists\heretic\status_effects\ghoul.dm"
#include "code\modules\antagonists\heretic\status_effects\mark_effects.dm"
#include "code\modules\antagonists\heretic\structures\carving_knife.dm"
+#include "code\modules\antagonists\heretic\structures\knock_final.dm"
#include "code\modules\antagonists\heretic\structures\mawed_crucible.dm"
#include "code\modules\antagonists\highlander\highlander.dm"
#include "code\modules\antagonists\hypnotized\hypnotized.dm"
@@ -3596,6 +3696,7 @@
#include "code\modules\events\shuttle_insurance.dm"
#include "code\modules\events\spider_infestation.dm"
#include "code\modules\events\stray_cargo.dm"
+#include "code\modules\events\supermatter_surge.dm"
#include "code\modules\events\tram_malfunction.dm"
#include "code\modules\events\vent_clog.dm"
#include "code\modules\events\wisdomcow.dm"
@@ -4682,7 +4783,6 @@
#include "code\modules\mod\modules\modules_supply.dm"
#include "code\modules\mod\modules\modules_timeline.dm"
#include "code\modules\mod\modules\modules_visor.dm"
-#include "code\modules\modular_computers\laptop_vendor.dm"
#include "code\modules\modular_computers\computers\item\computer.dm"
#include "code\modules\modular_computers\computers\item\computer_files.dm"
#include "code\modules\modular_computers\computers\item\computer_power.dm"
@@ -4992,6 +5092,7 @@
#include "code\modules\projectiles\projectile\special\floral.dm"
#include "code\modules\projectiles\projectile\special\gravity.dm"
#include "code\modules\projectiles\projectile\special\ion.dm"
+#include "code\modules\projectiles\projectile\special\lightbreaker.dm"
#include "code\modules\projectiles\projectile\special\meteor.dm"
#include "code\modules\projectiles\projectile\special\mindflayer.dm"
#include "code\modules\projectiles\projectile\special\neurotoxin.dm"
@@ -5794,6 +5895,7 @@
#include "modular_skyrat\master_files\code\modules\buildmode\submodes\offercontrol.dm"
#include "modular_skyrat\master_files\code\modules\cargo\goodies.dm"
#include "modular_skyrat\master_files\code\modules\cargo\orderconsole.dm"
+#include "modular_skyrat\master_files\code\modules\cargo\bounties\medical.dm"
#include "modular_skyrat\master_files\code\modules\cargo\exports\tools.dm"
#include "modular_skyrat\master_files\code\modules\cargo\exports\traitor.dm"
#include "modular_skyrat\master_files\code\modules\cargo\markets\market_items\weapons.dm"
@@ -5997,6 +6099,7 @@
#include "modular_skyrat\master_files\code\modules\surgery\surgery.dm"
#include "modular_skyrat\master_files\code\modules\surgery\bodyparts\_bodyparts.dm"
#include "modular_skyrat\master_files\code\modules\surgery\organs\tongue.dm"
+#include "modular_skyrat\master_files\code\modules\surgery\organs\internal\appendix\_appendix.dm"
#include "modular_skyrat\master_files\code\modules\vehicles\sealed.dm"
#include "modular_skyrat\master_files\code\modules\vehicles\snowmobile.dm"
#include "modular_skyrat\modules\additional_circuit\code\_designs.dm"
@@ -6610,6 +6713,7 @@
#include "modular_skyrat\modules\customization\modules\reagents\chemistry\reagents\toxin_reagents.dm"
#include "modular_skyrat\modules\customization\modules\reagents\chemistry\recipes\medicine.dm"
#include "modular_skyrat\modules\customization\modules\surgery\bodyparts\_bodyparts.dm"
+#include "modular_skyrat\modules\customization\modules\surgery\bodyparts\parts.dm"
#include "modular_skyrat\modules\customization\modules\surgery\bodyparts\robot_bodyparts.dm"
#include "modular_skyrat\modules\customization\modules\surgery\organs\cap.dm"
#include "modular_skyrat\modules\customization\modules\surgery\organs\ears.dm"
@@ -6717,7 +6821,6 @@
#include "modular_skyrat\modules\gunhud\code\gun_hud_component.dm"
#include "modular_skyrat\modules\gunpoint\code\gunpoint.dm"
#include "modular_skyrat\modules\gunpoint\code\gunpoint_datum.dm"
-#include "modular_skyrat\modules\gunsgalore\code\projectile.dm"
#include "modular_skyrat\modules\gunsgalore\code\ammo\ammo.dm"
#include "modular_skyrat\modules\gunsgalore\code\guns\akm.dm"
#include "modular_skyrat\modules\gunsgalore\code\guns\ballistic_master.dm"
@@ -6761,6 +6864,7 @@
#include "modular_skyrat\modules\ices_events\code\ICES_tgui.dm"
#include "modular_skyrat\modules\ices_events\code\effects\ef_foam.dm"
#include "modular_skyrat\modules\ices_events\code\events\ev_meteors.dm"
+#include "modular_skyrat\modules\ices_events\code\events\ev_roleplay_check.dm"
#include "modular_skyrat\modules\ices_events\code\events\ev_scrubbers.dm"
#include "modular_skyrat\modules\icspawning\code\cards_ids.dm"
#include "modular_skyrat\modules\icspawning\code\observer.dm"
@@ -6901,6 +7005,7 @@
#include "modular_skyrat\modules\medical\code\carbon_examine.dm"
#include "modular_skyrat\modules\medical\code\carbon_update_icons.dm"
#include "modular_skyrat\modules\medical\code\grasp.dm"
+#include "modular_skyrat\modules\medical\code\health_analyzer.dm"
#include "modular_skyrat\modules\medical\code\smartdarts.dm"
#include "modular_skyrat\modules\medical\code\wounds\_wounds.dm"
#include "modular_skyrat\modules\medical\code\wounds\bleed.dm"
@@ -7386,7 +7491,6 @@
#include "modular_skyrat\modules\subsystems\code\ticket_ping\adminhelp.dm"
#include "modular_skyrat\modules\subsystems\code\ticket_ping\preference.dm"
#include "modular_skyrat\modules\subsystems\code\ticket_ping\ticket_ss.dm"
-#include "modular_skyrat\modules\supermatter_surge\code\supermatter_surge.dm"
#include "modular_skyrat\modules\Syndie_edits\code\area.dm"
#include "modular_skyrat\modules\Syndie_edits\code\syndie_edits.dm"
#include "modular_skyrat\modules\synths\code\bodyparts\brain.dm"
diff --git a/tgui/packages/tgui/interfaces/CameraConsole.js b/tgui/packages/tgui/interfaces/CameraConsole.js
deleted file mode 100644
index 57cc34e77e18c7..00000000000000
--- a/tgui/packages/tgui/interfaces/CameraConsole.js
+++ /dev/null
@@ -1,137 +0,0 @@
-import { filter, sortBy } from 'common/collections';
-import { flow } from 'common/fp';
-import { classes } from 'common/react';
-import { createSearch } from 'common/string';
-import { useBackend, useLocalState } from '../backend';
-import { Button, ByondUi, Flex, Input, Section } from '../components';
-import { Window } from '../layouts';
-
-/**
- * Returns previous and next camera names relative to the currently
- * active camera.
- */
-export const prevNextCamera = (cameras, activeCamera) => {
- if (!activeCamera) {
- return [];
- }
- const index = cameras.findIndex(
- (camera) => camera.name === activeCamera.name
- );
- return [cameras[index - 1]?.name, cameras[index + 1]?.name];
-};
-
-/**
- * Camera selector.
- *
- * Filters cameras, applies search terms and sorts the alphabetically.
- */
-export const selectCameras = (cameras, searchText = '') => {
- const testSearch = createSearch(searchText, (camera) => camera.name);
- return flow([
- // Null camera filter
- filter((camera) => camera?.name),
- // Optional search term
- searchText && filter(testSearch),
- // Slightly expensive, but way better than sorting in BYOND
- sortBy((camera) => camera.name),
- ])(cameras);
-};
-
-export const CameraConsole = (props, context) => {
- const { act, data } = useBackend(context);
- const { mapRef, activeCamera } = data;
- const cameras = selectCameras(data.cameras);
- const [prevCameraName, nextCameraName] = prevNextCamera(
- cameras,
- activeCamera
- );
- return (
-
-
- act('confirm_order')}
- />
-
- );
-};
-
-const CfStep3 = (props, context) => {
- const { act, data } = useBackend(context);
- return (
-
-
- Your device is ready for fabrication...
-
-
- Please insert the required{' '}
-
- {data.totalprice} cr
-
-
-
- Current:
-
- = data.totalprice ? 'good' : 'bad'}>
- {data.credits} cr
-
- act('purchase')}
- />
-
- );
-};
-
-const CfStep4 = (props, context) => {
- return (
-
-
- Thank you for your purchase!
-
-
- If you experience any difficulties with your new device, please contact
- your local network administrator.
-
-
- );
-};
diff --git a/tgui/packages/tgui/interfaces/DelamProcedure.tsx b/tgui/packages/tgui/interfaces/DelamProcedure.tsx
new file mode 100644
index 00000000000000..f51c910d672c89
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/DelamProcedure.tsx
@@ -0,0 +1,105 @@
+import { Section, BlockQuote, Box, NoticeBox } from '../components';
+import { Window } from '../layouts';
+
+export const DelamProcedure = (context) => {
+ return (
+
+
+
+
+
+ So you've found yourself in a bit of a pickle with a
+ delamination of a supermatter reactor.
+
+
+ Don't worry, saving the day is just a few steps away!
+
+
+
+ Locate the ever-elusive red emergency stop button. It's
+ probably hiding in plain sight, so take your time, have a laugh, and
+ enjoy the anticipation. Remember, it's like a treasure hunt,
+ only with the added bonus of preventing a nuclear disaster.
+
+
+ Once you've uncovered the button, muster all your courage and
+ push it like there's no tomorrow. Well, actually, you're
+ pushing it to ensure there is a tomorrow. But hey, who doesn't
+ love a little paradoxical button-pushing?
+
+
+ Prepare for the impending suppression of the supermatter engine
+ room, because things are about to get real quiet. Just make sure
+ everyone has evacuated, or else they'll be in for a surprise.
+ The system needs its space, and it's not known for being the
+ friendliest neighbour.
+
+
+ After the delamination is successfully suppressed, take a moment to
+ appreciate the delicate beauty of crystal-based electricity. Take a
+ look around and fix any damage to those fragile glass components.
+ Feel free to put on your finest overalls and channel your inner
+ engiborg while doing so.
+
+
+ Keep an eye out for fires and the infamous air mix. It's always
+ an adventure trying to strike the perfect balance between breathable
+ air and potential suffocation. Remember, oxygen plus a spark equals
+ fireworks - the kind you definitely don't want inside a
+ reactor.
+
+
+
+ Did you know freon catches fire at low temperatures?
+
+
+ It even forms hot ice between 120K and 160K!
+
+
+ Remember you can always turn the engine room air alarm to
+ contaminated to assist in removing harmful gases!
+
+
+
+ To avoid singeing your eyebrows off, consider enlisting the help of
+ a synth or a trusty borg. After all, nothing says "safety
+ first" like outsourcing your firefighting to non-living,
+ non-breathing assistants.
+
+
+ Clear out any lightly radioactive debris and/or hot ice (The cargo
+ department will probably love to dispose it for you.)
+
+
+ Finally, revel in the satisfaction of knowing that you've
+ single-handedly prevented a delamination. But, of course, don't
+ forget to feel guilty because SAFETY MOTH Knows. SAFETY MOTH knows
+ everything. It's always watching, judging, and probably taking
+ notes for its next safety briefing. So bask in the glory of your
+ heroism, but know that the all-knowing Moff is onto you.
+
+
+ Optional step, for the true daredevils out there
+
+
+ When it comes time for your second attempt at starting the SM: Take
+ this sign, give it a good toss towards the crystal, and watch it
+ soar through the air.
+
+ Nothing says "I'm dealing with a potentially catastrophic
+ situation" like engaging in some whimsical shenanigans.
+