mirror of
https://github.com/ParadiseSS13/Paradise.git
synced 2026-08-23 12:07:27 +01:00
Ports Malf AI rolling (and some other tipping refactors) (#21900)
* some thoughts * I really forgot to commit all of this didn't I * Rework vendor crits * fixes some angle jank * yeag * Patches up most bugs with tilting things over * Fixes up most of the AI logic * rolling AI can now take teleporters, visual effect is now only seen by the AI * better effects * Remove a bunch of now useless code * I keep forgetting this * cleanup in aisle seven * oops * some more testing * adminbus hours * Fixes some buildmode stuff, does some more testing * Add documentation * Demo bugs * Apply suggestions from code review Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com> * rework crit damage amount * Adapt recent tg changes * Fix some small bugs in testing, contra review 1 * contra review 2 * Fixes tipping with pacifism * restore removed icons * Fix some remote untipping * restore the roll action * finally address contra's review (sorry) * remove debug tool * Move untilt logic into component --------- Co-authored-by: Henri215 <77684085+Henri215@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 += "<span class='notice'>It's been tilted over. <i>Alt+Click</i> it to right it.</span>"
|
||||
|
||||
/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, "<span class='warning'>You can't do that right now, you need to right it first!</span>")
|
||||
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(
|
||||
"<span class='notice'>[user] rights [parent].</span>",
|
||||
"<span class='notice'>You right [parent].</span>",
|
||||
"<span class='notice'>You hear a loud clang.</span>"
|
||||
)
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
@@ -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("<span class='warning'>[src] seems to rock, but doesn't fall over!</span>")
|
||||
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(
|
||||
"<span class='danger'>[target] is crushed by [src]!</span>",
|
||||
"<span class='userdanger'>[src] crushes you!</span>",
|
||||
"<span class='warning'>You hear a loud crunch!</span>"
|
||||
)
|
||||
|
||||
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("<span class='danger'>[src] tips over!</span>", "<span class='danger'>You hear a loud crash!</span>")
|
||||
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
|
||||
|
||||
@@ -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 = "<span class='notice'>You tap into the station's powernet. Click on a machine to detonate it, or use the ability again to cancel.</span>"
|
||||
disable_text = "<span class='notice'>You release your hold on the powernet.</span>"
|
||||
|
||||
@@ -820,3 +826,91 @@
|
||||
actual_action.fix_borg(robot_target)
|
||||
remove_ranged_ability(ranged_ability_user, "<span class='warning'>[robot_target] successfully rebooted.</span>")
|
||||
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 = "<span class='notice'>You gain the ability to roll over and crush anything in your way.</span>"
|
||||
|
||||
/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 = "<span class='notice'>Your inner servos shift as you prepare to roll around. Click adjacent tiles to roll into them!</span>"
|
||||
disable_text = "<span class='notice'>You disengage your rolling protocols.</span>"
|
||||
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, "<span class='warning'>Your rolling capacitors are still powering back up!</span>")
|
||||
return
|
||||
|
||||
var/turf/target = get_turf(target_atom)
|
||||
if(isnull(target))
|
||||
return
|
||||
|
||||
if(target == get_turf(ranged_ability_user))
|
||||
to_chat(ranged_ability_user, "<span class='warning'>You can't roll over on yourself!</span>")
|
||||
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("<span class='danger'>[ranged_ability_user] seems to be winding up!</span>")
|
||||
addtimer(CALLBACK(src, PROC_REF(do_roll_over), caller, picked_dir), MALF_AI_ROLL_TIME)
|
||||
|
||||
to_chat(ranged_ability_user, "<span class='warning'>Overloading machine circuitry...</span>")
|
||||
|
||||
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -314,14 +314,17 @@
|
||||
*/
|
||||
/obj/machinery/teleport/proc/blockAI(atom/A)
|
||||
if(isAI(A) || istype(A, /obj/structure/AIcore))
|
||||
visible_message("<span class='warning'>The teleporter rejects the AI unit.</span>")
|
||||
if(isAI(A))
|
||||
var/mob/living/silicon/ai/T = A
|
||||
if(T.allow_teleporter)
|
||||
return FALSE
|
||||
var/list/TPError = list("<span class='warning'>Firmware instructions dictate you must remain on your assigned station!</span>",
|
||||
"<span class='warning'>You cannot interface with this technology and get rejected!</span>",
|
||||
"<span class='warning'>External firewalls prevent you from utilizing this machine!</span>",
|
||||
"<span class='warning'>Your AI core's anti-bluespace failsafes trigger and prevent teleportation!</span>")
|
||||
to_chat(T, "[pick(TPError)]")
|
||||
|
||||
visible_message("<span class='warning'>The teleporter rejects the AI unit.</span>")
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
+58
-23
@@ -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(
|
||||
"<span class='danger'>[victim] gets pinned underneath [machine]!</span>",
|
||||
"<span class='userdanger'>You are pinned down by [machine]!</span>"
|
||||
"<span class='danger'>[victim] gets pinned underneath [tilter]!</span>",
|
||||
"<span class='userdanger'>You are pinned down by [tilter]!</span>"
|
||||
)
|
||||
|
||||
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(
|
||||
"<span class='danger'>[machine]'s panel shatters against [victim]!</span>",
|
||||
"<span class='userdanger>[machine] lands on you, its panel shattering!</span>"
|
||||
@@ -85,16 +114,21 @@
|
||||
|
||||
playsound(machine, "shatter", 50)
|
||||
|
||||
return machine.squish_damage * (3/4)
|
||||
return incoming_damage * (3 / 4)
|
||||
|
||||
/datum/vendor_crit/pop_head
|
||||
/datum/tilt_crit/pop_head
|
||||
name = "Head Pop"
|
||||
|
||||
/datum/vendor_crit/pop_head/tip_crit_effect(obj/machinery/economy/vending/machine, mob/living/carbon/victim)
|
||||
/datum/tilt_crit/pop_head/is_valid(atom/movable/tilter, mob/living/carbon/victim)
|
||||
. = ..()
|
||||
return . && iscarbon(victim)
|
||||
|
||||
/datum/tilt_crit/pop_head/tip_crit_effect(atom/movable/tilter, mob/living/carbon/victim, incoming_damage)
|
||||
// pop!
|
||||
var/obj/item/organ/external/head/H = victim.get_organ("head")
|
||||
var/obj/item/organ/internal/brain/B = victim.get_int_organ_tag("brain")
|
||||
if(H)
|
||||
victim.visible_message("<span class='danger'>[H] gets crushed under [machine], and explodes in a shower of gore!</span>", "<span class='userdanger'>Oh f-</span>")
|
||||
victim.visible_message("<span class='danger'>[H] gets crushed under [tilter], and explodes in a shower of gore!</span>", "<span class='userdanger'>Oh f-</span>")
|
||||
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("<span class='danger'>[victim]'s head seems to be crushed under [machine]...but wait, they had none in the first place!</span>")
|
||||
H.visible_message("<span class='danger'>[victim]'s head seems to be crushed under [tilter]...but wait, they had none in the first place!</span>")
|
||||
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(
|
||||
"<span class='danger'>[machine] crashes around [victim], but doesn't seem to crush them!</span>",
|
||||
"<span class='userdanger'>[machine] crashes around you, but only around you! You're fine!</span>"
|
||||
+34
-179
@@ -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)
|
||||
. += "<span class='warning'>It's been tipped over and won't be usable unless it's righted.</span>"
|
||||
. += "<span class='notice'>You can <b>Alt-Click</b> it to right it when adjacent.</span>"
|
||||
|
||||
if(aggressive)
|
||||
. += "<span class='warning'>Its product lights seem to be blinking ominously...</span>"
|
||||
|
||||
@@ -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(
|
||||
"<span class='danger'>[victim] is crushed by [src]!</span>",
|
||||
"<span class='userdanger'>[src] crushes you!</span>",
|
||||
"<span class='warning'>You hear a loud crunch!</span>"
|
||||
)
|
||||
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(
|
||||
"<span class='danger'>[L] is crushed by [src]!</span>",
|
||||
"<span class='userdanger'>[src] falls on top of you, crushing you!</span>"
|
||||
)
|
||||
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("<span class='danger'>[src] tips over!</span>", "<span class='danger'>You hear a loud crash!</span>")
|
||||
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(
|
||||
"<span class='notice'>[user] rights [src].</span>",
|
||||
"<span class='notice'>You right [src].</span>",
|
||||
"<span class='notice'>You hear a loud clang.</span>"
|
||||
)
|
||||
|
||||
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("<span class='notice'>[attacker] lightly presses [target] against [src].</span>", "<span class='warning'>You lightly press [target] against [src], you don't want to hurt [target.p_them()]!</span>")
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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, "<span class='notice'>***********************************************************</span>")
|
||||
to_chat(user, "<span class='notice'>Left Mouse Button on obj/mob = Select atom to tilt</span>")
|
||||
to_chat(user, "<span class='notice'>Right Mouse Button on turf/obj/mob = Tilt selected atom onto target</span>")
|
||||
to_chat(user, "<span class='notice'>Right Mouse Button + Alt = Untilt selected atom</span>")
|
||||
to_chat(user, "<span class='warning'>Right-click the main action button to customize tilting behavior.</span>")
|
||||
to_chat(user, "<span class='notice'>***********************************************************</span>")
|
||||
|
||||
/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, "<span class='warning'>You need to select something to tilt (or untilt) first.</span>")
|
||||
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, "<span class='warning'>You need to select a target first.</span>")
|
||||
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)]")
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user