mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 20:48:56 +01:00
## About The Pull Request <img width="194" height="63" alt="image" src="https://github.com/user-attachments/assets/8b81ede5-3ac1-427c-b580-f75a4b05fae5" /> Tripwires, for mechs. A very sturdy cable hung over a space 2-5 tiles wide, that mechs will trip over if they aren't paying attention. https://github.com/user-attachments/assets/eb77ac44-73ad-43f7-aa48-3008aedc79e0 It also hurts people it falls on in pretty much the same way as a vending machine. (it's using `fall_and_crush()`) https://github.com/user-attachments/assets/97fccb33-d479-4170-8601-07a5b32fe740 To humans, the tripwire blocks movement unless you carefully step over it(walk) or crawl under it. All beings suffer no issue walking across the tripwire the long way. The tripwire is created from wiring together two tripwire poles with a tripwire cable item. The poles are created from iron rods and iron, the cable is created from just cable. The wiring takes 3 seconds * how far the posts are to complete, to a maximum of 12 seconds. They take significantly increased (10x) damage from any sharp object, and can be cut in a few seconds using wirecutters. They otherwise have a `max_integrity` of 500, so a powerful demolition tool(like a mech's fists or a drill) or a sharp object are fairly necessary to take them down in a combat-reasonable amount of time. The tripping has four effects - the damage to the being that the mech fell on, the paralyzing of that being, damage to the mech pilot's head, and the time it takes for the mech to get back up. All four of these numbers are based off of the mech's max health divided by 5, or `damage_value`. `damage_value` is applied directly as damage to whoever it falls on. That person is paralyzed for `damage_value` / 10 seconds. The pilot of the falling mech recieves `damage_value` / 5 to their head, with a significant wound bonus. Wear a helmet. The mech itself recieves `damage_value` * 0.15 seconds of immobility(can still attack, but can't rotate). (`damage_value` / 10 * 1.5) What these numbers mean on common exosuits: - odysseus - mob under hit for 24 damage & 2.4 seconds, pilot hit for ~5 damage, immobile for 3.6 seconds - ripley - mob under hit for 40 damage & 4 seconds, pilot hit for 8 damage, immobile for 6 seconds - gygax - mob under hit for 50 damage & 5 seconds, pilot hit for 10 damage, immobile for 7.5 seconds - durand - mob under hit for 80 damage & 8 seconds, pilot hit for 16 damage, immobile for 12 seconds - marauder - mob under hit for 100 damage & 10 seconds, pilot hit for 20 damage, immobile for 15 seconds All of these stun times are large enough to where if there were multiple people with good weapons around, they could most certainly kill you before you stand back up. So, don't trip. Finally, obviously, the clarke is immune. Who'd have thunk. ## Why It's Good For The Game First, let's all thank the ideas guy. The ideas guy for today is [dubblywumps](https://discord.com/channels/699727870846697493/705679956021215264/1523883105276919969). Thank you, dubblywumps. Good idea. So, what's the point of it? It's an effective area denial tool and fortification specifically effective against mechs. You can set them up in the hallways in a reasonable length of time, and it'll at the very least slow a rampaging mech down, as while they're not strong enough to handle more than a few punches from a mech, they're strong enough to handle a few. Falling over as a mech is obviously extremely punishing, so unless they can take a delay to destroy the wire(or a long delay to destroy the wire at range), or get around it by tearing through walls(also a delay), they have to abide by your preferred positioning. No other tool exists that can truly force a delay in a mech's movement besides actual walls & windows, which delay humans far more significantly. It is not remotely practical in a directly offensive sense, with even the shortest 2-tile barricades taking too long to set up to do so before you've been clobbered and perforated. It is practical in an indirectly offensive sense, to force a fight to continue and prevent the worst part about fighting a mech: when it walks away. This requires forethought or teamwork, though. It is also practical in a defensive sense, in combination with normal barricades to block projectiles you could reasonably hold a mech off for a while or even potentially force a stalemate using ranged weapons. They're very, very cheap materials-wise, but the expense in their creation is in the time to set them up in a good location. Because they also slow down people, people won't be setting scores of them up in the middle of the halls. And if they did, wirecutters take them down faster than they can be put up. You may be balking about the immobility time applied to the mech. The numbers mean that in order to destroy the mech while it lays on the ground, you must deal a flat 33 + armor damage per second for the duration of the stun. This is an unreasonable task for one person with a good crew melee weapon, a reasonable task for two, and an easy task for three. I think that two people being able to beat you to death after you failed to take heed of the not-at-all-hidden anti-mech weapon is reasonable. This is also assuming you have no support team or second mech, which would be perfectly capable of defending you during your stun. ## Changelog 🆑 add: Added craftable anti-mech tripwires. A mech that foolishly steps on one will fall down and be immobilized temporarily. Don't let it fall on you! /🆑
347 lines
12 KiB
Plaintext
347 lines
12 KiB
Plaintext
/obj/structure/tripwire
|
|
name = "tripwire post?"
|
|
desc = "This should not be here. Somebody should be told about this."
|
|
icon = 'icons/obj/tripwire.dmi'
|
|
icon_state = "tripwire_post"
|
|
anchored = TRUE
|
|
density = TRUE
|
|
abstract_type = /obj/structure/tripwire
|
|
|
|
/obj/structure/tripwire/post
|
|
name = "stanchion"
|
|
desc = "A sturdy post made to support one end of a large cable."
|
|
custom_materials = list(/datum/material/iron = SHEET_MATERIAL_AMOUNT * 3.5)
|
|
/// The post that the other end of this post's tripwire, if any. Also serves as a check for if we have a tripwire.
|
|
var/obj/structure/tripwire/post/opposing_post
|
|
/// The wire or wires between us and the opposing post. Only one post in the pair will have this list filled.
|
|
var/list/obj/structure/tripwire/cable/tripwires = list()
|
|
|
|
/obj/structure/tripwire/post/Initialize(mapload)
|
|
. = ..()
|
|
var/static/list/loc_connections = list(
|
|
COMSIG_ATOM_ENTERED = PROC_REF(on_entered),
|
|
)
|
|
AddElement(/datum/element/connect_loc, loc_connections)
|
|
|
|
/obj/structure/tripwire/post/Destroy(force)
|
|
clear_wire()
|
|
return ..()
|
|
|
|
/obj/structure/tripwire/post/proc/clear_wire()
|
|
if(opposing_post)
|
|
opposing_post.opposing_post = null
|
|
opposing_post.clear_wire()
|
|
opposing_post = null
|
|
for(var/tripwire in tripwires)
|
|
UnregisterSignal(tripwire, COMSIG_QDELETING)
|
|
qdel(tripwire)
|
|
tripwires = list()
|
|
update_appearance(UPDATE_ICON)
|
|
|
|
/obj/structure/tripwire/post/proc/wire_qdeleted(datum/source)
|
|
tripwires -= source
|
|
UnregisterSignal(source, COMSIG_QDELETING)
|
|
clear_wire()
|
|
|
|
/obj/structure/tripwire/post/proc/connect_to_post(obj/structure/tripwire/post/connected_post, new_direction)
|
|
dir = new_direction
|
|
opposing_post = connected_post
|
|
update_appearance(UPDATE_ICON)
|
|
|
|
/obj/structure/tripwire/post/update_appearance(updates)
|
|
. = ..()
|
|
icon_state = "tripwire_post[opposing_post ? "_tied" : ""]"
|
|
|
|
/obj/structure/tripwire/post/wirecutter_act(mob/living/user, obj/item/tool)
|
|
if(!opposing_post)
|
|
return NONE
|
|
|
|
visible_message(span_notice("[user] begins cutting through \the [src]'s tripwire..."), \
|
|
span_notice("You begin cutting through \the [src]'s tripwire..."), \
|
|
span_hear("You hear snipping."))
|
|
if(!tool.use_tool(src, user, 5 SECONDS))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
if(!opposing_post)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
visible_message(span_notice("[user] cuts away \the [src]'s tripwire."), \
|
|
span_notice("You finish cutting \the [src]'s tripwire."), \
|
|
span_hear("You hear a final snip."))
|
|
new /obj/item/stack/cable_coil(drop_location(), 20)
|
|
clear_wire()
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/structure/tripwire/post/item_interaction(mob/living/user, obj/item/tool, list/modifiers)
|
|
if(!istype(tool, /obj/item/tripwire_cable))
|
|
return NONE
|
|
|
|
if(!anchored || opposing_post)
|
|
return NONE
|
|
|
|
var/obj/item/tripwire_cable/irrelevant_cable = tool
|
|
|
|
var/obj/structure/tripwire/post/connecting_post = irrelevant_cable.connecting_post?.resolve()
|
|
|
|
var/are_we_tying = FALSE
|
|
if(!connecting_post || connecting_post.z != z)
|
|
are_we_tying = TRUE
|
|
|
|
var/distance_between = get_dist(src, connecting_post)
|
|
if(distance_between > 6)
|
|
are_we_tying = TRUE
|
|
|
|
if(are_we_tying)// if it's on a different z level, or further than would be reasonable, refuse to acknowledge the distance and mulligan.
|
|
to_chat(user, span_notice("You tie one end of \the [irrelevant_cable] around \the [src]."))
|
|
irrelevant_cable.connecting_post = WEAKREF(src)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
if(connecting_post == src)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
if(!connecting_post.anchored)
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
if(distance_between > 4)
|
|
to_chat(user, span_notice("The post on the other end is too far away!"))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
if(distance_between == 0)
|
|
to_chat(user, span_notice("No point in connecting them, they're already together."))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
if(connecting_post.x != x && connecting_post.y != y)
|
|
to_chat(user, span_notice("The wire will be much less effective at an angle."))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
var/turf/end_point_turf = get_turf(connecting_post)
|
|
|
|
if(!do_after(user, distance_between * 3 SECONDS, src))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
if(!anchored || opposing_post || !connecting_post?.anchored || connecting_post.opposing_post || end_point_turf != get_turf(connecting_post))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
var/direction_to_extend = get_dir(src, connecting_post)
|
|
var/turf/iterating_turf = get_turf(src)
|
|
for(var/distance_extended in 1 to distance_between)
|
|
iterating_turf = get_step(iterating_turf, direction_to_extend)
|
|
if(iterating_turf == end_point_turf)
|
|
connect_to_post(connecting_post, direction_to_extend)
|
|
connecting_post.connect_to_post(src, REVERSE_DIR(direction_to_extend))
|
|
to_chat(user, span_notice("You successfully wire together the two posts."))
|
|
qdel(irrelevant_cable)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
if(iterating_turf.is_blocked_turf(TRUE))
|
|
clear_wire()
|
|
to_chat(user, span_notice("You can't feed the cable through solid objects."))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
var/obj/structure/tripwire/cable/new_wire = new(iterating_turf)
|
|
switch(distance_between)
|
|
// if(1) we'd have already dropped out at connecting the two posts together, and wouldn't get here
|
|
if(2)
|
|
new_wire.icon_state = "tripwire_1" // post - wire(1) - post
|
|
if(3)
|
|
new_wire.icon_state = "tripwire_2" // post - wire(2)(L) - wire(2)(R) - post
|
|
if(4)
|
|
new_wire.icon_state = "tripwire_[distance_extended == 2 ? "3" : "2"]" // post - wire(2)(L) - wire(3) - wire(2)(R) - post
|
|
|
|
new_wire.dir = (distance_extended > (distance_between / 2)) ? direction_to_extend : REVERSE_DIR(direction_to_extend) // Switch directions once we're halfway there
|
|
|
|
|
|
RegisterSignal(new_wire, COMSIG_QDELETING, PROC_REF(wire_qdeleted))
|
|
tripwires += new_wire
|
|
|
|
// HOW DID WE GET HERE WE SHOULD NEVER GET HERE BURN IT ALL
|
|
clear_wire()
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
/obj/structure/tripwire/post/wrench_act(mob/living/user, obj/item/tool)
|
|
if(opposing_post)
|
|
balloon_alert(user, "unwire first!")
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
if(!tool.use_tool(src, user, 0))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
balloon_alert(user, "[anchored ? "un" : ""]anchored")
|
|
set_anchored(!anchored)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/structure/tripwire/post/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
|
clear_wire()
|
|
return ..()
|
|
|
|
/obj/structure/tripwire/post/CanAllowThrough(atom/movable/mover, border_dir)
|
|
if(!opposing_post)
|
|
return TRUE
|
|
|
|
return ..()
|
|
|
|
/obj/structure/tripwire/post/on_entered(datum/source, atom/movable/entered)
|
|
if(!opposing_post)
|
|
return
|
|
|
|
return ..()
|
|
|
|
/obj/structure/tripwire/cable
|
|
name = "heavy cable"
|
|
desc = "A very thick cable made to withstand great force."
|
|
icon_state = "tripwire_1"
|
|
|
|
/obj/structure/tripwire/cable/Initialize(mapload)
|
|
. = ..()
|
|
RegisterSignal(get_turf(src), COMSIG_ATOM_ENTERED, PROC_REF(on_entered)) // No need for connect_loc, we won't be moving.
|
|
|
|
/obj/structure/tripwire/cable/attackby(obj/item/attacking_item, mob/user, list/modifiers, list/attack_modifiers)
|
|
if(attacking_item.sharpness)
|
|
MODIFY_ATTACK_FORCE_MULTIPLIER(attack_modifiers, 10)
|
|
return ..()
|
|
|
|
/obj/structure/tripwire/cable/wirecutter_act(mob/living/user, obj/item/tool)
|
|
visible_message(span_notice("[user] begins cutting through \the [src]..."), \
|
|
span_notice("You begin cutting through \the [src]..."), \
|
|
span_hear("You hear snipping."))
|
|
|
|
if(!tool.use_tool(src, user, 5 SECONDS))
|
|
return ITEM_INTERACT_BLOCKING
|
|
|
|
visible_message(span_notice("[user] cuts away \the [src]."), \
|
|
span_notice("You finish cutting \the [src]."), \
|
|
span_hear("You hear a final snip."))
|
|
new /obj/item/stack/cable_coil(drop_location(), 20)
|
|
qdel(src)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/obj/structure/tripwire/cable/Moved(atom/old_loc, movement_dir, forced, list/old_locs, momentum_change)
|
|
. = ..()
|
|
if(!QDELING(src))
|
|
qdel(src)
|
|
|
|
/obj/structure/tripwire/CanAllowThrough(atom/movable/mover, border_dir)
|
|
. = ..()
|
|
|
|
if(.)
|
|
return .
|
|
|
|
if(isprojectile(mover))
|
|
return TRUE
|
|
|
|
if(mover.dir == dir || mover.dir == REVERSE_DIR(dir))
|
|
return TRUE
|
|
|
|
if(!mover.has_gravity())
|
|
return TRUE
|
|
|
|
if(mover.movement_type & MOVETYPES_NOT_TOUCHING_GROUND)
|
|
return TRUE
|
|
|
|
if(iscarbon(mover))
|
|
var/mob/living/carbon/moving_carbon = mover
|
|
if(moving_carbon.body_position == LYING_DOWN) // Crawl under it. Bonus of allowing dragging dying people.
|
|
return TRUE
|
|
|
|
if(moving_carbon.move_intent == MOVE_INTENT_WALK)
|
|
return TRUE
|
|
|
|
if(ismecha(mover))
|
|
if(istype(mover, /obj/vehicle/sealed/mecha/clarke)) // treads
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
return FALSE
|
|
|
|
|
|
/obj/structure/tripwire/proc/on_entered(datum/source, atom/movable/entered)
|
|
if(!ismecha(entered))
|
|
return
|
|
|
|
var/obj/vehicle/sealed/mecha/falling_down = entered
|
|
if(falling_down.toppled) // did you trip directly into a tripwire? We don't want this to chain, however amusing the image might be.
|
|
return
|
|
|
|
if(astype(falling_down, /obj/vehicle/sealed/mecha/phazon)?.phasing)
|
|
return
|
|
|
|
if(falling_down.dir == dir || falling_down.dir == REVERSE_DIR(dir))
|
|
return
|
|
|
|
var/damage_value = falling_down.max_integrity / 5
|
|
|
|
var/falling_angle
|
|
if(falling_down.dir == WEST)
|
|
falling_angle = 270
|
|
else if(falling_down.dir == EAST)
|
|
falling_angle = 90
|
|
else
|
|
falling_angle = pick(90, 270)
|
|
|
|
// For disambiguation, damage_value is the mech's health / 5. damage_value is the base for all the rest of the numbers.
|
|
// damage_value is applied directly as damage to whoever it falls on. That person is also paralyzed for damage_value / 10 seconds.
|
|
// The pilot of the falling mech receives damage_value / 5 as a head trauma.
|
|
// The mech itself receives damage_value * 0.15 seconds of immobility(can still attack, but can't rotate). (damage_value / 10 * 1.5)
|
|
falling_down.toppled = TRUE
|
|
falling_down.forceMove(get_turf(src))
|
|
var/tripped = falling_down.fall_and_crush(get_step(falling_down, falling_down.dir), damage_value, 15, paralyze_time = damage_value, rotation = falling_angle)
|
|
addtimer(CALLBACK(falling_down, TYPE_PROC_REF(/obj/vehicle/sealed/mecha, right_self), falling_angle), damage_value * 1.5)
|
|
if (!(tripped & SUCCESSFULLY_FELL_OVER))
|
|
falling_down.toppled = FALSE
|
|
return // Don't know how it'd be avoided, but if you beat it, you beat it
|
|
|
|
var/drivers = falling_down.return_drivers()
|
|
|
|
visible_message(span_danger("[falling_down] topples over \the [src]!"), \
|
|
blind_message = span_danger("You hear a deafening CRASH!"), \
|
|
ignored_mobs = drivers)
|
|
|
|
for(var/mob/living/driver as anything in drivers) // can any mechs have two drivers? No. Could they? yes.
|
|
var/damage = driver.apply_damage(damage_value / 3, BRUTE, BODY_ZONE_HEAD, driver.run_armor_check(BODY_ZONE_HEAD, MELEE), wound_bonus = 30, wound_clothing = FALSE)
|
|
var/falling_string
|
|
switch(damage)
|
|
if(0)
|
|
falling_string = "You rapidly fall forward but only lightly hit [falling_down]'s controls. "
|
|
if(0 to 10)
|
|
falling_string = "You rapidly fall forward and hit your head on [falling_down]'s controls! "
|
|
if(10 to INFINITY)
|
|
falling_string = "You rapidly fall forward and smash your head against [falling_down]'s controls! "
|
|
|
|
falling_string += "When you recover, you [damage_value > 40 ? "slowly" : "quickly"] begin righting [falling_down]."
|
|
to_chat(driver, span_danger(falling_string))
|
|
|
|
/obj/item/tripwire_cable
|
|
name = "sturdy cable"
|
|
desc = "A coil of extremely thick cable that can withstand great force."
|
|
icon = 'icons/obj/tripwire.dmi'
|
|
icon_state = "tripwire_cable"
|
|
lefthand_file = 'icons/mob/inhands/equipment/tools_lefthand.dmi'
|
|
righthand_file = 'icons/mob/inhands/equipment/tools_righthand.dmi'
|
|
inhand_icon_state = "coil_yellow"
|
|
custom_materials = list(/datum/material/iron = SMALL_MATERIAL_AMOUNT * 3, /datum/material/glass = SMALL_MATERIAL_AMOUNT * 3)
|
|
/// The post we're trying to connect at the moment, if any.
|
|
var/datum/weakref/connecting_post
|
|
|
|
/obj/item/tripwire_cable/attack_self(mob/user, modifiers)
|
|
if(!connecting_post.resolve())
|
|
return
|
|
to_chat(user, span_notice("You recoil the length of cable until it's all back together again."))
|
|
connecting_post = null
|
|
|
|
/obj/item/tripwire_cable/wirecutter_act(mob/living/user, obj/item/tool)
|
|
to_chat(user, span_notice("You cut off the cable's excess bulk."))
|
|
new /obj/item/stack/cable_coil(drop_location(), 15)
|
|
qdel(src)
|
|
return ITEM_INTERACT_SUCCESS
|
|
|
|
/datum/crafting_recipe/tripwire_cable
|
|
name = "Tripwire Cable"
|
|
result = /obj/item/tripwire_cable
|
|
reqs = list(
|
|
/obj/item/stack/cable_coil = 30,
|
|
)
|
|
tool_behaviors = list(TOOL_WIRECUTTER)
|
|
time = 10 SECONDS
|
|
category = CAT_MISC
|