[MIRROR] Refactors vending machine tilting (#12833)

Co-authored-by: Guti <32563288+TheCaramelion@users.noreply.github.com>
This commit is contained in:
CHOMPStation2StaffMirrorBot
2026-06-21 19:53:56 -04:00
committed by GitHub
co-authored by Guti
parent 76862455ee
commit b09d40013d
6 changed files with 321 additions and 39 deletions
+15
View File
@@ -0,0 +1,15 @@
// from _vending.dm
/// Set if the tipped object successfully crushed a subtype of /mob/living.
#define SUCCESSFULLY_CRUSHED_MOB (1<<0)
/// Set if the tipped object successfully crushed a subtype of /atom.
#define SUCCESSFULLY_CRUSHED_ATOM (1<<1)
/// Set if the tipped object successfully actually fell over, which can fail if it couldn't enter the turf it tried to fall into.
#define SUCCESSFULLY_FELL_OVER (1<<2)
#define CRUSH_CRIT_SHATTER_LEGS "crush_crit_shatter_legs"
#define CRUSH_CRIT_PARAPLEGIC "crush_crit_paraplegic"
#define CRUSH_CRIT_SQUISH_LIMB "crush_crit_pin"
#define CRUSH_CRIT_HEADGIB "crush_crit_headgib"
#define VENDOR_CRUSH_CRIT_PIN "vendor_crush_crit_pin"
#define VENDOR_CRUSH_CRIT_GLASSCANDY "vendor_crush_crit_glasscandy"
+20 -7
View File
@@ -599,13 +599,26 @@ Turf and target are seperate in case you want to teleport some distance from a t
return 1
/proc/is_blocked_turf(turf/T)
var/cant_pass = 0
if(T.density) cant_pass = 1
for(var/atom/A in T)
if(A.density)//&&A.anchored
cant_pass = 1
return cant_pass
/proc/is_blocked_turf(turf/T, exclude_mobs = FALSE, source_atom = null, list/ignore_atoms, type_list = FALSE)
if(T.density)
return TRUE
for(var/atom/movable/movable_content as anything in T.contents)
if((movable_content == source_atom))
continue
if(length(ignore_atoms))
if(!type_list && (movable_content in ignore_atoms))
continue
else if(type_list && is_type_in_list(movable_content, ignore_atoms))
continue
// If the thing is dense AND we're including mobs or the thing isn't a mob AND if there's a source atom and
// it cannot pass through the thing on the turf, we consider the turf blocked.
if(movable_content.density && (!exclude_mobs || !ismob(movable_content)))
if(source_atom && movable_content.CanPass(source_atom, get_dir(T, source_atom)))
continue
return TRUE
return FALSE
/* //If you uncomment and use this I will assimilate you. -C.L.
//Takes: Anything that could possibly have variables and a varname to check.
+1 -1
View File
@@ -230,7 +230,7 @@
else
if(darth_vendor.tilted || !darth_vendor.density)
continue
darth_vendor.crush_under(living_guy)
darth_vendor.tilt(living_guy)
consume_omen()
return
+10 -31
View File
@@ -78,8 +78,12 @@
var/req_log_access = ACCESS_CARGO //default access for checking logs is cargo
var/has_logs = 0 //defaults to 0, set to anything else for vendor to have logs
var/can_rotate = 1 //Defaults to yes, can be set to 0 for vendors without or with unwanted directionals.
var/tilted = FALSE
var/tilted = FALSE
var/tilted_rotation = 0
var/tiltable = TRUE
var/squish_damage = 25
var/crit_chance = 15
/obj/machinery/vending/Initialize(mapload)
. = ..()
@@ -359,10 +363,10 @@ GLOBAL_LIST_EMPTY(vending_products)
if(shock(user, 100))
return
if(tilted)
balloon_alert(user, "setting upright")
if(do_after(user, 2 SECONDS, src))
tilt(TRUE)
if(tilted && !user.buckled)
to_chat(user, span_notice("You begin righting [src]."))
if(do_after(user, 5 SECONDS, target = src))
untilt(user)
return
if(user.a_intent == I_HURT && density)
@@ -755,38 +759,13 @@ GLOBAL_LIST_EMPTY(vending_products)
visible_message(span_warning("\The [src] launches \a [throw_item] at \the [target]!"))
return 1
/obj/machinery/vending/proc/crush_under(mob/living/unlucky)
if(!unlucky)
return
visible_message(span_warning("\The [src] tilts over, falling on [unlucky]!"))
forceMove(get_turf(unlucky))
for(var/mob/living/mob in get_turf(unlucky))
mob.take_overall_damage(30)
mob.Stun(2)
mob.Weaken(5)
tilt()
/obj/machinery/vending/proc/tilt(upright = FALSE)
if(!upright)
var/matrix/M = matrix()
M.Turn(90)
animate(src, transform = M, time = 0.25 SECOND)
playsound(src, 'sound/effects/meteorimpact.ogg', 50, 1)
tilted = TRUE
anchored = FALSE
plane = ABOVE_MOB_PLANE
else
animate(src, transform = matrix(), time = 0.4 SECOND)
tilted = FALSE
plane = initial(plane)
/obj/machinery/vending/proc/punch_machine(mob/living/stupid_person)
stupid_person.visible_message(span_danger("[stupid_person] kicks \the [src]!"), span_danger("You kick \the [src]"))
playsound(src, 'sound/effects/clang2.ogg', 25, TRUE)
animate_shake()
if(prob(10))
crush_under(stupid_person)
tilt(stupid_person)
return
if(prob(5))
+273
View File
@@ -0,0 +1,273 @@
/**
* Can this atom be curshed by the vending machine
* Arguments
*
* * atom/atom_target - the atom we are checking for
*/
/proc/check_atom_crushable(atom/atom_target)
var/static/list/vendor_uncrushable_objects = list(
/obj/structure/bed/chair,
/obj/machinery/conveyor,
/obj/structure/table
)
if(is_type_in_list(atom_target, vendor_uncrushable_objects))
return FALSE
if(atom_target.uses_integrity && !(atom_target.invisibility > SEE_INVISIBLE_LIVING))
return TRUE
return FALSE
/**
* Causes src to fall onto [target], crushing everything on it (including itself) with [damage]
* and a small chance to do a spectacular effect per entity (if a chance above 0 is provided).
*
* Args:
* * turf/target: The turf to fall onto. Cannot be null.
* * damage: The raw numerical damage to do by default.
* * chance_to_crit: The percent chance of a critical hit occurring. Default: 0
* * forced_crit_case: If given a value from crushing.dm, [target] and its contents will always be hit with that specific critical hit. Default: null
* * paralyze_time: The time, in deciseconds, a given mob/living will be paralyzed for if crushed.
* * crush_dir: The direction the crush is coming from. Default: dir of src to [target].
* * damage_type: The type of damage to do. Default: BRUTE
* * damage_flag: The attack flag for armor purposes. Default: MELEE
* * rotation: The angle of which to rotate src's transform by on a successful tilt. Default: 90.
*
* Returns: A collection of bitflags defined in crushing.dm. Read that file's documentation for info.
*/
/atom/movable/proc/fall_and_crush(turf/target, damage, chance_to_crit = 0, forced_crit_case = null, paralyze_time, crush_dir = get_dir(get_turf(src), target), damage_type = BRUTE, damage_flag = "melee", rotation = 90)
ASSERT(!isnull(target))
var/flags_to_return = NONE
if(!is_blocked_turf(target, TRUE, src, list(src)))
for(var/atom/atom_target in (target.contents) + target)
if(isarea(atom_target))
continue
if(SEND_SIGNAL(atom_target, COMSIG_PRE_TILT_AND_CRUSH, src) & COMPONENT_IMMUNE_TO_TILT_AND_CRUSH)
continue
var/crit_case = forced_crit_case
if(isnull(crit_case) && chance_to_crit > 0)
if(prob(chance_to_crit))
crit_case = pick_weight(get_crit_crush_chances())
var/crit_rebate_mult = 1
if(!isnull(crit_case))
crit_rebate_mult = fall_and_crush_crit_rebate_table(crit_case)
apply_crit_crush(crit_case, atom_target)
var/adjusted_damage = damage * crit_rebate_mult
var/crushed
if(isliving(atom_target))
crushed = TRUE
var/mob/living/carbon/human/living_target = atom_target
var/was_alive = living_target.stat != DEAD
var/blocked = living_target.run_armor_check(attack_flag = damage_flag)
if(iscarbon(living_target))
var/mob/living/carbon/carbon_target = living_target
if(prob(30))
carbon_target.take_overall_damage(adjusted_damage)
else
var/brute = (damage_type == BRUTE ? damage : 0) * 0.5
var/burn = (damage_type == BURN ? damage : 0) * 0.5
carbon_target.apply_damage(brute, BRUTE, pick(BP_ALL))
carbon_target.apply_damage(burn, BURN, pick(BP_ALL))
else
living_target.apply_damage(adjusted_damage, damage_type, blocked = blocked)
living_target.Stun(paralyze_time)
living_target.Weaken(paralyze_time*2)
living_target.emote("scream")
playsound(src, 'sound/effects/meteorimpact.ogg', 50, 1)
post_crush_living(living_target, was_alive)
flags_to_return |= (SUCCESSFULLY_CRUSHED_MOB|SUCCESSFULLY_CRUSHED_ATOM)
else if(check_atom_crushable(atom_target))
atom_target.take_damage(adjusted_damage, damage_type, damage_flag)
crushed = TRUE
flags_to_return |= SUCCESSFULLY_CRUSHED_ATOM
if(crushed)
atom_target.visible_message(span_danger("[atom_target] is crushed by [src]!"), span_userdanger("You are crushed by [src]!"))
SEND_SIGNAL(atom_target, COMSIG_POST_TILT_AND_CRUSH, src)
var/matrix/to_turn = turn(transform, rotation)
animate(src, transform = to_turn, 0.2 SECONDS)
playsound(src, 'sound/effects/bang.ogg', 40)
visible_message(span_danger("[src] tips over, slamming hard onto [target]!"))
flags_to_return |= SUCCESSFULLY_FELL_OVER
post_tilt()
else
visible_message(span_danger("[src] rebounts comically as it fails to slam onto [target]!"))
forceMove(target, crush_dir)
return flags_to_return
/**
* Returns a assoc list of (critcase -> num), where critcase is a critical define in crushing.dm and num is a weight.
* Use with pickweight to acquire a random critcase.
*/
/atom/movable/proc/get_crit_crush_chances()
RETURN_TYPE(/list)
return list(
CRUSH_CRIT_SHATTER_LEGS = 100,
/*
CRUSH_CRIT_PARAPLEGIC = 80,
CRUSH_CRIT_HEADGIB = 20,
*/
CRUSH_CRIT_SQUISH_LIMB = 100
)
/**
* Exists for the purposes of custom behavior.
* Called directly after [crushed] is crushed.
*
* Args:
* * mob/living/crushed: The mob that was crushed.
* * was_alive: Boolean. True if the mob was alive before the crushing.
*/
/atom/movable/proc/post_crush_living(mob/living/crushed, was_alive)
return
/**
* Exists for the purposes of custom behavior.
* Called directly after src actually rotates and falls over.
*/
/atom/movable/proc/post_tilt()
return
/**
* Should be where critcase effects are actually implemented. Use this to apply critcases.
* Args:
* * crit_case: The chosen critcase, defined in crushing.dm.
* * atom/atom_target: The target to apply the critical hit to. Cannot be null. Can be anything except /area.
*
* Returns:
* TRUE if a crit case is successfully applied, FALSE otherwise.
*/
/atom/movable/proc/apply_crit_crush(crit_case, atom/atom_target)
switch(crit_case)
if(CRUSH_CRIT_SHATTER_LEGS)
if(!ishuman(atom_target))
return FALSE
var/mob/living/carbon/human/human_target = atom_target
var/obj/item/organ/external/left_leg = human_target.get_organ(BP_L_LEG)
if(left_leg)
left_leg.fracture()
var/obj/item/organ/external/right_leg = human_target.get_organ(BP_R_LEG)
if(right_leg)
right_leg.fracture()
if(left_leg || right_leg)
human_target.visible_message(span_danger("[human_target]'s legs shatter with a sickening crunch!"), span_userdanger("Your legs shatter with a sickening crunch!"))
return TRUE
if(CRUSH_CRIT_SQUISH_LIMB)
if(!ishuman(atom_target))
return FALSE
var/mob/living/carbon/human/human_target = atom_target
for(var/obj/item/organ/external/squish_part in human_target.organs)
squish_part.take_damage(15)
human_target.visible_message(span_danger("[human_target]'s body is maimed underneath the mass of [src]!"), span_userdanger("Your body is maimed underneath the mass of [src]!"))
return TRUE
return FALSE
/**
* Tilts ontop of the atom supplied, if crit is true some extra shit can happen. See [fall_and_crush] for return values.
* Arguments:
* fatty - atom to tilt the vendor onto
* local_crit_chance - percent chance of a critical hit
* forced_crit - specific critical hit case to use, if any
* range - the range of the machine when thrown if not adjacent
*/
/obj/machinery/vending/proc/tilt(atom/fatty, local_crit_chance = crit_chance, forced_crit, range = 1)
if(QDELETED(src))
return
. = NONE
var/picked_rotation = pick(90, 270)
if(Adjacent(fatty))
. = fall_and_crush(get_turf(fatty), squish_damage, local_crit_chance, forced_crit, 4, rotation = picked_rotation)
if(. & SUCCESSFULLY_FELL_OVER)
visible_message(span_danger("[src] tips over!"))
tilted = TRUE
tilted_rotation = picked_rotation
anchored = FALSE
plane = ABOVE_MOB_PLANE
if(get_turf(fatty) != get_turf(src))
throw_at(get_turf(fatty), range, 1, FALSE)
/**
* Allows damage to be reduced on certain crit cases.
* Args:
* * crit_case: The critical case chosen.
*/
/atom/movable/proc/fall_and_crush_crit_rebate_table(crit_case)
ASSERT(!isnull(crit_case))
switch(crit_case)
if (CRUSH_CRIT_SHATTER_LEGS)
return 0.2
else
return 1
/obj/machinery/vending/fall_and_crush_crit_rebate_table(crit_case)
return crit_case == VENDOR_CRUSH_CRIT_GLASSCANDY ? 0.33 : ..()
/obj/machinery/vending/get_crit_crush_chances()
return list(
VENDOR_CRUSH_CRIT_GLASSCANDY = 100,
VENDOR_CRUSH_CRIT_PIN = 100
)
/obj/machinery/vending/apply_crit_crush(crit_case, atom_target)
. = ..()
if (.)
return TRUE
switch (crit_case)
if(VENDOR_CRUSH_CRIT_GLASSCANDY)
if (!ishuman(atom_target))
return FALSE
var/mob/living/carbon/human/human_target = atom_target
for(var/i in 1 to 7)
var/obj/item/material/shard/shard = new /obj/item/material/shard(get_turf(human_target))
human_target.embed(shard, def_zone = pick(BP_ALL))
if(VENDOR_CRUSH_CRIT_PIN)
if(!isliving(atom_target))
return FALSE
var/mob/living/living_target = atom_target
forceMove(get_turf(living_target))
buckle_mob(living_target, TRUE)
living_target.visible_message(span_danger("[living_target] is pinned underneath [src]!"), span_userdanger("You are pinned down by [src]!"))
living_target.Weaken(20)
return TRUE
return FALSE
/**
* Rights the vendor up, unpinning mobs under it, if any.
* Arguments:
* user - mob that has untilted the vendor
*/
/obj/machinery/vending/proc/untilt(mob/user)
if(user)
user.visible_message(span_notice("[user] rights [src]."), \
span_notice("You right [src]."))
unbuckle_all_mobs(TRUE)
tilted = FALSE
plane = initial(layer)
var/matrix/to_turn = turn(transform, -tilted_rotation)
animate(src, transform = to_turn, 0.2 SECONDS)
tilted_rotation = 0
+2
View File
@@ -86,6 +86,7 @@
#include "code\__defines\construction_datum.dm"
#include "code\__defines\cooldowns.dm"
#include "code\__defines\crafting.dm"
#include "code\__defines\crushing.dm"
#include "code\__defines\damage_organs.dm"
#include "code\__defines\database.dm"
#include "code\__defines\departmentgoals.dm"
@@ -2915,6 +2916,7 @@
#include "code\modules\economy\vending_machines.dm"
#include "code\modules\economy\vending_machines_vr.dm"
#include "code\modules\economy\vending_refills.dm"
#include "code\modules\economy\vending_tilting.dm"
#include "code\modules\emotes\custom_emote.dm"
#include "code\modules\emotes\emote_define.dm"
#include "code\modules\emotes\emote_mob.dm"