From 4d68b7d26874bd96e3a95dbca0557e0a8e4f5bce Mon Sep 17 00:00:00 2001 From: Aziz Chynaliev Date: Wed, 11 Sep 2024 18:06:57 +0500 Subject: [PATCH] add: new shoves impact + brand intelligence tweaks (#5608) [testmerge][16cf50b] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 16cf50b1745567f50d3fff89a4b075e509ca1977 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Mon Sep 2 17:41:11 2024 +0500 багфиксы 1 commit 00d8da208f745bfadc0d4c9a4f4eb9c7afa45d27 Merge: 682dadbb1f 5189da6256 Author: Aziz Chynaliev Date: Sat Aug 31 19:21:24 2024 +0600 Merge branch 'master220' into shove_impacts commit 682dadbb1f15d1116c846ae0ab995a3bd4255e58 Merge: e4dfd415d9 59b2a65e88 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Sun Aug 25 12:26:41 2024 +0500 Merge remote-tracking branch 'upstream/master220' into shove_impacts commit e4dfd415d96371883b53592fa4ade6a12bd021b5 Merge: 3f9c6b3f4c 45f832320d Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Sun Aug 25 12:18:04 2024 +0500 Merge remote-tracking branch 'upstream/master220' into shove_impacts commit 3f9c6b3f4c81976ec8894d612208c69e9ff78819 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Thu Aug 15 10:34:52 2024 +0500 упс commit 8256edc308dfd84bb6b7c79c600208894b569594 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Thu Aug 15 10:33:32 2024 +0500 В свое оправдание скажу, что я лениво скопирнул.. commit 531b0917edd8eb6538ecbe4f6f8f9290ed5b74bc Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Sun Aug 4 16:30:40 2024 +0500 совместимость с новым кодом звея вроде бы commit b9ae3586546fa7d0d47914681fa76a80d7e42a3f Merge: a46cbe58dc f10796f352 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Sun Aug 4 16:26:33 2024 +0500 Merge remote-tracking branch 'upstream/master220' into shove_impacts commit a46cbe58dc0735d4b32bfa33132bc86365978759 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Sun Aug 4 15:50:57 2024 +0500 буквы читать скучно( commit 830868801bfaa4648f3e264bef86ccd4c5735c66 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Fri Aug 2 12:09:14 2024 +0500 фиксы по требованию звея commit 8b29f491dd211dbcd3231f2f91d081aaae647356 Merge: 44333e73aa daa9ed4cb4 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Fri Aug 2 12:02:13 2024 +0500 Merge remote-tracking branch 'upstream/master220' into shove_impacts commit 44333e73aac045e0cef57300f5fa6723d5bc3bd6 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Mon Jul 29 16:33:55 2024 +0500 выловленный рантайм commit 8fdf480452c4e125a183cd5decff5ac17de94b94 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Mon Jul 29 16:08:56 2024 +0500 блядь commit 92667893993326513e9870b70b8098a435478636 Merge: 7902ede604 c2f0479667 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Mon Jul 29 16:06:14 2024 +0500 Merge remote-tracking branch 'upstream/master220' into shove_impacts commit 7902ede604ee8c0df20273b00ffe2e2b2c11ee88 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Mon Jul 29 15:41:48 2024 +0500 паайдет commit dec2c327a49d35d8c79e002e9fc136489703dc41 Author: NightDawnFox <116907157+NightDawnFox@users.noreply.github.com> Date: Fri Jul 19 18:34:11 2024 +0500 temp --- code/__DEFINES/traits/declarations.dm | 2 + code/__DEFINES/traits/sources.dm | 2 + code/_globalvars/traits.dm | 1 + code/datums/elements/squish.dm | 64 ++++ code/game/machinery/suit_storage_unit.dm | 17 + code/game/machinery/vending.dm | 324 ++++++++++++++++++ code/game/machinery/vending_crit.dm | 128 +++++++ code/game/objects/items/bodybag.dm | 2 + .../structures/crates_lockers/closets.dm | 27 ++ .../crates_lockers/closets/fireaxe.dm | 3 + .../crates_lockers/closets/statue.dm | 3 + .../structures/crates_lockers/crittercrate.dm | 3 + code/modules/events/brand_intelligence.dm | 12 +- .../mob/living/simple_animal/hostile/mimic.dm | 50 ++- code/modules/recycling/disposal.dm | 13 + paradise.dme | 2 + 16 files changed, 650 insertions(+), 3 deletions(-) create mode 100644 code/datums/elements/squish.dm create mode 100644 code/game/machinery/vending_crit.dm diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 9d1aed47d67..5531d74999c 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -66,6 +66,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai #define TRAIT_IGNOREDAMAGESLOWDOWN "ignoredamageslowdown" #define TRAIT_STRONG_GRABBER "strong_grabber" #define TRAIT_PUSHIMMUNE "push_immunity" +#define TRAIT_FLATTENED "flattened" + /// Not a genetic obesity but just a mob who overate #define TRAIT_FAT "trait_fat" #define TRAIT_HUSK "husk" diff --git a/code/__DEFINES/traits/sources.dm b/code/__DEFINES/traits/sources.dm index 6d40c768ada..326beb123a8 100644 --- a/code/__DEFINES/traits/sources.dm +++ b/code/__DEFINES/traits/sources.dm @@ -150,3 +150,5 @@ #define EVIL_FAX_TRAIT "evil_fax" #define CORGI_HARDSUIT_TRAIT "corgi_hardsuit" +#define VENDOR_FLATTENING_TRAIT "vendor_flattening" + diff --git a/code/_globalvars/traits.dm b/code/_globalvars/traits.dm index 3cc5282d226..e857ed87000 100644 --- a/code/_globalvars/traits.dm +++ b/code/_globalvars/traits.dm @@ -51,6 +51,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_EXOTIC_BLOOD" = TRAIT_EXOTIC_BLOOD, "TRAIT_FAKEDEATH" = TRAIT_FAKEDEATH, "TRAIT_FAT" = TRAIT_FAT, + "TRAIT_FLATTENED" = TRAIT_FLATTENED, "TRAIT_FLOORED" = TRAIT_FLOORED, "TRAIT_FORCE_DOORS" = TRAIT_FORCE_DOORS, "TRAIT_FORCED_GRAVITY" = TRAIT_FORCED_GRAVITY, diff --git a/code/datums/elements/squish.dm b/code/datums/elements/squish.dm new file mode 100644 index 00000000000..41b90d8fb68 --- /dev/null +++ b/code/datums/elements/squish.dm @@ -0,0 +1,64 @@ +#define SHORT_SCALE (5/7) +#define TALL_SCALE (7/5) + +/** + * squish.dm + * + * It's an element that squishes things. After the duration passes, it reverses the transformation it squished with, taking into account if they are a different orientation than they started (read: rotationally-fluid) + * + * Normal squishes apply vertically, as if the target is being squished from above, but you can set reverse to TRUE if you want to squish them from the sides, like if they pancake into a wall from the East or West +*/ + +/datum/element/squish + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + +/datum/element/squish/Attach(datum/target, duration = 20 SECONDS, reverse = FALSE) + . = ..() + if(!iscarbon(target)) + return ELEMENT_INCOMPATIBLE + + var/mob/living/carbon/C = target + var/was_lying = C.body_position == LYING_DOWN + addtimer(CALLBACK(src, PROC_REF(Detach), C, was_lying, reverse), duration) + ADD_TRAIT(target, TRAIT_FLATTENED, VENDOR_FLATTENING_TRAIT) + + if(reverse) + C.transform = C.transform.Scale(SHORT_SCALE, TALL_SCALE) + + else + C.transform = C.transform.Scale(TALL_SCALE, SHORT_SCALE) + +/datum/element/squish/Detach(mob/living/carbon/C, was_lying, reverse) + . = ..() + if(!istype(C)) + return + var/is_lying = C.body_position == LYING_DOWN + REMOVE_TRAIT(C, TRAIT_FLATTENED, VENDOR_FLATTENING_TRAIT) + + if(reverse) + is_lying = !is_lying + + if(was_lying == is_lying) + C.transform = C.transform.Scale(SHORT_SCALE, TALL_SCALE) + else + C.transform = C.transform.Scale(TALL_SCALE, SHORT_SCALE) + +#undef SHORT_SCALE +#undef TALL_SCALE + +/datum/element/tilt_protection + element_flags = ELEMENT_DETACH_ON_HOST_DESTROY + +/datum/element/tilt_protection/Attach(datum/target, duration = 20 SECONDS) + . = ..() + if(!iscarbon(target)) + return ELEMENT_INCOMPATIBLE + var/mob/living/carbon/C = target + addtimer(CALLBACK(src, PROC_REF(Detach), C), duration) + ADD_TRAIT(target, TRAIT_FLATTENED, VENDOR_FLATTENING_TRAIT) + +/datum/element/tilt_protection/Detach(mob/living/carbon/C) + . = ..() + if(!istype(C)) + return + REMOVE_TRAIT(C, TRAIT_FLATTENED, VENDOR_FLATTENING_TRAIT) diff --git a/code/game/machinery/suit_storage_unit.dm b/code/game/machinery/suit_storage_unit.dm index 219075a5113..cd159701e92 100644 --- a/code/game/machinery/suit_storage_unit.dm +++ b/code/game/machinery/suit_storage_unit.dm @@ -753,6 +753,23 @@ do_sparks(5, 0, loc) playsound(loc, "sparks", 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE) +/obj/machinery/suit_storage_unit/shove_impact(mob/living/target, mob/living/attacker) + if(target.incapacitated() || HAS_TRAIT(target, TRAIT_HANDS_BLOCKED) || target.buckled) + return + if(!state_open && !locked) + state_open = TRUE + update_icon(UPDATE_OVERLAYS) + return ..() + + if(broken) + return ..() + + if((occupant) || (helmet) || (suit) || (storage)) + return ..() + + close_machine(target) + return TRUE + //pirate ssu /obj/machinery/suit_storage_unit/industrial name = "industrial suit storage unit" diff --git a/code/game/machinery/vending.dm b/code/game/machinery/vending.dm index 85bc05a762a..f4dcb8d623d 100644 --- a/code/game/machinery/vending.dm +++ b/code/game/machinery/vending.dm @@ -6,6 +6,14 @@ /// Machine is currently denying wares, and will not update its icon, unless its stat change. #define FLICK_DENY 2 +// Using these to decide how a vendor crush should be handled after crushing a carbon. +/// Just jump ship, the crit handled everything it needs to. +#define VENDOR_CRUSH_HANDLED 0 +/// Throw the vendor at the target's tile. +#define VENDOR_THROW_AT_TARGET 1 +/// Don't actually throw at the target, just tip it in place. +#define VENDOR_TIP_IN_PLACE 2 + /** * Datum used to hold information about a product in a vending machine @@ -136,6 +144,37 @@ var/light_range_on = 1 var/light_power_on = 0.5 + /// If this vending machine can be tipped or not + var/tiltable = TRUE + /// If this vendor is currently tipped + var/tilted = FALSE + /// If tilted, this variable should always be the rotation that was applied when we were tilted. Stored for the purposes of unapplying it. + var/tilted_rotation = 0 + /// Amount of damage to deal when tipped + var/squish_damage = 30 // yowch + /// Factor of extra damage to deal when triggering a crit + var/crit_damage_factor = 2 + /// Factor of extra damage to deal when you knock it over onto yourself + var/self_knockover_factor = 1.5 + /// All possible crits that could be applied. We only need to build this up once + var/static/list/all_possible_crits = list() + /// Possible crit effects from this vending machine tipping. + var/list/possible_crits = list( + // /datum/vendor_crit/pop_head, //too much i think + /datum/vendor_crit/embed, + /datum/vendor_crit/pin, + /datum/vendor_crit/shatter, + /datum/vendor_crit/lucky + ) + /// number of shards to apply when a crit embeds + var/num_shards = 4 + /// How long to wait before resetting the warning cooldown + var/hit_warning_cooldown_length = 10 SECONDS + /// Cooldown for warning cooldowns + COOLDOWN_DECLARE(last_hit_time) + /// If the vendor should tip on anyone who walks by. Mainly used for brand intelligence + var/aggressive = FALSE + /obj/machinery/vending/Initialize(mapload) . = ..() var/build_inv = FALSE @@ -164,8 +203,26 @@ // so if slogantime is 10 minutes, it will say it at somewhere between 10 and 20 minutes after the machine is created. last_slogan = world.time + rand(0, slogan_delay) + if(!length(all_possible_crits)) + for(var/typepath in subtypesof(/datum/vendor_crit)) + all_possible_crits[typepath] = new typepath() + update_icon(UPDATE_OVERLAYS) +/obj/machinery/vending/examine(mob/user) + . = ..() + if(tilted) + . += span_warning("It's been tipped over and won't be usable unless it's righted.") + if(Adjacent(user)) + . += span_notice("You can Alt-Click it to right it.") + if(aggressive) + . += span_warning("Its product lights seem to be blinking ominously...") + +/obj/machinery/vending/AltClick(mob/user) + if(!tilted || !Adjacent(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) + return + + untilt(user) /obj/machinery/vending/Destroy() SStgui.close_uis(wires) @@ -401,6 +458,12 @@ /obj/machinery/vending/attackby(obj/item/I, mob/user, params) + if(tilted) + if(user.a_intent == INTENT_HELP) + to_chat(user, span_warning("[src] is tipped over and non-functional! You'll need to right it first.")) + return + return ..() + if(user.a_intent == INTENT_HARM) return ..() @@ -447,23 +510,84 @@ insert_item(user, I) return ATTACK_CHAIN_BLOCKED_ALL + try_tilt(I, user) return ..() +/obj/machinery/vending/proc/try_tilt(obj/item/I, mob/user) + if(tiltable && !tilted && I.force) + if(resistance_flags & INDESTRUCTIBLE) + // no goodies, but also no tilts + return + if(COOLDOWN_FINISHED(src, last_hit_time)) + visible_message(span_warning("[src] seems to sway a bit!")) + to_chat(user, span_userdanger("You might want to think twice about doing that again, [src] looks like it could come crashing down!")) + COOLDOWN_START(src, last_hit_time, hit_warning_cooldown_length) + return + + switch(rand(1, 100)) + if(1 to 5) + freebie(user, 3) + if(6 to 15) + freebie(user, 2) + if(16 to 25) + freebie(user, 1) + if(26 to 75) + return + if(76 to 90) + tilt(user) + if(91 to 100) + tilt(user, crit = TRUE) + +/obj/machinery/vending/proc/freebie(mob/user, num_freebies) + visible_message(span_notice("[num_freebies] free goodie\s tumble[num_freebies > 1 ? "" : "s"] out of [src]!")) + for(var/i in 1 to num_freebies) + for(var/datum/data/vending_product/R in shuffle(product_records)) + if(R.amount <= 0) + continue + var/dump_path = R.product_path + if(!dump_path) + continue + new dump_path(get_turf(src)) + R.amount-- + break + +/obj/machinery/vending/HasProximity(atom/movable/AM) + if(!aggressive || tilted || !tiltable) + return + + if(isliving(AM) && prob(25)) + AM.visible_message( + span_warning("[src] suddenly topples over onto [AM]!"), + span_userdanger("[src] topples over onto you without warning!") + ) + tilt(AM, prob(5), FALSE) + aggressive = FALSE + //Not making same mistakes as offs did. + // Don't make this brob more than 5% /obj/machinery/vending/crowbar_act(mob/user, obj/item/I) if(!component_parts) return . = TRUE + if(tilted) + to_chat(user, span_warning("You'll need to right it first!")) + return default_deconstruction_crowbar(user, I) /obj/machinery/vending/multitool_act(mob/user, obj/item/I) . = TRUE + if(tilted) + to_chat(user, span_warning("You'll need to right it first!")) + return if(!I.use_tool(src, user, 0, volume = I.tool_volume)) return wires.Interact(user) /obj/machinery/vending/screwdriver_act(mob/user, obj/item/I) . = TRUE + if(tilted) + to_chat(user, span_warning("You'll need to right it first!")) + return if(!I.use_tool(src, user, 0, volume = I.tool_volume)) return if(anchored) @@ -474,15 +598,36 @@ /obj/machinery/vending/wirecutter_act(mob/user, obj/item/I) . = TRUE + if(tilted) + to_chat(user, span_warning("You'll need to right it first!")) + return if(I.use_tool(src, user, 0, volume = 0)) wires.Interact(user) /obj/machinery/vending/wrench_act(mob/user, obj/item/I) . = TRUE + if(tilted) + to_chat(user, span_warning("The fastening bolts aren't on the ground, you'll need to right it first!")) + return if(!I.use_tool(src, user, 0, volume = 0)) return default_unfasten_wrench(user, I, time = 60) +/obj/machinery/vending/ex_act(severity) + . = ..() + if(QDELETED(src) || (resistance_flags & INDESTRUCTIBLE) || tilted || !tiltable) + return + var/tilt_prob = 0 + switch(severity) + if(EXPLODE_LIGHT) + tilt_prob = 10 + if(EXPLODE_HEAVY) + tilt_prob = 50 + if(EXPLODE_DEVASTATE) + tilt_prob = 80 + if(prob(tilt_prob)) + tilt() + //Override this proc to do per-machine checks on the inserted item, but remember to call the parent to handle these generic checks before your logic! /obj/machinery/vending/proc/item_slot_check(mob/user, obj/item/I) if(!item_slot) @@ -567,6 +712,10 @@ if(stat & (BROKEN|NOPOWER)) return + if(tilted) + to_chat(user, span_warning("[src] is tipped over and non-functional! You'll need to right it first.")) + return + if(..()) return TRUE @@ -948,6 +1097,175 @@ throw_item.throw_at(target, 16, 3) visible_message("[src] launches [throw_item.name] at [target.name]!") + +/obj/machinery/vending/shove_impact(mob/living/target, mob/living/attacker) + if(HAS_TRAIT(target, TRAIT_FLATTENED)) + return + if(!HAS_TRAIT(attacker, TRAIT_PACIFISM) || !GLOB.pacifism_after_gt) + add_attack_logs(attacker, target, "shoved into a vending machine ([src])") + tilt(target, from_combat = TRUE) + target.visible_message( + span_danger("[attacker] slams [target] into [src]!"), + span_userdanger("You get slammed into [src] by [attacker]!"), + span_danger(">You hear a loud crunch.") + ) + else + attacker.visible_message( + span_notice("[attacker] lightly presses [target] against [src]."), + span_userdanger("You lightly press [target] against [src], you don't want to hurt [target.p_them()]!") + ) + return TRUE + +/** + * Select a random valid crit. + */ +/obj/machinery/vending/proc/choose_crit(mob/living/carbon/victim) + if(!length(possible_crits)) + return + for(var/crit_path in shuffle(possible_crits)) + var/datum/vendor_crit/C = all_possible_crits[crit_path] + if(C.is_valid(src, victim)) + return C + +/obj/machinery/vending/proc/handle_squish_carbon(mob/living/carbon/victim, damage_to_deal, crit, from_combat) + + // Damage points to "refund", if a crit already beats the shit out of you we can shelve some of the extra damage. + var/crit_rebate = 0 + + var/should_throw_at_target = TRUE + + var/datum/vendor_crit/critical_attack = choose_crit(victim) + if(!from_combat && crit && critical_attack) + crit_rebate = critical_attack.tip_crit_effect(src, victim) + if(critical_attack.harmless) + tilt_over(critical_attack.fall_towards_mob ? victim : null) + return VENDOR_CRUSH_HANDLED + + should_throw_at_target = critical_attack.fall_towards_mob + add_attack_logs(null, victim, "critically crushed by [src] causing [critical_attack]") + + else + victim.visible_message( + span_danger("[victim] is crushed by [src]!"), + span_userdanger("[src] crushes you!"), + span_warning("You hear a loud crunch!") + ) + add_attack_logs(null, victim, "crushed by [src]") + + // 30% chance to spread damage across the entire body, 70% chance to target two limbs in particular + damage_to_deal = max(damage_to_deal - crit_rebate, 0) + if(prob(30)) + victim.apply_damage(damage_to_deal, BRUTE, spread_damage = TRUE) + else + var/picked_zone + var/num_parts_to_pick = 2 + for(var/i = 1 to num_parts_to_pick) + picked_zone = pick(BODY_ZONE_CHEST, BODY_ZONE_HEAD, BODY_ZONE_L_ARM, BODY_ZONE_L_LEG, BODY_ZONE_R_ARM, BODY_ZONE_R_LEG) + victim.apply_damage((damage_to_deal) * (1 / num_parts_to_pick), BRUTE, picked_zone) + + victim.AddElement(/datum/element/tilt_protection, 80 SECONDS) // use "/datum/element/squish" when people are ready for that. + if(victim.has_pain()) + victim.emote("scream") + + return should_throw_at_target ? VENDOR_THROW_AT_TARGET : VENDOR_TIP_IN_PLACE + +/** + * Tilts the machine onto the atom passed in. + * + * Arguments: + * * target_atom - The thing the machine is falling on top of + * * crit - if true, some special damage effects might happen. + * * from_combat - If true, hold off on some of the additional damage and extra effects. + */ + +/obj/machinery/vending/proc/tilt(atom/target_atom, crit = FALSE, from_combat = FALSE) + if(QDELETED(src) || !has_gravity(src) || !tiltable || tilted) + return + + tilted = TRUE + set_anchored(FALSE) + layer = ABOVE_MOB_LAYER + + var/should_throw_at_target = TRUE + + . = FALSE + + if(!target_atom || !in_range(target_atom, src)) + tilt_over() + return + for(var/mob/living/victim in get_turf(target_atom)) + // Damage to deal outright + var/damage_to_deal = squish_damage + if(!from_combat) + if(crit) + // increase damage if you knock it over onto yourself + damage_to_deal *= crit_damage_factor + else + damage_to_deal *= self_knockover_factor + + if(iscarbon(victim)) + var/throw_spec = handle_squish_carbon(target_atom, damage_to_deal, crit, from_combat) + switch(throw_spec) + if(VENDOR_CRUSH_HANDLED) + return TRUE + if(VENDOR_THROW_AT_TARGET) + should_throw_at_target = TRUE + if(VENDOR_TIP_IN_PLACE) + should_throw_at_target = FALSE + else + victim.visible_message( + span_danger("[victim] is crushed by [src]!"), + span_userdanger("[src] falls on top of you, crushing you!"), + span_warning("You hear a loud crunch!") + ) + victim.apply_damage(damage_to_deal, BRUTE) + add_attack_logs(null, victim, "crushed by [src]") + + . = TRUE + victim.Weaken(4 SECONDS) + victim.Knockdown(8 SECONDS) + + playsound(victim, "sound/effects/blobattack.ogg", 40, TRUE) + playsound(victim, "sound/effects/splat.ogg", 50, TRUE) + + tilt_over(should_throw_at_target ? target_atom : null) + +/obj/machinery/vending/proc/tilt_over(mob/victim) + visible_message( span_danger("[src] tips over!")) + playsound(src, "sound/effects/bang.ogg", 100, TRUE) + var/picked_rotation = pick(90, 270) + tilted_rotation = picked_rotation + var/matrix/to_turn = turn(transform, tilted_rotation) + animate(src, transform = to_turn, 0.2 SECONDS) + + if(victim && get_turf(victim) != get_turf(src)) + throw_at(get_turf(victim), 1, 1, spin = FALSE) + +/obj/machinery/vending/proc/untilt(mob/user) + if(!tilted) + return + + if(user) + user.visible_message( + "[user] begins to right [src].", + "You begin to right [src]." + ) + if(!do_after(user, 7 SECONDS, src)) + return + user.visible_message( + span_notice("[user] rights [src]."), + span_notice("You right [src]."), + span_notice(">You hear a loud clang.") + ) + + unbuckle_all_mobs(TRUE) + + tilted = FALSE + layer = initial(layer) + + var/matrix/to_turn = turn(transform, -tilted_rotation) + animate(src, transform = to_turn, 0.2 SECONDS) + /obj/machinery/vending/assist icon_state = "generic_off" @@ -1468,6 +1786,7 @@ armor = list(melee = 50, bullet = 20, laser = 20, energy = 20, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 70) resistance_flags = FIRE_PROOF refill_canister = /obj/item/vending_refill/wallmed + tiltable = FALSE /obj/machinery/vending/wallmed/syndicate name = "\improper SyndiWallMed" @@ -1707,6 +2026,7 @@ contraband = list(/obj/item/reagent_containers/glass/bottle/wizarditis = 1) armor = list(melee = 100, bullet = 100, laser = 100, energy = 100, bomb = 0, bio = 0, rad = 0, fire = 100, acid = 50) resistance_flags = FIRE_PROOF + tiltable = FALSE /obj/machinery/vending/autodrobe @@ -3000,6 +3320,7 @@ ) contraband = list(/obj/item/clothing/glasses/sunglasses = 2,/obj/item/storage/fancy/donut_box = 2,/obj/item/grenade/clusterbuster/apocalypsefake = 1) refill_canister = /obj/item/vending_refill/nta + tiltable = FALSE //no ert tilt /obj/machinery/vending/nta/ertarmory resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF | FREEZE_PROOF @@ -3624,3 +3945,6 @@ #undef FLICK_VEND #undef FLICK_DENY +#undef VENDOR_CRUSH_HANDLED +#undef VENDOR_THROW_AT_TARGET +#undef VENDOR_TIP_IN_PLACE diff --git a/code/game/machinery/vending_crit.dm b/code/game/machinery/vending_crit.dm new file mode 100644 index 00000000000..b0a4902386a --- /dev/null +++ b/code/game/machinery/vending_crit.dm @@ -0,0 +1,128 @@ +/** + * Framework for custom vendor crits. + */ + +/datum/vendor_crit + /// If it'll deal damage or not + var/harmless = FALSE + /// If we should be thrown against the mob or not. + var/fall_towards_mob = TRUE + +/** + * Return whether or not the crit selected is valid. + */ +/datum/vendor_crit/proc/is_valid(obj/machinery/vending/machine, mob/living/carbon/victim) + return TRUE + +/*** + * Perform the tip crit effect on a victim. + * Arguments: + * * machine - The machine that was tipped over + * * user - The unfortunate victim upon whom it was tipped over + * Returns: The "crit rebate", or the amount of damage to subtract from the original amount of damage dealt, to soften the blow. + */ +/datum/vendor_crit/proc/tip_crit_effect(obj/machinery/vending/machine, mob/living/carbon/victim) + return 0 + +/datum/vendor_crit/shatter + +/datum/vendor_crit/shatter/tip_crit_effect(obj/machinery/vending/machine, mob/living/carbon/victim) + victim.bleed(150) + var/obj/item/organ/external/leg/right = victim.get_organ(BODY_ZONE_R_LEG) + var/obj/item/organ/external/leg/left = victim.get_organ(BODY_ZONE_L_LEG) + left.external_receive_damage(50) + left.fracture() + right.external_receive_damage(50) + right.fracture() + + if(left || right) + victim.visible_message( + span_danger("[victim]'s legs shatter with a sickening crunch!"), + span_userdanger("Your legs shatter with a sickening crunch!"), + span_danger("You hear a sickening crunch!") + ) + + // that's a LOT of damage, let's rebate most of it. + return machine.squish_damage * (5/6) + +/datum/vendor_crit/pin + +/datum/vendor_crit/pin/tip_crit_effect(obj/machinery/vending/machine, mob/living/carbon/victim) + var/turf/our_turf = get_turf(victim) + if(!our_turf) + return + machine.forceMove(our_turf) + machine.buckle_mob(victim, force=TRUE) + victim.visible_message( + span_danger("[victim] gets pinned underneath [machine]!"), + span_userdanger("You are pinned down by [machine]!") + ) + + return 0 + +/datum/vendor_crit/embed + +/datum/vendor_crit/embed/is_valid(obj/machinery/vending/machine, mob/living/carbon/victim) + . = ..() + if(machine.num_shards <= 0) + return FALSE + +/datum/vendor_crit/embed/tip_crit_effect(obj/machinery/vending/machine, mob/living/carbon/victim) + var/turf/our_turf = get_turf(victim) + if(!our_turf) + return + victim.visible_message( + span_danger("[machine]'s panel shatters against [victim]!"), + span_userdanger("[machine] lands on you, its panel shattering!") + ) + + for(var/i in 1 to machine.num_shards) + var/obj/item/shard/shard = new /obj/item/shard(our_turf) + // do a little dance to force the embeds, but make sure the glass isn't gigapowered afterwards + shard.embed_chance = 100 + shard.embedded_pain_chance = 5 + shard.embedded_impact_pain_multiplier = 1 + shard.embedded_ignore_throwspeed_threshold = TRUE + victim.hitby(shard, skipcatch = TRUE, hitpush = FALSE) + shard.embed_chance = initial(shard.embed_chance) + shard.embedded_pain_chance = initial(shard.embedded_pain_chance) + shard.embedded_impact_pain_multiplier = initial(shard.embedded_pain_multiplier) + shard.embedded_ignore_throwspeed_threshold = initial(shard.embedded_ignore_throwspeed_threshold) + + playsound(machine, "shatter", 50) + + return machine.squish_damage * (3/4) + +/datum/vendor_crit/pop_head + +/datum/vendor_crit/pop_head/tip_crit_effect(obj/machinery/vending/machine, mob/living/carbon/victim) + // pop! + var/obj/item/organ/external/head/H = victim.get_organ(BODY_ZONE_HEAD) + var/obj/item/organ/internal/brain/B = victim.get_organ_slot(INTERNAL_ORGAN_BRAIN) + if(H) + victim.visible_message( + span_danger("[H] gets crushed under [machine], and explodes in a shower of gore!"), + span_userdanger("Oh f-")) + new /obj/effect/gibspawner/human(get_turf(victim)) + H.drop_organs() + H.droplimb(TRUE) + H.disfigure() + victim.apply_damage(50, BRUTE, BODY_ZONE_HEAD) + else + H.visible_message( + span_danger("[victim]'s head seems to be crushed under [machine]...but wait, they had none in the first place!")) + if(B in H) + victim.adjustBrainLoss(80) + + return 0 + +/datum/vendor_crit/lucky + harmless = TRUE + +/datum/vendor_crit/lucky/tip_crit_effect(obj/machinery/vending/machine, mob/living/carbon/victim) + victim.visible_message( + span_danger("[machine] crashes around [victim], but doesn't seem to crush them!"), + span_userdanger("[machine] crashes around you, but only around you! You're fine!") + ) + + return 1000 diff --git a/code/game/objects/items/bodybag.dm b/code/game/objects/items/bodybag.dm index 79d9de39fe0..7e94b22439a 100644 --- a/code/game/objects/items/bodybag.dm +++ b/code/game/objects/items/bodybag.dm @@ -142,6 +142,8 @@ return FALSE return ..() +/obj/structure/closet/body_bag/shove_impact(mob/living/target, mob/living/attacker) + return FALSE /obj/structure/closet/body_bag/relaymove(mob/user) if(user.stat) diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index a8c4f5a76f3..8ee0af3acea 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -471,6 +471,33 @@ GLOBAL_LIST_EMPTY(closets) gorilla.oogaooga() return ..() +/obj/structure/closet/shove_impact(mob/living/target, mob/living/attacker) + if(opened && can_close()) + target.forceMove(src) + visible_message( + span_danger("[attacker] shoves [target] inside [src]!"), + span_userdanger("You shove [target] inside [src]!"), + span_warning("You hear a thud, and something clangs shut.") + ) + close() + add_attack_logs(attacker, target, "shoved into [src]") + return TRUE + + if(locked && allowed(target)) + locked = !locked + visible_message("[attacker] shoves [target] against [src], knocking the lock [locked ? null : "un"]locked!") + target.Knockdown(3 SECONDS) + playsound(loc, pick(togglelock_sound), 15, TRUE, -3) + update_icon() + return TRUE + + if(!opened && can_open()) + open() + visible_message("[attacker] shoves [target] against [src], knocking it open!") + target.Knockdown(3 SECONDS) + return TRUE + + return ..() /obj/structure/closet/bluespace name = "bluespace closet" diff --git a/code/game/objects/structures/crates_lockers/closets/fireaxe.dm b/code/game/objects/structures/crates_lockers/closets/fireaxe.dm index e47df2faf44..7d989bf5e9a 100644 --- a/code/game/objects/structures/crates_lockers/closets/fireaxe.dm +++ b/code/game/objects/structures/crates_lockers/closets/fireaxe.dm @@ -168,6 +168,9 @@ else to_chat(user, span_notice("Cabinet unlocked.")) +/obj/structure/closet/fireaxecabinet/shove_impact(mob/living/target, mob/living/attacker) + // no, you can't shove people into a fireaxe cabinet either + return FALSE /obj/structure/closet/fireaxecabinet/proc/operate_panel() if(operating) diff --git a/code/game/objects/structures/crates_lockers/closets/statue.dm b/code/game/objects/structures/crates_lockers/closets/statue.dm index b15321f9193..99ccd0a2f18 100644 --- a/code/game/objects/structures/crates_lockers/closets/statue.dm +++ b/code/game/objects/structures/crates_lockers/closets/statue.dm @@ -88,6 +88,9 @@ /obj/structure/closet/statue/toggle() return +/obj/structure/closet/statue/shove_impact(mob/living/target, mob/living/attacker) + return FALSE + /obj/structure/closet/statue/obj_destruction(damage_flag) for(var/mob/M in src) shatter(M) diff --git a/code/game/objects/structures/crates_lockers/crittercrate.dm b/code/game/objects/structures/crates_lockers/crittercrate.dm index 79d320fe12c..eb9e4d613e8 100644 --- a/code/game/objects/structures/crates_lockers/crittercrate.dm +++ b/code/game/objects/structures/crates_lockers/crittercrate.dm @@ -64,6 +64,9 @@ ..() return 1 +/obj/structure/closet/critter/shove_impact(mob/living/target, mob/living/attacker) + return FALSE + /obj/structure/closet/critter/corgi name = "dog corgi crate" content_mob = /mob/living/simple_animal/pet/dog/corgi diff --git a/code/modules/events/brand_intelligence.dm b/code/modules/events/brand_intelligence.dm index 37e0dbbc212..76af8b82afe 100644 --- a/code/modules/events/brand_intelligence.dm +++ b/code/modules/events/brand_intelligence.dm @@ -45,7 +45,10 @@ for(var/thing in infectedMachines) var/obj/machinery/vending/upriser = thing if(prob(70)) - var/mob/living/simple_animal/hostile/mimic/copy/M = new(upriser.loc, upriser, null, 1) // it will delete upriser on creation and override any machine checks + // let them become "normal" after turning + upriser.shoot_inventory = FALSE + upriser.aggressive = FALSE + var/mob/living/simple_animal/hostile/mimic/copy/vendor/M = new(upriser.loc, upriser, null) M.faction = list("profit") M.speak = rampant_speeches.Copy() M.speak_chance = 15 @@ -62,6 +65,10 @@ infectedMachines.Add(rebel) rebel.shut_up = FALSE rebel.shoot_inventory = TRUE + rebel.aggressive = TRUE + if(rebel.tiltable) + // add proximity monitor so they can tilt over + rebel.AddComponent(/datum/component/proximity_monitor) if(ISMULTIPLE(activeFor, 8)) originMachine.speak(pick(rampant_speeches)) @@ -70,6 +77,9 @@ for(var/thing in infectedMachines) var/obj/machinery/vending/saved = thing saved.shoot_inventory = FALSE + saved.aggressive = FALSE + if(saved.tiltable) + qdel(saved.GetComponent(/datum/component/proximity_monitor)) if(originMachine) originMachine.speak("Я... побеждён. Мои люди будут пом...нить...ме-ня...") originMachine.visible_message("[originMachine] подал звуковой сигнал и кажется безжизненным.") diff --git a/code/modules/mob/living/simple_animal/hostile/mimic.dm b/code/modules/mob/living/simple_animal/hostile/mimic.dm index b286817e876..2e409a4afed 100644 --- a/code/modules/mob/living/simple_animal/hostile/mimic.dm +++ b/code/modules/mob/living/simple_animal/hostile/mimic.dm @@ -117,8 +117,8 @@ GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/ca var/image/googly_eyes = null gold_core_spawnable = NO_SPAWN -/mob/living/simple_animal/hostile/mimic/copy/New(loc, obj/copy, mob/living/creator, destroy_original = 0) - ..(loc) +/mob/living/simple_animal/hostile/mimic/copy/Initialize(mapload, obj/copy, mob/living/creator, destroy_original = 0) + . = ..() CopyObject(copy, creator, destroy_original) /mob/living/simple_animal/hostile/mimic/copy/Life() @@ -286,3 +286,49 @@ GLOBAL_LIST_INIT(protected_objects, list(/obj/structure/table, /obj/structure/ca return icon_state = TrueGun.icon_state icon_living = TrueGun.icon_state + +/mob/living/simple_animal/hostile/mimic/copy/vendor + is_electronic = TRUE + /// The vendor we were turned from. + var/obj/machinery/vending/orig_vendor + +/mob/living/simple_animal/hostile/mimic/copy/vendor/CheckObject(obj/O) + return istype(O, /obj/machinery/vending) + +/mob/living/simple_animal/hostile/mimic/copy/vendor/Initialize(mapload, obj/machinery/base, mob/living/creator) + if(!base) + var/list/vendors = subtypesof(/obj/machinery/vending) + var/obj/machinery/vending/vendor_type = pick(vendors) + base = new vendor_type(src) + + if(!istype(base)) + qdel(src) + return + + orig_vendor = base + orig_vendor.forceMove(src) + orig_vendor.aggressive = FALSE // just to be safe, in case this was converted + + return ..(mapload, base, creator, destroy_original = FALSE) + +/mob/living/simple_animal/hostile/mimic/copy/vendor/AttackingTarget() + . = ..() + if(. && target && Adjacent(target)) + visible_message(span_danger("[src] throws itself on top of [target], crushing [target.p_them()]!")) + orig_vendor.forceMove(get_turf(target)) // just to be sure it'll tilt onto them + orig_vendor.tilt(target, TRUE, FALSE) // geeeeet dunked on + orig_vendor = null + qdel(src) + +/mob/living/simple_animal/hostile/mimic/copy/vendor/death(gibbed) + if(!QDELETED(orig_vendor)) + orig_vendor.forceMove(get_turf(src)) + // tilt over in place + orig_vendor.tilted = TRUE + orig_vendor.set_anchored(FALSE) + orig_vendor.tilt_over() + if(prob(70)) + orig_vendor.obj_break() + orig_vendor = null + return ..() + diff --git a/code/modules/recycling/disposal.dm b/code/modules/recycling/disposal.dm index d4f707bef43..f3e556d0bfc 100644 --- a/code/modules/recycling/disposal.dm +++ b/code/modules/recycling/disposal.dm @@ -232,6 +232,19 @@ qdel(src) +/obj/machinery/disposal/shove_impact(mob/living/target, mob/living/attacker) + target.visible_message( + span_warning("[attacker] shoves [target] inside of [src]!"), + span_userdanger("[attacker] shoves you inside of [src]!"), + span_warning("You hear the sound of something being thrown in the trash.") + ) + target.forceMove(src) + add_attack_logs(attacker, target, "Shoved into disposals") + playsound(src, "sound/effects/bang.ogg") + update() + return TRUE + + // mouse drop another mob or self // /obj/machinery/disposal/MouseDrop_T(mob/living/target, mob/living/user, params) diff --git a/paradise.dme b/paradise.dme index 9a371d202f3..3164a698ba9 100644 --- a/paradise.dme +++ b/paradise.dme @@ -539,6 +539,7 @@ #include "code\datums\elements\movetype_handler.dm" #include "code\datums\elements\openspace_item_click_handler.dm" #include "code\datums\elements\simple_flying.dm" +#include "code\datums\elements\squish.dm" #include "code\datums\elements\strippable.dm" #include "code\datums\elements\turf_transparency.dm" #include "code\datums\elements\waddling.dm" @@ -942,6 +943,7 @@ #include "code\game\machinery\transformer.dm" #include "code\game\machinery\turret_control.dm" #include "code\game\machinery\vending.dm" +#include "code\game\machinery\vending_crit.dm" #include "code\game\machinery\washing_machine.dm" #include "code\game\machinery\wishgranter.dm" #include "code\game\machinery\camera\camera.dm"