diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index c95050f3a3..8fb61f2dd9 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -163,6 +163,11 @@ ///from base of atom/attack_paw(): (mob/user) //This signal return value bitflags can be found in __DEFINES/misc.dm +///from base of /obj/structure/stairs/top/use_stairs(var/atom/movable/AM, var/atom/oldloc) +#define COMSIG_MOVED_DOWN_STAIRS "atom_moved_down_stairs" +///from base of /obj/structure/stairs/bottom/use_stairs(var/atom/movable/AM, var/atom/oldloc) +#define COMSIG_MOVED_UP_STAIRS "atom_moved_up_stairs" + ///called for each movable in a turf contents on /turf/zImpact(): (atom/movable/A, levels) #define COMSIG_ATOM_INTERCEPT_Z_FALL "movable_intercept_z_impact" ///called on a movable (NOT living) when someone starts pulling it (atom/movable/puller, state, force) @@ -240,6 +245,8 @@ #define COMPONENT_MOVABLE_IMPACT_NEVERMIND (1<<1) //return true if you destroyed whatever it was you're impacting and there won't be anything for hitby() to run on ///from base of mob/living/hitby(): (mob/living/target, hit_zone) #define COMSIG_MOVABLE_IMPACT_ZONE "item_impact_zone" +///from the base of mob/living/carbon/human/hitby(): (atom/movable/source, speed) +#define COMSIG_HUMAN_ON_CATCH_THROW "human_on_catch_throw" ///from base of atom/movable/buckle_mob(): (mob, force) #define COMSIG_MOVABLE_BUCKLE "buckle" ///from base of atom/movable/unbuckle_mob(): (mob, force) @@ -290,6 +297,9 @@ #define COMSIG_MOB_ALTCLICKON "mob_altclickon" #define COMSIG_MOB_CANCEL_CLICKON (1<<0) +///from base of /obj/item/dice/proc/rollDice(mob/user as mob, var/silent = 0). Has the arguments of 'src, silent, result' +#define COMSIG_MOB_ROLLED_DICE "mob_rolled_dice" //can give a return value if we want it to make the dice roll a specific number! + ///from base of obj/allowed(mob/M): (/obj) returns bool, if TRUE the mob has id access to the obj #define COMSIG_MOB_ALLOWED "mob_allowed" ///from base of mob/anti_magic_check(): (mob/user, magic, holy, tinfoil, chargecost, self, protection_sources) @@ -406,6 +416,8 @@ #define COMSIG_TAKING_APPLY_EFFECT "applying_effect" ///Return this in response if you don't want the effect to be applied #define COMSIG_CANCEL_EFFECT (1<<0) +///from /mob/living/proc/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null, var/electric = FALSE) +#define COMSIG_STUN_EFFECT_ACT "stun_effect_act" ///Misc signal for checking for godmode. Used by /datum/element/godmode #define COMSIG_CHECK_FOR_GODMODE "check_for_godmode" @@ -450,7 +462,8 @@ ///called when being electrocuted, from /mob/living/carbon/electrocute_act(shock_damage, source, siemens_coeff, def_zone, stun) #define COMSIG_BEING_ELECTROCUTED "being_electrocuted" #define COMPONENT_CARBON_CANCEL_ELECTROCUTE (1<<0) //If this is set, the carbon will be not be electrocuted. - +///called when a carbon slipps, from /mob/living/carbon/slip(var/slipped_on,stun_duration=8) +#define COMSIG_ON_CARBON_SLIP "carbon_slip" // /mob/living/silicon signals ///called when a silicon is emp'd. from /mob/living/silicon/emp_act(severity) @@ -536,6 +549,8 @@ #define COMSIG_MOB_UNEQUIPPED_ITEM "mob_unequipped_item" ///from base of obj/item/pickup(): (/mob/taker) #define COMSIG_ITEM_PICKUP "item_pickup" +///from base of obj/item/pickup(): (/obj/item) +#define COMSIG_PICKED_UP_ITEM "piked_up_item" ///from base of mob/living/carbon/attacked_by(): (mob/living/carbon/target, mob/living/user, hit_zone) #define COMSIG_ITEM_ATTACK_ZONE "item_attack_zone" ///return a truthy value to prevent ensouling, checked in /obj/effect/proc_holder/spell/targeted/lichdom/cast(): (mob/user) diff --git a/code/__defines/traits/declarations.dm b/code/__defines/traits/declarations.dm index d5446e206e..bfc8c13e1c 100644 --- a/code/__defines/traits/declarations.dm +++ b/code/__defines/traits/declarations.dm @@ -16,6 +16,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// Prevents the affected object from opening a loot window via alt click. See atom/AltClick() #define TRAIT_ALT_CLICK_BLOCKER "no_alt_click" +/// Unlucky trait. Given by the 'unlucky' trait in character select. Checked by various things to cause unlucky interactions. +#define TRAIT_UNLUCKY "trait_unlucky" + #define TRAIT_INCAPACITATED "incapacitated" #define TRAIT_NOFIRE "nonflammable" diff --git a/code/datums/components/_component.dm b/code/datums/components/_component.dm index 1252339eb3..219ee6968d 100644 --- a/code/datums/components/_component.dm +++ b/code/datums/components/_component.dm @@ -370,6 +370,7 @@ old_component.InheritComponent(arglist(arguments)) else old_component.InheritComponent(new_component, TRUE) + QDEL_NULL(new_component) if(COMPONENT_DUPE_SOURCES) if(source in old_component.sources) diff --git a/code/datums/components/antags/changeling/powers/bioelectrogenesis.dm b/code/datums/components/antags/changeling/powers/bioelectrogenesis.dm index 41a7b441ab..f5e19eae3a 100644 --- a/code/datums/components/antags/changeling/powers/bioelectrogenesis.dm +++ b/code/datums/components/antags/changeling/powers/bioelectrogenesis.dm @@ -45,7 +45,7 @@ if(G.affecting) G.affecting.electrocute_act(10 * siemens, src, 1.0, BP_TORSO, 0) var/agony = 80 * siemens //Does more than if hit with an electric hand, since grabbing is slower. - G.affecting.stun_effect_act(0, agony, BP_TORSO, src) + G.affecting.stun_effect_act(0, agony, BP_TORSO, src, electric = TRUE) add_attack_logs(src,G.affecting,"Changeling shocked") diff --git a/code/datums/components/traits/unlucky.dm b/code/datums/components/traits/unlucky.dm new file mode 100644 index 0000000000..28ba800060 --- /dev/null +++ b/code/datums/components/traits/unlucky.dm @@ -0,0 +1,544 @@ +/** + * Ripped from /tg/ with modifications. + * unlucky.dm: For when you want someone to have a really bad day + * + * When you attach an omen component to someone, they start running the risk of all sorts of bad environmental injuries, like nearby vending machines randomly falling on you (TBI), + * or hitting your head really hard when you slip and fall, or you get shocked by the tram rails at an unfortunate moment. + * + * Omens are removed once the victim is either maimed by one of the possible injuries, or if they receive a blessing (read: bashing with a bible) from the chaplain. (TBI) + */ +/datum/component/omen + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// How many incidents are left. If 0 exactly, it will get deleted. + var/incidents_left = INFINITY + /// Base probability of negative events. Cursed are half as unlucky. + var/luck_mod = 1 + /// Base damage from negative events. Cursed take 25% of this damage. + var/damage_mod = 1 + /// If we want to do more evil events, such as spontaneous combustion + var/evil = TRUE + /// If our codebase has safe disposals or not + var/safe_disposals = FALSE + /// If we have vore interactions or not + var/vorish = TRUE + +/datum/component/omen/Initialize(incidents_left, luck_mod, damage_mod, evil, safe_disposals, vorish) + if(!isliving(parent)) + return COMPONENT_INCOMPATIBLE + + if(!isnull(incidents_left)) + src.incidents_left = incidents_left + if(!isnull(luck_mod)) + src.luck_mod = luck_mod + if(!isnull(damage_mod)) + src.damage_mod = damage_mod + if(!isnull(evil)) + src.evil = evil + if(!isnull(safe_disposals)) + src.safe_disposals = safe_disposals + if(!isnull(vorish)) + src.vorish = vorish + + ADD_TRAIT(parent, TRAIT_UNLUCKY, src) + +/** + * This is a omen eat omen world! The stronger omen survives. + */ +/datum/component/omen/InheritComponent( + datum/component/omen/new_comp, + i_am_original, + incidents_left, + luck_mod, + damage_mod, + evil, + safe_disposals, + vorish, +) + // If we have more incidents left the new one gets deleted. + if(src.incidents_left > incidents_left) + return + src.incidents_left = incidents_left + + // The new omen is weaker than our current omen? Let's split the difference. + if(src.luck_mod > luck_mod) + src.luck_mod += luck_mod * 0.5 + if(src.damage_mod > damage_mod) + src.damage_mod += damage_mod * 0.5 + + // If the new omen has special modifiers, we take them on forever! + if(evil) + src.evil = TRUE + if(safe_disposals) + src.safe_disposals = TRUE + if(vorish) + src.vorish = TRUE + //This means weaker, longing lasting omens will take priority, but have some of the strength of the original. + +/datum/component/omen/Destroy(force) + var/mob/living/person = parent + REMOVE_TRAIT(person, TRAIT_UNLUCKY, src) + to_chat(person, span_warning(span_green("You feel a horrible omen lifted off your shoulders!"))) + + return ..() + +/datum/component/omen/RegisterWithParent() + RegisterSignal(parent, COMSIG_MOVABLE_MOVED, PROC_REF(check_accident)) + RegisterSignal(parent, COMSIG_ON_CARBON_SLIP, PROC_REF(check_slip)) + RegisterSignal(parent, COMSIG_MOVED_DOWN_STAIRS, PROC_REF(check_stairs)) + RegisterSignal(parent, COMSIG_STUN_EFFECT_ACT, PROC_REF(check_taser)) + RegisterSignal(parent, COMSIG_MOB_ROLLED_DICE, PROC_REF(check_roll)) + RegisterSignal(parent, COMSIG_HUMAN_ON_CATCH_THROW, PROC_REF(check_throw)) + RegisterSignal(parent, COMSIG_PICKED_UP_ITEM, PROC_REF(check_pickup)) + +/datum/component/omen/UnregisterFromParent() + UnregisterSignal(parent, list(COMSIG_ON_CARBON_SLIP, COMSIG_MOVABLE_MOVED, COMSIG_STUN_EFFECT_ACT, COMSIG_MOVED_DOWN_STAIRS, COMSIG_MOB_ROLLED_DICE, COMSIG_HUMAN_ON_CATCH_THROW, COMSIG_PICKED_UP_ITEM)) + +/datum/component/omen/proc/consume_omen() + incidents_left-- + if(incidents_left < 1) + qdel(src) + +/** + * check_accident() is called each step we take + * + * While we're walking around, roll to see if there's any environmental hazards on one of the adjacent tiles we can trigger. + * We do the prob() at the beginning to A. add some tension for /when/ it will strike, and B. (more importantly) ameliorate the fact that we're checking up to 5 turfs's contents each time + */ +/datum/component/omen/proc/check_accident(atom/movable/our_guy) + SIGNAL_HANDLER + + if(!isliving(our_guy) || isbelly(our_guy.loc)) + return + + var/mob/living/living_guy = our_guy + if(living_guy.is_incorporeal()) //no being unlucky if you don't even exist on the same plane. + return + + if(evil && prob(0.0001) && (living_guy.stat != DEAD)) // 1 in a million + living_guy.visible_message(span_danger("[living_guy] suddenly bursts into flames!"), span_danger("You suddenly burst into flames!")) + living_guy.emote("scream") + living_guy.adjust_fire_stacks(20) + living_guy.ignite_mob() + consume_omen() + return + + var/effective_luck = luck_mod + + // If there's nobody to witness the misfortune, make it less likely. + // This way, we allow for people to be able to get into hilarious situations without making the game nigh unplayable most of the time. + + var/has_watchers = FALSE + for(var/mob/viewer in viewers(our_guy, world.view)) + if(viewer.client && !viewer.client.is_afk()) + has_watchers = TRUE + break + if(!has_watchers) + effective_luck *= 0.5 + + if(!prob(2 * effective_luck)) + return + + var/turf/our_guy_pos = get_turf(our_guy) + if(!our_guy_pos) + return + if(evil) + for(var/obj/machinery/door/airlock/darth_airlock in our_guy_pos) + if(darth_airlock.locked || !darth_airlock.arePowerSystemsOn()) + continue + to_chat(living_guy, span_warning("The airlock suddenly closes on you!")) + living_guy.Paralyse(1 SECONDS) + slam_airlock(darth_airlock) + consume_omen() + return + + for(var/turf/the_turf as anything in our_guy_pos.AdjacentTurfs(check_blockage = FALSE)) //need false so we can check disposal units + if(the_turf.CanZPass(our_guy, DOWN)) + to_chat(living_guy, span_warning("You lose your balance and slip towards the edge!")) + living_guy.Weaken(5) + living_guy.throw_at(the_turf, 1, 20) + consume_omen() + return + + if(vorish) + for(var/mob/living/living_mob in the_turf) + if(living_mob == our_guy) + continue //Don't do anything to ourselves. + if(living_mob.stat) + continue + if(!living_mob.CanStumbleVore(living_guy) && !living_guy.CanStumbleVore(living_mob)) //Works both ways! Either way, someone's getting eaten! + continue + living_mob.stumble_into(living_guy) //logic reversed here because the game is DUMB. This means that living_guy is stumbling into the target! + living_guy.visible_message(span_danger("[living_guy] loses their balance and slips into [living_mob]!"), span_boldwarning("You lose your balance, slipping into [living_mob]!")) + consume_omen() + return + + for(var/obj/machinery/washing_machine/evil_washer in the_turf) + if(evil_washer.state == 1) //Empty and open door + our_guy.visible_message(span_danger("[our_guy] slips near the [evil_washer] and falls in, the door shutting!"), span_boldwarning("You slip on a wet spot near the [evil_washer] and fall in, the door shutting! You're stuck!")) + our_guy.forceMove(evil_washer) + evil_washer.washing += our_guy + evil_washer.state = 4 + evil_washer.visible_message(span_danger("[evil_washer] begins its spin cycle!")) + evil_washer.start(TRUE, damage_mod) + consume_omen() + return + + if(evil || safe_disposals) //On servers without safe disposals, this is a death sentence. With servers with safe disposals, it's just funny. + for(var/obj/machinery/disposal/evil_disposal in the_turf) + if(evil_disposal.stat & (BROKEN|NOPOWER)) + continue + if(evil_disposal.loc == living_guy.loc) //Let's not do a continual loop of them falling into it as soon as they climb out, as funny as that is. + continue + our_guy.visible_message(span_danger("[our_guy] slips on a spill near the [evil_disposal] and falls in!"), span_boldwarning("You slip on a spill near the [evil_disposal] and fall in!")) + living_guy.forceMove(evil_disposal) + evil_disposal.flush = TRUE + evil_disposal.update() + living_guy.Weaken(5) + consume_omen() + return + + if(evil && prob(33)) //This has an additional 2 in 3 chance to not happen as there's a LOT of lights on stations. This should be rarer. + for(var/obj/machinery/light/evil_light in the_turf) + if((evil_light.status == LIGHT_BURNED || evil_light.status == LIGHT_BROKEN) || (living_guy.get_shock_protection() == 1)) // we can't do anything :( + to_chat(living_guy, span_warning("[evil_light] sparks weakly for a second.")) + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread //this shit is copy pasted all over the code...this needs to just be made into a proc at this point jesus christ + s.set_up(4, FALSE, evil_light) + s.start() + //We don't clear the omen as nothing really happened. + break + + to_chat(living_guy, span_warning("[evil_light] glows ominously...")) // ominously + evil_light.visible_message(span_boldwarning("[evil_light] suddenly flares brightly and sparks!")) + //evil_light.broken(skip_sound_and_sparks = FALSE) //Let's not break it actually. + evil_light.Beam(living_guy, icon_state = "lightning[rand(1,12)]", time = 0.5 SECONDS) + living_guy.electrocute_act(35 * (damage_mod * 0.5), evil_light, stun = TRUE) //Stun is binary and scales on damage..Lame. + living_guy.emote("scream") + consume_omen() + return + + for(var/obj/machinery/vending/darth_vendor in the_turf) + if(darth_vendor.stat & (BROKEN|NOPOWER)) + continue + to_chat(living_guy, span_warning("The delivery chute of [darth_vendor] raises up...")) + darth_vendor.throw_item(living_guy) + consume_omen() + return + + for(var/obj/structure/mirror/evil_mirror in the_turf) + to_chat(living_guy, span_warning("You pass by the mirror and glance at it...")) + if(evil_mirror.shattered) + to_chat(living_guy, span_notice("You feel lucky, somehow.")) + return + var/mirror_rand + if(evil) + mirror_rand = rand(1,5) + else + mirror_rand = rand(1,3) + switch(mirror_rand) + if(1) + to_chat(living_guy, span_boldwarning("You see your reflection, but it is grinning malevolently and staring directly at you!")) + living_guy.emote("scream") + if(2 to 3) + to_chat(living_guy, span_large(span_cult("Oh god, you can't see your reflection!!"))) + living_guy.emote("scream") + if(4 to 5) + to_chat(living_guy, span_warning("The mirror explodes into a million pieces! Wait, does that mean you're even more unlucky?")) + evil_mirror.shatter() + if(prob(50 * effective_luck)) // sometimes + luck_mod += 0.25 + damage_mod += 0.25 + var/max_health_coefficient = (living_guy.maxHealth * 0.06) + for(var/obj/item/organ/external/limb in living_guy.organs) + living_guy.apply_damage(max_health_coefficient * damage_mod, BRUTE, limb.organ_tag, used_weapon = "glass shrapnel") + + living_guy.make_jittery(250) + if(evil && prob(7 * effective_luck)) + to_chat(living_guy, span_warning("You are completely shocked by this turn of events!")) + if(ishuman(living_guy)) + var/mob/living/carbon/human/human_guy = living_guy + if(human_guy.should_have_organ(O_HEART)) + for(var/obj/item/organ/internal/heart/heart in human_guy.internal_organs) + heart.bruise() //Closest thing we have to a heart attack. + to_chat(living_guy, span_boldwarning("You clutch at your heart!")) + + consume_omen() + return + if(evil) + for(var/obj/item/reagent_containers/glass/beaker/evil_beaker in the_turf) + if(!evil_beaker.is_open_container() && (evil_beaker.reagents.total_volume > 0)) //A closed beaker is a safe beaker! + continue + living_guy.visible_message(span_danger("[evil_beaker] tilts, spilling its contents on [living_guy]!"), span_bolddanger("[evil_beaker] spills all over you!")) + evil_beaker.balloon_alert_visible("[evil_beaker]'s contents splashes onto [living_guy]!") + evil_beaker.reagents.splash(living_guy, evil_beaker.reagents.total_volume) + consume_omen() + return + + for(var/obj/structure/table/evil_table in the_turf) + if(!evil_table.material) //We only want tables, not just table frames. + continue + var/datum/gender/gender = GLOB.gender_datums[living_guy.get_visible_gender()] + living_guy.visible_message(span_danger("[living_guy] stubs [gender.his] toe on [evil_table]!"), span_bolddanger("You stub your toe on [evil_table]!")) + living_guy.apply_damage(2 * damage_mod, BRUTE, pick(BP_L_FOOT, BP_R_FOOT), used_weapon = "blunt force trauma") + living_guy.adjustHalLoss(25) //It REALLY hurts. + living_guy.Weaken(3) + consume_omen() + return + //Ran out of turf options. Let's do more generic options. + + if(prob(luck_mod * 5)) + // In complete darkness + if(our_guy_pos.get_lumcount() <= LIGHTING_SOFT_THRESHOLD) + living_guy.Blind(5) //10 seconds of 'OH GOD WHAT'S HAPPENING' + living_guy.silent = 5 + living_guy.Paralyse(5) + to_chat(living_guy, span_bolddanger("You feel the ground buckle underneath you, falling down, your vision going dark as you feel paralyzed in place!")) + consume_omen() + return + + +/datum/component/omen/proc/slam_airlock(obj/machinery/door/airlock/darth_airlock) + SIGNAL_HANDLER + . = darth_airlock.close(forced = TRUE, ignore_safties = TRUE, crush_damage = 15) //Not enough to cause any IB or massively injured organs. + if(.) + consume_omen() + +/// If we get knocked down, see if we have a really bad slip and bash our head hard +/datum/component/omen/proc/check_slip(mob/living/our_guy, amount) + SIGNAL_HANDLER + + if(prob(30)) // AAAA + our_guy.emote("scream") + to_chat(our_guy, span_cult("What a horrible night... To have a curse!")) + + if(prob(30 * luck_mod) && our_guy.get_bodypart_name(BP_HEAD)) /// Bonk! + playsound(our_guy, 'sound/effects/tableheadsmash.ogg', 90, TRUE) + var/datum/gender/gender = GLOB.gender_datums[our_guy.get_visible_gender()] + our_guy.visible_message(span_danger("[our_guy] hits [gender.his] head really badly falling down!"), span_bolddanger("You hit your head really badly falling down!")) + var/max_health_coefficient = (our_guy.maxHealth * 0.5) + our_guy.apply_damage(max_health_coefficient * damage_mod, BRUTE, BP_HEAD, used_weapon = "slipping") + if(ishuman(our_guy)) + var/mob/living/carbon/human/human_guy = our_guy + if(human_guy.should_have_organ(O_BRAIN)) + for(var/obj/item/organ/internal/brain/brain in human_guy.internal_organs) + brain.take_damage(30 * damage_mod) //60 damage kills. + if(human_guy.glasses && human_guy.canUnEquip(human_guy.glasses)) + var/turf/T = get_turf(human_guy) + if(T) + var/obj/item/our_glasses = human_guy.glasses + human_guy.unEquip(human_guy.glasses, target = T) + to_chat(human_guy, span_warning("Your glasses fly off as you hit the ground!")) + our_glasses.throw_at_random(FALSE, 3, 2) + consume_omen() + + return + +/datum/component/omen/proc/check_roll(mob/living/unlucky_soul, var/obj/item/dice/the_dice, silent, result) + SIGNAL_HANDLER + if(prob(20 * luck_mod)) + //unlucky_soul.visible_message(span_danger("[unlucky_soul] rolls [the_dice] with it landing on the edge of [result] before tilting over!"), span_boldwarning("You feel dreadfully unlucky as you roll the dice!")) + //I had thought about making this have a notice that it happened. + //However, gaslighting the user by providing no visible notice is MUCH funnier. + return 1 // We override the roll to a 1. + +///Returns TRUE and stops us from catching +/datum/component/omen/proc/check_throw(mob/living/unlucky_soul, source, speed) + SIGNAL_HANDLER + if(prob(30 * luck_mod)) //~9% chance + if(istype(source, /obj/item/grenade)) + var/obj/item/grenade/bad_grenade = source + if(bad_grenade.active) + unlucky_soul.put_in_active_hand(bad_grenade) + unlucky_soul.visible_message(span_warning("[src] catches [source] as it goes off in their hand!"), span_bolddanger("You catch [source] and it goes off in your hand!")) + unlucky_soul.throw_mode_off() + bad_grenade.detonate() + return TRUE + else + unlucky_soul.visible_message(span_attack("[unlucky_soul] tries to catch [source] and fumbles it, getting thrown back!")) + unlucky_soul.Weaken(5) + return TRUE + +/* + * Dynamic injury system for when you pick up objects! + * Some objects might cut, burn, or otherwise injure you if you pick them up! + * Genenerally more of an annoyance than anything. + * Variables that can be changed: + * injury_type, damage_to_inflict, damage_type, injury_verb, is_sharp, is_edge. +*/ +/datum/component/omen/proc/check_pickup(mob/living/unlucky_soul, obj/item/item) + SIGNAL_HANDLER + if(prob(3 * luck_mod) && ishuman(unlucky_soul)) // ~3% chance + var/mob/living/carbon/human/unlucky_human = unlucky_soul + + ///What the injury will show up as on an autopsy. + var/injury_type = "injury" + + ///How much damage we'll inflect. + var/damage_to_inflict = 0 + + ///What type of damage we'll inflict + var/damage_type = BRUTE + + ///If the item we get injured on has is sharp. + var/is_sharp = FALSE + + ///If the item we get injured on has an edge. + var/has_edge = FALSE + + ///What verb we use to describe the injury. + var/injury_verb = "cuts" + + ///What hand we are currently using, so we injure the correct one. + var/current_hand = BP_R_HAND + if(unlucky_human.hand) + current_hand = BP_L_HAND + + if(istype(item, /obj/item/paper)) + injury_verb = "cuts" + injury_type = "paper cut" + damage_to_inflict = 2 + + else if(istype(item, /obj/item/material/knife)) + var/obj/item/material/knife = item + + injury_verb = "cuts" + injury_type = "knife" + is_sharp = knife.sharp + has_edge = knife.edge + damage_to_inflict = knife.force + + else if(istype(item, /obj/item/material/shard)) + var/obj/item/material/shard/shard = item + + injury_verb = "cuts" + injury_type = "shard" + is_sharp = shard.sharp + has_edge = shard.edge + damage_to_inflict = shard.force + + else if(istype(item, /obj/item/flame/lighter)) + var/obj/item/flame/lighter/lighter = item + if(!lighter.lit) + return + + injury_verb = "burns" + injury_type = "lighter" + damage_type = BURN + damage_to_inflict = 5 + + else if(istype(item, /obj/item/tool/transforming/jawsoflife)) + var/obj/item/tool/transforming/jawsoflife/jaws = item + + injury_verb = "clamps" + injury_type = "industrial tool" + is_sharp = jaws.sharp + has_edge = jaws.edge + damage_to_inflict = jaws.force + + else if(istype(item, /obj/item/tool/screwdriver)) + var/obj/item/tool/screwdriver/screwdriver = item + + injury_verb = "stabs" + injury_type = "industrial tool" + is_sharp = screwdriver.sharp + has_edge = screwdriver.edge + damage_to_inflict = screwdriver.force + + else if(istype(item, /obj/item/tool/wirecutters)) + var/obj/item/tool/wirecutters/wirecutters = item + + injury_verb = "nips" + injury_type = "industrial tool" + is_sharp = wirecutters.sharp + has_edge = wirecutters.edge + damage_to_inflict = wirecutters.force + + if(!damage_to_inflict) + return + + var/datum/gender/gender = GLOB.gender_datums[unlucky_human.get_visible_gender()] + unlucky_human.visible_message(span_danger("[unlucky_human] accidentally [injury_verb] [gender.his] hand on [item]!")) + unlucky_human.apply_damage(damage_to_inflict * damage_mod, damage_type, current_hand, sharp = is_sharp, edge = has_edge, used_weapon = injury_type) + +/datum/component/omen/proc/check_stairs(mob/living/unlucky_soul) + SIGNAL_HANDLER + if(prob(3 * luck_mod)) /// Bonk! + playsound(unlucky_soul, 'sound/effects/tableheadsmash.ogg', 90, TRUE) + unlucky_soul.visible_message(span_danger("One of the stairs give way as [unlucky_soul] steps onto it, tumbling them down to the bottom!"), span_bolddanger("A stair gives way and you trip to the bottom!")) + var/max_health_coefficient = (unlucky_soul.maxHealth * 0.09) + for(var/obj/item/organ/external/limb in unlucky_soul.organs) //In total, you should have 11 limbs (generally, unless you have an amputation). The full omen variant we want to leave you at 1 hp, the trait version less. As of writing, the trait version is 25% of the damage, so you take 24.75 across all limbs. + unlucky_soul.apply_damage(max_health_coefficient * damage_mod, BRUTE, limb.organ_tag, used_weapon = "slipping") + unlucky_soul.Weaken(5) + consume_omen() + +/datum/component/omen/proc/check_taser(mob/living/unlucky_soul, stun_amount, agony_amount, def_zone, used_weapon, electric) + SIGNAL_HANDLER + if(!electric || !evil) //If it's not electric we don't care! Likewise, if we don't have the evil variant, don't care! + return + if(!ishuman(unlucky_soul)) + return + if(prob(3 * luck_mod)) + var/mob/living/carbon/human/human_guy = unlucky_soul + if(human_guy.should_have_organ(O_HEART)) + for(var/obj/item/organ/internal/heart/heart in human_guy.internal_organs) + if(heart.robotic) + continue //Robotic hearts are immune to this. + heart.take_damage(10 * stun_amount * damage_mod) + heart.take_damage(0.25 * agony_amount * damage_mod) + playsound(src, 'sound/effects/singlebeat.ogg', 50, FALSE) + to_chat(unlucky_soul, span_bolddanger("You feel as though your heart stopped")) + human_guy.Stun(5) + consume_omen() + return + +/** + * The trait omen. Permanent. + * Has only a 30% chance of bad things happening, and takes only 25% of normal damage. + * Evil is false, so you get less dramatic things happening. + */ +/datum/component/omen/trait + incidents_left = INFINITY + dupe_type = /datum/component/omen/trait + luck_mod = 0.3 // 30% chance of bad things happening + damage_mod = 0.25 // 25% of normal damage + evil = FALSE + safe_disposals = FALSE + +///Major variant of the trait. +/datum/component/omen/trait/major + evil = TRUE + damage_mod = 0.75 //75% of normal damage + +///Variant trait for downstreams that have safe disposals. +/datum/component/omen/trait/safe_disposals + safe_disposals = TRUE + +/datum/component/omen/trait/safe_disposals/major + evil = TRUE + damage_mod = 0.75 //75% of normal damage + +/** + * The bible omen. + * While it lasts, parent gets a cursed aura filter. + */ +/datum/component/omen/bible + incidents_left = 1 + +/datum/component/omen/bible/RegisterWithParent() + . = ..() + var/mob/living/living_parent = parent + living_parent.add_filter("omen", 2, list("type" = "drop_shadow", "color" = "#A50824", "alpha" = 0, "size" = 2)) + var/filter = living_parent.get_filter("omen") + animate(filter, alpha = 255, time = 2 SECONDS, loop = -1) + animate(alpha = 0, time = 2 SECONDS) + +/datum/component/omen/bible/UnregisterFromParent() + . = ..() + var/mob/living/living_parent = parent + living_parent.remove_filter("omen") + +/** + * The dice omen. + * Single use omen from rolling a nat 1 on a cursed d20. + */ +/datum/component/omen/dice + incidents_left = 1 diff --git a/code/datums/elements/lootable/_lootable.dm b/code/datums/elements/lootable/_lootable.dm index b461bc22f2..b4856b7c53 100644 --- a/code/datums/elements/lootable/_lootable.dm +++ b/code/datums/elements/lootable/_lootable.dm @@ -9,6 +9,7 @@ var/loot_left = 0 // When this reaches zero, and loot_depleted is true, you can't obtain anymore loot. var/delete_on_depletion = FALSE // If true, and if the loot gets depleted as above, the pile is deleted. + var/list/unlucky_loot = list() // Unlucky is the worst tier. Only people with the unlucky trait can get this stuff. Primed grenades, dangerous syringes, etc. var/list/common_loot = list() // Common is generally less-than-useful junk and filler, at least for maint loot piles. var/list/uncommon_loot = list() // Uncommon is actually maybe some useful items, usually the reason someone bothers looking inside. var/list/rare_loot = list() // Rare is really powerful, or at least unique items. @@ -47,7 +48,17 @@ var/obj/item/loot = null var/span = "notice" // Blue - if(prob(chance_uncommon) && uncommon_loot.len) // You might still get something good. + if(HAS_TRAIT(L, TRAIT_UNLUCKY) && unlucky_loot.len) // If you're unlucky, you will always find bad stuff. + loot = produce_unlucky_item(source) + span = "cult" // Purple and bold. + if(prob(1)) + to_chat(L, span_danger("You cut your hand on something in the trash!")) + L.apply_damage(2, BRUTE, pick(BP_L_HAND, BP_R_HAND), used_weapon = "sharp object") + var/datum/disease/advance/random/random_disease = new /datum/disease/advance/random() + random_disease.spread_flags |= DISEASE_SPREAD_NON_CONTAGIOUS + L.ForceContractDisease(random_disease) + + else if(prob(chance_uncommon) && uncommon_loot.len) // You might still get something good. loot = produce_uncommon_item(source) span = "alium" // Green @@ -96,6 +107,9 @@ if(delete_on_depletion) qdel(source) +/datum/element/lootable/proc/produce_unlucky_item(atom/source) + var/path = pick(unlucky_loot) + return new path(source) /datum/element/lootable/proc/produce_common_item(atom/source) var/path = pick(common_loot) diff --git a/code/datums/elements/lootable/boxes.dm b/code/datums/elements/lootable/boxes.dm index 7001ac4a96..ed5488fe33 100644 --- a/code/datums/elements/lootable/boxes.dm +++ b/code/datums/elements/lootable/boxes.dm @@ -1,6 +1,44 @@ // Contains loads of different types of boxes, which may have items inside! ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /datum/element/lootable/boxes + + unlucky_loot = list( + /obj/item/grenade/flashbang/clusterbang/primed, + /obj/item/storage/box/old_syringes, + /obj/item/storage/box/donut/empty, + /obj/item/grenade/smokebomb/primed, + /obj/item/storage/box, + /obj/item/storage/box/cups, + /obj/item/trash/candle, + /obj/item/trash/candy, + /obj/item/trash/candy/proteinbar, + /obj/item/trash/candy/gums, + /obj/item/trash/cheesie, + /obj/item/trash/chips, + /obj/item/trash/chips/bbq, + /obj/item/trash/liquidfood, + /obj/item/trash/pistachios, + /obj/item/trash/plate, + /obj/item/trash/popcorn, + /obj/item/trash/raisins, + /obj/item/trash/semki, + /obj/item/trash/snack_bowl, + /obj/item/trash/sosjerky, + /obj/item/trash/syndi_cakes, + /obj/item/trash/tastybread, + /obj/item/trash/coffee, + /obj/item/trash/tray, + /obj/item/trash/unajerky, + /obj/item/trash/waffles, + /obj/item/spacecash/c1, + /obj/item/card/emag_broken, + /obj/effect/decal/remains/lizard, + /obj/effect/decal/remains/mouse, + /obj/effect/decal/remains/robot, + /obj/item/pizzabox/old, + /obj/item/paper/crumpled + ) + common_loot = list( /obj/item/storage/box, /obj/item/storage/box/beakers, diff --git a/code/datums/elements/lootable/maint.dm b/code/datums/elements/lootable/maint.dm index 3c51312654..3521a15fd0 100644 --- a/code/datums/elements/lootable/maint.dm +++ b/code/datums/elements/lootable/maint.dm @@ -1,6 +1,43 @@ // Has large amounts of possible items, most of which may or may not be useful. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /datum/element/lootable/maint/junk + unlucky_loot = list( + /obj/item/grenade/flashbang/clusterbang/primed, + /obj/item/storage/box/old_syringes, + /obj/item/storage/box/donut/empty, + /obj/item/grenade/smokebomb/primed, + /obj/item/storage/box, + /obj/item/storage/box/cups, + /obj/item/trash/candle, + /obj/item/trash/candy, + /obj/item/trash/candy/proteinbar, + /obj/item/trash/candy/gums, + /obj/item/trash/cheesie, + /obj/item/trash/chips, + /obj/item/trash/chips/bbq, + /obj/item/trash/liquidfood, + /obj/item/trash/pistachios, + /obj/item/trash/plate, + /obj/item/trash/popcorn, + /obj/item/trash/raisins, + /obj/item/trash/semki, + /obj/item/trash/snack_bowl, + /obj/item/trash/sosjerky, + /obj/item/trash/syndi_cakes, + /obj/item/trash/tastybread, + /obj/item/trash/coffee, + /obj/item/trash/tray, + /obj/item/trash/unajerky, + /obj/item/trash/waffles, + /obj/item/spacecash/c1, + /obj/item/card/emag_broken, + /obj/effect/decal/remains/lizard, + /obj/effect/decal/remains/mouse, + /obj/effect/decal/remains/robot, + /obj/item/pizzabox/old, + /obj/item/paper/crumpled + ) + common_loot = list( /obj/item/flashlight/flare, /obj/item/flashlight/glowstick, @@ -95,6 +132,43 @@ // Contains mostly useless garbage. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /datum/element/lootable/maint/trash + unlucky_loot = list( + /obj/item/grenade/flashbang/clusterbang/primed, + /obj/item/storage/box/old_syringes, + /obj/item/storage/box/donut/empty, + /obj/item/grenade/smokebomb/primed, + /obj/item/storage/box, + /obj/item/storage/box/cups, + /obj/item/trash/candle, + /obj/item/trash/candy, + /obj/item/trash/candy/proteinbar, + /obj/item/trash/candy/gums, + /obj/item/trash/cheesie, + /obj/item/trash/chips, + /obj/item/trash/chips/bbq, + /obj/item/trash/liquidfood, + /obj/item/trash/pistachios, + /obj/item/trash/plate, + /obj/item/trash/popcorn, + /obj/item/trash/raisins, + /obj/item/trash/semki, + /obj/item/trash/snack_bowl, + /obj/item/trash/sosjerky, + /obj/item/trash/syndi_cakes, + /obj/item/trash/tastybread, + /obj/item/trash/coffee, + /obj/item/trash/tray, + /obj/item/trash/unajerky, + /obj/item/trash/waffles, + /obj/item/spacecash/c1, + /obj/item/card/emag_broken, + /obj/effect/decal/remains/lizard, + /obj/effect/decal/remains/mouse, + /obj/effect/decal/remains/robot, + /obj/item/pizzabox/old, + /obj/item/paper/crumpled + ) + common_loot = list( /obj/item/trash/candle, /obj/item/trash/candy, diff --git a/code/datums/elements/lootable/trash.dm b/code/datums/elements/lootable/trash.dm index 437236b0e8..29679e4971 100644 --- a/code/datums/elements/lootable/trash.dm +++ b/code/datums/elements/lootable/trash.dm @@ -5,6 +5,43 @@ chance_rare = 2 chance_gamma = 1 // Special single drop table + unlucky_loot = list( + /obj/item/grenade/flashbang/clusterbang/primed, + /obj/item/storage/box/old_syringes, + /obj/item/storage/box/donut/empty, + /obj/item/grenade/smokebomb/primed, + /obj/item/storage/box, + /obj/item/storage/box/cups, + /obj/item/trash/candle, + /obj/item/trash/candy, + /obj/item/trash/candy/proteinbar, + /obj/item/trash/candy/gums, + /obj/item/trash/cheesie, + /obj/item/trash/chips, + /obj/item/trash/chips/bbq, + /obj/item/trash/liquidfood, + /obj/item/trash/pistachios, + /obj/item/trash/plate, + /obj/item/trash/popcorn, + /obj/item/trash/raisins, + /obj/item/trash/semki, + /obj/item/trash/snack_bowl, + /obj/item/trash/sosjerky, + /obj/item/trash/syndi_cakes, + /obj/item/trash/tastybread, + /obj/item/trash/coffee, + /obj/item/trash/tray, + /obj/item/trash/unajerky, + /obj/item/trash/waffles, + /obj/item/spacecash/c1, + /obj/item/card/emag_broken, + /obj/effect/decal/remains/lizard, + /obj/effect/decal/remains/mouse, + /obj/effect/decal/remains/robot, + /obj/item/pizzabox/old, + /obj/item/paper/crumpled + ) + common_loot = list( /obj/item/clothing/gloves/rainbow, /obj/item/clothing/gloves/white, diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index b0dc9d5551..3ce8f64f80 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -442,7 +442,7 @@ var/list/mob/living/forced_ambiance_list = list() if(istype(get_turf(mob), /turf/space)) // Can't fall onto nothing. return - if(istype(mob,/mob/living/carbon/human/)) + if(ishuman(mob)) var/mob/living/carbon/human/H = mob if(H.buckled) return // Being buckled to something solid keeps you in place. @@ -460,6 +460,11 @@ var/list/mob/living/forced_ambiance_list = list() H.AdjustStunned(1) // CHOMPedit: No longer a supermassive long stun. // H.AdjustWeakened(3) // CHOMPedit: No longer weakens. to_chat(mob, span_notice("The sudden appearance of gravity makes you fall to the floor!")) + if(HAS_TRAIT(H, TRAIT_UNLUCKY) && prob(50) && H.get_bodypart_name(BP_HEAD)) + var/datum/gender/gender = GLOB.gender_datums[H.get_visible_gender()] + H.visible_message(span_warning("[H] falls to the ground from the sudden appearance of gravity, smashing [gender.his] head against the ground!"),span_warning("You smash your head into the ground as gravity appears!")) + H.apply_damage(14, BRUTE, BP_HEAD, used_weapon = "blunt force") + playsound(H, 'sound/effects/tableheadsmash.ogg', 90, TRUE) playsound(mob, "bodyfall", 50, 1) /area/proc/prison_break(break_lights = TRUE, open_doors = TRUE, open_blast_doors = FALSE) //CHOMP Edit set blast doors to FALSE diff --git a/code/game/gamemodes/events/clang.dm b/code/game/gamemodes/events/clang.dm index 1a9abbace3..46aac26401 100644 --- a/code/game/gamemodes/events/clang.dm +++ b/code/game/gamemodes/events/clang.dm @@ -75,6 +75,7 @@ anchored = TRUE movement_type = UNSTOPPABLE var/turf/despawn_loc = null + var/has_hunted_unlucky = FALSE /obj/effect/immovablerod/proc/TakeFlight(var/turf/end) despawn_loc = end @@ -112,8 +113,8 @@ if(despawn_loc != null && (src.x == despawn_loc.x && src.y == despawn_loc.y)) qdel(src) return -/* //Used in conjunction with the Unlucky/Cursed trait. NYI. - if(prob(10)) + + if(prob(10) && !has_hunted_unlucky) hunt_unlucky() /obj/effect/immovablerod/proc/hunt_unlucky() @@ -132,7 +133,7 @@ /obj/effect/immovablerod/proc/resume_path() walk(src, 0) walk_towards(src, despawn_loc, 1) -*/ + /obj/effect/immovablerod/Destroy() walk(src, 0) // Because we might have called walk_towards, we must stop the walk loop or BYOND keeps an internal reference to us forever. return ..() diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm index 65643e74fc..8b72207203 100644 --- a/code/game/machinery/doors/airlock.dm +++ b/code/game/machinery/doors/airlock.dm @@ -911,13 +911,13 @@ About the new airlock wires panel: adjustBruteLoss(crush_damage) return FALSE -/obj/machinery/door/airlock/close(var/forced=0) +/obj/machinery/door/airlock/close(var/forced= FALSE, var/ignore_safties = FALSE, var/crush_damage = DOOR_CRUSH_DAMAGE) if(!can_close(forced)) return FALSE hold_open = null //if it passes the can close check, always make sure to clear hold open - if(safe) + if(safe && !ignore_safties) for(var/turf/turf in locs) for(var/atom/movable/AM in turf) if(AM.blocks_airlock()) @@ -929,8 +929,8 @@ About the new airlock wires panel: for(var/turf/turf in locs) for(var/atom/movable/AM in turf) - if(AM.airlock_crush(DOOR_CRUSH_DAMAGE)) - take_damage(DOOR_CRUSH_DAMAGE) + if(AM.airlock_crush(crush_damage)) + take_damage(crush_damage) use_power(360) //360 W seems much more appropriate for an actuator moving an industrial door capable of crushing people has_beeped = 0 diff --git a/code/game/machinery/doors/airlock_control.dm b/code/game/machinery/doors/airlock_control.dm index 3119789181..5e70300958 100644 --- a/code/game/machinery/doors/airlock_control.dm +++ b/code/game/machinery/doors/airlock_control.dm @@ -128,9 +128,9 @@ if(!surpress_send) send_status() -/obj/machinery/door/airlock/close(surpress_send) +/obj/machinery/door/airlock/close(var/forced= FALSE, var/ignore_safties = FALSE, var/crush_damage = DOOR_CRUSH_DAMAGE) . = ..() - if(!surpress_send) send_status() + if(!forced) send_status() /obj/machinery/door/airlock/Bumped(atom/AM) diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 518421a256..1dcc813d44 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -527,7 +527,7 @@ open_speed = 15 return (normalspeed ? open_speed : 5) -/obj/machinery/door/proc/close(var/forced = 0) +/obj/machinery/door/proc/close(var/forced = 0, var/ignore_safties = FALSE, var/crush_damage = DOOR_CRUSH_DAMAGE) if(!can_close(forced)) return operating = 1 diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 9974b99602..39dd3f2983 100644 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -115,6 +115,50 @@ return //CHOMPEdit End + // Checks to make sure he's not in space doing it, and that the area got proper power. + if(!powered()) + to_chat(user, span_warning("\The [src] blinks red as you try to insert [G]!")) + return + if(istype(G, /obj/item/gun/energy)) + var/obj/item/gun/energy/E = G + if(E.self_recharge) + to_chat(user, span_notice("\The [E] has no recharge port.")) + return + if(istype(G, /obj/item/modular_computer)) + var/obj/item/modular_computer/C = G + if(!C.battery_module) + to_chat(user, span_notice("\The [C] does not have a battery installed. ")) + return + if(istype(G, /obj/item/flash)) + var/obj/item/flash/F = G + if(F.use_external_power) + to_chat(user, span_notice("\The [F] has no recharge port.")) + return + if(istype(G, /obj/item/weldingtool/electric)) + var/obj/item/weldingtool/electric/EW = G + if(EW.use_external_power) + to_chat(user, span_notice("\The [EW] has no recharge port.")) + return + if(!G.get_cell() && !istype(G, /obj/item/ammo_casing/microbattery) && !istype(G, /obj/item/paicard)) //VOREStation Edit: NSFW charging + to_chat(user, "\The [G] does not have a battery installed.") + return + if(istype(G, /obj/item/paicard)) + var/obj/item/paicard/ourcard = G + if(ourcard.panel_open) + to_chat(user, span_warning("\The [ourcard] won't fit in the recharger with its panel open.")) + return + if(ourcard.pai) + if(ourcard.pai.stat == CONSCIOUS) + to_chat(user, span_warning("\The [ourcard] boops... it doesn't need to be recharged!")) + return + else + to_chat(user, span_warning("\The [ourcard] doesn't have a personality!")) + return + if(HAS_TRAIT(user, TRAIT_UNLUCKY) && prob(10)) + user.visible_message("[user] inserts [charging] into [src] backwards!", "You insert [charging] into [src] backwards!") + user.drop_item() + G.loc = get_turf(src) + return user.drop_item() G.loc = src charging = G diff --git a/code/game/machinery/washing_machine.dm b/code/game/machinery/washing_machine.dm index 52b008076c..5e226f3ced 100644 --- a/code/game/machinery/washing_machine.dm +++ b/code/game/machinery/washing_machine.dm @@ -1,3 +1,13 @@ +#define EMPTY_OPEN 1 +#define EMPTY_CLOSED 2 +#define FULL_OPEN 3 +#define FULL_CLOSED 4 +#define RUNNING 5 +#define BLOODY_OPEN 6 //Not actually used... +#define BLOODY_CLOSED 7 +#define BLOODY_RUNNING 8 + + /obj/machinery/washing_machine name = "Washing Machine" desc = "Not a hiding place. Unfit for pets." @@ -9,19 +19,9 @@ clickvol = 40 circuit = /obj/item/circuitboard/washing - var/state = 1 - //1 = empty, open door - //2 = empty, closed door - //3 = full, open door - //4 = full, closed door - //5 = running - //6 = blood, open door - //7 = blood, closed door - //8 = blood, running - var/hacked = 1 //Bleh, screw hacking, let's have it hacked by default. - //0 = not hacked - //1 = hacked - var/gibs_ready = 0 + var/state = EMPTY_OPEN + var/hacked = TRUE //Bleh, screw hacking, let's have it hacked by default. + var/gibs_ready = FALSE var/obj/crayon var/list/washing = list() var/list/disallowed_types = list( @@ -34,6 +34,14 @@ default_apply_parts() AddElement(/datum/element/climbable) +/obj/machinery/washing_machine/Destroy() + for(var/atom/movable/washed_items in contents) + washed_items.forceMove(get_turf(src)) + washing.Cut() + crayon = null + . = ..() + + /obj/machinery/washing_machine/AltClick() start() @@ -43,26 +51,27 @@ set src in oview(1) start() -/obj/machinery/washing_machine/proc/start() +/obj/machinery/washing_machine/proc/start(force, damage_modifier) - if(!isliving(usr)) //ew ew ew usr, but it's the only way to check. + if(!isliving(usr) && !force) //ew ew ew usr, but it's the only way to check. return - - if(state != 4) - to_chat(usr, "The washing machine cannot run in this state.") + if(!damage_modifier) + damage_modifier = 0.5 + if(state != FULL_CLOSED) + visible_message("The washing machine buzzes - it can not run in this state!") return if(locate(/mob,washing)) - state = 8 + state = BLOODY_RUNNING else - state = 5 + state = RUNNING update_icon() - to_chat(usr, "The washing machine starts a cycle.") + visible_message("The washing machine starts a cycle.") playsound(src, 'sound/items/washingmachine.ogg', 50, 1, 1) - addtimer(CALLBACK(src, PROC_REF(finish_wash)), 2 SECONDS) + addtimer(CALLBACK(src, PROC_REF(finish_wash), damage_modifier), 2 SECONDS) -/obj/machinery/washing_machine/proc/finish_wash() +/obj/machinery/washing_machine/proc/finish_wash(damage_modifier) for(var/atom/A in washing) A.wash(CLEAN_ALL) @@ -74,21 +83,49 @@ HH.use(HH.get_amount()) washing += WL + var/has_mobs = FALSE + for(var/mob/living/mobs in washing) + has_mobs = TRUE + if(ishuman(mobs)) + var/mob/living/carbon/human/our_human = mobs + var/max_health_coefficient = (our_human.maxHealth * 0.09) //9 for 100% hp human, 4.5 for 50% hp human (teshari), etc. + for(var/i=0,i<10,i++) + our_human.apply_damage(max_health_coefficient*damage_modifier, BRUTE, used_weapon = "spin cycle") //Let's randomly do damage across the body. One limb might get hurt more than the others. At 100% damge mod, does 90% of max hp in damage. + continue + mobs.stat = DEAD //Kill them so they can't interact anymore. - if(locate(/mob,washing)) - state = 7 - gibs_ready = 1 + if(has_mobs) + state = BLOODY_CLOSED + gibs_ready = TRUE else - state = 4 + state = FULL_CLOSED update_icon() /obj/machinery/washing_machine/verb/climb_out() set name = "Climb out" set category = "Object" set src in usr.loc + user_climb_out(usr) - if((state in list(1,3,6)) && do_after(usr, 2 SECONDS, target = src)) - usr.loc = src.loc +/obj/machinery/washing_machine/proc/user_climb_out(mob/user) + if(user.loc != src) //Have to be in it to climb out of it. + return + if(state in list(EMPTY_OPEN, FULL_OPEN, BLOODY_OPEN)) //Door is open, we can climb out easily. + visible_message("[user] begins to climb out of the [src]!") + if(do_after(user, 2 SECONDS, target = src)) + if(!(state in list(EMPTY_CLOSED, FULL_CLOSED, BLOODY_CLOSED))) //Someone shut the door while we were trying to climb out! + user.forceMove(get_turf(src)) + visible_message("[user] climbs out of the [src]!") + else + to_chat(user, "Someone shut the door on you!") + else if(state in list(EMPTY_CLOSED, FULL_CLOSED, BLOODY_CLOSED)) //Door is shut. + visible_message("[src] begins to rattle and shake!") + if(do_after(user, 60 SECONDS, target = src)) + visible_message("[user] climbs out of the [src]!") + attack_hand(user, force = TRUE) + +/obj/machinery/washing_machine/container_resist(mob/living/escapee) + user_climb_out(escapee) /obj/machinery/washing_machine/update_icon() cut_overlays() @@ -97,7 +134,7 @@ add_overlay("panel") /obj/machinery/washing_machine/attackby(obj/item/W as obj, mob/user as mob) - if(state == 2 && washing.len < 1) + if(state == EMPTY_CLOSED && washing.len < 1) if(default_deconstruction_screwdriver(user, W)) return if(default_deconstruction_crowbar(user, W)) @@ -108,22 +145,30 @@ panel = !panel to_chat(user, span_notice("You [panel ? "open" : "close"] the [src]'s maintenance panel"))*/ if(istype(W,/obj/item/pen/crayon) || istype(W,/obj/item/stamp)) - if(state in list( 1, 3, 6)) + if(state in list (EMPTY_OPEN, FULL_OPEN, BLOODY_OPEN)) if(!crayon) user.drop_item() crayon = W + crayon.forceMove(src) crayon.loc = src else ..() else ..() else if(istype(W,/obj/item/grab)) - if((state == 1) && hacked) + if((state == EMPTY_OPEN) && hacked) var/obj/item/grab/G = W - if(ishuman(G.assailant) && iscorgi(G.affecting)) - G.affecting.loc = src - qdel(G) - state = 3 + if(ishuman(G.assailant) && (iscorgi(G.affecting) || ishuman(G.affecting))) + user.visible_message("[user] begins stuffing [G.affecting] into the [src]!", "You begin stuffing [G.affecting] into the [src]!") + if(do_after(user, 5 SECONDS, target = src)) + if(state == EMPTY_OPEN) //Checking to make sure nobody closed it before we shoved em in it. + user.visible_message("[user] stuffs [G.affecting] into the [src] and shuts the door!", "You stuff [G.affecting] into the [src] and shut the door!") + G.affecting.forceMove(src) + washing += G.affecting + qdel(G) + state = FULL_CLOSED + else + to_chat(user, "You can't shove [G.affecting] in unless the washer is empty and open!") else ..() @@ -133,11 +178,11 @@ else if(istype(W, /obj/item/clothing) || istype(W, /obj/item/bedsheet) || istype(W, /obj/item/stack/hairlesshide)) if(washing.len < 5) - if(state in list(1, 3)) + if(state in list(EMPTY_OPEN, FULL_OPEN)) user.drop_item() - W.loc = src + W.forceMove(src) washing += W - state = 3 + state = FULL_OPEN else to_chat(user, span_notice("You can't put the item in right now.")) else @@ -146,38 +191,50 @@ ..() update_icon() -/obj/machinery/washing_machine/attack_hand(mob/user as mob) +/obj/machinery/washing_machine/attack_hand(mob/user, force) + if(user.loc == src && !force) + return //No interacting with it from the inside! switch(state) - if(1) - state = 2 - if(2) - state = 1 + if(EMPTY_OPEN) + state = EMPTY_CLOSED + if(EMPTY_CLOSED) + state = EMPTY_OPEN for(var/atom/movable/O in washing) - O.loc = src.loc + O.forceMove(get_turf(src)) washing.Cut() - if(3) - state = 4 - if(4) - state = 3 + if(FULL_OPEN) + state = FULL_CLOSED + if(FULL_CLOSED) for(var/atom/movable/O in washing) - O.loc = src.loc + O.forceMove(get_turf(src)) crayon = null washing.Cut() - state = 1 - if(5) - to_chat(user, span_warning("The [src] is busy.")) - if(6) - state = 7 - if(7) + state = EMPTY_OPEN + if(RUNNING) + if(user) + to_chat(user, span_warning("The [src] is busy.")) + if(BLOODY_OPEN) + state = BLOODY_CLOSED + if(BLOODY_CLOSED) if(gibs_ready) - gibs_ready = 0 - if(locate(/mob,washing)) - var/mob/M = locate(/mob,washing) - M.gib() + gibs_ready = FALSE + for(var/mob/living/mobs in washing) + if(ishuman(mobs)) //Humans have special handling. + continue + mobs.gib() for(var/atom/movable/O in washing) - O.loc = src.loc + O.forceMove(get_turf(src)) crayon = null - state = 1 + state = EMPTY_OPEN washing.Cut() update_icon() + +#undef EMPTY_OPEN +#undef EMPTY_CLOSED +#undef FULL_OPEN +#undef FULL_CLOSED +#undef RUNNING +#undef BLOODY_OPEN +#undef BLOODY_CLOSED +#undef BLOODY_RUNNING diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index 2ebd8fed32..c7aaae1e11 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -440,6 +440,7 @@ // called just as an item is picked up (loc is not yet changed) /obj/item/proc/pickup(mob/user) SEND_SIGNAL(src, COMSIG_ITEM_PICKUP, user) + SEND_SIGNAL(user, COMSIG_PICKED_UP_ITEM, src) pixel_x = 0 pixel_y = 0 return diff --git a/code/game/objects/items/devices/defib.dm b/code/game/objects/items/devices/defib.dm index 64b55fcd9a..ca5040bd12 100644 --- a/code/game/objects/items/devices/defib.dm +++ b/code/game/objects/items/devices/defib.dm @@ -389,6 +389,10 @@ return H.apply_damage(burn_damage_amt, BURN, BP_TORSO) + if(HAS_TRAIT(H, TRAIT_UNLUCKY) && prob(5)) + make_announcement("buzzes, \"Unknown error occurred. Please try again.\"", "warning") + playsound(src, 'sound/machines/defib_failed.ogg', 50, FALSE) + return //set oxyloss so that the patient is just barely in crit, if possible var/barely_in_crit = H.get_crit_point() - 1 @@ -437,7 +441,7 @@ playsound(src, 'sound/weapons/egloves.ogg', 100, 1, -1) set_cooldown(cooldowntime) - H.stun_effect_act(2, 120, target_zone) + H.stun_effect_act(2, 120, target_zone, electric = TRUE) var/burn_damage = H.electrocute_act(burn_damage_amt*2, src, def_zone = target_zone) if(burn_damage > 15 && H.can_feel_pain()) H.emote("scream") diff --git a/code/game/objects/items/weapons/grenades/flashbang.dm b/code/game/objects/items/weapons/grenades/flashbang.dm index 5c80944dad..f391969bff 100644 --- a/code/game/objects/items/weapons/grenades/flashbang.dm +++ b/code/game/objects/items/weapons/grenades/flashbang.dm @@ -162,3 +162,10 @@ walk_away(src,temploc,stepdist) addtimer(CALLBACK(src, PROC_REF(detonate)), rand(15, 60), TIMER_DELETE_ME) + +/obj/item/grenade/flashbang/clusterbang/primed + desc = "This clusterbang seems to have already been activated. Uhoh." + +/obj/item/grenade/flashbang/clusterbang/primed/Initialize(mapload) + . = ..() + activate() diff --git a/code/game/objects/items/weapons/grenades/smokebomb.dm b/code/game/objects/items/weapons/grenades/smokebomb.dm index cd68ca3f3a..3a012ae99a 100644 --- a/code/game/objects/items/weapons/grenades/smokebomb.dm +++ b/code/game/objects/items/weapons/grenades/smokebomb.dm @@ -37,3 +37,10 @@ var/new_smoke_color = tgui_color_picker(user, "Choose a color for the smoke:", "Smoke Color", smoke_color) if(new_smoke_color) smoke_color = new_smoke_color + +/obj/item/grenade/smokebomb/primed + desc = "A smoke bomb. This one appears to be already activated!" + +/obj/item/grenade/smokebomb/primed/Initialize(mapload) + . = ..() + activate() diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm index 498c148e44..c1e1959c4a 100644 --- a/code/game/objects/items/weapons/stunbaton.dm +++ b/code/game/objects/items/weapons/stunbaton.dm @@ -205,7 +205,7 @@ //stun effects if(status) - target.stun_effect_act(stun, agony, hit_zone, src) + target.stun_effect_act(stun, agony, hit_zone, src, electric = TRUE) msg_admin_attack("[key_name(user)] stunned [key_name(target)] with the [src].") if(ishuman(target)) diff --git a/code/game/objects/mail.dm b/code/game/objects/mail.dm index 5b152f0f6c..5331dce999 100644 --- a/code/game/objects/mail.dm +++ b/code/game/objects/mail.dm @@ -216,6 +216,13 @@ user.put_in_hands(stuff) else stuff.forceMove(drop_location()) + //Now here's the kicker + if(HAS_TRAIT(user, TRAIT_UNLUCKY) && prob(5)) //1 in 20 chance for your mail to be rigged with a glitter bomb + to_chat(user, span_bolddanger("You open the mail and - OH SHIT IS THAT A BOMB!")) + var/obj/item/grenade/confetti/confetti_nade = new /obj/item/grenade/confetti() + confetti_nade.name = "Pipebomb" + confetti_nade.desc = span_bolddanger("What the hell are you looking at it for?! RUN!!") + confetti_nade.activate() playsound(loc, 'sound/items/poster_ripped.ogg', 100, TRUE) qdel(src) diff --git a/code/modules/blob2/overmind/types/energized_jelly.dm b/code/modules/blob2/overmind/types/energized_jelly.dm index a73d0f0b9a..0f715474e8 100644 --- a/code/modules/blob2/overmind/types/energized_jelly.dm +++ b/code/modules/blob2/overmind/types/energized_jelly.dm @@ -22,7 +22,7 @@ /datum/blob_type/energized_jelly/on_attack(obj/structure/blob/B, mob/living/victim, def_zone) victim.electrocute_act(10, src, 1, def_zone) - victim.stun_effect_act(0, 40, BP_TORSO, src) + victim.stun_effect_act(0, 40, BP_TORSO, src, electric = TRUE) /datum/blob_type/energized_jelly/on_chunk_tick(obj/item/blobcore_chunk/B) for(var/mob/living/L in oview(world.view, get_turf(B))) diff --git a/code/modules/economy/vending.dm b/code/modules/economy/vending.dm index cc42342faf..5ac4b4a4eb 100644 --- a/code/modules/economy/vending.dm +++ b/code/modules/economy/vending.dm @@ -590,6 +590,13 @@ GLOBAL_LIST_EMPTY(vending_products) addtimer(CALLBACK(src, PROC_REF(delayed_vend), R, user), vend_delay) /obj/machinery/vending/proc/delayed_vend(datum/stored_item/vending_product/R, mob/user) + if(HAS_TRAIT(user, TRAIT_UNLUCKY) && prob(10)) + visible_message(span_infoplain(span_bold("\The [src]") + " clunks and fails to dispense any item.")) + playsound(src, "sound/[vending_sound]", 100, TRUE, 1) + vend_ready = 1 + currently_vending = null + SStgui.update_uis(src) + return R.get_product(get_turf(src)) if(has_logs) do_logging(R, user, 1) @@ -715,9 +722,11 @@ GLOBAL_LIST_EMPTY(vending_products) return //Somebody cut an important wire and now we're following a new definition of "pitch." -/obj/machinery/vending/proc/throw_item() +/obj/machinery/vending/proc/throw_item(forced_target) var/obj/item/throw_item = null var/mob/living/target = locate() in view(7,src) + if(forced_target && isliving(forced_target)) + target = forced_target if(!target) return 0 diff --git a/code/modules/emotes/definitions/audible_snap.dm b/code/modules/emotes/definitions/audible_snap.dm index 4e9e314030..04d156078d 100644 --- a/code/modules/emotes/definitions/audible_snap.dm +++ b/code/modules/emotes/definitions/audible_snap.dm @@ -27,3 +27,16 @@ to_chat(user, span_warning("You need at least one working hand to snap your fingers.")) return FALSE . = ..() + +/decl/emote/audible/snap/do_extra(mob/user) + . = ..() + if(HAS_TRAIT(user, TRAIT_UNLUCKY) && prob(0.1) && ishuman(user)) //1 in a thousand + var/mob/living/carbon/human/unlucky_human = user + var/datum/component/omen/bad_luck = unlucky_human.GetComponent(/datum/component/omen) //Also going to make sure they got the EVIL version. + if(bad_luck.evil) + unlucky_human.visible_message(span_danger("[unlucky_human] snaps, their hand fading to ash!"), span_danger(span_huge("OH GOD YOUR HAND"))) + for(var/limb in list(BP_L_HAND, BP_R_HAND)) + var/obj/item/organ/external/L = unlucky_human.get_organ(limb) + if(istype(L) && L.is_usable() && !L.splinted) + L.droplimb(TRUE, DROPLIMB_BURN) + break diff --git a/code/modules/games/dice.dm b/code/modules/games/dice.dm index 45a6390cf3..f1d5b37d33 100644 --- a/code/modules/games/dice.dm +++ b/code/modules/games/dice.dm @@ -91,10 +91,10 @@ sides = 10 result = 10 -/obj/item/dice/attack_self(mob/user as mob) +/obj/item/dice/attack_self(mob/user) rollDice(user, 0) -/obj/item/dice/proc/rollDice(mob/user as mob, var/silent = 0) +/obj/item/dice/proc/rollDice(mob/user, silent = FALSE) result = rand(1, sides) if(loaded) if(cheater) @@ -103,6 +103,9 @@ else if(prob(75)) //makeshift weighted dice don't always work result = loaded icon_state = "[name][result]" + var/result_override = SEND_SIGNAL(user, COMSIG_MOB_ROLLED_DICE, src, silent, result) //We can override dice rolls! + if(result_override) + result = result_override if(!silent) var/comment = "" @@ -192,18 +195,18 @@ /obj/item/dice, ) -/obj/item/storage/dicecup/attack_self(mob/user as mob) +/obj/item/storage/dicecup/attack_self(mob/user) user.visible_message(span_notice("[user] shakes [src]."), \ span_notice("You shake [src]."), \ span_notice("You hear dice rolling.")) rollCup(user) -/obj/item/storage/dicecup/proc/rollCup(mob/user as mob) +/obj/item/storage/dicecup/proc/rollCup(mob/user) for(var/obj/item/dice/I in src.contents) var/obj/item/dice/D = I D.rollDice(user, 1) -/obj/item/storage/dicecup/proc/revealDice(var/mob/viewer) +/obj/item/storage/dicecup/proc/revealDice(mob/viewer) for(var/obj/item/dice/I in src.contents) var/obj/item/dice/D = I to_chat(viewer, "The [D.name] shows a [D.result].") @@ -230,3 +233,40 @@ . = ..() for(var/i = 1 to 5) new /obj/item/dice(src) + +/obj/item/dice/d20/cursed + name = "d20" + desc = "A dice with twenty sides." + icon_state = "d2020" + sides = 20 + result = 20 + + ///If the dice will apply the major version of unlucky or not. + var/evil = TRUE + + +/obj/item/dice/d20/cursed/rollDice(mob/user, silent = FALSE) + ..() + if(result == 1) + to_chat(user, span_cult("You feel extraordinarily unlucky...")) + if(evil) + user.AddComponent( + /datum/component/omen/dice,\ + incidents_left = 1,\ + luck_mod = 1,\ + damage_mod = 1,\ + evil = TRUE,\ + safe_disposals = FALSE,\ + vorish = TRUE,\ + ) + + else + user.AddComponent( + /datum/component/omen/dice,\ + incidents_left = 1,\ + luck_mod = 0.3,\ + damage_mod = 1,\ + evil = FALSE,\ + safe_disposals = FALSE,\ + vorish = TRUE,\ + ) diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm index 1cf046ba05..1aedf0afe2 100644 --- a/code/modules/mob/living/bot/secbot.dm +++ b/code/modules/mob/living/bot/secbot.dm @@ -323,7 +323,7 @@ if(!H.lying || H.handcuffed || arrest_type) cuff = FALSE if(!cuff) - H.stun_effect_act(0, stun_strength, null) + H.stun_effect_act(0, stun_strength, null, electric = TRUE) playsound(src, 'sound/weapons/egloves.ogg', 50, 1, -1) do_attack_animation(H) busy = TRUE diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index 8da8a1ff57..5c33f4d345 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -389,8 +389,9 @@ return /mob/living/carbon/slip(var/slipped_on,stun_duration=8) + SEND_SIGNAL(src, COMSIG_ON_CARBON_SLIP, slipped_on, stun_duration) if(buckled) - return 0 + return FALSE stop_pulling() to_chat(src, span_warning("You slipped on [slipped_on]!")) playsound(src, 'sound/misc/slip.ogg', 50, 1, -3) @@ -399,7 +400,7 @@ src.emote("sflip") return 1 //CHOMPEdit End Weaken(FLOOR(stun_duration/2, 1)) - return 1 + return TRUE /mob/living/carbon/proc/add_chemical_effect(var/effect, var/magnitude = 1) if(effect in chem_effects) diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm index 6e5dd8c0ad..eb4bd4cd63 100644 --- a/code/modules/mob/living/carbon/human/human.dm +++ b/code/modules/mob/living/carbon/human/human.dm @@ -1506,12 +1506,12 @@ if(lying) playsound(src, 'sound/misc/slip.ogg', 25, 1, -1) drop_both_hands() - return 0 + return FALSE if((species.flags & NO_SLIP && !footcoverage_check) || (shoes && (shoes.item_flags & NOSLIP))) //Footwear negates a species' natural traction. - return 0 + return FALSE if(..(slipped_on,stun_duration)) drop_both_hands() - return 1 + return TRUE /mob/living/carbon/human/proc/relocate() set category = "Object" diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 4651198e68..b36df8a1e2 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -58,7 +58,7 @@ emp_act return (..(P , def_zone)) -/mob/living/carbon/human/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone) +/mob/living/carbon/human/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null, var/electric = FALSE) var/obj/item/organ/external/affected = get_organ(check_zone(def_zone)) var/siemens_coeff = get_siemens_coefficient_organ(affected) if(fire_stacks < 0) // Water makes you more conductive. @@ -87,7 +87,7 @@ emp_act var/emote_scream = pick("screams in pain and ", "lets out a sharp cry and ", "cries out and ") automatic_custom_emote(VISIBLE_MESSAGE, "[affected.organ_can_feel_pain() ? "" : emote_scream] drops what they were holding in their [affected.name]!", check_stat = TRUE) - ..(stun_amount, agony_amount, def_zone) + ..(stun_amount, agony_amount, def_zone, used_weapon, electric) /mob/living/carbon/human/getarmor(var/def_zone, var/type) var/armorval = 0 @@ -433,8 +433,8 @@ emp_act return if(in_throw_mode && speed <= THROWFORCE_SPEED_DIVISOR) //empty active hand and we're in throw mode if(canmove && !restrained() && !src.is_incorporeal()) - if(isturf(O.loc)) - if(can_catch(O)) + if(isturf(O.loc) && can_catch(O)) + if(!SEND_SIGNAL(src, COMSIG_HUMAN_ON_CATCH_THROW, source, speed)) put_in_active_hand(O) visible_message(span_warning("[src] catches [O]!")) throw_mode_off() diff --git a/code/modules/mob/living/carbon/human/species/station/traits/negative.dm b/code/modules/mob/living/carbon/human/species/station/traits/negative.dm index 08b5a71015..d871c08045 100644 --- a/code/modules/mob/living/carbon/human/species/station/traits/negative.dm +++ b/code/modules/mob/living/carbon/human/species/station/traits/negative.dm @@ -830,3 +830,26 @@ addiction = REAGENT_ID_ASUSTENANCE custom_only = FALSE hidden = TRUE //Disabled on Virgo + +/datum/trait/negative/unlucky + name = "Unlucky" + desc = "You are naturally unlucky and ill-events often befall you." + cost = -2 + is_genetrait = FALSE + hidden = FALSE + custom_only = FALSE + added_component_path = /datum/component/omen/trait + excludes = list(/datum/trait/negative/unlucky/major) + + +/datum/trait/negative/unlucky/major + name = "Unlucky, Major" + desc = "Your luck is extremely awful and potentially fatal." + cost = -5 + tutorial = "You should avoid disposal bins." + is_genetrait = TRUE + hidden = FALSE + added_component_path = /datum/component/omen/trait/major + excludes = list(/datum/trait/negative/unlucky) + activation_message= span_cult(span_bold("What a terrible night to have a curse!")) + primitive_expression_messages=list("unluckily stubs their toe!") diff --git a/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm b/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm index 49529e6c5b..e85af9e566 100644 --- a/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm +++ b/code/modules/mob/living/carbon/human/species/virtual_reality/avatar.dm @@ -117,7 +117,7 @@ // Getting hurt in VR doesn't damage the physical body, but you still got hurt. if(ishuman(vr_holder) && total_damage) var/mob/living/carbon/human/V = vr_holder - V.stun_effect_act(0, total_damage*2/3, null) // 200 damage leaves the user in paincrit for several seconds, agony reaches 0 after around 2m. + V.stun_effect_act(0, total_damage*2/3, null, electric = FALSE) // 200 damage leaves the user in paincrit for several seconds, agony reaches 0 after around 2m. to_chat(vr_holder, span_warning("Pain from your time in VR lingers.")) // 250 damage leaves the user unconscious for several seconds in addition to paincrit // Maintain a link with the mob, but don't use teleop diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm index 4a924e4320..57f9106cb6 100644 --- a/code/modules/mob/living/living_defense.dm +++ b/code/modules/mob/living/living_defense.dm @@ -134,7 +134,7 @@ //Stun Beams if(P.taser_effect) - stun_effect_act(0, P.agony, def_zone, P) + stun_effect_act(0, P.agony, def_zone, P, electric = TRUE) if(!P.nodamage) apply_damage(P.damage, P.damage_type, def_zone, absorb, soaked, 0, sharp=proj_sharp, edge=proj_edge, used_weapon=P, projectile=TRUE) qdel(P) @@ -154,8 +154,9 @@ // return absorb //Handles the effects of "stun" weapons -/mob/living/proc/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) +/mob/living/proc/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null, var/electric = FALSE) flash_pain() + SEND_SIGNAL(src, COMSIG_STUN_EFFECT_ACT, stun_amount, agony_amount, def_zone, used_weapon, electric) if (stun_amount) Stun(stun_amount) diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm index 7f94a29462..ab07eb7d3f 100644 --- a/code/modules/mob/living/silicon/silicon.dm +++ b/code/modules/mob/living/silicon/silicon.dm @@ -91,7 +91,7 @@ to_chat(src, span_danger("Warning: Electromagnetic pulse detected.")) ..() -/mob/living/silicon/stun_effect_act(var/stun_amount, var/agony_amount) +/mob/living/silicon/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null, var/electric = FALSE) return //immune /mob/living/silicon/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 0.0, var/def_zone = null, var/stun = 1) diff --git a/code/modules/mob/living/simple_mob/defense.dm b/code/modules/mob/living/simple_mob/defense.dm index 45ced044d8..f86d0fcbac 100644 --- a/code/modules/mob/living/simple_mob/defense.dm +++ b/code/modules/mob/living/simple_mob/defense.dm @@ -190,9 +190,8 @@ . = min(., 1.0) // Electricity -/mob/living/simple_mob/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null) - var/zap = min((1-get_shock_protection()), siemens_coeff) - shock_damage *= zap +/mob/living/simple_mob/electrocute_act(var/shock_damage, var/obj/source, var/siemens_coeff = 1.0, var/def_zone = null, var/stun = 1) + shock_damage *= siemens_coeff if(shock_damage < 1) return 0 @@ -217,7 +216,7 @@ . = min(., 1.0) // Shot with taser/stunvolver -/mob/living/simple_mob/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null) +/mob/living/simple_mob/stun_effect_act(var/stun_amount, var/agony_amount, var/def_zone, var/used_weapon=null, var/electric = FALSE) if(taser_kill) var/stunDam = 0 var/agonyDam = 0 diff --git a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm index 4de155da2d..e7d3b53545 100644 --- a/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm +++ b/code/modules/mob/living/simple_mob/subtypes/slime/xenobio/defense.dm @@ -44,7 +44,7 @@ adjust_discipline(2) // Justified. // Shocked grilles don't hurt slimes, and in fact give them charge. -/mob/living/simple_mob/slime/xenobio/electrocute_act(shock_damage, obj/source, siemens_coeff = 1.0, def_zone = null) +/mob/living/simple_mob/slime/xenobio/electrocute_act(shock_damage, obj/source, siemens_coeff = 1.0, def_zone = null, stun = 1) power_charge = between(0, power_charge + round(shock_damage / 10), 10) to_chat(src, span_notice("\The [source] shocks you, and it charges you.")) diff --git a/code/modules/modular_computers/computers/modular_computer/interaction.dm b/code/modules/modular_computers/computers/modular_computer/interaction.dm index f7d1044402..d0f72e6739 100644 --- a/code/modules/modular_computers/computers/modular_computer/interaction.dm +++ b/code/modules/modular_computers/computers/modular_computer/interaction.dm @@ -116,8 +116,19 @@ // On-click handling. Turns on the computer if it's off and opens the GUI. /obj/item/modular_computer/attack_self(var/mob/user) if(enabled && screen_on) + if(isliving(user) && HAS_TRAIT(user, TRAIT_UNLUCKY) && prob(5)) + var/mob/living/unlucky_soul = user + to_chat(user, span_danger("You interact with \the [src] and are met with a sudden shock!")) + var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread + s.set_up(5, 1, src) + s.start() + unlucky_soul.electrocute_act(5, src, 1) + return tgui_interact(user) else if(!enabled && screen_on) + if(HAS_TRAIT(user, TRAIT_UNLUCKY) && prob(25)) + to_chat(user, "You try to turn on \the [src] but it doesn't respond.") + return turn_on(user) /obj/item/modular_computer/attackby(var/obj/item/W, var/mob/user) diff --git a/code/modules/multiz/stairs.dm b/code/modules/multiz/stairs.dm index 6b6904d7f9..c57c0348ed 100644 --- a/code/modules/multiz/stairs.dm +++ b/code/modules/multiz/stairs.dm @@ -193,7 +193,7 @@ var/mob/living/L = P if(L.client) L.client.Process_Grab() // Update any miscellanous grabs, possibly break grab-chains - + SEND_SIGNAL(AM, COMSIG_MOVED_UP_STAIRS, AM, oldloc) return TRUE /obj/structure/stairs/bottom/use_stairs_instant(var/atom/movable/AM) @@ -231,6 +231,7 @@ L.client.Process_Grab() else AM.forceMove(get_turf(top)) + SEND_SIGNAL(AM, COMSIG_MOVED_UP_STAIRS, AM) ////////////////////////////////////////////////////////////////////// @@ -452,7 +453,7 @@ var/mob/living/L = P if(L.client) L.client.Process_Grab() // Update any miscellanous grabs, possibly break grab-chains - + SEND_SIGNAL(AM, COMSIG_MOVED_DOWN_STAIRS, AM, oldloc) return TRUE /obj/structure/stairs/top/use_stairs_instant(var/atom/movable/AM) @@ -489,6 +490,7 @@ L.client.Process_Grab() else AM.forceMove(get_turf(bottom)) + SEND_SIGNAL(AM, COMSIG_MOVED_DOWN_STAIRS, AM) // Mapping pieces, placed at the bottommost part of the stairs /obj/structure/stairs/spawner diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm index d4b1523f8c..d87ff3b57f 100644 --- a/code/modules/power/lighting.dm +++ b/code/modules/power/lighting.dm @@ -869,7 +869,7 @@ GLOBAL_LIST_EMPTY(light_type_cache) // break the light and make sparks if was on -/obj/machinery/light/proc/broken(var/skip_sound_and_sparks = 0) +/obj/machinery/light/proc/broken(var/skip_sound_and_sparks = FALSE) if(status == LIGHT_EMPTY) return diff --git a/code/modules/power/lighting_vr.dm b/code/modules/power/lighting_vr.dm index c5214fa3aa..3eaaf76807 100644 --- a/code/modules/power/lighting_vr.dm +++ b/code/modules/power/lighting_vr.dm @@ -150,7 +150,7 @@ overlay_above_everything = TRUE color = "#3e5064" -/obj/machinery/light/small/fairylights/broken() +/obj/machinery/light/small/fairylights/broken(var/skip_sound_and_sparks = FALSE) return /obj/machinery/light/small/fairylights/flicker diff --git a/code/modules/projectiles/guns/energy/cyborg.dm b/code/modules/projectiles/guns/energy/cyborg.dm index 8dbba00144..7d2539d54d 100644 --- a/code/modules/projectiles/guns/energy/cyborg.dm +++ b/code/modules/projectiles/guns/energy/cyborg.dm @@ -273,7 +273,7 @@ target.visible_message(span_danger("[target] has been zapped with [src] by [user]!")) playsound(src, 'sound/weapons/egloves.ogg', 50, 1, -1) - target.stun_effect_act(0, agony, hit_zone, src) + target.stun_effect_act(0, agony, hit_zone, src, electric = TRUE) msg_admin_attack("[key_name(user)] stunned [key_name(target)] with the [src].") if(ishuman(target)) var/mob/living/carbon/human/H = target @@ -495,7 +495,7 @@ else target.visible_message(span_danger("[target] has been prodded with [src] by [user]!")) playsound(src, 'sound/weapons/egloves.ogg', 50, 1, -1) - target.stun_effect_act(stun, agony, hit_zone, src) + target.stun_effect_act(stun, agony, hit_zone, src, electric = TRUE) msg_admin_attack("[key_name(user)] stunned [key_name(target)] with the [src].") if(ishuman(target)) var/mob/living/carbon/human/H = target diff --git a/code/modules/vore/resizing/crackers.dm b/code/modules/vore/resizing/crackers.dm index e27848e15b..dd75e8faf2 100644 --- a/code/modules/vore/resizing/crackers.dm +++ b/code/modules/vore/resizing/crackers.dm @@ -2,6 +2,14 @@ //The winner of the pull has an effect applied to them. //Crackers do already exist, but these ones are a more memey scene item. +#define SHRINKING_CRACKER "shrinking" +#define GROWING_CRACKER "growing" +#define DRUGGED_CRACKER "drugged" +#define INVISIBLE_CRACKER "invisibility" +#define FALLING_CRACKER "knockdown" +#define TELEPORTING_CRACKER "teleport" +#define WEALTHY_CRACKER "wealth" + /obj/item/cracker name = "bluespace cracker" //I have no idea why this was called shrink ray when this increased and decreased size. desc = "A celebratory little game with a bluespace twist! Pull it between two people until it snaps, and the person who recieves the larger end gets a prize!" @@ -13,26 +21,35 @@ ) item_state = "blue" var/rigged = 0 //So that they can be rigged by varedits to go one way or the other. positive values mean holder always wins, negative values mean target always wins. - var/list/prizes = list("shrinking","growing","drugged","invisibility","knocked over","teleport","wealth") - var/list/jokes = list( - "When is a boat just like snow? When it's adrift.", - "What happens to naughty elves? Santa gives them the sack.", - "What do you call an old snowman? Water.", - "Why has Santa been banned from sooty chimneys? Carbon footprints.", - "What goes down but doesn't come up? A yo.", - "What's green and fuzzy, has four legs and would kill you if it fell from a tree? A pool table.", - "Why did the blind man fall into the well? Because he couldn't see that well.", - "What did the pirate get on his report card? Seven Cs", - "What do you call a fish with no eyes? Fsh", - "How do you make an egg roll? You push it.", - "What do you call a deer with no eyes? NO EYED DEER!", - "What's red, and smells like blue paint? Red paint.", - "Where do cows go to dance? A meat ball.", - "What do you call a person who steals all your toenail clippings? A cliptoemaniac.", - "What's brown and sticky? A stick.", - "What's the best way to kill a circus? Go for the juggler.", - "What do you call a cow with no legs? Ground Beef.", - "Why'd the scarecrow win the Nobel prize? He was outstanding in his field.") + var/prize //What prize we have loaded + var/joke //What joke we have loaded + +/obj/item/cracker/Initialize(mapload) + . = ..() + var/style = pick("blue","green","yellow","red","heart","hazard") + icon_state = style + item_state = style + if(!prize) + prize = pick(SHRINKING_CRACKER,GROWING_CRACKER,GROWING_CRACKER,INVISIBLE_CRACKER,FALLING_CRACKER,TELEPORTING_CRACKER,WEALTHY_CRACKER) + if(!joke) + joke = pick("When is a boat just like snow? When it's adrift.", + "What happens to naughty elves? Santa gives them the sack.", + "What do you call an old snowman? Water.", + "Why has Santa been banned from sooty chimneys? Carbon footprints.", + "What goes down but doesn't come up? A yo.", + "What's green and fuzzy, has four legs and would kill you if it fell from a tree? A pool table.", + "Why did the blind man fall into the well? Because he couldn't see that well.", + "What did the pirate get on his report card? Seven Cs", + "What do you call a fish with no eyes? Fsh", + "How do you make an egg roll? You push it.", + "What do you call a deer with no eyes? NO EYED DEER!", + "What's red, and smells like blue paint? Red paint.", + "Where do cows go to dance? A meat ball.", + "What do you call a person who steals all your toenail clippings? A cliptoemaniac.", + "What's brown and sticky? A stick.", + "What's the best way to kill a circus? Go for the juggler.", + "What do you call a cow with no legs? Ground Beef.", + "Why'd the scarecrow win the Nobel prize? He was outstanding in his field.") /obj/item/cracker/attack(atom/A, mob/living/user, adjacent, params) var/mob/living/carbon/human/target = A @@ -56,8 +73,6 @@ to_chat(user, span_notice("\The [src] is no longer in-hand!")) to_chat(target, span_notice("\The [src] is no longer in-hand!")) return - var/prize = pick(prizes) - var/joke = pick(jokes) var/mob/living/carbon/human/winner var/mob/living/carbon/human/loser if(!rigged) @@ -74,46 +89,52 @@ else winner = target loser = user + if(HAS_TRAIT(loser, TRAIT_UNLUCKY) && prob(66)) + if(prize == (SHRINKING_CRACKER || GROWING_CRACKER || FALLING_CRACKER || TELEPORTING_CRACKER)) //If we're unlucky and the prize is bad, chance for us to get it! + var/former_winner = winner + winner = loser + loser = former_winner var/spawnloc = get_turf(winner) winner.visible_message(span_notice("\The [winner] wins the cracker prize!"),span_notice("You win the cracker prize!")) - if(prize == "shrinking") - winner.resize(0.25) - winner.visible_message(span_bold("\The [winner]") + " shrinks suddenly!") - if(prize == "growing") - winner.resize(2) - winner.visible_message(span_bold("\The [winner]") + " grows in height suddenly.") - if(prize == "drugged") - winner.druggy = max(winner.druggy, 50) - if(prize == "invisibility") - if(!winner.cloaked) - winner.visible_message(span_bold("\The [winner]") + " vanishes from sight.") - winner.cloak() - spawn(600) - if(winner.cloaked) - winner.uncloak() - winner.visible_message(span_bold("\The [winner]") + " appears as if from thin air.") - if(prize == "knocked over") - winner.visible_message(span_bold("\The [winner]") + " is suddenly knocked to the ground.") - winner.weakened = max(winner.weakened,50) - if(prize == "teleport") - if(loser.can_be_drop_pred && loser.vore_selected) - if(winner.devourable && winner.can_be_drop_prey) - winner.visible_message(span_bold("\The [winner]") + " is teleported to somewhere nearby...") - var/datum/effect/effect/system/spark_spread/spk - spk = new(winner) + switch(prize) + if(SHRINKING_CRACKER) + winner.resize(0.25) + winner.visible_message(span_bold("\The [winner]") + " shrinks suddenly!") + if(GROWING_CRACKER) + winner.resize(2) + winner.visible_message(span_bold("\The [winner]") + " grows in height suddenly.") + if(GROWING_CRACKER) + winner.druggy = max(winner.druggy, 50) + if(INVISIBLE_CRACKER) + if(!winner.cloaked) + winner.visible_message(span_bold("\The [winner]") + " vanishes from sight.") + winner.cloak() + spawn(600) + if(winner.cloaked) + winner.uncloak() + winner.visible_message(span_bold("\The [winner]") + " appears as if from thin air.") + if(FALLING_CRACKER) + winner.visible_message(span_bold("\The [winner]") + " is suddenly knocked to the ground.") + winner.weakened = max(winner.weakened,50) + if(TELEPORTING_CRACKER) + if(loser.can_be_drop_pred && loser.vore_selected) + if(winner.devourable && winner.can_be_drop_prey) + winner.visible_message(span_bold("\The [winner]") + " is teleported to somewhere nearby...") + var/datum/effect/effect/system/spark_spread/spk + spk = new(winner) - var/T = get_turf(winner) - spk.set_up(5, 0, winner) - spk.attach(winner) - playsound(T, "sparks", 50, 1) - anim(T,winner,'icons/mob/mob.dmi',,"phaseout",,winner.dir) - winner.forceMove(loser.vore_selected) - if(prize == "wealth") - new /obj/random/cash/huge(spawnloc) - new /obj/random/cash/huge(spawnloc) - winner.visible_message(span_bold("\The [winner]") + " has a whole load of cash fall at their feet!") + var/T = get_turf(winner) + spk.set_up(5, 0, winner) + spk.attach(winner) + playsound(T, "sparks", 50, 1) + anim(T,winner,'icons/mob/mob.dmi',,"phaseout",,winner.dir) + winner.forceMove(loser.vore_selected) + if(WEALTHY_CRACKER) + new /obj/random/cash/huge(spawnloc) + new /obj/random/cash/huge(spawnloc) + winner.visible_message(span_bold("\The [winner]") + " has a whole load of cash fall at their feet!") playsound(user, 'sound/effects/snap.ogg', 50, 1) user.drop_item(src) @@ -123,40 +144,33 @@ J.info = joke qdel(src) -/obj/item/cracker/Initialize(mapload) - var/list/styles = list("blue","green","yellow","red","heart","hazard") - var/style = pick(styles) - icon_state = style - item_state = style - . = ..() - /obj/item/cracker/shrinking name = "shrinking bluespace cracker" - prizes = list("shrinking") + prize = SHRINKING_CRACKER /obj/item/cracker/growing name = "growing bluespace cracker" - prizes = list("growing") + prize = GROWING_CRACKER /obj/item/cracker/invisibility name = "cloaking bluespace cracker" - prizes = list("invisibility") + prize = INVISIBLE_CRACKER /obj/item/cracker/drugged name = "psychedelic bluespace cracker" - prizes = list("drugged") + prize = GROWING_CRACKER /obj/item/cracker/knockover name = "forceful bluespace cracker" - prizes = list("knocked over") + prize = FALLING_CRACKER /obj/item/cracker/vore name = "teleporting bluespace cracker" - prizes = list("teleport") + prize = TELEPORTING_CRACKER /obj/item/cracker/money name = "fortuitous bluespace cracker" - prizes = list("wealth") + prize = WEALTHY_CRACKER /obj/item/clothing/head/paper_crown name = "paper crown" @@ -181,3 +195,11 @@ /obj/item/paper/cracker_joke/update_icon() return + +#undef SHRINKING_CRACKER +#undef GROWING_CRACKER +#undef DRUGGED_CRACKER +#undef INVISIBLE_CRACKER +#undef FALLING_CRACKER +#undef TELEPORTING_CRACKER +#undef WEALTHY_CRACKER diff --git a/sound/effects/tableheadsmash.ogg b/sound/effects/tableheadsmash.ogg new file mode 100644 index 0000000000..1f11344d56 Binary files /dev/null and b/sound/effects/tableheadsmash.ogg differ diff --git a/vorestation.dme b/vorestation.dme index 3214be9345..fa1e904ecd 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -688,6 +688,7 @@ #include "code\datums\components\traits\gargoyle.dm" #include "code\datums\components\traits\nutrition_size_change.dm" #include "code\datums\components\traits\photosynth.dm" +#include "code\datums\components\traits\unlucky.dm" #include "code\datums\components\traits\waddle.dm" #include "code\datums\components\traits\weaver.dm" #include "code\datums\diseases\_disease.dm"