diff --git a/README.md b/README.md
index 6fccae54e43c1..a28f910037134 100644
--- a/README.md
+++ b/README.md
@@ -39,12 +39,15 @@ Space Station 13 is a paranoia-laden round-based roleplaying game set against th
**[How to compile in VSCode and other build options](tools/build/README.md).**
-## Contributors
-[Guides for Contributors](.github/CONTRIBUTING.md)
+## Getting started
-[/tg/station HACKMD account](https://hackmd.io/@tgstation) - Design documentation here
+For contribution guidelines refer to the [Guides for Contributors](.github/CONTRIBUTING.md).
-[Interested in some starting lore?](https://github.com/tgstation/common_core)
+For getting started (dev env, compilation) see the HackMD document [here](https://hackmd.io/@tgstation/HJ8OdjNBc#tgstation-Development-Guide).
+
+For overall design documentation see [HackMD](https://hackmd.io/@tgstation).
+
+For lore, [see Common Core](https://github.com/tgstation/common_core).
## LICENSE
diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm
index 97a86538e3ad9..ac5d51cdc9921 100644
--- a/code/__DEFINES/mobs.dm
+++ b/code/__DEFINES/mobs.dm
@@ -649,7 +649,8 @@
#define GRADIENT_APPLIES_TO_FACIAL_HAIR (1<<1)
// Hair masks
-#define HAIR_MASK_HIDE_ABOVE_45_DEG_MEDIUM "hide_above_45deg"
+#define HAIR_MASK_HIDE_ABOVE_45_DEG_MEDIUM "hide_above_45deg_medium"
+#define HAIR_MASK_HIDE_ABOVE_45_DEG_LOW "hide_above_45deg_low"
// Height defines
// - They are numbers so you can compare height values (x height < y height)
diff --git a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm
index e50794aa6d310..8d05402676846 100644
--- a/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm
+++ b/code/controllers/subsystem/dynamic/dynamic_rulesets_midround.dm
@@ -261,7 +261,7 @@
candidates -= player
else if(is_centcom_level(player.z))
candidates -= player // We don't autotator people in CentCom
- else if(player.mind && (player.mind.special_role || player.mind.can_roll_midround()))
+ else if(player.mind && (player.mind.special_role || !player.mind.can_roll_midround()))
candidates -= player // We don't autotator people with roles already
/datum/dynamic_ruleset/midround/from_living/autotraitor/execute()
@@ -310,7 +310,7 @@
continue
if(isnull(player.mind))
continue
- if(player.mind.special_role || player.mind.can_roll_midround())
+ if(player.mind.special_role || !player.mind.can_roll_midround())
continue
candidates += player
@@ -479,7 +479,7 @@
candidates -= player
continue
- if(player.mind && (player.mind.special_role || player.mind.can_roll_midround()))
+ if(player.mind && (player.mind.special_role || !player.mind.can_roll_midround()))
candidates -= player
/datum/dynamic_ruleset/midround/from_living/blob_infection/execute()
diff --git a/code/datums/components/blob_minion.dm b/code/datums/components/blob_minion.dm
index b74f813894db5..78bff449317ce 100644
--- a/code/datums/components/blob_minion.dm
+++ b/code/datums/components/blob_minion.dm
@@ -139,6 +139,7 @@
/// We only speak telepathically to blobs
/datum/component/blob_minion/proc/on_try_speech(mob/living/minion, message, ignore_spam, forced)
SIGNAL_HANDLER
+ minion.log_talk(message, LOG_SAY, tag = "blob hivemind telepathy")
var/spanned_message = minion.say_quote(message)
var/rendered = span_blob("\[Blob Telepathy\] [minion.real_name] [spanned_message]")
relay_to_list_and_observers(rendered, GLOB.blob_telepathy_mobs, minion)
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index b979c9cda0f5c..b51f7097a9fbf 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -23,7 +23,7 @@
// argument handling
// if the precision is not specified, default to 0, but apply BoH penalties
- if (isnull(precision))
+ if(isnull(precision))
precision = 0
switch(channel)
@@ -40,7 +40,7 @@
to_chat(MM, span_warning("The bluespace interface on your bag of holding interferes with the teleport!"))
// if effects are not specified and not explicitly disabled, sparks
- if ((!effectin || !effectout) && !no_effects)
+ if((!effectin || !effectout) && !no_effects)
var/datum/effect_system/spark_spread/sparks = new
sparks.set_up(5, 1, teleatom)
if (!effectin)
@@ -78,10 +78,16 @@
return TRUE
tele_play_specials(teleatom, curturf, effectin, asoundin)
+
var/success = teleatom.forceMove(destturf)
- if(success)
- log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]")
- tele_play_specials(teleatom, destturf, effectout, asoundout)
+ if(!success)
+ return FALSE
+
+ . = TRUE
+ /* Past this point, the teleport is successful and you can assume that they're already there */
+
+ log_game("[key_name(teleatom)] has teleported from [loc_name(curturf)] to [loc_name(destturf)]")
+ tele_play_specials(teleatom, destturf, effectout, asoundout)
if(ismob(teleatom))
var/mob/M = teleatom
@@ -90,7 +96,22 @@
SEND_SIGNAL(teleatom, COMSIG_MOVABLE_POST_TELEPORT, destination, channel)
- return TRUE
+ //We need to be sure that the buckled mobs can teleport too
+ if(teleatom.has_buckled_mobs())
+ for(var/mob/living/rider in teleatom.buckled_mobs)
+ //just in case it fails, but the mob gets unbuckled anyways even if it passes
+ teleatom.unbuckle_mob(rider, TRUE, FALSE)
+
+ var/rider_success = do_teleport(rider, destturf, precision, channel=channel, no_effects=TRUE)
+ if(!rider_success)
+ continue
+
+ if(get_turf(rider) != destturf) //precision made them teleport somewhere else
+ to_chat(rider, span_warning("As you reorient your senses, you realize you aren't riding [teleatom] anymore!"))
+ continue
+
+ // [mob/living].forceMove() forces mobs to unbuckle, so we need to buckle them again
+ teleatom.buckle_mob(rider, force=TRUE)
/proc/tele_play_specials(atom/movable/teleatom, atom/location, datum/effect_system/effect, sound)
if(!location)
diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm
index f06ac5d920916..4104367a73e93 100644
--- a/code/game/objects/buckling.dm
+++ b/code/game/objects/buckling.dm
@@ -215,23 +215,25 @@
if(target == src)
return FALSE
- // Check if the target to buckle isn't INSIDE OF A WALL
- if(!isopenturf(loc) || !isopenturf(target.loc))
- return FALSE
-
- // Check if the target to buckle isn't A SOLID OBJECT (not including vehicles)
var/turf/ground = get_turf(src)
- if(ground.is_blocked_turf(exclude_mobs = TRUE, source_atom = src))
- return FALSE
+ // If we're not already on the same turf as our target...
+ if(get_turf(target) != ground)
+ // Check if the target to buckle isn't INSIDE OF A WALL
+ if(!isopenturf(loc) || !isopenturf(target.loc))
+ return FALSE
+
+ // Check if the target to buckle isn't INSIDE A SOLID OBJECT (not including vehicles)
+ if(ground.is_blocked_turf(exclude_mobs = TRUE, source_atom = src))
+ return FALSE
+
+ // If we're checking the loc, make sure the target is on the thing we're bucking them to.
+ if(check_loc && !target.Adjacent(src))
+ return FALSE
// Check if this atom can have things buckled to it.
if(!can_buckle && !force)
return FALSE
- // If we're checking the loc, make sure the target is on the thing we're bucking them to.
- if(check_loc && !target.Adjacent(src))
- return FALSE
-
// Make sure the target isn't already buckled to something.
if(target.buckled)
return FALSE
diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm
index 28d52d3260456..cba2a3ee46cf4 100644
--- a/code/game/objects/items.dm
+++ b/code/game/objects/items.dm
@@ -1027,36 +1027,93 @@
///Called BEFORE the object is ground up - use this to change grind results based on conditions. Return "-1" to prevent the grinding from occurring
/obj/item/proc/on_grind()
+ PROTECTED_PROC(TRUE)
+
return SEND_SIGNAL(src, COMSIG_ITEM_ON_GRIND)
///Grind item, adding grind_results to item's reagents and transfering to target_holder if specified
-/obj/item/proc/grind(datum/reagents/target_holder, mob/user)
+/obj/item/proc/grind(datum/reagents/target_holder, mob/user, atom/movable/grinder = loc)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
. = FALSE
- if(on_grind() == -1)
+ if(on_grind() == -1 || target_holder.holder_full())
return
+ . = grind_atom(target_holder, user)
+
+ //reccursive grinding to get all them juices
+ var/result
+ for(var/obj/item/ingredient as anything in get_all_contents_type(/obj/item))
+ if(ingredient == src)
+ continue
+
+ result = ingredient.grind(target_holder, user)
+ if(!.)
+ . = result
+
+ if(. && istype(grinder))
+ return grinder.blended(src, grinded = TRUE)
+
+///Subtypes override his proc for custom grinding
+/obj/item/proc/grind_atom(datum/reagents/target_holder, mob/user)
+ PROTECTED_PROC(TRUE)
+
+ . = FALSE
if(length(grind_results))
target_holder.add_reagent_list(grind_results)
. = TRUE
- if(reagents?.total_volume)
- reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user)
+ if(reagents?.trans_to(target_holder, reagents.total_volume, transferred_by = user))
. = TRUE
///Called BEFORE the object is ground up - use this to change grind results based on conditions. Return "-1" to prevent the grinding from occurring
/obj/item/proc/on_juice()
+ PROTECTED_PROC(TRUE)
+
if(!juice_typepath)
return -1
+
return SEND_SIGNAL(src, COMSIG_ITEM_ON_JUICE)
///Juice item, converting nutriments into juice_typepath and transfering to target_holder if specified
-/obj/item/proc/juice(datum/reagents/target_holder, mob/user)
+/obj/item/proc/juice(datum/reagents/target_holder, mob/user, atom/movable/juicer = loc)
+ SHOULD_NOT_OVERRIDE(TRUE)
+
+ . = FALSE
if(on_juice() == -1 || !reagents?.total_volume)
- return FALSE
+ return
+
+ . = juice_atom(target_holder, user)
+
+ //reccursive juicing to get all them juices
+ var/result
+ for(var/obj/item/ingredient as anything in get_all_contents_type(/obj/item))
+ if(ingredient == src)
+ continue
+
+ result = ingredient.juice(target_holder, user)
+ if(!.)
+ . = result
+
+ if(. && istype(juicer))
+ return juicer.blended(src, grinded = FALSE)
+
+///Subtypes override his proc for custom juicing
+/obj/item/proc/juice_atom(datum/reagents/target_holder, mob/user)
+ PROTECTED_PROC(TRUE)
+
+ . = FALSE
if(ispath(juice_typepath))
reagents.convert_reagent(/datum/reagent/consumable/nutriment, juice_typepath, include_source_subtypes = FALSE)
reagents.convert_reagent(/datum/reagent/consumable/nutriment/vitamin, juice_typepath, include_source_subtypes = FALSE)
- reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user)
+ . = TRUE
+
+ if(!QDELETED(target_holder))
+ reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user)
+
+///What should The atom that blended an object do with it afterwards? Default behaviour is to delete it
+/atom/movable/proc/blended(obj/item/blended_item, grinded)
+ qdel(blended_item)
return TRUE
diff --git a/code/game/objects/items/cigarettes.dm b/code/game/objects/items/cigarettes.dm
index 5eddcf93d4bb2..51b38d032c090 100644
--- a/code/game/objects/items/cigarettes.dm
+++ b/code/game/objects/items/cigarettes.dm
@@ -185,11 +185,6 @@ CIGARETTE PACKETS ARE IN FANCY.DM
/obj/item/cigarette/Initialize(mapload)
. = ..()
- create_reagents(chem_volume, INJECTABLE | NO_REACT)
- if(list_reagents)
- reagents.add_reagent_list(list_reagents)
- if(starts_lit)
- light()
AddComponent(/datum/component/knockoff, 90, list(BODY_ZONE_PRECISE_MOUTH), slot_flags) //90% to knock off when wearing a mask
AddElement(/datum/element/update_icon_updates_onmob)
RegisterSignal(src, COMSIG_ATOM_TOUCHED_SPARKS, PROC_REF(sparks_touched))
@@ -201,15 +196,17 @@ CIGARETTE PACKETS ARE IN FANCY.DM
initial_reagents = list_reagents,\
food_flags = FOOD_NO_EXAMINE,\
foodtypes = JUNKFOOD,\
- volume = 50,\
+ volume = chem_volume,\
eat_time = 0 SECONDS,\
- tastes = list("a never before experienced flavour.", "finally sitting down after standing your entire life"),\
+ tastes = list("a never before experienced flavour", "finally sitting down after standing your entire life"),\
eatverbs = list("taste"),\
- bite_consumption = 50,\
+ bite_consumption = chem_volume,\
junkiness = 0,\
reagent_purity = null,\
on_consume = CALLBACK(src, PROC_REF(on_consume)),\
)
+ if(starts_lit)
+ light()
/obj/item/cigarette/Destroy()
STOP_PROCESSING(SSobj, src)
diff --git a/code/game/objects/items/stacks/stack.dm b/code/game/objects/items/stacks/stack.dm
index 18891ebdd9306..fd1529eb3301c 100644
--- a/code/game/objects/items/stacks/stack.dm
+++ b/code/game/objects/items/stacks/stack.dm
@@ -135,14 +135,10 @@
return
return TRUE
-/obj/item/stack/grind(datum/reagents/target_holder, mob/user)
+/obj/item/stack/grind_atom(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)
diff --git a/code/game/objects/items/theft_tools.dm b/code/game/objects/items/theft_tools.dm
index 6f09f58169521..34176864492da 100644
--- a/code/game/objects/items/theft_tools.dm
+++ b/code/game/objects/items/theft_tools.dm
@@ -158,6 +158,18 @@
pulseicon = "supermatter_sliver_pulse"
layer = ABOVE_MOB_LAYER
+/obj/item/nuke_core/supermatter_sliver/Initialize(mapload)
+ . = ..()
+ RegisterSignal(src, COMSIG_FISHING_ROD_CAST, PROC_REF(on_hook))
+
+/obj/item/nuke_core/supermatter_sliver/proc/on_hook(obj/item/nuke_core/supermatter_sliver/source, obj/item/fishing_rod/rod, mob/user)
+ SIGNAL_HANDLER
+
+ //hook gets dusted but the rod remains intact
+ attackby(rod.hook, user)
+
+ return FISHING_ROD_CAST_HANDLED
+
/obj/item/nuke_core/supermatter_sliver/attack_tk(mob/user) // no TK dusting memes
return
diff --git a/code/modules/clothing/head/helmet.dm b/code/modules/clothing/head/helmet.dm
index c3072ac135540..2bbd908c04443 100644
--- a/code/modules/clothing/head/helmet.dm
+++ b/code/modules/clothing/head/helmet.dm
@@ -39,6 +39,7 @@
drop_sound = 'sound/items/handling/helmet/helmet_drop1.ogg'
visor_toggle_up_sound = SFX_VISOR_UP
visor_toggle_down_sound = SFX_VISOR_DOWN
+ hair_mask = HAIR_MASK_HIDE_ABOVE_45_DEG_LOW
/obj/item/clothing/head/helmet/sec/Initialize(mapload)
. = ..()
@@ -68,6 +69,17 @@
return ..()
+/obj/item/clothing/head/helmet/sec/attack_self(mob/user)
+ . = ..()
+ if(.)
+ return
+ balloon_alert(user, "[flags_inv & HIDEHAIR ? "loosening" : "tightening"] straps...")
+ if(!do_after(user, 3 SECONDS, src))
+ return
+ flags_inv ^= HIDEHAIR
+ balloon_alert(user, "[flags_inv & HIDEHAIR ? "tightened" : "loosened"] straps")
+ return TRUE
+
/obj/item/clothing/head/helmet/sec/click_alt(mob/user)
flipped_visor = !flipped_visor
balloon_alert(user, "visor flipped")
diff --git a/code/modules/hydroponics/grown.dm b/code/modules/hydroponics/grown.dm
index 6edbfd382f964..b55277820114d 100644
--- a/code/modules/hydroponics/grown.dm
+++ b/code/modules/hydroponics/grown.dm
@@ -139,10 +139,7 @@
reagents.add_reagent(/datum/reagent/consumable/ethanol/fruit_wine, reagent.volume, data, added_purity = reagent_purity)
reagents.del_reagent(reagent.type)
-/obj/item/food/grown/grind(datum/reagents/target_holder, mob/user)
- if(on_grind() == -1)
- return FALSE
-
+/obj/item/food/grown/grind_atom(datum/reagents/target_holder, mob/user)
var/grind_results_num = LAZYLEN(grind_results)
if(grind_results_num)
var/average_purity = reagents.get_average_purity()
@@ -152,9 +149,7 @@
for(var/reagent in grind_results)
reagents.add_reagent(reagent, single_reagent_amount, added_purity = average_purity)
- if(reagents && target_holder)
- reagents.trans_to(target_holder, reagents.total_volume, transferred_by = user)
- return TRUE
+ return reagents?.trans_to(target_holder, reagents.total_volume, transferred_by = user)
#undef BITE_SIZE_POTENCY_MULTIPLIER
#undef BITE_SIZE_VOLUME_MULTIPLIER
diff --git a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
index 5a3e7a044e5a0..09d4bc285645b 100644
--- a/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
+++ b/code/modules/jobs/job_types/chaplain/chaplain_nullrod.dm
@@ -300,6 +300,7 @@
righthand_file = 'icons/mob/inhands/items/touchspell_righthand.dmi'
slot_flags = null
item_flags = ABSTRACT | DROPDEL
+ resistance_flags = FIRE_PROOF|ACID_PROOF
w_class = WEIGHT_CLASS_HUGE
hitsound = 'sound/items/weapons/sear.ogg'
damtype = BURN
@@ -399,6 +400,7 @@
w_class = WEIGHT_CLASS_HUGE
slot_flags = null
item_flags = ABSTRACT
+ resistance_flags = FIRE_PROOF|ACID_PROOF
sharpness = SHARP_EDGED
attack_verb_continuous = list("saws", "tears", "lacerates", "cuts", "chops", "dices")
attack_verb_simple = list("saw", "tear", "lacerate", "cut", "chop", "dice")
@@ -522,6 +524,7 @@
righthand_file = 'icons/mob/inhands/antag/changeling_righthand.dmi'
slot_flags = null
item_flags = ABSTRACT
+ resistance_flags = FIRE_PROOF|ACID_PROOF
w_class = WEIGHT_CLASS_HUGE
sharpness = SHARP_EDGED
wound_bonus = -20
@@ -572,7 +575,7 @@
/obj/item/nullrod/bostaff
name = "monk's staff"
desc = "A long, tall staff made of polished wood. Traditionally used in ancient old-Earth martial arts, it is now used to harass the clown."
- force = 14
+ force = 10
block_chance = 40
block_sound = 'sound/items/weapons/genhit.ogg'
slot_flags = ITEM_SLOT_BACK
@@ -600,6 +603,10 @@
icon_state = inhand_icon_state = "[base_icon_state][HAS_TRAIT(src, TRAIT_WIELDED)]"
return ..()
+/obj/item/nullrod/bostaff/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK, damage_type = BRUTE)
+ if(attack_type == PROJECTILE_ATTACK)
+ final_block_chance = 0 //Don't bring a sword to a gunfight
+ return ..()
// Arrhythmic Knife - Lets your walk without rhythm by varying your walk speed. Can't be put away.
diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm
index 87c0cdd402918..a642a1f33eac2 100644
--- a/code/modules/mob/living/silicon/ai/ai.dm
+++ b/code/modules/mob/living/silicon/ai/ai.dm
@@ -241,7 +241,7 @@
/// Removes all malfunction-related abilities from the AI
/mob/living/silicon/ai/proc/remove_malf_abilities()
QDEL_NULL(modules_action)
- for(var/datum/ai_module/AM in current_modules)
+ for(var/datum/ai_module/malf/AM in current_modules)
for(var/datum/action/A in actions)
if(istype(A, initial(AM.power_type)))
qdel(A)
diff --git a/code/modules/plumbing/plumbers/grinder_chemical.dm b/code/modules/plumbing/plumbers/grinder_chemical.dm
index bd0a69e6d5e86..c631e26def6f5 100644
--- a/code/modules/plumbing/plumbers/grinder_chemical.dm
+++ b/code/modules/plumbing/plumbers/grinder_chemical.dm
@@ -74,7 +74,7 @@
to_chat(user, span_notice("You dump items from [tool] into the grinder."))
for(var/obj/item/obj_item in tool.contents)
- grind(obj_item)
+ blend(obj_item)
return ITEM_INTERACT_SUCCESS
else if(!tool.tool_behaviour)
var/action = "[grinding ? "grind" : "juice"]"
@@ -83,7 +83,7 @@
return ITEM_INTERACT_BLOCKING
to_chat(user, span_notice("You attempt to [action] [tool]."))
- grind(tool)
+ blend(tool)
return ITEM_INTERACT_SUCCESS
/obj/machinery/plumbing/grinder_chemical/CanAllowThrough(atom/movable/mover, border_dir)
@@ -97,33 +97,45 @@
/obj/machinery/plumbing/grinder_chemical/proc/on_entered(datum/source, atom/movable/AM)
SIGNAL_HANDLER
- INVOKE_ASYNC(src, PROC_REF(grind), AM)
+ if(!isitem(AM))
+ return
+
+ INVOKE_ASYNC(src, PROC_REF(blend), AM)
+
+
+/obj/machinery/plumbing/grinder_chemical/blended(obj/item/blended_item, grinded)
+ //don't delete slime extracts
+ if(istype(blended_item, /obj/item/slime_extract))
+ //so you can't regrind them for extra stuff
+ blended_item.grind_results = null
+
+ blended_item.forceMove(drop_location())
+
+ return TRUE
+
+ return ..()
/**
* Grinds/Juices the atom
* Arguments
* * [AM][atom] - the atom to grind or juice
*/
-/obj/machinery/plumbing/grinder_chemical/proc/grind(atom/AM)
+/obj/machinery/plumbing/grinder_chemical/proc/blend(obj/item/I)
PRIVATE_PROC(TRUE)
if(!is_operational || !anchored)
return
if(reagents.holder_full())
return
- if(!isitem(AM))
- return
- var/obj/item/I = AM
if((I.item_flags & ABSTRACT) || (I.flags_1 & HOLOGRAM_1))
return
+ if(!I.blend_requirements(src))
+ return
- var/result
if(!grinding)
- result = I.juice(reagents, usr)
+ I.juice(reagents, usr, src)
else if(length(I.grind_results) || I.reagents?.total_volume)
- result = I.grind(reagents, usr)
+ I.grind(reagents, usr, src)
use_energy(active_power_usage)
- if(result)
- qdel(I)
diff --git a/code/modules/reagents/chemistry/machinery/chem_master.dm b/code/modules/reagents/chemistry/machinery/chem_master.dm
index 2aac6a457abfd..bcb6cac2f183d 100644
--- a/code/modules/reagents/chemistry/machinery/chem_master.dm
+++ b/code/modules/reagents/chemistry/machinery/chem_master.dm
@@ -454,6 +454,11 @@
//are we printing a valid container
var/container_found = FALSE
for(var/category in printable_containers)
+ //container found in previous iteration
+ if(container_found)
+ break
+
+ //find for matching typepath
for(var/obj/item/reagent_containers/container as anything in printable_containers[category])
if(target == container)
container_found = TRUE
diff --git a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
index 141fb7c4e6fb0..7716784e4b438 100644
--- a/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
+++ b/code/modules/reagents/chemistry/machinery/reagentgrinder.dm
@@ -434,43 +434,33 @@
playsound(src, 'sound/machines/juicer.ogg', 20, TRUE)
var/total_weight
- for(var/obj/item/weapon in src)
- if((weapon in component_parts) || weapon == beaker)
- continue
+ var/item_weight
+ for(var/obj/item/ingredient in contents)
if(beaker.reagents.holder_full())
break
+ if((ingredient in component_parts) || ingredient == beaker)
+ continue
+
+ //record item weight before & after blending
+ item_weight = ingredient.w_class
- //recursively process everything inside this atom
- var/item_processed = FALSE
- var/item_weight = weapon.w_class
- for(var/obj/item/ingredient as anything in weapon.get_all_contents_type(/obj/item))
- if(beaker.reagents.holder_full())
- break
-
- if(juicing)
- if(!ingredient.juice(beaker.reagents, user))
- to_chat(user, span_danger("[src] shorts out as it tries to juice up [ingredient], and transfers it back to storage."))
- continue
- item_processed = TRUE
- else if(length(ingredient.grind_results) || ingredient.reagents?.total_volume)
- if(!ingredient.grind(beaker.reagents, user))
- if(isstack(ingredient))
- to_chat(user, span_notice("[src] attempts to grind as many pieces of [ingredient] as possible."))
- else
- to_chat(user, span_danger("[src] shorts out as it tries to grind up [ingredient], and transfers it back to storage."))
- continue
- item_processed = TRUE
+ if(juicing)
+ if(!ingredient.juice(beaker.reagents, user))
+ to_chat(user, span_danger("[src] shorts out as it tries to juice up [ingredient], and transfers it back to storage."))
+ continue
+ else if(!ingredient.grind(beaker.reagents, user))
+ if(isstack(ingredient))
+ to_chat(user, span_notice("[src] attempts to grind as many pieces of [ingredient] as possible."))
+ else
+ to_chat(user, span_danger("[src] shorts out as it tries to grind up [ingredient], and transfers it back to storage."))
+ continue
//happens only for stacks where some of the sheets were grinded so we roughly compute the weight grinded
- if(item_weight != weapon.w_class)
- total_weight += item_weight - weapon.w_class
+ if(item_weight != ingredient.w_class)
+ total_weight += item_weight - ingredient.w_class
else
total_weight += item_weight
- //delete only if operation was successfull for atleast 1 item(also delete atoms for whom only some of its contents were processed as they are non functional now)
- if(item_processed)
- qdel(weapon)
-
//use power according to the total weight of items grinded
use_energy((active_power_usage * (duration / 1 SECONDS)) * (total_weight / maximum_weight))
diff --git a/code/modules/reagents/reagent_containers/cups/_cup.dm b/code/modules/reagents/reagent_containers/cups/_cup.dm
index b4493eb2ae4ab..0c6d638fca18d 100644
--- a/code/modules/reagents/reagent_containers/cups/_cup.dm
+++ b/code/modules/reagents/reagent_containers/cups/_cup.dm
@@ -506,11 +506,17 @@
if(grinded)
to_chat(user, span_warning("There is something inside already!"))
return
- if(I.juice_typepath || I.grind_results)
+ if(!I.blend_requirements(src))
+ to_chat(user, span_warning("Cannot grind this!"))
+ return
+ if(length(I.grind_results) || I.reagents?.total_volume)
I.forceMove(src)
grinded = I
- return
- to_chat(user, span_warning("You can't grind this!"))
+
+/obj/item/reagent_containers/cup/mortar/blended(obj/item/blended_item, grinded)
+ src.grinded = null
+
+ return ..()
/obj/item/reagent_containers/cup/mortar/proc/grind_item(obj/item/item, mob/living/carbon/human/user)
if(item.flags_1 & HOLOGRAM_1)
@@ -520,13 +526,12 @@
if(!item.grind(reagents, user))
if(isstack(item))
- to_chat(usr, span_notice("[src] attempts to grind as many pieces of [item] as possible."))
+ to_chat(user, 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.flags_1 & HOLOGRAM_1)
@@ -537,9 +542,8 @@
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."))
- grinded = null
- QDEL_NULL(item)
//Coffeepots: for reference, a standard cup is 30u, to allow 20u for sugar/sweetener/milk/creamer
/obj/item/reagent_containers/cup/coffeepot
diff --git a/html/changelogs/AutoChangeLog-pr-87489.yml b/html/changelogs/AutoChangeLog-pr-87489.yml
deleted file mode 100644
index 8ad3a9a7489c0..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87489.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "grungussuss"
-delete-after: True
-changes:
- - sound: "only insulated,nitrile, enhanced retrieval, latex, boxing, improvised gripper gloves have an equip sound"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87684.yml b/html/changelogs/AutoChangeLog-pr-87684.yml
deleted file mode 100644
index e13ae3fcc6cf5..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87684.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "tontyGH"
-delete-after: True
-changes:
- - server: "mob/camera has been renamed to mob/eye, which may break downstreams"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87706.yml b/html/changelogs/AutoChangeLog-pr-87706.yml
deleted file mode 100644
index 0265c8918a5c8..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87706.yml
+++ /dev/null
@@ -1,5 +0,0 @@
-author: "Ghommie"
-delete-after: True
-changes:
- - bugfix: "Fixed fishing rod duping with poly belts and shapeshift spells."
- - spellcheck: "Fixed a small typo when examining fishing rods."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87735.yml b/html/changelogs/AutoChangeLog-pr-87735.yml
new file mode 100644
index 0000000000000..b304247196cc0
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87735.yml
@@ -0,0 +1,6 @@
+author: "SyncIt21"
+delete-after: True
+changes:
+ - bugfix: "mortar pedestal now grinds & juices items that previously could not be processed"
+ - bugfix: "plumbing grinder won't destroy slime extracts after grinding"
+ - refactor: "grinding & juicing code has been refactored overall. Please report bugs on github"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87741.yml b/html/changelogs/AutoChangeLog-pr-87741.yml
deleted file mode 100644
index e27aee03b3bf4..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87741.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "SmArtKar"
-delete-after: True
-changes:
- - bugfix: "Fixed wings and jetpacks sometimes preventing you from opening doors"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87744.yml b/html/changelogs/AutoChangeLog-pr-87744.yml
deleted file mode 100644
index c3ac14a47e82b..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87744.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "Xander3359"
-delete-after: True
-changes:
- - bugfix: "fix blade ascension not giving you the ring of blades"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87749.yml b/html/changelogs/AutoChangeLog-pr-87749.yml
deleted file mode 100644
index bf27ebeb9319e..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87749.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "timothymtorres"
-delete-after: True
-changes:
- - admin: "Admins can now add/remove TRAIT_EVIL from mobs."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87756.yml b/html/changelogs/AutoChangeLog-pr-87756.yml
deleted file mode 100644
index a5c9f801d186e..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87756.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "OrionTheFox"
-delete-after: True
-changes:
- - image: "redid most basic drinking glass sprites, and moved several drinks to use the same color system as beakers. Please bug report any incorrect colored drinks or juices!"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87759.yml b/html/changelogs/AutoChangeLog-pr-87759.yml
deleted file mode 100644
index 612020fc7e78c..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87759.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "harryob"
-delete-after: True
-changes:
- - bugfix: "the abductor console now correctly loads images of equipment"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87762.yml b/html/changelogs/AutoChangeLog-pr-87762.yml
new file mode 100644
index 0000000000000..6b64fb9da5557
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87762.yml
@@ -0,0 +1,4 @@
+author: "Majkl-J"
+delete-after: True
+changes:
+ - bugfix: "Losing malf no longer wipes nonmalf AI abilities"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87775.yml b/html/changelogs/AutoChangeLog-pr-87775.yml
new file mode 100644
index 0000000000000..f66a13a25b250
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87775.yml
@@ -0,0 +1,5 @@
+author: "necromanceranne"
+delete-after: True
+changes:
+ - bugfix: "Abstract nullrod types can no longer be burned or melted with acid."
+ - bugfix: "Monk staff now properly does not block projectiles, and uses the correct force before being wielded."
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87780.yml b/html/changelogs/AutoChangeLog-pr-87780.yml
deleted file mode 100644
index 159c49658736a..0000000000000
--- a/html/changelogs/AutoChangeLog-pr-87780.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-author: "SyncIt21"
-delete-after: True
-changes:
- - bugfix: "chem master validates selected container in UI so no more href exploits"
\ No newline at end of file
diff --git a/html/changelogs/AutoChangeLog-pr-87784.yml b/html/changelogs/AutoChangeLog-pr-87784.yml
new file mode 100644
index 0000000000000..bbd6cd5293395
--- /dev/null
+++ b/html/changelogs/AutoChangeLog-pr-87784.yml
@@ -0,0 +1,4 @@
+author: "SyncIt21"
+delete-after: True
+changes:
+ - bugfix: "Super matter sliver dusts fishing hooks & cannot be picked up by them"
\ No newline at end of file
diff --git a/html/changelogs/archive/2024-11.yml b/html/changelogs/archive/2024-11.yml
index d5e17a66462b0..3c70b294dc65c 100644
--- a/html/changelogs/archive/2024-11.yml
+++ b/html/changelogs/archive/2024-11.yml
@@ -311,3 +311,35 @@
unit0016:
- code_imp: Mappers can now opt out of automatically linking their up/down station
traits.
+2024-11-10:
+ Ghommie:
+ - bugfix: Fixed fishing rod duping with poly belts and shapeshift spells.
+ - spellcheck: Fixed a small typo when examining fishing rods.
+ Majkl-J:
+ - bugfix: Cigarettes can be injected again and have the right amount of nicotine
+ OrionTheFox:
+ - image: redid most basic drinking glass sprites, and moved several drinks to use
+ the same color system as beakers. Please bug report any incorrect colored drinks
+ or juices!
+ Rhials:
+ - bugfix: Blob Overmind/Minion/Blobbernaut speech is now logged. Beware.
+ SmArtKar:
+ - bugfix: Fixed wings and jetpacks sometimes preventing you from opening doors
+ SyncIt21:
+ - bugfix: chem master validates selected container in UI so no more href exploits
+ Xander3359:
+ - bugfix: fix blade ascension not giving you the ring of blades
+ grungussuss:
+ - qol: security helmet straps can be loosened to show hair
+ - sound: only insulated,nitrile, enhanced retrieval, latex, boxing, improvised gripper
+ gloves have an equip sound
+ harryob:
+ - bugfix: dynamic rulesets can get candidates for their roles
+ - bugfix: the abductor console now correctly loads images of equipment
+ timothymtorres:
+ - admin: Admins can now add/remove TRAIT_EVIL from mobs.
+ tontyGH:
+ - server: mob/camera has been renamed to mob/eye, which may break downstreams
+ - bugfix: Teleporting while buckled to something now works as expected
+ - bugfix: You can buckle to anything if you share the same tile (cause at that point
+ it doesn't matter if there's a wall, right?)
diff --git a/icons/mob/human/hair_masks.dmi b/icons/mob/human/hair_masks.dmi
index 45ecb761d9a54..5dbd4917a87e3 100644
Binary files a/icons/mob/human/hair_masks.dmi and b/icons/mob/human/hair_masks.dmi differ
diff --git a/tools/Tgstation.DiscordDiscussions/Program.cs b/tools/Tgstation.DiscordDiscussions/Program.cs
index 1989ef6145070..8766418cfec82 100644
--- a/tools/Tgstation.DiscordDiscussions/Program.cs
+++ b/tools/Tgstation.DiscordDiscussions/Program.cs
@@ -174,7 +174,7 @@ async Task RunAsync(string[] args)
var isReopen = Boolean.Parse(args[7]);
var joinLink = args.Length > 8 ? args[8] : null;
- var prTitle = Environment.GetEnvironmentVariable("GITHUB_PULL_REQUEST_TITLE");
+ var prTitle = Environment.GetEnvironmentVariable("GITHUB_PULL_REQUEST_TITLE")!;
var gitHubClient = new GitHubClient(new ProductHeaderValue("Tgstation.DiscordDiscussions"))
{
@@ -214,6 +214,12 @@ async Task RunAsync(string[] args)
var prLink = $"https://github.com/{repoOwner}/{repoName}/pull/{prNumber}";
var messageContent = $"#{prNumber} - {prTitle}";
+ // thread titles can only be 100 long
+ if (messageContent.Length > 100)
+ {
+ messageContent = $"#{prNumber} - {prTitle[..^(messageContent.Length - 97)]}...";
+ }
+
var channelsClient = serviceProvider.GetRequiredService();
var channelId = new Snowflake(discussionsChannelId);