mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-08-22 20:48:56 +01:00
## Main Additions and Modifications This PR adds in a quite a few new things. ### Overview **WT-550 "Enhancements"** - made available via research. These modifications retain the full-auto capability of the WT-550 to varying degrees. Additionally a compressed WT550 magazine is available via research. It contains 40 weaker bullets compared to the normal magazine's 20. It is only allowed to contain compressed bullets to avoid cheesing, and requires bluespace to fabricate. It is designed as a sustained dps ammunition as opposed to a high dps ammunition **WT-550-B** - Shoots in bursts with decent recoil and slightly higher dps. **WT-550-C** - Shoots slower but has a higher damage and projectile velocity along with a scope. **Sawn-off WT-550** - Is smaller but has egregious recoil and spread. **.38 BR** - A simple but slightly less powerful alternative to the NT-38 battle rifle. Craftable via a kit obtained from research. **Stunstaff** - A weapon that is available via crafting and functions as an alternative to a baton and riot shield. A heroic variant exists within the code but the recipe can only be learned from a book that sometimes spawns in the armory contraband closet. **Advanced Energy SMG** - A hybrid of a disabler SMG, laser carbine, and advanced energy gun. Does exactly what you think it does. Obtainable via crafting. **Stingballer** - A rifle that rapidly shoots stingballs. Direct damage is lesser than a stingbang. Purchasable from cargo. **Security Juggernaut Suit** - A heavily armoured suit that severely cripples your mobility in exchange for very high protection. Crafting kit is printable from a technode. **Horizon Beam Rifle** - Damage is now 110 with 35 armor piercing, and a demo mod of 2. Upon contact with walls, it destroys them like how a pulse laser does. ### Crafting WT-550 weapons can all be made with just the gun and the kit. The sawn-off WT550 can be made by taking a saw to the WT550. The .38 BR requires an NT-38 battle rifle along with a multitool and a screwdriver. The Advanced Energy SMG requires a disabler smg, laser carbine, and an advanced energy gun. The Stunstaff requires two security batons, some plasteel and some cable, along with a welder and wrench. The Heroic Stunstaff requires some other ingredients. The Security Juggernaut suit requires a full toolbox of tools as well as the armor plates, a full coil of cable, a pair of magboots, a swat helmet, a swat suit, and two seclites. Stingbangs can be prepared into stingball canisters with a screwdriver and some wirecutters. Each contains 25 rounds are are used to fill the stingballer. ### Cargo **Stingballer** - Armory crate - Contains one stingballer. - 4500 credits. **Grenade Launcher** - Armory crate - Contains one grenade launcher and a box of flashbangs. - 15000 credits. **Kiboko Grenade Launcher** - Armory crate - Contains one Kiboko grenade launcher. - 7500 credits. ### R&D The **"Ballisitic Research"** technode has been removed and dragon's breath shells have been moved to the illegal technology node. The node remains within the code though if more exotic ammunition is developed and requires an additional research node for it. New **"Advanced Ballistics"** technode contains the part kits for the WT-550 modifications and NT-BR simplification kit and requires Exotic Ammunition to research. New **"Advanced Armor"** technode contains the Security Juggernaut Plates and requires Riot Suppression and Gas Compression to research. ### Code Adds to the buckling system where if something has TRAIT_NO_BUCKLE, it cannot be buckled at all. Adds to the vehicle system where if something has TRAIT_NO_VEHICLE, it will be flung off the vehicle it attempts to move the vehicle themselves. Adds to the chair system where if something has TRAIT_NO_VEHICLE, it will destroy the chair. Adds to the movement system where if something has TRAIT_LARGE_CLOTHES, TRAIT_BULKY_CLOTHES, or TRAIT_HUGE_CLOTHES, it will incur a 3, 4, and 5 move speed multiplier when pulling that thing. If anyone can help me do this more modular, please reach out, I have no clue how. Edits the equipment slowdown system to properly apply a special slowdown that cannot be negated by anything. Requires the TRUE_IMMUTABLE_SLOW bubber_obj_flag to be present. IMMUTABLE_SLOW is still required for red potion immunity. ### Other Misc. Changes The WT-551 has been effectively rendered impossible to get. This decision stems from the fact that the WT-550 and the WT-551 were functionally identical when used in-game. Instances of the 551 have been replaced with the 550. Skyrat's renaming of /tg/ bullets has been completely removed. Some descriptions have been kept and altered because they were kinda neat. .460 Ceres -> .45 .416 Stabilis -> .50 BMG 8mm Usurpator -> 4.6x30mm .454 Trucidator -> .50 Action Express .277 Aestus -> .223 ## Statistics For New Additions **Stunstaff** <img width="32" height="32" alt="image" src="https://github.com/user-attachments/assets/0e1c2a1a-20ba-47a9-91f1-e63ca11b5e43" /> - Two-handed, bulky. Fits on the back and an armor vest. - 65 stamina damage - 20 stun armour penetration - 14 force - 8 throw force - 50 block chance - 20 effective block vs tackles - 10 effective block vs projectiles **Heroic Stunstaff** - 24 stamina damage - 0.5 second stun - 4 times the cell charge consumption - No cooldown between stuns - 35 stun armor penetration - 15 force - 10 throw force - 75% throw stun chance - Can be thrown like a boomerang - 75 block chance - 30 effective block vs tackles - 50 effective block vs projectiles **WT-550-B** <img width="32" height="32" alt="image" src="https://github.com/user-attachments/assets/56fea4dd-9b0c-4e06-93a7-b0389f3d32c9" /> - Bulky sized, small weapon weight, dual wielding. - 2 round burst every 0.5 seconds - 0.25 recoil - 3 spread - Can be suppressed **WT-550-C** <img width="48" height="32" alt="image" src="https://github.com/user-attachments/assets/bd80570d-3e70-463d-8b6b-7cefcb1d54e6" /> - Bulky sized, heavy weapon weight, no dual wielding. - Half fire rate of normal WT (0.6 compared to 0.3) - 1.5x damage multiplier - 1.5x projectile speed multiplier - +5 projectile wound bonus - Can be Suppressed - Mediocre Scope **Sawn-off WT-550** <img width="32" height="32" alt="image" src="https://github.com/user-attachments/assets/6d786022-2d93-42da-9dcc-fe6dfadbbce1" /> - Normal sized, light weapon weight, dual wielding - 10 spread - 20 dual wielded spread - Sawn-off recoil (high) **Compressed WT-550 magazine** - 40 capacity - 12 damage (20 for normal rounds) **Stingballer** <img width="32" height="32" alt="image" src="https://github.com/user-attachments/assets/de30bda5-9e0b-44da-8097-ca9827d3d8c8" /> - Bulky sized, normal weapon weight, no dual wielding. - 8 rounds per second - 7.5 spread - 50 round capacity - 1.5 damage - 4 stamina damage - Embed chance at close range - Bouncy projectile - Low effective range **.38 Battle Rifle** <img width="48" height="32" alt="image" src="https://github.com/user-attachments/assets/24a9a257-97fe-4313-ba39-93d17230f1b4" /> - Overall same statistics as the NT-38 Battle Rifle except for - 1.0x damage multiplier (compared to 1.2x) - 1.0x projectile speed multiplier (compared to 1.2x) - 25.0% faster rate of fire (1.5 compared to 2) - No degradation mechanics - No scope **Advanced Energy SMG** <img width="32" height="32" alt="image" src="https://github.com/user-attachments/assets/2671009a-a994-4054-92ff-4db2959501e8" /> - Bulky Size, no dual wielding. - 0.8x damage multiplier (12 on kill, 12 on disable) - 1.2x projectile speed multiplier - 40 shots on kill, 40 shots on disable - Charges about half as fast as the regular advanced energy gun. - Same fire rate as the disabler SMG - 2 spread **Security Juggernaut Suit** <img width="32" height="32" alt="image" src="https://github.com/user-attachments/assets/5de0a084-4a07-4d8f-88a3-d9996de41010" /> - Built in helmet - Helmet protects head from flashes, loud noises, impacts and pepperspray - Full temperature and pressure protection - Helmet has powerful flashlight mounted - Takes 12 seconds to put on, 15 to put on someone else - Slows you down everywhere - Slows you down when in-hand - Slows down someone who's dragging you - Prevents aggressive grabs against you when worn - Prevents combat-mode pushing against you when worn - Prevents shove knockdowns when worn - Prevents gravity effects when worn - Prevents buckling when worn - Cannot be red potion'd. - Does NOT protect against batons or knockdown. Current Armor values: - melee = 80 - bullet = 80 - laser = 70 - energy = 60 - bomb = 100 - bio = 100 - fire = 100 - acid = 100 - wound = 30 ## Why It's Good For The Game This PR adds in quite a few items that fill certain role archetypes within security while allowing them to still use equipment that security would already have. ## Proof Of Testing Testing overall has been completed. Testing left to do: - [x] Cargo Orders ## Changelog 🆑 add: Added a bunch of alternative additional weapon and equipment upgrade options to security, allowing a little bit of difference in the late-game arsenal or general armament. add: Adds the WT-550-B and WT-550-C, upgrades to the classic autorifle. add: The WT550 can now also be sawn-off. add: Adds the Stingballer, a close range weapon that rapidly shoots stingballs. add: Adds the Stunstaff, an aura-farming alternative to the classic baton and shield. add: Adds the Advanced Energy SMG, an a modified AEG that gains rapid-fire capabilites. add: Adds the Security Juggernaut suit, an impossibly slow suit that hinders your movement but has some of the best armour you'll ever see. add: Adds the .38 battle rifle, a simplified NT-38 Battle Rifle. Lacks the combat improvements, but does not degrade. balance: The Horizon Beam Rifle's performance has been tweaked. Damage is now 110, with 35 armor penetration and a demolition mod of 2, as well as the ability to destroy walls like pulse rifles. add: The Kiboko and pneumatic grenade launcher can be bought from cargo now. del: The WT-551 has be completely removed and replaced with the WT-550 del: Any instances of Skyrat's ammunition naming system have been wiped. Please report any inconsistencies you may find. /🆑 --------- Co-authored-by: Cabbage <supredoode@gmail.com> Co-authored-by: Mathilde <146444134+ibexgoetia@users.noreply.github.com> Co-authored-by: Roxy <75404941+TealSeer@users.noreply.github.com> Co-authored-by: LT3 <83487515+lessthnthree@users.noreply.github.com>
393 lines
15 KiB
Plaintext
393 lines
15 KiB
Plaintext
/atom/movable
|
|
/// Whether the atom allows mobs to be buckled to it. Can be ignored in [/atom/movable/proc/buckle_mob()] if force = TRUE
|
|
var/can_buckle = FALSE
|
|
/// Bed-like behaviour, forces mob.lying = buckle_lying if not set to [NO_BUCKLE_LYING].
|
|
var/buckle_lying = NO_BUCKLE_LYING
|
|
/// Bed-like behaviour, sets mob dir to buckle_dir if not set to [BUCKLE_MATCH_DIR]. If set to [BUCKLE_MATCH_DIR], makes mob dir match ours.
|
|
var/buckle_dir = BUCKLE_MATCH_DIR
|
|
/// Require people to be handcuffed before being able to buckle. eg: pipes
|
|
var/buckle_requires_restraints = FALSE
|
|
/// The mobs currently buckled to this atom
|
|
var/list/mob/living/buckled_mobs = null //list()
|
|
/// How much time it takes to buckle a mob to us?
|
|
var/buckle_delay = 2 SECONDS
|
|
/// The maximum number of mob/livings allowed to be buckled to this atom at once
|
|
var/max_buckled_mobs = 1
|
|
/// Whether things buckled to this atom can be pulled while they're buckled
|
|
var/buckle_prevents_pull = FALSE
|
|
|
|
//Interaction
|
|
/atom/movable/attack_hand(mob/living/user, list/modifiers)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(can_buckle && has_buckled_mobs())
|
|
if(length(buckled_mobs) > 1)
|
|
var/mob/living/unbuckled = tgui_input_list(user, "Who do you wish to unbuckle?", "Unbuckle", sort_names(buckled_mobs))
|
|
if(isnull(unbuckled))
|
|
return
|
|
if(user_unbuckle_mob(unbuckled,user))
|
|
return TRUE
|
|
else
|
|
if(user_unbuckle_mob(buckled_mobs[1],user))
|
|
return TRUE
|
|
|
|
//literally just the above extension of attack_hand(), but for silicons instead (with an adjacency check, since attack_robot() being called doesn't mean that you're adjacent to something)
|
|
/atom/movable/attack_robot(mob/living/user)
|
|
. = ..()
|
|
if(.)
|
|
return
|
|
if(Adjacent(user) && can_buckle && has_buckled_mobs())
|
|
if(length(buckled_mobs) > 1)
|
|
var/mob/living/unbuckled = tgui_input_list(user, "Who do you wish to unbuckle?", "Unbuckle", sort_names(buckled_mobs))
|
|
if(isnull(unbuckled))
|
|
return
|
|
return user_unbuckle_mob(unbuckled,user)
|
|
else
|
|
return user_unbuckle_mob(buckled_mobs[1], user)
|
|
|
|
/atom/movable/mouse_drop_receive(mob/living/M, mob/user, params)
|
|
return mouse_buckle_handling(M, user)
|
|
|
|
/**
|
|
* Does some typechecks and then calls user_buckle_mob
|
|
*
|
|
* Arguments:
|
|
* M - The mob being buckled to src
|
|
* user - The mob buckling M to src
|
|
*/
|
|
/atom/movable/proc/mouse_buckle_handling(mob/living/M, mob/living/user)
|
|
if(can_buckle && istype(M) && istype(user))
|
|
return user_buckle_mob(M, user, check_loc = FALSE)
|
|
|
|
/**
|
|
* Returns TRUE if there are mobs buckled to this atom and FALSE otherwise
|
|
*/
|
|
/atom/movable/proc/has_buckled_mobs()
|
|
if(length(buckled_mobs))
|
|
return TRUE
|
|
return FALSE
|
|
|
|
/**
|
|
* Set a mob as buckled to src
|
|
*
|
|
* If you want to have a mob buckling another mob to something, or you want a chat message sent, use user_buckle_mob instead.
|
|
* Arguments:
|
|
* M - The mob to be buckled to src
|
|
* force - Set to TRUE to ignore src's can_buckle and M's can_buckle_to
|
|
* check_loc - Set to FALSE to allow buckling from adjacent turfs, or TRUE if buckling is only allowed with src and M on the same turf.
|
|
* buckle_mob_flags- Used for riding cyborgs and humans if we need to reserve an arm or two on either the rider or the ridden mob.
|
|
* ignore_self - If set to TRUE, this will not do a check to see if the user can move into the turf of the mob and will just automatically mount them.
|
|
*/
|
|
/atom/movable/proc/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE, buckle_mob_flags= NONE)
|
|
if(!buckled_mobs)
|
|
buckled_mobs = list()
|
|
|
|
if(!is_buckle_possible(M, force, check_loc))
|
|
return FALSE
|
|
|
|
// This signal will check if the mob is mounting this atom to ride it. There are 3 possibilities for how this goes
|
|
// 1. This movable doesn't have a ridable element and can't be ridden, so nothing gets returned, so continue on
|
|
// 2. There's a ridable element but we failed to mount it for whatever reason (maybe it has no seats left, for example), so we cancel the buckling
|
|
// 3. There's a ridable element and we were successfully able to mount, so keep it going and continue on with buckling
|
|
if(SEND_SIGNAL(src, COMSIG_MOVABLE_PREBUCKLE, M, force, buckle_mob_flags) & COMPONENT_BLOCK_BUCKLE)
|
|
return FALSE
|
|
|
|
if(M.pulledby)
|
|
if(buckle_prevents_pull)
|
|
M.pulledby.stop_pulling()
|
|
else if(isliving(M.pulledby))
|
|
var/mob/living/L = M.pulledby
|
|
L.reset_pull_offsets(M, TRUE)
|
|
|
|
if(anchored)
|
|
ADD_TRAIT(M, TRAIT_NO_FLOATING_ANIM, BUCKLED_TRAIT)
|
|
if(!length(buckled_mobs))
|
|
RegisterSignal(src, COMSIG_MOVABLE_SET_ANCHORED, PROC_REF(on_set_anchored))
|
|
M.set_buckled(src)
|
|
buckled_mobs |= M
|
|
|
|
///If the icon is too big, don't add it to the screen alert
|
|
var/add_src_icon = TRUE
|
|
var/list/dim = get_icon_dimensions(icon)
|
|
if(dim["height"] > ICON_SIZE_Y || dim["width"] > ICON_SIZE_X)
|
|
add_src_icon = FALSE
|
|
|
|
M.throw_alert(ALERT_BUCKLED, /atom/movable/screen/alert/buckled, new_master = add_src_icon ? src : null)
|
|
M.set_glide_size(glide_size)
|
|
|
|
M.Move(loc)
|
|
if(buckle_dir == BUCKLE_MATCH_DIR)
|
|
M.setDir(dir)
|
|
else
|
|
M.setDir(buckle_dir)
|
|
|
|
//Something has unbuckled us in reaction to the above movement
|
|
if(!M.buckled)
|
|
return FALSE
|
|
|
|
post_buckle_mob(M)
|
|
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_BUCKLE, M, force)
|
|
SEND_SIGNAL(M, COMSIG_MOB_BUCKLED, src)
|
|
return TRUE
|
|
|
|
/obj/buckle_mob(mob/living/M, force = FALSE, check_loc = TRUE)
|
|
. = ..()
|
|
if(.)
|
|
if(resistance_flags & ON_FIRE) //Sets the mob on fire if you buckle them to a burning atom/movableect
|
|
M.adjust_fire_stacks(1)
|
|
M.ignite_mob()
|
|
if(buckle_sound)
|
|
playsound(src, buckle_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, pressure_affected = TRUE, ignore_walls = FALSE)
|
|
|
|
/obj/unbuckle_mob(mob/living/buckled_mob, force, can_fall)
|
|
. = ..()
|
|
if(. && unbuckle_sound)
|
|
playsound(src, unbuckle_sound, 50, TRUE, SHORT_RANGE_SOUND_EXTRARANGE, pressure_affected = TRUE, ignore_walls = FALSE)
|
|
|
|
/**
|
|
* Set a mob as unbuckled from src
|
|
*
|
|
* The mob must actually be buckled to src or else bad things will happen.
|
|
* Arguments:
|
|
* buckled_mob - The mob to be unbuckled
|
|
* force - TRUE if we should ignore buckled_mob.can_buckle_to
|
|
*/
|
|
/atom/movable/proc/unbuckle_mob(mob/living/buckled_mob, force = FALSE, can_fall = TRUE)
|
|
if(!isliving(buckled_mob))
|
|
CRASH("Non-living [buckled_mob] thing called unbuckle_mob() for source.")
|
|
if(buckled_mob.buckled != src)
|
|
CRASH("[buckled_mob] called unbuckle_mob() for source while having buckled as [buckled_mob.buckled].")
|
|
if(!force && !buckled_mob.can_buckle_to)
|
|
return
|
|
. = buckled_mob
|
|
buckled_mob.set_buckled(null)
|
|
buckled_mob.set_anchored(initial(buckled_mob.anchored))
|
|
buckled_mob.clear_alert(ALERT_BUCKLED)
|
|
buckled_mob.set_glide_size(DELAY_TO_GLIDE_SIZE(buckled_mob.cached_multiplicative_slowdown))
|
|
buckled_mobs -= buckled_mob
|
|
if(anchored)
|
|
REMOVE_TRAIT(buckled_mob, TRAIT_NO_FLOATING_ANIM, BUCKLED_TRAIT)
|
|
if(!length(buckled_mobs))
|
|
UnregisterSignal(src, COMSIG_MOVABLE_SET_ANCHORED)
|
|
SEND_SIGNAL(src, COMSIG_MOVABLE_UNBUCKLE, buckled_mob, force)
|
|
SEND_SIGNAL(buckled_mob, COMSIG_MOB_UNBUCKLED, src)
|
|
|
|
if(can_fall)
|
|
var/turf/location = buckled_mob.loc
|
|
if(istype(location) && !buckled_mob.currently_z_moving)
|
|
location.zFall(buckled_mob)
|
|
|
|
post_unbuckle_mob(.)
|
|
|
|
if(!QDELETED(buckled_mob) && !buckled_mob.currently_z_moving && isturf(buckled_mob.loc)) // In the case they unbuckled to a flying movable midflight.
|
|
var/turf/pitfall = buckled_mob.loc
|
|
pitfall?.zFall(buckled_mob)
|
|
|
|
/atom/movable/proc/on_set_anchored(atom/movable/source, anchorvalue)
|
|
SIGNAL_HANDLER
|
|
for(var/_buckled_mob in buckled_mobs)
|
|
if(!_buckled_mob)
|
|
continue
|
|
var/mob/living/buckled_mob = _buckled_mob
|
|
if(anchored)
|
|
ADD_TRAIT(buckled_mob, TRAIT_NO_FLOATING_ANIM, BUCKLED_TRAIT)
|
|
else
|
|
REMOVE_TRAIT(buckled_mob, TRAIT_NO_FLOATING_ANIM, BUCKLED_TRAIT)
|
|
|
|
/**
|
|
* Call [/atom/movable/proc/unbuckle_mob] for all buckled mobs
|
|
*/
|
|
/atom/movable/proc/unbuckle_all_mobs(force=FALSE)
|
|
if(!has_buckled_mobs())
|
|
return
|
|
for(var/m in buckled_mobs)
|
|
unbuckle_mob(m, force)
|
|
|
|
//Handle any extras after buckling
|
|
//Called on buckle_mob()
|
|
/atom/movable/proc/post_buckle_mob(mob/living/buckled_mob)
|
|
|
|
//same but for unbuckle
|
|
/atom/movable/proc/post_unbuckle_mob(mob/living/unbuckled_mob)
|
|
|
|
/**
|
|
* Simple helper proc that runs a suite of checks to test whether it is possible or not to buckle the target mob to src.
|
|
*
|
|
* Returns FALSE if any conditions that should prevent buckling are satisfied. Returns TRUE otherwise.
|
|
* Called from [/atom/movable/proc/buckle_mob] and [/atom/movable/proc/is_user_buckle_possible].
|
|
* Arguments:
|
|
* * target - Target mob to check against buckling to src.
|
|
* * force - Whether or not the buckle should be forced. If TRUE, ignores src's can_buckle var and target's can_buckle_to
|
|
* * check_loc - TRUE if target and src have to be on the same tile, FALSE if they are allowed to just be adjacent
|
|
*/
|
|
/atom/movable/proc/is_buckle_possible(mob/living/target, force = FALSE, check_loc = TRUE)
|
|
// Make sure target is mob/living
|
|
if(!istype(target))
|
|
return FALSE
|
|
|
|
// No bucking you to yourself.
|
|
if(target == src)
|
|
return FALSE
|
|
|
|
var/turf/ground = get_turf(src)
|
|
// If we're not already on the same turf as our target...
|
|
if(get_turf(target) != ground)
|
|
// Check if the target to buckle isn't INSIDE OF A WALL
|
|
if(!isopenturf(loc) || !isopenturf(target.loc))
|
|
return FALSE
|
|
|
|
// Check if the target to buckle isn't INSIDE A SOLID OBJECT (not including vehicles)
|
|
if(ground.is_blocked_turf(exclude_mobs = TRUE, source_atom = src))
|
|
return FALSE
|
|
|
|
// If we're checking the loc, make sure the target is on the thing we're bucking them to.
|
|
if(check_loc && !target.Adjacent(src))
|
|
return FALSE
|
|
|
|
// Check if this atom can have things buckled to it.
|
|
if(!can_buckle && !force)
|
|
return FALSE
|
|
|
|
// Make sure the target isn't already buckled to something.
|
|
if(target.buckled)
|
|
return FALSE
|
|
|
|
// Make sure this atom can still have more things buckled to it.
|
|
if(LAZYLEN(buckled_mobs) >= max_buckled_mobs)
|
|
return FALSE
|
|
|
|
// Stacking buckling leads to lots of jank and issues, better to just nix it entirely
|
|
if(target.has_buckled_mobs())
|
|
return FALSE
|
|
|
|
// If the buckle requires restraints, make sure the target is actually restrained.
|
|
if(buckle_requires_restraints && !HAS_TRAIT(target, TRAIT_RESTRAINED))
|
|
return FALSE
|
|
|
|
//If buckling is forbidden for the target, cancel
|
|
if(!target.can_buckle_to && !force)
|
|
return FALSE
|
|
//Bubber Addition
|
|
//If the target has the TRAIT_NO_BUCKLE, preventing buckling
|
|
if(HAS_TRAIT(target, TRAIT_NO_BUCKLE))
|
|
to_chat(target, span_danger("You are unable to do that."))
|
|
return FALSE
|
|
//Bubber Addition End
|
|
return TRUE
|
|
|
|
/**
|
|
* Simple helper proc that runs a suite of checks to test whether it is possible or not for user to buckle target mob to src.
|
|
*
|
|
* Returns FALSE if any conditions that should prevent buckling are satisfied. Returns TRUE otherwise.
|
|
* Called from [/atom/movable/proc/user_buckle_mob].
|
|
* Arguments:
|
|
* * target - Target mob to check against buckling to src.
|
|
* * user - The mob who is attempting to buckle the target to src.
|
|
* * check_loc - TRUE if target and src have to be on the same tile, FALSE if buckling is allowed from adjacent tiles
|
|
*/
|
|
/atom/movable/proc/is_user_buckle_possible(mob/living/target, mob/user, check_loc = TRUE)
|
|
// Standard adjacency and other checks.
|
|
if(!Adjacent(user) || !Adjacent(target) || !isturf(user.loc) || user.incapacitated || target.anchored)
|
|
return FALSE
|
|
|
|
if(iscarbon(user))
|
|
var/mob/living/carbon/carbon_user = user
|
|
if(carbon_user.usable_hands <= 0)
|
|
return FALSE
|
|
|
|
// In buckling even possible in the first place?
|
|
if(!is_buckle_possible(target, FALSE, check_loc))
|
|
return FALSE
|
|
|
|
return TRUE
|
|
|
|
/**
|
|
* Handles a mob buckling another mob to src and sends a visible_message
|
|
*
|
|
* Basically exists to do some checks on the user and then call buckle_mob where the real buckling happens.
|
|
* First, checks if the buckle is valid and cancels if it isn't.
|
|
* Second, checks if src is on a different turf from the target; if it is, does a do_after and another check for sanity
|
|
* Finally, calls [/atom/movable/proc/buckle_mob] to buckle the target to src then gives chat feedback
|
|
* Arguments:
|
|
* * M - The target mob/living being buckled to src
|
|
* * user - The other mob that's buckling M to src
|
|
* * check_loc - TRUE if src and M have to be on the same turf, false otherwise
|
|
*/
|
|
/atom/movable/proc/user_buckle_mob(mob/living/M, mob/user, check_loc = TRUE)
|
|
// Is buckling even possible? Do a full suite of checks.
|
|
if(!is_user_buckle_possible(M, user, check_loc))
|
|
return FALSE
|
|
|
|
add_fingerprint(user)
|
|
|
|
// If the mob we're attempting to buckle is not stood on this atom's turf and it isn't the user buckling themselves,
|
|
// we'll try it with a 2 second do_after delay.
|
|
if(M != user && (get_turf(M) != get_turf(src)))
|
|
M.visible_message(span_warning("[user] starts buckling [M] to [src]!"),\
|
|
span_userdanger("[user] starts buckling you to [src]!"),\
|
|
span_hear("You hear metal clanking."))
|
|
if(!do_after(user, buckle_delay, M))
|
|
return FALSE
|
|
|
|
// Sanity check before we attempt to buckle. Is everything still in a kosher state for buckling after the 3 seconds have elapsed?
|
|
// Covers situations where, for example, the chair was moved or there's some other issue.
|
|
if(!is_user_buckle_possible(M, user, check_loc))
|
|
return FALSE
|
|
|
|
. = buckle_mob(M, check_loc = check_loc)
|
|
if(.)
|
|
buckle_feedback(M, user)
|
|
|
|
/// Feedback displayed to nearby players after a mob is buckled to src.
|
|
/atom/movable/proc/buckle_feedback(mob/living/being_buckled, mob/buckler)
|
|
if(being_buckled == buckler)
|
|
being_buckled.visible_message(
|
|
span_notice("[buckler] buckles [buckler.p_them()]self to [src]."),
|
|
span_notice("You buckle yourself to [src]."),
|
|
span_hear("You hear metal clanking."),
|
|
)
|
|
else
|
|
being_buckled.visible_message(
|
|
span_warning("[buckler] buckles [being_buckled] to [src]!"),
|
|
span_warning("[buckler] buckles you to [src]!"),
|
|
span_hear("You hear metal clanking."),
|
|
)
|
|
|
|
/**
|
|
* Handles a user unbuckling a mob from src and sends a visible_message
|
|
*
|
|
* Basically just calls unbuckle_mob, sets fingerprint, and sends a visible_message
|
|
* about the user unbuckling the mob
|
|
* Arguments:
|
|
* buckled_mob - The mob/living to unbuckle
|
|
* user - The mob unbuckling buckled_mob
|
|
*/
|
|
/atom/movable/proc/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
|
|
if(!(buckled_mob in buckled_mobs) || !buckled_mob.IsReachableBy(user))
|
|
return
|
|
var/mob/living/M = unbuckle_mob(buckled_mob)
|
|
if(M)
|
|
unbuckle_feedback(M, user)
|
|
add_fingerprint(user)
|
|
if(isliving(M.pulledby))
|
|
var/mob/living/L = M.pulledby
|
|
L.set_pull_offsets(M, L.grab_state)
|
|
return M
|
|
|
|
/// Feedback displayed to nearby players after a mob is unbuckled from src.
|
|
/atom/movable/proc/unbuckle_feedback(mob/living/unbuckled_mob, mob/unbuckler)
|
|
if(unbuckled_mob == unbuckler)
|
|
unbuckled_mob.visible_message(
|
|
span_notice("[unbuckler] unbuckles [unbuckler.p_them()]self from [src]."),
|
|
span_notice("You unbuckle yourself from [src]."),
|
|
span_hear("You hear metal clanking."),
|
|
)
|
|
else
|
|
unbuckled_mob.visible_message(
|
|
span_notice("[unbuckler] unbuckles [unbuckled_mob] from [src]."),
|
|
span_notice("[unbuckler] unbuckles you from [src]."),
|
|
span_hear("You hear metal clanking."),
|
|
)
|