diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index b5c62ae9056..5603bae60f2 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -226,6 +226,8 @@ #define COMSIG_CLICK_CTRL "ctrl_click" ///from base of atom/AltClick(): (/mob) #define COMSIG_CLICK_ALT "alt_click" + /// Cancel the alt-click, since this isn't properly part of the attack chain + #define COMPONENT_CANCEL_ALTCLICK (1<<0) ///from base of atom/CtrlShiftClick(/mob) #define COMSIG_CLICK_CTRL_SHIFT "ctrl_shift_click" ///from base of atom/MouseDrop(): (/atom/over, /mob/user) @@ -304,6 +306,13 @@ #define HEARING_SPANS 6 #define HEARING_MESSAGE_MODE 7 */ +/// Called just before something gets untilted +#define COMSIG_MOVABLE_TRY_UNTILT "movable_try_untilt" + /// Return this to block an untilt attempt + #define COMPONENT_BLOCK_UNTILT (1<<0) +/// Called when something gets untilted, from /datum/element/tilted/proc/do_untilt(atom/movable/source, mob/user) +#define COMSIG_MOVABLE_UNTILTED "movable_untilted" + ///called when the movable is added to a disposal holder object for disposal movement: (obj/structure/disposalholder/holder, obj/machinery/disposal/source) #define COMSIG_MOVABLE_DISPOSING "movable_disposing" ///called when the movable is removed from a disposal holder object: /obj/structure/disposalpipe/proc/expel(): (obj/structure/disposalholder/H, turf/T, direction) diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 82bac6d4557..ac36ea86129 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -149,6 +149,13 @@ continue // These are not valid objectives to add. GLOB.admin_objective_list[initial(O.name)] = path + for(var/path in subtypesof(/datum/tilt_crit)) + var/datum/tilt_crit/crit = path + if(isnull(initial(crit.name))) + continue + crit = new path() + GLOB.tilt_crits[path] = crit + /* // Uncomment to debug chemical reaction list. /client/verb/debug_chemical_list() diff --git a/code/__HELPERS/unsorted.dm b/code/__HELPERS/unsorted.dm index fdbfe06905f..22cc060d2f2 100644 --- a/code/__HELPERS/unsorted.dm +++ b/code/__HELPERS/unsorted.dm @@ -627,7 +627,7 @@ Returns 1 if the chain up to the area contains the given typepath return 1 -/proc/is_blocked_turf(turf/T, exclude_mobs) +/proc/is_blocked_turf(turf/T, exclude_mobs, list/excluded_objs) if(T.density) return TRUE if(locate(/mob/living/silicon/ai) in T) //Prevents jaunting onto the AI core cheese, AI should always block a turf due to being a dense mob even when unanchored @@ -636,7 +636,10 @@ Returns 1 if the chain up to the area contains the given typepath for(var/mob/living/L in T) if(L.density) return TRUE + var/any_excluded_objs = length(excluded_objs) for(var/obj/O in T) + if(any_excluded_objs && (O in excluded_objs)) + continue if(O.density) return TRUE return FALSE diff --git a/code/_globalvars/lists/misc_lists.dm b/code/_globalvars/lists/misc_lists.dm index 0db07058085..d4112046a41 100644 --- a/code/_globalvars/lists/misc_lists.dm +++ b/code/_globalvars/lists/misc_lists.dm @@ -66,3 +66,6 @@ GLOBAL_LIST_EMPTY(blurb_witnesses) /// List of looping sounds GLOBAL_LIST_EMPTY(looping_sounds) + +/// List of possible crits from things tipping over +GLOBAL_LIST_EMPTY(tilt_crits) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 2370ddccd17..f92675a1f60 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -337,7 +337,8 @@ ..() /atom/proc/AltClick(mob/user) - SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) + if(SEND_SIGNAL(src, COMSIG_CLICK_ALT, user) & COMPONENT_CANCEL_ALTCLICK) + return var/turf/T = get_turf(src) if(T && (isturf(loc) || isturf(src)) && user.TurfAdjacent(T)) user.listed_turf = T diff --git a/code/datums/components/tilted.dm b/code/datums/components/tilted.dm new file mode 100644 index 00000000000..c4e97ee6efd --- /dev/null +++ b/code/datums/components/tilted.dm @@ -0,0 +1,109 @@ +/** + * A component that should be attached to things that have been tilted over, and can be righted. + * This can optionally block normal attack_hand interactions + */ + +/datum/component/tilted + dupe_mode = COMPONENT_DUPE_UNIQUE_PASSARGS + /// How long it should take to untilt + var/untilt_duration + /// Whether we should block any interactions with it + var/block_interactions + /// The angle by which we rotated as a result of tilting. Should help us avoid cases where something gets tilted until it's upright. + var/rotated_angle + +/datum/component/tilted/Initialize(_untilt_duration = 16 SECONDS, _block_interactions = FALSE, _rotated_angle) + . = ..() + untilt_duration = _untilt_duration + block_interactions = _block_interactions + rotated_angle = _rotated_angle + +/datum/component/tilted/InheritComponent(datum/component/C, i_am_original, _untilt_duration, _block_interactions, _rotated_angle) + . = ..() + untilt_duration = _untilt_duration + block_interactions = _block_interactions + rotated_angle += _rotated_angle + + if(rotated_angle % 360 == 0) + qdel(src) + +/datum/component/tilted/RegisterWithParent() + . = ..() + + if(!ismovable(parent)) + return COMPONENT_INCOMPATIBLE + + RegisterSignal(parent, COMSIG_CLICK_ALT, PROC_REF(on_alt_click)) + RegisterSignal(parent, COMSIG_PARENT_EXAMINE, PROC_REF(on_examine)) + RegisterSignal(parent, COMSIG_ATOM_ATTACK_HAND, PROC_REF(on_interact)) + RegisterSignal(parent, COMSIG_MOVABLE_TRY_UNTILT, PROC_REF(on_try_untilt)) + RegisterSignal(parent, COMSIG_MOVABLE_UNTILTED, PROC_REF(on_untilt)) + +/datum/component/tilted/UnregisterFromParent() + . = ..() + UnregisterSignal(parent, COMSIG_CLICK_ALT) + UnregisterSignal(parent, COMSIG_PARENT_EXAMINE) + UnregisterSignal(parent, COMSIG_ATOM_ATTACK_HAND) + UnregisterSignal(parent, COMSIG_MOVABLE_TRY_UNTILT) + UnregisterSignal(parent, COMSIG_MOVABLE_UNTILTED) + +/datum/component/tilted/proc/on_examine(datum/source, mob/user, list/examine_list) + SIGNAL_HANDLER // COMSIG_PARENT_EXAMINE + examine_list += "It's been tilted over. Alt+Click it to right it." + +/datum/component/tilted/proc/on_alt_click(atom/source, mob/user) + SIGNAL_HANDLER // COMSIG_CLICK_ALT + INVOKE_ASYNC(src, PROC_REF(untilt), user, untilt_duration) + return COMPONENT_CANCEL_ALTCLICK + +/datum/component/tilted/proc/on_interact(atom/source, mob/user) + SIGNAL_HANDLER // COMSIG_ATOM_ATTACK_HAND + if(block_interactions) + to_chat(user, "You can't do that right now, you need to right it first!") + return COMPONENT_CANCEL_ATTACK_CHAIN + +/datum/component/tilted/proc/on_untilt(atom/source, mob/user) + SIGNAL_HANDLER + qdel(src) + +/datum/component/tilted/proc/on_try_untilt(atom/source, mob/living/user) + SIGNAL_HANDLER + INVOKE_ASYNC(src, PROC_REF(untilt), user, untilt_duration) + +/// Untilt a tilted object. +/datum/component/tilted/proc/untilt(mob/living/user, duration = 10 SECONDS) + var/atom/movable/atom_parent = parent + + if(!istype(atom_parent)) + return + + if(!istype(user) || !atom_parent.Adjacent(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) + return + + if(user) + user.visible_message( + "[user] begins to right [parent].", + "You begin to right [parent]." + ) + if(!do_after(user, duration, TRUE, parent)) + return + user.visible_message( + "[user] rights [parent].", + "You right [parent].", + "You hear a loud clang." + ) + + if(QDELETED(atom_parent)) + return + + atom_parent.unbuckle_all_mobs(TRUE) + + SEND_SIGNAL(parent, COMSIG_MOVABLE_UNTILTED, user) + + atom_parent.layer = initial(atom_parent.layer) + + var/matrix/M = matrix() + M.Turn(0) + atom_parent.transform = M + if(istype(user) && user.incapacitated()) + return COMPONENT_BLOCK_UNTILT diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm index 740a0e026f9..c545f6c2d49 100644 --- a/code/datums/status_effects/neutral.dm +++ b/code/datums/status_effects/neutral.dm @@ -235,48 +235,11 @@ return if(L == owner || L.stat == DEAD || isslime(L) || ismonkeybasic(L)) //xenobio moment continue - new /obj/effect/temp_visual/lwap_ping(owner.loc, owner, L) + new /obj/effect/temp_visual/single_user/lwap_ping(owner.loc, owner, L) locks++ #undef LWAP_LOCK_CAP -/obj/effect/temp_visual/lwap_ping - duration = 0.5 SECONDS - randomdir = FALSE - icon = 'icons/obj/projectiles.dmi' - /// The image shown to lwap users - var/image/lwap_image - /// The person with the lwap at the moment, really just used to remove this from their screen - var/source_UID - /// The icon state applied to the image created for this ping. - var/real_icon_state = "red_laser" - -/obj/effect/temp_visual/lwap_ping/Initialize(mapload, mob/living/looker, mob/living/creature) - . = ..() - if(!looker || !creature) - return INITIALIZE_HINT_QDEL - lwap_image = image(icon = icon, loc = src, icon_state = real_icon_state, layer = ABOVE_ALL_MOB_LAYER, pixel_x = ((creature.x - looker.x) * 32), pixel_y = ((creature.y - looker.y) * 32)) - lwap_image.plane = ABOVE_LIGHTING_PLANE - lwap_image.mouse_opacity = MOUSE_OPACITY_TRANSPARENT - source_UID = looker.UID() - add_mind(looker) - -/obj/effect/temp_visual/lwap_ping/Destroy() - var/mob/living/previous_user = locateUID(source_UID) - if(previous_user) - remove_mind(previous_user) - // Null so we don't shit the bed when we delete - lwap_image = null - return ..() - -/// Add the image to the lwap user's screen -/obj/effect/temp_visual/lwap_ping/proc/add_mind(mob/living/looker) - looker.client?.images |= lwap_image - -/// Remove the image from the lwap user's screen -/obj/effect/temp_visual/lwap_ping/proc/remove_mind(mob/living/looker) - looker.client?.images -= lwap_image - /datum/status_effect/delayed id = "delayed_status_effect" status_type = STATUS_EFFECT_MULTIPLE diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 90d8d2ae548..aa83c9e1bc9 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -663,3 +663,164 @@ /atom/movable/MouseExited(location, control, params) usr.hud_used.screentip_text.maptext = "" + +/atom/movable/proc/choose_crush_crit(mob/living/carbon/victim) + if(!length(GLOB.tilt_crits)) + return + for(var/crit_path in shuffle(GLOB.tilt_crits)) + var/datum/tilt_crit/C = GLOB.tilt_crits[crit_path] + if(C.is_valid(src, victim)) + return C + +/atom/movable/proc/handle_squish_carbon(mob/living/carbon/victim, damage_to_deal, datum/tilt_crit/crit) + + // 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 + + if(HAS_TRAIT(victim, TRAIT_DWARF)) + // also double damage if you're short + damage_to_deal *= 2 + + if(crit) + crit_rebate = crit.tip_crit_effect(src, victim) + if(crit.harmless) + return + + add_attack_logs(null, victim, "critically crushed by [src] causing [crit]") + else + 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, BODY_ZONE_CHEST, spread_damage = TRUE) + else + var/picked_zone + var/num_parts_to_pick = 2 + for(var/i in 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/squish, 80 SECONDS) + +#define NO_CRUSH_DIR "no_dir" + +/** + * Tip over this atom onto a turf, crushing things in its path. + * + * Arguments: + * * target_turf - The turf to fall onto. + * * should_crit - If true, we'll try to crit things that we crush. + * * crit_damage_factor - If a crit is rolled, crush_damage will be multiplied by this amount. + * * forced_crit - If passed, this crit will be applied to everything it crushes. + * * weaken_time - The amount of time that weaken will be applied to crushed mobs. + * * knockdown_time - The amount of time that knockdown will be applied to crushed mobs. + * * ignore_gravity - If false, we won't fall over in zero G. + * * should_rotate - If false, we won't rotate when we fall. + * * angle - The angle by which we'll rotate. If this is null/0, we'll randomly rotate 90 degrees clockwise or counterclockwise. + * * rightable - If true, the tilted component will be applied, allowing people to alt-click to right it. + * * block_interactions_until_righted - If true, interactions with the object will be blocked until it's righted. + * * crush_dir - An override on the cardinal direction we're crushing. + */ +/atom/movable/proc/fall_and_crush(turf/target_turf, crush_damage, should_crit = FALSE, crit_damage_factor = 2, datum/tilt_crit/forced_crit, weaken_time = 4 SECONDS, knockdown_time = 10 SECONDS, ignore_gravity = FALSE, should_rotate = TRUE, angle, rightable = FALSE, block_interactions_until_righted = FALSE, crush_dir = NO_CRUSH_DIR) + if(QDELETED(src) || isnull(target_turf)) + return + + if(crush_dir == NO_CRUSH_DIR) + crush_dir = get_dir(get_turf(src), target_turf) + + var/has_tried_to_move = FALSE + + if(is_blocked_turf(target_turf, TRUE, excluded_objs=list(src))) + has_tried_to_move = TRUE + if(!Move(target_turf, crush_dir)) + // we'll try to move, and if we didn't end up going anywhere, then we do nothing. + visible_message("[src] seems to rock, but doesn't fall over!") + return + + for(var/atom/target in (target_turf.contents) + target_turf) + if(isarea(target) || target == src) // don't crush ourselves + continue + + if(isobserver(target)) + continue + + // ignore things that are under the ground + if(isobj(target) && (target.invisibility > SEE_INVISIBLE_LIVING) || iseffect(target) || isitem(target) || target.level == 1) + continue + + var/datum/tilt_crit/crit_case = forced_crit + if(isnull(forced_crit) && should_crit) + crit_case = choose_crush_crit(target) + // note that it could still be null after this point, in which case it won't crit + var/damage_to_deal = crush_damage + + if(isliving(target)) + var/mob/living/L = target + + if(crit_case) + damage_to_deal *= crit_damage_factor + if(iscarbon(L)) + handle_squish_carbon(L, damage_to_deal, crit_case) + else + L.apply_damage(damage_to_deal, BRUTE) + L.Weaken(weaken_time) + L.emote("scream") + L.KnockDown(knockdown_time) + playsound(L, 'sound/effects/blobattack.ogg', 40, TRUE) + playsound(L, 'sound/effects/splat.ogg', 50, TRUE) + add_attack_logs(src, L, "crushed by [src]") + + + else if(isobj(target)) // don't crush things on the floor, that'd probably be annoying + var/obj/O = target + O.take_damage(damage_to_deal, BRUTE, "", FALSE) + else + continue + + target.visible_message( + "[target] is crushed by [src]!", + "[src] crushes you!", + "You hear a loud crunch!" + ) + + tilt_over(target_turf, angle, should_rotate, rightable, block_interactions_until_righted) + // for things that trigger on Crossed() + if(!has_tried_to_move) + Move(target_turf, crush_dir) + + return TRUE + +#undef NO_CRUSH_DIR + +/** + * Tip over an atom without too much fuss. This won't cause damage to anything, and just rotates the thing and (optionally) adds the component. + * + * Arguments: + * * target - The turf to tilt over onto + * * rotation_angle - The angle to rotate by. If not given, defaults to random rotating by 90 degrees clockwise or counterclockwise + * * should_rotate - Whether or not we should rotate at all + * * rightable - Whether or not this object should be rightable, attaching the tilted component to it + * * block_interactions_until_righted - If true, this object will need to be righted before it can be interacted with + */ +/atom/movable/proc/tilt_over(turf/target, rotation_angle, should_rotate, rightable, block_interactions_until_righted) + visible_message("[src] tips over!", "You hear a loud crash!") + playsound(src, "sound/effects/bang.ogg", 100, TRUE) + var/rot_angle = rotation_angle ? rotation_angle : pick(90, -90) + if(should_rotate) + var/matrix/to_turn = turn(transform, rot_angle) + animate(src, transform = to_turn, 0.2 SECONDS) + if(target && target != get_turf(src)) + throw_at(target, 1, 1, spin = FALSE) + if(rightable) + layer = ABOVE_MOB_LAYER + AddComponent(/datum/component/tilted, 14 SECONDS, block_interactions_until_righted, rot_angle) + +/// Untilt a tilted object. +/atom/movable/proc/untilt(mob/living/user, duration = 10 SECONDS) + SEND_SIGNAL(src, COMSIG_MOVABLE_TRY_UNTILT, user) + + +/// useful callback for things that want special behavior on crush +/atom/movable/proc/on_crush_thing(atom/thing) + return diff --git a/code/game/gamemodes/malfunction/Malf_Modules.dm b/code/game/gamemodes/malfunction/Malf_Modules.dm index 19febda2574..24bf862a9da 100644 --- a/code/game/gamemodes/malfunction/Malf_Modules.dm +++ b/code/game/gamemodes/malfunction/Malf_Modules.dm @@ -1,3 +1,9 @@ +#define MALF_AI_ROLL_TIME 0.5 SECONDS +#define MALF_AI_ROLL_COOLDOWN (1 SECONDS + MALF_AI_ROLL_TIME) +#define MALF_AI_ROLL_DAMAGE 75 +// crit percent +#define MALF_AI_ROLL_CRIT_CHANCE 5 + //The malf AI action subtype. All malf actions are subtypes of this. /datum/action/innate/ai name = "AI Action" @@ -474,7 +480,7 @@ /obj/effect/proc_holder/ranged_ai/overload_machine active = FALSE - ranged_mousepointer = 'icons/effects/overload_machine_target.dmi' + ranged_mousepointer = 'icons/effects/cult_target.dmi' enable_text = "You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel." disable_text = "You release your hold on the powernet." @@ -820,3 +826,91 @@ actual_action.fix_borg(robot_target) remove_ranged_ability(ranged_ability_user, "[robot_target] successfully rebooted.") return TRUE + +/datum/AI_Module/core_tilt + module_name = "Rolling Servos" + mod_pick_name = "watchforrollingcores" + description = "Allows you to slowly roll your core around, crushing anything in your path with your bulk." + cost = 10 + one_purchase = FALSE + power_type = /datum/action/innate/ai/ranged/core_tilt + unlock_sound = 'sound/effects/bang.ogg' + unlock_text = "You gain the ability to roll over and crush anything in your way." + +/datum/action/innate/ai/ranged/core_tilt + name = "Roll Over" + button_icon_state = "roll_over" + desc = "Allows you to roll over in the direction of your choosing, crushing anything in your way." + auto_use_uses = FALSE + linked_ability_type = /obj/effect/proc_holder/ranged_ai/roll_over + + +/obj/effect/proc_holder/ranged_ai/roll_over + active = FALSE + ranged_mousepointer = 'icons/effects/cult_target.dmi' + enable_text = "Your inner servos shift as you prepare to roll around. Click adjacent tiles to roll into them!" + disable_text = "You disengage your rolling protocols." + COOLDOWN_DECLARE(time_til_next_tilt) + /// How long does it take us to roll? + var/roll_over_time = MALF_AI_ROLL_TIME + /// How long does it take for the ability to cool down, on top of [roll_over_time]? + var/roll_over_cooldown = MALF_AI_ROLL_COOLDOWN + + +/obj/effect/proc_holder/ranged_ai/roll_over/InterceptClickOn(mob/living/caller, params, atom/target_atom) + if(..()) + return + if(!isAI(ranged_ability_user)) + return + if(ranged_ability_user.incapacitated() || !isturf(ranged_ability_user.loc)) + remove_ranged_ability() + return + if(!COOLDOWN_FINISHED(src, time_til_next_tilt)) + to_chat(ranged_ability_user, "Your rolling capacitors are still powering back up!") + return + + var/turf/target = get_turf(target_atom) + if(isnull(target)) + return + + if(target == get_turf(ranged_ability_user)) + to_chat(ranged_ability_user, "You can't roll over on yourself!") + return + + var/picked_dir = get_dir(caller, target) + if(!picked_dir) + return FALSE + // we can move during the timer so we cant just pass the ref + var/turf/temp_target = get_step(ranged_ability_user, picked_dir) + + new /obj/effect/temp_visual/single_user/ai_telegraph(temp_target, ranged_ability_user) + ranged_ability_user.visible_message("[ranged_ability_user] seems to be winding up!") + addtimer(CALLBACK(src, PROC_REF(do_roll_over), caller, picked_dir), MALF_AI_ROLL_TIME) + + to_chat(ranged_ability_user, "Overloading machine circuitry...") + + COOLDOWN_START(src, time_til_next_tilt, roll_over_cooldown) + + return TRUE + +/obj/effect/proc_holder/ranged_ai/roll_over/proc/do_roll_over(mob/living/silicon/ai/ai_caller, picked_dir) + var/turf/target = get_step(ai_caller, picked_dir) // in case we moved we pass the dir not the target turf + + if(isnull(target) || ai_caller.incapacitated() || !isturf(ai_caller.loc)) + return + + + var/paralyze_time = clamp(6 SECONDS, 0 SECONDS, (roll_over_cooldown * 0.9)) // the clamp prevents stunlocking as the max is always a little less than the cooldown between rolls + ai_caller.allow_teleporter = TRUE + ai_caller.fall_and_crush(target, MALF_AI_ROLL_DAMAGE, prob(MALF_AI_ROLL_CRIT_CHANCE), 2, null, paralyze_time, crush_dir = picked_dir, angle = get_rotation_from_dir(picked_dir)) + ai_caller.allow_teleporter = FALSE + +/obj/effect/proc_holder/ranged_ai/roll_over/proc/get_rotation_from_dir(dir) + switch(dir) + if(NORTH, NORTHWEST, WEST, SOUTHWEST) + return 270 // try our best to not return 180 since it works badly with animate + if(EAST, NORTHEAST, SOUTH, SOUTHEAST) + return 90 + else + stack_trace("non-standard dir entered to get_rotation_from_dir. (got: [dir])") + return 0 diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm index 1b41c2b3f5c..401ec6048bf 100644 --- a/code/game/machinery/machinery.dm +++ b/code/game/machinery/machinery.dm @@ -540,3 +540,6 @@ */ /obj/machinery/proc/flicker() return FALSE + +/obj/machinery/fall_and_crush(turf/target_turf, crush_damage, should_crit, crit_damage_factor, datum/tilt_crit/forced_crit, weaken_time, knockdown_time, ignore_gravity, should_rotate, angle, rightable, block_interactions) + . = ..(target_turf, crush_damage, should_crit, crit_damage_factor, forced_crit, weaken_time, knockdown_time, ignore_gravity = FALSE, should_rotate = TRUE, rightable = TRUE, block_interactions_until_righted = TRUE) diff --git a/code/game/machinery/teleporter.dm b/code/game/machinery/teleporter.dm index a79a2e928c8..18449d37277 100644 --- a/code/game/machinery/teleporter.dm +++ b/code/game/machinery/teleporter.dm @@ -314,14 +314,17 @@ */ /obj/machinery/teleport/proc/blockAI(atom/A) if(isAI(A) || istype(A, /obj/structure/AIcore)) - visible_message("The teleporter rejects the AI unit.") if(isAI(A)) var/mob/living/silicon/ai/T = A + if(T.allow_teleporter) + return FALSE var/list/TPError = list("Firmware instructions dictate you must remain on your assigned station!", "You cannot interface with this technology and get rejected!", "External firewalls prevent you from utilizing this machine!", "Your AI core's anti-bluespace failsafes trigger and prevent teleportation!") to_chat(T, "[pick(TPError)]") + + visible_message("The teleporter rejects the AI unit.") return TRUE return FALSE diff --git a/code/game/machinery/vendors/vendor_crits.dm b/code/game/machinery/vendors/tilt_crits.dm similarity index 56% rename from code/game/machinery/vendors/vendor_crits.dm rename to code/game/machinery/vendors/tilt_crits.dm index cbc21cb31c5..f4421707641 100644 --- a/code/game/machinery/vendors/vendor_crits.dm +++ b/code/game/machinery/vendors/tilt_crits.dm @@ -2,31 +2,47 @@ * Framework for custom vendor crits. */ -/datum/vendor_crit +/datum/tilt_crit + /// Name of a crit. Only crits with a name will be options. + var/name /// 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 + /// List of types which we should be valid for + var/list/valid_types_whitelist = list(/atom/movable) + /// Typecache of valid types + var/list/valid_typecache + +/datum/tilt_crit/New() + valid_typecache = typecacheof(valid_types_whitelist) /** * Return whether or not the crit selected is valid. */ -/datum/vendor_crit/proc/is_valid(obj/machinery/economy/vending/machine, mob/living/carbon/victim) - return TRUE +/datum/tilt_crit/proc/is_valid(atom/movable/tilter, mob/living/carbon/victim) + SHOULD_CALL_PARENT(TRUE) + return is_type_in_typecache(tilter, valid_typecache) /*** * 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 + * * incoming_damage - The amount of damage that was already being dealt to the victim * 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/economy/vending/machine, mob/living/carbon/victim) +/datum/tilt_crit/proc/tip_crit_effect(atom/movable/tilter, mob/living/carbon/victim, incoming_damage) return 0 -/datum/vendor_crit/shatter +/datum/tilt_crit/shatter + name = "Leg Crush" -/datum/vendor_crit/shatter/tip_crit_effect(obj/machinery/economy/vending/machine, mob/living/carbon/victim) +/datum/tilt_crit/shatter/is_valid(atom/movable/tilter, mob/living/carbon/victim) + . = ..() + return . && iscarbon(victim) + +/datum/tilt_crit/shatter/tip_crit_effect(atom/movable/tilter, mob/living/carbon/victim, incoming_damage) 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) @@ -43,28 +59,41 @@ ) // that's a LOT of damage, let's rebate most of it. - return machine.squish_damage * (5/6) + return incoming_damage * (5/6) -/datum/vendor_crit/pin +/datum/tilt_crit/pin + name = "Pin" -/datum/vendor_crit/pin/tip_crit_effect(obj/machinery/economy/vending/machine, mob/living/carbon/victim) - machine.forceMove(get_turf(victim)) - machine.buckle_mob(victim, force=TRUE) +/datum/tilt_crit/pin/is_valid(atom/movable/tilter, mob/living/victim) + . = ..() + return . && isliving(victim) + +/datum/tilt_crit/pin/tip_crit_effect(atom/movable/tilter, mob/living/victim, incoming_damage) + tilter.forceMove(get_turf(victim)) + tilter.buckle_mob(victim, force=TRUE) victim.visible_message( - "[victim] gets pinned underneath [machine]!", - "You are pinned down by [machine]!" + "[victim] gets pinned underneath [tilter]!", + "You are pinned down by [tilter]!" ) return 0 -/datum/vendor_crit/embed -/datum/vendor_crit/embed/is_valid(obj/machinery/economy/vending/machine, mob/living/carbon/victim) +/datum/tilt_crit/vendor + valid_types_whitelist = list(/obj/machinery/economy/vending) + +/datum/tilt_crit/vendor/embed + name = "Panel Shatter" + +/datum/tilt_crit/vendor/embed/is_valid(obj/machinery/economy/vending/machine, mob/living/carbon/victim) . = ..() + if(!. || !istype(machine)) + return if(machine.num_shards <= 0) return FALSE + return iscarbon(victim) -/datum/vendor_crit/embed/tip_crit_effect(obj/machinery/economy/vending/machine, mob/living/carbon/victim) +/datum/tilt_crit/vendor/embed/tip_crit_effect(obj/machinery/economy/vending/machine, mob/living/carbon/victim, incoming_damage) victim.visible_message( "[machine]'s panel shatters against [victim]!", "[H] gets crushed under [machine], and explodes in a shower of gore!", "Oh f-") + victim.visible_message("[H] gets crushed under [tilter], and explodes in a shower of gore!", "Oh f-") var/gibspawner = /obj/effect/gibspawner/human if(ismachineperson(victim)) gibspawner = /obj/effect/gibspawner/robot @@ -107,16 +141,17 @@ H.disfigure() victim.apply_damage(50, BRUTE, BODY_ZONE_HEAD) else - H.visible_message("[victim]'s head seems to be crushed under [machine]...but wait, they had none in the first place!") + H.visible_message("[victim]'s head seems to be crushed under [tilter]...but wait, they had none in the first place!") if(B in H) victim.adjustBrainLoss(80) return 0 -/datum/vendor_crit/lucky +/datum/tilt_crit/lucky harmless = TRUE + name = "Lucky" -/datum/vendor_crit/lucky/tip_crit_effect(obj/machinery/economy/vending/machine, mob/living/carbon/victim) +/datum/tilt_crit/lucky/tip_crit_effect(obj/machinery/economy/vending/machine, mob/living/carbon/victim, incoming_damage) victim.visible_message( "[machine] crashes around [victim], but doesn't seem to crush them!", "[machine] crashes around you, but only around you! You're fine!" diff --git a/code/game/machinery/vendors/vending.dm b/code/game/machinery/vendors/vending.dm index 9572499012a..2365b55154e 100644 --- a/code/game/machinery/vendors/vending.dm +++ b/code/game/machinery/vendors/vending.dm @@ -1,11 +1,3 @@ -// 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 */ @@ -132,16 +124,6 @@ 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, - /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 = 7 /// Last time the machine was punched @@ -189,14 +171,11 @@ if(account_database) vendor_account = account_database.vendor_account - - if(!length(all_possible_crits)) - for(var/typepath in subtypesof(/datum/vendor_crit)) - all_possible_crits[typepath] = new typepath() - update_icon(UPDATE_OVERLAYS) reconnect_database() power_change() + RegisterSignal(src, COMSIG_MOVABLE_UNTILTED, PROC_REF(on_untilt)) + RegisterSignal(src, COMSIG_MOVABLE_TRY_UNTILT, PROC_REF(on_try_untilt)) /obj/machinery/economy/vending/Destroy() SStgui.close_uis(wires) @@ -206,10 +185,6 @@ /obj/machinery/economy/vending/examine(mob/user) . = ..() - if(tilted) - . += "It's been tipped over and won't be usable unless it's righted." - . += "You can Alt-Click it to right it when adjacent." - if(aggressive) . += "Its product lights seem to be blinking ominously..." @@ -359,11 +334,9 @@ else ..() -/obj/machinery/economy/vending/AltClick(mob/user) - if(!tilted || !Adjacent(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED)) - return - - untilt(user) +/obj/machinery/economy/vending/proc/on_try_untilt(atom/source, mob/user) + if(user && (!Adjacent(user) || HAS_TRAIT(user, TRAIT_HANDS_BLOCKED))) + return COMPONENT_BLOCK_UNTILT /obj/machinery/economy/vending/attackby(obj/item/I, mob/user, params) if(tilted) @@ -940,6 +913,10 @@ if(dump_amount >= 16) return +/obj/machinery/economy/vending/proc/on_untilt(atom/source, mob/user) + SIGNAL_HANDLER // COMSIG_MOVABLE_UNTILTED + tilted = FALSE + //Somebody cut an important wire and now we're following a new definition of "pitch." /obj/machinery/economy/vending/proc/throw_item() var/obj/throw_item = null @@ -965,167 +942,50 @@ /obj/machinery/economy/vending/onTransitZ() return -/** - * Select a random valid crit. - */ -/obj/machinery/economy/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/economy/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 - - if(HAS_TRAIT(victim, TRAIT_DWARF)) - // also double damage if you're short - damage_to_deal *= 2 - - 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( - "[victim] is crushed by [src]!", - "[src] crushes you!", - "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, BODY_ZONE_CHEST, 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/squish, 80 SECONDS) - 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: - * * victim - 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/economy/vending/proc/tilt(atom/victim, crit = FALSE, from_combat = FALSE, from_anywhere = FALSE) if(QDELETED(src) || !has_gravity(src) || !tiltable || tilted) return - tilted = TRUE - layer = ABOVE_MOB_LAYER + + if(from_anywhere) + forceMove(get_turf(victim)) + + + if(Adjacent(victim)) + var/damage = squish_damage + var/picked_angle = pick(90, 270) + var/should_crit = !from_combat && crit + if(!crit && !from_combat) + // only deal this extra bit of damage if they wouldn't otherwise be taking the double damage from critting + damage *= self_knockover_factor + + . = fall_and_crush(get_turf(victim), damage, should_crit, crit_damage_factor, null, from_combat ? 4 SECONDS : 6 SECONDS, 12 SECONDS, FALSE, picked_angle) + if(.) + tilted = TRUE + layer = ABOVE_MOB_LAYER var/should_throw_at_target = TRUE . = FALSE - if(from_anywhere) - forceMove(get_turf(victim)) - if(!victim || !in_range(victim, src)) - tilt_over() - return - for(var/mob/living/L in get_turf(victim)) - // Damage to deal outright - var/damage_to_deal = squish_damage - if(!from_combat) - L.Weaken(6 SECONDS) - if(crit) - // increase damage if you knock it over onto yourself - damage_to_deal *= crit_damage_factor - else - damage_to_deal *= self_knockover_factor - else - L.Weaken(4 SECONDS) - if(iscarbon(L)) - var/throw_spec = handle_squish_carbon(victim, 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 - L.visible_message( - "[L] is crushed by [src]!", - "[src] falls on top of you, crushing you!" - ) - L.apply_damage(damage_to_deal, BRUTE) - - add_attack_logs(null, L, "crushed by [src]") - - . = TRUE - L.KnockDown(12 SECONDS) - - playsound(L, "sound/effects/blobattack.ogg", 40, TRUE) - playsound(L, "sound/effects/splat.ogg", 50, TRUE) - - tilt_over(should_throw_at_target ? victim : null) - -/obj/machinery/economy/vending/proc/tilt_over(mob/victim) - visible_message("[src] tips over!", "You hear a loud crash!") - playsound(src, "sound/effects/bang.ogg", 100, TRUE) - var/matrix/M = matrix() - M.Turn(pick(90, 270)) - transform = M - if(victim && get_turf(victim) != get_turf(src)) + if(get_turf(victim) != get_turf(src)) throw_at(get_turf(victim), 1, 1, spin = FALSE) -/obj/machinery/economy/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, TRUE, src)) - return - user.visible_message( - "[user] rights [src].", - "You right [src].", - "You hear a loud clang." - ) - - unbuckle_all_mobs(TRUE) - - tilted = FALSE - layer = initial(layer) - - var/matrix/M = matrix() - M.Turn(0) - transform = M + tilt_over(should_throw_at_target ? victim : null) /obj/machinery/economy/vending/shove_impact(mob/living/target, mob/living/attacker) if(HAS_TRAIT(target, TRAIT_FLATTENED)) return - add_attack_logs(attacker, target, "shoved into a vending machine ([src])") + if(!HAS_TRAIT(attacker, TRAIT_PACIFISM)) + add_attack_logs(attacker, target, "shoved into a vending machine ([src])") tilt(target, from_combat = TRUE) - else + else if(HAS_TRAIT_FROM(attacker, TRAIT_PACIFISM, GHOST_ROLE)) // should only apply to the ghost bar + add_attack_logs(attacker, target, "shoved into a vending machine ([src]), but flattened themselves.") tilt(attacker, crit = TRUE, from_anywhere = TRUE) // get fucked + else + attacker.visible_message("[attacker] lightly presses [target] against [src].", "You lightly press [target] against [src], you don't want to hurt [target.p_them()]!") return TRUE /obj/machinery/economy/vending/hit_by_thrown_carbon(mob/living/carbon/human/C, datum/thrownthing/throwingdatum, damage, mob_hurt, self_hurt) @@ -1153,8 +1013,3 @@ premium = list() */ - - -#undef VENDOR_CRUSH_HANDLED -#undef VENDOR_THROW_AT_TARGET -#undef VENDOR_TIP_IN_PLACE diff --git a/code/game/objects/effects/temporary_visuals/misc_visuals.dm b/code/game/objects/effects/temporary_visuals/misc_visuals.dm index 5eedef93b65..ee3702ecf9e 100644 --- a/code/game/objects/effects/temporary_visuals/misc_visuals.dm +++ b/code/game/objects/effects/temporary_visuals/misc_visuals.dm @@ -399,6 +399,77 @@ icon_state = "rcd_short_reverse" duration = 3.1 SECONDS +/** + * A visual effect that will be shown only to a particular user for a period of time. + */ +/obj/effect/temp_visual/single_user + /// The image to show to the user + var/image/displayed_image + /// The UID of the person who the image is being displayed to + var/source_UID + /// The real icon state to be applied to the image + var/image_icon_state + /// The plane to apply the image to + var/image_plane = ABOVE_LIGHTING_PLANE + /// The layer to apply the image to + var/image_layer = ABOVE_ALL_MOB_LAYER + /// The icon to pull the image from + var/image_icon + + +/obj/effect/temp_visual/single_user/Initialize(mapload, mob/living/user) + . = ..() + if(!user) + return INITIALIZE_HINT_QDEL + displayed_image = create_image(user) + displayed_image.plane = image_plane + displayed_image.mouse_opacity = MOUSE_OPACITY_TRANSPARENT + source_UID = user.UID() + add_mind(user) + + +/obj/effect/temp_visual/single_user/proc/create_image(mob/living/looker) + return image(icon = image_icon, loc = src, icon_state = image_icon_state, layer = image_layer) + + +/obj/effect/temp_visual/single_user/Destroy() + var/mob/living/previous_user = locateUID(source_UID) + if(previous_user) + remove_mind(previous_user) + // Null so we don't shit the bed when we delete + displayed_image = null + return ..() + +/// Add the image to the user's screen +/obj/effect/temp_visual/single_user/proc/add_mind(mob/living/looker) + looker.client?.images |= displayed_image + +/// Remove the image from the user's screen +/obj/effect/temp_visual/single_user/proc/remove_mind(mob/living/looker) + looker.client?.images -= displayed_image + +/obj/effect/temp_visual/single_user/lwap_ping + duration = 0.5 SECONDS + randomdir = FALSE + image_icon = 'icons/obj/projectiles.dmi' + image_icon_state = "red_laser" + +/obj/effect/temp_visual/single_user/lwap_ping/Initialize(mapload, mob/living/looker, mob/living/creature) + if(!looker || !creature) + return INITIALIZE_HINT_QDEL + . = ..() + displayed_image.pixel_x = (creature.x - looker.x) * 32 + displayed_image.pixel_y = (creature.y - looker.y) * 32 + +/obj/effect/temp_visual/single_user/ai_telegraph + duration = 2 SECONDS + randomdir = FALSE + image_layer = BELOW_MOB_LAYER + image_plane = GAME_PLANE + image_icon = 'icons/mob/telegraphing/telegraph_holographic.dmi' + image_icon_state = "target_box" + + /obj/effect/temp_visual/obliteration duration = 2 SECONDS diff --git a/code/game/objects/structures.dm b/code/game/objects/structures.dm index 36fcbe03aa7..f5b913dc184 100644 --- a/code/game/objects/structures.dm +++ b/code/game/objects/structures.dm @@ -206,3 +206,6 @@ take_damage(power / 8000, BURN, ENERGY) power -= power / 2000 //walls take a lot out of ya . = ..() + +/obj/structure/fall_and_crush(turf/target_turf, crush_damage, should_crit, crit_damage_factor, datum/tilt_crit/forced_crit, weaken_time, knockdown_time, ignore_gravity, should_rotate, angle, rightable, block_interactions) + . = ..(target_turf, crush_damage, should_crit, crit_damage_factor, forced_crit, weaken_time, knockdown_time, ignore_gravity, should_rotate, angle, rightable = TRUE, block_interactions_until_righted = FALSE) diff --git a/code/modules/buildmode/submodes/tilt.dm b/code/modules/buildmode/submodes/tilt.dm new file mode 100644 index 00000000000..9f1b3c07ab3 --- /dev/null +++ b/code/modules/buildmode/submodes/tilt.dm @@ -0,0 +1,72 @@ +/datum/buildmode_mode/tilting + key = "tilt" + + /// The thing we're tilting over + var/atom/movable/tilter + var/crush_damage = 25 + var/crit_chance = 0 + var/datum/tilt_crit/forced_crit + var/weaken_time = 4 SECONDS + var/knockdown_time = 14 SECONDS + var/ignore_gravity = TRUE + var/should_rotate = TRUE + var/rotation_angle + var/rightable = TRUE + var/block_interactions_until_righted = TRUE + + + +/datum/buildmode_mode/tilting/show_help(mob/user) + to_chat(user, "***********************************************************") + to_chat(user, "Left Mouse Button on obj/mob = Select atom to tilt") + to_chat(user, "Right Mouse Button on turf/obj/mob = Tilt selected atom onto target") + to_chat(user, "Right Mouse Button + Alt = Untilt selected atom") + to_chat(user, "Right-click the main action button to customize tilting behavior.") + to_chat(user, "***********************************************************") + +/datum/buildmode_mode/tilting/change_settings(mob/user) + crush_damage = input(user, "Crush Damage", "Damage", initial(crush_damage)) as num|null + crit_chance = input(user, "Crit Chance (out of 100)", "Crit chance", 0) as num|null + if(crit_chance > 0) + var/forced_crit_path = input(user, "Force a specific crit?", "Forced Crit", null) as null|anything in GLOB.tilt_crits + if(forced_crit_path) + forced_crit = GLOB.tilt_crits[forced_crit_path] + weaken_time = input(user, "How long to weaken (in seconds)?", "Weaken Time", 4) as num|null + weaken_time = weaken_time SECONDS + knockdown_time = input(user, "How long to knockdown (in seconds)?", "Knockdown Time", 12) as num|null + knockdown_time = knockdown_time SECONDS + ignore_gravity = alert(user, "Ignore gravity?", "Ignore gravity", "Yes", "No") == "Yes" + should_rotate = alert(user, "Should it rotate on falling?", "Should rotate", "Yes", "No") == "Yes" + if(should_rotate) + rotation_angle = input(user, "Which angle to rotate at? (if empty, defaults to 90 degrees in either direction)", "Rotation angle", 0) as num|null + rightable = alert(user, "Should it be rightable with alt-click?", "Rightable", "Yes", "No") == "Yes" + if(rightable) + block_interactions_until_righted = alert(user, "Should it block interactions until righted (by alt-clicking)?", "Block interactions", "Yes", "No") == "Yes" + +/datum/buildmode_mode/tilting/handle_click(mob/user, params, atom/movable/object) + var/list/pa = params2list(params) + var/left_click = pa.Find("left") + var/right_click = pa.Find("right") + var/alt_click = pa.Find("alt") + + if(left_click) + if(!ismovable(object)) + return + tilter = object + to_chat(user, "Selected object '[tilter]' to tilt.") + if(right_click) + if(!tilter) + to_chat(user, "You need to select something to tilt (or untilt) first.") + return + if(tilter.GetComponent(/datum/component/tilted) && alt_click) + tilter.untilt(duration = 0) + log_admin("Build Mode: [key_name(user)] has righted [tilter] ([tilter.x],[tilter.y],[tilter.z])") + return + + if(!object || isnull(get_turf(object))) + to_chat(user, "You need to select a target first.") + return + + tilter.fall_and_crush(get_turf(object), crush_damage, prob(crit_chance), 2, forced_crit, weaken_time, knockdown_time, ignore_gravity, should_rotate, rotation_angle, rightable, block_interactions_until_righted) + + log_admin("Build Mode: [key_name(user)] tilted [tilter] onto [ADMIN_COORDJMP(object)]") diff --git a/code/modules/mob/living/carbon/carbon.dm b/code/modules/mob/living/carbon/carbon.dm index b9f5e11df55..dc0bbe531fa 100644 --- a/code/modules/mob/living/carbon/carbon.dm +++ b/code/modules/mob/living/carbon/carbon.dm @@ -1342,3 +1342,8 @@ so that different stomachs can handle things in different ways VB*/ if(wear_suit.flags_inv & HIDEGLOVES) clean_hands = FALSE ..(clean_hands, clean_mask, clean_feet) + +/mob/living/carbon/fall_and_crush(turf/target_turf, crush_damage, should_crit, crit_damage_factor, datum/tilt_crit/forced_crit, weaken_time, knockdown_time, ignore_gravity, should_rotate = TRUE, angle) + // keep most of what's passed in, but don't change the angle + . = ..(target_turf, crush_damage, should_crit, crit_damage_factor, forced_crit, weaken_time, knockdown_time, should_rotate = FALSE, rightable = FALSE) + KnockDown(10 SECONDS) diff --git a/code/modules/mob/living/silicon/ai/ai_mob.dm b/code/modules/mob/living/silicon/ai/ai_mob.dm index d0a92b3180f..7394e6cd6ad 100644 --- a/code/modules/mob/living/silicon/ai/ai_mob.dm +++ b/code/modules/mob/living/silicon/ai/ai_mob.dm @@ -104,6 +104,9 @@ GLOBAL_LIST_INIT(ai_verbs_default, list( var/acceleration = 1 var/tracking = FALSE //this is 1 if the AI is currently tracking somebody, but the track has not yet been completed. + /// If true, this AI core can use the teleporter. + var/allow_teleporter = FALSE + var/obj/machinery/camera/portable/builtInCamera var/obj/structure/AIcore/deactivated/linked_core //For exosuit control diff --git a/code/modules/power/engines/supermatter/supermatter.dm b/code/modules/power/engines/supermatter/supermatter.dm index 650207cd74e..8c8a21e3e73 100644 --- a/code/modules/power/engines/supermatter/supermatter.dm +++ b/code/modules/power/engines/supermatter/supermatter.dm @@ -1194,6 +1194,9 @@ power += amount message_admins("[src] has been activated and given an increase EER of [amount] at [ADMIN_JMP(src)]") +/obj/machinery/atmospherics/supermatter_crystal/on_crush_thing(atom/thing) + Bumped(thing) + /obj/machinery/atmospherics/supermatter_crystal/proc/make_next_event_time() // Some completely random bullshit to make a "bell curve" var/fake_time = rand(5 MINUTES, 25 MINUTES) diff --git a/icons/misc/buildmode.dmi b/icons/misc/buildmode.dmi index 149a13d7b71..0520a9ef005 100644 Binary files a/icons/misc/buildmode.dmi and b/icons/misc/buildmode.dmi differ diff --git a/icons/mob/actions/actions.dmi b/icons/mob/actions/actions.dmi index 0abf772e7f7..381b39730fd 100644 Binary files a/icons/mob/actions/actions.dmi and b/icons/mob/actions/actions.dmi differ diff --git a/icons/mob/telegraphing/telegraph_holographic.dmi b/icons/mob/telegraphing/telegraph_holographic.dmi new file mode 100644 index 00000000000..45fc0b2094d Binary files /dev/null and b/icons/mob/telegraphing/telegraph_holographic.dmi differ diff --git a/paradise.dme b/paradise.dme index 92bb8c39d04..809fe396388 100644 --- a/paradise.dme +++ b/paradise.dme @@ -393,6 +393,7 @@ #include "code\datums\components\sticky.dm" #include "code\datums\components\surgery_initiator.dm" #include "code\datums\components\swarming.dm" +#include "code\datums\components\tilted.dm" #include "code\datums\components\two_handed.dm" #include "code\datums\discord\discord_manager.dm" #include "code\datums\discord\discord_webhook.dm" @@ -841,8 +842,8 @@ #include "code\game\machinery\vendors\contraband_vendors.dm" #include "code\game\machinery\vendors\departmental_vendors.dm" #include "code\game\machinery\vendors\generic_vendors.dm" +#include "code\game\machinery\vendors\tilt_crits.dm" #include "code\game\machinery\vendors\vending.dm" -#include "code\game\machinery\vendors\vendor_crits.dm" #include "code\game\machinery\vendors\wardrobe_vendors.dm" #include "code\game\magic\Uristrunes.dm" #include "code\game\mecha\mech_bay.dm" @@ -1511,6 +1512,7 @@ #include "code\modules\buildmode\submodes\mapgen.dm" #include "code\modules\buildmode\submodes\save.dm" #include "code\modules\buildmode\submodes\throwing.dm" +#include "code\modules\buildmode\submodes\tilt.dm" #include "code\modules\buildmode\submodes\variable_edit.dm" #include "code\modules\client\2fa.dm" #include "code\modules\client\asset_cache.dm"