diff --git a/_maps/map_files/LayeniaStation/LayeniaStation.dmm b/_maps/map_files/LayeniaStation/LayeniaStation.dmm index e5feee0bf..bc5414030 100644 --- a/_maps/map_files/LayeniaStation/LayeniaStation.dmm +++ b/_maps/map_files/LayeniaStation/LayeniaStation.dmm @@ -7019,6 +7019,12 @@ /obj/effect/landmark/nuclear_waste_spawner, /turf/open/floor/plasteel, /area/maintenance/disposal/incinerator) +"aRt" = ( +/obj/machinery/power/grounding_rod{ + anchored = 1 + }, +/turf/open/floor/plasteel/dark, +/area/engine/tether) "aRw" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden{ dir = 4 @@ -10931,6 +10937,12 @@ dir = 1; pixel_y = 16 }, +/obj/machinery/power/grounding_rod{ + anchored = 1 + }, +/obj/machinery/power/grounding_rod{ + anchored = 1 + }, /turf/open/floor/plasteel/dark, /area/engine/tether) "bxV" = ( @@ -12621,6 +12633,9 @@ icon_state = "sandyfloor_red"; name = "crimson sand" }, +/obj/structure/cable{ + icon_state = "0-2" + }, /turf/open/floor/plasteel/airless/solarpanel{ initial_gas_mix = "o2=22;n2=82;TEMP=180"; planetary_atmos = 1 @@ -42973,6 +42988,10 @@ name = "parquet" }, /area/chapel/main) +"gQR" = ( +/obj/machinery/safety_tether, +/turf/open/floor/engine, +/area/engine/tether) "gQT" = ( /obj/effect/spawner/structure/window/reinforced, /turf/open/floor/plating, @@ -62489,6 +62508,7 @@ dir = 10; name = "air supply pipe" }, +/obj/item/paper/fluff/safety_tether, /turf/open/floor/plasteel/dark{ dir = 1; icon_state = "darkfull_plate" @@ -100318,6 +100338,10 @@ }, /turf/open/floor/plating/layenia, /area/layenia/cloudlayer) +"pkn" = ( +/obj/item/paper/fluff/safety_tether, +/turf/open/floor/plasteel/dark, +/area/engine/tether) "pko" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden/layer3{ dir = 8; @@ -131201,6 +131225,16 @@ /obj/effect/turf_decal/stripes/corner, /turf/open/floor/plasteel/dark, /area/quartermaster/miningdock) +"txg" = ( +/obj/machinery/light{ + dir = 1; + pixel_y = 16 + }, +/obj/machinery/power/grounding_rod{ + anchored = 1 + }, +/turf/open/floor/plasteel/dark, +/area/engine/tether) "txl" = ( /obj/structure/closet/secure_closet/personal, /obj/machinery/power/apc{ @@ -206870,7 +206904,7 @@ yip woc bxU pLi -pLi +aRt lgU qTO woc @@ -207128,7 +207162,7 @@ woc jgm jgm jgm -pLi +pkn cZp woc tgZ @@ -207383,7 +207417,7 @@ ykt wWR woc jgm -jgm +gQR jgm pLi pIP @@ -207896,9 +207930,9 @@ xdO ykt wWR woc -bxU -pLi +txg pLi +aRt uPI xqo woc diff --git a/code/__HELPERS/_hyper_helpers.dm b/code/__HELPERS/_hyper_helpers.dm index 5e23e5857..a898715b3 100644 --- a/code/__HELPERS/_hyper_helpers.dm +++ b/code/__HELPERS/_hyper_helpers.dm @@ -3,6 +3,9 @@ GLOBAL_LIST_EMPTY(cosmetic_chests) GLOBAL_LIST_EMPTY(cosmetic_arms) GLOBAL_LIST_EMPTY(cosmetic_legs) +//A list of safety tether machines for cloud chasms to refer to for emergency teleportation of fallen mobs +GLOBAL_LIST_EMPTY(safety_tethers_list) + /proc/init_cosmetic_parts(datum/cosmetic_part/type, list/global_list) for(var/subtype in subtypesof(type)) var/datum/cosmetic_part/part = new subtype() diff --git a/code/datums/components/chasm.dm b/code/datums/components/chasm.dm index 4a64e5f8c..f64f60494 100644 --- a/code/datums/components/chasm.dm +++ b/code/datums/components/chasm.dm @@ -1,6 +1,7 @@ // Used by /turf/open/chasm and subtypes to implement the "dropping" mechanic /datum/component/chasm var/turf/target_turf + var/turf/linked_turf //turf tile this chasm tile is linked to. Used to get type of turf tile to determine if safety tether applies var/fall_message = "GAH! Ah... where are you?" var/oblivion_message = "You stumble and stare into the abyss before you. It stares back, and you fall into the enveloping dark." @@ -30,9 +31,10 @@ /obj/effect/baseturf_helper )) -/datum/component/chasm/Initialize(turf/target) +/datum/component/chasm/Initialize(turf/target, turf/linked) RegisterSignal(parent, list(COMSIG_MOVABLE_CROSSED, COMSIG_ATOM_ENTERED), .proc/Entered) target_turf = target + linked_turf = linked START_PROCESSING(SSobj, src) // process on create, in case stuff is still there /datum/component/chasm/proc/Entered(datum/source, atom/movable/AM) @@ -94,6 +96,7 @@ return TRUE /datum/component/chasm/proc/drop(atom/movable/AM) + //Make sure the item is still there after our sleep if(!AM || QDELETED(AM)) return @@ -112,44 +115,79 @@ falling_atoms -= AM else + // send to oblivion AM.visible_message("[AM] falls into [parent]!", "[oblivion_message]") + if (isliving(AM)) var/mob/living/L = AM L.notransform = TRUE L.Stun(200) - L.resting = TRUE + if(!issilicon(AM)) + L.resting = TRUE if(L.client && check_rights_for(L.client, R_FUN)) playsound(AM, pick('hyperstation/sound/misc/yodadeath.ogg', 'hyperstation/sound/misc/fallingthroughclouds.ogg', 'hyperstation/sound/misc/goofy.ogg', 'hyperstation/sound/misc/wilhelm.ogg'), 100, 0) else if(prob(5)) playsound(AM, pick('hyperstation/sound/misc/yodadeath.ogg', 'hyperstation/sound/misc/fallingthroughclouds.ogg', 'hyperstation/sound/misc/goofy.ogg', 'hyperstation/sound/misc/wilhelm.ogg'), 100, 0) - var/oldtransform = AM.transform - var/oldcolor = AM.color - var/oldalpha = AM.alpha - animate(AM, transform = matrix() - matrix(), alpha = 0, color = rgb(0, 0, 0), time = 10) - for(var/i in 1 to 5) - //Make sure the item is still there after our sleep - if(!AM || QDELETED(AM)) - return - AM.pixel_y-- - sleep(2) + if(linked_turf.name == "clouds" && (iscyborg(AM) || iscarbon(AM))) + var/mob/living/victim = AM + + var/tether_number = GLOB.safety_tethers_list.len + + //If safety tethers are present, get one from the global list to teleport the body to if operational + if(tether_number > 0) + if(tether_number == 1) + + // If teleportation fails + if(!GLOB.safety_tethers_list[1].bungee_teleport(victim)) + finishdrop(AM) + else + + //Just in case multiple safety tethers are present + if(!GLOB.safety_tethers_list[rand(1,GLOB.safety_tethers_list.len)].bungee_teleport(victim)) + finishdrop(AM) + if(isliving(AM)) + var/mob/living/L = AM + L.notransform = FALSE + else + finishdrop(AM) + else + finishdrop(AM) + + +/datum/component/chasm/proc/finishdrop(atom/movable/AM) + + var/oldalpha = AM.alpha + var/oldcolor = AM.color + var/oldtransform = AM.transform + + //Animate our falling items, then delete them. + animate(AM, transform = matrix() - matrix(), alpha = 0, color = rgb(0, 0, 0), time = 10) + + for(var/i in 1 to 5) //Make sure the item is still there after our sleep if(!AM || QDELETED(AM)) return + AM.pixel_y-- + sleep(2) - if(iscyborg(AM)) - var/mob/living/silicon/robot/S = AM - qdel(S.mmi) + //Make sure the item is still there after our sleep + if(!AM || QDELETED(AM)) + return - falling_atoms -= AM - qdel(AM) - if(AM && !QDELETED(AM)) //It's indestructible - var/atom/parent = src.parent - parent.visible_message("[parent] spits out [AM]!") - AM.alpha = oldalpha - AM.color = oldcolor - AM.transform = oldtransform - AM.throw_at(get_edge_target_turf(parent,pick(GLOB.alldirs)),rand(1, 10),rand(1, 10)) + if(iscyborg(AM)) + var/mob/living/silicon/robot/S = AM + qdel(S.mmi) + + falling_atoms -= AM + qdel(AM) + if(AM && !QDELETED(AM)) //It's indestructible + var/atom/parent = src.parent + parent.visible_message("[parent] spits out [AM]!") + AM.alpha = oldalpha + AM.color = oldcolor + AM.transform = oldtransform + AM.throw_at(get_edge_target_turf(parent,pick(GLOB.alldirs)),rand(1, 10),rand(1, 10)) \ No newline at end of file diff --git a/code/game/machinery/_machinery.dm b/code/game/machinery/_machinery.dm index 114492543..cd18282d5 100644 --- a/code/game/machinery/_machinery.dm +++ b/code/game/machinery/_machinery.dm @@ -157,8 +157,13 @@ Class Procs: /obj/machinery/proc/locate_machinery() return -/obj/machinery/process()//If you dont use process or power why are you here - return PROCESS_KILL +/obj/machinery/process() + + //Here because if the machine doesn't have a process code attached to it or if PROCESS_KILL is returned it will no longer draw power. + if(is_operational()) + return 1 + else + return 0 /obj/machinery/proc/process_atmos()//If you dont use process why are you here return PROCESS_KILL diff --git a/code/game/turfs/simulated/chasm.dm b/code/game/turfs/simulated/chasm.dm index 88211a9fb..8cdd7be14 100644 --- a/code/game/turfs/simulated/chasm.dm +++ b/code/game/turfs/simulated/chasm.dm @@ -12,16 +12,20 @@ /turf/open/chasm/Initialize() . = ..() - AddComponent(/datum/component/chasm, SSmapping.get_turf_below(src)) + AddComponent(/datum/component/chasm, SSmapping.get_turf_below(src), src) /turf/open/chasm/proc/set_target(turf/target) var/datum/component/chasm/chasm_component = GetComponent(/datum/component/chasm) chasm_component.target_turf = target +// Only runs in the case of things like pride's mirror who are just using the turf as an easy reference + /turf/open/chasm/proc/drop(atom/movable/AM) + var/datum/component/chasm/chasm_component = GetComponent(/datum/component/chasm) chasm_component.drop(AM) + /turf/open/chasm/MakeSlippery(wet_setting, min_wet_time, wet_time_to_add, max_wet_time, permanent) return @@ -137,4 +141,4 @@ /turf/open/chasm/cloud/get_smooth_underlay_icon(mutable_appearance/underlay_appearance, turf/asking_turf, adjacency_dir) underlay_appearance.icon = 'icons/turf/space.dmi' underlay_appearance.plane = PLANE_SPACE - return TRUE + return TRUE \ No newline at end of file diff --git a/hyperstation/code/game/machinery/safety_tether.dm b/hyperstation/code/game/machinery/safety_tether.dm new file mode 100644 index 000000000..1955d7599 --- /dev/null +++ b/hyperstation/code/game/machinery/safety_tether.dm @@ -0,0 +1,254 @@ +//Teleports players who fall off cloud chasm tiles to itself, drawing power in the process. + +//bugs; power draw not working + +//Maybe if separating into multiple objects like with the grav gen, have it arc tesla lightning to each corner upon teleport? + + +// Have it pull back all player mobs, and all borgs if possible +// have chasm tiles, when something falls in send a signal that the safety tether catches, containing the chasm turf and the mob +// then the tether can either report it's inactive to the tile so that it then deletes the entity, or teeleport the player + +// create global list of safety tethers in the world to then process + +//Easy definitions to have the machine talk on the appropriate frequencies. +#define SPEAKCOMMON(message) radio.talk_into(src, message, null) +#define SPEAKMEDICAL(message) radio.talk_into(src, message, RADIO_CHANNEL_MEDICAL) +#define SPEAKSCIENCE(message) radio.talk_into(src, message, RADIO_CHANNEL_SCIENCE) + +/obj/machinery/safety_tether + name = "safety tether" + desc = "A gargantuan machine that performs emergency teleportations on those unlucky or clumsy enough to slip off the edge." + density = FALSE + icon = 'hyperstation/icons/obj/machinery/safety_tether.dmi' + icon_state = "safety_tether" + verb_say = "states" + + //Shifts the starting location so as to center the machine + pixel_x = -32 + pixel_y = -32 + + //Set width and height in case we make this dense + bound_width = 96 + bound_height = 96 + + //We don't want this getting destroyed since it cannot be remade. + move_resist = INFINITY + resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF + + //var/obj/machinery/computer/tether_computer/connected = null //So we remember the connected clone machine. + + //Here for easy balancing and possibly changing it between different tether objects or if upgrades occur. + + var/dismember_prob = 50 //Probability of dismembering 2 limbs rather than 1 + + //Max and min amounts of clone damage for organics + var/cloneloss_min = 45 + var/cloneloss_max = 70 + + //Amount of subject's blood to remove on successful teleport + var/bleed_ratio = 0.25 + + //Max and min amounts of burn damage for silicates. + /* + Cyborg health guide + 100; max health + <50; module 3 offline + <0; module 2 offline + <-50; module 3 offline + -100+; death + */ + var/silicon_burn_min = 120 + var/silicon_burn_max = 140 + + var/brightness_on = 4 //Range of light when on + light_power = 1.5 //Strength of the light when on + light_color = LIGHT_COLOR_CYAN + + //Defines the center point around which lighting is located + var/atom/light_source + + use_power = IDLE_POWER_USE + power_channel = EQUIP + idle_power_usage = 1000 + var/teleport_power_draw = 900000 //Uses about a fourth of the upgraded power cell plus default in APCs + var/tesla_power_ratio = 0.75 //ratio of its actual power draw to the tesla it creates + + critical_machine = TRUE //If this machine is critical to station operation and should have the area be excempted from power failures. + + var/internal_radio = TRUE + var/obj/item/radio/radio + + //Give it permission to talk on both medical and science frequencies, as with geneticists + var/radio_key = /obj/item/encryptionkey/headset_medsci + +/obj/machinery/safety_tether/Initialize() + . = ..() + + //Adds this to the global list of safety tethers in the world to pull from when chasms attempt to drop mobs + GLOB.safety_tethers_list += src + + if(internal_radio) + radio = new(src) + radio.keyslot = new radio_key + radio.subspace_transmission = TRUE + radio.canhear_range = 0 + radio.recalculateChannels() + + //ensures light is properly centered around the tether. Removes lighting system's pixel approximation that breaks it. + light_source = get_turf(src) + update_icon() + power_change() //Here to start lights on ititialization. + +/obj/machinery/safety_tether/Destroy() + QDEL_NULL(radio) + GLOB.safety_tethers_list -= src + light_source.set_light(0) + . = ..() + +/obj/machinery/safety_tether/doMove(atom/destination) + . = ..() + //ensures light is properly centered around the tether. Removes lighting system's pixel approximation that breaks it. + light_source.set_light(0) + light_source = get_turf(src) + return . + +/obj/machinery/safety_tether/update_icon() + cut_overlays() + if(is_operational()) + add_overlay("operational_overlay") + + +/obj/machinery/safety_tether/examine(mob/user) + . = ..() + if(is_operational()) + . += "The safety tether's currently protecting the station." + else + . += "The safety tether's offline." + + +/obj/machinery/safety_tether/attack_ai(mob/user) + return examine(user) + +//Returns true if teleport is successful, false otherwise +/obj/machinery/safety_tether/proc/bungee_teleport(mob/living/M) + + if(ismovableatom(M) && is_operational() != 0 && do_teleport(M, get_turf(src), channel = TELEPORT_CHANNEL_BLUESPACE)) + use_power(teleport_power_draw) + + //Style points + playsound(src, 'sound/magic/lightningbolt.ogg', 100, 1, extrarange = 5) + // TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE | TESLA_MOB_STUN | TESLA_ALLOW_DUPLICATES + tesla_zap(src, 3, teleport_power_draw * tesla_power_ratio, TESLA_MOB_DAMAGE | FALSE | TESLA_MOB_STUN | FALSE) //Set to zap harmlessly but in a way that looks cool + + M.spawn_gibs() + M.emote("scream") + + log_admin("[M] ([key_name(M)]) was saved by the Safety Tether.") + + if(iscarbon(M)) + + var/mob/living/carbon/Carbon = M + + to_chat(Carbon, "Buzzing static snaps taut on your chest....") + Carbon.adjustCloneLoss(rand(cloneloss_min, cloneloss_max)) + + //Random limb removal + var/dismember_num = 1 + + //Dismember two limbs + if(prob(dismember_prob)) + dismember_num = 2 + + var/dismembered_arm = FALSE + var/dismembered_leg = FALSE + + for(var/obj/item/bodypart/BP in Carbon.bodyparts) + if(BP.body_part != CHEST && BP.body_part != HEAD) //I am not ready to find out what happens if your chest is missing + var/zone = BP.body_zone + //Checks to ensure that only one arm and one leg each are dismembered, to prevent severe disabling like 2 arms being removed. + if(zone == BODY_ZONE_L_ARM || zone == BODY_ZONE_R_ARM) + if(!dismembered_arm) + dismembered_arm = TRUE + BP.dismember() + + if(zone == BODY_ZONE_L_LEG || zone == BODY_ZONE_R_LEG) + if(!dismembered_leg) + dismembered_leg = TRUE + BP.dismember() + + dismember_num -= 1 + + //We've dismembered enough limbs. + if(dismember_num <= 0) + break + + //Bleed our pal a little + M.blood_volume -= BLOOD_VOLUME_NORMAL * M.blood_ratio * bleed_ratio + + src.visible_message("[src] spits out [M] and viscera!") + if(internal_radio) + + //Area name gotten just in case the locale of it's moved from engineering when mapmaking. + var/area/A = get_area(get_turf(src)) + var/area_name = A.name + SPEAKMEDICAL("The safety tether's caught the would-be crater [M] at the [area_name].") + + //All this ended up causing issues by canting the player 90 degrees due to the player having enforced rest at the time of teleportation + //animate(M, transform = oldtransform, alpha = oldalpha, color = oldcolor, time = 10) + + //M.transform = oldtransform + //M.alpha = oldalpha + //M.color = oldcolor + else + if(issilicon(M)) + var/mob/living/silicon/S = M + to_chat(S, "Your circuits spark, slag, and pop as overwhelming white noise crackles and YANKS...") + S.apply_damage_type(damage = rand(silicon_burn_min, silicon_burn_max), damagetype = BURN) + + src.visible_message("[src] spits out [M] and oily, smoking circuits!") + if(internal_radio) + + //Area name gotten just in case the locale of it's moved from engineering when mapmaking. + var/area/A = get_area(get_turf(src)) + var/area_name = A.name + SPEAKSCIENCE("The safety tether's caught the would-be crater [M] at the [area_name].") + + return TRUE + + else + log_admin("[M] ([key_name(M)]) was failed by the Safety Tether and fell into the clouds.") + return FALSE + +//Updates machine icon and lighting every time power in the area changes +/obj/machinery/safety_tether/power_change() + . = ..() + if(light_source) + if(stat & NOPOWER) + SPEAKCOMMON("The Safety Tether's shut down from a lack of power.") + light_source.set_light(0) + else + SPEAKCOMMON("The Safety Tether is back online.") + light_source.set_light(brightness_on, light_power, light_color) + update_icon() + +/* + * Manual -- A big ol' manual. + */ + +/obj/item/paper/fluff/safety_tether + name = "paper - 'Safety Tether Introduction" + info = {"