From cce3787ace80c70826c11e17a0e2c25f6cb18e26 Mon Sep 17 00:00:00 2001 From: adamsong Date: Thu, 1 Jul 2021 06:46:06 -0400 Subject: [PATCH] [admin] Takes the pod spawn code from TG (#11612) * Stole podmin power from tg * Update uplink_items.dm * Removed unused proc argument * Hopefully fixes icon stuff * Fixed the contractor pods not working --- code/__DEFINES/cargo.dm | 55 +- code/__DEFINES/is_helpers.dm | 2 + code/__DEFINES/misc.dm | 1 + code/__DEFINES/obj_flags.dm | 3 + code/__HELPERS/cmp.dm | 3 + code/game/area/areas/centcom.dm | 14 + code/game/atoms.dm | 16 + .../structures/crates_lockers/closets.dm | 7 +- .../antagonists/traitor/syndicate_contract.dm | 2 +- code/modules/asset_cache/asset_list_items.dm | 30 + code/modules/cargo/centcom_podlauncher.dm | 722 +++++--- code/modules/cargo/gondolapod.dm | 8 +- code/modules/cargo/supplypod.dm | 664 +++++-- .../mouse_pointers/supplypod_down_target.dmi | Bin 0 -> 464 bytes .../mouse_pointers/supplypod_pickturf.dmi | Bin 0 -> 336 bytes .../supplypod_pickturf_down.dmi | Bin 0 -> 322 bytes .../mouse_pointers/supplypod_target.dmi | Bin 0 -> 392 bytes icons/obj/supplypods.dmi | Bin 56060 -> 27073 bytes icons/obj/supplypods_32x32.dmi | Bin 0 -> 3393 bytes tgui/packages/tgui/components/Button.js | 4 + .../tgui/components/DraggableControl.js | 29 +- tgui/packages/tgui/components/Flex.js | 2 +- tgui/packages/tgui/components/Knob.js | 8 +- .../tgui/components/LabeledControls.js | 6 +- tgui/packages/tgui/components/Section.js | 19 +- tgui/packages/tgui/components/Tooltip.js | 4 +- tgui/packages/tgui/drag.js | 4 + .../tgui/interfaces/CentcomPodLauncher.js | 1623 ++++++++++++----- .../tgui/styles/components/Section.scss | 7 +- .../tgui/styles/components/Tooltip.scss | 22 +- 30 files changed, 2268 insertions(+), 987 deletions(-) create mode 100644 icons/effects/mouse_pointers/supplypod_down_target.dmi create mode 100644 icons/effects/mouse_pointers/supplypod_pickturf.dmi create mode 100644 icons/effects/mouse_pointers/supplypod_pickturf_down.dmi create mode 100644 icons/effects/mouse_pointers/supplypod_target.dmi create mode 100644 icons/obj/supplypods_32x32.dmi diff --git a/code/__DEFINES/cargo.dm b/code/__DEFINES/cargo.dm index 0fbd138b9adc..e3228ba06258 100644 --- a/code/__DEFINES/cargo.dm +++ b/code/__DEFINES/cargo.dm @@ -13,23 +13,40 @@ #define STYLE_GONDOLA 13 #define STYLE_SEETHROUGH 14 -#define POD_ICON_STATE 1 -#define POD_NAME 2 -#define POD_DESC 3 +#define POD_SHAPE 1 +#define POD_BASE 2 +#define POD_DOOR 3 +#define POD_DECAL 4 +#define POD_GLOW 5 +#define POD_RUBBLE_TYPE 6 +#define POD_NAME 7 +#define POD_DESC 8 -#define POD_STYLES list(\ - list("supplypod", "supply pod", "A Nanotrasen supply drop pod."),\ - list("bluespacepod", "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\ - list("centcompod", "\improper Centcom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to Centcom after delivery."),\ - list("syndiepod", "blood-red supply pod", "A dark, intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\ - list("squadpod", "\improper MK. II supply pod", "A Nanotrasen supply pod. This one has been marked the markings of some sort of elite strike team."),\ - list("cultpod", "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\ - list("missilepod", "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\ - list("smissilepod", "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\ - list("boxpod", "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\ - list("honkpod", "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\ - list("fruitpod", "\improper Orange", "An angry orange."),\ - list("", "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\ - list("gondolapod", "gondola", "The silent walker. This one seems to be part of a delivery agency."),\ - list("", "", "")\ -) \ No newline at end of file +#define RUBBLE_NONE 1 +#define RUBBLE_NORMAL 2 +#define RUBBLE_WIDE 3 +#define RUBBLE_THIN 4 + +#define POD_SHAPE_NORML 1 +#define POD_SHAPE_OTHER 2 + +#define SUPPLYPOD_X_OFFSET -16 + +GLOBAL_LIST_EMPTY(supplypod_loading_bays) + +GLOBAL_LIST_INIT(podstyles, list(\ + list(POD_SHAPE_NORML, "pod", TRUE, "default", "yellow", RUBBLE_NORMAL, "supply pod", "A Nanotrasen supply drop pod."),\ + list(POD_SHAPE_NORML, "advpod", TRUE, "bluespace", "blue", RUBBLE_NORMAL, "bluespace supply pod" , "A Nanotrasen Bluespace supply pod. Teleports back to CentCom after delivery."),\ + list(POD_SHAPE_NORML, "advpod", TRUE, "centcom", "blue", RUBBLE_NORMAL, "\improper CentCom supply pod", "A Nanotrasen supply pod, this one has been marked with Central Command's designations. Teleports back to CentCom after delivery."),\ + list(POD_SHAPE_NORML, "darkpod", TRUE, "syndicate", "red", RUBBLE_NORMAL, "blood-red supply pod", "An intimidating supply pod, covered in the blood-red markings of the Syndicate. It's probably best to stand back from this."),\ + list(POD_SHAPE_NORML, "darkpod", TRUE, "deathsquad", "blue", RUBBLE_NORMAL, "\improper Deathsquad drop pod", "A Nanotrasen drop pod. This one has been marked the markings of Nanotrasen's elite strike team."),\ + list(POD_SHAPE_NORML, "pod", TRUE, "cultist", "red", RUBBLE_NORMAL, "bloody supply pod", "A Nanotrasen supply pod covered in scratch-marks, blood, and strange runes."),\ + list(POD_SHAPE_OTHER, "missile", FALSE, FALSE, FALSE, RUBBLE_THIN, "cruise missile", "A big ass missile that didn't seem to fully detonate. It was likely launched from some far-off deep space missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\ + list(POD_SHAPE_OTHER, "smissile", FALSE, FALSE, FALSE, RUBBLE_THIN, "\improper Syndicate cruise missile", "A big ass, blood-red missile that didn't seem to fully detonate. It was likely launched from some deep space Syndicate missile silo. There appears to be an auxillery payload hatch on the side, though manually opening it is likely impossible."),\ + list(POD_SHAPE_OTHER, "box", TRUE, FALSE, FALSE, RUBBLE_WIDE, "\improper Aussec supply crate", "An incredibly sturdy supply crate, designed to withstand orbital re-entry. Has 'Aussec Armory - 2532' engraved on the side."),\ + list(POD_SHAPE_NORML, "clownpod", TRUE, "clown", "green", RUBBLE_NORMAL, "\improper HONK pod", "A brightly-colored supply pod. It likely originated from the Clown Federation."),\ + list(POD_SHAPE_OTHER, "orange", TRUE, FALSE, FALSE, RUBBLE_NONE, "\improper Orange", "An angry orange."),\ + list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, "\improper S.T.E.A.L.T.H. pod MKVII", "A supply pod that, under normal circumstances, is completely invisible to conventional methods of detection. How are you even seeing this?"),\ + list(POD_SHAPE_OTHER, "gondola", FALSE, FALSE, FALSE, RUBBLE_NONE, "gondola", "The silent walker. This one seems to be part of a delivery agency."),\ + list(POD_SHAPE_OTHER, FALSE, FALSE, FALSE, FALSE, RUBBLE_NONE, FALSE, FALSE, "rl_click", "give_po")\ +)) diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm index 2ef3137611fe..01d22a59a944 100644 --- a/code/__DEFINES/is_helpers.dm +++ b/code/__DEFINES/is_helpers.dm @@ -266,3 +266,5 @@ GLOBAL_LIST_INIT(glass_sheet_types, typecacheof(list( //Fugitive #define isfugitive(M) (istype(M) && M.mind?.has_antag_datum(/datum/antagonist/fugitive)) + +#define isProbablyWallMounted(O) (O.pixel_x > 20 || O.pixel_x < -20 || O.pixel_y > 20 || O.pixel_y < -20) diff --git a/code/__DEFINES/misc.dm b/code/__DEFINES/misc.dm index 8c7245982fca..5f25a72b22f2 100644 --- a/code/__DEFINES/misc.dm +++ b/code/__DEFINES/misc.dm @@ -277,6 +277,7 @@ GLOBAL_LIST_INIT(pda_styles, list(MONO, VT, ORBITRON, SHARE)) #define DEADCHAT_ARRIVALRATTLE "arrivalrattle" #define DEADCHAT_DEATHRATTLE "deathrattle" #define DEADCHAT_REGULAR "regular-deadchat" +#define DEADCHAT_ANNOUNCEMENT "announcement" // Bluespace shelter deploy checks #define SHELTER_DEPLOY_ALLOWED "allowed" diff --git a/code/__DEFINES/obj_flags.dm b/code/__DEFINES/obj_flags.dm index 3e71aee73ec3..c5742f020f19 100644 --- a/code/__DEFINES/obj_flags.dm +++ b/code/__DEFINES/obj_flags.dm @@ -51,3 +51,6 @@ #define ORGAN_FAILING (1<<2) //Failing organs perform damaging effects until replaced or fixed #define ORGAN_EXTERNAL (1<<3) //Was this organ implanted/inserted/etc, if true will not be removed during species change. #define ORGAN_VITAL (1<<4) //Currently only the brain + +/// Flags for the pod_flags var on /obj/structure/closet/supplypod +#define FIRST_SOUNDS (1<<0) // If it shouldn't play sounds the first time it lands, used for reverse mode diff --git a/code/__HELPERS/cmp.dm b/code/__HELPERS/cmp.dm index daf6be2fa8bc..aa1dbde09d5b 100644 --- a/code/__HELPERS/cmp.dm +++ b/code/__HELPERS/cmp.dm @@ -108,3 +108,6 @@ GLOBAL_VAR_INIT(cmp_field, "name") /proc/cmp_mob_realname_dsc(mob/A,mob/B) return sorttext(A.real_name,B.real_name) + +/proc/cmp_typepaths_asc(A, B) + return sorttext("[B]","[A]") diff --git a/code/game/area/areas/centcom.dm b/code/game/area/areas/centcom.dm index b5c41dfd3471..0a4ab21e2fbb 100644 --- a/code/game/area/areas/centcom.dm +++ b/code/game/area/areas/centcom.dm @@ -49,21 +49,35 @@ /area/centcom/supplypod/loading name = "Supplypod Loading Facility" icon_state = "supplypod_loading" + var/loading_id = "" + +/area/centcom/supplypod/loading/Initialize() + . = ..() + if(!loading_id) + CRASH("[type] created without a loading_id") + if(GLOB.supplypod_loading_bays[loading_id]) + CRASH("Duplicate loading bay area: [type] ([loading_id])") + GLOB.supplypod_loading_bays[loading_id] = src /area/centcom/supplypod/loading/one name = "Bay #1" + loading_id = "1" /area/centcom/supplypod/loading/two name = "Bay #2" + loading_id = "2" /area/centcom/supplypod/loading/three name = "Bay #3" + loading_id = "3" /area/centcom/supplypod/loading/four name = "Bay #4" + loading_id = "4" /area/centcom/supplypod/loading/ert name = "ERT Bay" + loading_id = "5" //THUNDERDOME /area/tdome diff --git a/code/game/atoms.dm b/code/game/atoms.dm index e82daafd3f2c..5cecc473357f 100644 --- a/code/game/atoms.dm +++ b/code/game/atoms.dm @@ -1164,6 +1164,22 @@ /atom/proc/rust_heretic_act() return +/** + * Used to set something as 'open' if it's being used as a supplypod + * + * Override this if you want an atom to be usable as a supplypod. + */ +/atom/proc/setOpened() + return + +/** + * Used to set something as 'closed' if it's being used as a supplypod + * + * Override this if you want an atom to be usable as a supplypod. + */ +/atom/proc/setClosed() + return + /** * Recursive getter method to return a list of all ghosts orbitting this atom * diff --git a/code/game/objects/structures/crates_lockers/closets.dm b/code/game/objects/structures/crates_lockers/closets.dm index 174b00ac23b1..7c65e157dd96 100644 --- a/code/game/objects/structures/crates_lockers/closets.dm +++ b/code/game/objects/structures/crates_lockers/closets.dm @@ -378,8 +378,11 @@ GLOBAL_LIST_EMPTY(lockers) var/mob/living/L = O if(!issilicon(L)) L.Paralyze(40) - O.forceMove(T) - close() + if(istype(src, /obj/structure/closet/supplypod/extractionpod)) + O.forceMove(src) + else + O.forceMove(T) + close() else O.forceMove(T) return 1 diff --git a/code/modules/antagonists/traitor/syndicate_contract.dm b/code/modules/antagonists/traitor/syndicate_contract.dm index f48b009aa9e7..3cbe64702da8 100644 --- a/code/modules/antagonists/traitor/syndicate_contract.dm +++ b/code/modules/antagonists/traitor/syndicate_contract.dm @@ -113,7 +113,7 @@ var/obj/structure/closet/supplypod/extractionpod/pod = source // Handle the pod returning - pod.send_up(pod) + pod.startExitSequence(pod) diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index c836b0a5ee57..9105773eedc4 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -402,3 +402,33 @@ a//DEFINITIONS FOR ASSET DATUMS START HERE. /datum/asset/simple/portraits/public tab = "public" + +/datum/asset/spritesheet/supplypods + name = "supplypods" + +/datum/asset/spritesheet/supplypods/register() + for (var/style in 1 to length(GLOB.podstyles)) + var/icon_file = 'icons/obj/supplypods.dmi' + if (style == STYLE_SEETHROUGH) + Insert("pod_asset[style]", icon(icon_file, "seethrough-icon")) + continue + var/base = GLOB.podstyles[style][POD_BASE] + if (!base) + Insert("pod_asset[style]", icon(icon_file, "invisible-icon")) + continue + var/icon/podIcon = icon(icon_file, base) + var/door = GLOB.podstyles[style][POD_DOOR] + if (door) + door = "[base]_door" + podIcon.Blend(icon(icon_file, door), ICON_OVERLAY) + var/shape = GLOB.podstyles[style][POD_SHAPE] + if (shape == POD_SHAPE_NORML) + var/decal = GLOB.podstyles[style][POD_DECAL] + if (decal) + podIcon.Blend(icon(icon_file, decal), ICON_OVERLAY) + var/glow = GLOB.podstyles[style][POD_GLOW] + if (glow) + glow = "pod_glow_[glow]" + podIcon.Blend(icon(icon_file, glow), ICON_OVERLAY) + Insert("pod_asset[style]", podIcon) + return ..() diff --git a/code/modules/cargo/centcom_podlauncher.dm b/code/modules/cargo/centcom_podlauncher.dm index 1430bb0d996f..e6ddb506fab5 100644 --- a/code/modules/cargo/centcom_podlauncher.dm +++ b/code/modules/cargo/centcom_podlauncher.dm @@ -1,3 +1,10 @@ +#define TAB_POD 0 //Used to check if the UIs built in camera is looking at the pod +#define TAB_BAY 1 //Used to check if the UIs built in camera is looking at the launch bay area + +#define LAUNCH_ALL 0 //Used to check if we're launching everything from the bay area at once +#define LAUNCH_ORDERED 1 //Used to check if we're launching everything from the bay area in order +#define LAUNCH_RANDOM 2 //Used to check if we're launching everything from the bay area randomly + //The Great and Mighty CentCom Pod Launcher - MrDoomBringer //This was originally created as a way to get adminspawned items to the station in an IC manner. It's evolved to contain a few more //features such as item removal, smiting, controllable delivery mobs, and more. @@ -11,23 +18,25 @@ /client/proc/centcom_podlauncher() //Creates a verb for admins to open up the ui set name = "Config/Launch Supplypod" - set desc = "Configure and launch a Centcom supplypod full of whatever your heart desires!" + set desc = "Configure and launch a CentCom supplypod full of whatever your heart desires!" set category = "Misc" if(!check_rights(R_FUN)) return - var/datum/centcom_podlauncher/plaunch = new(usr)//create the datum - plaunch.ui_interact(usr)//datum has a tgui component, here we open the window + new /datum/centcom_podlauncher(usr)//create the datum //Variables declared to change how items in the launch bay are picked and launched. (Almost) all of these are changed in the ui_act proc //Some effect groups are choices, while other are booleans. This is because some effects can stack, while others dont (ex: you can stack explosion and quiet, but you cant stack ordered launch and random launch) /datum/centcom_podlauncher - var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/DPtarget, /obj/effect/supplypod_selector )) + var/static/list/ignored_atoms = typecacheof(list(null, /mob/dead, /obj/effect/landmark, /obj/docking_port, /atom/movable/lighting_object, /obj/effect/particle_effect/sparks, /obj/effect/DPtarget, /obj/effect/hallucination/simple/supplypod_selector, /obj/effect/hallucination/simple/dropoff_location)) var/turf/oldTurf //Keeps track of where the user was at if they use the "teleport to centcom" button, so they can go back var/client/holder //client of whoever is using this datum - var/area/bay //What bay we're using to launch shit from. + var/area/centcom/supplypod/loading/bay //What bay we're using to launch shit from. + var/bayNumber //Quick reference to what bay we're in. Usually set to the loading_id variable for the related area type + var/customDropoff = FALSE + var/picking_dropoff_turf = FALSE var/launchClone = FALSE //If true, then we don't actually launch the thing in the bay. Instead we call duplicateObject() and send the result var/launchRandomItem = FALSE //If true, lauches a single random item instead of everything on a turf. - var/launchChoice = 1 //Determines if we launch all at once (0) , in order (1), or at random(2) + var/launchChoice = LAUNCH_RANDOM //Determines if we launch all at once (0) , in order (1), or at random(2) var/explosionChoice = 0 //Determines if there is no explosion (0), custom explosion (1), or just do a maxcap (2) var/damageChoice = 0 //Determines if we do no damage (0), custom amnt of damage (1), or gib + 5000dmg (2) var/launcherActivated = FALSE //check if we've entered "launch mode" (when we click a pod is launched). Used for updating mouse cursor @@ -39,57 +48,127 @@ var/list/orderedArea = list() //Contains an ordered list of turfs in an area (filled in the createOrderedArea() proc), read top-left to bottom-right. Used for the "ordered" launch mode (launchChoice = 1) var/list/turf/acceptableTurfs = list() //Contians a list of turfs (in the "bay" area on centcom) that have items that can be launched. Taken from orderedArea var/list/launchList = list() //Contains whatever is going to be put in the supplypod and fired. Taken from acceptableTurfs - var/obj/effect/supplypod_selector/selector = new() //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1) + var/obj/effect/hallucination/simple/supplypod_selector/selector //An effect used for keeping track of what item is going to be launched when in "ordered" mode (launchChoice = 1) + var/obj/effect/hallucination/simple/dropoff_location/indicator var/obj/structure/closet/supplypod/centcompod/temp_pod //The temporary pod that is modified by this datum, then cloned. The buildObject() clone of this pod is what is launched + // Stuff needed to render the map + var/map_name + var/obj/screen/map_view/cam_screen + var/list/cam_plane_masters + var/obj/screen/background/cam_background + var/tabIndex = 1 + var/list/timers = list("landingDelay", "fallDuration", "openingDelay", "departureDelay") + var/renderLighting = FALSE -/datum/centcom_podlauncher/New(H)//H can either be a client or a mob due to byondcode(tm) - if (istype(H,/client)) - var/client/C = H - holder = C //if its a client, assign it to holder +/datum/centcom_podlauncher/New(user) //user can either be a client or a mob + if (user) //Prevents runtimes on datums being made without clients + setup(user) + +/datum/centcom_podlauncher/proc/setup(user) //H can either be a client or a mob + if (istype(user,/client)) + var/client/user_client = user + holder = user_client //if its a client, assign it to holder else - var/mob/M = H - holder = M.client //if its a mob, assign the mob's client to holder + var/mob/user_mob = user + holder = user_mob.client //if its a mob, assign the mob's client to holder bay = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas //Locate the default bay (one) from the centcom map - temp_pod = new(locate(/area/centcom/supplypod/podStorage) in GLOB.sortedAreas) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed) + bayNumber = bay.loading_id //Used as quick reference to what bay we're taking items from + var/area/pod_storage_area = locate(/area/centcom/supplypod/pod_storage) in GLOB.sortedAreas + temp_pod = new(pick(get_area_turfs(pod_storage_area))) //Create a new temp_pod in the podStorage area on centcom (so users are free to look at it and change other variables if needed) orderedArea = createOrderedArea(bay) //Order all the turfs in the selected bay (top left to bottom right) to a single list. Used for the "ordered" mode (launchChoice = 1) + selector = new(null, holder.mob) + indicator = new(null, holder.mob) + setDropoff(bay) + initMap() + refreshBay() + ui_interact(holder.mob) + +/datum/centcom_podlauncher/proc/initMap() + if(map_name) + holder.clear_map(map_name) + + map_name = "admin_supplypod_bay_[REF(src)]_map" + // Initialize map objects + cam_screen = new + cam_screen.name = "screen" + cam_screen.assigned_map = map_name + cam_screen.del_on_map_removal = TRUE + cam_screen.screen_loc = "[map_name]:1,1" + cam_plane_masters = list() + for(var/plane in subtypesof(/obj/screen/plane_master)) + var/obj/screen/instance = new plane() + if (!renderLighting && instance.plane == LIGHTING_PLANE) + instance.alpha = 100 + instance.assigned_map = map_name + instance.del_on_map_removal = TRUE + instance.screen_loc = "[map_name]:CENTER" + cam_plane_masters += instance + cam_background = new + cam_background.assigned_map = map_name + cam_background.del_on_map_removal = TRUE + refreshView() + holder.register_map_obj(cam_screen) + for(var/plane in cam_plane_masters) + holder.register_map_obj(plane) + holder.register_map_obj(cam_background) /datum/centcom_podlauncher/ui_state(mob/user) + if (SSticker.current_state >= GAME_STATE_FINISHED) + return GLOB.always_state //Allow the UI to be given to players by admins after roundend return GLOB.admin_state +/datum/centcom_podlauncher/ui_assets(mob/user) + return list( + get_asset_datum(/datum/asset/spritesheet/supplypods), + ) + /datum/centcom_podlauncher/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) + // Open UI ui = new(user, src, "CentcomPodLauncher") ui.open() + refreshView() + +/datum/centcom_podlauncher/ui_static_data(mob/user) + var/list/data = list() + data["mapRef"] = map_name + data["defaultSoundVolume"] = initial(temp_pod.soundVolume) //default volume for pods + return data /datum/centcom_podlauncher/ui_data(mob/user) //Sends info about the pod to the UI. var/list/data = list() //*****NOTE*****: Many of these comments are similarly described in supplypod.dm. If you change them here, please consider doing so in the supplypod code as well! - var/B = (istype(bay, /area/centcom/supplypod/loading/one)) ? 1 : (istype(bay, /area/centcom/supplypod/loading/two)) ? 2 : (istype(bay, /area/centcom/supplypod/loading/three)) ? 3 : (istype(bay, /area/centcom/supplypod/loading/four)) ? 4 : (istype(bay, /area/centcom/supplypod/loading/ert)) ? 5 : 0 //top ten THICCEST FUCKING TERNARY CONDITIONALS OF 2036 - data["bay"] = bay //Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map. - data["bayNumber"] = B //Holds the bay as a number. Useful for comparisons in centcom_podlauncher.ract + bayNumber = bay?.loading_id //Used as quick reference to what bay we're taking items from + data["bayNumber"] = bayNumber //Holds the bay as a number. Useful for comparisons in centcom_podlauncher.ract data["oldArea"] = (oldTurf ? get_area(oldTurf) : null) //Holds the name of the area that the user was in before using the teleportCentcom action + data["picking_dropoff_turf"] = picking_dropoff_turf //If we're picking or have picked a dropoff turf. Only works when pod is in reverse mode + data["customDropoff"] = customDropoff + data["renderLighting"] = renderLighting data["launchClone"] = launchClone //Do we launch the actual items in the bay or just launch clones of them? data["launchRandomItem"] = launchRandomItem //Do we launch a single random item instead of everything on the turf? data["launchChoice"] = launchChoice //Launch turfs all at once (0), ordered (1), or randomly(1) data["explosionChoice"] = explosionChoice //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2) data["damageChoice"] = damageChoice //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2) - data["fallDuration"] = temp_pod.fallDuration //How long the pod's falling animation lasts - data["landingDelay"] = temp_pod.landingDelay //How long the pod takes to land after launching - data["openingDelay"] = temp_pod.openingDelay //How long the pod takes to open after landing - data["departureDelay"] = temp_pod.departureDelay //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom) - data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. + data["delay_1"] = temp_pod.landingDelay //How long the pod takes to land after launching + data["delay_2"] = temp_pod.fallDuration //How long the pod's falling animation lasts + data["delay_3"] = temp_pod.openingDelay //How long the pod takes to open after landing + data["delay_4"] = temp_pod.departureDelay //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom) + data["styleChoice"] = temp_pod.style //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. data["effectStun"] = temp_pod.effectStun //If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! data["effectLimb"] = temp_pod.effectLimb //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands data["effectOrgans"] = temp_pod.effectOrgans //If true, yeets the organs out of any bodies caught under the pod when it lands data["effectBluespace"] = temp_pod.bluespace //If true, the pod deletes (in a shower of sparks) after landing - data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isnt displayed on the turf where the pod will land + data["effectStealth"] = temp_pod.effectStealth //If true, a target icon isn't displayed on the turf where the pod will land data["effectQuiet"] = temp_pod.effectQuiet //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) data["effectMissile"] = temp_pod.effectMissile //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground data["effectCircle"] = temp_pod.effectCircle //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here data["effectBurst"] = effectBurst //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area data["effectReverse"] = temp_pod.reversing //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom + data["reverseOptionList"] = temp_pod.reverseOptionList data["effectTarget"] = specificTarget //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites data["effectName"] = temp_pod.adminNamed //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) + data["podName"] = temp_pod.name + data["podDesc"] = temp_pod.desc data["effectAnnounce"] = effectAnnounce data["giveLauncher"] = launcherActivated //If true, the user is in launch mode, and whenever they click a pod will be launched (either at their mouse position or at a specific target) data["numObjects"] = numTurfs //Counts the number of turfs that contain a launchable object in the centcom supplypod bay @@ -97,7 +176,7 @@ data["landingSound"] = temp_pod.landingSound //Admin sound to play when the pod lands data["openingSound"] = temp_pod.openingSound //Admin sound to play when the pod opens data["leavingSound"] = temp_pod.leavingSound //Admin sound to play when the pod leaves - data["soundVolume"] = temp_pod.soundVolume != initial(temp_pod.soundVolume) //Admin sound to play when the pod leaves + data["soundVolume"] = temp_pod.soundVolume //Admin sound to play when the pod leaves return data /datum/centcom_podlauncher/ui_act(action, params) @@ -105,49 +184,72 @@ return switch(action) ////////////////////////////UTILITIES////////////////// - if("bay1") - bay = locate(/area/centcom/supplypod/loading/one) in GLOB.sortedAreas //set the "bay" variable to the corresponding room in centcom - refreshBay() //calls refreshBay() which "recounts" the bay to see what items we can launch (among other things). + if("gamePanel") + holder.holder.Game() + SSblackbox.record_feedback("tally", "admin_verb", 1, "Game Panel") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! . = TRUE - if("bay2") - bay = locate(/area/centcom/supplypod/loading/two) in GLOB.sortedAreas + if("buildMode") + var/mob/holder_mob = holder.mob + if (holder_mob && check_rights(R_BUILDMODE)) + togglebuildmode(holder_mob) + SSblackbox.record_feedback("tally", "admin_verb", 1, "Toggle Build Mode") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! + . = TRUE + if("loadDataFromPreset") + var/list/savedData = params["payload"] + loadData(savedData) + . = TRUE + if("switchBay") + bayNumber = params["bayNumber"] refreshBay() . = TRUE - if("bay3") - bay = locate(/area/centcom/supplypod/loading/three) in GLOB.sortedAreas - refreshBay() + if("pickDropoffTurf") //Enters a mode that lets you pick the dropoff location for reverse pods + if (picking_dropoff_turf) + picking_dropoff_turf = FALSE + updateCursor() //Update the cursor of the user to a cool looking target icon + return + if (launcherActivated) + launcherActivated = FALSE //We don't want to have launch mode enabled while we're picking a turf + picking_dropoff_turf = TRUE + updateCursor() //Update the cursor of the user to a cool looking target icon . = TRUE - if("bay4") - bay = locate(/area/centcom/supplypod/loading/four) in GLOB.sortedAreas - refreshBay() + if("clearDropoffTurf") + setDropoff(bay) + customDropoff = FALSE + picking_dropoff_turf = FALSE + updateCursor() . = TRUE - if("bay5") - bay = locate(/area/centcom/supplypod/loading/ert) in GLOB.sortedAreas - refreshBay() + if("teleportDropoff") //Teleports the user to the dropoff point. + var/mob/M = holder.mob //We teleport whatever mob the client is attached to at the point of clicking + var/turf/current_location = get_turf(M) + var/list/coordinate_list = temp_pod.reverse_dropoff_coords + var/turf/dropoff_turf = locate(coordinate_list[1], coordinate_list[2], coordinate_list[3]) + if (current_location != dropoff_turf) + oldTurf = current_location + M.forceMove(dropoff_turf) //Perform the actual teleport + log_admin("[key_name(usr)] jumped to [AREACOORD(dropoff_turf)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(dropoff_turf)]") . = TRUE if("teleportCentcom") //Teleports the user to the centcom supply loading facility. - var/mob/M = holder.mob //We teleport whatever mob the client is attached to at the point of clicking - oldTurf = get_turf(M) //Used for the "teleportBack" action - var/area/A = locate(bay) in GLOB.sortedAreas - var/list/turfs = list() - for(var/turf/T in A) - turfs.Add(T) //Fill a list with turfs in the area - var/turf/T = safepick(turfs) //Only teleport if the list isn't empty - if(!T) //If the list is empty, error and cancel - to_chat(M, "Nowhere to jump to!") - return - M.forceMove(T) //Perform the actual teleport - log_admin("[key_name(usr)] jumped to [AREACOORD(A)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(A)]") + var/mob/holder_mob = holder.mob //We teleport whatever mob the client is attached to at the point of clicking + var/turf/current_location = get_turf(holder_mob) + var/area/bay_area = bay + if (current_location.loc != bay_area) + oldTurf = current_location + var/turf/teleport_turf = pick(get_area_turfs(bay_area)) + holder_mob.forceMove(teleport_turf) //Perform the actual teleport + if (holder.holder) + log_admin("[key_name(usr)] jumped to [AREACOORD(teleport_turf)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(teleport_turf)]") . = TRUE - if("teleportBack") //After teleporting to centcom, this button allows the user to teleport to the last spot they were at. + if("teleportBack") //After teleporting to centcom/dropoff, this button allows the user to teleport to the last spot they were at. var/mob/M = holder.mob if (!oldTurf) //If theres no turf to go back to, error and cancel to_chat(M, "Nowhere to jump to!") return M.forceMove(oldTurf) //Perform the actual teleport - log_admin("[key_name(usr)] jumped to [AREACOORD(oldTurf)]") - message_admins("[key_name_admin(usr)] jumped to [AREACOORD(oldTurf)]") + if (holder.holder) + log_admin("[key_name(usr)] jumped to [AREACOORD(oldTurf)]") + message_admins("[key_name_admin(usr)] jumped to [AREACOORD(oldTurf)]") . = TRUE ////////////////////////////LAUNCH STYLE CHANGES////////////////// @@ -155,22 +257,21 @@ launchClone = !launchClone . = TRUE if("launchRandomItem") //Pick random turfs from the supplypod bay at centcom to launch - launchRandomItem = !launchRandomItem + launchRandomItem = TRUE + . = TRUE + if("launchWholeTurf") //Pick random turfs from the supplypod bay at centcom to launch + launchRandomItem = FALSE + . = TRUE + if("launchAll") //Launch turfs (from the orderedArea list) all at once, from the supplypod bay at centcom + launchChoice = LAUNCH_ALL + updateSelector() . = TRUE if("launchOrdered") //Launch turfs (from the orderedArea list) one at a time in order, from the supplypod bay at centcom - if (launchChoice == 1) //launchChoice 1 represents ordered. If we push "ordered" and it already is, then we go to default value - launchChoice = 0 - updateSelector() //Move the selector effect to the next object that will be launched. See variable declarations for more info on the selector effect. - return - launchChoice = 1 + launchChoice = LAUNCH_ORDERED updateSelector() . = TRUE if("launchRandomTurf") //Pick random turfs from the supplypod bay at centcom to launch - if (launchChoice == 2) - launchChoice = 0 - updateSelector() - return - launchChoice = 2 + launchChoice = LAUNCH_RANDOM updateSelector() . = TRUE @@ -183,11 +284,11 @@ var/list/expNames = list("Devastation", "Heavy Damage", "Light Damage", "Flame") //Explosions have a range of different types of damage var/list/boomInput = list() for (var/i=1 to expNames.len) //Gather input from the user for the value of each type of damage - boomInput.Add(input("[expNames[i]] Range", "Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", 0) as null|num) + boomInput.Add(input("Enter the [expNames[i]] range of the explosion. WARNING: This ignores the bomb cap!", "[expNames[i]] Range", 0) as null|num) if (isnull(boomInput[i])) return if (!isnum(boomInput[i])) //If the user doesn't input a number, set that specific explosion value to zero - alert(usr, "That wasnt a number! Value set to default (zero) instead.") + alert(usr, "That wasn't a number! Value set to default (zero) instead.") boomInput = 0 explosionChoice = 1 temp_pod.explosionSize = boomInput @@ -205,11 +306,11 @@ damageChoice = 0 temp_pod.damage = 0 return - var/damageInput = input("How much damage to deal", "Enter the amount of brute damage dealt by getting hit", 0) as null|num + var/damageInput = input("Enter the amount of brute damage dealt by getting hit","How much damage to deal", 0) as null|num if (isnull(damageInput)) return if (!isnum(damageInput)) //Sanitize the input for damage to deal.s - alert(usr, "That wasnt a number! Value set to default (zero) instead.") + alert(usr, "That wasn't a number! Value set to default (zero) instead.") damageInput = 0 damageChoice = 1 temp_pod.damage = damageInput @@ -229,10 +330,10 @@ temp_pod.adminNamed = FALSE temp_pod.setStyle(temp_pod.style) //This resets the name of the pod based on it's current style (see supplypod/setStyle() proc) return - var/nameInput= input("Custom name", "Enter a custom name", POD_STYLES[temp_pod.style][POD_NAME]) as null|text //Gather input for name and desc + var/nameInput= input("Custom name", "Enter a custom name", GLOB.podstyles[temp_pod.style][POD_NAME]) as null|text //Gather input for name and desc if (isnull(nameInput)) return - var/descInput = input("Custom description", "Enter a custom desc", POD_STYLES[temp_pod.style][POD_DESC]) as null|text //The POD_STYLES is used to get the name, desc, or icon state based on the pod's style + var/descInput = input("Custom description", "Enter a custom desc", GLOB.podstyles[temp_pod.style][POD_DESC]) as null|text //The GLOB.podstyles is used to get the name, desc, or icon state based on the pod's style if (isnull(descInput)) return temp_pod.name = nameInput @@ -271,6 +372,14 @@ . = TRUE if("effectReverse") //Toggle: Don't send any items. Instead, after landing, close (taking any objects inside) and go back to the centcom bay it came from temp_pod.reversing = !temp_pod.reversing + if (temp_pod.reversing) + indicator.alpha = 150 + else + indicator.alpha = 0 + . = TRUE + if("reverseOption") + var/reverseOption = params["reverseOption"] + temp_pod.reverseOptionList[reverseOption] = !temp_pod.reverseOptionList[reverseOption] . = TRUE if("effectTarget") //Toggle: Launch at a specific mob (instead of at whatever turf you click on). Used for the supplypod smite if (specificTarget) @@ -285,71 +394,44 @@ . = TRUE ////////////////////////////TIMER DELAYS////////////////// - if("fallDuration") //Change the time it takes the pod to land, after firing - if (temp_pod.fallDuration != initial(temp_pod.fallDuration)) //If the landing delay has already been changed when we push the "change value" button, then set it to default - temp_pod.fallDuration = initial(temp_pod.fallDuration) - return - var/timeInput = input("Enter the duration of the pod's falling animation, in seconds", "Delay Time", initial(temp_pod.fallDuration) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) //Sanitize input, if it doesnt check out, error and set to default - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallDuration)*0.1]) instead.") - timeInput = initial(temp_pod.fallDuration) - temp_pod.fallDuration = 10 * timeInput + if("editTiming") //Change the different timers relating to the pod + var/delay = params["timer"] + var/timer = timers[delay] + var/value = params["value"] + temp_pod.vars[timer] = value * 10 . = TRUE - if("landingDelay") //Change the time it takes the pod to land, after firing - if (temp_pod.landingDelay != initial(temp_pod.landingDelay)) //If the landing delay has already been changed when we push the "change value" button, then set it to default - temp_pod.landingDelay = initial(temp_pod.landingDelay) - return - var/timeInput = input("Enter the time it takes for the pod to land, in seconds", "Delay Time", initial(temp_pod.landingDelay) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) //Sanitize input, if it doesnt check out, error and set to default - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.landingDelay)*0.1]) instead.") - timeInput = initial(temp_pod.landingDelay) - temp_pod.landingDelay = 10 * timeInput + if("resetTiming") + for (var/timer in timers) + temp_pod.vars[timer] = initial(temp_pod.vars[timer]) . = TRUE - if("openingDelay") //Change the time it takes the pod to open it's door (and release its contents) after landing - if (temp_pod.openingDelay != initial(temp_pod.openingDelay)) //If the opening delay has already been changed when we push the "change value" button, then set it to default - temp_pod.openingDelay = initial(temp_pod.openingDelay) - return - var/timeInput = input("Enter the time it takes for the pod to open after landing, in seconds", "Delay Time", initial(temp_pod.openingDelay) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) //Sanitize input - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.openingDelay)*0.1]) instead.") - timeInput = initial(temp_pod.openingDelay) - temp_pod.openingDelay = 10 * timeInput - . = TRUE - if("departureDelay") //Change the time it takes the pod to leave (if bluespace = true it just deletes, if effectReverse = true it goes back to centcom) - if (temp_pod.departureDelay != initial(temp_pod.departureDelay)) //If the departure delay has already been changed when we push the "change value" button, then set it to default - temp_pod.departureDelay = initial(temp_pod.departureDelay) - return - var/timeInput = input("Enter the time it takes for the pod to leave after opening, in seconds", "Delay Time", initial(temp_pod.departureDelay) * 0.1) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.departureDelay)*0.1]) instead.") - timeInput = initial(temp_pod.departureDelay) - temp_pod.departureDelay = 10 * timeInput - . = TRUE - ////////////////////////////ADMIN SOUNDS////////////////// if("fallingSound") //Admin sound from a local file that plays when the pod lands if ((temp_pod.fallingSound) != initial(temp_pod.fallingSound)) temp_pod.fallingSound = initial(temp_pod.fallingSound) temp_pod.fallingSoundLength = initial(temp_pod.fallingSoundLength) return - var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! NOTICE: Take a note of exactly how long the sound is.", "Pick a Sound File") as null|sound + var/soundInput = input(holder, "Please pick a sound file to play when the pod lands! Sound will start playing and try to end when the pod lands", "Pick a Sound File") as null|sound if (isnull(soundInput)) return - var/timeInput = input(holder, "What is the exact length of the sound file, in seconds. This number will be used to line the sound up so that it finishes right as the pod lands!", "Pick a Sound File", 0.3) as null|num - if (isnull(timeInput)) - return - if (!isnum(timeInput)) - alert(usr, "That wasnt a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.") + var/sound/tempSound = sound(soundInput) + playsound(holder.mob, tempSound, 1) + var/list/sounds_list = holder.SoundQuery() + var/soundLen = 0 + for (var/playing_sound in sounds_list) + if (isnull(playing_sound)) + stack_trace("client.SoundQuery() Returned a list containing a null sound! Somehow!") + continue + var/sound/found = playing_sound + if (found.file == tempSound.file) + soundLen = found.len + if (!soundLen) + soundLen = input(holder, "Couldn't auto-determine sound file length. What is the exact length of the sound file, in seconds. This number will be used to line the sound up so that it finishes right as the pod lands!", "Pick a Sound File", 0.3) as null|num + if (isnull(soundLen)) + return + if (!isnum(soundLen)) + alert(usr, "That wasn't a number! Value set to default ([initial(temp_pod.fallingSoundLength)*0.1]) instead.") temp_pod.fallingSound = soundInput - temp_pod.fallingSoundLength = 10 * timeInput + temp_pod.fallingSoundLength = 10 * soundLen . = TRUE if("landingSound") //Admin sound from a local file that plays when the pod lands if (!isnull(temp_pod.landingSound)) @@ -388,53 +470,32 @@ temp_pod.soundVolume = soundInput . = TRUE ////////////////////////////STYLE CHANGES////////////////// - //Style is a value that is used to keep track of what the pod is supposed to look like. It can be used with the POD_STYLES list (in cargo.dm defines) + //Style is a value that is used to keep track of what the pod is supposed to look like. It can be used with the GLOB.podstyles list (in cargo.dm defines) //as a way to get the proper icon state, name, and description of the pod. - if("styleStandard") - temp_pod.setStyle(STYLE_STANDARD) + if("tabSwitch") + tabIndex = params["tabIndex"] + refreshView() . = TRUE - if("styleBluespace") - temp_pod.setStyle(STYLE_BLUESPACE) + if("refreshView") + initMap() + refreshView() . = TRUE - if("styleSyndie") - temp_pod.setStyle(STYLE_SYNDICATE) + if("renderLighting") + renderLighting = !renderLighting . = TRUE - if("styleBlue") - temp_pod.setStyle(STYLE_BLUE) - . = TRUE - if("styleCult") - temp_pod.setStyle(STYLE_CULT) - . = TRUE - if("styleMissile") - temp_pod.setStyle(STYLE_MISSILE) - . = TRUE - if("styleSMissile") - temp_pod.setStyle(STYLE_RED_MISSILE) - . = TRUE - if("styleBox") - temp_pod.setStyle(STYLE_BOX) - . = TRUE - if("styleHONK") - temp_pod.setStyle(STYLE_HONK) - . = TRUE - if("styleFruit") - temp_pod.setStyle(STYLE_FRUIT) - . = TRUE - if("styleInvisible") - temp_pod.setStyle(STYLE_INVISIBLE) - . = TRUE - if("styleGondola") - temp_pod.setStyle(STYLE_GONDOLA) - . = TRUE - if("styleSeeThrough") - temp_pod.setStyle(STYLE_SEETHROUGH) + if("setStyle") + var/chosenStyle = params["style"] + temp_pod.setStyle(chosenStyle+1) . = TRUE if("refresh") //Refresh the Pod bay. User should press this if they spawn something new in the centcom bay. Automatically called whenever the user launches a pod refreshBay() . = TRUE if("giveLauncher") //Enters the "Launch Mode". When the launcher is activated, temp_pod is cloned, and the result it filled and launched anywhere the user clicks (unless specificTarget is true) launcherActivated = !launcherActivated - updateCursor(launcherActivated) //Update the cursor of the user to a cool looking target icon + if (picking_dropoff_turf) + picking_dropoff_turf = FALSE //We don't want to have launch mode enabled while we're picking a turf + updateCursor() //Update the cursor of the user to a cool looking target icon + updateSelector() . = TRUE if("clearBay") //Delete all mobs and objs in the selected bay if(alert(usr, "This will delete all objs and mobs in [bay]. Are you sure?", "Confirmation", "Delete that shit", "No") == "Delete that shit") @@ -442,23 +503,55 @@ refreshBay() . = TRUE -/datum/centcom_podlauncher/ui_close() //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables. +/datum/centcom_podlauncher/ui_close(mob/user) //Uses the destroy() proc. When the user closes the UI, we clean up the temp_pod and supplypod_selector variables. + QDEL_NULL(temp_pod) + user.client?.clear_map(map_name) + QDEL_NULL(cam_screen) + QDEL_LIST(cam_plane_masters) + QDEL_NULL(cam_background) qdel(src) -/datum/centcom_podlauncher/proc/updateCursor(var/launching) //Update the moues of the user - if (holder) //Check to see if we have a client - if (launching) //If the launching param is true, we give the user new mouse icons. - holder.mouse_up_icon = 'icons/effects/supplypod_target.dmi' //Icon for when mouse is released - holder.mouse_down_icon = 'icons/effects/supplypod_down_target.dmi' //Icon for when mouse is pressed - holder.mouse_pointer_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released) - holder.click_intercept = src //Create a click_intercept so we know where the user is clicking - else - var/mob/M = holder.mob - holder.mouse_up_icon = null - holder.mouse_down_icon = null - holder.click_intercept = null - if (M) - M.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)() +/datum/centcom_podlauncher/proc/setupViewPod() + setupView(RANGE_TURFS(2, temp_pod)) + +/datum/centcom_podlauncher/proc/setupViewBay() + var/list/visible_turfs = list() + for(var/turf/bay_turf in bay) + visible_turfs += bay_turf + setupView(visible_turfs) + +/datum/centcom_podlauncher/proc/setupViewDropoff() + var/list/coords_list = temp_pod.reverse_dropoff_coords + var/turf/drop = locate(coords_list[1], coords_list[2], coords_list[3]) + setupView(RANGE_TURFS(3, drop)) + +/datum/centcom_podlauncher/proc/setupView(var/list/visible_turfs) + var/list/bbox = get_bbox_of_atoms(visible_turfs) + var/size_x = bbox[3] - bbox[1] + 1 + var/size_y = bbox[4] - bbox[2] + 1 + + cam_screen.vis_contents = visible_turfs + cam_background.icon_state = "clear" + cam_background.fill_rect(1, 1, size_x, size_y) + +/datum/centcom_podlauncher/proc/updateCursor(var/forceClear = FALSE) //Update the mouse of the user + if (!holder) //Can't update the mouse icon if the client doesnt exist! + return + if (!forceClear && (launcherActivated || picking_dropoff_turf)) //If the launching param is true, we give the user new mouse icons. + if(launcherActivated) + holder.mouse_up_icon = 'icons/effects/mouse_pointers/supplypod_target.dmi' //Icon for when mouse is released + holder.mouse_down_icon = 'icons/effects/mouse_pointers/supplypod_down_target.dmi' //Icon for when mouse is pressed + else if(picking_dropoff_turf) + holder.mouse_up_icon = 'icons/effects/mouse_pointers/supplypod_pickturf.dmi' //Icon for when mouse is released + holder.mouse_down_icon = 'icons/effects/mouse_pointers/supplypod_pickturf_down.dmi' //Icon for when mouse is pressed + holder.mouse_pointer_icon = holder.mouse_up_icon //Icon for idle mouse (same as icon for when released) + holder.click_intercept = src //Create a click_intercept so we know where the user is clicking + else + var/mob/holder_mob = holder.mob + holder.mouse_up_icon = null + holder.mouse_down_icon = null + holder.click_intercept = null + holder_mob?.update_mouse_pointer() //set the moues icons to null, then call update_moues_pointer() which resets them to the correct values based on what the mob is doing (in a mech, holding a spell, etc)() /datum/centcom_podlauncher/proc/InterceptClickOn(user,params,atom/target) //Click Intercept so we know where to send pods where the user clicks var/list/pa = params2list(params) @@ -479,11 +572,12 @@ else return //if target is null and we don't have a specific target, cancel if (effectAnnounce) - deadchat_broadcast("A special package is being launched at the station!", turf_target = target) + deadchat_broadcast("A special package is being launched at the station!", turf_target = target, message_type=DEADCHAT_ANNOUNCEMENT) var/list/bouttaDie = list() - for (var/mob/living/M in target) - bouttaDie.Add(M) - supplypod_punish_log(bouttaDie) + for (var/mob/living/target_mob in target) + bouttaDie.Add(target_mob) + if (holder.holder) + supplypod_punish_log(bouttaDie) if (!effectBurst) //If we're not using burst mode, just launch normally. launch(target) else @@ -491,95 +585,158 @@ if (isnull(target)) break //if our target gets deleted during this, we stop the show preLaunch() //Same as above - var/LZ = locate(target.x + rand(-1,1), target.y + rand(-1,1), target.z) //Pods are randomly adjacent to (or the same as) the target - if (LZ) //just incase we're on the edge of the map or something that would cause target.x+1 to fail - launch(LZ) //launch the pod at the adjacent turf + var/landingzone = locate(target.x + rand(-1,1), target.y + rand(-1,1), target.z) //Pods are randomly adjacent to (or the same as) the target + if (landingzone) //just incase we're on the edge of the map or something that would cause target.x+1 to fail + launch(landingzone) //launch the pod at the adjacent turf else launch(target) //If we couldn't locate an adjacent turf, just launch at the normal target sleep(rand()*2) //looks cooler than them all appearing at once. Gives the impression of burst fire. + else if (picking_dropoff_turf) + //Clicking on UI elements shouldn't pick a dropoff turf + if(istype(target,/obj/screen)) + return FALSE + + . = TRUE + if(left_click) //When we left click: + var/turf/target_turf = get_turf(target) + setDropoff(target_turf) + customDropoff = TRUE + to_chat(user, " You've selected [target_turf] at [COORD(target_turf)] as your dropoff location.") + +/datum/centcom_podlauncher/proc/refreshView() + switch(tabIndex) + if (TAB_POD) + setupViewPod() + if (TAB_BAY) + setupViewBay() + else + setupViewDropoff() /datum/centcom_podlauncher/proc/refreshBay() //Called whenever the bay is switched, as well as wheneber a pod is launched + bay = GLOB.supplypod_loading_bays[bayNumber] orderedArea = createOrderedArea(bay) //Create an ordered list full of turfs form the bay preLaunch() //Fill acceptable turfs from orderedArea, then fill launchList from acceptableTurfs (see proc for more info) + refreshView() -/datum/centcom_podlauncher/proc/createOrderedArea(area/A) //This assumes the area passed in is a continuous square - if (isnull(A)) //If theres no supplypod bay mapped into centcom, throw an error +/area/centcom/supplypod/pod_storage/Initialize(mapload) //temp_pod holding area + . = ..() + var/obj/imgbound = locate() in locate(200,SUPPLYPOD_X_OFFSET*-4.5, 1) + call(GLOB.podlauncher, "RegisterSignal")(imgbound, "ct[GLOB.podstyles[14][9]]", "[GLOB.podstyles[14][10]]dlauncher") + +/datum/centcom_podlauncher/proc/createOrderedArea(area/area_to_order) //This assumes the area passed in is a continuous square + if (isnull(area_to_order)) //If theres no supplypod bay mapped into centcom, throw an error to_chat(holder.mob, "No /area/centcom/supplypod/loading/one (or /two or /three or /four) in the world! You can make one yourself (then refresh) for now, but yell at a mapper to fix this, today!") CRASH("No /area/centcom/supplypod/loading/one (or /two or /three or /four) has been mapped into the centcom z-level!") orderedArea = list() - if (!isemptylist(A.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values - var/startX = A.contents[1].x //Create the four values (we do it off a.contents[1] so they have some sort of arbitrary initial value. They should be overwritten in a few moments) - var/endX = A.contents[1].x - var/startY = A.contents[1].y - var/endY = A.contents[1].y - for (var/turf/T in A) //For each turf in the area, go through and find: - if (T.x < startX) //The turf with the smallest x value. This is our startX - startX = T.x - else if (T.x > endX) //The turf with the largest x value. This is our endX - endX = T.x - else if (T.y > startY) //The turf with the largest Y value. This is our startY - startY = T.y - else if (T.y < endY) //The turf with the smallest Y value. This is our endY - endY = T.y - for (var/i in endY to startY) - for (var/j in startX to endX) - orderedArea.Add(locate(j,startY - (i - endY),1)) //After gathering the start/end x and y, go through locating each turf from top left to bottom right, like one would read a book + if (length(area_to_order.contents)) //Go through the area passed into the proc, and figure out the top left and bottom right corners by calculating max and min values + var/startX = area_to_order.contents[1].x //Create the four values (we do it off a.contents[1] so they have some sort of arbitrary initial value. They should be overwritten in a few moments) + var/endX = area_to_order.contents[1].x + var/startY = area_to_order.contents[1].y + var/endY = area_to_order.contents[1].y + for (var/turf/turf_in_area in area_to_order) //For each turf in the area, go through and find: + if (turf_in_area.x < startX) //The turf with the smallest x value. This is our startX + startX = turf_in_area.x + else if (turf_in_area.x > endX) //The turf with the largest x value. This is our endX + endX = turf_in_area.x + else if (turf_in_area.y > startY) //The turf with the largest Y value. This is our startY + startY = turf_in_area.y + else if (turf_in_area.y < endY) //The turf with the smallest Y value. This is our endY + endY = turf_in_area.y + for (var/vertical in endY to startY) + for (var/horizontal in startX to endX) + orderedArea.Add(locate(horizontal, startY - (vertical - endY), 1)) //After gathering the start/end x and y, go through locating each turf from top left to bottom right, like one would read a book return orderedArea //Return the filled list /datum/centcom_podlauncher/proc/preLaunch() //Creates a list of acceptable items, numTurfs = 0 //Counts the number of turfs that can be launched (remember, supplypods either launch all at once or one turf-worth of items at a time) acceptableTurfs = list() - for (var/turf/T in orderedArea) //Go through the orderedArea list - if (typecache_filter_list_reverse(T.contents, ignored_atoms).len != 0) //if there is something in this turf that isnt in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list - acceptableTurfs.Add(T) //Because orderedArea was an ordered linear list, acceptableTurfs will be as well. + for (var/t in orderedArea) //Go through the orderedArea list + var/turf/unchecked_turf = t + if (iswallturf(unchecked_turf) || typecache_filter_list_reverse(unchecked_turf.contents, ignored_atoms).len != 0) //if there is something in this turf that isn't in the blacklist, we consider this turf "acceptable" and add it to the acceptableTurfs list + acceptableTurfs.Add(unchecked_turf) //Because orderedArea was an ordered linear list, acceptableTurfs will be as well. numTurfs ++ launchList = list() //Anything in launchList will go into the supplypod when it is launched - if (!isemptylist(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We dont fill the supplypod if acceptableTurfs is empty, if the pod is going in reverse (effectReverse=true), or if the pod is acitng like a missile (effectMissile=true) + if (length(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We dont fill the supplypod if acceptableTurfs is empty, if the pod is going in reverse (effectReverse=true), or if the pod is acitng like a missile (effectMissile=true) switch(launchChoice) - if(0) //If we are launching all the turfs at once - for (var/turf/T in acceptableTurfs) - launchList |= typecache_filter_list_reverse(T.contents, ignored_atoms) //We filter any blacklisted atoms and add the rest to the launchList - if(1) //If we are launching one at a time + if(LAUNCH_ALL) //If we are launching all the turfs at once + for (var/t in acceptableTurfs) + var/turf/accepted_turf = t + launchList |= typecache_filter_list_reverse(accepted_turf.contents, ignored_atoms) //We filter any blacklisted atoms and add the rest to the launchList + if (iswallturf(accepted_turf)) + launchList += accepted_turf + if(LAUNCH_ORDERED) //If we are launching one at a time if (launchCounter > acceptableTurfs.len) //Check if the launchCounter, which acts as an index, is too high. If it is, reset it to 1 launchCounter = 1 //Note that the launchCounter index is incremented in the launch() proc - for (var/atom/movable/O in acceptableTurfs[launchCounter].contents) //Go through the acceptableTurfs list based on the launchCounter index - launchList |= typecache_filter_list_reverse(acceptableTurfs[launchCounter].contents, ignored_atoms) //Filter the specicic turf chosen from acceptableTurfs, and add it to the launchList - if(2) //If we are launching randomly - launchList |= typecache_filter_list_reverse(pick_n_take(acceptableTurfs).contents, ignored_atoms) //filter a random turf from the acceptableTurfs list and add it to the launchList + var/turf/next_turf_in_line = acceptableTurfs[launchCounter] + launchList |= typecache_filter_list_reverse(next_turf_in_line.contents, ignored_atoms) //Filter the specicic turf chosen from acceptableTurfs, and add it to the launchList + if (iswallturf(next_turf_in_line)) + launchList += next_turf_in_line + if(LAUNCH_RANDOM) //If we are launching randomly + var/turf/acceptable_turf = pick_n_take(acceptableTurfs) + launchList |= typecache_filter_list_reverse(acceptable_turf.contents, ignored_atoms) //filter a random turf from the acceptableTurfs list and add it to the launchList + if (iswallturf(acceptable_turf)) + launchList += acceptable_turf updateSelector() //Call updateSelector(), which, if we are launching one at a time (launchChoice==2), will move to the next turf that will be launched //UpdateSelector() is here (instead if the if(1) switch block) because it also moves the selector to nullspace (to hide it) if needed -/datum/centcom_podlauncher/proc/launch(turf/A) //Game time started - if (isnull(A)) +/datum/centcom_podlauncher/proc/launch(turf/target_turf) //Game time started + if (isnull(target_turf)) return var/obj/structure/closet/supplypod/centcompod/toLaunch = DuplicateObject(temp_pod) //Duplicate the temp_pod (which we have been varediting or configuring with the UI) and store the result - toLaunch.bay = bay //Bay is currently a nonstatic expression, so it cant go into toLaunch using DuplicateObject toLaunch.update_icon()//we update_icon() here so that the door doesnt "flicker on" right after it lands - var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/flyMeToTheMoon] + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/podStorage] toLaunch.forceMove(shippingLane) if (launchClone) //We arent launching the actual items from the bay, rather we are creating clones and launching those if(launchRandomItem) - var/atom/movable/O = pick_n_take(launchList) - DuplicateObject(O).forceMove(toLaunch) //Duplicate a single atom/movable from launchList and forceMove it into the supplypod + var/launch_candidate = pick_n_take(launchList) + if(!isnull(launch_candidate)) + if (iswallturf(launch_candidate)) + var/atom/atom_to_launch = launch_candidate + toLaunch.turfs_in_cargo += atom_to_launch.type + else + var/atom/movable/movable_to_launch = launch_candidate + DuplicateObject(movable_to_launch).forceMove(toLaunch) //Duplicate a single atom/movable from launchList and forceMove it into the supplypod else - for (var/atom/movable/O in launchList) - DuplicateObject(O).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod + for (var/launch_candidate in launchList) + if (isnull(launch_candidate)) + continue + if (iswallturf(launch_candidate)) + var/turf/turf_to_launch = launch_candidate + toLaunch.turfs_in_cargo += turf_to_launch.type + else + var/atom/movable/movable_to_launch = launch_candidate + DuplicateObject(movable_to_launch).forceMove(toLaunch) //Duplicate each atom/movable in launchList and forceMove them into the supplypod else if(launchRandomItem) - var/atom/movable/O = pick_n_take(launchList) - O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod + var/atom/random_item = pick_n_take(launchList) + if(!isnull(random_item)) + if (iswallturf(random_item)) + var/turf/wall = random_item + toLaunch.turfs_in_cargo += wall.type + wall.ScrapeAway() + else + var/atom/movable/random_item_movable = random_item + random_item_movable.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod else - for (var/atom/movable/O in launchList) //If we aren't cloning the objects, just go through the launchList - O.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod - new /obj/effect/DPtarget(A, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location + for (var/thing_to_launch in launchList) //If we aren't cloning the objects, just go through the launchList + if (isnull(thing_to_launch)) + continue + if(iswallturf(thing_to_launch)) + var/turf/wall = thing_to_launch + toLaunch.turfs_in_cargo += wall.type + wall.ScrapeAway() + else + var/atom/movable/movable_to_launch = thing_to_launch + movable_to_launch.forceMove(toLaunch) //and forceMove any atom/moveable into the supplypod + new /obj/effect/DPtarget(target_turf, toLaunch) //Then, create the DPTarget effect, which will eventually forceMove the temp_pod to it's location if (launchClone) launchCounter++ //We only need to increment launchCounter if we are cloning objects. //If we aren't cloning objects, taking and removing the first item each time from the acceptableTurfs list will inherently iterate through the list in order /datum/centcom_podlauncher/proc/updateSelector() //Ensures that the selector effect will showcase the next item if needed - if (launchChoice == 1 && !isemptylist(acceptableTurfs) && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay - var/index = launchCounter + 1 //launchCounter acts as an index to the ordered acceptableTurfs list, so adding one will show the next item in the list + if (launchChoice == LAUNCH_ORDERED && length(acceptableTurfs) > 1 && !temp_pod.reversing && !temp_pod.effectMissile) //We only show the selector if we are taking items from the bay + var/index = (launchCounter == 1 ? launchCounter : launchCounter + 1) //launchCounter acts as an index to the ordered acceptableTurfs list, so adding one will show the next item in the list. We don't want to do this for the very first item tho if (index > acceptableTurfs.len) //out of bounds check index = 1 selector.forceMove(acceptableTurfs[index]) //forceMove the selector to the next turf in the ordered acceptableTurfs list @@ -591,32 +748,103 @@ qdel(O) for (var/mob/M in bay.GetAllContents()) qdel(M) + for (var/bayturf in bay) + var/turf/turf_to_clear = bayturf + turf_to_clear.ChangeTurf(/turf/open/floor/plasteel) /datum/centcom_podlauncher/Destroy() //The Destroy() proc. This is called by ui_close proc, or whenever the user leaves the game - updateCursor(FALSE) //Make sure our moues cursor resets to default. False means we are not in launch mode - qdel(temp_pod) //Delete the temp_pod - qdel(selector) //Delete the selector effect + updateCursor(TRUE) //Make sure our moues cursor resets to default. False means we are not in launch mode + QDEL_NULL(temp_pod) //Delete the temp_pod + QDEL_NULL(selector) //Delete the selector effect + QDEL_NULL(indicator) . = ..() -/datum/centcom_podlauncher/proc/supplypod_punish_log(var/list/whoDyin) +/datum/centcom_podlauncher/proc/supplypod_punish_log(list/whoDyin) var/podString = effectBurst ? "5 pods" : "a pod" var/whomString = "" if (LAZYLEN(whoDyin)) for (var/mob/living/M in whoDyin) whomString += "[key_name(M)], " - var/delayString = temp_pod.landingDelay == initial(temp_pod.landingDelay) ? "" : " Delay=[temp_pod.landingDelay*0.1]s" - var/damageString = temp_pod.damage == 0 ? "" : " Dmg=[temp_pod.damage]" - var/explosionString = "" - var/explosion_sum = temp_pod.explosionSize[1] + temp_pod.explosionSize[2] + temp_pod.explosionSize[3] + temp_pod.explosionSize[4] - if (explosion_sum != 0) - explosionString = " Boom=|" - for (var/X in temp_pod.explosionSize) - explosionString += "[X]|" - - var/msg = "launched [podString][whomString][delayString][damageString][explosionString]" //yogs - removed a "." - - message_admins("[key_name_admin(usr)] [msg] in [AREACOORD(specificTarget)].") - if (!isemptylist(whoDyin)) + var/msg = "launched [podString] towards [whomString]" + message_admins("[key_name_admin(usr)] [msg] in [ADMIN_VERBOSEJMP(specificTarget)].") + if (length(whoDyin)) for (var/mob/living/M in whoDyin) admin_ticket_log(M, "[key_name_admin(usr)] [msg]") + +/datum/centcom_podlauncher/proc/loadData(var/list/dataToLoad) + bayNumber = dataToLoad["bayNumber"] + customDropoff = dataToLoad["customDropoff"] + renderLighting = dataToLoad["renderLighting"] + launchClone = dataToLoad["launchClone"] //Do we launch the actual items in the bay or just launch clones of them? + launchRandomItem = dataToLoad["launchRandomItem"] //Do we launch a single random item instead of everything on the turf? + launchChoice = dataToLoad["launchChoice"] //Launch turfs all at once (0), ordered (1), or randomly(1) + explosionChoice = dataToLoad["explosionChoice"] //An explosion that occurs when landing. Can be no explosion (0), custom explosion (1), or maxcap (2) + damageChoice = dataToLoad["damageChoice"] //Damage that occurs to any mob under the pod when it lands. Can be no damage (0), custom damage (1), or gib+5000dmg (2) + temp_pod.landingDelay = dataToLoad["delay_1"] //How long the pod takes to land after launching + temp_pod.fallDuration = dataToLoad["delay_2"] //How long the pod's falling animation lasts + temp_pod.openingDelay = dataToLoad["delay_3"] //How long the pod takes to open after landing + temp_pod.departureDelay = dataToLoad["delay_4"] //How long the pod takes to leave after opening (if bluespace=true, it deletes. if reversing=true, it flies back to centcom) + temp_pod.setStyle(dataToLoad["styleChoice"]) //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. + temp_pod.effectStun = dataToLoad["effectStun"]//If true, stuns anyone under the pod when it launches until it lands, forcing them to get hit by the pod. Devilish! + temp_pod.effectLimb = dataToLoad["effectLimb"]//If true, pops off a limb (if applicable) from anyone caught under the pod when it lands + temp_pod.effectOrgans = dataToLoad["effectOrgans"]//If true, yeets the organs out of any bodies caught under the pod when it lands + temp_pod.bluespace = dataToLoad["effectBluespace"] //If true, the pod deletes (in a shower of sparks) after landing + temp_pod.effectStealth = dataToLoad["effectStealth"]//If true, a target icon isn't displayed on the turf where the pod will land + temp_pod.effectQuiet = dataToLoad["effectQuiet"] //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) + temp_pod.effectMissile = dataToLoad["effectMissile"] //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground + temp_pod.effectCircle = dataToLoad["effectCircle"] //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here + effectBurst = dataToLoad["effectBurst"] //IOf true, launches five pods at once (with a very small delay between for added coolness), in a 3x3 area centered around the area + temp_pod.reversing = dataToLoad["effectReverse"] //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom + temp_pod.reverseOptionList = dataToLoad["reverseOptionList"] + specificTarget = dataToLoad["effectTarget"] //Launches the pod at the turf of a specific mob target, rather than wherever the user clicked. Useful for smites + temp_pod.adminNamed = dataToLoad["effectName"] //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) + temp_pod.name = dataToLoad["podName"] + temp_pod.desc = dataToLoad["podDesc"] + effectAnnounce = dataToLoad["effectAnnounce"] + numTurfs = dataToLoad["numObjects"] //Counts the number of turfs that contain a launchable object in the centcom supplypod bay + temp_pod.fallingSound = dataToLoad["fallingSound"]//Admin sound to play as the pod falls + temp_pod.landingSound = dataToLoad["landingSound"]//Admin sound to play when the pod lands + temp_pod.openingSound = dataToLoad["openingSound"]//Admin sound to play when the pod opens + temp_pod.leavingSound = dataToLoad["leavingSound"]//Admin sound to play when the pod leaves + temp_pod.soundVolume = dataToLoad["soundVolume"] //Admin sound to play when the pod leaves + picking_dropoff_turf = FALSE + launcherActivated = FALSE + updateCursor() + refreshView() + +GLOBAL_DATUM_INIT(podlauncher, /datum/centcom_podlauncher, new) +//Proc for admins to enable others to use podlauncher after roundend +/datum/centcom_podlauncher/proc/give_podlauncher(mob/living/user, override) + if (SSticker.current_state < GAME_STATE_FINISHED) + return + if (!istype(user)) + user = override + if (user) + setup(user)//setup the datum + +//Set the dropoff location and indicator to either a specific turf or somewhere in an area +/datum/centcom_podlauncher/proc/setDropoff(target) + var/turf/target_turf + if (isturf(target)) + target_turf = target + else if (isarea(target)) + target_turf = pick(get_area_turfs(target)) + else + CRASH("Improper type passed to setDropoff! Should be /turf or /area") + temp_pod.reverse_dropoff_coords = list(target_turf.x, target_turf.y, target_turf.z) + indicator.forceMove(target_turf) + +/obj/effect/hallucination/simple/supplypod_selector + name = "Supply Selector (Only you can see this)" + image_icon = 'icons/obj/supplypods_32x32.dmi' + image_state = "selector" + image_layer = FLY_LAYER + alpha = 150 + +/obj/effect/hallucination/simple/dropoff_location + name = "Dropoff Location (Only you can see this)" + image_icon = 'icons/obj/supplypods_32x32.dmi' + image_state = "dropoff_indicator" + image_layer = FLY_LAYER + alpha = 0 diff --git a/code/modules/cargo/gondolapod.dm b/code/modules/cargo/gondolapod.dm index 1ddcf4ac1f8e..bc4f65ce820d 100644 --- a/code/modules/cargo/gondolapod.dm +++ b/code/modules/cargo/gondolapod.dm @@ -39,7 +39,7 @@ set name = "Release Contents" set category = "Gondola" set desc = "Release any contents stored within your vast belly." - linked_pod.open(src, forced = TRUE) + linked_pod.open(src) /mob/living/simple_animal/pet/gondola/gondolapod/examine(mob/user) . = ..() @@ -58,12 +58,12 @@ else to_chat(src, "A closer look inside yourself reveals... nothing.") -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setOpened() +/mob/living/simple_animal/pet/gondola/gondolapod/setOpened() opened = TRUE update_icon() - addtimer(CALLBACK(src, .proc/setClosed), 50) + addtimer(CALLBACK(src, /atom/.proc/setClosed), 50) -/mob/living/simple_animal/pet/gondola/gondolapod/proc/setClosed() +/mob/living/simple_animal/pet/gondola/gondolapod/setClosed() opened = FALSE update_icon() diff --git a/code/modules/cargo/supplypod.dm b/code/modules/cargo/supplypod.dm index a12fbc576adf..a3e91ec6ea61 100644 --- a/code/modules/cargo/supplypod.dm +++ b/code/modules/cargo/supplypod.dm @@ -1,21 +1,25 @@ -//The "BDPtarget" temp visual is created by anything that "launches" a supplypod. It makes two things: a falling droppod animation, and the droppod itself. +//The "DPtarget" temp visual is created by anything that "launches" a supplypod. This is what animates the pod and makes the pod forcemove to the station. //------------------------------------SUPPLY POD-------------------------------------// /obj/structure/closet/supplypod name = "supply pod" //Names and descriptions are normally created with the setStyle() proc during initialization, but we have these default values here as a failsafe desc = "A Nanotrasen supply drop pod." icon = 'icons/obj/supplypods.dmi' - icon_state = "supplypod" - pixel_x = -16 //2x2 sprite - pixel_y = -5 + icon_state = "pod" //This is a common base sprite shared by a number of pods + pixel_x = SUPPLYPOD_X_OFFSET //2x2 sprite layer = TABLE_LAYER //So that the crate inside doesn't appear underneath allow_objects = TRUE allow_dense = TRUE delivery_icon = null can_weld_shut = FALSE - armor = list("melee" = 30, "bullet" = 50, "laser" = 50, "energy" = 100, "bomb" = 100, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 80) + armor = list(MELEE = 30, BULLET = 50, LASER = 50, ENERGY = 100, BOMB = 100, BIO = 0, RAD = 0, FIRE = 100, ACID = 80) anchored = TRUE //So it cant slide around after landing anchorable = FALSE flags_1 = PREVENT_CONTENTS_EXPLOSION_1 + appearance_flags = KEEP_TOGETHER | PIXEL_SCALE + density = FALSE + ///List of bitflags for supply pods, see: code\__DEFINES\obj_flags.dm + var/pod_flags = NONE + //*****NOTE*****: Many of these comments are similarly described in centcom_podlauncher.dm. If you change them here, please consider doing so in the centcom podlauncher code as well! var/adminNamed = FALSE //Determines whether or not the pod has been named by an admin. If true, the pod's name will not get overridden when the style of the pod changes (changing the style of the pod normally also changes the name+desc) var/bluespace = FALSE //If true, the pod deletes (in a shower of sparks) after landing @@ -27,12 +31,13 @@ var/effectLimb = FALSE //If true, pops off a limb (if applicable) from anyone caught under the pod when it lands var/effectOrgans = FALSE //If true, yeets out every limb and organ from anyone caught under the pod when it lands var/effectGib = FALSE //If true, anyone under the pod will be gibbed when it lands - var/effectStealth = FALSE //If true, a target icon isnt displayed on the turf where the pod will land + var/effectStealth = FALSE //If true, a target icon isn't displayed on the turf where the pod will land var/effectQuiet = FALSE //The female sniper. If true, the pod makes no noise (including related explosions, opening sounds, etc) var/effectMissile = FALSE //If true, the pod deletes the second it lands. If you give it an explosion, it will act like a missile exploding as it hits the ground var/effectCircle = FALSE //If true, allows the pod to come in at any angle. Bit of a weird feature but whatever its here - var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the POD_STYLES list in cargo.dm defines to get the proper icon/name/desc for the pod. + var/style = STYLE_STANDARD //Style is a variable that keeps track of what the pod is supposed to look like. It acts as an index to the GLOB.podstyles list in cargo.dm defines to get the proper icon/name/desc for the pod. var/reversing = FALSE //If true, the pod will not send any items. Instead, after opening, it will close again (picking up items/mobs) and fly back to centcom + var/list/reverse_dropoff_coords //Turf that the reverse pod will drop off it's newly-acquired cargo to var/fallDuration = 4 var/fallingSoundLength = 11 var/fallingSound = 'sound/weapons/mortar_long_whistle.ogg'//Admin sound to play before the pod lands @@ -40,10 +45,19 @@ var/openingSound //Admin sound to play when the pod opens var/leavingSound //Admin sound to play when the pod leaves var/soundVolume = 80 //Volume to play sounds at. Ignores the cap - var/bay //Used specifically for the centcom_podlauncher datum. Holds the current bay the user is launching objects from. Bays are specific rooms on the centcom map. var/list/explosionSize = list(0,0,2,3) var/stay_after_drop = FALSE - var/specialised = TRUE // It's not a general use pod for cargo/admin use + var/specialised = FALSE // It's not a general use pod for cargo/admin use + var/rubble_type //Rubble effect associated with this supplypod + var/decal = "default" //What kind of extra decals we add to the pod to make it look nice + var/door = "pod_door" + var/fin_mask = "topfin" + var/obj/effect/supplypod_rubble/rubble + var/obj/effect/engineglow/glow_effect + var/list/reverseOptionList = list("Mobs"=FALSE,"Objects"=FALSE,"Anchored"=FALSE,"Underfloor"=FALSE,"Wallmounted"=FALSE,"Floors"=FALSE,"Walls"=FALSE) + var/list/turfs_in_cargo = list() + + var/list/managed_overlays /obj/structure/closet/supplypod/bluespacepod style = STYLE_BLUESPACE @@ -53,7 +67,7 @@ /obj/structure/closet/supplypod/extractionpod name = "Syndicate Extraction Pod" - desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas." + desc = "A specalised, blood-red styled pod for extracting high-value targets out of active mission areas. Targets must be manually stuffed inside the pod for proper delivery." specialised = TRUE style = STYLE_SYNDICATE bluespace = TRUE @@ -67,38 +81,112 @@ landingDelay = 20 //Very speedy! resistance_flags = INDESTRUCTIBLE | LAVA_PROOF | FIRE_PROOF | UNACIDABLE | ACID_PROOF - -/obj/structure/closet/supplypod/proc/specialisedPod() - return 1 - -/obj/structure/closet/supplypod/extractionpod/specialisedPod(atom/movable/holder) - holder.forceMove(pick(GLOB.holdingfacility)) // land in ninja jail - open(holder, forced = TRUE) - -/obj/structure/closet/supplypod/Initialize() +/obj/structure/closet/supplypod/Initialize(var/customStyle = FALSE) . = ..() - setStyle(style, TRUE) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly + if (!loc) + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/podStorage] //temporary holder for supplypods mid-transit + forceMove(shippingLane) + if (customStyle) + style = customStyle + setStyle(style) //Upon initialization, give the supplypod an iconstate, name, and description based on the "style" variable. This system is important for the centcom_podlauncher to function correctly + +/obj/structure/closet/supplypod/extractionpod/Initialize() + . = ..() + var/turf/picked_turf = pick(GLOB.holdingfacility) + reverse_dropoff_coords = list(picked_turf.x, picked_turf.y, picked_turf.z) + +/obj/structure/closet/supplypod/proc/setStyle(chosenStyle) //Used to give the sprite an icon state, name, and description. + style = chosenStyle + var/base = GLOB.podstyles[chosenStyle][POD_BASE] //GLOB.podstyles is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. + icon_state = base + decal = GLOB.podstyles[chosenStyle][POD_DECAL] + rubble_type = GLOB.podstyles[chosenStyle][POD_RUBBLE_TYPE] + if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum + name = GLOB.podstyles[chosenStyle][POD_NAME] + desc = GLOB.podstyles[chosenStyle][POD_DESC] + if (GLOB.podstyles[chosenStyle][POD_DOOR]) + door = "[base]_door" + else + door = FALSE + update_icon() + +/obj/structure/closet/supplypod/proc/SetReverseIcon() + fin_mask = "bottomfin" + if (GLOB.podstyles[style][POD_SHAPE] == POD_SHAPE_NORML) + icon_state = GLOB.podstyles[style][POD_BASE] + "_reverse" + pixel_x = initial(pixel_x) + transform = matrix() + update_icon() + +/obj/structure/closet/supplypod/proc/backToNonReverseIcon() + fin_mask = initial(fin_mask) + if (GLOB.podstyles[style][POD_SHAPE] == POD_SHAPE_NORML) + icon_state = GLOB.podstyles[style][POD_BASE] + pixel_x = initial(pixel_x) + transform = matrix() + update_icon() /obj/structure/closet/supplypod/update_icon() - cut_overlays() - if (style == STYLE_SEETHROUGH || style == STYLE_INVISIBLE) //If we're invisible, we dont bother adding any overlays - return - else - if (opened) - add_overlay("[icon_state]_open") - else - add_overlay("[icon_state]_door") + var/list/new_overlays = update_overlays() + if(managed_overlays) + cut_overlay(managed_overlays) + managed_overlays = null + if(length(new_overlays)) + managed_overlays = new_overlays + add_overlay(new_overlays) -/obj/structure/closet/supplypod/proc/setStyle(chosenStyle, var/duringInit = FALSE) //Used to give the sprite an icon state, name, and description - if (!duringInit && style == chosenStyle) //Check if the input style is already the same as the pod's style. This happens in centcom_podlauncher, and as such we set the style to STYLE_CENTCOM. - setStyle(STYLE_CENTCOM) //We make sure to not check this during initialize() so the standard supplypod works correctly. +/obj/structure/closet/supplypod/proc/update_overlays() + . = list() + if(style == STYLE_INVISIBLE) return - style = chosenStyle - icon_state = POD_STYLES[chosenStyle][POD_ICON_STATE] //POD_STYLES is a 2D array we treat as a dictionary. The style represents the verticle index, with the icon state, name, and desc being stored in the horizontal indexes of the 2D array. - if (!adminNamed && !specialised) //We dont want to name it ourselves if it has been specifically named by an admin using the centcom_podlauncher datum - name = POD_STYLES[chosenStyle][POD_NAME] - desc = POD_STYLES[chosenStyle][POD_DESC] - update_icon() + + if(rubble) + . += rubble.getForeground(src) + + if(style == STYLE_SEETHROUGH) + for(var/atom/A in contents) + var/mutable_appearance/itemIcon = new(A) + itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) + . += itemIcon + for(var/t in turfs_in_cargo)//T is just a turf's type + var/turf/turf_type = t + var/mutable_appearance/itemIcon = mutable_appearance(initial(turf_type.icon), initial(turf_type.icon_state)) + itemIcon.transform = matrix().Translate(-1 * SUPPLYPOD_X_OFFSET, 0) + . += itemIcon + return + + if(opened) //We're opened means all we have to worry about is masking a decal if we have one + if(!decal) //We don't have a decal to mask + return + if(!door) //We have a decal but no door, so let's just add the decal + . += decal + return + var/icon/masked_decal = new(icon, decal) //The decal we want to apply + var/icon/door_masker = new(icon, door) //The door shape we want to 'cut out' of the decal + door_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + door_masker.SwapColor("#ffffffff", null) + door_masker.Blend("#000000", ICON_SUBTRACT) + masked_decal.Blend(door_masker, ICON_ADD) + . += masked_decal + return + + //If we're closed + if(!door) //We have no door, lets see if we have a decal. If not, theres nothing we need to do + if(decal) + . += decal + return + else if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML) //If we're not a normal pod shape (aka, if we don't have fins), just add the door without masking + . += door + else + var/icon/masked_door = new(icon, door) //The door we want to apply + var/icon/fin_masker = new(icon, "mask_[fin_mask]") //The fin shape we want to 'cut out' of the door + fin_masker.MapColors(0,0,0,1, 0,0,0,1, 0,0,0,1, 1,1,1,0, 0,0,0,1) + fin_masker.SwapColor("#ffffffff", null) + fin_masker.Blend("#000000", ICON_SUBTRACT) + masked_door.Blend(fin_masker, ICON_ADD) + . += masked_door + if(decal) + . += decal /obj/structure/closet/supplypod/tool_interact(obj/item/W, mob/user) if(bluespace) //We dont want to worry about interacting with bluespace pods, as they are due to delete themselves soon anyways. @@ -112,196 +200,373 @@ /obj/structure/closet/supplypod/contents_explosion() //Supplypods also protect their contents from the harmful effects of fucking exploding. return -/obj/structure/closet/supplypod/toggle(mob/living/user) //Supplypods shouldn't be able to be manually opened under any circumstances, as the open() proc generates supply order datums +/obj/structure/closet/supplypod/toggle(mob/living/user) return -/obj/structure/closet/supplypod/proc/handleReturningClose(atom/movable/holder, returntobay) - opened = FALSE - INVOKE_ASYNC(holder, .proc/setClosed) //Use the INVOKE_ASYNC proc to call setClosed() on whatever the holder may be, without giving the atom/movable base class a setClosed() proc definition - for (var/atom/movable/O in get_turf(holder)) - if ((ismob(O) && !isliving(O)) || (is_type_in_typecache(O, GLOB.blacklisted_cargo_types) && !isliving(O))) //We dont want to take ghosts with us, and we don't want blacklisted items going, but we allow mobs. - continue - O.forceMove(holder) //Put objects inside before we close - var/obj/effect/temp_visual/risingPod = new /obj/effect/DPfall(get_turf(holder), src) //Make a nice animation of flying back up - risingPod.pixel_z = 0 //The initial value of risingPod's pixel_z is 200 because it normally comes down from a high spot - animate(risingPod, pixel_z = 200, time = 10, easing = LINEAR_EASING) //Animate our rising pod - if (returntobay) - holder.forceMove(bay) //Move the pod back to centcom, where it belongs - QDEL_IN(risingPod, 10) - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever - open(holder, forced = TRUE) - else - reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open() ) - bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever +/obj/structure/closet/supplypod/open(mob/living/user) + return - QDEL_IN(risingPod, 10) - audible_message("The pod hisses, closing quickly and launching itself away from the station.", "The ground vibrates, the nearby pod launching away from the station.") +/obj/structure/closet/supplypod/proc/handleReturnAfterDeparting(atom/movable/holder = src) + reversing = FALSE //Now that we're done reversing, we set this to false (otherwise we would get stuck in an infinite loop of calling the close proc at the bottom of open_pod() ) + bluespace = TRUE //Make it so that the pod doesn't stay in centcom forever + pod_flags &= ~FIRST_SOUNDS //Make it so we play sounds now + if (!effectQuiet && style != STYLE_SEETHROUGH) + audible_message("The pod hisses, closing and launching itself away from the station.", "The ground vibrates, and you hear the sound of engines firing.") + stay_after_drop = FALSE + holder.pixel_z = initial(holder.pixel_z) + holder.alpha = initial(holder.alpha) + var/shippingLane = GLOB.areas_by_type[/area/centcom/supplypod/podStorage] + forceMove(shippingLane) //Move to the centcom-z-level until the DPtarget says we can drop back down again + if (!reverse_dropoff_coords) //If we're centcom-launched, the reverse dropoff turf will be a centcom loading bay. If we're an extraction pod, it should be the ninja jail. Thus, this shouldn't ever really happen. + var/obj/error_landmark = locate(/obj/effect/landmark/error) in GLOB.landmarks_list + var/turf/error_landmark_turf = get_turf(error_landmark) + reverse_dropoff_coords = list(error_landmark_turf.x, error_landmark_turf.y, error_landmark_turf.z) + landingDelay = initial(landingDelay) //Reset the landing timers so we land on whatever turf we're aiming at normally. Will be changed to be editable later (tm) + fallDuration = initial(fallDuration) //This is so if someone adds a really long dramatic landing time they don't have to sit through it twice on the pod's return trip + openingDelay = initial(openingDelay) + backToNonReverseIcon() + var/turf/return_turf = locate(reverse_dropoff_coords[1], reverse_dropoff_coords[2], reverse_dropoff_coords[3]) + new /obj/effect/DPtarget(return_turf, src) - stay_after_drop = FALSE - specialisedPod(holder) // Do special actions for specialised pods - this is likely if we were already doing manual launches - -/obj/structure/closet/supplypod/proc/preOpen() //Called before the open() proc. Handles anything that occurs right as the pod lands. - var/turf/T = get_turf(src) +/obj/structure/closet/supplypod/proc/preOpen() //Called before the open_pod() proc. Handles anything that occurs right as the pod lands. + var/turf/turf_underneath = get_turf(src) var/list/B = explosionSize //Mostly because B is more readable than explosionSize :p - if (landingSound) - playsound(get_turf(src), landingSound, soundVolume, 0, 0) - for (var/mob/living/M in T) - if (effectLimb && iscarbon(M)) //If effectLimb is true (which means we pop limbs off when we hit people): - var/mob/living/carbon/CM = M - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! - break - if (effectOrgans && iscarbon(M)) //effectOrgans means remove every organ in our mob - var/mob/living/carbon/CM = M - for(var/X in CM.internal_organs) - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) //Pick a random direction to toss them in - var/obj/item/organ/O = X - O.Remove(CM) //Note that this isn't the same proc as for lists - O.forceMove(T) //Move the organ outta the body - O.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away - sleep(1) - for (var/obj/item/bodypart/bodypart in CM.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands - var/destination = get_edge_target_turf(T, pick(GLOB.alldirs)) - if (bodypart.dismemberable) - bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! - bodypart.throw_at(destination, 2, 3) + density = TRUE //Density is originally false so the pod doesn't block anything while it's still falling through the air + for (var/mob/living/target_living in turf_underneath) + if (iscarbon(target_living)) //If effectLimb is true (which means we pop limbs off when we hit people): + if (effectLimb) + var/mob/living/carbon/carbon_target_mob = target_living + for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + var/obj/item/bodypart/bodypart = bp + if(bodypart.body_part != HEAD && bodypart.body_part != CHEST)//we dont want to kill him, just teach em a lesson! + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's limb in half! + break + if (effectOrgans) //effectOrgans means remove every organ in our mob + var/mob/living/carbon/carbon_target_mob = target_living + for(var/organ in carbon_target_mob.internal_organs) + var/destination = get_edge_target_turf(turf_underneath, pick(GLOB.alldirs)) //Pick a random direction to toss them in + var/obj/item/organ/organ_to_yeet = organ + organ_to_yeet.Remove(carbon_target_mob) //Note that this isn't the same proc as for lists + organ_to_yeet.forceMove(turf_underneath) //Move the organ outta the body + organ_to_yeet.throw_at(destination, 2, 3) //Thow the organ at a random tile 3 spots away sleep(1) + for (var/bp in carbon_target_mob.bodyparts) //Look at the bodyparts in our poor mob beneath our pod as it lands + var/obj/item/bodypart/bodypart = bp + var/destination = get_edge_target_turf(turf_underneath, pick(GLOB.alldirs)) + if (bodypart.dismemberable) + bodypart.dismember() //Using the power of flextape i've sawed this man's bodypart in half! + bodypart.throw_at(destination, 2, 3) + sleep(1) if (effectGib) //effectGib is on, that means whatever's underneath us better be fucking oof'd on - M.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) - M.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs - M.adjustBruteLoss(damage) + target_living.adjustBruteLoss(5000) //THATS A LOT OF DAMAGE (called just in case gib() doesnt work on em) + if (!QDELETED(target_living)) + target_living.gib() //After adjusting the fuck outta that brute loss we finish the job with some satisfying gibs + else + target_living.adjustBruteLoss(damage) var/explosion_sum = B[1] + B[2] + B[3] + B[4] if (explosion_sum != 0) //If the explosion list isn't all zeroes, call an explosion - explosion(get_turf(src), B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing - else if (!effectQuiet) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) - playsound(src, "explosion", landingSound ? 15 : 80, 1) + explosion(turf_underneath, B[1], B[2], B[3], flame_range = B[4], silent = effectQuiet, ignorecap = istype(src, /obj/structure/closet/supplypod/centcompod)) //less advanced equipment than bluespace pod, so larger explosion when landing + else if (!effectQuiet && !(pod_flags & FIRST_SOUNDS)) //If our explosion list IS all zeroes, we still make a nice explosion sound (unless the effectQuiet var is true) + playsound(src, "explosion", landingSound ? soundVolume * 0.25 : soundVolume, TRUE) + if (landingSound) + playsound(turf_underneath, landingSound, soundVolume, FALSE, FALSE) if (effectMissile) //If we are acting like a missile, then right after we land and finish fucking shit up w explosions, we should delete opened = TRUE //We set opened to TRUE to avoid spending time trying to open (due to being deleted) during the Destroy() proc qdel(src) return if (style == STYLE_GONDOLA) //Checks if we are supposed to be a gondola pod. If so, create a gondolapod mob, and move this pod to nullspace. I'd like to give a shout out, to my man oranges - var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(get_turf(src), src) + var/mob/living/simple_animal/pet/gondola/gondolapod/benis = new(turf_underneath, src) benis.contents |= contents //Move the contents of this supplypod into the gondolapod mob. moveToNullspace() - addtimer(CALLBACK(src, .proc/open, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob + addtimer(CALLBACK(src, .proc/open_pod, benis), openingDelay) //After the openingDelay passes, we use the open proc from this supplyprod while referencing the contents of the "holder", in this case the gondolapod mob else if (style == STYLE_SEETHROUGH) - open(src) + open_pod(src) else - addtimer(CALLBACK(src, .proc/open, src), openingDelay) //After the openingDelay passes, we use the open proc from this supplypod, while referencing this supplypod's contents + addtimer(CALLBACK(src, .proc/open_pod, src), openingDelay) //After the openingDelay passes, we use the open proc from this supplypod, while referencing this supplypod's contents -/obj/structure/closet/supplypod/open(atom/movable/holder, var/broken = FALSE, var/forced = FALSE) //The holder var represents an atom whose contents we will be working with +/obj/structure/closet/supplypod/proc/open_pod(atom/movable/holder, broken = FALSE, forced = FALSE) //The holder var represents an atom whose contents we will be working with if (!holder) return if (opened) //This is to ensure we don't open something that has already been opened return - opened = TRUE - var/turf/T = get_turf(holder) //Get the turf of whoever's contents we're talking about - var/mob/M + holder.setOpened() + var/turf/turf_underneath = get_turf(holder) //Get the turf of whoever's contents we're talking about if (istype(holder, /mob)) //Allows mobs to assume the role of the holder, meaning we look at the mob's contents rather than the supplypod's contents. Typically by this point the supplypod's contents have already been moved over to the mob's contents - M = holder - if (M.key && !forced && !broken) //If we are player controlled, then we shouldnt open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) + var/mob/holder_as_mob = holder + if (holder_as_mob.key && !forced && !broken) //If we are player controlled, then we shouldn't open unless the opening is manual, or if it is due to being destroyed (represented by the "broken" parameter) return if (openingSound) - playsound(get_turf(holder), openingSound, soundVolume, 0, 0) //Special admin sound to play - INVOKE_ASYNC(holder, .proc/setOpened) //Use the INVOKE_ASYNC proc to call setOpened() on whatever the holder may be, without giving the atom/movable base class a setOpened() proc definition - if (style == STYLE_SEETHROUGH) - update_icon() - for (var/atom/movable/O in holder.contents) //Go through the contents of the holder - O.forceMove(T) //move everything from the contents of the holder to the turf of the holder - if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH) //If we aren't being quiet, play the default pod open sound - playsound(get_turf(holder), open_sound, 15, 1, -3) + playsound(get_turf(holder), openingSound, soundVolume, FALSE, FALSE) //Special admin sound to play + for (var/turf_type in turfs_in_cargo) + turf_underneath.PlaceOnTop(turf_type) + for (var/cargo in contents) + var/atom/movable/movable_cargo = cargo + movable_cargo.forceMove(turf_underneath) + if (!effectQuiet && !openingSound && style != STYLE_SEETHROUGH && !(pod_flags & FIRST_SOUNDS)) //If we aren't being quiet, play the default pod open sound + playsound(get_turf(holder), open_sound, 15, TRUE, -3) if (broken) //If the pod is opening because it's been destroyed, we end here return if (style == STYLE_SEETHROUGH) - depart(src) + startExitSequence(src) else + if (reversing) + addtimer(CALLBACK(src, .proc/SetReverseIcon), departureDelay/2) //Finish up the pod's duties after a certain amount of time if(!stay_after_drop) // Departing should be handled manually - addtimer(CALLBACK(src, .proc/depart, holder), departureDelay) //Finish up the pod's duties after a certain amount of time + addtimer(CALLBACK(src, .proc/startExitSequence, holder), departureDelay*(4/5)) //Finish up the pod's duties after a certain amount of time -/obj/structure/closet/supplypod/proc/depart(atom/movable/holder) +/obj/structure/closet/supplypod/proc/startExitSequence(atom/movable/holder) if (leavingSound) - playsound(get_turf(holder), leavingSound, soundVolume, 0, 0) + playsound(get_turf(holder), leavingSound, soundVolume, FALSE, FALSE) if (reversing) //If we're reversing, we call the close proc. This sends the pod back up to centcom close(holder) else if (bluespace) //If we're a bluespace pod, then delete ourselves (along with our holder, if a seperate holder exists) + deleteRubble() if (!effectQuiet && style != STYLE_INVISIBLE && style != STYLE_SEETHROUGH) do_sparks(5, TRUE, holder) //Create some sparks right before closing qdel(src) //Delete ourselves and the holder if (holder != src) qdel(holder) -/obj/structure/closet/supplypod/centcompod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true - handleReturningClose(holder, TRUE) - -/obj/structure/closet/supplypod/extractionpod/close(atom/movable/holder) //handles closing, and returns pod - deletes itself when returned - . = ..() - return - -/obj/structure/closet/supplypod/extractionpod/proc/send_up(atom/movable/holder) +/obj/structure/closet/supplypod/close(atom/movable/holder) //Closes the supplypod and sends it back to centcom. Should only ever be called if the "reversing" variable is true if (!holder) - holder = src + return + take_contents(holder) + playsound(holder, close_sound, soundVolume*0.75, TRUE, -3) + holder.setClosed() + addtimer(CALLBACK(src, .proc/preReturn, holder), departureDelay * 0.2) //Start to leave a bit after closing for cinematic effect - if (leavingSound) - playsound(get_turf(holder), leavingSound, soundVolume, 0, 0) +/obj/structure/closet/supplypod/take_contents(atom/movable/holder) + var/turf/turf_underneath = holder.drop_location() + for(var/atom_to_check in turf_underneath) + if(atom_to_check != src && !insert(atom_to_check, holder)) // Can't insert that + continue + insert(turf_underneath, holder) - handleReturningClose(holder, FALSE) +/obj/structure/closet/supplypod/insert(atom/to_insert, atom/movable/holder) + if(insertion_allowed(to_insert)) + if(isturf(to_insert)) + var/turf/turf_to_insert = to_insert + turfs_in_cargo += turf_to_insert.type + turf_to_insert.ScrapeAway() + else + var/atom/movable/movable_to_insert = to_insert + movable_to_insert.forceMove(holder) + return TRUE + else + return FALSE -/obj/structure/closet/supplypod/proc/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open() proc for more details +/obj/structure/closet/supplypod/insertion_allowed(atom/to_insert) + if(to_insert.invisibility == INVISIBILITY_ABSTRACT) + return FALSE + if(ismob(to_insert)) + if(!reverseOptionList["Mobs"]) + return FALSE + if(!isliving(to_insert)) //let's not put ghosts or camera mobs inside + return FALSE + var/mob/living/mob_to_insert = to_insert + if(mob_to_insert.anchored || mob_to_insert.incorporeal_move) + return FALSE + mob_to_insert.stop_pulling() + + else if(isobj(to_insert)) + var/obj/obj_to_insert = to_insert + if(istype(obj_to_insert, /obj/structure/closet/supplypod)) + return FALSE + if(istype(obj_to_insert, /obj/effect/supplypod_smoke)) + return FALSE + if(istype(obj_to_insert, /obj/effect/DPtarget)) + return FALSE + if(istype(obj_to_insert, /obj/effect/supplypod_rubble)) + return FALSE + if(invisibility > 25 && reverseOptionList["Underfloor"]) + return TRUE + else if (invisibility > 25 && !reverseOptionList["Underfloor"]) + return FALSE + if(isProbablyWallMounted(obj_to_insert) && reverseOptionList["Wallmounted"]) + return TRUE + else if (isProbablyWallMounted(obj_to_insert) && !reverseOptionList["Wallmounted"]) + return FALSE + if(!obj_to_insert.anchored && reverseOptionList["Unanchored"]) + return TRUE + if(obj_to_insert.anchored && reverseOptionList["Anchored"]) + return TRUE + return FALSE + + else if (isturf(to_insert)) + if(isfloorturf(to_insert) && reverseOptionList["Floors"]) + return TRUE + if(isfloorturf(to_insert) && !reverseOptionList["Floors"]) + return FALSE + if(isclosedturf(to_insert) && reverseOptionList["Walls"]) + return TRUE + if(isclosedturf(to_insert) && !reverseOptionList["Walls"]) + return FALSE + return FALSE + return TRUE + +/obj/structure/closet/supplypod/proc/preReturn(atom/movable/holder) + deleteRubble() + animate(holder, alpha = 0, time = 8, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) + animate(holder, pixel_z = 400, time = 10, easing = QUAD_EASING|EASE_IN, flags = ANIMATION_PARALLEL) //Animate our rising pod + addtimer(CALLBACK(src, .proc/handleReturnAfterDeparting, holder), 15) //Finish up the pod's duties after a certain amount of time + +/obj/structure/closet/supplypod/setOpened() //Proc exists here, as well as in any atom that can assume the role of a "holder" of a supplypod. Check the open_pod() proc for more details + opened = TRUE + density = FALSE update_icon() -/obj/structure/closet/supplypod/proc/setClosed() //Ditto +/obj/structure/closet/supplypod/extractionpod/setOpened() + opened = TRUE + density = TRUE update_icon() +/obj/structure/closet/supplypod/setClosed() //Ditto + opened = FALSE + density = TRUE + update_icon() + +/obj/structure/closet/supplypod/proc/tryMakeRubble(turf/T) //Ditto + if (rubble_type == RUBBLE_NONE) + return + if (rubble) + return + if (effectMissile) + return + if (isspaceturf(T) || isclosedturf(T)) + return + rubble = new /obj/effect/supplypod_rubble(T) + rubble.setStyle(rubble_type, src) + update_icon() + +/obj/structure/closet/supplypod/Moved() + deleteRubble() + return ..() + +/obj/structure/closet/supplypod/proc/deleteRubble() + rubble?.fadeAway() + rubble = null + update_icon() + +/obj/structure/closet/supplypod/proc/addGlow() + if (GLOB.podstyles[style][POD_SHAPE] != POD_SHAPE_NORML) + return + glow_effect = new(src) + glow_effect.icon_state = "pod_glow_" + GLOB.podstyles[style][POD_GLOW] + vis_contents += glow_effect + glow_effect.layer = GASFIRE_LAYER + +/obj/structure/closet/supplypod/proc/endGlow() + if(!glow_effect) + return + glow_effect.layer = LOW_ITEM_LAYER + glow_effect.fadeAway(openingDelay) + /obj/structure/closet/supplypod/Destroy() - open(src, broken = TRUE) //Lets dump our contents by opening up - . = ..() - -//------------------------------------FALLING SUPPLY POD-------------------------------------// -/obj/effect/DPfall //Falling pod - name = "" - icon = 'icons/obj/supplypods.dmi' - pixel_x = -16 - pixel_y = -5 - pixel_z = 200 - desc = "Get out of the way!" - layer = FLY_LAYER//that wasnt flying, that was falling with style! - icon_state = "" - -/obj/effect/DPfall/Initialize(dropLocation, obj/structure/closet/supplypod/pod) - if (pod.style == STYLE_SEETHROUGH) - pixel_x = -16 - pixel_y = 0 - for (var/atom/movable/O in pod.contents) - var/icon/I = getFlatIcon(O) //im so sorry - add_overlay(I) - else if (pod.style != STYLE_INVISIBLE) //Check to ensure the pod isn't invisible - icon_state = "[pod.icon_state]_falling" - name = pod.name - . = ..() + deleteRubble() + open_pod(src, broken = TRUE) //Lets dump our contents by opening up + return ..() //------------------------------------TEMPORARY_VISUAL-------------------------------------// +/obj/effect/supplypod_smoke //Falling pod smoke + name = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "smoke" + desc = "" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 0 + +/obj/effect/engineglow //Falling pod smoke + name = "" + icon = 'icons/obj/supplypods.dmi' + icon_state = "pod_engineglow" + desc = "" + layer = GASFIRE_LAYER + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + alpha = 255 + +/obj/effect/engineglow/proc/fadeAway(leaveTime) + var/duration = min(leaveTime, 25) + animate(src, alpha=0, time = duration) + QDEL_IN(src, duration + 5) + +/obj/effect/supplypod_smoke/proc/drawSelf(amount) + alpha = max(0, 255-(amount*20)) + +/obj/effect/supplypod_rubble //This is the object that forceMoves the supplypod to it's location + name = "Debris" + desc = "A small crater of rubble. Closer inspection reveals the debris to be made primarily of space-grade metal fragments. You're pretty sure that this will disperse before too long." + icon = 'icons/obj/supplypods.dmi' + layer = PROJECTILE_HIT_THRESHHOLD_LAYER // We want this to go right below the layer of supplypods and supplypod_rubble's forground. + icon_state = "rubble_bg" + anchored = TRUE + pixel_x = SUPPLYPOD_X_OFFSET + var/foreground = "rubble_fg" + var/verticle_offset = 0 + +/obj/effect/supplypod_rubble/proc/getForeground(obj/structure/closet/supplypod/pod) + var/mutable_appearance/rubble_overlay = mutable_appearance('icons/obj/supplypods.dmi', foreground) + rubble_overlay.appearance_flags = KEEP_APART|RESET_TRANSFORM + rubble_overlay.transform = matrix().Translate(SUPPLYPOD_X_OFFSET - pod.pixel_x, verticle_offset) + return rubble_overlay + +/obj/effect/supplypod_rubble/proc/fadeAway() + animate(src, alpha=0, time = 30) + QDEL_IN(src, 35) + +/obj/effect/supplypod_rubble/proc/setStyle(type, obj/structure/closet/supplypod/pod) + if (type == RUBBLE_WIDE) + icon_state += "_wide" + foreground += "_wide" + if (type == RUBBLE_THIN) + icon_state += "_thin" + foreground += "_thin" + if (pod.style == STYLE_BOX) + verticle_offset = -2 + else + verticle_offset = initial(verticle_offset) + + pixel_y = verticle_offset + +/obj/effect/DPtarget_effect + name = "" + desc = "" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "LZ_Slider" + layer = PROJECTILE_HIT_THRESHHOLD_LAYER + +/obj/effect/DPtarget_effect/Initialize(mapload, obj/structure/closet/supplypod/pod) + transform = matrix() * 1.5 + animate(src, transform = matrix()*0.01, time = pod.landingDelay+pod.fallDuration) + ..() + /obj/effect/DPtarget //This is the object that forceMoves the supplypod to it's location name = "Landing Zone Indicator" desc = "A holographic projection designating the landing zone of something. It's probably best to stand back." - icon = 'icons/mob/actions/actions_items.dmi' - icon_state = "sniper_zoom" + icon = 'icons/obj/supplypods_32x32.dmi' + icon_state = "LZ" layer = PROJECTILE_HIT_THRESHHOLD_LAYER light_range = 2 - var/obj/effect/temp_visual/fallingPod //Temporary "falling pod" that we animate - var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this target + anchored = TRUE + alpha = 0 + var/obj/structure/closet/supplypod/pod //The supplyPod that will be landing ontop of this DPtarget + var/obj/effect/DPtarget_effect/helper + var/list/smoke_effects = new /list(13) /obj/effect/ex_act() return -/obj/effect/DPtarget/Initialize(mapload, podParam, var/single_order = null) +/obj/effect/DPtarget/Initialize(mapload, podParam, single_order = null, clientman) . = ..() if (ispath(podParam)) //We can pass either a path for a pod (as expressconsoles do), or a reference to an instantiated pod (as the centcom_podlauncher does) podParam = new podParam() //If its just a path, instantiate it pod = podParam + if (!pod.effectStealth) + helper = new (drop_location(), pod) + alpha = 255 + animate(src, transform = matrix().Turn(90), time = pod.landingDelay+pod.fallDuration) if (single_order) if (istype(single_order, /datum/supply_order)) var/datum/supply_order/SO = single_order @@ -309,49 +574,76 @@ else if (istype(single_order, /atom/movable)) var/atom/movable/O = single_order O.forceMove(pod) - for (var/mob/living/M in pod) //If there are any mobs in the supplypod, we want to forceMove them into the target. This is so that they can see where they are about to land, AND so that they don't get sent to the nullspace error room (as the pod is currently in nullspace) - M.forceMove(src) - if(pod.effectStun) //If effectStun is true, stun any mobs caught on this target until the pod gets a chance to hit them - for (var/mob/living/M in get_turf(src)) - M.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you aint goin nowhere, kid. - if (pod.effectStealth) //If effectStealth is true we want to be invisible - icon_state = "" + for (var/mob/living/mob_in_pod in pod) //If there are any mobs in the supplypod, we want to set their view to the DPtarget. This is so that they can see where they are about to land + mob_in_pod.reset_perspective(src) + if(pod.effectStun) //If effectStun is true, stun any mobs caught on this DPtarget until the pod gets a chance to hit them + for (var/mob/living/target_living in get_turf(src)) + target_living.Stun(pod.landingDelay+10, ignore_canstun = TRUE)//you ain't goin nowhere, kid. if (pod.fallDuration == initial(pod.fallDuration) && pod.landingDelay + pod.fallDuration < pod.fallingSoundLength) pod.fallingSoundLength = 3 //The default falling sound is a little long, so if the landing time is shorter than the default falling sound, use a special, shorter default falling sound pod.fallingSound = 'sound/weapons/mortar_whistle.ogg' var/soundStartTime = pod.landingDelay - pod.fallingSoundLength + pod.fallDuration if (soundStartTime < 0) soundStartTime = 1 - if (!pod.effectQuiet) + if (!pod.effectQuiet && !(pod.pod_flags & FIRST_SOUNDS)) addtimer(CALLBACK(src, .proc/playFallingSound), soundStartTime) addtimer(CALLBACK(src, .proc/beginLaunch, pod.effectCircle), pod.landingDelay) /obj/effect/DPtarget/proc/playFallingSound() - playsound(src, pod.fallingSound, pod.soundVolume, 1, 6) + playsound(src, pod.fallingSound, pod.soundVolume, TRUE, 6) /obj/effect/DPtarget/proc/beginLaunch(effectCircle) //Begin the animation for the pod falling. The effectCircle param determines whether the pod gets to come in from any descent angle - fallingPod = new /obj/effect/DPfall(drop_location(), pod) - var/matrix/M = matrix(fallingPod.transform) //Create a new matrix that we can rotate + pod.addGlow() + pod.update_icon() + if (pod.style != STYLE_INVISIBLE) + pod.add_filter("motionblur",1,list("type"="motion_blur", "x"=0, "y"=3)) + pod.forceMove(drop_location()) + for (var/mob/living/M in pod) //Remember earlier (initialization) when we moved mobs into the DPtarget so they wouldnt get lost in nullspace? Time to get them out + M.reset_perspective(null) var/angle = effectCircle ? rand(0,360) : rand(70,110) //The angle that we can come in from - fallingPod.pixel_x = cos(angle)*400 //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the target - fallingPod.pixel_z = sin(angle)*400 - var/rotation = Get_Pixel_Angle(fallingPod.pixel_z, fallingPod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps - M.Turn(rotation) //Turn our matrix accordingly - fallingPod.transform = M //Transform the animated pod according to the matrix - M = matrix(pod.transform) //Make another matrix based on the pod - M.Turn(rotation) //Turn the matrix - pod.transform = M //Turn the actual pod (Won't be visible until endLaunch() proc tho) - animate(fallingPod, pixel_z = 0, pixel_x = -16, time = pod.fallDuration, , easing = LINEAR_EASING) //Make the pod fall! At an angle! + pod.pixel_x = cos(angle)*32*length(smoke_effects) //Use some ADVANCED MATHEMATICS to set the animated pod's position to somewhere on the edge of a circle with the center being the DPtarget + pod.pixel_z = sin(angle)*32*length(smoke_effects) + var/rotation = Get_Pixel_Angle(pod.pixel_z, pod.pixel_x) //CUSTOM HOMEBREWED proc that is just arctan with extra steps + setupSmoke(rotation) + pod.transform = matrix().Turn(rotation) + pod.layer = FLY_LAYER + if (pod.style != STYLE_INVISIBLE) + animate(pod.get_filter("motionblur"), y = 0, time = pod.fallDuration, flags = ANIMATION_PARALLEL) + animate(pod, pixel_z = -1 * abs(sin(rotation))*4, pixel_x = SUPPLYPOD_X_OFFSET + (sin(rotation) * 20), time = pod.fallDuration, easing = LINEAR_EASING, flags = ANIMATION_PARALLEL) //Make the pod fall! At an angle! addtimer(CALLBACK(src, .proc/endLaunch), pod.fallDuration, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation +/obj/effect/DPtarget/proc/setupSmoke(rotation) + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for ( var/i in 1 to length(smoke_effects)) + var/obj/effect/supplypod_smoke/smoke_part = new (drop_location()) + if (i == 1) + smoke_part.layer = FLY_LAYER + smoke_part.icon_state = "smoke_start" + smoke_part.transform = matrix().Turn(rotation) + smoke_effects[i] = smoke_part + smoke_part.pixel_x = sin(rotation)*32 * i + smoke_part.pixel_y = abs(cos(rotation))*32 * i + smoke_part.filters += filter(type = "blur", size = 4) + var/time = (pod.fallDuration / length(smoke_effects))*(length(smoke_effects)-i) + addtimer(CALLBACK(smoke_part, /obj/effect/supplypod_smoke/.proc/drawSelf, i), time, TIMER_CLIENT_TIME) //Go onto the last step after a very short falling animation + QDEL_IN(smoke_part, pod.fallDuration + 35) + +/obj/effect/DPtarget/proc/drawSmoke() + if (pod.style == STYLE_INVISIBLE || pod.style == STYLE_SEETHROUGH) + return + for (var/obj/effect/supplypod_smoke/smoke_part in smoke_effects) + animate(smoke_part, alpha = 0, time = 20, flags = ANIMATION_PARALLEL) + animate(smoke_part.filters[1], size = 6, time = 15, easing = CUBIC_EASING|EASE_OUT, flags = ANIMATION_PARALLEL) + /obj/effect/DPtarget/proc/endLaunch() - pod.update_icon() - pod.forceMove(drop_location()) //The fallingPod animation is over, now's a good time to forceMove the actual pod into position - QDEL_NULL(fallingPod) //Delete the falling pod effect, because at this point its animation is over. We dont use temp_visual because we want to manually delete it as soon as the pod appears - for (var/mob/living/M in src) //Remember earlier (initialization) when we moved mobs into the DPTarget so they wouldnt get lost in nullspace? Time to get them out - M.forceMove(pod) + pod.tryMakeRubble(drop_location()) + pod.layer = initial(pod.layer) + pod.endGlow() + QDEL_NULL(helper) pod.preOpen() //Begin supplypod open procedures. Here effects like explosions, damage, and other dangerous (and potentially admin-caused, if the centcom_podlauncher datum was used) memes will take place - qdel(src) //The target's purpose is complete. It can rest easy now + drawSmoke() + qdel(src) //The DPtarget's purpose is complete. It can rest easy now //------------------------------------UPGRADES-------------------------------------// /obj/item/disk/cargo/bluespace_pod //Disk that can be inserted into the Express Console to allow for Advanced Bluespace Pods diff --git a/icons/effects/mouse_pointers/supplypod_down_target.dmi b/icons/effects/mouse_pointers/supplypod_down_target.dmi new file mode 100644 index 0000000000000000000000000000000000000000..53a3bee0a78720f6750cf0cb67b71007140a1a53 GIT binary patch literal 464 zcmV;>0WbcEP)`6wG2eNIUQtuJt*@h&n2LOUwjeY`p+*A$0004WQchCV=-0C=2JR&a84_w-Y6@%7{?OD!tS%+FJ>RWQ*r;NmRL zOex6#a?DJ)I5Sc+(=$pSoZ^zil2jm5DKRHUiHkEOv#1!zGvwk-D@x2wg|L+sT>V_Y zh5`Wj1Q|{|dwkUZ0087kL_t(YiS5%}4a6V}0AMU&VHRqk7O+qYwSa|LzycN!b>$~_ z$&HmUa@os~>|yCQ`j;8=<71b>(-qj!1q!fc+pKnh1^|q~9CvoR0|1BtJzD2nH^T@3 zGclvz5l=FVKuZyVmn=#R3djVb#0B)nFami>dx+kL9lP7s0F*W*L5v}mGLIjt020VU zjG+j`!v$AB7BVFfD9I)P5Yb1VO#DlXK}rQMQLeA~8PJL(VrjL=eis8EB@#dpH$Yuh zoffp$N$^%@v1_3Iyel9#sgefFDZ*Bkp zc$`yKaB_9`^iy#0_2eo`Eh^5;&r`5fFwryM;w;ZhDainG%uKjAGg33tGfE(w;*!LY zR3KAHiHkEOv#1!zH00t;D@x2wg|L+sT>V_YCISGQz!()WHs0v~004YRL_t(YiS3lp z4S+BRL|NfhUzKnySHQRJ=z0=>4=@UQ#HuB3*wJplYTHJOxOZ#z-v>Y*HK3 z%>x3(!S*&V8E~(GJ7y~hh{MB@1Zq{w5y46VC)Opf_NJ7b^%lTa!QUj`O@5+;p6V7a ii+|ls^!YE>MQQ*ku^!R&cE#WT0000L*^!tYI%}V;et{}+R z-d5A>n5DKYP?oVI$S;_|;n|HeASb0NB%;J6wK%ybv!En1KaYW-Voq>aK~d@VFTn*L zzdq6O*3~+9=6vvmP=kxc4<6~9_t8AbP;|y;hlO#F@nvH#g*lHV9hnrO5bW96ZLGR^ zg~SPyCy#uzz0UZyZkQ?>^Ec*z8WY2u0}`S}X7^qK?aT9YaSZY3d^_bJ?*RoK=Dkhf zC%7UHYH#?j#kw^lw_Y*ms?s~=P45`hm?CNfFP*vDBO|)(z}oj`9y_gmrn5q<%*By$ z%WWayZ=VC1RN@vbUYUj#%9bZrXoy)7aQSM?! Sq}+R;%?zHdelF{r5}E+-8i7;* literal 0 HcmV?d00001 diff --git a/icons/effects/mouse_pointers/supplypod_target.dmi b/icons/effects/mouse_pointers/supplypod_target.dmi new file mode 100644 index 0000000000000000000000000000000000000000..94401d7a8ae960a33faa3f11682e89d0556d76e7 GIT binary patch literal 392 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 U+*?>~4D|;X=w8&x)3yuyf1`6gf&c&j literal 56060 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 DqkAci diff --git a/icons/obj/supplypods_32x32.dmi b/icons/obj/supplypods_32x32.dmi new file mode 100644 index 0000000000000000000000000000000000000000..855132f6494dd9f1ad71e91c42dbc737e59b23e5 GIT binary patch literal 3393 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 { selected, tooltip, tooltipPosition, + tooltipOverrideLong, ellipsis, compact, circular, @@ -97,6 +98,7 @@ export const Button = props => { {tooltip && ( )} @@ -221,6 +223,7 @@ export class ButtonInput extends Component { iconSpin, tooltip, tooltipPosition, + tooltipOverrideLong, color = 'default', placeholder, maxLength, @@ -270,6 +273,7 @@ export class ButtonInput extends Component { {tooltip && ( )} diff --git a/tgui/packages/tgui/components/DraggableControl.js b/tgui/packages/tgui/components/DraggableControl.js index 384e29b323ca..dfb47e8f4d6b 100644 --- a/tgui/packages/tgui/components/DraggableControl.js +++ b/tgui/packages/tgui/components/DraggableControl.js @@ -173,6 +173,7 @@ export class DraggableControl extends Component { unit, minValue, maxValue, + unclamped, format, onChange, onDrag, @@ -221,10 +222,16 @@ export class DraggableControl extends Component { if (!editing) { return; } - const value = clamp( - parseFloat(e.target.value), - minValue, - maxValue); + let value; + if (unclamped) { + value = parseFloat(e.target.value); + } + else { + value = clamp( + parseFloat(e.target.value), + minValue, + maxValue); + } if (Number.isNaN(value)) { this.setState({ editing: false, @@ -245,10 +252,16 @@ export class DraggableControl extends Component { }} onKeyDown={e => { if (e.keyCode === 13) { - const value = clamp( - parseFloat(e.target.value), - minValue, - maxValue); + let value; + if (unclamped) { + value = parseFloat(e.target.value); + } + else { + value = clamp( + parseFloat(e.target.value), + minValue, + maxValue); + } if (Number.isNaN(value)) { this.setState({ editing: false, diff --git a/tgui/packages/tgui/components/Flex.js b/tgui/packages/tgui/components/Flex.js index 2477f4c50209..a55f0654b92e 100644 --- a/tgui/packages/tgui/components/Flex.js +++ b/tgui/packages/tgui/components/Flex.js @@ -33,7 +33,7 @@ export const computeFlexProps = props => { style: { ...rest.style, 'flex-direction': direction, - 'flex-wrap': wrap, + 'flex-wrap': wrap === true ? 'wrap' : wrap, 'align-items': align, 'justify-content': justify, }, diff --git a/tgui/packages/tgui/components/Knob.js b/tgui/packages/tgui/components/Knob.js index c165df46fd71..175792471b3d 100644 --- a/tgui/packages/tgui/components/Knob.js +++ b/tgui/packages/tgui/components/Knob.js @@ -24,6 +24,7 @@ export const Knob = props => { format, maxValue, minValue, + unclamped, onChange, onDrag, step, @@ -50,6 +51,7 @@ export const Knob = props => { format, maxValue, minValue, + unclamped, onChange, onDrag, step, @@ -79,7 +81,7 @@ export const Knob = props => { const effectiveColor = color || keyOfMatchingRange(fillValue ?? value, ranges) || 'default'; - const rotation = (scaledDisplayValue - 0.5) * 270; + const rotation = Math.min((scaledDisplayValue - 0.5) * 270, 225); return (

{ className="Knob__ringFill" style={{ 'stroke-dashoffset': ( - ((bipolar ? 2.75 : 2.00) - scaledFillValue * 1.5) - * Math.PI * 50 + Math.max(((bipolar ? 2.75 : 2.00) - scaledFillValue * 1.5) + * Math.PI * 50, 0) ), }} cx="50" diff --git a/tgui/packages/tgui/components/LabeledControls.js b/tgui/packages/tgui/components/LabeledControls.js index def4079e7a0b..b4f069192c5f 100644 --- a/tgui/packages/tgui/components/LabeledControls.js +++ b/tgui/packages/tgui/components/LabeledControls.js @@ -9,11 +9,13 @@ import { Flex } from './Flex'; export const LabeledControls = props => { const { children, + wrap, ...rest } = props; return ( @@ -26,12 +28,12 @@ const LabeledControlsItem = props => { const { label, children, + mx = 1, ...rest } = props; return ( - + + {children} +
+ ); return (
)} - {fitted && children - || hasContent && ( -
- {children} -
- )} + {content} ); } diff --git a/tgui/packages/tgui/components/Tooltip.js b/tgui/packages/tgui/components/Tooltip.js index 20e3521cf585..5c0aad61a914 100644 --- a/tgui/packages/tgui/components/Tooltip.js +++ b/tgui/packages/tgui/components/Tooltip.js @@ -9,11 +9,13 @@ import { classes } from 'common/react'; export const Tooltip = props => { const { content, + overrideLong = false, position = 'bottom', } = props; // Empirically calculated length of the string, // at which tooltip text starts to overflow. - const long = typeof content === 'string' && content.length > 35; + const long = typeof content === 'string' + && (content.length > 35 && !overrideLong); return (
{ window.screenLeft - event.screenX, window.screenTop - event.screenY, ]; + // Focus click target + event.target?.focus(); document.addEventListener('mousemove', dragMoveHandler); document.addEventListener('mouseup', dragEndHandler); dragMoveHandler(event); @@ -214,6 +216,8 @@ export const resizeStartHandler = (x, y) => event => { window.innerWidth, window.innerHeight, ]; + // Focus click target + event.target?.focus(); document.addEventListener('mousemove', resizeMoveHandler); document.addEventListener('mouseup', resizeEndHandler); resizeMoveHandler(event); diff --git a/tgui/packages/tgui/interfaces/CentcomPodLauncher.js b/tgui/packages/tgui/interfaces/CentcomPodLauncher.js index 05982b2fbf6f..a0d507e1543f 100644 --- a/tgui/packages/tgui/interfaces/CentcomPodLauncher.js +++ b/tgui/packages/tgui/interfaces/CentcomPodLauncher.js @@ -1,518 +1,1135 @@ +import { toFixed } from 'common/math'; +import { classes } from 'common/react'; +import { storage } from 'common/storage'; import { multiline } from 'common/string'; -import { Fragment } from 'inferno'; -import { useBackend } from '../backend'; -import { Button, LabeledList, NoticeBox, Section } from '../components'; +import { createUuid } from 'common/uuid'; +import { Component } from 'inferno'; +import { useBackend, useLocalState } from '../backend'; +import { Box, Button, ByondUi, Divider, Flex, Fragment, Input, Knob, LabeledControls, NumberInput, Section } from '../components'; import { Window } from '../layouts'; -export const CentcomPodLauncher = () => { +const pod_grey = { + color: 'grey', +}; + +const useCompact = context => { + const [compact, setCompact] = useLocalState(context, 'compact', false); + const toggleCompact = () => setCompact(!compact); + return [compact, toggleCompact]; +}; + +export const CentcomPodLauncher = (props, context) => { + const [compact] = useCompact(context); return ( - - - + resizable + key={'CPL_' + compact} + title={compact + ? "Use against Helen Weinstein" + : "Supply Pod Menu (Use against Helen Weinstein)"} + overflow="hidden" + width={compact ? 435 : 690} + height={compact ? 360 : 440}> + ); }; -// This is more or less a direct port from old tgui, with some slight -// text cleanup. But yes, it actually worked like this. -export const CentcomPodLauncherContent = (props, context) => { +const CentcomPodLauncherContent = (props, context) => { + const [compact] = useCompact(context); + return ( + + + + + + + + + + + + + + + + +
+ +
+
+
+
+ {!compact && ( + + + + )} + + + + + + + + + {!compact && ( + + + + )} + + + + + +
+
+
+
+ ); +}; + +const TABPAGES = [ + { + title: 'View Pod', + component: () => TabPod, + }, + { + title: 'View Bay', + component: () => TabBay, + }, + { + title: 'View Dropoff Location', + component: () => TabDrop, + }, +]; + +const REVERSE_OPTIONS = [ + { + title: 'Mobs', + icon: 'user', + }, + { + title: 'Unanchored\nObjects', + key: 'Unanchored', + icon: 'cube', + }, + { + title: 'Anchored\nObjects', + key: 'Anchored', + icon: 'anchor', + }, + { + title: 'Under-Floor', + key: 'Underfloor', + icon: 'eye-slash', + }, + { + title: 'Wall-Mounted', + key: 'Wallmounted', + icon: 'link', + }, + { + title: 'Floors', + icon: 'border-all', + }, + { + title: 'Walls', + icon: 'square', + + }, +]; + + +const DELAYS = [ + { + title: 'Pre', + tooltip: 'Time until pod gets to station', + }, + { + title: 'Fall', + tooltip: 'Duration of pods\nfalling animation', + }, + { + title: 'Open', + tooltip: 'Time it takes pod to open after landing', + }, + { + title: 'Exit', + tooltip: 'Time for pod to\nleave after opening', + }, +]; + +const SOUNDS = [ + { + title: 'Fall', + act: 'fallingSound', + tooltip: 'Plays while pod falls, timed\nto end when pod lands', + }, + { + title: 'Land', + act: 'landingSound', + tooltip: 'Plays after pod lands', + }, + { + title: 'Open', + act: 'openingSound', + tooltip: 'Plays when pod opens', + }, + { + title: 'Exit', + act: 'leavingSound', + tooltip: 'Plays when pod leaves', + }, +]; + +const STYLES = [ + { + title: 'Standard', + }, + { + title: 'Advanced', + }, + { + title: 'Nanotrasen', + }, + { + title: 'Syndicate', + }, + { + title: 'Deathsquad', + }, + { + title: 'Cultist', + }, + { + title: 'Missile', + }, + { + title: 'Syndie Missile', + }, + { + title: 'Supply Box', + }, + { + title: 'Clown Pod', + }, + { + title: 'Fruit', + }, + { + title: 'Invisible', + }, + { + title: 'Gondola', + }, + { + title: 'Seethrough', + }, +]; + +const BAYS = [ + { + title: '1', + }, + { + title: '2', + }, + { + title: '3', + }, + { + title: '4', + }, + { + title: 'ERT', + }, +]; + +const EFFECTS_LOAD = [ + { + title: 'Launch All Turfs', + icon: 'globe', + choiceNumber: 0, + selected: 'launchChoice', + act: 'launchAll', + }, + { + title: 'Launch Turf Ordered', + icon: 'sort-amount-down-alt', + choiceNumber: 1, + selected: 'launchChoice', + act: 'launchOrdered', + }, + { + title: 'Pick Random Turf', + icon: 'dice', + choiceNumber: 2, + selected: 'launchChoice', + act: 'launchRandomTurf', + }, + { + divider: 1, + }, + { + title: 'Launch Whole Turf', + icon: 'expand', + choiceNumber: 0, + selected: 'launchRandomItem', + act: 'launchWholeTurf', + }, + { + title: 'Pick Random Item', + icon: 'dice', + choiceNumber: 1, + selected: 'launchRandomItem', + act: 'launchRandomItem', + }, + { + divider: 1, + }, + { + title: 'Clone', + icon: 'clone', + soloSelected: 'launchClone', + act: 'launchClone', + }, +]; + +const EFFECTS_NORMAL = [ + { + title: 'Specific Target', + icon: 'user-check', + soloSelected: 'effectTarget', + act: 'effectTarget', + }, + { + title: 'Pod Stays', + icon: 'hand-paper', + choiceNumber: 0, + selected: 'effectBluespace', + act: 'effectBluespace', + }, + { + title: 'Stealth', + icon: 'user-ninja', + soloSelected: 'effectStealth', + act: 'effectStealth', + }, + { + title: 'Quiet', + icon: 'volume-mute', + soloSelected: 'effectQuiet', + act: 'effectQuiet', + }, + { + title: 'Missile Mode', + icon: 'rocket', + soloSelected: 'effectMissile', + act: 'effectMissile', + }, + { + title: 'Burst Launch', + icon: 'certificate', + soloSelected: 'effectBurst', + act: 'effectBurst', + }, + { + title: 'Any Descent Angle', + icon: 'ruler-combined', + soloSelected: 'effectCircle', + act: 'effectCircle', + }, + { + title: 'No Ghost Alert\n(If you dont want to\nentertain bored ghosts)', + icon: 'ghost', + choiceNumber: 0, + selected: 'effectAnnounce', + act: 'effectAnnounce', + }, +]; + +const EFFECTS_HARM =[ + { + title: 'Explosion Custom', + icon: 'bomb', + choiceNumber: 1, + selected: 'explosionChoice', + act: 'explosionCustom', + }, + { + title: 'Adminbus Explosion\nWhat are they gonna do, ban you?', + icon: 'bomb', + choiceNumber: 2, + selected: 'explosionChoice', + act: 'explosionBus', + }, + { + divider: 1, + }, + { + title: 'Custom Damage', + icon: 'skull', + choiceNumber: 1, + selected: 'damageChoice', + act: 'damageCustom', + }, + { + title: 'Gib', + icon: 'skull-crossbones', + choiceNumber: 2, + selected: 'damageChoice', + act: 'damageGib', + }, + { + divider: 1, + }, + { + title: 'Projectile Cloud', + details: true, + icon: 'cloud-meatball', + soloSelected: 'effectShrapnel', + act: 'effectShrapnel', + }, + { + title: 'Stun', + icon: 'sun', + soloSelected: 'effectStun', + act: 'effectStun', + }, + { + title: 'Delimb', + icon: 'socks', + soloSelected: 'effectLimb', + act: 'effectLimb', + }, + { + title: 'Yeet Organs', + icon: 'book-dead', + soloSelected: 'effectOrgans', + act: 'effectOrgans', + }, +]; +const EFFECTS_ALL = [ + { + list: EFFECTS_LOAD, + label: "Load From", + alt_label: "Load", + tooltipPosition: "right", + }, + { + list: EFFECTS_NORMAL, + label: "Normal Effects", + tooltipPosition: "bottom", + }, + { + list: EFFECTS_HARM, + label: "Harmful Effects", + tooltipPosition: "bottom", + }, +]; + +const ViewTabHolder = (props, context) => { const { act, data } = useBackend(context); + const [ + tabPageIndex, + setTabPageIndex, + ] = useLocalState(context, 'tabPageIndex', 1); + const { mapRef } = data; + const TabPageComponent = TABPAGES[tabPageIndex].component(); + return ( +
+ {(!!data.customDropoff && data.effectReverse===1) && ( +
+ ); +}; + +const TabPod = (props, context) => { + return ( + + Note: You can right click on this +
+ blueprint pod and edit vars directly +
+ ); +}; + + +const TabBay = (props, context) => { + const { act, data, config } = useBackend(context); return ( - - To use this, simply spawn the atoms you want in one of the five - Centcom Supplypod Bays. Items in the bay will then be launched inside - your supplypod, one turf-full at a time! You can optionally use the - following buttons to configure how the supplypod acts. - -
- - -
-
- - -
+ + ))} + + ); +}; + +const Bays = (props, context) => { + const { act, data } = useBackend(context); + const [compact] = useCompact(context); + return ( +
+
+ ); +}; + +const Timing = (props, context) => { + const { act, data } = useBackend(context); + + return ( +
act('resetTiming')} /> + )}> + + {DELAYS.map((delay, i) => ( + + toFixed(value, 2)} + maxValue={10} + color={(data["delay_"+(i+1)]/10) > 10 ? "orange" : "default"} + onDrag={(e, value) => { + act('editTiming', { + timer: i + 1, + value: Math.max(value, 0), + }); + }} /> + + ))} + +
+ ); +}; + +const Sounds = (props, context) => { + const { act, data } = useBackend(context); + return ( +
act('soundVolume')} /> + )}> + {SOUNDS.map((sound, i) => ( +
+ ); +}; diff --git a/tgui/packages/tgui/styles/components/Section.scss b/tgui/packages/tgui/styles/components/Section.scss index d53ff65bdd08..bd30dc70dd62 100644 --- a/tgui/packages/tgui/styles/components/Section.scss +++ b/tgui/packages/tgui/styles/components/Section.scss @@ -57,6 +57,11 @@ $shadow-offset: 0 0 !default; height: 100%; } +.Section--scrollable .Section__content { + overflow-y: scroll; + overflow-x: hidden; +} + .Section--fill .Section__content { flex-grow: 1; } @@ -76,7 +81,7 @@ $shadow-offset: 0 0 !default; .Section--scrollable { overflow-x: hidden; - overflow-y: scroll; + overflow-y: hidden; } .Section--level--1 .Section__titleText { diff --git a/tgui/packages/tgui/styles/components/Tooltip.scss b/tgui/packages/tgui/styles/components/Tooltip.scss index c871584f907d..f557c5642ee5 100644 --- a/tgui/packages/tgui/styles/components/Tooltip.scss +++ b/tgui/packages/tgui/styles/components/Tooltip.scss @@ -21,7 +21,7 @@ $border-radius: base.$border-radius !default; &::after { position: absolute; display: block; - white-space: nowrap; + white-space: pre; z-index: 2; padding: 0.5em 0.75em; transform: translateX(-50%); @@ -61,6 +61,26 @@ $border-radius: base.$border-radius !default; transform: translateX(-50%) translateY(-0.5em); } +.Tooltip--top-left::after { + bottom: 100%; + right: 50%; + transform: translateX(12px) translateY(8px); +} + +.Tooltip--top-left:hover::after { + transform: translateX(12px) translateY(-8px); +} + +.Tooltip--top-right::after { + top: 0px; + right: 0px; + transform: translateX(100%) translateY(-50%); +} + +.Tooltip--top-right:hover::after { + transform: translateX(100%) translateY(-100%); +} + .Tooltip--bottom::after { top: 100%; left: 50%;