diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 678486f019..1b5698ba5e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,8 +4,9 @@ # In the event that multiple org members are to be informed of changes # to the same file or dir, add them to the end under Multiple Owners -# ShadowLarkens -/code/__DEFINES/tgui.dm @ShadowLarkens -/code/controllers/subsystem/tgui.dm @ShadowLarkens -/code/modules/tgui @ShadowLarkens -/tgui @ShadowLarkens \ No newline at end of file +# ItsSelis + +/code/__DEFINES/tgui.dm @ItsSelis +/code/controllers/subsystem/tgui.dm @ItsSelis +/code/modules/tgui @ItsSelis +/tgui @ItsSelis diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 91% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug_report.md index e26a763524..73e550111f 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,12 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: 'Type: Bug' +assignees: '' + +--- + diff --git a/code/_global_vars/religion.dm b/code/_global_vars/religion.dm deleted file mode 100644 index 7dab008b19..0000000000 --- a/code/_global_vars/religion.dm +++ /dev/null @@ -1,8 +0,0 @@ -// All religion stuff -GLOBAL_VAR(religion) -GLOBAL_VAR(deity) - -//bible -GLOBAL_VAR(bible_name) -GLOBAL_VAR(bible_icon_state) -GLOBAL_VAR(bible_item_state) diff --git a/code/_onclick/hud/_defines.dm b/code/_onclick/hud/_defines.dm index 14feaf1b5f..f1a7f22248 100644 --- a/code/_onclick/hud/_defines.dm +++ b/code/_onclick/hud/_defines.dm @@ -47,6 +47,7 @@ #define ui_construct_purge "EAST:00,CENTER-1:15" #define ui_construct_fire "EAST-1:16,CENTER+1:13" //above health, slightly to the left #define ui_construct_pull "EAST-1:28,SOUTH+1:10" //above the zone_sel icon +#define ui_pai_comms "EAST-1:28,SOUTH+1:5" //Lower right, persistant menu #define ui_dropbutton "EAST-4:22,SOUTH:5" diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index a06df0c082..03fd9bac74 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -422,16 +422,28 @@ usr.a_intent_change("right") if(I_HELP) usr.a_intent = I_HELP - usr.hud_used.action_intent.icon_state = "intent_help" + if(ispAI(usr)) + usr.a_intent_change(I_HELP) + else + usr.hud_used.action_intent.icon_state = "intent_help" if(I_HURT) usr.a_intent = I_HURT - usr.hud_used.action_intent.icon_state = "intent_harm" + if(ispAI(usr)) + usr.a_intent_change(I_HURT) + else + usr.hud_used.action_intent.icon_state = "intent_harm" if(I_GRAB) usr.a_intent = I_GRAB - usr.hud_used.action_intent.icon_state = "intent_grab" + if(ispAI(usr)) + usr.a_intent_change(I_GRAB) + else + usr.hud_used.action_intent.icon_state = "intent_grab" if(I_DISARM) usr.a_intent = I_DISARM - usr.hud_used.action_intent.icon_state = "intent_disarm" + if(ispAI(usr)) + usr.a_intent_change(I_DISARM) + else + usr.hud_used.action_intent.icon_state = "intent_disarm" if("pull") usr.stop_pulling() diff --git a/code/_onclick/hud/screen_objects_vr.dm b/code/_onclick/hud/screen_objects_vr.dm index 4d83780327..6ea02dd34a 100644 --- a/code/_onclick/hud/screen_objects_vr.dm +++ b/code/_onclick/hud/screen_objects_vr.dm @@ -65,6 +65,43 @@ if(istype(H) && istype(H.species, /datum/species/xenochimera)) // If you're somehow able to click this while not a chimera, this should prevent weird runtimes. Will need changing if regeneration is ever opened to non-chimera using the same alert. if(H.revive_ready == REVIVING_DONE) // Sanity check. H.hatch() // Hatch. + //pAI buttons!!! + if("fold/unfold") + if(ispAI(usr)) + var/mob/living/silicon/pai/p = usr + if(p.loc == p.card) + p.fold_out() + else + p.fold_up() + if("choose chassis") + if(ispAI(usr)) + var/mob/living/silicon/pai/p = usr + p.choose_chassis() + + if("software interface") + if(ispAI(usr)) + var/mob/living/silicon/pai/p = usr + p.paiInterface() + + if("radio configuration") + if(ispAI(usr)) + var/mob/living/silicon/pai/p = usr + p.radio.tgui_interact(p) + + if("pda") + if(ispAI(usr)) + var/mob/living/silicon/pai/p = usr + p.pda.cmd_pda_open_ui() + + if("communicator") + if(ispAI(usr)) + var/mob/living/silicon/pai/p = usr + p.communicator.activate() + + if("known languages") + if(ispAI(usr)) + var/mob/living/silicon/pai/p = usr + p.check_languages() else return 0 diff --git a/code/controllers/subsystems/tgui.dm b/code/controllers/subsystems/tgui.dm index a0d96cf80c..d70d06017a 100644 --- a/code/controllers/subsystems/tgui.dm +++ b/code/controllers/subsystems/tgui.dm @@ -23,6 +23,10 @@ SUBSYSTEM_DEF(tgui) /datum/controller/subsystem/tgui/PreInit() basehtml = file2text('tgui/public/tgui.html') + // Inject inline polyfills + var/polyfill = file2text('tgui/public/tgui-polyfill.min.js') + polyfill = "" + basehtml = replacetextEx(basehtml, "", polyfill) /datum/controller/subsystem/tgui/Shutdown() close_all_uis() diff --git a/code/controllers/subsystems/vote.dm b/code/controllers/subsystems/vote.dm index 658cff1716..61ba792151 100644 --- a/code/controllers/subsystems/vote.dm +++ b/code/controllers/subsystems/vote.dm @@ -362,7 +362,12 @@ SUBSYSTEM_DEF(vote) if(VOTE_RESTART) if(config.allow_vote_restart || usr.client.holder) - initiate_vote(VOTE_RESTART, usr.key) + var/admin_number_present = send2irc_adminless_only(usr.ckey, usr) + if(admin_number_present <= 0 || usr.client.holder) + if(tgui_alert(usr, "Are you sure you want to start a RESTART VOTE? You should only do this if the server is dying and no staff are around to investigate.", "RESTART VOTE", list("No", "Yes I want to start a RESTART VOTE")) == "Yes I want to start a RESTART VOTE") + initiate_vote(VOTE_RESTART, usr.key) + else + to_chat(usr, "You can't start a RESTART VOTE while there are staff around. If you are having an issue with the round, please ahelp it.") if(VOTE_GAMEMODE) if(config.allow_vote_mode || usr.client.holder) initiate_vote(VOTE_GAMEMODE, usr.key) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index a35c23cd6b..736fdff021 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -75,6 +75,8 @@ //used to store what traits the player had picked out in their preferences before joining, in text form. var/list/traits = list() + var/datum/religion/my_religion + /datum/mind/New(var/key) src.key = key purchase_log = list() @@ -571,4 +573,4 @@ /mob/living/simple_mob/construct/juggernaut/mind_initialize() . = ..() mind.assigned_role = "Juggernaut" - mind.special_role = "Cultist" + mind.special_role = "Cultist" \ No newline at end of file diff --git a/code/datums/supplypacks/misc_vr.dm b/code/datums/supplypacks/misc_vr.dm index 63f055d2d4..539e88f75b 100644 --- a/code/datums/supplypacks/misc_vr.dm +++ b/code/datums/supplypacks/misc_vr.dm @@ -102,6 +102,16 @@ containername = "Commonwealth engineering hardsuit crate" access = access_engine +/datum/supply_pack/misc/breacher_rig + name = "unathi breacher hardsuit (empty)" + contains = list( + /obj/item/weapon/rig/breacher = 1 + ) + cost = 250 + containertype = /obj/structure/closet/crate/secure/gear + containername = "unathi breacher hardsuit crate" + access = access_armory + /datum/supply_pack/misc/zero_rig name = "null hardsuit (jets)" contains = list( diff --git a/code/datums/supplypacks/supply.dm b/code/datums/supplypacks/supply.dm index eff6fb84fd..70f8386a91 100644 --- a/code/datums/supplypacks/supply.dm +++ b/code/datums/supplypacks/supply.dm @@ -9,18 +9,30 @@ /datum/supply_pack/supply/food name = "Kitchen supply crate" contains = list( - /obj/item/weapon/reagent_containers/food/condiment/flour = 6, + /obj/item/weapon/reagent_containers/food/condiment/carton/flour = 6, /obj/item/weapon/reagent_containers/food/drinks/milk = 3, /obj/item/weapon/reagent_containers/food/drinks/soymilk = 2, /obj/item/weapon/storage/fancy/egg_box = 2, /obj/item/weapon/reagent_containers/food/snacks/tofu = 4, /obj/item/weapon/reagent_containers/food/snacks/meat = 4, - /obj/item/weapon/reagent_containers/food/condiment/yeast = 3 + /obj/item/weapon/reagent_containers/food/condiment/yeast = 3, + /obj/item/weapon/reagent_containers/food/condiment/sprinkles = 1 ) cost = 10 containertype = /obj/structure/closet/crate/freezer/centauri containername = "Food crate" +/datum/supply_pack/supply/fancyfood + name = "Artisanal food delivery" + contains = list( + /obj/item/weapon/reagent_containers/food/condiment/carton/flour/rustic = 6, + /obj/item/weapon/reagent_containers/food/condiment/carton/sugar/rustic = 6 + ) + cost = 25 + containertype = /obj/structure/closet/crate/freezer/centauri + containername = "Artisanal food crate" + + /datum/supply_pack/supply/toner name = "Toner cartridges" contains = list(/obj/item/device/toner = 6) diff --git a/code/game/jobs/job/civilian_chaplain.dm b/code/game/jobs/job/civilian_chaplain.dm index 5fba7b155d..f811abf957 100644 --- a/code/game/jobs/job/civilian_chaplain.dm +++ b/code/game/jobs/job/civilian_chaplain.dm @@ -29,20 +29,14 @@ return var/obj/item/weapon/storage/bible/B = locate(/obj/item/weapon/storage/bible) in H - if(!B) + var/obj/item/weapon/card/id/I = locate(/obj/item/weapon/card/id) in H + + if(!B || !I) return - if(GLOB.religion) - B.deity_name = GLOB.deity - B.name = GLOB.bible_name - B.icon_state = GLOB.bible_icon_state - B.item_state = GLOB.bible_item_state - to_chat(H, "There is already an established religion onboard the station. You are an acolyte of [GLOB.deity]. Defer to the [title].") - return - - INVOKE_ASYNC(src, .proc/religion_prompts, H, B) + INVOKE_ASYNC(src, .proc/religion_prompts, H, B, I) -/datum/job/chaplain/proc/religion_prompts(mob/living/carbon/human/H, obj/item/weapon/storage/bible/B) +/datum/job/chaplain/proc/religion_prompts(mob/living/carbon/human/H, obj/item/weapon/storage/bible/B, obj/item/weapon/card/id/I) var/religion_name = "Unitarianism" var/new_religion = sanitize(input(H, "You are the crew services officer. Would you like to change your religion? Default is Unitarianism", "Name change", religion_name), MAX_NAME_LEN) if(!new_religion) @@ -83,18 +77,48 @@ B.name = "Guru Granth Sahib" else B.name = "The Holy Book of [new_religion]" - feedback_set_details("religion_name","[new_religion]") var/deity_name = "Hashem" var/new_deity = sanitize(input(H, "Would you like to change your deity? Default is Hashem", "Name change", deity_name), MAX_NAME_LEN) if((length(new_deity) == 0) || (new_deity == "Hashem")) new_deity = deity_name - B.deity_name = new_deity - GLOB.religion = new_religion - GLOB.bible_name = B.name - GLOB.deity = B.deity_name - feedback_set_details("religion_deity","[new_deity]") - + var/new_title = sanitize(input(H, "Would you like to change your title?", "Title Change", I.assignment), MAX_NAME_LEN) + var/list/all_jobs = get_job_datums() + + // Are they trying to fake an actual existent job + var/faking_job = FALSE + + for (var/datum/job/J in all_jobs) + if (J.title == new_title || (new_title in get_alternate_titles(J.title))) + faking_job = TRUE + + if (length(new_title) != 0 && !faking_job) + I.assignment = new_title + + H.mind.my_religion = new /datum/religion(new_religion, new_deity, B.name, "bible", "bible", new_title) + + B.deity_name = H.mind.my_religion.deity + I.assignment = H.mind.my_religion.title + I.name = text("[I.registered_name]'s ID Card ([I.assignment])") + data_core.manifest_modify(I.registered_name, I.assignment, I.rank) + +/datum/religion + var/religion = "Unitarianism" + var/deity = "Hashem" + var/bible_name = "Bible" + var/bible_icon_state = "bible" + var/bible_item_state = "bible" + var/title = "Chaplain" + var/configured = FALSE + +/datum/religion/New(var/r, var/d, var/bn, var/bis, var/bits, var/t) + . = ..() + religion = r + deity = d + bible_name = bn + bible_icon_state = bis + bible_item_state = bits + title = t \ No newline at end of file diff --git a/code/game/machinery/telecomms/logbrowser.dm b/code/game/machinery/telecomms/logbrowser.dm index c896b77f37..ba63655b9c 100644 --- a/code/game/machinery/telecomms/logbrowser.dm +++ b/code/game/machinery/telecomms/logbrowser.dm @@ -26,13 +26,13 @@ data["network"] = network data["temp"] = temp - var/list/servers = list() + var/list/serverData = list() for(var/obj/machinery/telecomms/T in servers) - servers.Add(list(list( + serverData.Add(list(list( "id" = T.id, "name" = T.name, ))) - data["servers"] = servers + data["servers"] = serverData data["selectedServer"] = null if(SelectedServer) diff --git a/code/game/machinery/telecomms/telemonitor.dm b/code/game/machinery/telecomms/telemonitor.dm index 5a79f08be0..384790e60d 100644 --- a/code/game/machinery/telecomms/telemonitor.dm +++ b/code/game/machinery/telecomms/telemonitor.dm @@ -26,13 +26,13 @@ data["network"] = network data["temp"] = temp - var/list/machinelist = list() + var/list/machinelistData = list() for(var/obj/machinery/telecomms/T in machinelist) - machinelist.Add(list(list( + machinelistData.Add(list(list( "id" = T.id, "name" = T.name, ))) - data["machinelist"] = machinelist + data["machinelist"] = machinelistData data["selectedMachine"] = null if(SelectedMachine) diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index bc0c2dca14..bdaf5f7d75 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -107,6 +107,8 @@ var/tip_timer // reference to timer id for a tooltip we might open soon + var/no_random_knockdown = FALSE //stops item from being able to randomly knock people down in combat + /obj/item/New() ..() if(embed_chance < 0) diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm index 4195d1db98..a9bc3afe6d 100644 --- a/code/game/objects/items/devices/communicator/messaging.dm +++ b/code/game/objects/items/devices/communicator/messaging.dm @@ -95,7 +95,20 @@ L = loc if(L) - to_chat(L, "[bicon(src)] Message from [who].") + to_chat(L, "[bicon(src)] Message from [who]: \"[text]\" (Reply)") + +// This is the only Topic the communicators really uses +/obj/item/device/communicator/Topic(href, href_list) + switch(href_list["action"]) + if("Reply") + var/obj/item/device/communicator/comm = locate(href_list["target"]) + var/message = input(usr, "Enter your message below.", "Reply") + + if(message) + exonet.send_message(comm.exonet.address, "text", message) + im_list += list(list("address" = exonet.address, "to_address" = comm.exonet.address, "im" = message)) + log_pda("(COMM: [src]) sent \"[message]\" to [exonet.get_atom_from_address(comm.exonet.address)]", usr) + to_chat(usr, "[bicon(src)] Sent message to [comm.owner], \"[message]\"") // Verb: text_communicator() // Parameters: None diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index f743ab87f2..0a73ea74c6 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -14,6 +14,9 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard) var/obj/item/device/radio/radio var/looking_for_personality = 0 var/mob/living/silicon/pai/pai + var/image/screen_layer + var/screen_color = "#00ff0d" + var/last_notify = 0 /obj/item/device/paicard/relaymove(var/mob/user, var/direction) if(user.stat || user.stunned) @@ -42,19 +45,52 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard) if(choice == "No") return ..() - var/pai_name = input(user, "Choose your character's name", "Character Name") as text - var/actual_pai_name = sanitize_name(pai_name, ,1) - if(isnull(actual_pai_name)) - return ..() - + choice = tgui_alert(user, "Do you want to load your pAI data?", "Load", list("Yes", "No")) + var/actual_pai_name var/turf/location = get_turf(src) - var/obj/item/device/paicard/card = new(location) - var/mob/living/silicon/pai/new_pai = new(card) + if(choice == "No") + var/pai_name = input(user, "Choose your character's name", "Character Name") as text + actual_pai_name = sanitize_name(pai_name, ,1) + if(isnull(actual_pai_name)) + return ..() + if(istype(src , /obj/item/device/paicard/typeb)) + var/obj/item/device/paicard/typeb/card = new(location) + var/mob/living/silicon/pai/new_pai = new(card) + new_pai.key = user.key + card.setPersonality(new_pai) + new_pai.SetName(actual_pai_name) + else + var/obj/item/device/paicard/card = new(location) + var/mob/living/silicon/pai/new_pai = new(card) + new_pai.key = user.key + card.setPersonality(new_pai) + new_pai.SetName(actual_pai_name) + + if(choice == "Yes") + if(istype(src , /obj/item/device/paicard/typeb)) + var/obj/item/device/paicard/typeb/card = new(location) + var/mob/living/silicon/pai/new_pai = new(card) + new_pai.key = user.key + card.setPersonality(new_pai) + if(!new_pai.savefile_load(new_pai)) + var/pai_name = input(new_pai, "Choose your character's name", "Character Name") as text + actual_pai_name = sanitize_name(pai_name, ,1) + if(isnull(actual_pai_name)) + return ..() + else + var/obj/item/device/paicard/card = new(location) + var/mob/living/silicon/pai/new_pai = new(card) + new_pai.key = user.key + card.setPersonality(new_pai) + if(!new_pai.savefile_load(new_pai)) + var/pai_name = input(new_pai, "Choose your character's name", "Character Name") as text + actual_pai_name = sanitize_name(pai_name, ,1) + if(isnull(actual_pai_name)) + return ..() + qdel(src) - new_pai.key = user.key - card.setPersonality(new_pai) - new_pai.SetName(actual_pai_name) return ..() + // VOREStation Edit End /obj/item/device/paicard/attack_self(mob/user) @@ -214,6 +250,7 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard) else // dat += "Radio Uplink
" dat += "Radio firmware not loaded. Please install a pAI personality to load firmware.
" + /* - //A button for instantly deleting people from the game is lame, especially considering that pAIs on our server tend to activate without a master. dat += {"
Wipe current pAI personality @@ -221,6 +258,7 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard)
"} + */ else if(looking_for_personality) dat += {" @@ -304,40 +342,48 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard) /obj/item/device/paicard/proc/setPersonality(mob/living/silicon/pai/personality) src.pai = personality - add_overlay("pai-happy") + setEmotion(1) /obj/item/device/paicard/proc/removePersonality() src.pai = null cut_overlays() - add_overlay("pai-off") + setEmotion(16) /obj/item/device/paicard var/current_emotion = 1 /obj/item/device/paicard/proc/setEmotion(var/emotion) if(pai) cut_overlays() + qdel(screen_layer) + screen_layer = null switch(emotion) - if(1) add_overlay("pai-happy") - if(2) add_overlay("pai-cat") - if(3) add_overlay("pai-extremely-happy") - if(4) add_overlay("pai-face") - if(5) add_overlay("pai-laugh") - if(6) add_overlay("pai-off") - if(7) add_overlay("pai-sad") - if(8) add_overlay("pai-angry") - if(9) add_overlay("pai-what") - if(10) add_overlay("pai-neutral") - if(11) add_overlay("pai-silly") - if(12) add_overlay("pai-nose") - if(13) add_overlay("pai-smirk") - if(14) add_overlay("pai-exclamation") - if(15) add_overlay("pai-question") + if(1) screen_layer = image(icon, "pai-neutral") + if(2) screen_layer = image(icon, "pai-what") + if(3) screen_layer = image(icon, "pai-happy") + if(4) screen_layer = image(icon, "pai-cat") + if(5) screen_layer = image(icon, "pai-extremely-happy") + if(6) screen_layer = image(icon, "pai-face") + if(7) screen_layer = image(icon, "pai-laugh") + if(8) screen_layer = image(icon, "pai-sad") + if(9) screen_layer = image(icon, "pai-angry") + if(10) screen_layer = image(icon, "pai-silly") + if(11) screen_layer = image(icon, "pai-nose") + if(12) screen_layer = image(icon, "pai-smirk") + if(13) screen_layer = image(icon, "pai-exclamation") + if(14) screen_layer = image(icon, "pai-question") + if(15) screen_layer = image(icon, "pai-blank") + if(16) screen_layer = image(icon, "pai-off") + + screen_layer.color = pai.eye_color + add_overlay(screen_layer) current_emotion = emotion /obj/item/device/paicard/proc/alertUpdate() - var/turf/T = get_turf_or_move(src.loc) - for (var/mob/M in viewers(T)) - M.show_message("\The [src] flashes a message across its screen, \"Additional personalities available for download.\"", 3, "\The [src] bleeps electronically.", 2) + if(pai) + return + if(last_notify == 0 || (5 MINUTES <= world.time - last_notify)) + audible_message("\The [src] flashes a message across its screen, \"Additional personalities available for download.\"", hearing_distance = world.view, runemessage = "bleeps!") + last_notify = world.time /obj/item/device/paicard/emp_act(severity) for(var/mob/M in src) @@ -396,4 +442,21 @@ GLOBAL_LIST_BOILERPLATE(all_pai_cards, /obj/item/device/paicard) name = initial(src.name) to_chat(AI, span_notice("You feel a tad claustrophobic as your mind closes back into your card, ejecting from \the [initial(src.name)].")) if(user) - to_chat(user, span_notice("You eject the card from \the [initial(src.name)].")) \ No newline at end of file + to_chat(user, span_notice("You eject the card from \the [initial(src.name)].")) + +/obj/item/device/paicard/typeb + name = "personal AI device" + icon = 'icons/obj/paicard.dmi' + +/obj/random/paicard + name = "personal AI device spawner" + icon = 'icons/obj/paicard.dmi' + icon_state = "pai" + +/obj/random/paicard/item_to_spawn() + return pick(/obj/item/device/paicard ,/obj/item/device/paicard/typeb) + +/obj/item/device/paicard/digest_act(var/atom/movable/item_storage = null) + if(!pai.digestable) + return + . = ..() diff --git a/code/game/objects/items/devices/scanners/gas.dm b/code/game/objects/items/devices/scanners/gas.dm new file mode 100644 index 0000000000..ca35911448 --- /dev/null +++ b/code/game/objects/items/devices/scanners/gas.dm @@ -0,0 +1,36 @@ +/obj/item/device/analyzer + name = "gas analyzer" + desc = "A hand-held environmental scanner which reports current gas levels." + icon_state = "atmos" + item_state = "analyzer" + w_class = ITEMSIZE_SMALL + slot_flags = SLOT_BELT + throwforce = 5 + throw_speed = 4 + throw_range = 20 + + matter = list(MAT_STEEL = 30,MAT_GLASS = 20) + + origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1) + +/obj/item/device/analyzer/atmosanalyze(var/mob/user) + var/air = user.return_air() + if (!air) + return + + return atmosanalyzer_scan(src, air, user) + +/obj/item/device/analyzer/attack_self(mob/user as mob) + if (user.stat) + return + if (!(ishuman(user) || ticker) && ticker.mode.name != "monkey") + to_chat(usr, "You don't have the dexterity to do this!") + return + + analyze_gases(src, user) + return + +/obj/item/device/analyzer/afterattack(var/obj/O, var/mob/user, var/proximity) + if(proximity) + analyze_gases(O, user) + return \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners/halogen.dm b/code/game/objects/items/devices/scanners/halogen.dm new file mode 100644 index 0000000000..f2f302c334 --- /dev/null +++ b/code/game/objects/items/devices/scanners/halogen.dm @@ -0,0 +1,20 @@ +/obj/item/device/halogen_counter + name = "halogen counter" + icon_state = "eftpos" + desc = "A hand-held halogen counter, used to detect the level of irradiation of living beings." + w_class = ITEMSIZE_SMALL + origin_tech = list(TECH_MAGNET = 1, TECH_BIO = 2) + throwforce = 0 + throw_speed = 3 + throw_range = 7 + +/obj/item/device/halogen_counter/attack(mob/living/M as mob, mob/living/user as mob) + if(!iscarbon(M)) + to_chat(user, "This device can only scan organic beings!") + return + user.visible_message("\The [user] has analyzed [M]'s radiation levels!", "Analyzing Results for [M]:") + if(M.radiation) + to_chat(user, "Radiation Level: [M.radiation]") + else + to_chat(user, "No radiation detected.") + return \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners.dm b/code/game/objects/items/devices/scanners/health.dm similarity index 70% rename from code/game/objects/items/devices/scanners.dm rename to code/game/objects/items/devices/scanners/health.dm index dd40b46c32..98aa6ec2db 100644 --- a/code/game/objects/items/devices/scanners.dm +++ b/code/game/objects/items/devices/scanners/health.dm @@ -1,548 +1,327 @@ -#define DEFIB_TIME_LIMIT (10 MINUTES) //VOREStation addition- past this many seconds, defib is useless. -/* -CONTAINS: -T-RAY -DETECTIVE SCANNER -HEALTH ANALYZER -GAS ANALYZER - Analyzes atmosphere, container -MASS SPECTROMETER -REAGENT SCANNER -HALOGEN COUNTER - Radcount on mobs -*/ - - -/obj/item/device/healthanalyzer - name = "health analyzer" - desc = "A hand-held body scanner able to distinguish vital signs of the subject." - icon_state = "health" - item_state = "healthanalyzer" - slot_flags = SLOT_BELT - throwforce = 3 - w_class = ITEMSIZE_SMALL - throw_speed = 5 - throw_range = 10 - matter = list(MAT_STEEL = 200) - origin_tech = list(TECH_MAGNET = 1, TECH_BIO = 1) - var/mode = 1; - var/advscan = 0 - var/showadvscan = 1 - -/obj/item/device/healthanalyzer/New() - if(advscan >= 1) - verbs += /obj/item/device/healthanalyzer/proc/toggle_adv - ..() - -/obj/item/device/healthanalyzer/do_surgery(mob/living/M, mob/living/user) - if(user.a_intent != I_HELP) //in case it is ever used as a surgery tool - return ..() - scan_mob(M, user) //default surgery behaviour is just to scan as usual - return 1 - -/obj/item/device/healthanalyzer/attack(mob/living/M, mob/living/user) - scan_mob(M, user) - -/obj/item/device/healthanalyzer/proc/scan_mob(mob/living/M, mob/living/user) - var/dat = "" - if ((CLUMSY in user.mutations) && prob(50)) - user.visible_message("\The [user] has analyzed the floor's vitals!", "You try to analyze the floor's vitals!") - dat += "Analyzing Results for the floor:
" - dat += "Overall Status: Healthy
" - dat += "\tDamage Specifics: 0-0-0-0
" - dat += "Key: Suffocation/Toxin/Burns/Brute
" - dat += "Body Temperature: ???" - user.show_message("[dat]", 1) - return - if (!(ishuman(user) || ticker) && ticker.mode.name != "monkey") - to_chat(user, "You don't have the dexterity to do this!") - return - - flick("[icon_state]-scan", src) //makes it so that it plays the scan animation on a succesful scan - user.visible_message("[user] has analyzed [M]'s vitals.","You have analyzed [M]'s vitals.") - - if (!ishuman(M) || M.isSynthetic()) - //these sensors are designed for organic life - dat += "Analyzing Results for ERROR:\n\tOverall Status: ERROR
" - dat += "\tKey: Suffocation/Toxin/Burns/Brute
" - dat += "\tDamage Specifics: ? - ? - ? - ?
" - dat += "Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)

" - dat += "Warning: Blood Level ERROR: --% --cl. Type: ERROR
" - dat += "Subject's pulse: -- bpm." - user.show_message(dat, 1) - return - - var/fake_oxy = max(rand(1,40), M.getOxyLoss(), (300 - (M.getToxLoss() + M.getFireLoss() + M.getBruteLoss()))) - var/OX = M.getOxyLoss() > 50 ? "[M.getOxyLoss()]" : M.getOxyLoss() - var/TX = M.getToxLoss() > 50 ? "[M.getToxLoss()]" : M.getToxLoss() - var/BU = M.getFireLoss() > 50 ? "[M.getFireLoss()]" : M.getFireLoss() - var/BR = M.getBruteLoss() > 50 ? "[M.getBruteLoss()]" : M.getBruteLoss() - if(M.status_flags & FAKEDEATH) - OX = fake_oxy > 50 ? "[fake_oxy]" : fake_oxy - dat += "Analyzing Results for [M]:
" - dat += "Overall Status: dead
" - else - dat += "Analyzing Results for [M]:\n\t Overall Status: [M.stat > 1 ? "dead" : "[round((M.health/M.getMaxHealth())*100) ]% healthy"]
" - dat += "\tKey: Suffocation/Toxin/Burns/Brute
" - dat += "\tDamage Specifics: [OX] - [TX] - [BU] - [BR]
" - dat += "Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)

" - //VOREStation edit/addition starts - if(M.timeofdeath && (M.stat == DEAD || (M.status_flags & FAKEDEATH))) - dat += "Time of Death: [worldtime2stationtime(M.timeofdeath)]
" - var/tdelta = round(world.time - M.timeofdeath) - if(tdelta < (DEFIB_TIME_LIMIT * 10)) - dat += "Subject died [DisplayTimeText(tdelta)] ago - resuscitation may be possible!
" - //VOREStation edit/addition ends - if(istype(M, /mob/living/carbon/human) && mode == 1) - var/mob/living/carbon/human/H = M - var/list/damaged = H.get_damaged_organs(1,1) - dat += "Localized Damage, Brute/Burn:
" - if(length(damaged)>0) - for(var/obj/item/organ/external/org in damaged) - if(org.robotic >= ORGAN_ROBOT) - continue - else - dat += " [capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : 0]" - dat += "[(org.status & ORGAN_BLEEDING)?"\[Bleeding\]":""] - " - dat += "[(org.burn_dam > 0) ? "[org.burn_dam]" : 0]
" - else - dat += " Limbs are OK.
" - - OX = M.getOxyLoss() > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" - TX = M.getToxLoss() > 50 ? "Dangerous amount of toxins detected" : "Subject bloodstream toxin level minimal" - BU = M.getFireLoss() > 50 ? "Severe burn damage detected" : "Subject burn injury status O.K" - BR = M.getBruteLoss() > 50 ? "Severe anatomical damage detected" : "Subject brute-force injury status O.K" - if(M.status_flags & FAKEDEATH) - OX = fake_oxy > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" - dat += "[OX] | [TX] | [BU] | [BR]
" - if(M.radiation) - if(advscan >= 2 && showadvscan == 1) - var/severity = "" - if(M.radiation >= 75) - severity = "Critical" - else if(M.radiation >= 50) - severity = "Severe" - else if(M.radiation >= 25) - severity = "Moderate" - else if(M.radiation >= 1) - severity = "Low" - dat += "[severity] levels of radiation detected. [(severity == "Critical") ? " Immediate treatment advised." : ""]
" - else - dat += "Radiation detected.
" - if(iscarbon(M)) - var/mob/living/carbon/C = M - if(C.reagents.total_volume) - var/unknown = 0 - var/reagentdata[0] - var/unknownreagents[0] - for(var/datum/reagent/R as anything in C.reagents.reagent_list) - if(R.scannable) - reagentdata["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" - else - unknown++ - unknownreagents["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" - if(reagentdata.len) - dat += "Beneficial reagents detected in subject's blood:
" - for(var/d in reagentdata) - dat += reagentdata[d] - if(unknown) - if(advscan >= 3 && showadvscan == 1) - dat += "Warning: Non-medical reagent[(unknown>1)?"s":""] detected in subject's blood:
" - for(var/d in unknownreagents) - dat += unknownreagents[d] - else - dat += "Warning: Unknown substance[(unknown>1)?"s":""] detected in subject's blood.
" - if(C.ingested && C.ingested.total_volume) - var/unknown = 0 - var/stomachreagentdata[0] - var/stomachunknownreagents[0] - for(var/datum/reagent/R as anything in C.ingested.reagent_list) - if(R.scannable) - stomachreagentdata["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" - if (advscan == 0 || showadvscan == 0) - dat += "[R.name] found in subject's stomach.
" - else - ++unknown - stomachunknownreagents["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" - if(advscan >= 1 && showadvscan == 1) - dat += "Beneficial reagents detected in subject's stomach:
" - for(var/d in stomachreagentdata) - dat += stomachreagentdata[d] - if(unknown) - if(advscan >= 3 && showadvscan == 1) - dat += "Warning: Non-medical reagent[(unknown > 1)?"s":""] found in subject's stomach:
" - for(var/d in stomachunknownreagents) - dat += stomachunknownreagents[d] - else - dat += "Unknown substance[(unknown > 1)?"s":""] found in subject's stomach.
" - if(C.touching && C.touching.total_volume) - var/unknown = 0 - var/touchreagentdata[0] - var/touchunknownreagents[0] - for(var/datum/reagent/R as anything in C.touching.reagent_list) - if(R.scannable) - touchreagentdata["[R.id]"] = "\t[round(C.touching.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.can_overdose_touch && R.volume > R.overdose) ? " - Overdose" : ""]
" - if (advscan == 0 || showadvscan == 0) - dat += "[R.name] found in subject's dermis.
" - else - ++unknown - touchunknownreagents["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.can_overdose_touch && R.volume > R.overdose) ? " - Overdose" : ""]
" - if(advscan >= 1 && showadvscan == 1) - dat += "Beneficial reagents detected in subject's dermis:
" - for(var/d in touchreagentdata) - dat += touchreagentdata[d] - if(unknown) - if(advscan >= 3 && showadvscan == 1) - dat += "Warning: Non-medical reagent[(unknown > 1)?"s":""] found in subject's dermis:
" - for(var/d in touchunknownreagents) - dat += touchunknownreagents[d] - else - dat += "Unknown substance[(unknown > 1)?"s":""] found in subject's dermis.
" - if(C.virus2.len) - for (var/ID in C.virus2) - if (ID in virusDB) - var/datum/data/record/V = virusDB[ID] - dat += "Warning: Pathogen [V.fields["name"]] detected in subject's blood. Known antigen : [V.fields["antigen"]]
" - else - dat += "Warning: Unknown pathogen detected in subject's blood.
" - if (M.getCloneLoss()) - dat += "Subject appears to have been imperfectly cloned.
" -// if (M.reagents && M.reagents.get_reagent_amount("inaprovaline")) -// user.show_message("Bloodstream Analysis located [M.reagents:get_reagent_amount("inaprovaline")] units of rejuvenation chemicals.") - if (M.has_brain_worms()) - dat += "Subject suffering from aberrant brain activity. Recommend further scanning.
" - else if (M.getBrainLoss() >= 60 || !M.has_brain()) - dat += "Subject is brain dead.
" - else if (M.getBrainLoss() >= 25) - dat += "Severe brain damage detected. Subject likely to have a traumatic brain injury.
" - else if (M.getBrainLoss() >= 10) - dat += "Significant brain damage detected. Subject may have had a concussion.
" - else if (M.getBrainLoss() >= 1 && advscan >= 2 && showadvscan == 1) - dat += "Minor brain damage detected.
" - if(ishuman(M)) - var/mob/living/carbon/human/H = M - for(var/obj/item/organ/internal/appendix/a in H.internal_organs) - var/severity = "" - if(a.inflamed > 3) - severity = "Severe" - else if(a.inflamed > 2) - severity = "Moderate" - else if(a.inflamed >= 1) - severity = "Mild" - if(severity) - dat += "[severity] inflammation detected in subject [a.name].
" - // Infections, fractures, and IB - var/basic_fracture = 0 // If it's a basic scanner - var/basic_ib = 0 // If it's a basic scanner - var/fracture_dat = "" // All the fractures - var/infection_dat = "" // All the infections - var/ib_dat = "" // All the IB - for(var/obj/item/organ/external/e in H.organs) - if(!e) - continue - // Broken limbs - if(e.status & ORGAN_BROKEN) - if((e.name in list(BP_L_ARM, BP_R_ARM, BP_L_LEG, BP_R_LEG, BP_HEAD, BP_TORSO, BP_GROIN)) && (!e.splinted)) - fracture_dat += "Unsecured fracture in subject [e.name]. Splinting recommended for transport.
" - else if(advscan >= 1 && showadvscan == 1) - fracture_dat += "Bone fractures detected in subject [e.name].
" - else - basic_fracture = 1 - // Infections - if(e.has_infected_wound()) - dat += "Infected wound detected in subject [e.name]. Disinfection recommended.
" - // IB - for(var/datum/wound/W in e.wounds) - if(W.internal) - if(advscan >= 1 && showadvscan == 1) - ib_dat += "Internal bleeding detected in subject [e.name].
" - else - basic_ib = 1 - if(basic_fracture) - fracture_dat += "Bone fractures detected. Advanced scanner required for location.
" - if(basic_ib) - ib_dat += "Internal bleeding detected. Advanced scanner required for location.
" - dat += fracture_dat - dat += infection_dat - dat += ib_dat - - // Blood level - if(M:vessel) - var/blood_volume = H.vessel.get_reagent_amount("blood") - var/blood_percent = round((blood_volume / H.species.blood_volume)*100) - var/blood_type = H.dna.b_type - if(blood_volume <= H.species.blood_volume*H.species.blood_level_danger) - dat += "Warning: Blood Level CRITICAL: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" - else if(blood_volume <= H.species.blood_volume*H.species.blood_level_warning) - dat += "Warning: Blood Level VERY LOW: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" - else if(blood_volume <= H.species.blood_volume*H.species.blood_level_safe) - dat += "Warning: Blood Level LOW: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" - else - dat += "Blood Level Normal: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" - dat += "Subject's pulse: [H.get_pulse(GETPULSE_TOOL)] bpm.
" // VORE Edit: Missed a linebreak here. - if(istype(H.species, /datum/species/xenochimera)) // VOREStation Edit Start: Visible feedback for medmains on Xenochimera. - if(H.stat == DEAD && H.revive_ready == REVIVING_READY && !H.hasnutriment()) - dat += "WARNING: Protein levels low. Subject incapable of reconstitution." - else if(H.revive_ready == REVIVING_NOW) - dat += "Subject is undergoing form reconstruction. Estimated time to finish is in: [round((H.revive_finished - world.time) / 10)] seconds." - else if(H.revive_ready == REVIVING_DONE) - dat += "Subject is ready to hatch. Transfer to dark room for holding with food available." - else if(H.stat == DEAD) - dat+= "WARNING: Defib will cause extreme pain and set subject feral. Sedation recommended prior to defibrillation." - else // If they bop them and they're not dead or reviving, give 'em a little notice. - dat += "Subject is a Xenochimera. Treat accordingly." - // VOREStation Edit End - user.show_message(dat, 1) - -/obj/item/device/healthanalyzer/verb/toggle_mode() - set name = "Switch Verbosity" - set category = "Object" - - mode = !mode - switch (mode) - if(1) - to_chat(usr, "The scanner now shows specific limb damage.") - if(0) - to_chat(usr, "The scanner no longer shows limb damage.") - -/obj/item/device/healthanalyzer/proc/toggle_adv() - set name = "Toggle Advanced Scan" - set category = "Object" - - showadvscan = !showadvscan - switch (showadvscan) - if(1) - to_chat(usr, "The scanner will now perform an advanced analysis.") - if(0) - to_chat(usr, "The scanner will now perform a basic analysis.") - -/obj/item/device/healthanalyzer/improved //reports bone fractures, IB, quantity of beneficial reagents in stomach; also regular health analyzer stuff - name = "improved health analyzer" - desc = "A miracle of medical technology, this handheld scanner can produce an accurate and specific report of a patient's biosigns." - advscan = 1 - origin_tech = list(TECH_MAGNET = 5, TECH_BIO = 6) - icon_state = "health1" - -/obj/item/device/healthanalyzer/advanced //reports all of the above, as well as radiation severity and minor brain damage - name = "advanced health analyzer" - desc = "An even more advanced handheld health scanner, complete with a full biosign monitor and on-board radiation and neurological analysis suites." - advscan = 2 - origin_tech = list(TECH_MAGNET = 6, TECH_BIO = 7) - icon_state = "health2" - -/obj/item/device/healthanalyzer/phasic //reports all of the above, as well as name and quantity of nonmed reagents in stomach - name = "phasic health analyzer" - desc = "Possibly the most advanced health analyzer to ever have existed, utilising bluespace technology to determine almost everything worth knowing about a patient." - advscan = 3 - origin_tech = list(TECH_MAGNET = 7, TECH_BIO = 8) - icon_state = "health3" - -/obj/item/device/analyzer - name = "analyzer" - desc = "A hand-held environmental scanner which reports current gas levels." - icon_state = "atmos" - item_state = "analyzer" - w_class = ITEMSIZE_SMALL - slot_flags = SLOT_BELT - throwforce = 5 - throw_speed = 4 - throw_range = 20 - - matter = list(MAT_STEEL = 30,MAT_GLASS = 20) - - origin_tech = list(TECH_MAGNET = 1, TECH_ENGINEERING = 1) - -/obj/item/device/analyzer/atmosanalyze(var/mob/user) - var/air = user.return_air() - if (!air) - return - - return atmosanalyzer_scan(src, air, user) - -/obj/item/device/analyzer/attack_self(mob/user as mob) - if (user.stat) - return - if (!(ishuman(user) || ticker) && ticker.mode.name != "monkey") - to_chat(usr, "You don't have the dexterity to do this!") - return - - analyze_gases(src, user) - return - -/obj/item/device/analyzer/afterattack(var/obj/O, var/mob/user, var/proximity) - if(proximity) - analyze_gases(O, user) - return - - -/obj/item/device/mass_spectrometer - name = "mass spectrometer" - desc = "A hand-held mass spectrometer which identifies trace chemicals in a blood sample." - icon_state = "spectrometer" - w_class = ITEMSIZE_SMALL - flags = OPENCONTAINER - slot_flags = SLOT_BELT - throwforce = 5 - throw_speed = 4 - throw_range = 20 - - matter = list(MAT_STEEL = 30,MAT_GLASS = 20) - - origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2) - var/details = 0 - var/recent_fail = 0 - -/obj/item/device/mass_spectrometer/New() - ..() - var/datum/reagents/R = new/datum/reagents(5) - reagents = R - R.my_atom = src - -/obj/item/device/mass_spectrometer/on_reagent_change() - if(reagents.total_volume) - icon_state = initial(icon_state) + "_s" - else - icon_state = initial(icon_state) - -/obj/item/device/mass_spectrometer/attack_self(mob/user as mob) - if (user.stat) - return - if (!(ishuman(user) || ticker) && ticker.mode.name != "monkey") - to_chat(user, "You don't have the dexterity to do this!") - return - if(reagents.total_volume) - var/list/blood_traces = list() - for(var/datum/reagent/R in reagents.reagent_list) - if(R.id != "blood") - reagents.clear_reagents() - to_chat(user, "The sample was contaminated! Please insert another sample") - return - else - blood_traces = params2list(R.data["trace_chem"]) - break - var/dat = "Trace Chemicals Found: " - for(var/R in blood_traces) - if(details) - dat += "[R] ([blood_traces[R]] units) " - else - dat += "[R] " - to_chat(user, "[dat]") - reagents.clear_reagents() - return - -/obj/item/device/mass_spectrometer/adv - name = "advanced mass spectrometer" - icon_state = "adv_spectrometer" - details = 1 - origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2) - -/obj/item/device/reagent_scanner - name = "reagent scanner" - desc = "A hand-held reagent scanner which identifies chemical agents." - icon_state = "spectrometer" - item_state = "analyzer" - w_class = ITEMSIZE_SMALL - slot_flags = SLOT_BELT - throwforce = 5 - throw_speed = 4 - throw_range = 20 - matter = list(MAT_STEEL = 30,MAT_GLASS = 20) - - origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2) - var/details = 0 - var/recent_fail = 0 - -/obj/item/device/reagent_scanner/afterattack(obj/O, mob/living/user, proximity) - if(!proximity || user.stat || !istype(O)) - return - if(!istype(user)) - return - - if(!isnull(O.reagents)) - if(!(O.flags & OPENCONTAINER)) // The idea is that the scanner has to touch the reagents somehow. This is done to prevent cheesing unidentified autoinjectors. - to_chat(user, span("warning", "\The [O] is sealed, and cannot be scanned by \the [src] until unsealed.")) - return - - var/dat = "" - if(O.reagents.reagent_list.len > 0) - var/one_percent = O.reagents.total_volume / 100 - for (var/datum/reagent/R in O.reagents.reagent_list) - dat += "\n \t " + span("notice", "[R][details ? ": [R.volume / one_percent]%" : ""]") - if(dat) - to_chat(user, span("notice", "Chemicals found: [dat]")) - else - to_chat(user, span("notice", "No active chemical agents found in [O].")) - else - to_chat(user, span("notice", "No significant chemical agents found in [O].")) - - return - -/obj/item/device/reagent_scanner/adv - name = "advanced reagent scanner" - icon_state = "adv_spectrometer" - details = 1 - origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2) - -/obj/item/device/slime_scanner - name = "slime scanner" - icon_state = "xenobio" - item_state = "xenobio" - origin_tech = list(TECH_BIO = 1) - w_class = ITEMSIZE_SMALL - throwforce = 0 - throw_speed = 3 - throw_range = 7 - matter = list(MAT_STEEL = 30,MAT_GLASS = 20) - -/obj/item/device/slime_scanner/attack(mob/living/M as mob, mob/living/user as mob) - if(!istype(M, /mob/living/simple_mob/slime/xenobio)) - to_chat(user, "This device can only scan lab-grown slimes!") - return - var/mob/living/simple_mob/slime/xenobio/S = M - user.show_message("Slime scan results:
[S.slime_color] [S.is_adult ? "adult" : "baby"] slime
Health: [S.health]
Mutation Probability: [S.mutation_chance]") - - var/list/mutations = list() - for(var/potential_color in S.slime_mutation) - var/mob/living/simple_mob/slime/xenobio/slime = potential_color - mutations.Add(initial(slime.slime_color)) - user.show_message("Potental to mutate into [english_list(mutations)] colors.
Extract potential: [S.cores]
Nutrition: [S.nutrition]/[S.max_nutrition]") - - if (S.nutrition < S.get_starve_nutrition()) - user.show_message("Warning: Subject is starving!") - else if (S.nutrition < S.get_hunger_nutrition()) - user.show_message("Warning: Subject is hungry.") - user.show_message("Electric change strength: [S.power_charge]") - - if(S.has_AI()) - var/datum/ai_holder/simple_mob/xenobio_slime/AI = S.ai_holder - if(AI.resentment) - user.show_message("Warning: Subject is harboring resentment.") - if(AI.rabid) - user.show_message("Subject is enraged and extremely dangerous!") - if(S.harmless) - user.show_message("Subject has been pacified.") - if(S.unity) - user.show_message("Subject is friendly to other slime colors.") - - user.show_message("Growth progress: [S.amount_grown]/10") - -/obj/item/device/halogen_counter - name = "halogen counter" - icon_state = "eftpos" - desc = "A hand-held halogen counter, used to detect the level of irradiation of living beings." - w_class = ITEMSIZE_SMALL - origin_tech = list(TECH_MAGNET = 1, TECH_BIO = 2) - throwforce = 0 - throw_speed = 3 - throw_range = 7 - -/obj/item/device/halogen_counter/attack(mob/living/M as mob, mob/living/user as mob) - if(!iscarbon(M)) - to_chat(user, "This device can only scan organic beings!") - return - user.visible_message("\The [user] has analyzed [M]'s radiation levels!", "Analyzing Results for [M]:") - if(M.radiation) - to_chat(user, "Radiation Level: [M.radiation]") - else - to_chat(user, "No radiation detected.") - return - +#define DEFIB_TIME_LIMIT (10 MINUTES) //VOREStation addition- past this many seconds, defib is useless. + +/obj/item/device/healthanalyzer + name = "health analyzer" + desc = "A hand-held body scanner able to distinguish vital signs of the subject." + icon_state = "health" + item_state = "healthanalyzer" + slot_flags = SLOT_BELT + throwforce = 3 + w_class = ITEMSIZE_SMALL + throw_speed = 5 + throw_range = 10 + matter = list(MAT_STEEL = 200) + origin_tech = list(TECH_MAGNET = 1, TECH_BIO = 1) + var/mode = 1; + var/advscan = 0 + var/showadvscan = 1 + +/obj/item/device/healthanalyzer/New() + if(advscan >= 1) + verbs += /obj/item/device/healthanalyzer/proc/toggle_adv + ..() + +/obj/item/device/healthanalyzer/do_surgery(mob/living/M, mob/living/user) + if(user.a_intent != I_HELP) //in case it is ever used as a surgery tool + return ..() + scan_mob(M, user) //default surgery behaviour is just to scan as usual + return 1 + +/obj/item/device/healthanalyzer/attack(mob/living/M, mob/living/user) + scan_mob(M, user) + +/obj/item/device/healthanalyzer/proc/scan_mob(mob/living/M, mob/living/user) + var/dat = "" + if ((CLUMSY in user.mutations) && prob(50)) + user.visible_message("\The [user] has analyzed the floor's vitals!", "You try to analyze the floor's vitals!") + dat += "Analyzing Results for the floor:
" + dat += "Overall Status: Healthy
" + dat += "\tDamage Specifics: 0-0-0-0
" + dat += "Key: Suffocation/Toxin/Burns/Brute
" + dat += "Body Temperature: ???" + user.show_message("[dat]", 1) + return + if (!(ishuman(user) || ticker) && ticker.mode.name != "monkey") + to_chat(user, "You don't have the dexterity to do this!") + return + + flick("[icon_state]-scan", src) //makes it so that it plays the scan animation on a succesful scan + user.visible_message("[user] has analyzed [M]'s vitals.","You have analyzed [M]'s vitals.") + + if (!ishuman(M) || M.isSynthetic()) + //these sensors are designed for organic life + dat += "Analyzing Results for ERROR:\n\tOverall Status: ERROR
" + dat += "\tKey: Suffocation/Toxin/Burns/Brute
" + dat += "\tDamage Specifics: ? - ? - ? - ?
" + dat += "Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)

" + dat += "Warning: Blood Level ERROR: --% --cl. Type: ERROR
" + dat += "Subject's pulse: -- bpm." + user.show_message(dat, 1) + return + + var/fake_oxy = max(rand(1,40), M.getOxyLoss(), (300 - (M.getToxLoss() + M.getFireLoss() + M.getBruteLoss()))) + var/OX = M.getOxyLoss() > 50 ? "[M.getOxyLoss()]" : M.getOxyLoss() + var/TX = M.getToxLoss() > 50 ? "[M.getToxLoss()]" : M.getToxLoss() + var/BU = M.getFireLoss() > 50 ? "[M.getFireLoss()]" : M.getFireLoss() + var/BR = M.getBruteLoss() > 50 ? "[M.getBruteLoss()]" : M.getBruteLoss() + if(M.status_flags & FAKEDEATH) + OX = fake_oxy > 50 ? "[fake_oxy]" : fake_oxy + dat += "Analyzing Results for [M]:
" + dat += "Overall Status: dead
" + else + dat += "Analyzing Results for [M]:\n\t Overall Status: [M.stat > 1 ? "dead" : "[round((M.health/M.getMaxHealth())*100) ]% healthy"]
" + dat += "\tKey: Suffocation/Toxin/Burns/Brute
" + dat += "\tDamage Specifics: [OX] - [TX] - [BU] - [BR]
" + dat += "Body Temperature: [M.bodytemperature-T0C]°C ([M.bodytemperature*1.8-459.67]°F)

" + //VOREStation edit/addition starts + if(M.timeofdeath && (M.stat == DEAD || (M.status_flags & FAKEDEATH))) + dat += "Time of Death: [worldtime2stationtime(M.timeofdeath)]
" + var/tdelta = round(world.time - M.timeofdeath) + if(tdelta < (DEFIB_TIME_LIMIT * 10)) + dat += "Subject died [DisplayTimeText(tdelta)] ago - resuscitation may be possible!
" + //VOREStation edit/addition ends + if(istype(M, /mob/living/carbon/human) && mode == 1) + var/mob/living/carbon/human/H = M + var/list/damaged = H.get_damaged_organs(1,1) + dat += "Localized Damage, Brute/Burn:
" + if(length(damaged)>0) + for(var/obj/item/organ/external/org in damaged) + if(org.robotic >= ORGAN_ROBOT) + continue + else + dat += " [capitalize(org.name)]: [(org.brute_dam > 0) ? "[org.brute_dam]" : 0]" + dat += "[(org.status & ORGAN_BLEEDING)?"\[Bleeding\]":""] - " + dat += "[(org.burn_dam > 0) ? "[org.burn_dam]" : 0]
" + else + dat += " Limbs are OK.
" + + OX = M.getOxyLoss() > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" + TX = M.getToxLoss() > 50 ? "Dangerous amount of toxins detected" : "Subject bloodstream toxin level minimal" + BU = M.getFireLoss() > 50 ? "Severe burn damage detected" : "Subject burn injury status O.K" + BR = M.getBruteLoss() > 50 ? "Severe anatomical damage detected" : "Subject brute-force injury status O.K" + if(M.status_flags & FAKEDEATH) + OX = fake_oxy > 50 ? "Severe oxygen deprivation detected" : "Subject bloodstream oxygen level normal" + dat += "[OX] | [TX] | [BU] | [BR]
" + if(M.radiation) + if(advscan >= 2 && showadvscan == 1) + var/severity = "" + if(M.radiation >= 75) + severity = "Critical" + else if(M.radiation >= 50) + severity = "Severe" + else if(M.radiation >= 25) + severity = "Moderate" + else if(M.radiation >= 1) + severity = "Low" + dat += "[severity] levels of radiation detected. [(severity == "Critical") ? " Immediate treatment advised." : ""]
" + else + dat += "Radiation detected.
" + if(iscarbon(M)) + var/mob/living/carbon/C = M + if(C.reagents.total_volume) + var/unknown = 0 + var/reagentdata[0] + var/unknownreagents[0] + for(var/datum/reagent/R as anything in C.reagents.reagent_list) + if(R.scannable) + reagentdata["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" + else + unknown++ + unknownreagents["[R.id]"] = "\t[round(C.reagents.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" + if(reagentdata.len) + dat += "Beneficial reagents detected in subject's blood:
" + for(var/d in reagentdata) + dat += reagentdata[d] + if(unknown) + if(advscan >= 3 && showadvscan == 1) + dat += "Warning: Non-medical reagent[(unknown>1)?"s":""] detected in subject's blood:
" + for(var/d in unknownreagents) + dat += unknownreagents[d] + else + dat += "Warning: Unknown substance[(unknown>1)?"s":""] detected in subject's blood.
" + if(C.ingested && C.ingested.total_volume) + var/unknown = 0 + var/stomachreagentdata[0] + var/stomachunknownreagents[0] + for(var/datum/reagent/R as anything in C.ingested.reagent_list) + if(R.scannable) + stomachreagentdata["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" + if (advscan == 0 || showadvscan == 0) + dat += "[R.name] found in subject's stomach.
" + else + ++unknown + stomachunknownreagents["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.volume > R.overdose) ? " - Overdose" : ""]
" + if(advscan >= 1 && showadvscan == 1) + dat += "Beneficial reagents detected in subject's stomach:
" + for(var/d in stomachreagentdata) + dat += stomachreagentdata[d] + if(unknown) + if(advscan >= 3 && showadvscan == 1) + dat += "Warning: Non-medical reagent[(unknown > 1)?"s":""] found in subject's stomach:
" + for(var/d in stomachunknownreagents) + dat += stomachunknownreagents[d] + else + dat += "Unknown substance[(unknown > 1)?"s":""] found in subject's stomach.
" + if(C.touching && C.touching.total_volume) + var/unknown = 0 + var/touchreagentdata[0] + var/touchunknownreagents[0] + for(var/datum/reagent/R as anything in C.touching.reagent_list) + if(R.scannable) + touchreagentdata["[R.id]"] = "\t[round(C.touching.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.can_overdose_touch && R.volume > R.overdose) ? " - Overdose" : ""]
" + if (advscan == 0 || showadvscan == 0) + dat += "[R.name] found in subject's dermis.
" + else + ++unknown + touchunknownreagents["[R.id]"] = "\t[round(C.ingested.get_reagent_amount(R.id), 1)]u [R.name][(R.overdose && R.can_overdose_touch && R.volume > R.overdose) ? " - Overdose" : ""]
" + if(advscan >= 1 && showadvscan == 1) + dat += "Beneficial reagents detected in subject's dermis:
" + for(var/d in touchreagentdata) + dat += touchreagentdata[d] + if(unknown) + if(advscan >= 3 && showadvscan == 1) + dat += "Warning: Non-medical reagent[(unknown > 1)?"s":""] found in subject's dermis:
" + for(var/d in touchunknownreagents) + dat += touchunknownreagents[d] + else + dat += "Unknown substance[(unknown > 1)?"s":""] found in subject's dermis.
" + if(C.virus2.len) + for (var/ID in C.virus2) + if (ID in virusDB) + var/datum/data/record/V = virusDB[ID] + dat += "Warning: Pathogen [V.fields["name"]] detected in subject's blood. Known antigen : [V.fields["antigen"]]
" + else + dat += "Warning: Unknown pathogen detected in subject's blood.
" + if (M.getCloneLoss()) + dat += "Subject appears to have been imperfectly cloned.
" +// if (M.reagents && M.reagents.get_reagent_amount("inaprovaline")) +// user.show_message("Bloodstream Analysis located [M.reagents:get_reagent_amount("inaprovaline")] units of rejuvenation chemicals.") + if (M.has_brain_worms()) + dat += "Subject suffering from aberrant brain activity. Recommend further scanning.
" + else if (M.getBrainLoss() >= 60 || !M.has_brain()) + dat += "Subject is brain dead.
" + else if (M.getBrainLoss() >= 25) + dat += "Severe brain damage detected. Subject likely to have a traumatic brain injury.
" + else if (M.getBrainLoss() >= 10) + dat += "Significant brain damage detected. Subject may have had a concussion.
" + else if (M.getBrainLoss() >= 1 && advscan >= 2 && showadvscan == 1) + dat += "Minor brain damage detected.
" + if(ishuman(M)) + var/mob/living/carbon/human/H = M + for(var/obj/item/organ/internal/appendix/a in H.internal_organs) + var/severity = "" + if(a.inflamed > 3) + severity = "Severe" + else if(a.inflamed > 2) + severity = "Moderate" + else if(a.inflamed >= 1) + severity = "Mild" + if(severity) + dat += "[severity] inflammation detected in subject [a.name].
" + // Infections, fractures, and IB + var/basic_fracture = 0 // If it's a basic scanner + var/basic_ib = 0 // If it's a basic scanner + var/fracture_dat = "" // All the fractures + var/infection_dat = "" // All the infections + var/ib_dat = "" // All the IB + for(var/obj/item/organ/external/e in H.organs) + if(!e) + continue + // Broken limbs + if(e.status & ORGAN_BROKEN) + if((e.name in list(BP_L_ARM, BP_R_ARM, BP_L_LEG, BP_R_LEG, BP_HEAD, BP_TORSO, BP_GROIN)) && (!e.splinted)) + fracture_dat += "Unsecured fracture in subject [e.name]. Splinting recommended for transport.
" + else if(advscan >= 1 && showadvscan == 1) + fracture_dat += "Bone fractures detected in subject [e.name].
" + else + basic_fracture = 1 + // Infections + if(e.has_infected_wound()) + dat += "Infected wound detected in subject [e.name]. Disinfection recommended.
" + // IB + for(var/datum/wound/W in e.wounds) + if(W.internal) + if(advscan >= 1 && showadvscan == 1) + ib_dat += "Internal bleeding detected in subject [e.name].
" + else + basic_ib = 1 + if(basic_fracture) + fracture_dat += "Bone fractures detected. Advanced scanner required for location.
" + if(basic_ib) + ib_dat += "Internal bleeding detected. Advanced scanner required for location.
" + dat += fracture_dat + dat += infection_dat + dat += ib_dat + + // Blood level + if(M:vessel) + var/blood_volume = H.vessel.get_reagent_amount("blood") + var/blood_percent = round((blood_volume / H.species.blood_volume)*100) + var/blood_type = H.dna.b_type + if(blood_volume <= H.species.blood_volume*H.species.blood_level_danger) + dat += "Warning: Blood Level CRITICAL: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" + else if(blood_volume <= H.species.blood_volume*H.species.blood_level_warning) + dat += "Warning: Blood Level VERY LOW: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" + else if(blood_volume <= H.species.blood_volume*H.species.blood_level_safe) + dat += "Warning: Blood Level LOW: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" + else + dat += "Blood Level Normal: [blood_percent]% [blood_volume]cl. Type: [blood_type]
" + dat += "Subject's pulse: [H.get_pulse(GETPULSE_TOOL)] bpm.
" // VORE Edit: Missed a linebreak here. + if(istype(H.species, /datum/species/xenochimera)) // VOREStation Edit Start: Visible feedback for medmains on Xenochimera. + if(H.stat == DEAD && H.revive_ready == REVIVING_READY && !H.hasnutriment()) + dat += "WARNING: Protein levels low. Subject incapable of reconstitution." + else if(H.revive_ready == REVIVING_NOW) + dat += "Subject is undergoing form reconstruction. Estimated time to finish is in: [round((H.revive_finished - world.time) / 10)] seconds." + else if(H.revive_ready == REVIVING_DONE) + dat += "Subject is ready to hatch. Transfer to dark room for holding with food available." + else if(H.stat == DEAD) + dat+= "WARNING: Defib will cause extreme pain and set subject feral. Sedation recommended prior to defibrillation." + else // If they bop them and they're not dead or reviving, give 'em a little notice. + dat += "Subject is a Xenochimera. Treat accordingly." + // VOREStation Edit End + user.show_message(dat, 1) + +/obj/item/device/healthanalyzer/verb/toggle_mode() + set name = "Switch Verbosity" + set category = "Object" + + mode = !mode + switch (mode) + if(1) + to_chat(usr, "The scanner now shows specific limb damage.") + if(0) + to_chat(usr, "The scanner no longer shows limb damage.") + +/obj/item/device/healthanalyzer/proc/toggle_adv() + set name = "Toggle Advanced Scan" + set category = "Object" + + showadvscan = !showadvscan + switch (showadvscan) + if(1) + to_chat(usr, "The scanner will now perform an advanced analysis.") + if(0) + to_chat(usr, "The scanner will now perform a basic analysis.") + +/obj/item/device/healthanalyzer/improved //reports bone fractures, IB, quantity of beneficial reagents in stomach; also regular health analyzer stuff + name = "improved health analyzer" + desc = "A miracle of medical technology, this handheld scanner can produce an accurate and specific report of a patient's biosigns." + advscan = 1 + origin_tech = list(TECH_MAGNET = 5, TECH_BIO = 6) + icon_state = "health1" + +/obj/item/device/healthanalyzer/advanced //reports all of the above, as well as radiation severity and minor brain damage + name = "advanced health analyzer" + desc = "An even more advanced handheld health scanner, complete with a full biosign monitor and on-board radiation and neurological analysis suites." + advscan = 2 + origin_tech = list(TECH_MAGNET = 6, TECH_BIO = 7) + icon_state = "health2" + +/obj/item/device/healthanalyzer/phasic //reports all of the above, as well as name and quantity of nonmed reagents in stomach + name = "phasic health analyzer" + desc = "Possibly the most advanced health analyzer to ever have existed, utilising bluespace technology to determine almost everything worth knowing about a patient." + advscan = 3 + origin_tech = list(TECH_MAGNET = 7, TECH_BIO = 8) + icon_state = "health3" + #undef DEFIB_TIME_LIMIT //VOREStation addition \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners/mass_spectrometer.dm b/code/game/objects/items/devices/scanners/mass_spectrometer.dm new file mode 100644 index 0000000000..fee41eb880 --- /dev/null +++ b/code/game/objects/items/devices/scanners/mass_spectrometer.dm @@ -0,0 +1,60 @@ +/obj/item/device/mass_spectrometer + name = "mass spectrometer" + desc = "A hand-held mass spectrometer which identifies trace chemicals in a blood sample." + icon_state = "spectrometer" + w_class = ITEMSIZE_SMALL + flags = OPENCONTAINER + slot_flags = SLOT_BELT + throwforce = 5 + throw_speed = 4 + throw_range = 20 + + matter = list(MAT_STEEL = 30,MAT_GLASS = 20) + + origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2) + var/details = 0 + var/recent_fail = 0 + +/obj/item/device/mass_spectrometer/New() + ..() + var/datum/reagents/R = new/datum/reagents(5) + reagents = R + R.my_atom = src + +/obj/item/device/mass_spectrometer/on_reagent_change() + if(reagents.total_volume) + icon_state = initial(icon_state) + "_s" + else + icon_state = initial(icon_state) + +/obj/item/device/mass_spectrometer/attack_self(mob/user as mob) + if (user.stat) + return + if (!(ishuman(user) || ticker) && ticker.mode.name != "monkey") + to_chat(user, "You don't have the dexterity to do this!") + return + if(reagents.total_volume) + var/list/blood_traces = list() + for(var/datum/reagent/R in reagents.reagent_list) + if(R.id != "blood") + reagents.clear_reagents() + to_chat(user, "The sample was contaminated! Please insert another sample") + return + else + blood_traces = params2list(R.data["trace_chem"]) + break + var/dat = "Trace Chemicals Found: " + for(var/R in blood_traces) + if(details) + dat += "[R] ([blood_traces[R]] units) " + else + dat += "[R] " + to_chat(user, "[dat]") + reagents.clear_reagents() + return + +/obj/item/device/mass_spectrometer/adv + name = "advanced mass spectrometer" + icon_state = "adv_spectrometer" + details = 1 + origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2) \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners/reagents.dm b/code/game/objects/items/devices/scanners/reagents.dm new file mode 100644 index 0000000000..8310d13fab --- /dev/null +++ b/code/game/objects/items/devices/scanners/reagents.dm @@ -0,0 +1,46 @@ +/obj/item/device/reagent_scanner + name = "reagent scanner" + desc = "A hand-held reagent scanner which identifies chemical agents." + icon_state = "spectrometer" + item_state = "analyzer" + w_class = ITEMSIZE_SMALL + slot_flags = SLOT_BELT + throwforce = 5 + throw_speed = 4 + throw_range = 20 + matter = list(MAT_STEEL = 30,MAT_GLASS = 20) + + origin_tech = list(TECH_MAGNET = 2, TECH_BIO = 2) + var/details = 0 + var/recent_fail = 0 + +/obj/item/device/reagent_scanner/afterattack(obj/O, mob/living/user, proximity) + if(!proximity || user.stat || !istype(O)) + return + if(!istype(user)) + return + + if(!isnull(O.reagents)) + if(!(O.flags & OPENCONTAINER)) // The idea is that the scanner has to touch the reagents somehow. This is done to prevent cheesing unidentified autoinjectors. + to_chat(user, span("warning", "\The [O] is sealed, and cannot be scanned by \the [src] until unsealed.")) + return + + var/dat = "" + if(O.reagents.reagent_list.len > 0) + var/one_percent = O.reagents.total_volume / 100 + for (var/datum/reagent/R in O.reagents.reagent_list) + dat += "\n \t " + span("notice", "[R][details ? ": [R.volume / one_percent]%" : ""]") + if(dat) + to_chat(user, span("notice", "Chemicals found: [dat]")) + else + to_chat(user, span("notice", "No active chemical agents found in [O].")) + else + to_chat(user, span("notice", "No significant chemical agents found in [O].")) + + return + +/obj/item/device/reagent_scanner/adv + name = "advanced reagent scanner" + icon_state = "adv_spectrometer" + details = 1 + origin_tech = list(TECH_MAGNET = 4, TECH_BIO = 2) \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners_vr.dm b/code/game/objects/items/devices/scanners/sleevemate.dm similarity index 99% rename from code/game/objects/items/devices/scanners_vr.dm rename to code/game/objects/items/devices/scanners/sleevemate.dm index 1722617106..5308e2c842 100644 --- a/code/game/objects/items/devices/scanners_vr.dm +++ b/code/game/objects/items/devices/scanners/sleevemate.dm @@ -300,4 +300,4 @@ var/global/mob/living/carbon/human/dummy/mannequin/sleevemate_mob if(stored_mind) icon_state = "[initial(icon_state)]_on" else - icon_state = initial(icon_state) + icon_state = initial(icon_state) \ No newline at end of file diff --git a/code/game/objects/items/devices/scanners/slime.dm b/code/game/objects/items/devices/scanners/slime.dm new file mode 100644 index 0000000000..adcdce06a0 --- /dev/null +++ b/code/game/objects/items/devices/scanners/slime.dm @@ -0,0 +1,42 @@ +/obj/item/device/slime_scanner + name = "slime scanner" + icon_state = "xenobio" + item_state = "xenobio" + origin_tech = list(TECH_BIO = 1) + w_class = ITEMSIZE_SMALL + throwforce = 0 + throw_speed = 3 + throw_range = 7 + matter = list(MAT_STEEL = 30,MAT_GLASS = 20) + +/obj/item/device/slime_scanner/attack(mob/living/M as mob, mob/living/user as mob) + if(!istype(M, /mob/living/simple_mob/slime/xenobio)) + to_chat(user, "This device can only scan lab-grown slimes!") + return + var/mob/living/simple_mob/slime/xenobio/S = M + user.show_message("Slime scan results:
[S.slime_color] [S.is_adult ? "adult" : "baby"] slime
Health: [S.health]
Mutation Probability: [S.mutation_chance]") + + var/list/mutations = list() + for(var/potential_color in S.slime_mutation) + var/mob/living/simple_mob/slime/xenobio/slime = potential_color + mutations.Add(initial(slime.slime_color)) + user.show_message("Potental to mutate into [english_list(mutations)] colors.
Extract potential: [S.cores]
Nutrition: [S.nutrition]/[S.max_nutrition]") + + if (S.nutrition < S.get_starve_nutrition()) + user.show_message("Warning: Subject is starving!") + else if (S.nutrition < S.get_hunger_nutrition()) + user.show_message("Warning: Subject is hungry.") + user.show_message("Electric change strength: [S.power_charge]") + + if(S.has_AI()) + var/datum/ai_holder/simple_mob/xenobio_slime/AI = S.ai_holder + if(AI.resentment) + user.show_message("Warning: Subject is harboring resentment.") + if(AI.rabid) + user.show_message("Subject is enraged and extremely dangerous!") + if(S.harmless) + user.show_message("Subject has been pacified.") + if(S.unity) + user.show_message("Subject is friendly to other slime colors.") + + user.show_message("Growth progress: [S.amount_grown]/10") \ No newline at end of file diff --git a/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm b/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm index a730263afa..3332adbe0c 100644 --- a/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm +++ b/code/game/objects/items/stacks/tiles/fifty_spawner_tiles.dm @@ -4,14 +4,6 @@ name = "stack of grass" type_to_spawn = /obj/item/stack/tile/grass -/obj/fiftyspawner/grass/sif - name = "stack of sifgrass" - type_to_spawn = /obj/item/stack/tile/grass/sif - -/obj/fiftyspawner/grass/sif/forest - name = "stack of sifgrass" - type_to_spawn = /obj/item/stack/tile/grass/sif/forest - /obj/fiftyspawner/wood name = "stack of wood" type_to_spawn = /obj/item/stack/tile/wood diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index 3c17fb0acf..759d57414e 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -42,16 +42,6 @@ drop_sound = 'sound/items/drop/herb.ogg' pickup_sound = 'sound/items/pickup/herb.ogg' -/obj/item/stack/tile/grass/sif - name = "sivian grass tile" - singular_name = "sivian grass floor tile" - desc = "A patch of grass like those that decorate the plains of Sif." - -/obj/item/stack/tile/grass/sif/forest - name = "sivian overgrowth tile" - singular_name = "sivian overgrowth floor tile" - desc = "A patch of dark overgrowth like those that decorate the plains of Sif." - /* * Wood */ diff --git a/code/game/objects/items/weapons/storage/bible.dm b/code/game/objects/items/weapons/storage/bible.dm index aaa7fbe144..20fe500575 100644 --- a/code/game/objects/items/weapons/storage/bible.dm +++ b/code/game/objects/items/weapons/storage/bible.dm @@ -37,30 +37,35 @@ GLOBAL_LIST_INIT(bibleitemstates, list( drop_sound = 'sound/bureaucracy/bookclose.ogg' /obj/item/weapon/storage/bible/attack_self(mob/living/carbon/human/user) - if(GLOB.bible_icon_state) - icon_state = GLOB.bible_icon_state - item_state = GLOB.bible_item_state - return FALSE + if(user?.mind?.assigned_role != "Chaplain") return FALSE - var/list/skins = list() - for(var/i in 1 to GLOB.biblestates.len) - var/image/bible_image = image(icon = 'icons/obj/storage.dmi', icon_state = GLOB.biblestates[i]) - skins += list("[GLOB.biblenames[i]]" = bible_image) - - var/choice = show_radial_menu(user, src, skins, custom_check = CALLBACK(src, .proc/check_menu, user), radius = 40, require_near = TRUE) - if(!choice) + if (!user.mind.my_religion) return FALSE - var/bible_index = GLOB.biblenames.Find(choice) - if(!bible_index) - return FALSE - icon_state = GLOB.biblestates[bible_index] - item_state = GLOB.bibleitemstates[bible_index] - GLOB.bible_icon_state = icon_state - GLOB.bible_item_state = item_state - feedback_set_details("religion_book", "[choice]") + if (!user.mind.my_religion.configured) + var/list/skins = list() + for(var/i in 1 to GLOB.biblestates.len) + var/image/bible_image = image(icon = 'icons/obj/storage.dmi', icon_state = GLOB.biblestates[i]) + skins += list("[GLOB.biblenames[i]]" = bible_image) + + var/choice = show_radial_menu(user, src, skins, custom_check = CALLBACK(src, .proc/check_menu, user), radius = 40, require_near = TRUE) + if(!choice) + return FALSE + var/bible_index = GLOB.biblenames.Find(choice) + if(!bible_index) + return FALSE + + user.mind.my_religion.bible_icon_state = GLOB.biblestates[bible_index] + user.mind.my_religion.bible_item_state = GLOB.bibleitemstates[bible_index] + user.mind.my_religion.configured = TRUE + + deity_name = user.mind.my_religion.deity + name = user.mind.my_religion.bible_name + icon_state = user.mind.my_religion.bible_icon_state + item_state = user.mind.my_religion.bible_item_state + to_chat(user, "You invoke [user.mind.my_religion.deity] and prepare a copy of [src].") /** * Checks if we are allowed to interact with a radial menu @@ -69,7 +74,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list( * * user The mob interacting with the menu */ /obj/item/weapon/storage/bible/proc/check_menu(mob/living/carbon/human/user) - if(GLOB.bible_icon_state) + if(user.mind.my_religion.configured) return FALSE if(!istype(user)) return FALSE @@ -107,4 +112,4 @@ GLOBAL_LIST_INIT(bibleitemstates, list( /obj/item/weapon/storage/bible/attackby(obj/item/weapon/W as obj, mob/user as mob) if (src.use_sound) playsound(src, src.use_sound, 50, 1, -5) - ..() + ..() \ No newline at end of file diff --git a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm index 63a5613c88..66695f3511 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/freezer.dm @@ -3,8 +3,10 @@ req_access = list(access_kitchen) starts_with = list( - /obj/item/weapon/reagent_containers/food/condiment/flour = 7, - /obj/item/weapon/reagent_containers/food/condiment/sugar = 2, + /obj/item/weapon/reagent_containers/food/condiment/carton/flour = 6, + /obj/item/weapon/reagent_containers/food/condiment/carton/sugar = 1, + /obj/item/weapon/reagent_containers/food/condiment/carton/flour/rustic = 1, + /obj/item/weapon/reagent_containers/food/condiment/carton/sugar/rustic = 1, /obj/item/weapon/reagent_containers/food/condiment/spacespice = 2 ) diff --git a/code/game/objects/structures/crates_lockers/crates.dm b/code/game/objects/structures/crates_lockers/crates.dm index 6a5e2fd954..288652c31f 100644 --- a/code/game/objects/structures/crates_lockers/crates.dm +++ b/code/game/objects/structures/crates_lockers/crates.dm @@ -90,7 +90,9 @@ src.set_dir(turn(src.dir, 90)) /obj/structure/closet/crate/attackby(obj/item/weapon/W as obj, mob/user as mob) - if(opened) + if(W.is_wrench() && istype(src,/obj/structure/closet/crate/bin)) + return ..() + else if(opened) if(isrobot(user)) return if(W.loc != user) // This should stop mounted modules ending up outside the module. diff --git a/code/game/objects/structures/railing.dm b/code/game/objects/structures/railing.dm index 2f07cff6bf..3efd7091b0 100644 --- a/code/game/objects/structures/railing.dm +++ b/code/game/objects/structures/railing.dm @@ -121,21 +121,21 @@ else icon_state = "[icon_modifier]railing1" if (check & 32) - add_overlay(image('icons/obj/railing.dmi', src, "[icon_modifier]corneroverlay")) + add_overlay(image(icon, src, "[icon_modifier]corneroverlay")) if ((check & 16) || !(check & 32) || (check & 64)) - add_overlay(image('icons/obj/railing.dmi', src, "[icon_modifier]frontoverlay_l")) + add_overlay(image(icon, src, "[icon_modifier]frontoverlay_l")) if (!(check & 2) || (check & 1) || (check & 4)) - add_overlay(image('icons/obj/railing.dmi', src, "[icon_modifier]frontoverlay_r")) + add_overlay(image(icon, src, "[icon_modifier]frontoverlay_r")) if(check & 4) switch (src.dir) if (NORTH) - add_overlay(image('icons/obj/railing.dmi', src, "[icon_modifier]mcorneroverlay", pixel_x = 32)) + add_overlay(image(icon, src, "[icon_modifier]mcorneroverlay", pixel_x = 32)) if (SOUTH) - add_overlay(image('icons/obj/railing.dmi', src, "[icon_modifier]mcorneroverlay", pixel_x = -32)) + add_overlay(image(icon, src, "[icon_modifier]mcorneroverlay", pixel_x = -32)) if (EAST) - add_overlay(image('icons/obj/railing.dmi', src, "[icon_modifier]mcorneroverlay", pixel_y = -32)) + add_overlay(image(icon, src, "[icon_modifier]mcorneroverlay", pixel_y = -32)) if (WEST) - add_overlay(image('icons/obj/railing.dmi', src, "[icon_modifier]mcorneroverlay", pixel_y = 32)) + add_overlay(image(icon, src, "[icon_modifier]mcorneroverlay", pixel_y = 32)) /obj/structure/railing/verb/rotate_counterclockwise() set name = "Rotate Railing Counter-Clockwise" diff --git a/code/game/turfs/flooring/flooring.dm b/code/game/turfs/flooring/flooring.dm index ba7429eec4..a843bc06d3 100644 --- a/code/game/turfs/flooring/flooring.dm +++ b/code/game/turfs/flooring/flooring.dm @@ -146,7 +146,7 @@ var/list/flooring_types flags = 0 icon = 'icons/turf/outdoors.dmi' icon_base = "grass_sif" - build_type = /obj/item/stack/tile/grass/sif + build_type = null has_base_range = 1 /decl/flooring/grass/sif/forest @@ -155,7 +155,6 @@ var/list/flooring_types flags = 0 icon = 'icons/turf/outdoors.dmi' icon_base = "grass_sif_dark" - build_type = /obj/item/stack/tile/grass/sif/forest has_base_range = 1 /decl/flooring/water diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm index 9f4d81120c..b871c3315b 100644 --- a/code/modules/admin/admin_verb_lists_vr.dm +++ b/code/modules/admin/admin_verb_lists_vr.dm @@ -112,7 +112,7 @@ var/list/admin_verbs_admin = list( /client/proc/change_human_appearance_self, // Allows the human-based mob itself change its basic appearance , /client/proc/change_security_level, /client/proc/view_chemical_reaction_logs, - /client/proc/makePAI, + /client/proc/makepAI, /client/proc/toggle_debug_logs, /client/proc/toggle_attack_logs, /datum/admins/proc/paralyze_mob, @@ -519,7 +519,7 @@ var/list/admin_verbs_event_manager = list( /client/proc/change_human_appearance_admin, // Allows an admin to change the basic appearance of human-based mobs , /client/proc/change_human_appearance_self, // Allows the human-based mob itself change its basic appearance , /client/proc/change_security_level, - /client/proc/makePAI, + /client/proc/makepAI, /client/proc/toggle_debug_logs, /client/proc/toggle_attack_logs, /datum/admins/proc/paralyze_mob, diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index e9d63621b0..db51bd4ed3 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -35,11 +35,24 @@ set category = "Admin" set name = "Aghost" if(!holder) return + + var/build_mode + if(src.buildmode) + build_mode = tgui_alert(src, "You appear to be currently in buildmode. Do you want to re-enter buildmode after aghosting?", "Buildmode", list("Yes", "No")) + if(build_mode != "Yes") + to_chat(src, "Will not re-enter buildmode after switch.") + if(istype(mob,/mob/observer/dead)) //re-enter var/mob/observer/dead/ghost = mob if(ghost.can_reenter_corpse) - ghost.reenter_corpse() + if(build_mode) + togglebuildmode(mob) + ghost.reenter_corpse() + if(build_mode == "Yes") + togglebuildmode(mob) + else + ghost.reenter_corpse() else to_chat(ghost, "Error: Aghost: Can't reenter corpse.") return @@ -51,8 +64,16 @@ else //ghostize var/mob/body = mob - var/mob/observer/dead/ghost = body.ghostize(1) - ghost.admin_ghosted = 1 + var/mob/observer/dead/ghost + if(build_mode) + togglebuildmode(body) + ghost = body.ghostize(1) + ghost.admin_ghosted = 1 + if(build_mode == "Yes") + togglebuildmode(ghost) + else + ghost = body.ghostize(1) + ghost.admin_ghosted = 1 if(body) body.teleop = ghost if(!body.key) diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index ffd38ddf53..3b4b817c00 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -151,7 +151,7 @@ switch(new_rank) if(null,"") return if("*New Rank*") - new_rank = input(usr, "Please input a new rank", "New custom rank", null, null) as null|text + new_rank = input(usr, "Please input a new rank", "New custom rank") as null|text if(config.admin_legacy_system) new_rank = ckeyEx(new_rank) if(!new_rank) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 8b0baa5967..40635bb7fe 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -129,31 +129,35 @@ M.Animalize() -/client/proc/makepAI(var/turf/T in mob_list) +/client/proc/makepAI() set category = "Fun" set name = "Make pAI" - set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI" + set desc = "Spawn someone in as a pAI!" + if(!check_rights(R_ADMIN|R_EVENT|R_DEBUG)) + return + var/turf/T = get_turf(mob) var/list/available = list() for(var/mob/C in mob_list) - if(C.key) + if(C.key && isobserver(C)) available.Add(C) var/mob/choice = tgui_input_list(usr, "Choose a player to play the pAI", "Spawn pAI", available) if(!choice) return 0 - if(!istype(choice, /mob/observer/dead)) - var/confirm = tgui_alert(usr, "[choice.key] isn't ghosting right now. Are you sure you want to yank them out of them out of their body and place them in this pAI?", "Spawn pAI Confirmation", list("No", "Yes")) - if(confirm != "Yes") - return 0 - var/obj/item/device/paicard/card = new(T) + var/obj/item/device/paicard/typeb/card = new(T) var/mob/living/silicon/pai/pai = new(card) - pai.name = sanitizeSafe(input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text) pai.real_name = pai.name pai.key = choice.key card.setPersonality(pai) + if(tgui_alert(pai, "Do you want to load your pAI data?", "Load", list("Yes", "No")) == "Yes") + pai.savefile_load(pai) + else + pai.name = sanitizeSafe(input(pai, "Enter your pAI name:", "pAI Name", "Personal AI") as text) + card.setPersonality(pai) for(var/datum/paiCandidate/candidate in paiController.pai_candidates) if(candidate.key == choice.key) paiController.pai_candidates.Remove(candidate) + log_admin("made a pAI with key=[pai.key] at ([T.x],[T.y],[T.z])") feedback_add_details("admin_verb","MPAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! /client/proc/cmd_admin_alienize(var/mob/M in mob_list) diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index ffd7033c4d..0d3837b209 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -1,9 +1,4 @@ //DEFINITIONS FOR ASSET DATUMS START HERE. -/datum/asset/simple/tgui_common - // keep_local_name = TRUE - assets = list( - "tgui-common.bundle.js" = file("tgui/public/tgui-common.bundle.js"), - ) /datum/asset/simple/tgui // keep_local_name = TRUE @@ -535,6 +530,15 @@ "stellardelight_nanomap_z1.png" = 'icons/_nanomaps/sd_deck1.png', "stellardelight_nanomap_z2.png" = 'icons/_nanomaps/sd_deck2.png', "stellardelight_nanomap_z3.png" = 'icons/_nanomaps/sd_deck3.png', + "groundbase_nanomap_z1.png" = 'icons/_nanomaps/gb1.png', + "groundbase_nanomap_z2.png" = 'icons/_nanomaps/gb2.png', + "groundbase_nanomap_z3.png" = 'icons/_nanomaps/gb3.png', + "groundbase_nanomap_z4.png" = 'icons/_nanomaps/gbnorth.png', + "groundbase_nanomap_z5.png" = 'icons/_nanomaps/gbsouth.png', + "groundbase_nanomap_z6.png" = 'icons/_nanomaps/gbeast.png', + "groundbase_nanomap_z7.png" = 'icons/_nanomaps/gbwest.png', + "groundbase_nanomap_z10.png" = 'icons/_nanomaps/gbmining.png', + // VOREStation Edit End ) \ No newline at end of file diff --git a/code/modules/client/preference_setup/global/01_ui.dm b/code/modules/client/preference_setup/global/01_ui.dm index 6bdcdccf5c..68c569c04e 100644 --- a/code/modules/client/preference_setup/global/01_ui.dm +++ b/code/modules/client/preference_setup/global/01_ui.dm @@ -3,28 +3,30 @@ sort_order = 1 /datum/category_item/player_setup_item/player_global/ui/load_preferences(var/savefile/S) - S["UI_style"] >> pref.UI_style - S["UI_style_color"] >> pref.UI_style_color - S["UI_style_alpha"] >> pref.UI_style_alpha - S["ooccolor"] >> pref.ooccolor - S["tooltipstyle"] >> pref.tooltipstyle - S["client_fps"] >> pref.client_fps - S["ambience_freq"] >> pref.ambience_freq - S["ambience_chance"] >> pref.ambience_chance - S["tgui_fancy"] >> pref.tgui_fancy - S["tgui_lock"] >> pref.tgui_lock + S["UI_style"] >> pref.UI_style + S["UI_style_color"] >> pref.UI_style_color + S["UI_style_alpha"] >> pref.UI_style_alpha + S["ooccolor"] >> pref.ooccolor + S["tooltipstyle"] >> pref.tooltipstyle + S["client_fps"] >> pref.client_fps + S["ambience_freq"] >> pref.ambience_freq + S["ambience_chance"] >> pref.ambience_chance + S["tgui_fancy"] >> pref.tgui_fancy + S["tgui_lock"] >> pref.tgui_lock + S["tgui_input_mode"] >> pref.tgui_input_mode /datum/category_item/player_setup_item/player_global/ui/save_preferences(var/savefile/S) - S["UI_style"] << pref.UI_style - S["UI_style_color"] << pref.UI_style_color - S["UI_style_alpha"] << pref.UI_style_alpha - S["ooccolor"] << pref.ooccolor - S["tooltipstyle"] << pref.tooltipstyle - S["client_fps"] << pref.client_fps - S["ambience_freq"] << pref.ambience_freq - S["ambience_chance"] << pref.ambience_chance - S["tgui_fancy"] << pref.tgui_fancy - S["tgui_lock"] << pref.tgui_lock + S["UI_style"] << pref.UI_style + S["UI_style_color"] << pref.UI_style_color + S["UI_style_alpha"] << pref.UI_style_alpha + S["ooccolor"] << pref.ooccolor + S["tooltipstyle"] << pref.tooltipstyle + S["client_fps"] << pref.client_fps + S["ambience_freq"] << pref.ambience_freq + S["ambience_chance"] << pref.ambience_chance + S["tgui_fancy"] << pref.tgui_fancy + S["tgui_lock"] << pref.tgui_lock + S["tgui_input_mode"] << pref.tgui_input_mode /datum/category_item/player_setup_item/player_global/ui/sanitize_preferences() pref.UI_style = sanitize_inlist(pref.UI_style, all_ui_styles, initial(pref.UI_style)) @@ -35,8 +37,9 @@ pref.client_fps = sanitize_integer(pref.client_fps, 0, MAX_CLIENT_FPS, initial(pref.client_fps)) pref.ambience_freq = sanitize_integer(pref.ambience_freq, 0, 60, initial(pref.ambience_freq)) // No more than once per hour. pref.ambience_chance = sanitize_integer(pref.ambience_chance, 0, 100, initial(pref.ambience_chance)) // 0-100 range. - pref.tgui_fancy = sanitize_integer(pref.tgui_fancy, 0, 1, initial(pref.tgui_fancy)) - pref.tgui_lock = sanitize_integer(pref.tgui_lock, 0, 1, initial(pref.tgui_lock)) + pref.tgui_fancy = sanitize_integer(pref.tgui_fancy, 0, 1, initial(pref.tgui_fancy)) + pref.tgui_lock = sanitize_integer(pref.tgui_lock, 0, 1, initial(pref.tgui_lock)) + pref.tgui_input_mode = sanitize_integer(pref.tgui_input_mode, 0, 1, initial(pref.tgui_input_mode)) /datum/category_item/player_setup_item/player_global/ui/content(var/mob/user) . = "UI Style: [pref.UI_style]
" @@ -49,6 +52,7 @@ . += "Ambience Chance: [pref.ambience_chance]
" . += "tgui Window Mode: [(pref.tgui_fancy) ? "Fancy (default)" : "Compatible (slower)"]
" . += "tgui Window Placement: [(pref.tgui_lock) ? "Primary Monitor" : "Free (default)"]
" + . += "Input Mode (Say, Me, Whisper, Subtle): [(pref.tgui_input_mode) ? "TGUI" : "BYOND (default)"]
" if(can_select_ooc_color(user)) . += "OOC Color:" if(pref.ooccolor == initial(pref.ooccolor)) @@ -118,6 +122,10 @@ pref.tgui_lock = !pref.tgui_lock return TOPIC_REFRESH + else if(href_list["tgui_input_mode"]) + pref.tgui_input_mode = !pref.tgui_input_mode + return TOPIC_REFRESH + else if(href_list["reset"]) switch(href_list["reset"]) if("ui") diff --git a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm index 8b34719c0f..4801ee9d82 100644 --- a/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_fluffitems_vr.dm @@ -90,7 +90,7 @@ ckeywhitelist = list("allweek") character_name = list("Fifi The Magnificent") -/datum/gear/fluff/fifi_hat +/datum/gear/fluff/fifi_socks path = /obj/item/clothing/shoes/fluff/fifi_socks display_name = "Fifi's socks" ckeywhitelist = list("allweek") @@ -1062,11 +1062,11 @@ ckeywhitelist = list("swat43") character_name = list("Fortune Bloise") -/datum/gear/fluff/alexis_cane - path = /obj/item/weapon/cane/wand - display_name = "Alexis' Cane" +/datum/gear/fluff/kyutar + path = /obj/item/instrument/piano_synth/fluff/kyutar + display_name = "Kyu's Holotar" ckeywhitelist = list("stobarico") - character_name = list("Alexis Bloise") + character_name = list("Kyu Comet") /datum/gear/fluff/roiz_coat path = /obj/item/clothing/suit/storage/hooded/wintercoat/roiz diff --git a/code/modules/client/preference_setup/loadout/loadout_general_vr.dm b/code/modules/client/preference_setup/loadout/loadout_general_vr.dm index da8e271890..d68050dd85 100644 --- a/code/modules/client/preference_setup/loadout/loadout_general_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_general_vr.dm @@ -96,3 +96,8 @@ display_name = "schnapsen playing cards" description = "An ancient Austro-Hungarian suit of cards!" path = /obj/item/weapon/deck/schnapsen + +/datum/gear/egy_game + display_name = "EGY playing cards" + description = "A deck of cards for playing EGY! Be the first to lose all cards!" + path = /obj/item/weapon/deck/egy diff --git a/code/modules/client/preference_setup/loadout/loadout_utility.dm b/code/modules/client/preference_setup/loadout/loadout_utility.dm index 4a945f442c..3d2dcb20a4 100644 --- a/code/modules/client/preference_setup/loadout/loadout_utility.dm +++ b/code/modules/client/preference_setup/loadout/loadout_utility.dm @@ -72,9 +72,13 @@ path = /obj/item/weapon/folder/yellow /datum/gear/utility/paicard - display_name = "personal AI device" + display_name = "personal AI device (classic)" path = /obj/item/device/paicard +/datum/gear/utility/paicard_b + display_name = "personal AI device (new)" + path = /obj/item/device/paicard/typeb + /datum/gear/utility/securecase display_name = "secure briefcase" path =/obj/item/weapon/storage/secure/briefcase diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index e4b7c74dcf..d95a3350fa 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -28,6 +28,7 @@ var/list/preferences_datums = list() var/tgui_fancy = TRUE var/tgui_lock = FALSE + var/tgui_input_mode = FALSE // Say, Me, Whisper, Subtle Input Mode; Disabled by default; FALSE = BYOND, TRUE = TGUI //character preferences var/real_name //our character's name diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm index 52841c8464..9d2a3f064b 100644 --- a/code/modules/client/verbs/suicide.dm +++ b/code/modules/client/verbs/suicide.dm @@ -78,6 +78,7 @@ adjustOxyLoss(max(getMaxHealth() * 2 - getToxLoss() - getFireLoss() - getBruteLoss() - getOxyLoss(), 0)) updatehealth() +/* /mob/living/silicon/pai/verb/suicide() set category = "pAI Commands" set desc = "Kill yourself and become a ghost (You will receive a confirmation prompt)" @@ -91,4 +92,5 @@ M.show_message("[src] flashes a message across its screen, \"Wiping core files. Please acquire a new personality to continue using pAI device functions.\"", 3, "[src] bleeps electronically.", 2) death(0) else - to_chat(src, "Aborting suicide attempt.") \ No newline at end of file + to_chat(src, "Aborting suicide attempt.") +*/ \ No newline at end of file diff --git a/code/modules/clothing/spacesuits/rig/suits/alien.dm b/code/modules/clothing/spacesuits/rig/suits/alien.dm index 89d854b370..3e301b6078 100644 --- a/code/modules/clothing/spacesuits/rig/suits/alien.dm +++ b/code/modules/clothing/spacesuits/rig/suits/alien.dm @@ -7,7 +7,7 @@ desc = "A cheap NT knock-off of an Unathi battle-rig. Looks like a fish, moves like a fish, steers like a cow." suit_type = "\improper NT breacher" icon_state = "breacher_rig_cheap" - armor = list(melee = 60, bullet = 60, laser = 60, energy = 60, bomb = 70, bio = 100, rad = 50) + armor = list(melee = 60, bullet = 45, laser = 35, energy = 35, bomb = 70, bio = 100, rad = 50) emp_protection = -20 slowdown = 6 offline_slowdown = 10 diff --git a/code/modules/economy/ATM.dm b/code/modules/economy/ATM.dm index 8abd3583c0..2eef935e81 100644 --- a/code/modules/economy/ATM.dm +++ b/code/modules/economy/ATM.dm @@ -258,17 +258,17 @@ log transactions var/new_sec_level = max( min(text2num(href_list["new_security_level"]), 2), 0) authenticated_account.security_level = new_sec_level if("attempt_auth") - - // check if they have low security enabled - scan_user(usr) - - if(!ticks_left_locked_down && held_card) - var/tried_account_num = text2num(href_list["account_num"]) - if(!tried_account_num) - tried_account_num = held_card.associated_account_number + if(!ticks_left_locked_down) + var/tried_account_num = held_card ? held_card.associated_account_number : text2num(href_list["account_num"]) var/tried_pin = text2num(href_list["account_pin"]) - authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1) + // check if they have low security enabled + + if (!tried_account_num) + scan_user(usr) + else + authenticated_account = attempt_account_access(tried_account_num, tried_pin, held_card && held_card.associated_account_number == tried_account_num ? 2 : 1) + if(!authenticated_account) number_incorrect_tries++ if(previous_account_number == tried_account_num) @@ -460,19 +460,6 @@ log transactions I = P.id if(I) authenticated_account = attempt_account_access(I.associated_account_number) - if(authenticated_account) - to_chat(human_user, "[bicon(src)] Access granted. Welcome user '[authenticated_account.owner_name].'") - - //create a transaction log entry - var/datum/transaction/T = new() - T.target_name = authenticated_account.owner_name - T.purpose = "Remote terminal access" - T.source_terminal = machine_id - T.date = current_date_string - T.time = stationtime2text() - authenticated_account.transaction_log.Add(T) - - view_screen = NO_SCREEN // put the currently held id on the ground or in the hand of the user /obj/machinery/atm/proc/release_held_id(mob/living/carbon/human/human_user as mob) diff --git a/code/modules/emotes/definitions/audible.dm b/code/modules/emotes/definitions/audible.dm index 1a5a4e3084..d702a32d3a 100644 --- a/code/modules/emotes/definitions/audible.dm +++ b/code/modules/emotes/definitions/audible.dm @@ -183,6 +183,7 @@ key = "squish" emote_sound = 'sound/effects/slime_squish.ogg' //Credit to DrMinky (freesound.org) for the sound. emote_message_3p = "squishes." + sound_vary = FALSE /decl/emote/audible/warble key = "warble" @@ -204,6 +205,14 @@ emote_message_3p = "purrs." emote_sound = 'sound/voice/cat_purr_long.ogg' +/decl/emote/audible/fennecscream + key = "fennecscream" + emote_message_3p = "screeches!" + +/decl/emote/audible/zoom + key = "zoom" + emote_message_3p = "zooms." + /decl/emote/audible/teshsqueak key = "surprised" emote_message_1p = "You chirp in surprise!" @@ -211,6 +220,7 @@ emote_message_1p_target = "You chirp in surprise at TARGET!" emote_message_3p_target = "chirps in surprise at TARGET!" emote_sound = 'sound/voice/teshsqueak.ogg' // Copyright CC BY 3.0 InspectorJ (freesound.org) for the source audio. + sound_vary = FALSE /decl/emote/audible/teshchirp key = "tchirp" diff --git a/code/modules/emotes/definitions/audible_furry_vr.dm b/code/modules/emotes/definitions/audible_furry_vr.dm index 61d980ad27..f2118f1030 100644 --- a/code/modules/emotes/definitions/audible_furry_vr.dm +++ b/code/modules/emotes/definitions/audible_furry_vr.dm @@ -34,6 +34,7 @@ key = "chirp" emote_message_3p = "chirps!" emote_sound = 'sound/misc/nymphchirp.ogg' + sound_vary = FALSE /decl/emote/audible/hoot key = "hoot" emote_message_3p = "hoots!" @@ -109,10 +110,12 @@ key = "snort" emote_message_3p = "snorts!" emote_sound = 'sound/voice/Snort.ogg' + sound_vary = FALSE /decl/emote/audible/meow key = "meow" emote_message_3p = "gently meows!" emote_sound = 'sound/voice/Meow.ogg' + sound_vary = FALSE /decl/emote/audible/moo key = "moo" emote_message_3p = "takes a breath and lets out a moo." @@ -155,6 +158,12 @@ emote_sound = pick(smolsound) else emote_sound = pick(bigsound) + else if(istype(user, /mob/living/silicon/pai)) + var/mob/living/silicon/pai/me = user + if(me.chassis == "teppi") + emote_sound = pick(bigsound) + else + emote_sound = pick(smolsound) else if(user.size_multiplier >= 1.5) emote_sound = pick(bigsound) else diff --git a/code/modules/emotes/definitions/synthetics.dm b/code/modules/emotes/definitions/synthetics.dm index 70e05481ce..4f94b7b59b 100644 --- a/code/modules/emotes/definitions/synthetics.dm +++ b/code/modules/emotes/definitions/synthetics.dm @@ -1,17 +1,18 @@ /decl/emote/audible/synth - key = "beep" - emote_message_3p = "beeps." - emote_sound = 'sound/machines/twobeep.ogg' + key = "ping" + emote_message_3p = "pings." + emote_sound = 'sound/machines/ping.ogg' /decl/emote/audible/synth/mob_can_use(var/mob/living/user) if(istype(user) && user.isSynthetic()) return ..() return FALSE -/decl/emote/audible/synth/ping - key = "ping" - emote_message_3p = "pings." - emote_sound = 'sound/machines/ping.ogg' +/decl/emote/audible/synth/beep + key = "beep" + emote_message_3p = "beeps." + emote_sound = 'sound/machines/twobeep.ogg' + sound_vary = FALSE /decl/emote/audible/synth/buzz key = "buzz" @@ -54,3 +55,21 @@ emote_message_3p_target = "chirps happily at TARGET!" emote_message_3p = "chirps happily." emote_sound = 'sound/machines/dwoop.ogg' + +/decl/emote/audible/synth/boop + key = "roboboop" + emote_message_1p_target = "You boop at TARGET!" + emote_message_1p = "You boop." + emote_message_3p_target = "boops at TARGET!" + emote_message_3p = "boops." + emote_sound = 'sound/voice/roboboop.ogg' + sound_vary = TRUE + +/decl/emote/audible/synth/robochirp + key = "robochirp" + emote_message_1p_target = "You chirp at TARGET!" + emote_message_1p = "You chirp." + emote_message_3p_target = "chirps at TARGET!" + emote_message_3p = "chirps." + emote_sound = 'sound/voice/robochirp.ogg' + sound_vary = TRUE diff --git a/code/modules/emotes/emote_define.dm b/code/modules/emotes/emote_define.dm index daff23f85d..7aed1e982f 100644 --- a/code/modules/emotes/emote_define.dm +++ b/code/modules/emotes/emote_define.dm @@ -44,6 +44,7 @@ var/global/list/emotes_by_key var/emote_range = 0 // If >0, restricts emote visibility to viewers within range. var/sound_preferences = list(/datum/client_preference/emote_noises) // Default emote sound_preferences is just emote_noises. Belch emote overrides this list for pref-checks. + var/sound_vary = FALSE /decl/emote/Initialize() . = ..() @@ -186,7 +187,7 @@ var/global/list/emotes_by_key if(islist(sound_to_play) && length(sound_to_play)) sound_to_play = pick(sound_to_play) if(sound_to_play) - playsound(user.loc, sound_to_play, use_sound["vol"], 0, preference = sound_preferences) //VOREStation Add - Preference + playsound(user.loc, sound_to_play, use_sound["vol"], sound_vary, frequency = null, preference = sound_preferences) //VOREStation Add - Preference /decl/emote/proc/mob_can_use(var/mob/user) return istype(user) && user.stat != DEAD && (type in user.get_available_emotes()) diff --git a/code/modules/food/food/condiment.dm b/code/modules/food/food/condiment.dm index 5b137957db..2373db3bc2 100644 --- a/code/modules/food/food/condiment.dm +++ b/code/modules/food/food/condiment.dm @@ -135,6 +135,11 @@ desc = "Barbecue sauce, it's labeled 'sweet and spicy'." icon_state = "barbecue" center_of_mass = list("x"=16, "y"=6) + if("sprinkles") + name = "sprinkles" + desc = "Bottle of sprinkles, colourful!" + icon_state= "sprinkles" + center_of_mass = list("x"=16, "y"=6) else name = "Misc Condiment Bottle" if (reagents.reagent_list.len==1) @@ -208,6 +213,13 @@ . = ..() reagents.add_reagent("yeast", 50) +/obj/item/weapon/reagent_containers/food/condiment/sprinkles + name = "Sprinkles" + +/obj/item/weapon/reagent_containers/food/condiment/sprinkles/Initialize() + . = ..() + reagents.add_reagent("sprinkles", 50) + /obj/item/weapon/reagent_containers/food/condiment/small possible_transfer_amounts = list(1,20) amount_per_transfer_from_this = 1 @@ -448,22 +460,58 @@ //End of MRE stuff. -/obj/item/weapon/reagent_containers/food/condiment/flour - name = "flour sack" - desc = "A big bag of flour. Good for baking!" +/obj/item/weapon/reagent_containers/food/condiment/carton/flour + name = "flour carton" + desc = "A big carton of flour. Good for baking!" icon = 'icons/obj/food.dmi' icon_state = "flour" volume = 220 center_of_mass = list("x"=16, "y"=8) -/obj/item/weapon/reagent_containers/food/condiment/flour/on_reagent_change() +/obj/item/weapon/reagent_containers/food/condiment/carton/flour/on_reagent_change() + update_icon() return -/obj/item/weapon/reagent_containers/food/condiment/flour/Initialize() +/obj/item/weapon/reagent_containers/food/condiment/carton/flour/Initialize() . = ..() reagents.add_reagent("flour", 200) randpixel_xy() +/obj/item/weapon/reagent_containers/food/condiment/carton/update_icon() + overlays.Cut() + + if(reagents.total_volume) + var/image/filling = image('icons/obj/food.dmi', src, "[icon_state]10") + + filling.icon_state = "[icon_state]-[clamp(round(100 * reagents.total_volume / volume, 25), 0, 100)]" + + overlays += filling + +/obj/item/weapon/reagent_containers/food/condiment/carton/flour/rustic + name = "flour sack" + desc = "An artisanal sack of flour. Classy!" + icon_state = "flour_bag" + +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar + name = "sugar carton" + desc = "A big carton of sugar. Sweet!" + icon_state = "sugar" + volume = 120 + center_of_mass = list("x"=16, "y"=8) + +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar/on_reagent_change() + update_icon() + return + +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar/Initialize() + . = ..() + reagents.add_reagent("sugar", 100) + +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar/rustic + name = "sugar sack" + desc = "An artisanal sack of sugar. Classy!" + icon_state = "sugar_bag" + /obj/item/weapon/reagent_containers/food/condiment/spacespice name = "space spices" desc = "An exotic blend of spices for cooking. Definitely not worms." @@ -477,4 +525,4 @@ /obj/item/weapon/reagent_containers/food/condiment/spacespice/Initialize() . = ..() - reagents.add_reagent("spacespice", 40) \ No newline at end of file + reagents.add_reagent("spacespice", 40) diff --git a/code/modules/food/kitchen/cooking_machines/container.dm b/code/modules/food/kitchen/cooking_machines/container.dm index 981a809fd4..bd4a9d8e00 100644 --- a/code/modules/food/kitchen/cooking_machines/container.dm +++ b/code/modules/food/kitchen/cooking_machines/container.dm @@ -7,6 +7,7 @@ var/shortname var/max_space = 20//Maximum sum of w-classes of foods in this container at once var/max_reagents = 80//Maximum units of reagents + var/food_items = 0 // Used for icon updates flags = OPENCONTAINER | NOREACT var/list/insertable = list( /obj/item/weapon/reagent_containers/food/snacks, @@ -59,6 +60,8 @@ return I.forceMove(src) to_chat(user, "You put the [I] into the [src].") + food_items += 1 + update_icon() return /obj/item/weapon/reagent_containers/cooking_container/verb/empty() @@ -102,6 +105,8 @@ /obj/item/weapon/reagent_containers/cooking_container/AltClick(var/mob/user) do_empty(user) + food_items = 0 + update_icon() //Deletes contents of container. //Used when food is burned, before replacing it with a burned mess @@ -158,6 +163,22 @@ if (weights[I]) holder.trans_to(I, weights[I] / total) +/obj/item/weapon/reagent_containers/cooking_container/update_icon() + overlays.Cut() + + if(food_items) + var/image/filling = image('icons/obj/cooking_machines.dmi', src, "[icon_state]10") + + var/percent = round((food_items / max_space) * 100) + switch(percent) + if(0 to 2) filling.icon_state = "[icon_state]" + if(3 to 24) filling.icon_state = "[icon_state]1" + if(25 to 49) filling.icon_state = "[icon_state]2" + if(50 to 74) filling.icon_state = "[icon_state]3" + if(75 to 79) filling.icon_state = "[icon_state]4" + if(80 to INFINITY) filling.icon_state = "[icon_state]5" + + overlays += filling /obj/item/weapon/reagent_containers/cooking_container/oven name = "oven dish" @@ -186,4 +207,4 @@ name = "grill rack" shortname = "rack" desc = "Put ingredients 'in'/on this; designed for use with a grill. Warranty void if used incorrectly. Alt click to remove contents." - icon_state = "grillrack" \ No newline at end of file + icon_state = "grillrack" diff --git a/code/modules/games/egy_cards_vr.dm b/code/modules/games/egy_cards_vr.dm new file mode 100644 index 0000000000..c41fd68b19 --- /dev/null +++ b/code/modules/games/egy_cards_vr.dm @@ -0,0 +1,47 @@ +//Sprites ported from Eris + +/obj/item/weapon/deck/egy + name = "deck of EGY playing cards" + desc = "A simple deck of EGY playing cards. Be the first to lose all cards, but forget not to declare: EGY on your second to last trick." + icon_state = "deck3" + + + + +/obj/item/weapon/deck/egy/New() + ..() + var/datum/playingcard/P + //Universal cards + for(var/i=0; i<=3; i++) + P = new() + P.name = "\improper Wild +4" + P.card_icon = "+4" + P.back_icon = "deck1" + cards += P + for(var/i=0; i<=3; i++) + P = new() + P.name = "\improper Wildcard" + P.card_icon = "colorswap" + P.back_icon = "deck1" + cards += P + //Colour cards + for(var/colour in list("red", "yellow", "blue", "green")) + //Specials + for(var/special in list("reverse","+2","skip")) + //2 of each + for(var/i=0; i<=1; i++) + P = new() + P.name = "\improper [colour] [special]" + P.card_icon = "[colour]_[special]" + P.back_icon = "deck1" + cards += P + //Number cards + for(var/number in list("0","1","2","3","4","5","6","7","8","9")) + //2 of each for 0-9, using 2 of "0" per Crow's request + for(var/i=0; i<=1; i++) + P = new() + P.name = "\improper [colour] [number]" + P.card_icon = "[colour]_[number]" + P.back_icon = "deck1" + cards += P + diff --git a/code/modules/holodeck/HolodeckObjects.dm b/code/modules/holodeck/HolodeckObjects.dm index ee7060dfd8..c8b324d6f4 100644 --- a/code/modules/holodeck/HolodeckObjects.dm +++ b/code/modules/holodeck/HolodeckObjects.dm @@ -278,6 +278,7 @@ /obj/item/weapon/holo damtype = HALLOSS no_attack_log = 1 + no_random_knockdown = TRUE /obj/item/weapon/holo/esword desc = "May the force be within you. Sorta." diff --git a/code/modules/library/lib_machines.dm b/code/modules/library/lib_machines.dm index 9f5a2d511b..41f0c8688e 100644 --- a/code/modules/library/lib_machines.dm +++ b/code/modules/library/lib_machines.dm @@ -341,14 +341,7 @@ screenstate = 5 if("6") if(!bibledelay) - - var/obj/item/weapon/storage/bible/B = new /obj/item/weapon/storage/bible(src.loc) - if(GLOB.religion) - B.icon_state = GLOB.bible_icon_state - B.item_state = GLOB.bible_item_state - B.name = GLOB.bible_name - B.deity_name = GLOB.deity - + new /obj/item/weapon/storage/bible(src.loc) bibledelay = 1 spawn(60) bibledelay = 0 @@ -578,4 +571,4 @@ b.icon_state = "book[rand(1,7)]" qdel(O) else - ..() + ..() \ No newline at end of file diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 2d991e7290..f642131278 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -984,8 +984,8 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp var/obj/item/device/paicard/PP = p if(PP.pai == null) count++ - PP.icon = 'icons/obj/pda_vr.dmi' // VOREStation Edit PP.add_overlay("pai-ghostalert") + PP.alertUpdate() spawn(54) PP.cut_overlays() to_chat(usr,"Flashing the displays of [count] unoccupied PAIs.") diff --git a/code/modules/mob/holder.dm b/code/modules/mob/holder.dm index 5b6d155640..b2cd769b5b 100644 --- a/code/modules/mob/holder.dm +++ b/code/modules/mob/holder.dm @@ -29,11 +29,13 @@ var/list/holder_mob_icon_cache = list() ASSERT(ismob(held)) . = ..() held.forceMove(src) + held.reset_view(src) START_PROCESSING(SSobj, src) /obj/item/weapon/holder/Entered(mob/held, atom/OldLoc) if(held_mob) held.forceMove(get_turf(src)) + held.reset_view(null) return ASSERT(ismob(held)) . = ..() @@ -71,6 +73,7 @@ var/list/holder_mob_icon_cache = list() held_mob.transform = original_transform held_mob.vis_flags = original_vis_flags held_mob.forceMove(get_turf(src)) + held_mob.reset_view(null) held_mob = null /obj/item/weapon/holder/throw_at(atom/target, range, speed, thrower) @@ -103,9 +106,12 @@ var/list/holder_mob_icon_cache = list() holster.clear_holster() to_chat(held, "You extricate yourself from [holster].") forceMove(get_turf(src)) + held.reset_view(null) + else if(isitem(loc)) to_chat(held, "You struggle free of [loc].") forceMove(get_turf(src)) + held.reset_view(null) //Mob specific holders. /obj/item/weapon/holder/diona @@ -167,6 +173,12 @@ var/list/holder_mob_icon_cache = list() item_state = "cat" /obj/item/weapon/holder/cat/runtime + +/obj/item/holder/fennec + origin_tech = list(TECH_BIO = 2) + +/obj/item/holder/cat/runtime + origin_tech = list(TECH_BIO = 2, TECH_DATA = 4) /obj/item/weapon/holder/cat/cak diff --git a/code/modules/mob/living/carbon/human/emote.dm b/code/modules/mob/living/carbon/human/emote.dm index 1881e2856a..028fc518e2 100644 --- a/code/modules/mob/living/carbon/human/emote.dm +++ b/code/modules/mob/living/carbon/human/emote.dm @@ -1,12 +1,14 @@ var/list/_human_default_emotes = list( /decl/emote/visible/blink, /decl/emote/audible/synth, - /decl/emote/audible/synth/ping, + /decl/emote/audible/synth/beep, /decl/emote/audible/synth/buzz, /decl/emote/audible/synth/confirm, /decl/emote/audible/synth/deny, /decl/emote/audible/synth/scary, /decl/emote/audible/synth/dwoop, + /decl/emote/audible/synth/boop, + /decl/emote/audible/synth/robochirp, /decl/emote/visible/nod, /decl/emote/visible/shake, /decl/emote/visible/shiver, @@ -141,7 +143,9 @@ var/list/_human_default_emotes = list( /decl/emote/audible/coyawoo2, /decl/emote/audible/coyawoo3, /decl/emote/audible/coyawoo4, - /decl/emote/audible/coyawoo5 + /decl/emote/audible/coyawoo5, + /decl/emote/audible/fennecscream, + /decl/emote/audible/zoom //VOREStation Add End ) @@ -271,7 +275,10 @@ var/list/_simple_mob_default_emotes = list( /decl/emote/visible/blep, /decl/emote/audible/prbt, /decl/emote/audible/gyoh, - /decl/emote/audible/rumble + /decl/emote/audible/rumble, + /decl/emote/audible/fennecscream, + /decl/emote/audible/zoom + ) //VOREStation Add End diff --git a/code/modules/mob/living/carbon/human/human_defense.dm b/code/modules/mob/living/carbon/human/human_defense.dm index 8591001d28..b0244149d8 100644 --- a/code/modules/mob/living/carbon/human/human_defense.dm +++ b/code/modules/mob/living/carbon/human/human_defense.dm @@ -321,7 +321,7 @@ emp_act H.bloody_body(src) H.bloody_hands(src) - if(!stat) + if(!stat && !(I.no_random_knockdown)) switch(hit_zone) if(BP_HEAD)//Harder to score a stun but if you do it lasts a bit longer if(prob(effective_force)) diff --git a/code/modules/mob/living/carbon/human/human_movement.dm b/code/modules/mob/living/carbon/human/human_movement.dm index f13051be24..9293eec473 100644 --- a/code/modules/mob/living/carbon/human/human_movement.dm +++ b/code/modules/mob/living/carbon/human/human_movement.dm @@ -289,3 +289,8 @@ playsound(T, S, volume, FALSE) return + +/mob/living/carbon/human/set_dir(var/new_dir) + . = ..() + if(. && species.tail) + update_tail_showing() \ No newline at end of file diff --git a/code/modules/mob/living/carbon/human/species/station/alraune.dm b/code/modules/mob/living/carbon/human/species/station/alraune.dm index 12424f993c..999425d4e6 100644 --- a/code/modules/mob/living/carbon/human/species/station/alraune.dm +++ b/code/modules/mob/living/carbon/human/species/station/alraune.dm @@ -2,7 +2,7 @@ name = SPECIES_ALRAUNE name_plural = "Alraunes" unarmed_types = list(/datum/unarmed_attack/stomp, /datum/unarmed_attack/kick, /datum/unarmed_attack/punch, /datum/unarmed_attack/bite) - num_alternate_languages = 2 + num_alternate_languages = 3 slowdown = 1 //slow, they're plants. Not as slow as full diona. total_health = 100 //standard brute_mod = 1 //nothing special diff --git a/code/modules/mob/living/carbon/human/species/station/teshari.dm b/code/modules/mob/living/carbon/human/species/station/teshari.dm index 6f24341889..2917dfcf39 100644 --- a/code/modules/mob/living/carbon/human/species/station/teshari.dm +++ b/code/modules/mob/living/carbon/human/species/station/teshari.dm @@ -59,7 +59,7 @@ ambiguous_genders = TRUE - spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED | SPECIES_NO_POSIBRAIN + spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED appearance_flags = HAS_HAIR_COLOR | HAS_SKIN_COLOR | HAS_EYE_COLOR bump_flag = MONKEY swap_flags = MONKEY|SLIME|SIMPLE_ANIMAL diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm index 889ed2fdfa..248439381c 100644 --- a/code/modules/mob/living/carbon/human/update_icons.dm +++ b/code/modules/mob/living/carbon/human/update_icons.dm @@ -66,37 +66,40 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() #define MOB_DAM_LAYER 4 //Injury overlay sprites like open wounds #define SURGERY_LAYER 5 //Overlays for open surgical sites #define UNDERWEAR_LAYER 6 //Underwear/bras/etc -#define SHOES_LAYER_ALT 7 //Shoe-slot item (when set to be under uniform via verb) -#define UNIFORM_LAYER 8 //Uniform-slot item -#define ID_LAYER 9 //ID-slot item -#define SHOES_LAYER 10 //Shoe-slot item -#define GLOVES_LAYER 11 //Glove-slot item -#define BELT_LAYER 12 //Belt-slot item -#define SUIT_LAYER 13 //Suit-slot item -#define TAIL_LAYER 14 //Some species have tails to render -#define GLASSES_LAYER 15 //Eye-slot item -#define BELT_LAYER_ALT 16 //Belt-slot item (when set to be above suit via verb) -#define SUIT_STORE_LAYER 17 //Suit storage-slot item -#define BACK_LAYER 18 //Back-slot item -#define HAIR_LAYER 19 //The human's hair -#define HAIR_ACCESSORY_LAYER 20 //VOREStation edit. Simply move this up a number if things are added. -#define EARS_LAYER 21 //Both ear-slot items (combined image) -#define EYES_LAYER 22 //Mob's eyes (used for glowing eyes) -#define FACEMASK_LAYER 23 //Mask-slot item -#define HEAD_LAYER 24 //Head-slot item -#define HANDCUFF_LAYER 25 //Handcuffs, if the human is handcuffed, in a secret inv slot -#define LEGCUFF_LAYER 26 //Same as handcuffs, for legcuffs -#define L_HAND_LAYER 27 //Left-hand item -#define R_HAND_LAYER 28 //Right-hand item -#define WING_LAYER 29 //Wings or protrusions over the suit. -#define TAIL_LAYER_ALT 30 //Modified tail-sprite layer. Tend to be larger. -#define MODIFIER_EFFECTS_LAYER 31 //Effects drawn by modifiers -#define FIRE_LAYER 32 //'Mob on fire' overlay layer -#define MOB_WATER_LAYER 33 //'Mob submerged' overlay layer -#define TARGETED_LAYER 34 //'Aimed at' overlay layer -#define TOTAL_LAYERS 34 //VOREStation edit. <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list. +#define TAIL_SOUTH_LAYER 7 //Tail as viewed from the south +#define SHOES_LAYER_ALT 8 //Shoe-slot item (when set to be under uniform via verb) +#define UNIFORM_LAYER 9 //Uniform-slot item +#define ID_LAYER 10 //ID-slot item +#define SHOES_LAYER 11 //Shoe-slot item +#define GLOVES_LAYER 12 //Glove-slot item +#define BELT_LAYER 13 //Belt-slot item +#define SUIT_LAYER 14 //Suit-slot item +#define TAIL_NORTH_LAYER 15 //Some species have tails to render (As viewed from the N, E, or W) +#define GLASSES_LAYER 16 //Eye-slot item +#define BELT_LAYER_ALT 17 //Belt-slot item (when set to be above suit via verb) +#define SUIT_STORE_LAYER 18 //Suit storage-slot item +#define BACK_LAYER 19 //Back-slot item +#define HAIR_LAYER 20 //The human's hair +#define HAIR_ACCESSORY_LAYER 21 //VOREStation edit. Simply move this up a number if things are added. +#define EARS_LAYER 22 //Both ear-slot items (combined image) +#define EYES_LAYER 23 //Mob's eyes (used for glowing eyes) +#define FACEMASK_LAYER 24 //Mask-slot item +#define HEAD_LAYER 25 //Head-slot item +#define HANDCUFF_LAYER 26 //Handcuffs, if the human is handcuffed, in a secret inv slot +#define LEGCUFF_LAYER 27 //Same as handcuffs, for legcuffs +#define L_HAND_LAYER 28 //Left-hand item +#define R_HAND_LAYER 29 //Right-hand item +#define WING_LAYER 30 //Wings or protrusions over the suit. +#define TAIL_LAYER_ALT 31 //Modified tail-sprite layer. Tend to be larger. +#define MODIFIER_EFFECTS_LAYER 32 //Effects drawn by modifiers +#define FIRE_LAYER 33 //'Mob on fire' overlay layer +#define MOB_WATER_LAYER 34 //'Mob submerged' overlay layer +#define TARGETED_LAYER 35 //'Aimed at' overlay layer +#define TOTAL_LAYERS 35 //VOREStation edit. <---- KEEP THIS UPDATED, should always equal the highest number here, used to initialize a list. ////////////////////////////////// +#define GET_TAIL_LAYER (dir == NORTH ? TAIL_NORTH_LAYER : TAIL_SOUTH_LAYER) + /mob/living/carbon/human var/list/overlays_standing[TOTAL_LAYERS] var/previous_damage_appearance // store what the body last looked like, so we only have to update it if something changed @@ -133,6 +136,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() var/desired_scale_y = size_multiplier * icon_scale_y desired_scale_x *= species.icon_scale_x desired_scale_y *= species.icon_scale_y + vis_height = species.icon_height appearance_flags |= PIXEL_SCALE if(fuzzy) appearance_flags &= ~PIXEL_SCALE @@ -271,7 +275,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() for(var/M in part.markings) icon_key += "[M][part.markings[M]["color"]]" - + // VOREStation Edit Start if(part.nail_polish) icon_key += "_[part.nail_polish.icon]_[part.nail_polish.icon_state]_[part.nail_polish.color]" @@ -369,6 +373,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() update_tail_showing() update_wing_showing() + /mob/living/carbon/human/proc/update_skin() if(QDESTROYING(src)) return @@ -488,7 +493,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() face_standing.Blend(ears_s, ICON_OVERLAY) if(ear_style?.em_block) em_block_ears = em_block_image_generic(image(ears_s)) - + var/image/semifinal = image(face_standing, layer = BODY_LAYER+HAIR_LAYER, "pixel_y" = head_organ.head_offset) if(em_block_ears) semifinal.overlays += em_block_ears // Leaving this as overlays += @@ -831,7 +836,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() suit_sprite = INV_SUIT_DEF_ICON var/icon/c_mask = null - var/tail_is_rendered = (overlays_standing[TAIL_LAYER] || overlays_standing[TAIL_LAYER_ALT]) + var/tail_is_rendered = (overlays_standing[TAIL_NORTH_LAYER] || overlays_standing[TAIL_SOUTH_LAYER]) var/valid_clip_mask = tail_style?.clip_mask if(tail_is_rendered && valid_clip_mask && !(istype(suit) && suit.taurized)) //Clip the lower half of the suit off using the tail's clip mask for taurs since taur bodies aren't hidden. @@ -948,16 +953,16 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() if(QDESTROYING(src)) return - remove_layer(TAIL_LAYER) - remove_layer(TAIL_LAYER_ALT) // Alt Tail Layer + remove_layer(TAIL_NORTH_LAYER) + remove_layer(TAIL_SOUTH_LAYER) - var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER + var tail_layer = GET_TAIL_LAYER var/image/tail_image = get_tail_image() if(tail_image) - tail_image.layer = BODY_LAYER+used_tail_layer - overlays_standing[used_tail_layer] = tail_image - apply_layer(used_tail_layer) + tail_image.layer = BODY_LAYER+tail_layer + overlays_standing[tail_layer] = tail_image + apply_layer(tail_layer) return var/species_tail = species.get_tail(src) // Species tail icon_state prefix. @@ -965,7 +970,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() //This one is actually not that bad I guess. if(species_tail && !(wear_suit && wear_suit.flags_inv & HIDETAIL)) var/icon/tail_s = get_tail_icon() - overlays_standing[used_tail_layer] = image(icon = tail_s, icon_state = "[species_tail]_s", layer = BODY_LAYER+used_tail_layer) // Alt Tail Layer + overlays_standing[tail_layer] = image(icon = tail_s, icon_state = "[species_tail]_s", layer = BODY_LAYER+tail_layer) animate_tail_reset() //TODO: Is this the appropriate place for this, and not on species...? @@ -990,19 +995,19 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() return tail_icon /mob/living/carbon/human/proc/set_tail_state(var/t_state) - var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER // Alt Tail Layer - var/image/tail_overlay = overlays_standing[used_tail_layer] + var/tail_layer = GET_TAIL_LAYER + var/image/tail_overlay = overlays_standing[tail_layer] - remove_layer(TAIL_LAYER) - remove_layer(TAIL_LAYER_ALT) + remove_layer(TAIL_NORTH_LAYER) + remove_layer(TAIL_SOUTH_LAYER) if(tail_overlay) - overlays_standing[used_tail_layer] = tail_overlay + overlays_standing[tail_layer] = tail_overlay if(species.get_tail_animation(src)) tail_overlay.icon_state = t_state . = tail_overlay - apply_layer(used_tail_layer) + apply_layer(tail_layer) //Not really once, since BYOND can't do that. //Update this if the ability to flick() images or make looping animation start at the first frame is ever added. @@ -1012,9 +1017,9 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() return var/t_state = "[species.get_tail(src)]_once" - var/used_tail_layer = tail_alt ? TAIL_LAYER_ALT : TAIL_LAYER // Alt Tail Layer + var/tail_layer = GET_TAIL_LAYER - var/image/tail_overlay = overlays_standing[used_tail_layer] // Alt Tail Layer + var/image/tail_overlay = overlays_standing[tail_layer] if(tail_overlay && tail_overlay.icon_state == t_state) return //let the existing animation finish @@ -1022,7 +1027,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() if(tail_overlay) spawn(20) //check that the animation hasn't changed in the meantime - if(overlays_standing[used_tail_layer] == tail_overlay && tail_overlay.icon_state == t_state) // Alt Tail Layer + if(overlays_standing[tail_layer] == tail_overlay && tail_overlay.icon_state == t_state) animate_tail_stop() /mob/living/carbon/human/proc/animate_tail_start() @@ -1176,7 +1181,7 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() var/icon/ears_s = new/icon("icon" = synthetic.icon, "icon_state" = "ears") ears_s.Blend(rgb(src.r_ears, src.g_ears, src.b_ears), species.color_mult ? ICON_MULTIPLY : ICON_ADD) return ears_s - + if(ear_style && !(head && (head.flags_inv & BLOCKHEADHAIR))) var/icon/ears_s = new/icon("icon" = ear_style.icon, "icon_state" = ear_style.icon_state) if(ear_style.do_colouration) @@ -1272,7 +1277,9 @@ var/global/list/damage_icon_parts = list() //see UpdateDamageIcon() #undef GLOVES_LAYER #undef BELT_LAYER #undef SUIT_LAYER -#undef TAIL_LAYER +#undef TAIL_NORTH_LAYER +#undef TAIL_SOUTH_LAYER +#undef GET_TAIL_LAYER #undef GLASSES_LAYER #undef BELT_LAYER_ALT #undef SUIT_STORE_LAYER diff --git a/code/modules/mob/living/carbon/lick_wounds.dm b/code/modules/mob/living/carbon/lick_wounds.dm index bd726e4ac6..d9f33039c4 100644 --- a/code/modules/mob/living/carbon/lick_wounds.dm +++ b/code/modules/mob/living/carbon/lick_wounds.dm @@ -1,4 +1,4 @@ -/mob/living/carbon/human/proc/lick_wounds(var/mob/living/carbon/M) // Allows the user to lick themselves. Given how rarely this trait is used, I don't see an issue with a slight buff. +/mob/living/carbon/human/proc/lick_wounds(var/mob/living/carbon/M as mob in range(1)) // Allows the user to lick themselves. Given how rarely this trait is used, I don't see an issue with a slight buff. set name = "Lick Wounds" set category = "Abilities" set desc = "Disinfect and heal small wounds with your saliva." @@ -83,4 +83,4 @@ W.bandage() W.disinfect() H.UpdateDamageIcon() - playsound(src, 'sound/effects/ointment.ogg', 25) \ No newline at end of file + playsound(src, 'sound/effects/ointment.ogg', 25) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index 09147c527f..f62dbb8ec5 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -1206,9 +1206,13 @@ if(!screen_icon) screen_icon = new() RegisterSignal(screen_icon, COMSIG_CLICK, .proc/character_setup_click) - screen_icon.icon = HUD.ui_style - screen_icon.color = HUD.ui_color - screen_icon.alpha = HUD.ui_alpha + if(ispAI(user)) + screen_icon.icon = 'icons/mob/pai_hud.dmi' + screen_icon.screen_loc = ui_acti + else + screen_icon.icon = HUD.ui_style + screen_icon.color = HUD.ui_color + screen_icon.alpha = HUD.ui_alpha LAZYADD(HUD.other_important, screen_icon) user.client?.screen += screen_icon diff --git a/code/modules/mob/living/say.dm b/code/modules/mob/living/say.dm index 4fce3c84ea..84b825b644 100644 --- a/code/modules/mob/living/say.dm +++ b/code/modules/mob/living/say.dm @@ -141,7 +141,7 @@ var/list/channel_to_radio_key = new return "asks" return verb -/mob/living/say(var/message, var/whispering = 0) +/mob/living/say(var/message, var/datum/language/speaking = null, var/whispering = 0) //If you're muted for IC chat if(client) if(message) diff --git a/code/modules/mob/living/silicon/emote.dm b/code/modules/mob/living/silicon/emote.dm index 153e05a7b6..a471c62b22 100644 --- a/code/modules/mob/living/silicon/emote.dm +++ b/code/modules/mob/living/silicon/emote.dm @@ -1,14 +1,23 @@ var/list/_silicon_default_emotes = list( /decl/emote/audible/synth, - /decl/emote/audible/synth/ping, + /decl/emote/audible/synth/beep, /decl/emote/audible/synth/buzz, /decl/emote/audible/synth/confirm, /decl/emote/audible/synth/deny, /decl/emote/audible/synth/scary, /decl/emote/audible/synth/dwoop, + /decl/emote/audible/synth/boop, + /decl/emote/audible/synth/robochirp, /decl/emote/audible/synth/security, /decl/emote/audible/synth/security/halt ) /mob/living/silicon/get_available_emotes() return global._silicon_default_emotes + +/mob/living/silicon/pai/get_available_emotes() + + var/list/fulllist = _silicon_default_emotes + fulllist |= _robot_default_emotes + fulllist |= _human_default_emotes + return fulllist \ No newline at end of file diff --git a/code/modules/mob/living/silicon/pai/examine.dm b/code/modules/mob/living/silicon/pai/examine.dm index b6890470c9..9c009dcf6f 100644 --- a/code/modules/mob/living/silicon/pai/examine.dm +++ b/code/modules/mob/living/silicon/pai/examine.dm @@ -9,16 +9,10 @@ // VOREStation Edit: Start . += attempt_vr(src,"examine_bellies",args) //VOREStation Edit - if(ooc_notes) - . += "OOC Notes: \[View\]" - // VOREStation Edit: End - - . += "*---------*" - if(print_flavor_text()) . += "\n[print_flavor_text()]\n" - + // VOREStation Edit: End + . += "*---------*" if (pose) if(!findtext(pose, regex("\[.?!]$"))) // Will be zero if the last character is not a member of [.?!] pose = addtext(pose,".") //Makes sure all emotes end with a period. . += "
It is [pose]" //Extra
intentional - \ No newline at end of file diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm index e4f461179d..2871988eee 100644 --- a/code/modules/mob/living/silicon/pai/life.dm +++ b/code/modules/mob/living/silicon/pai/life.dm @@ -1,6 +1,6 @@ /mob/living/silicon/pai/Life() - if (src.stat == 2) + if (src.stat == DEAD) return if(src.cable) @@ -25,7 +25,9 @@ if(health <= 0) death(null,"gives one shrill beep before falling lifeless.") - + else if(health < maxHealth && istype(src.loc , /obj/item/device/paicard)) + adjustBruteLoss(-0.5) + adjustFireLoss(-0.5) /mob/living/silicon/pai/updatehealth() if(status_flags & GODMODE) diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index a1764fbcd2..e007e0aa44 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -31,7 +31,7 @@ "Cat" = "pai-cat", "Mouse" = "pai-mouse", "Monkey" = "pai-monkey", - "Corgi" = "pai-borgi", + "Borgi" = "pai-borgi", "Fox" = "pai-fox", "Parrot" = "pai-parrot", "Rabbit" = "pai-rabbit", @@ -47,7 +47,12 @@ "Hawk" = "pai-hawk", "Duffel" = "pai-duffel", "Rat" = "rat", - "Panther" = "panther" + "Panther" = "panther", + "Cyber Elf" = "cyberelf", + "Teppi" = "teppi", + "Catslug" = "catslug", + "Car" = "car", + "Type One" = "typeone" //VOREStation Addition End ) @@ -256,10 +261,6 @@ return 0 else if(istype(card.loc,/mob)) var/mob/holder = card.loc - var/datum/belly/inside_belly = check_belly(card) //VOREStation edit. - if(inside_belly) //VOREStation edit. - to_chat(src, "There is no room to unfold in here. You're good and stuck.") //VOREStation edit. - return 0 //VOREStation edit. if(ishuman(holder)) var/mob/living/carbon/human/H = holder for(var/obj/item/organ/external/affecting in H.organs) @@ -269,6 +270,9 @@ H.visible_message("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!") break holder.drop_from_inventory(card) + else if(isbelly(card.loc)) //VOREStation edit. + to_chat(src, "There is no room to unfold in here. You're good and stuck.") //VOREStation edit. + return 0 //VOREStation edit. else if(istype(card.loc,/obj/item/device/pda)) var/obj/item/device/pda/holder = card.loc holder.pai = null @@ -282,9 +286,9 @@ var/turf/T = get_turf(src) if(istype(T)) T.visible_message("[src] folds outwards, expanding into a mobile form.") - verbs += /mob/living/silicon/pai/proc/pai_nom //VOREStation edit - verbs += /mob/living/proc/set_size //VOREStation edit - verbs += /mob/living/proc/shred_limb //VORREStation edit + verbs |= /mob/living/silicon/pai/proc/pai_nom + verbs |= /mob/living/proc/vertical_nom + update_icon() /mob/living/silicon/pai/verb/fold_up() set category = "pAI Commands" @@ -378,7 +382,7 @@ if(src.loc == card) return - release_vore_contents() //VOREStation Add + release_vore_contents(FALSE) //VOREStation Add var/turf/T = get_turf(src) if(istype(T)) T.visible_message("[src] neatly folds inwards, compacting down to a rectangular card.") @@ -408,7 +412,10 @@ canmove = 1 resting = 0 icon_state = "[chassis]" - verbs -= /mob/living/silicon/pai/proc/pai_nom //VOREStation edit. Let's remove their nom verb + if(isopenspace(card.loc)) + fall() + verbs -= /mob/living/silicon/pai/proc/pai_nom + verbs -= /mob/living/proc/vertical_nom // No binary for pAIs. /mob/living/silicon/pai/binarycheck() @@ -433,10 +440,12 @@ if("Add Access") idcard.access |= ID.access to_chat(user, "You add the access from the [W] to [src].") + to_chat(src, "\The [user] swipes the [W] over you. You copy the access codes.") return if("Remove Access") idcard.access = list() to_chat(user, "You remove the access from [src].") + to_chat(src, "\The [user] swipes the [W] over you, removing access codes from you.") return if("Cancel") return @@ -451,16 +460,16 @@ if(idaccessible == 0) idaccessible = 1 - to_chat(src, "You allow access modifications.") - + visible_message("\The [src] clicks as their access modification slot opens.","You allow access modifications.", runemessage = "click") else idaccessible = 0 - to_chat(src, "You block access modfications.") + visible_message("\The [src] clicks as their access modification slot closes.","You block access modfications.", runemessage = "click") + /mob/living/silicon/pai/verb/wipe_software() - set name = "Wipe Software" - set category = "OOC" - set desc = "Wipe your software. This is functionally equivalent to cryo or robotic storage, freeing up your job slot." + set name = "Enter Storage" + set category = "pAI Commands" + set desc = "Upload your personality to the cloud and wipe your software from the card. This is functionally equivalent to cryo or robotic storage, freeing up your job slot." // Make sure people don't kill themselves accidentally if(tgui_alert(usr, "WARNING: This will immediately wipe your software and ghost you, removing your character from the round permanently (similar to cryo and robotic storage). Are you entirely sure you want to do this?", "Wipe Software", list("No", "Yes")) != "Yes") diff --git a/code/modules/mob/living/silicon/pai/pai_hud.dm b/code/modules/mob/living/silicon/pai/pai_hud.dm new file mode 100644 index 0000000000..6bdea250d2 --- /dev/null +++ b/code/modules/mob/living/silicon/pai/pai_hud.dm @@ -0,0 +1,263 @@ +/mob/living/silicon/pai + var/obj/screen/pai/pai_fold_display = null + +/obj/screen/pai/pai_fold_display + name = "fold/unfold" + icon = 'icons/mob/pai_hud.dmi' + +/datum/hud + var/list/hud_elements = list() + +/mob/living/silicon/pai/create_mob_hud(datum/hud/HUD) + ..() + + var/ui_style = 'icons/mob/pai_hud.dmi' + + var/ui_color = "#ffffff" + var/ui_alpha = 255 + + var/list/adding = list() + var/list/other = list() + var/list/hotkeybuttons = list() + var/list/hud_elements = list() + + HUD.adding = adding + HUD.other = other + HUD.hotkeybuttons = hotkeybuttons + HUD.hud_elements = hud_elements + + var/obj/screen/using + + //Small intent quarters + + using = new /obj/screen() + using.name = I_HELP + using.icon = ui_style + using.icon_state = "intent_help-s" + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM //These sit on the intent box + HUD.adding += using + HUD.help_intent = using + + using = new /obj/screen() + using.name = I_DISARM + using.icon = ui_style + using.icon_state = "intent_disarm-n" + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM + HUD.adding += using + HUD.disarm_intent = using + + using = new /obj/screen() + using.name = I_GRAB + using.icon = ui_style + using.icon_state = "intent_grab-n" + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM + HUD.adding += using + HUD.grab_intent = using + + using = new /obj/screen() + using.name = I_HURT + using.icon = ui_style + using.icon_state = "intent_harm-n" + using.screen_loc = ui_acti + using.alpha = ui_alpha + using.layer = LAYER_HUD_ITEM + HUD.adding += using + HUD.hurt_intent = using + + //Move intent (walk/run) + using = new /obj/screen() + using.name = "mov_intent" + using.icon = ui_style + using.icon_state = (m_intent == "run" ? "running" : "walking") + using.screen_loc = ui_movi + using.color = ui_color + using.alpha = ui_alpha + HUD.adding += using + HUD.move_intent = using + + //Resist button + using = new /obj/screen() + using.name = "resist" + using.icon = ui_style + using.icon_state = "act_resist" + using.screen_loc = ui_movi + using.color = ui_color + using.alpha = ui_alpha + HUD.hotkeybuttons += using + + //Choose chassis button + using = new /obj/screen() + using.name = "choose chassis" + using.icon = ui_style + using.icon_state = "choose_chassis" + using.screen_loc = ui_movi + using.color = ui_color + using.alpha = ui_alpha + hud_elements |= using + + //Software interface button + using = new /obj/screen() + using.name = "software interface" + using.icon = ui_style + using.icon_state = "software_interface" + using.screen_loc = ui_acti + using.color = ui_color + using.alpha = ui_alpha + hud_elements |= using + + //Radio configuration button + using = new /obj/screen() + using.name = "radio configuration" + using.icon = ui_style + using.icon_state = "radio_configuration" + using.screen_loc = ui_acti + using.color = ui_color + using.alpha = ui_alpha + hud_elements |= using + + //PDA button + using = new /obj/screen() + using.name = "pda" + using.icon = ui_style + using.icon_state = "pda" + using.screen_loc = ui_pai_comms + using.color = ui_color + using.alpha = ui_alpha + hud_elements |= using + + //Communicator button + using = new /obj/screen() + using.name = "communicator" + using.icon = ui_style + using.icon_state = "communicator" + using.screen_loc = ui_pai_comms + using.color = ui_color + using.alpha = ui_alpha + hud_elements |= using + + //Language button + using = new /obj/screen() + using.name = "known languages" + using.icon = ui_style + using.icon_state = "language" + using.screen_loc = ui_acti + using.color = ui_color + using.alpha = ui_alpha + hud_elements |= using + + //Pull button + pullin = new /obj/screen() + pullin.icon = ui_style + pullin.icon_state = "pull0" + pullin.name = "pull" + pullin.screen_loc = ui_movi + HUD.hotkeybuttons += pullin + HUD.hud_elements |= pullin + + //Health status + healths = new /obj/screen() + healths.icon = ui_style + healths.icon_state = "health0" + healths.name = "health" + healths.screen_loc = ui_health + HUD.hud_elements |= healths + + pain = new /obj/screen( null ) + + zone_sel = new /obj/screen/zone_sel( null ) + zone_sel.icon = ui_style + zone_sel.color = ui_color + zone_sel.alpha = ui_alpha + zone_sel.cut_overlays() + zone_sel.update_icon() + HUD.hud_elements |= zone_sel + + pai_fold_display = new /obj/screen/pai/pai_fold_display() + pai_fold_display.screen_loc = ui_health + pai_fold_display.icon_state = "folded" + HUD.hud_elements |= pai_fold_display + + + if(client) + client.screen = list() + client.screen += hud_elements + client.screen += adding + hotkeybuttons + client.screen += client.void + +/mob/living/silicon/pai/handle_regular_hud_updates() + . = ..() + if(healths) + if(stat != DEAD) + var/heal_per = (health / getMaxHealth()) * 100 + switch(heal_per) + if(100 to INFINITY) + healths.icon_state = "health0" + if(80 to 100) + healths.icon_state = "health1" + if(60 to 80) + healths.icon_state = "health2" + if(40 to 60) + healths.icon_state = "health3" + if(20 to 40) + healths.icon_state = "health4" + if(0 to 20) + healths.icon_state = "health5" + else + healths.icon_state = "health6" + else + healths.icon_state = "health7" + + if(pai_fold_display) + if(loc == card) + pai_fold_display.icon_state = "folded" + else + pai_fold_display.icon_state = "unfolded" + +/mob/living/silicon/pai/toggle_hud_vis(full) + + if(hud_used.hud_shown) + hud_used.hud_shown = 0 + if(hud_used.adding) + client.screen -= hud_used.adding + if(hud_used.other) + client.screen -= hud_used.other + if(hud_used.hotkeybuttons) + client.screen -= hud_used.hotkeybuttons + if(hud_used.other_important) + client.screen -= hud_used.other_important + if(hud_used.hud_elements) + client.screen -= hud_used.hud_elements + + else + hud_used.hud_shown = 1 + if(hud_used.adding) + client.screen += hud_used.adding + if(hud_used.other && hud_used.inventory_shown) + client.screen += hud_used.other + if(hud_used.other_important) + client.screen += hud_used.other_important + if(hud_used.hotkeybuttons && !hud_used.hotkey_ui_hidden) + client.screen += hud_used.hotkeybuttons + if(healths) + client.screen |= healths + if(internals) + client.screen |= internals + if(gun_setting_icon) + client.screen |= gun_setting_icon + if(hud_used.hud_elements) + client.screen |= hud_used.hud_elements + + hud_used?.action_intent.screen_loc = ui_acti //Restore intent selection to the original position + client.screen += zone_sel //This one is a special snowflake + + hud_used.hidden_inventory_update() + hud_used.persistant_inventory_update() + update_action_buttons() + hud_used.reorganize_alerts() + return TRUE diff --git a/code/modules/mob/living/silicon/pai/pai_vr.dm b/code/modules/mob/living/silicon/pai/pai_vr.dm index d5f989b67e..61e2f10a21 100644 --- a/code/modules/mob/living/silicon/pai/pai_vr.dm +++ b/code/modules/mob/living/silicon/pai/pai_vr.dm @@ -1,10 +1,56 @@ /mob/living/silicon/pai var/people_eaten = 0 icon = 'icons/mob/pai_vr.dmi' + softfall = TRUE + var/eye_glow = TRUE + var/image/eye_layer = null // Holds the eye overlay. + var/eye_color = "#00ff0d" var/global/list/wide_chassis = list( "rat", - "panther" + "panther", + "teppi" ) + var/global/list/flying_chassis = list( + "pai-parrot", + "pai-bat", + "pai-butterfly", + "pai-hawk", + "cyberelf" + ) + + //Sure I could spend all day making wacky overlays for all of the different forms + //but quite simply most of these sprites aren't made for that, and I'd rather just make new ones + //the birds especially! Just naw. If someone else wants to mess with 12x4 frames of animation where + //most of the pixels are different kinds of green and tastefully translate that to whitescale + //they can have fun with that! I not doing it! + var/global/list/allows_eye_color = list( + "pai-repairbot", + "pai-typezero", + "pai-bat", + "pai-butterfly", + "pai-mouse", + "pai-monkey", + "pai-raccoon", + "pai-cat", + "rat", + "panther", + "pai-bear", + "pai-fen", + "cyberelf", + "teppi", + "catslug", + "car", + "typeone" + ) + +/mob/living/silicon/pai/Initialize() + . = ..() + + verbs |= /mob/living/proc/hide + verbs |= /mob/proc/dominate_predator + verbs |= /mob/living/proc/dominate_prey + verbs |= /mob/living/proc/set_size + verbs |= /mob/living/proc/shred_limb /mob/living/silicon/pai/proc/pai_nom(var/mob/living/T in oview(1)) set name = "pAI Nom" @@ -42,15 +88,13 @@ icon_state = "[chassis]_rest_full" else icon_state = "[chassis]_rest" - if(chassis in wide_chassis) - icon = 'icons/mob/pai_vr64x64.dmi' pixel_x = -16 - vis_height = 64 + default_pixel_x = -16 else - icon = 'icons/mob/pai_vr.dmi' pixel_x = 0 - vis_height = 32 + default_pixel_x = 0 + add_eyes() /mob/living/silicon/pai/update_icons() //And other functions cause this to occur, such as digesting someone. ..() @@ -63,13 +107,14 @@ icon_state = "[chassis]_full" else if(people_eaten && resting) icon_state = "[chassis]_rest_full" - if(chassis in wide_chassis) - icon = 'icons/mob/pai_vr64x64.dmi' pixel_x = -16 + default_pixel_x = -16 else - icon = 'icons/mob/pai_vr.dmi' pixel_x = 0 + default_pixel_x = 0 + add_eyes() + //proc override to avoid pAI players being invisible while the chassis selection window is open /mob/living/silicon/pai/proc/choose_chassis() set category = "pAI Commands" @@ -78,10 +123,243 @@ choice = tgui_input_list(usr, "What would you like to use for your mobile chassis icon?", "Chassis Choice", possible_chassis) if(!choice) return + var/oursize = size_multiplier + resize(1, FALSE, TRUE, TRUE, FALSE) //We resize ourselves to normal here for a moment to let the vis_height get reset chassis = possible_chassis[choice] - verbs |= /mob/living/proc/hide + if(chassis in wide_chassis) + icon = 'icons/mob/pai_vr64x64.dmi' + vis_height = 64 + else + icon = 'icons/mob/pai_vr.dmi' + vis_height = 32 + resize(oursize, FALSE, TRUE, TRUE, FALSE) //And then back again now that we're sure the vis_height is correct. + + if(chassis in flying_chassis) + hovering = TRUE + else + hovering = FALSE + if(isopenspace(loc)) + fall() + update_icon() + +/mob/living/silicon/pai/verb/toggle_eyeglow() + set category = "pAI Commands" + set name = "Toggle Eye Glow" + if(chassis in allows_eye_color) + if(eye_glow) + eye_glow = FALSE + else + eye_glow = TRUE + update_icon() + else + to_chat(src, "Your selected chassis cannot modify its eye glow!") + return + + +/mob/living/silicon/pai/verb/pick_eye_color() + set category = "pAI Commands" + set name = "Pick Eye Color" + if(chassis in allows_eye_color) + else + to_chat(src, "Your selected chassis eye color can not be modified. The color you pick will only apply to supporting chassis and your card screen.") + + var/new_eye_color = input(src, "Choose your character's eye color:", "Eye Color") as color|null + if(new_eye_color) + eye_color = new_eye_color + update_icon() + card.setEmotion(card.current_emotion) + // Release belly contents before being gc'd! /mob/living/silicon/pai/Destroy() release_vore_contents() - return ..() \ No newline at end of file + return ..() + +/mob/living/silicon/pai/proc/add_eyes() + remove_eyes() + if(chassis in allows_eye_color) + if(!eye_layer) + eye_layer = image(icon, "[icon_state]-eyes") + eye_layer.appearance_flags = appearance_flags + eye_layer.color = eye_color + if(eye_glow) + eye_layer.plane = PLANE_LIGHTING_ABOVE + add_overlay(eye_layer) + +/mob/living/silicon/pai/proc/remove_eyes() + cut_overlay(eye_layer) + qdel(eye_layer) + eye_layer = null + +/mob/living/silicon/pai/UnarmedAttack(atom/A, proximity_flag) + . = ..() + + if(!ismob(A) || A == src) + return + + switch(a_intent) + if(I_HELP) + if(isliving(A)) + hug(src, A) + if(I_GRAB) + pai_nom(A) + +/mob/living/silicon/pai/proc/hug(var/mob/living/silicon/pai/H, var/mob/living/target) + + var/t_him = "them" + if(ishuman(target)) + var/mob/living/carbon/human/T = target + switch(T.identifying_gender) + if(MALE) + t_him = "him" + if(FEMALE) + t_him = "her" + if(NEUTER) + t_him = "it" + if(HERM) + t_him = "hir" + else + t_him = "them" + else + switch(target.gender) + if(MALE) + t_him = "him" + if(FEMALE) + t_him = "her" + if(NEUTER) + t_him = "it" + if(HERM) + t_him = "hir" + else + t_him = "them" + + if(H.zone_sel.selecting == "head") + H.visible_message( \ + "[H] pats [target] on the head.", \ + "You pat [target] on the head.", ) + else if(H.zone_sel.selecting == "r_hand" || H.zone_sel.selecting == "l_hand") + H.visible_message( \ + "[H] shakes [target]'s hand.", \ + "You shake [target]'s hand.", ) + else if(H.zone_sel.selecting == "mouth") + H.visible_message( \ + "[H] boops [target]'s nose.", \ + "You boop [target] on the nose.", ) + else + H.visible_message("[H] hugs [target] to make [t_him] feel better!", \ + "You hug [target] to make [t_him] feel better!") + playsound(src, 'sound/weapons/thudswoosh.ogg', 50, 1, -1) + +/mob/living/silicon/pai/proc/savefile_path(mob/user) + return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav" + +/mob/living/silicon/pai/proc/savefile_save(mob/user) + if(IsGuestKey(user.key)) + return 0 + + var/savefile/F = new /savefile(src.savefile_path(user)) + + + F["name"] << src.name + F["description"] << src.flavor_text + F["eyecolor"] << src.eye_color + F["chassis"] << src.chassis + F["emotion"] << src.card.current_emotion + F["gender"] << src.gender + F["version"] << 1 + + return 1 + +/mob/living/silicon/pai/proc/savefile_load(mob/user, var/silent = 1) + if (IsGuestKey(user.key)) + return 0 + + var/path = savefile_path(user) + + if (!fexists(path)) + return 0 + + var/savefile/F = new /savefile(path) + + if(!F) return //Not everyone has a pai savefile. + + var/version = null + F["version"] >> version + + if (isnull(version) || version != 1) + fdel(path) + if (!silent) + tgui_alert_async(user, "Your savefile was incompatible with this version and was deleted.") + return 0 + var/ourname + var/ouremotion + var/ourdesc + var/oureyes + var/ourchassis + var/ourgender + F["name"] >> ourname + F["description"] >> ourdesc + F["eyecolor"] >> oureyes + F["chassis"] >> ourchassis + F["emotion"] >> ouremotion + F["gender"] >> ourgender + if(ourname) + SetName(ourname) + if(ourdesc) + flavor_text = ourdesc + if(ourchassis) + chassis = ourchassis + if(ourgender) + gender = ourgender + if(oureyes) + card.screen_color = oureyes + eye_color = oureyes + if(ouremotion) + card.setEmotion(ouremotion) + + update_icon() + return 1 + +/mob/living/silicon/pai/verb/save_pai_to_slot() + set category = "pAI Commands" + set name = "Save Configuration" + savefile_save(src) + to_chat(src, "[name] configuration saved to global pAI settings.") + +/mob/living/silicon/pai/a_intent_change(input as text) + . = ..() + + switch(a_intent) + if(I_HELP) + hud_used.help_intent.icon_state = "intent_help-s" + hud_used.disarm_intent.icon_state = "intent_disarm-n" + hud_used.grab_intent.icon_state = "intent_grab-n" + hud_used.hurt_intent.icon_state = "intent_harm-n" + + if(I_DISARM) + hud_used.help_intent.icon_state = "intent_help-n" + hud_used.disarm_intent.icon_state = "intent_disarm-s" + hud_used.grab_intent.icon_state = "intent_grab-n" + hud_used.hurt_intent.icon_state = "intent_harm-n" + + if(I_GRAB) + hud_used.help_intent.icon_state = "intent_help-n" + hud_used.disarm_intent.icon_state = "intent_disarm-n" + hud_used.grab_intent.icon_state = "intent_grab-s" + hud_used.hurt_intent.icon_state = "intent_harm-n" + + if(I_HURT) + hud_used.help_intent.icon_state = "intent_help-n" + hud_used.disarm_intent.icon_state = "intent_disarm-n" + hud_used.grab_intent.icon_state = "intent_grab-n" + hud_used.hurt_intent.icon_state = "intent_harm-s" + +/mob/living/silicon/pai/verb/toggle_gender_identity_vr() + set name = "Set Gender Identity" + set desc = "Sets the pronouns when examined and performing an emote." + set category = "IC" + var/new_gender_identity = tgui_input_list(usr, "Please select a gender Identity:", "Set Gender Identity", list(FEMALE, MALE, NEUTER, PLURAL, HERM)) + if(!new_gender_identity) + return 0 + gender = new_gender_identity + return 1 diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm index 73ae49dcb7..de2b8fa202 100644 --- a/code/modules/mob/living/silicon/pai/personality.dm +++ b/code/modules/mob/living/silicon/pai/personality.dm @@ -57,4 +57,8 @@ F["description"] >> src.description F["role"] >> src.role F["comments"] >> src.comments + F["eyecolor"] >> src.eye_color + F["chassis"] >> src.chassis + F["emotion"] >> src.ouremotion + return 1 diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm index ffd817d9c4..a1dd02b163 100644 --- a/code/modules/mob/living/silicon/pai/recruit.dm +++ b/code/modules/mob/living/silicon/pai/recruit.dm @@ -11,7 +11,10 @@ var/datum/paiController/paiController // Global handler for pAI candidates var/role var/comments var/ready = 0 - + var/chassis + var/ouremotion + var/eye_color + var/gender /hook/startup/proc/paiControllerSetup() paiController = new /datum/paiController() @@ -33,14 +36,25 @@ var/datum/paiController/paiController // Global handler for pAI candidates return if(istype(card,/obj/item/device/paicard) && istype(candidate,/datum/paiCandidate)) var/mob/living/silicon/pai/pai = new(card) - if(!candidate.name) - pai.name = pick(ninja_names) - else - pai.name = candidate.name - pai.real_name = pai.name pai.key = candidate.key - card.setPersonality(pai) + if(!candidate.name) + pai.SetName(pick(ninja_names)) + else + pai.SetName(candidate.name) + if(candidate.description) + pai.flavor_text = candidate.description + if(candidate.eye_color) + pai.eye_color = candidate.eye_color + card.screen_color = pai.eye_color + if(candidate.chassis) + pai.chassis = candidate.chassis + if(candidate.ouremotion) + card.setEmotion(candidate.ouremotion) + if(candidate.gender) + pai.gender = candidate.gender + pai.update_icon() + pai.real_name = pai.name card.looking_for_personality = 0 if(pai.mind) update_antag_icons(pai.mind) @@ -210,7 +224,7 @@ var/datum/paiController/paiController // Global handler for pAI candidates - Load Personality + Load Personality
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm index 3a98a5bea3..3475700c4b 100644 --- a/code/modules/mob/living/silicon/pai/software.dm +++ b/code/modules/mob/living/silicon/pai/software.dm @@ -1,19 +1,20 @@ var/list/pai_emotions = list( - "Happy" = 1, - "Cat" = 2, - "Extremely Happy" = 3, - "Face" = 4, - "Laugh" = 5, - "Off" = 6, - "Sad" = 7, - "Angry" = 8, - "What" = 9, - "Neutral" = 10, - "Silly" = 11, - "Nose" = 12, - "Smirk" = 13, - "Exclamation Points" = 14, - "Question Mark" = 15 + "Neutral" = 1, + "What" = 2, + "Happy" = 3, + "Cat" = 4, + "Extremely Happy" = 5, + "Face" = 6, + "Laugh" = 7, + "Sad" = 8, + "Angry" = 9, + "Silly" = 10, + "Nose" = 11, + "Smirk" = 12, + "Exclamation Points" = 13, + "Question Mark" = 14, + "Blank" = 15, + "Off" = 16 ) diff --git a/code/modules/mob/living/silicon/pai/software_modules.dm b/code/modules/mob/living/silicon/pai/software_modules.dm index 1fb28a58fd..07d82b74d0 100644 --- a/code/modules/mob/living/silicon/pai/software_modules.dm +++ b/code/modules/mob/living/silicon/pai/software_modules.dm @@ -98,9 +98,10 @@ /datum/pai_software/crew_manifest name = "Crew Manifest" - ram_cost = 5 + ram_cost = 0 id = "manifest" toggle = 0 + default = 1 //Comes with the communicator already, also why not /datum/pai_software/crew_manifest/tgui_interact(mob/user, datum/tgui/ui, datum/tgui/parent_ui) ui = SStgui.try_update_ui(user, src, ui) @@ -117,9 +118,10 @@ /datum/pai_software/messenger name = "Digital Messenger" - ram_cost = 5 + ram_cost = 0 id = "messenger" toggle = 0 + default = 1 //Can already be accessed through verbs, and also why not /datum/pai_software/messenger/tgui_interact(mob/living/silicon/pai/user, datum/tgui/ui, datum/tgui/parent_ui) return user.pda.tgui_interact(user, parent_ui = parent_ui) @@ -411,6 +413,35 @@ user.add_language(LANGUAGE_SKRELLIAN) user.add_language(LANGUAGE_ZADDAT) user.add_language(LANGUAGE_SCHECHI) + user.add_language(LANGUAGE_DRUDAKAR) + user.add_language(LANGUAGE_SLAVIC) + user.add_language(LANGUAGE_BIRDSONG) + user.add_language(LANGUAGE_SAGARU) + user.add_language(LANGUAGE_CANILUNZT) + user.add_language(LANGUAGE_ECUREUILIAN) + user.add_language(LANGUAGE_DAEMON) + user.add_language(LANGUAGE_ENOCHIAN) + user.add_language(LANGUAGE_VESPINAE) + user.add_language(LANGUAGE_SPACER) + user.add_language(LANGUAGE_CLOWNISH) + user.add_language(LANGUAGE_TAVAN) + user.add_language(LANGUAGE_ECHOSONG) + user.add_language(LANGUAGE_CHIMPANZEE) + user.add_language(LANGUAGE_NEAERA) + user.add_language(LANGUAGE_STOK) + user.add_language(LANGUAGE_FARWA) + user.add_language(LANGUAGE_ROOTLOCAL) + user.add_language(LANGUAGE_VOX) + user.add_language(LANGUAGE_SKRELLIANFAR) + user.add_language(LANGUAGE_MINBUS) + user.add_language(LANGUAGE_ALAI) + user.add_language(LANGUAGE_PROMETHEAN) + user.add_language(LANGUAGE_GIBBERISH) + user.add_language("Mouse") + user.add_language("Cat") + user.add_language("Bird") + user.add_language("Dog") + user.add_language("Teppi") else user.remove_language(LANGUAGE_UNATHI) user.remove_language(LANGUAGE_SIIK) @@ -418,6 +449,35 @@ user.remove_language(LANGUAGE_SKRELLIAN) user.remove_language(LANGUAGE_ZADDAT) user.remove_language(LANGUAGE_SCHECHI) + user.remove_language(LANGUAGE_DRUDAKAR) + user.remove_language(LANGUAGE_SLAVIC) + user.remove_language(LANGUAGE_BIRDSONG) + user.remove_language(LANGUAGE_SAGARU) + user.remove_language(LANGUAGE_CANILUNZT) + user.remove_language(LANGUAGE_ECUREUILIAN) + user.remove_language(LANGUAGE_DAEMON) + user.remove_language(LANGUAGE_ENOCHIAN) + user.remove_language(LANGUAGE_VESPINAE) + user.remove_language(LANGUAGE_SPACER) + user.remove_language(LANGUAGE_CLOWNISH) + user.remove_language(LANGUAGE_TAVAN) + user.remove_language(LANGUAGE_ECHOSONG) + user.remove_language(LANGUAGE_CHIMPANZEE) + user.remove_language(LANGUAGE_NEAERA) + user.remove_language(LANGUAGE_STOK) + user.remove_language(LANGUAGE_FARWA) + user.remove_language(LANGUAGE_ROOTLOCAL) + user.remove_language(LANGUAGE_VOX) + user.remove_language(LANGUAGE_SKRELLIANFAR) + user.remove_language(LANGUAGE_MINBUS) + user.remove_language(LANGUAGE_ALAI) + user.remove_language(LANGUAGE_PROMETHEAN) + user.remove_language(LANGUAGE_GIBBERISH) + user.remove_language("Mouse") + user.remove_language("Cat") + user.remove_language("Bird") + user.remove_language("Dog") + user.remove_language("Teppi") /datum/pai_software/translator/is_active(mob/living/silicon/pai/user) return user.translator_on diff --git a/code/modules/mob/living/silicon/robot/emote.dm b/code/modules/mob/living/silicon/robot/emote.dm index f2a4d9775b..9def436c7c 100644 --- a/code/modules/mob/living/silicon/robot/emote.dm +++ b/code/modules/mob/living/silicon/robot/emote.dm @@ -16,12 +16,14 @@ var/list/_robot_default_emotes = list( /decl/emote/visible/spin, /decl/emote/visible/sidestep, /decl/emote/audible/synth, - /decl/emote/audible/synth/ping, + /decl/emote/audible/synth/beep, /decl/emote/audible/synth/buzz, /decl/emote/audible/synth/confirm, /decl/emote/audible/synth/deny, /decl/emote/audible/synth/scary, /decl/emote/audible/synth/dwoop, + /decl/emote/audible/synth/boop, + /decl/emote/audible/synth/robochirp, /decl/emote/audible/synth/security, /decl/emote/audible/synth/security/halt, //VOREStation Add diff --git a/code/modules/mob/living/simple_mob/subtypes/animal/pets/fennec.dm b/code/modules/mob/living/simple_mob/subtypes/animal/pets/fennec.dm new file mode 100644 index 0000000000..0ea451246f --- /dev/null +++ b/code/modules/mob/living/simple_mob/subtypes/animal/pets/fennec.dm @@ -0,0 +1,29 @@ +/mob/living/simple_mob/animal/passive/fennec + name = "fennec" + desc = "A fox preferring arid climates, also known as a dingler, or a goob." + tt_desc = "Vulpes Zerda" + icon_state = "fennec" + item_state = "fennec" + + movement_cooldown = 0.5 SECONDS + + see_in_dark = 6 + response_help = "pets" + response_disarm = "gently pushes aside" + response_harm = "kicks" + + holder_type = /obj/item/holder/fennec + mob_size = MOB_SMALL + + has_langs = list("Cat, Dog") //they're similar, why not. + +/mob/living/simple_mob/animal/passive/fennec/faux + name = "faux" + desc = "Domesticated fennec. Seems to like screaming just as much though." + +/mob/living/simple_mob/animal/passive/fennec/Initialize() + icon_living = "[initial(icon_state)]" + icon_dead = "[initial(icon_state)]_dead" + icon_rest = "[initial(icon_state)]_rest" + update_icon() + return ..() diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/dominated_brain.dm b/code/modules/mob/living/simple_mob/subtypes/vore/dominated_brain.dm index 82375f0a15..e69e0fc54a 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/dominated_brain.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/dominated_brain.dm @@ -169,6 +169,10 @@ pred = loc.loc else if(isliving(prey.loc)) pred = loc + else if(ispAI(src)) + var/mob/living/silicon/pai/pocketpal = src + if(isbelly(pocketpal.card.loc)) + pred = pocketpal.card.loc.loc else to_chat(prey, "You are not inside anyone.") return diff --git a/code/modules/mob/new_player/preferences_setup.dm b/code/modules/mob/new_player/preferences_setup.dm index 66d712bc44..e564bba7b0 100644 --- a/code/modules/mob/new_player/preferences_setup.dm +++ b/code/modules/mob/new_player/preferences_setup.dm @@ -259,6 +259,7 @@ mannequin.update_transform() //VOREStation Edit to update size/shape stuff. mannequin.toggle_tail(setting = TRUE) mannequin.toggle_wing(setting = TRUE) + mannequin.update_tail_showing() COMPILE_OVERLAYS(mannequin) update_character_previews(new /mutable_appearance(mannequin)) diff --git a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm index f6b0c77abe..4c09f5d7cd 100644 --- a/code/modules/mob/new_player/sprite_accessories_ear_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_ear_vr.dm @@ -119,6 +119,15 @@ do_colouration = 1 color_blend_mode = ICON_MULTIPLY +/datum/sprite_accessory/ears/antennae_eye + name = "antennae eye, colorable" + desc = "" + icon_state = "antennae" + extra_overlay = "antennae_eye_1" + extra_overlay2 = "antennae_eye_2" + do_colouration = 1 + color_blend_mode = ICON_MULTIPLY + /datum/sprite_accessory/ears/curly_bug name = "curly antennae, colorable" desc = "" diff --git a/code/modules/mob/new_player/sprite_accessories_taur.dm b/code/modules/mob/new_player/sprite_accessories_taur.dm index 4637ac008e..4288f9bbc6 100644 --- a/code/modules/mob/new_player/sprite_accessories_taur.dm +++ b/code/modules/mob/new_player/sprite_accessories_taur.dm @@ -285,6 +285,11 @@ suit_sprites = 'icons/mob/taursuits_slug.dmi' icon_sprite_tag = "slug" +/datum/sprite_accessory/tail/taur/slug/snail + name = "Snail (Taur)" + icon_state = "slug_s" + extra_overlay = "snail_shell_marking" + /datum/sprite_accessory/tail/taur/frog name = "Frog (Taur)" icon_state = "frog_s" diff --git a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm index f182b5792e..2a574510b0 100644 --- a/code/modules/mob/new_player/sprite_accessories_taur_vr.dm +++ b/code/modules/mob/new_player/sprite_accessories_taur_vr.dm @@ -69,6 +69,15 @@ extra_overlay2 = "wolf_markings_2" //icon_sprite_tag = "wolf2c" +/datum/sprite_accessory/tail/taur/wolf/wolf_2c_wag + name = "Wolf 3-color (Taur, vwag)" + icon_state = "wolf_s" + extra_overlay = "wolf_markings" + extra_overlay2 = "wolf_markings_2" + ani_state = "fatwolf_s" + extra_overlay_w = "fatwolf_markings" + extra_overlay2_w = "wolf_markings_2" + /datum/sprite_accessory/tail/taur/wolf/fatwolf_2c name = "Fat Wolf 3-color (Taur)" icon_state = "fatwolf_s" @@ -411,6 +420,12 @@ suit_sprites = 'icons/mob/taursuits_drake_vr.dmi' icon_sprite_tag = "drake" +/datum/sprite_accessory/tail/taur/ch/fatdrake + name = "Drake (Fat Taur dual-color)" + icon_state = "fatdrake_s" + extra_overlay = "fatdrake_markings" + icon_sprite_tag = "drake" + /datum/sprite_accessory/tail/taur/otie name = "Otie (Taur)" icon_state = "otie_s" diff --git a/code/modules/mob/typing_indicator.dm b/code/modules/mob/typing_indicator.dm index 35fe2d8bbb..7ab6de6fe5 100644 --- a/code/modules/mob/typing_indicator.dm +++ b/code/modules/mob/typing_indicator.dm @@ -40,7 +40,11 @@ set hidden = 1 set_typing_indicator(TRUE) - var/message = tgui_input_text(usr, "Type your message:", "Say") + var/message + if(usr.client.prefs.tgui_input_mode) + message = tgui_input_text(usr, "Type your message:", "Say") + else + message = input(usr, "Type your message:", "Say") as text set_typing_indicator(FALSE) if(message) @@ -51,7 +55,11 @@ set hidden = 1 set_typing_indicator(TRUE) - var/message = tgui_input_message(usr, "Type your message:", "Emote") + var/message + if(usr.client.prefs.tgui_input_mode) + message = tgui_input_message(usr, "Type your message:", "Emote") + else + message = input(usr, "Type your message:", "Emote") as message set_typing_indicator(FALSE) if(message) @@ -62,7 +70,12 @@ set name = ".Whisper" set hidden = 1 - var/message = tgui_input_text(usr, "Type your message:", "Whisper") + var/message + if(usr.client.prefs.tgui_input_mode) + message = tgui_input_text(usr, "Type your message:", "Whisper") + else + message = input(usr, "Type your message:", "Whisper") as text + if(message) whisper(message) @@ -70,6 +83,11 @@ set name = ".Subtle" set hidden = 1 - var/message = tgui_input_message(usr, "Type your message:", "Subtle") + var/message + if(usr.client.prefs.tgui_input_mode) + message = tgui_input_message(usr, "Type your message:", "Subtle") + else + message = input(usr, "Type your message:", "Subtle") as message + if(message) me_verb_subtle(message) diff --git a/code/modules/nifsoft/software/14_commlink.dm b/code/modules/nifsoft/software/14_commlink.dm index 93e1af799c..75a04fc589 100644 --- a/code/modules/nifsoft/software/14_commlink.dm +++ b/code/modules/nifsoft/software/14_commlink.dm @@ -128,4 +128,4 @@ return if(ringer && nif.human) - nif.notify("Commlink message from [who]: \"[text]\" (Open)") + nif.notify("Commlink message from [who]: \"[text]\" (Open) (Reply)") diff --git a/code/modules/tgui/modules/rcon.dm b/code/modules/tgui/modules/rcon.dm index 01ba355248..0a29153d76 100644 --- a/code/modules/tgui/modules/rcon.dm +++ b/code/modules/tgui/modules/rcon.dm @@ -1,3 +1,5 @@ +#define SMES_PER_PAGE 4 + /datum/tgui_module/rcon name = "Power RCON" tgui_id = "RCON" @@ -5,17 +7,41 @@ var/list/known_SMESs = null var/list/known_breakers = null + var/filtered_smeslist = list() + + var/current_page = 1 + var/number_pages = 0 + +/datum/tgui_module/rcon/proc/filter_smeslist(var/page) + number_pages = known_SMESs.len / SMES_PER_PAGE + + if(number_pages != round(number_pages)) + number_pages = round(number_pages) + 1 + var/page_index = page - 1 + + var/lower_bound = page_index * SMES_PER_PAGE + 1 + var/upper_bound = (page_index + 1) * SMES_PER_PAGE + upper_bound = min(upper_bound, known_SMESs.len) + filtered_smeslist = list() + + for(var/index = lower_bound, index <= upper_bound, index++) + filtered_smeslist += known_SMESs[index] + /datum/tgui_module/rcon/tgui_data(mob/user) FindDevices() // Update our devices list var/list/data = ..() + filter_smeslist(current_page) + // SMES DATA (simplified view) var/list/smeslist[0] - for(var/obj/machinery/power/smes/buildable/SMES in known_SMESs) + for(var/obj/machinery/power/smes/buildable/SMES in filtered_smeslist) var/list/smes_data = SMES.tgui_data() smes_data["RCON_tag"] = SMES.RCon_tag smeslist.Add(list(smes_data)) + data["pages"] = number_pages + 1 + data["current_page"] = current_page data["smes_info"] = sortByKey(smeslist, "RCON_tag") // BREAKER DATA (simplified view) @@ -34,6 +60,10 @@ return TRUE switch(action) + if("set_smes_page") + var/page = params["index"] + current_page = page + . = TRUE if("smes_in_toggle") var/obj/machinery/power/smes/buildable/SMES = GetSMESByTag(params["smes"]) if(SMES) diff --git a/code/modules/tgui/tgui.dm b/code/modules/tgui/tgui.dm index 5f95ab7db2..c34ccf63d7 100644 --- a/code/modules/tgui/tgui.dm +++ b/code/modules/tgui/tgui.dm @@ -92,10 +92,9 @@ if(!window.is_ready()) window.initialize( fancy = user.client.prefs.tgui_fancy, - inline_assets = list( - get_asset_datum(/datum/asset/simple/tgui_common), - get_asset_datum(/datum/asset/simple/tgui) - )) + assets = list( + get_asset_datum(/datum/asset/simple/tgui), + )) else window.send_message("ping") window.send_asset(get_asset_datum(/datum/asset/simple/fontawesome)) diff --git a/code/modules/tgui/tgui_window.dm b/code/modules/tgui/tgui_window.dm index cc8a63ef27..2e1b64bac1 100644 --- a/code/modules/tgui/tgui_window.dm +++ b/code/modules/tgui/tgui_window.dm @@ -18,8 +18,11 @@ var/message_queue var/sent_assets = list() // Vars passed to initialize proc (and saved for later) - var/inline_assets - var/fancy + var/initial_fancy + var/initial_assets + var/initial_inline_html + var/initial_inline_js + var/initial_inline_css /** * public @@ -45,21 +48,26 @@ * state. You can begin sending messages right after initializing. Messages * will be put into the queue until the window finishes loading. * - * optional inline_assets list List of assets to inline into the html. + * optional assets list List of assets to inline into the html. * optional inline_html string Custom HTML to inject. * optional fancy bool If TRUE, will hide the window titlebar. */ /datum/tgui_window/proc/initialize( - inline_assets = list(), + fancy = FALSE, + assets = list(), inline_html = "", - fancy = FALSE) + inline_js = "", + inline_css = "") #ifdef TGUI_DEBUGGING log_tgui(client, "[id]/initiailize ([src])") #endif if(!client) return - src.inline_assets = inline_assets - src.fancy = fancy + src.initial_fancy = fancy + src.initial_assets = assets + src.initial_inline_html = inline_html + src.initial_inline_js = inline_js + src.initial_inline_css = inline_css status = TGUI_WINDOW_LOADING fatally_errored = FALSE // Build window options @@ -72,9 +80,9 @@ // Generate page html var/html = SStgui.basehtml html = replacetextEx(html, "\[tgui:windowId]", id) - // Inject inline assets + // Inject assets var/inline_assets_str = "" - for(var/datum/asset/asset in inline_assets) + for(var/datum/asset/asset in assets) var/mappings = asset.get_url_mappings() for(var/name in mappings) var/url = mappings[name] @@ -87,8 +95,17 @@ if(length(inline_assets_str)) inline_assets_str = "\n" html = replacetextEx(html, "\n", inline_assets_str) - // Inject custom HTML - html = replacetextEx(html, "\n", inline_html) + // Inject inline HTML + if (inline_html) + html = replacetextEx(html, "", inline_html) + // Inject inline JS + if (inline_js) + inline_js = "" + html = replacetextEx(html, "", inline_js) + // Inject inline CSS + if (inline_css) + inline_css = "" + html = replacetextEx(html, "", inline_css) // Open the window client << browse(html, "window=[id];[options]") // Detect whether the control is a browser @@ -281,6 +298,17 @@ : "[id].browser:update") message_queue = null +/** + * public + * + * Replaces the inline HTML content. + * + * required inline_html string HTML to inject + */ +/datum/tgui_window/proc/replace_html(inline_html = "") + client << output(url_encode(inline_html), is_browser \ + ? "[id]:replaceHtml" \ + : "[id].browser:replaceHtml") /** * private @@ -325,7 +353,12 @@ client << link(href_list["url"]) if("cacheReloaded") // Reinitialize - initialize(inline_assets = inline_assets, fancy = fancy) + initialize( + fancy = initial_fancy, + assets = initial_assets, + inline_html = initial_inline_html, + inline_js = initial_inline_js, + inline_css = initial_inline_css) // Resend the assets for(var/asset in sent_assets) send_asset(asset) \ No newline at end of file diff --git a/code/modules/tooltip/tooltip.html b/code/modules/tooltip/tooltip.html index 67fb8a77f1..77ac63c1a9 100644 --- a/code/modules/tooltip/tooltip.html +++ b/code/modules/tooltip/tooltip.html @@ -217,8 +217,13 @@ $wrap.width($wrap.width() + 2); //Dumb hack to fix a bizarre sizing bug - var docWidth = $wrap.outerWidth(), - docHeight = $wrap.outerHeight(); + var pixelRatio = 1; + if (window.devicePixelRatio) { + pixelRatio = window.devicePixelRatio; + } + + var docWidth = Math.floor($wrap.outerWidth() * pixelRatio), + docHeight = Math.floor($wrap.outerHeight() * pixelRatio); if (posY + docHeight > map.size.y) { //Is the bottom edge below the window? Snap it up if so posY = (posY - docHeight) - realIconSize - tooltip.padding; diff --git a/code/modules/vchat/vchat_client.dm b/code/modules/vchat/vchat_client.dm index a918c94cde..7520b41abb 100644 --- a/code/modules/vchat/vchat_client.dm +++ b/code/modules/vchat/vchat_client.dm @@ -161,6 +161,7 @@ GLOBAL_DATUM_INIT(iconCache, /savefile, new("data/iconCache.sav")) //Cache of ic update_vis() spawn() + if(owner.is_preference_enabled(/datum/client_preference/vchat_enable)) tgui_alert_async(owner,"VChat didn't load after some time. Switching to use oldchat as a fallback. Try using 'Reload VChat' verb in OOC verbs, or reconnecting to try again.") //Provide the JS with who we are diff --git a/code/modules/vore/eating/belly_obj_vr.dm b/code/modules/vore/eating/belly_obj_vr.dm index 554dac57f1..6ebda2446b 100644 --- a/code/modules/vore/eating/belly_obj_vr.dm +++ b/code/modules/vore/eating/belly_obj_vr.dm @@ -30,6 +30,7 @@ var/escapechance = 0 // % Chance of prey beginning to escape if prey struggles. var/transferchance = 0 // % Chance of prey being trasnsfered, goes from 0-100% var/transferchance_secondary = 0 // % Chance of prey being transfered to transferchance_secondary, also goes 0-100% + var/save_digest_mode = TRUE // Whether this belly's digest mode persists across rounds var/can_taste = FALSE // If this belly prints the flavor of prey when it eats someone. var/bulge_size = 0.25 // The minimum size the prey has to be in order to show up on examine. var/display_absorbed_examine = FALSE // Do we display absorption examine messages for this belly at all? @@ -157,61 +158,67 @@ //For serialization, keep this updated, required for bellies to save correctly. /obj/belly/vars_to_save() - return ..() + list( - "name", - "desc", - "absorbed_desc", - "vore_sound", - "vore_verb", - "human_prey_swallow_time", - "nonhuman_prey_swallow_time", - "emote_time", - "nutrition_percent", - "digest_brute", - "digest_burn", - "digest_oxy", - "immutable", - "can_taste", - "escapable", - "escapetime", - "digestchance", - "absorbchance", - "escapechance", - "transferchance", - "transferchance_secondary", - "transferlocation", - "transferlocation_secondary", - "bulge_size", - "display_absorbed_examine", - "shrink_grow_size", - "struggle_messages_outside", - "struggle_messages_inside", - "absorbed_struggle_messages_outside", - "absorbed_struggle_messages_inside", - "digest_messages_owner", - "digest_messages_prey", - "absorb_messages_owner", - "absorb_messages_prey", - "unabsorb_messages_owner", - "unabsorb_messages_prey", - "examine_messages", - "examine_messages_absorbed", - "emote_lists", - "emote_time", - "emote_active", - "mode_flags", - "item_digest_mode", - "contaminates", - "contamination_flavor", - "contamination_color", - "release_sound", - "fancy_vore", - "is_wet", - "wet_loop", - "belly_fullscreen", - "disable_hud", - "egg_type" - ) + var/list/saving = list( + "name", + "desc", + "absorbed_desc", + "vore_sound", + "vore_verb", + "human_prey_swallow_time", + "nonhuman_prey_swallow_time", + "emote_time", + "nutrition_percent", + "digest_brute", + "digest_burn", + "digest_oxy", + "immutable", + "can_taste", + "escapable", + "escapetime", + "digestchance", + "absorbchance", + "escapechance", + "transferchance", + "transferchance_secondary", + "transferlocation", + "transferlocation_secondary", + "bulge_size", + "display_absorbed_examine", + "shrink_grow_size", + "struggle_messages_outside", + "struggle_messages_inside", + "absorbed_struggle_messages_outside", + "absorbed_struggle_messages_inside", + "digest_messages_owner", + "digest_messages_prey", + "absorb_messages_owner", + "absorb_messages_prey", + "unabsorb_messages_owner", + "unabsorb_messages_prey", + "examine_messages", + "examine_messages_absorbed", + "emote_lists", + "emote_time", + "emote_active", + "mode_flags", + "item_digest_mode", + "contaminates", + "contamination_flavor", + "contamination_color", + "release_sound", + "fancy_vore", + "is_wet", + "wet_loop", + "belly_fullscreen", + "disable_hud", + "egg_type", + "save_digest_mode" + ) + + if (save_digest_mode == 1) + return ..() + saving + list("digest_mode") + + return ..() + saving /obj/belly/Initialize() . = ..() @@ -335,7 +342,7 @@ owner.update_icons() //Print notifications/sound if necessary - if(!silent) + if(!silent && count) owner.visible_message("[owner] expels everything from their [lowertext(name)]!") var/soundfile if(!fancy_vore) @@ -371,6 +378,9 @@ slip.slip_protect = world.time + 25 // This is to prevent slipping back into your pred if they stand on soap or something. //Place them into our drop_location M.forceMove(drop_location()) + if(ismob(M)) + var/mob/ourmob = M + ourmob.reset_view(null) items_preserved -= M //Special treatment for absorbed prey @@ -409,6 +419,10 @@ soundfile = fancy_release_sounds[release_sound] if(soundfile) playsound(src, soundfile, vol = 100, vary = 1, falloff = VORE_SOUND_FALLOFF, preference = /datum/client_preference/eating_noises, volume_channel = VOLUME_CHANNEL_VORE) + //Should fix your view not following you out of mobs sometimes! + if(ismob(M)) + var/mob/ourmob = M + ourmob.reset_view(null) return 1 @@ -422,6 +436,9 @@ prey.buckled.unbuckle_mob() prey.forceMove(src) + if(ismob(prey)) + var/mob/ourmob = prey + ourmob.reset_view(owner) owner.updateVRPanel() if(isanimal(owner)) owner.update_icon() @@ -716,6 +733,10 @@ if(M.loc != src) M.forceMove(src) + if(ismob(M)) + var/mob/ourmob = M + ourmob.reset_view(owner) + //Seek out absorbed prey of the prey, absorb them too. //This in particular will recurse oddly because if there is absorbed prey of prey of prey... //it will just move them up one belly. This should never happen though since... when they were @@ -1026,6 +1047,9 @@ if(!(content in src) || !istype(target)) return content.forceMove(target) + if(ismob(content)) + var/mob/ourmob = content + ourmob.reset_view(owner) if(isitem(content)) var/obj/item/I = content if(istype(I,/obj/item/weapon/card/id)) @@ -1101,6 +1125,7 @@ dupe.egg_type = egg_type dupe.emote_time = emote_time dupe.emote_active = emote_active + dupe.save_digest_mode = save_digest_mode //// Object-holding variables //struggle_messages_outside - strings diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index fdaca89429..d0acc9614f 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -234,6 +234,7 @@ P.vore_taste = src.vore_taste P.vore_smell = src.vore_smell P.permit_healbelly = src.permit_healbelly + P.noisy = src.noisy P.show_vore_fx = src.show_vore_fx P.can_be_drop_prey = src.can_be_drop_prey P.can_be_drop_pred = src.can_be_drop_pred @@ -273,6 +274,7 @@ vore_taste = P.vore_taste vore_smell = P.vore_smell permit_healbelly = P.permit_healbelly + noisy = P.noisy show_vore_fx = P.show_vore_fx can_be_drop_prey = P.can_be_drop_prey can_be_drop_pred = P.can_be_drop_pred @@ -678,6 +680,13 @@ to_chat(src, "You are not allowed to eat this.") return + if(istype(I, /obj/item/device/paicard)) + var/obj/item/device/paicard/palcard = I + var/mob/living/silicon/pai/pocketpal = palcard.pai + if(!pocketpal.devourable) + to_chat(src, "\The [pocketpal] doesn't allow you to eat it.") + return + if(is_type_in_list(I,edible_trash) | adminbus_trash) if(I.hidden_uplink) to_chat(src, "You really should not be eating this.") @@ -1053,9 +1062,13 @@ if(!screen_icon) screen_icon = new() RegisterSignal(screen_icon, COMSIG_CLICK, .proc/vore_panel_click) - screen_icon.icon = HUD.ui_style - screen_icon.color = HUD.ui_color - screen_icon.alpha = HUD.ui_alpha + if(ispAI(user)) + screen_icon.icon = 'icons/mob/pai_hud.dmi' + screen_icon.screen_loc = ui_acti + else + screen_icon.icon = HUD.ui_style + screen_icon.color = HUD.ui_color + screen_icon.alpha = HUD.ui_alpha LAZYADD(HUD.other_important, screen_icon) user.client?.screen += screen_icon diff --git a/code/modules/vore/eating/vore_vr.dm b/code/modules/vore/eating/vore_vr.dm index bbb26b7e3c..a1668e0482 100644 --- a/code/modules/vore/eating/vore_vr.dm +++ b/code/modules/vore/eating/vore_vr.dm @@ -54,6 +54,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE var/digest_leave_remains = FALSE var/allowmobvore = TRUE var/permit_healbelly = TRUE + var/noisy = FALSE // These are 'modifier' prefs, do nothing on their own but pair with drop_prey/drop_pred settings. var/drop_vore = TRUE @@ -140,6 +141,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE vore_taste = json_from_file["vore_taste"] vore_smell = json_from_file["vore_smell"] permit_healbelly = json_from_file["permit_healbelly"] + noisy = json_from_file["noisy"] show_vore_fx = json_from_file["show_vore_fx"] can_be_drop_prey = json_from_file["can_be_drop_prey"] can_be_drop_pred = json_from_file["can_be_drop_pred"] @@ -169,6 +171,8 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE allowmobvore = TRUE if(isnull(permit_healbelly)) permit_healbelly = TRUE + if (isnull(noisy)) + noisy = FALSE if(isnull(show_vore_fx)) show_vore_fx = TRUE if(isnull(can_be_drop_prey)) @@ -211,6 +215,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE "vore_taste" = vore_taste, "vore_smell" = vore_smell, "permit_healbelly" = permit_healbelly, + "noisy" = noisy, "show_vore_fx" = show_vore_fx, "can_be_drop_prey" = can_be_drop_prey, "can_be_drop_pred" = can_be_drop_pred, diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index a84bbd4526..471d2fca03 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -176,6 +176,7 @@ "digest_burn" = selected.digest_burn, "digest_oxy" = selected.digest_oxy, "bulge_size" = selected.bulge_size, + "save_digest_mode" = selected.save_digest_mode, "display_absorbed_examine" = selected.display_absorbed_examine, "shrink_grow_size" = selected.shrink_grow_size, "emote_time" = selected.emote_time, @@ -1074,6 +1075,9 @@ if("b_disable_hud") host.vore_selected.disable_hud = !host.vore_selected.disable_hud . = TRUE + if("b_save_digest_mode") + host.vore_selected.save_digest_mode = !host.vore_selected.save_digest_mode + . = TRUE if("b_del") var/alert = tgui_alert(usr, "Are you sure you want to delete your [lowertext(host.vore_selected.name)]?","Confirmation",list("Cancel","Delete")) if(!(alert == "Delete")) diff --git a/code/modules/vore/fluffstuff/custom_items_vr.dm b/code/modules/vore/fluffstuff/custom_items_vr.dm index 09d2902a12..bb8666908e 100644 --- a/code/modules/vore/fluffstuff/custom_items_vr.dm +++ b/code/modules/vore/fluffstuff/custom_items_vr.dm @@ -638,34 +638,6 @@ desc = "An elaborately made custom walking stick with a dark wooding core, a crimson red gemstone on its head and a steel cover around the bottom. you'd probably hear someone using this down the hall." icon = 'icons/vore/custom_items_vr.dmi' -//Stobarico - Alexis Bloise -/obj/item/weapon/cane/wand - name = "Ancient wand" - desc = "A really old looking wand with floating parts and cyan crystals, wich seem to radiate a cyan glow. The wand has a golden plaque on the side that would say Corncobble, but it is covered by a sticker saying Bloise." - icon = 'icons/vore/custom_items_vr.dmi' - icon_state = "alexiswand" - item_icons = list (slot_r_hand_str = 'icons/vore/custom_items_vr.dmi', slot_l_hand_str = 'icons/vore/custom_items_vr.dmi') - item_state_slots = list(slot_r_hand_str = "alexiswandmob_r", slot_l_hand_str = "alexiswandmob_l") - force = 1.0 - throwforce = 2.0 - w_class = ITEMSIZE_SMALL - matter = list(MAT_STEEL = 50) - attack_verb = list("sparkled", "whacked", "twinkled", "radiated", "dazzled", "zapped") - hitsound = 'sound/weapons/sparkle.ogg' - var/last_use = 0 - var/cooldown = 30 - -/obj/item/weapon/cane/wand/attack_self(mob/user) - if(last_use + cooldown >= world.time) - return - playsound(src, 'sound/weapons/sparkle.ogg', 50, 1) - user.visible_message(" [user] swings their wand.") - var/datum/effect/effect/system/spark_spread/s = new - s.set_up(3, 1, src) - s.start() - last_use = world.time - qdel () - /obj/item/device/fluff/id_kit_ivy name = "Holo-ID reprinter" desc = "Stick your ID in one end and it'll print a new ID out the other!" @@ -1505,3 +1477,12 @@ nano_printer = new/obj/item/weapon/computer_hardware/nano_printer(src) battery_module = new/obj/item/weapon/computer_hardware/battery_module(src) battery_module.charge_to_full() + + +//Stobarico - Kyu Comet +/obj/item/instrument/piano_synth/fluff/kyutar + name = "Kyu's Custom Instrument" + desc = "A pastel pink guitar-like instrument with a body resembling a smug cat face. It seems to have a few different parts from a regular stringed instrument, including the lack of any strings, and the hand looking like a small screen, which connects to a small array of projectors." + icon = 'icons/vore/custom_items_vr.dmi' + item_icons = list(slot_l_hand_str = 'icons/vore/custom_items_left_hand_vr.dmi', slot_r_hand_str = 'icons/vore/custom_items_right_hand_vr.dmi') + icon_state = "kyuholotar" diff --git a/code/modules/vore/resizing/resize_vr.dm b/code/modules/vore/resizing/resize_vr.dm index b82ecb07e7..dd131ffca3 100644 --- a/code/modules/vore/resizing/resize_vr.dm +++ b/code/modules/vore/resizing/resize_vr.dm @@ -113,8 +113,6 @@ /mob/living/carbon/human/resize(var/new_size, var/animate = TRUE, var/uncapped = FALSE, var/ignore_prefs = FALSE, var/aura_animation = TRUE) if(!resizable && !ignore_prefs) return 1 - if(species) - vis_height = species.icon_height . = ..() if(LAZYLEN(hud_list) && has_huds) var/new_y_offset = vis_height * (size_multiplier - 1) @@ -147,8 +145,8 @@ resize(new_size/100, uncapped = has_large_resize_bounds(), ignore_prefs = TRUE) // I'm not entirely convinced that `src ? ADMIN_JMP(src) : "null"` here does anything // but just in case it does, I'm leaving the null-src checking - message_admins("[key_name(src)] used the resize command in-game to be [new_size]% size. [src ? ADMIN_JMP(src) : "null"]") - + log_admin("[key_name(src)] used the resize command in-game to be [new_size]% size. [src ? ADMIN_JMP(src) : "null"]") + /* //Add the set_size() proc to usable verbs. By commenting this out, we can leave the proc and hand it to species that need it. /hook/living_new/proc/resize_setup(mob/living/H) diff --git a/icons/_nanomaps/gb1.png b/icons/_nanomaps/gb1.png new file mode 100644 index 0000000000..8c3ac8f2db Binary files /dev/null and b/icons/_nanomaps/gb1.png differ diff --git a/icons/_nanomaps/gb2.png b/icons/_nanomaps/gb2.png new file mode 100644 index 0000000000..63277ea186 Binary files /dev/null and b/icons/_nanomaps/gb2.png differ diff --git a/icons/_nanomaps/gb3.png b/icons/_nanomaps/gb3.png new file mode 100644 index 0000000000..2ad403f15e Binary files /dev/null and b/icons/_nanomaps/gb3.png differ diff --git a/icons/_nanomaps/gbeast.png b/icons/_nanomaps/gbeast.png new file mode 100644 index 0000000000..2f6f110f42 Binary files /dev/null and b/icons/_nanomaps/gbeast.png differ diff --git a/icons/_nanomaps/gbmining.png b/icons/_nanomaps/gbmining.png new file mode 100644 index 0000000000..769d080980 Binary files /dev/null and b/icons/_nanomaps/gbmining.png differ diff --git a/icons/_nanomaps/gbnorth.png b/icons/_nanomaps/gbnorth.png new file mode 100644 index 0000000000..5b4f03662e Binary files /dev/null and b/icons/_nanomaps/gbnorth.png differ diff --git a/icons/_nanomaps/gbsouth.png b/icons/_nanomaps/gbsouth.png new file mode 100644 index 0000000000..1a3a1b22e3 Binary files /dev/null and b/icons/_nanomaps/gbsouth.png differ diff --git a/icons/_nanomaps/gbwest.png b/icons/_nanomaps/gbwest.png new file mode 100644 index 0000000000..d19c3019a8 Binary files /dev/null and b/icons/_nanomaps/gbwest.png differ diff --git a/icons/_nanomaps/tether_nanomap_z1.png b/icons/_nanomaps/tether_nanomap_z1.png index a2ef4f92e0..2cf0c71d84 100644 Binary files a/icons/_nanomaps/tether_nanomap_z1.png and b/icons/_nanomaps/tether_nanomap_z1.png differ diff --git a/icons/_nanomaps/tether_nanomap_z2.png b/icons/_nanomaps/tether_nanomap_z2.png index 5f1eacdb19..53492f77ee 100644 Binary files a/icons/_nanomaps/tether_nanomap_z2.png and b/icons/_nanomaps/tether_nanomap_z2.png differ diff --git a/icons/_nanomaps/tether_nanomap_z3.png b/icons/_nanomaps/tether_nanomap_z3.png index b6d0eed70d..53888984ca 100644 Binary files a/icons/_nanomaps/tether_nanomap_z3.png and b/icons/_nanomaps/tether_nanomap_z3.png differ diff --git a/icons/_nanomaps/tether_nanomap_z4.png b/icons/_nanomaps/tether_nanomap_z4.png index c2623219ee..510d22b3d6 100644 Binary files a/icons/_nanomaps/tether_nanomap_z4.png and b/icons/_nanomaps/tether_nanomap_z4.png differ diff --git a/icons/inventory/head/mob_vox.dmi b/icons/inventory/head/mob_vox.dmi index 346ea9b7d1..f1570089c8 100644 Binary files a/icons/inventory/head/mob_vox.dmi and b/icons/inventory/head/mob_vox.dmi differ diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi index def737dcc3..2cb35cf667 100644 Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi new file mode 100644 index 0000000000..3e8d60a579 Binary files /dev/null and b/icons/mob/head.dmi differ diff --git a/icons/mob/pai_hud.dmi b/icons/mob/pai_hud.dmi new file mode 100644 index 0000000000..c1be3ab314 Binary files /dev/null and b/icons/mob/pai_hud.dmi differ diff --git a/icons/mob/pai_vr.dmi b/icons/mob/pai_vr.dmi index 0b8b985ff7..859cfc7116 100644 Binary files a/icons/mob/pai_vr.dmi and b/icons/mob/pai_vr.dmi differ diff --git a/icons/mob/pai_vr64x64.dmi b/icons/mob/pai_vr64x64.dmi index f9c99e51eb..d19f5137df 100644 Binary files a/icons/mob/pai_vr64x64.dmi and b/icons/mob/pai_vr64x64.dmi differ diff --git a/icons/mob/species/teshari/head.dmi b/icons/mob/species/teshari/head.dmi index 0bb0637c4e..6cf6b2ac7d 100644 Binary files a/icons/mob/species/teshari/head.dmi and b/icons/mob/species/teshari/head.dmi differ diff --git a/icons/mob/taursuits_horse.dmi b/icons/mob/taursuits_horse.dmi index 192f282796..4986bed14f 100644 Binary files a/icons/mob/taursuits_horse.dmi and b/icons/mob/taursuits_horse.dmi differ diff --git a/icons/mob/taursuits_horse_vr.dmi b/icons/mob/taursuits_horse_vr.dmi index 192f282796..4986bed14f 100644 Binary files a/icons/mob/taursuits_horse_vr.dmi and b/icons/mob/taursuits_horse_vr.dmi differ diff --git a/icons/mob/vore/ears_vr.dmi b/icons/mob/vore/ears_vr.dmi index 032ba8e4fa..4bde02b8bf 100644 Binary files a/icons/mob/vore/ears_vr.dmi and b/icons/mob/vore/ears_vr.dmi differ diff --git a/icons/mob/vore/taurs_vr.dmi b/icons/mob/vore/taurs_vr.dmi index d10b18cb04..20ddaee2a4 100644 Binary files a/icons/mob/vore/taurs_vr.dmi and b/icons/mob/vore/taurs_vr.dmi differ diff --git a/icons/obj/cooking_machines.dmi b/icons/obj/cooking_machines.dmi index 4009cb6224..b8c3ea6f5d 100644 Binary files a/icons/obj/cooking_machines.dmi and b/icons/obj/cooking_machines.dmi differ diff --git a/icons/obj/food.dmi b/icons/obj/food.dmi index 915c170551..f1637aca4b 100644 Binary files a/icons/obj/food.dmi and b/icons/obj/food.dmi differ diff --git a/icons/obj/paicard.dmi b/icons/obj/paicard.dmi new file mode 100644 index 0000000000..c69ead6efd Binary files /dev/null and b/icons/obj/paicard.dmi differ diff --git a/icons/obj/pda.dmi b/icons/obj/pda.dmi index 9aa94d1fe9..f98d60b5bd 100644 Binary files a/icons/obj/pda.dmi and b/icons/obj/pda.dmi differ diff --git a/icons/obj/playing_cards.dmi b/icons/obj/playing_cards.dmi index 071d53b04b..0f2e51923a 100644 Binary files a/icons/obj/playing_cards.dmi and b/icons/obj/playing_cards.dmi differ diff --git a/icons/vore/custom_items_left_hand_vr.dmi b/icons/vore/custom_items_left_hand_vr.dmi index 1ee6c0a336..a9abdac0c3 100644 Binary files a/icons/vore/custom_items_left_hand_vr.dmi and b/icons/vore/custom_items_left_hand_vr.dmi differ diff --git a/icons/vore/custom_items_right_hand_vr.dmi b/icons/vore/custom_items_right_hand_vr.dmi index a6e419e2ed..130ac35689 100644 Binary files a/icons/vore/custom_items_right_hand_vr.dmi and b/icons/vore/custom_items_right_hand_vr.dmi differ diff --git a/icons/vore/custom_items_vr.dmi b/icons/vore/custom_items_vr.dmi index c83155640c..1f3fb6cea7 100644 Binary files a/icons/vore/custom_items_vr.dmi and b/icons/vore/custom_items_vr.dmi differ diff --git a/maps/atoll/atoll.dmm b/maps/atoll/atoll.dmm new file mode 100644 index 0000000000..55567d90fd --- /dev/null +++ b/maps/atoll/atoll.dmm @@ -0,0 +1,397 @@ +"aa" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 1},/obj/machinery/gateway{dir = 9},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ab" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 4},/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 1},/obj/machinery/gateway{dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ac" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 8},/obj/machinery/gateway{dir = 5},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ad" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ae" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"af" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 4},/obj/machinery/gateway{dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ag" = (/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"ah" = (/obj/effect/floor_decal/atoll,/turf/simulated/floor/atoll,/area/gateway/atoll) +"ai" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"aj" = (/obj/structure/flora/moss,/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"ak" = (/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"al" = (/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"am" = (/turf/simulated/floor/atoll,/area/gateway/atoll) +"an" = (/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"ao" = (/obj/structure/railing/overhang/waterless,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"ap" = (/obj/effect/decal/shadow,/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aq" = (/obj/structure/railing/overhang,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"ar" = (/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"as" = (/obj/structure/bed/chair/sofa/bench/marble,/turf/simulated/floor/atoll,/area/gateway/atoll) +"at" = (/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"au" = (/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"av" = (/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aw" = (/obj/structure/lightpost/atoll,/turf/simulated/floor/atoll,/area/gateway/atoll) +"ax" = (/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"ay" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"az" = (/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 4},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aA" = (/obj/effect/decal/shadow/silhouette/pillar{icon_state = "silhouette_p"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aB" = (/obj/machinery/gateway/centerstation,/turf/simulated/floor/atoll,/area/gateway/atoll) +"aC" = (/obj/effect/blocker,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aD" = (/obj/effect/blocker,/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aE" = (/obj/effect/blocker,/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"aF" = (/obj/effect/blocker,/turf/simulated/floor/atoll,/area/gateway/atoll) +"aG" = (/obj/effect/blocker,/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aH" = (/obj/effect/blocker,/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 8},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aI" = (/obj/effect/blocker,/obj/structure/railing/overhang/waterless,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aJ" = (/obj/effect/blocker,/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 4},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aK" = (/obj/effect/blocker,/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aL" = (/obj/effect/blocker,/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aM" = (/obj/effect/blocker,/obj/structure/flora/moss{icon_state = "moss"; dir = 8},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aN" = (/obj/effect/blocker,/obj/structure/flora/moss,/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aO" = (/obj/effect/decal/shadow/silhouette,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aP" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 4},/obj/structure/bed/chair/sofa/bench/marble{dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"aQ" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 9},/obj/effect/floor_decal/atoll/bronze,/turf/simulated/floor/atoll,/area/gateway/atoll) +"aR" = (/obj/effect/floor_decal/atoll/border,/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"aS" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 5},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"aT" = (/obj/effect/floor_decal/atoll,/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"aU" = (/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 8},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aV" = (/obj/structure/flora/lily2,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aW" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"aX" = (/obj/effect/decal/shadow/silhouette{icon_state = "silhouette"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"aY" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 9},/turf/simulated/floor/atoll,/area/gateway/atoll) +"aZ" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ba" = (/obj/structure/flora/lily2,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bb" = (/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bc" = (/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bd" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"be" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 5},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bf" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bg" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bh" = (/obj/structure/railing/overhang,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bi" = (/obj/structure/flora/lily3,/obj/structure/railing/overhang,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bj" = (/obj/structure/railing/overhang,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bk" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bl" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bm" = (/obj/structure/flora/lily3,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bn" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 10},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bo" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bp" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bq" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/border/invert,/turf/simulated/floor/atoll,/area/gateway/atoll) +"br" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 6},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bs" = (/obj/structure/flora/lily1,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bt" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 8},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"bu" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 1},/obj/structure/bed/chair/sofa/bench/marble{dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bv" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 10},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"bw" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bx" = (/obj/effect/floor_decal/atoll/border/invert,/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"by" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border/invert,/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bz" = (/obj/effect/floor_decal/atoll{icon_state = "mural"; dir = 8},/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bA" = (/obj/effect/decal/shadow,/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"bB" = (/obj/effect/floor_decal/atoll/stairs,/obj/effect/decal/shadow,/turf/simulated/floor/atoll,/area/gateway/atoll) +"bC" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 4},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"bD" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bE" = (/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 1},/obj/structure/flora/moss{icon_state = "moss"; dir = 4},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"bF" = (/obj/effect/floor_decal/atoll/stairs,/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bG" = (/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 1},/obj/structure/flora/moss{icon_state = "moss"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"bH" = (/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 1},/obj/structure/flora/moss{icon_state = "moss"; dir = 8},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"bI" = (/obj/effect/floor_decal/atoll/stairs,/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bJ" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 9},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bK" = (/obj/structure/bed/chair/sofa/bench/marble,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 5},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bL" = (/obj/effect/decal/shadow/silhouette{icon_state = "silhouette"; dir = 1},/obj/structure/railing/overhang,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bM" = (/obj/structure/flora/lily2,/obj/structure/railing/overhang,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bN" = (/obj/effect/floor_decal/atoll/stairs,/obj/effect/decal/shadow{icon_state = "wall_shadow"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bO" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bP" = (/obj/structure/flora/moss,/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"bQ" = (/obj/structure/flora/log2,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"bR" = (/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 4},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"bS" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bT" = (/obj/effect/decal/shadow/floor,/turf/simulated/floor/atoll,/area/gateway/atoll) +"bU" = (/obj/effect/decal/shadow/floor,/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"bV" = (/obj/effect/decal/shadow/floor,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 5},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bW" = (/obj/structure/bed/chair/sofa/bench/marble{dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bX" = (/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 8},/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"bY" = (/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 1},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"bZ" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ca" = (/obj/effect/floor_decal/atoll/bronze,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cb" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 6},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cc" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 10},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cd" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/bronze,/turf/simulated/floor/atoll,/area/gateway/atoll) +"ce" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cf" = (/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 6},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cg" = (/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"ch" = (/turf/simulated/floor/beach/sand/outdoors/atoll,/area/gateway/atoll) +"ci" = (/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cj" = (/obj/structure/flora/tree/jungle,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"ck" = (/obj/structure/flora/tree/jungle_small,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cl" = (/obj/structure/flora/bboulder1,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cm" = (/obj/structure/flora/log1,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cn" = (/obj/structure/flora/ausbushes/reedbush,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"co" = (/obj/structure/flora/rocks1,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cp" = (/obj/structure/flora/rocks2,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cq" = (/obj/structure/flora/ausbushes/fernybush,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cr" = (/obj/structure/flora/bboulder2,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cs" = (/obj/structure/flora/log1,/turf/simulated/floor/beach/sand/outdoors/atoll,/area/gateway/atoll) +"ct" = (/obj/structure/bed/chair/sofa/bench/marble,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 9},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cu" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cv" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cw" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 48; teleport_y = 107},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cx" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 33; teleport_y = 107},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/obj/effect/floor_decal/atoll/border,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cy" = (/obj/structure/flora/ausbushes/palebush,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cz" = (/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 8},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cA" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/bronze,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cB" = (/obj/structure/flora/log1,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cC" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cD" = (/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 10},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cE" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/bronze,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cF" = (/obj/structure/flora/smallbould,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cG" = (/obj/structure/flora/ausbushes/pointybush,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cH" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/obj/effect/floor_decal/atoll/border/invert,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cI" = (/obj/effect/decal/shadow,/obj/structure/flora/moss{icon_state = "moss"; dir = 8},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"cJ" = (/obj/effect/decal/shadow,/obj/structure/flora/moss{icon_state = "moss"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"cK" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 62; teleport_y = 103},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cL" = (/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cM" = (/obj/structure/flora/log2,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"cN" = (/obj/structure/flora/ausbushes/fullgrass,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"cO" = (/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 1},/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"cP" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"cQ" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 99; teleport_y = 83},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cR" = (/obj/structure/flora/moss{icon_state = "moss"; dir = 8},/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"cS" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 108; teleport_y = 90},/obj/effect/floor_decal/atoll/border,/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cT" = (/obj/effect/decal/shadow/silhouette/pillar{icon_state = "silhouette_p"; dir = 4},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"cU" = (/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"cV" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"cW" = (/obj/effect/floor_decal/atoll/border/invert,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cX" = (/obj/structure/flora/tree/atoll/huge,/turf/simulated/floor/beach/sand/outdoors/atoll,/area/gateway/atoll) +"cY" = (/obj/effect/floor_decal/atoll/border,/turf/simulated/floor/atoll,/area/gateway/atoll) +"cZ" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"da" = (/obj/structure/flora/lily1,/obj/structure/railing/overhang,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"db" = (/obj/effect/floor_decal/atoll,/obj/machinery/gateway{dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dc" = (/obj/structure/bed/chair/sofa/bench/marble{dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 9},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dd" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"de" = (/obj/structure/bed/chair/sofa/bench/marble,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"df" = (/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 8},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 5},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dg" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dh" = (/obj/machinery/gateway,/turf/simulated/floor/atoll,/area/gateway/atoll) +"di" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 9},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dj" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dk" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 5},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dl" = (/obj/structure/flora/lily2,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dm" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dn" = (/obj/effect/floor_decal/atoll/power,/turf/simulated/floor/atoll{desc = "Scuff and weathered etchings make these floors out to be pretty old. This particular area looks energized, maybe it can supply power to something?"},/area/gateway/atoll/powered) +"do" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dp" = (/obj/effect/floor_decal/atoll/moss/random,/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dq" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dr" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 10},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"ds" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dt" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 6},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"du" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border/invert,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dv" = (/obj/structure/flora/lily3,/obj/structure/railing/overhang,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dw" = (/obj/structure/flora/lily3,/obj/structure/railing/overhang,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dx" = (/obj/effect/decal/shadow/silhouette/pillar,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dy" = (/obj/effect/decal/shadow/silhouette/pillar,/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dz" = (/obj/structure/bed/chair/sofa/bench/marble{dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 10},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dA" = (/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 1},/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dB" = (/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 8},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 6},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dC" = (/obj/effect/decal/shadow/silhouette/pillar{icon_state = "silhouette_p"; dir = 8},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dD" = (/obj/effect/decal/shadow/silhouette/pillar{icon_state = "silhouette_p"; dir = 8},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dE" = (/obj/effect/decal/shadow/silhouette/pillar{icon_state = "silhouette_p"; dir = 4},/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dF" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 4},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dG" = (/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 8},/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dH" = (/obj/structure/flora/ausbushes/genericbush,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"dI" = (/obj/structure/railing/overhang/waterless,/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dJ" = (/obj/structure/railing/overhang/waterless,/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dK" = (/obj/structure/flora/tree/atoll,/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dL" = (/obj/structure/flora/moss,/obj/effect/step_trigger/teleporter/atoll{teleport_x = 97; teleport_y = 46},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dM" = (/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"dN" = (/obj/effect/decal/shadow,/obj/structure/flora/moss{icon_state = "moss"; dir = 4},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"dO" = (/obj/structure/flora/ausbushes/grassybush,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"dP" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 80; teleport_y = 55},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/obj/effect/floor_decal/atoll/border,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dQ" = (/obj/structure/flora/tree/atoll/huge{icon_state = "tree"; dir = 1},/obj/structure/flora/rocks1,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"dR" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 15; teleport_y = 39},/obj/effect/floor_decal/atoll/border,/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dS" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 36; teleport_y = 41},/obj/effect/floor_decal/atoll,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dT" = (/obj/structure/flora/ausbushes/sparsegrass,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"dU" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 44; teleport_y = 33},/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 1},/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dV" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 44; teleport_y = 38},/obj/effect/floor_decal/atoll/border/invert,/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dW" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 8},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"dX" = (/obj/structure/flora/ausbushes/brflowers,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"dY" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 30; teleport_y = 25},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/obj/effect/floor_decal/atoll/border,/turf/simulated/floor/atoll,/area/gateway/atoll) +"dZ" = (/obj/structure/flora/ausbushes/sunnybush,/turf/simulated/floor/outdoors/grass/heavy/atoll,/area/gateway/atoll) +"ea" = (/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 4},/obj/structure/flora/lily1,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"eb" = (/obj/structure/flora/rocks1,/turf/simulated/floor/beach/sand/outdoors/atoll,/area/gateway/atoll) +"ec" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 101; teleport_y = 19},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/obj/effect/floor_decal/atoll/border,/turf/simulated/floor/atoll,/area/gateway/atoll) +"ed" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"ee" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 114; teleport_y = 20},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 4},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ef" = (/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 1},/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 4},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"eg" = (/obj/structure/railing/overhang/waterless{icon_state = "wbronze_railing0"; dir = 4},/turf/simulated/floor/atoll/vertical,/area/gateway/atoll) +"eh" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 33; teleport_y = 91},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 4},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ei" = (/obj/effect/blocker,/obj/effect/decal/shadow/silhouette,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"ej" = (/obj/machinery/gateway{dir = 10},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ek" = (/obj/machinery/gateway{dir = 6},/turf/simulated/floor/atoll,/area/gateway/atoll) +"el" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 9},/turf/simulated/floor/atoll,/area/gateway/atoll) +"em" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/border,/turf/simulated/floor/atoll,/area/gateway/atoll) +"en" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 5},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eo" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ep" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eq" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 10},/turf/simulated/floor/atoll,/area/gateway/atoll) +"er" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"es" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 6},/turf/simulated/floor/atoll,/area/gateway/atoll) +"et" = (/obj/structure/railing/overhang{icon_state = "bronze_railing0"; dir = 4},/obj/structure/railing/overhang,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"eu" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ev" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ew" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 8},/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ex" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ey" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 1},/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"ez" = (/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 8},/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eA" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 9},/obj/structure/bed/chair/sofa/bench/marble,/turf/simulated/floor/atoll,/area/gateway/atoll) +"eB" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eC" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eD" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 9},/obj/effect/floor_decal/atoll/bronze,/obj/structure/bed/chair/sofa/bench/marble{dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eE" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border,/turf/simulated/floor/atoll,/area/gateway/atoll) +"eF" = (/obj/effect/step_trigger/teleporter/atoll{teleport_x = 45; teleport_y = 26},/obj/effect/floor_decal/atoll,/turf/simulated/floor/atoll,/area/gateway/atoll) +"eG" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eH" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 5},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eI" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border/invert{icon_state = "border_invert"; dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eJ" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 5},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eK" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 10},/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eL" = (/obj/effect/floor_decal/atoll/moss/random,/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 6},/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 1},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eM" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 10},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/structure/bed/chair/sofa/bench/marble{dir = 4},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eN" = (/obj/effect/floor_decal/atoll/border{icon_state = "border"; dir = 6},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eO" = (/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 4},/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 1},/obj/structure/bed/chair/sofa/bench/marble,/turf/simulated/floor/atoll,/area/gateway/atoll) +"eP" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/structure/flora/tree/atoll{icon_state = "green"; dir = 8},/turf/simulated/floor/atoll,/area/gateway/atoll) +"eQ" = (/obj/effect/floor_decal/atoll/bronze,/obj/effect/floor_decal/atoll/bronze{icon_state = "bronze"; dir = 8},/obj/structure/bed/chair/sofa/bench/marble{icon_state = "bench"; dir = 1},/obj/effect/floor_decal/atoll/moss/random,/turf/simulated/floor/atoll,/area/gateway/atoll) +"eR" = (/obj/vehicle/boat{icon_state = "boat"; dir = 8},/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"eS" = (/obj/vehicle/boat,/turf/simulated/floor/water/atoll,/area/gateway/atoll) +"eT" = (/obj/vehicle/boat{icon_state = "boat"; dir = 4},/turf/simulated/floor/water/atoll,/area/gateway/atoll) + +(1,1,1) = {" +anananananananananananananananananananananananananananananananananananananananananananananananananananananaoaoananananananananananananananaoanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananan +ananananananananananananananananananananananananananananananananananananananananananananananananananananaradaeatananananananananananananaragatananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananan +ananananananananananananananananananananananananananananananananananananananananananananananananananananarahaiatanananananananananananananajananananananananananananananananananananananananananananananananananananananananananananananananaoaoaoaoaoaoaoaoanananananananananananananan +anananananananananananananananananananananananananananananananananananananananananananananananananananananakakananananananananananananananalanananananananananananananananananananananananananananananananananananananananananananananananaramamamamamamamamatananananananananananananan +ananananananananananananananananananananananananananananananananananananananananananananananaoaoaoananananapapananananananaqanananananananalanananananaoaoananananananananananananananananananananananananananananananananananananananananaramamamamamamamamatananananananananananananan +anananananananananananananananananananananananananananananananananananananananananananananaramasamatanananauauanananananavawaxananananananapananananaradaeatanananananananananananananananananananananananananananananananananananananananaramamamamamamamamatananananananananananananan +anananananananananananananananananananananananananananananananananananananananananananananaramagayatanananazazananananananaAanananananananauananananarahaiatananananananananananananananananananananananananananananananananananananananananakakakakakakakakanananananananananananananan +ananananananaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaDaEaFaEaGaCaCaCaHaHaCaIaIaIaIaIaIaIaIaIaIaIaCaCaJaCaCaCaCaCaKaKaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaLaLaLaMaLaLaNaLaCaCaCaCaCaCaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananakakakananananaOaOaraPaQaRaRaRaRaRaRaRaSaTatanaUanananananapapanananananananananananananananananananananananananananananananananananananananananalalalalalalalalananananananaCananananananan +ananananananaCananananananananananaVananananananananananananananananananananananananananananaWalalananananaXaXaraYaZbabbbbbbbbbbbcbdbeatanaOanananananauauanananananananananananananananananananananananananananananananananananananananananalalalalalalalalananananananaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananalalalanananananaVarbfbgbhaqaqbiaqaqbjbkblatanaXanananananazazanananananananananananananananananananananananananananananananananananananananananalalalalalalalalananananbmanaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananapapapananananananarbnbobpbpbpbpbpbpbpbqbratanananananananaUaUanananananananananananananananananananananananananananananananbsananananbmananananalalalalalbtalalananananananaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananauauauananananananarbubvbwbwbxbwbybwbwbrbzatanananbmanananaOaOanananananananananananananaoaoaoaoanaVananananananananananananananananananananananalbtalalalalalalananananananaCananananananan +ananananananaCananananbmanananananananananananananananananananananananananananananananaqananazazazanananananananbAbAbAbAbBbAbBbAbAbAbAananananananananaXaXananananananananananananaramamagamatanananananananananananananananananananananananalalalaWalalbCalananananananaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananavawaxanaUaUaUananananananbDaubEauaubFbGbFaubHauauananananananananananananananananananananananaramagamamatanananananananananananananananananananananananalalalalalalalalananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananbmanaqaqaqaqananaAananaOaOaOananbmanananavazazazazbIazbIazazazazaxananananananananananananananananbmanananananakakakakananananananananananananananbmanananananaqanananapapapapapapapapanbmananananaCananananananan +ananananananaCanananananananananananananananananananananananananananananavbJbpbpbKbhaqaqaqaqbLbLbLaqaqaqbMaqaqbjaUaUaUaUbNaUbNaUaUaUaUaxanananananananananananananananananananananbOalalalbPanananananananananananananananananananavawaxananaubGauauauauauauananananananaCananananananan +ananananananaCananbQananananananananananananananananananananananananaqanavbkbRagbSamamamamamambSddddeOddddcuamambTbUbTbTbTbUbTbTbTbTbVaxanaqananananananaoaoaoanananananananananananalalalalananananananananananananananananananananaAanananazazazazazazazazananananananaCananananananan +ananananananaCanananananananbmananananananananananaVanananananananavawaxavbWagagamamamamamamamagagagamamamamamamamamamagamamamamagambXaxavawaxananananaramagbYatbsanananananananananalalalalananananananananananananananananananananananananaUaUaUaUaUaUaUaUananananananaCananananananan +ananananananaCanananananananananananananananananananananananananananaAanavbZamamcaamamamamamamcadsePeQcececCamamamamamamamamamamamamcbaxanaAananananananakakakanananananananananananalalalalananananananananananananananananananananananananaOaOaOaOaOaOaOaOananananananaCananananananan +ananananananaCanananananananananananbmanananananananananananananananananavcccdcecfcgbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbcamamamcgbbbbbbanananananananananalaWalanananananananananananapapapapananananananananananananananananananananananananaXaXaXaXaXaXaXaXananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananbbbbbbbbananaqanananananananananananananaqanavamamamaxanaqananananananananananalalalanananananananananananauauauauananananananananananananananananananananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananchchchchchchchchchchananananananananavawaxanananananananananananavawaxavamamamaxavawaxanbmanananananananapapapananbsananananananananazazazazanananananananananananananbmananananananananananananananananananananananananaCananananananan +ananananananaCbmananananananananananananananananchcicichchcichcicichanananananananananaAananananananananananananbsaAanavamamamaxanaAananananananananananauauauanananananananbmanananaUaUaUaUananananananananananananananananananananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananchcicicicicicicicichchananananananananananananananananananananananananavamamamaxananananananananananananazazazanananananananananananaOaOaOaOanananananananananananananananananananananananbmananananananananananananananaCananananananan +ananananananaCanananananananananananananananchchchcicicicicicicicichchchchanananananananananananananaVananananananananavamamamaxananananananananananananaUaUaUanananananananananananaXaXaXaXananananananananananananananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananbmananananchcicicjcicickciciclcicicicichananananananananananananananananananananananavamamamaxchchchchananananananananaOaOaOananananananananananananananananananananananananananbsananananananananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananchchchchcicicicicicicicicichchanananananananananananeRananananananananananavamamamchchcicichchanananananananaXaXaXanananananananananananananananananananananananananananananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananchchchcicicicjcicicicickchchananananananananananananananananchchchchchchchamamcmcicicicicnchchchchchanananananananananananananananananananananananananananananananananananbmanananananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananchchcicicicicicicicicicichananananananananchchchchchchchchchcicicicicicoamamamcicicicicicicicicichanananananananananananaqanananananananananananananananananananananananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananchcicpclcqcickcicicicichananananananananchcncicicicicicicicicicicicicramamamcicjcicicicicicicichchchchchchchananananavawaxanananananchchchchchchchchchananananananananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananbmanchcicicicicicicickcicicsananananananananchcicicicicicicicjcicickcicictcuambSbKcicicicicicicickcicicicicicichanananananaAanananananchchcicicicickcicichchchchchchchananananbmanananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananchciciciciciciadcYaecochchananananbmanchchaiaYdgciciciamcicicicickcicvagagagbgcpcicickcicicicicicicicrcicichaqaqaqaqaqaqaqaqaqaqaqchcrckcicncicicjctbpbpbpbKcicichchchchchananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananchchcickcicicibfcwblclcichananananananchcicxdmamciciciciamcicyciamamamagczagamamamamamamamamamamamamamamamamadaYcYcYcYcYcYcYbeaeamamamamamamamamamcuagagamcAcicicicicocBchchchananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananchchcicicicrcjahbwaichchchbQanbsanananchclaebncVciciamciciciamcicicoamamagamamamamamamamamamamamamamamamamamahbnbwbwbwbwbwbwbraiamamamamamamamamamcCagbRamcAciciciciciclcichchananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananbmchchchcrchchchcschcichananananananananchchchcicicicicicicicicicicicicDcCamcEcfcicicicicicicicicpcjcicicicichbbaAbbbbbbbbbbbbaAbbcscpcicicickcicFcicDcCagcEcfcicicjcicicicichchananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananchchchchchanananchchchananananananananananchcicicicicicicicicicrcicicicVamcWcicicicicicicicicicicicicicicichanananananaqananananchcicicicicocicicicnamamamcicickcicicicicichchananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananananananananaqananananananananananananananchcicicicjciclcicicicicicicibncHbrcicicncicjcicicicicicicicicicjchananananavawaxanananchchcicjciclcicGciciamamamclcicicicickcicichchananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananananananananavawaxanananananananananananananchchchchchchchchcicnciciciciaeehadcicicicickcicicjcichchchchchchchanananananaAanananananchciciciciciciciciamamamcicicicicicicicicschananananananananananananananbmanananananaCananananananan +ananananananaCananananananananananananananananananananananananaAanananananananananananananananananananananchchchcicjchchchchchchchchchchchchchchchananananananananaVananananananananchcicicicicicickciamamamcicicicicicicicichanananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananananananananananananananananananananchchchchanananananananananananananananananananananananananananananananchchckcicicicicicpamamamcicicickcicicicichanbQanananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananchchchcicjciciciciamamamcjcicicicicjcicichanananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananaoaoanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananchchchchciciciciciamamamcicicicicicicichchanananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananaramamatananananananananananananananananananananananananananananananananananananbmananananananananananananananananananananananananananananananchchchchchciciciamamamcicrcjcicicichchananananbmanananananananananananananananananaCananananananan +ananananananaCananananananananananananananakakananananananananananananananananananananananananananananananananananananananananananananananbmananananananananananananananananananbmananananananchchcjclamamamcochchchchchchanananananananananananananbsanananananananananaCananananananan +ananananananaCanananananananananananananancIcJanananananananananananananananbsananananananananananananananananananananananananananananananananananananananananananananananananananananananananchchcschamamamchchchanananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananbmanananauauanananbmanananananananananananananananananananananananananananananananananananananaoaoaoanananananananananananananananananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananazazanananananaoaoaoaoaoananananananananananananananananananananananananananananananaramamamatananananananananananananananananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananaUaUananananaramamagamcKatananananananaoaoanananananananananananananananananananananaramcLamatananananananbmananananananananananananananananananananananancMamamamaxanananbmanananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananaOaOananananaragagagagagatanananananaramamatanananananbsanananananananananananananananakakakanananananananananananananananananananananananananananananananavamamamaxananananaoaoaoaoaoaoanananananananananananananananananananaCananananananan +ananananananaCananananananananananananananaXaXananananaragbRbYagamatananananananakcOananananananananananananananananananananananaWbtalanananananananananananananananananananananananananananananananavamamamaxanananaragagamasamamatananananananananananananananananananaCananananananan +ananananananaCananananananbmananananananananananananananakakakakakanananananancPapcJananananananananananananananananananaVanananalalalanananananananananananananananananananananananananananananananavamamamaxanananarcQagamagagczatanaoaobmananananananananananananananaCananananananan +ananananananaCanananananananananananbmanananaqananananbOaWalbCalalananananbmananauauananananananananananananananananananananananapapapanananananananananananananananananananananananananananananananavcVamamaxananananakakakakakcRanaradaeatananananananananananananananaCananananananan +ananananananaCananananananananananananananavawaxanananbmalalalalalanananananananazazananananananananananananananananananananananauauauanananananananananananananananananananananananananananananananavbfamamaxanaVananalaWbCalalalanarahaiatananananananananananbmanananaCananananananan +ananananananaCanananananananananananananananaAanananananapapapapapananananananbmaUaUananananananananananananananananananananananazazazananananananananananananananananananananananananananananananbsavbfamamaxananananalalalalalalananakakanananananananananananananananaCananananananan +ananananananaCananananananananananananananananbmananananauauauauauanananananananaOaOanbmananananananananananananananananananananaUaUaUananananananananananananananananananananananananananananananaqbjbfamamaxananananalalalalalalananalalanananananananananananananananaCananananananan +ananananananaCananananananananananananbmananananananananazazazazazanbmanananananaXaXananananananananananananananbsanananananananaOaOaOananananananananananananananbmananananananananananananananavaiaYdgamamaxananananalalalalalalananapapananananbmananananananananananaCananananananan +ananananananaCanananananananananananananananananananananaUaUaUaUaUanananananananbmananananananananananananananbmananananananananaXaXaXanananananananananananananananananananananananananananananavcSdmamamamaxananananalalalalalalananauauanananananananananananananananaCananananananan +ananananananaCanananananananananananananananananananananaOaOaOaOaOanananananananananananananananananananananbmanananananananananananananananananbmanananananananananananananananananananananananavaebncVamagaxananananapapapapapapananazazanananananananananananananananaCananananananan +ananananananaCanananananananananananananananananananananaXaXaXaXaXananananananananananananananananananbQanananananananananananananananananananananananananananananananananananananananananananananbbcTbfagagcUananananauauauauauauananaUaUbmananananananananananananananaCananananananan +ananananananaCananananananananananbsananananananananananananananananananananananananananananananananananbmanananananananananananananananananananananananananananananananananananananananananananananavbfamagaxananananazazazazazazananaOaOanananananananananananananananaCananananananan +ananananananaCanananananananananananananananananananbmanananananananananananananananananananaVanananananananananananananananananananananananananananananananananananananananananananananananananananavbfamamaxananananaUaUaUaUaUaUananaXaXanananananananananbmanananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananbsananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananavdgamamaxananananaOaOaOaOaOaOanananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananananananananananbmanananananananananananananananananananananananananananananananananananananananananananananananananananananchchchchananananananananavamamamaxananananaXaXaXaXaXaXanananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananananananananananananananananananananananananananananaqananananananananananananbsanaqananananananchcicicichananananaqanananavamamamaxanananaqanananananananananananananananananaVanananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananananananananananaqanananananananaqananananananananavawaxanananeRananananananananavawaxanananananchcicicichanananavawaxananavcVamcWaxananavawaxananananananananananananananananananananananananaCananananananan +ananananananaCananananananananananananananbmananananananananananananananananananananananavawaxanananananavawaxananananananananaAananananananananananananananaAananananananchcXcicichananananaAanaqaqbjbfamblbhaqaqanaAanananananbmanananananananananananananananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananananananananananaAananaqaqaqananaAananbsananananaqaqaqaqaqananananananananananaqaqaqaqaqaneRbmananchchchananananananavaPaYcYdgamcZcYbeaTaxananananananananananananananananananananananananananaCananananananan +ananananananaCananbsananananchchchchchananananananananananananananbmananananbmanananananananandaaaabacbhaqaqaqaqaqaqaqaqbjdcdddedddfbhaqaqbMaqaqaqaqaqbjaYcYcYcYbebhaqaqaqaqaqaqaqaqaqaqaqaqaqbjaYdgcacecececCcZbeaxananananananananananananananananananananananananananaCananananananan +ananananananaCananananananchchchcicichchchanananananananananananananananananananananananananavahafaBdbaiamamamamamamamamamamcWbwcVamamamamamamamamamcZcYdgdidjdkcZcYdgamamamamamamamamamamamcZcYdgambgdlbbbcbkamblaxananananananananananananananananananananananananananaCananananananan +ananananananaCanananananchchcicicicicicichanananananananananananananananananananananananananavdmejdhekamamamamamamamamamamambldnbfamamamamamamamamamamamagdodpdqamamamamamamamamamamamamamamamamamambgaxbsavbkamblaxananananananananananananananananananananananananananaCananananananan +ananananananaCanananananchcicicicicicicichchananananananananananananananananananananananananavadaeamadaeamamamamamamamamamamcZcYdgamamamamamamamamamcWbwcVdrdsdtdubwcVamamamamamamamamamamamcWbwcVambgdvaqdwbkamblaxananananananananananananananananananananananananananaCananananananan +ananananananaCanananananchciclcicicickcicichchanananananananananananananananananananananananandxaibxahdybbbbbbbbbbbbbbbbcTdzcedAcedBdCbbbbbbbbbbbbbbbbcTbnbwbwbwbrdCbbbbbbbbbbbbbbbbbbbbbbbbbbcTbncVbSddddddcucWbraxananananananananananananananananananananananananananaCananananananan +ananananananaCanananananchchcocickcicicicicichananananananananananananananananananananananaqananbbbbbbananaqanbmanananananbbdDaAdEbbanananananananananbQbbdDaAdEbbananananananananananananananavbubnbwcVamcWbwbrbzaxananananananananananananananananananananananananananaCananananananan +ananananananaCananananananchcicicicicicrcicichanananananbmanananananananananananananananavawaxanananananavawaxananananananananaqananananananananananananananaqanananananananananananananananaqanbbbbcTbfamdFdCbbbbanaqanananananananananananananananananananananananananaCananananananan +ananananananaCanananananchchcicicicicpcqcicichananananananananananananananananananananananaAanananananananaAananananananananavawaxananananananananananananavawaxanananananananananananananavawaxananavdgagdGaxananavawaxananananananananananananananananananananananananaCananananananan +ananananananaCanananananchcicickcNcicicicickchananananananananananananananbmanananananananananananananananananananananananananaAananananananananaVanananananaAanananananananananananananananaAanananavamamagaxanananaAanananananananananananananananananbmanananananananaCananananananan +ananananananaCanananananchchchcicicichchchchchanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCanananananananchchcrchchanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananananananananchchchanananananananananananananananananananananananananananananananananananananananananananbsanananananananananananananananananananananaVananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananavamamamaxananananananananananbmananananananananananananananananananaCananananananan +ananananananaCananananananbmananananananananananananananananananananananananananananananananananananananbmananananananananananananananananananananananananananananananananananananananananananananbmavamamamaxanananananananananananananananananaoaoanananbmananananananaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananaqanavamamamaxanaqanananananananananananananandIamamdJanananananananananaCananananananan +ananananananaCananananananananananananananbmananananananananananananananananananananananananananananananananananananananananananananaoaoaoaoaoaoaoaoaoaoaoaoaoaoaoananananananananananananananavawaxavamamamaxavawaxananananananananananananaramdKagamatananananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananananananananananananananananananananananananananananananaramamamagamamamasamamamamamamagatananananananananananananananaAanavamamamaxanaAananananananananananananananakakakakanananananananananaCananananananan +ananananananaCananananananananananbsananananananananananananananbmananananananananananananananananananananananananananananananananaragagamamamamamagamamamamagamdLatananananananananananananananananavamamamaxananananananananananananananananalbtbCalanananananananananaCananananananan +ananananananaCananananananananananananananananananananananananananananananananananananananananananananananananananananbmananananananakakakakdMdMdMdMdMdMakakakakakanananananananananchchchchchchchchavamamamaxananananananananananananaoaoananalalalalanananananaoaoaoanaCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananananananananananananananananaoaoananancPalaWalalananbmanananalalalalalanananananananchchchcicicicicicichavamamamaxanbmanananananananananaramagatanapapapapananananaramagamataCananananananan +ananananananaCanananananananananananananananananananananananananananananananananananananananaVananananananananananananaragbYatanananapapapapananananananapdNapapapanananananananchcicicicicicicicichchcVamcWaxananananananananananananakakananauauauauanananananakakakanaCananananananan +ananananananaCanananananananananananananananananbmananananananananananananchchchchchananananananananananananananananananakakananananauauauauananananananauauauauauananananananchchcicicicicicicicicicibfamblaxananananananananananananbCalananazazazazanananananalaWalanaCananananananan +ananananananaCananananananananananananananananananananananananananananananchcicicichchananananananananananbmananananananbtalanaqananazazazazananbsanananazazazazazananaqanananchcicicrcicicickcicicicibfamblaxananananananananananananapapananaUaUaUaUananananbmalalalanaCananananananan +ananananananaCanananananananbmanananananananananananananananananananananchchcicickcichchchchchanananananananananananananapapavawaxanaUaUaUaUananananananaUaUaUaUaUanavawaxanchchcicicicicicicicickcicibfamblaxananananananananananananauauananaOaOaOaOanananananalalalanaCananananananan +ananananananaCanananananananananananananananananananananananananananchchchcicicicicicicicicichanananananananananananananauauanaAananaOaOaOaOananananbmanaOaOaOaOaOananaAananchcicicicicicicicidHciciaYdgamblaxanaqananananananananananazazananaXaXaXaXanananananapapapanaCananananananan +ananananananaCananananananananananananananananananananananananananchchcicicicickcicicicicichchanananananananananananananazazananananaXaXaXaXananananananaXaXaXaXaXanananananchcicicickcicicicraeaYcYeuamamblaxavawaxanananananananananaUaUanananananaqanananananauauauanaCananananananan +ananananananaCanananananananananananananananananananananananananchchcicicicicicicicicicickchananananananananananananananaUaUananananananananananananananananananananananananchcicicicicicicicidPdmagbRagamblaxanaAananananananananananaOaOananananavawaxananananazazazanaCananananananan +ananananananaCanananananananananananananaVanananananananananananchciciciciciciclcicjcicNcichchanananananananananbsanananaOaOananananananananananananananananananananananananchcicicicicicickciaibnbwevagamblaxananananananananananananaXaXanananaVanaAanananananaUaUaUanaCananananananan +ananananananaCanananananananananananananananananananananananananchcicpcickciciamamamamclcicichanananananananananananananaXaXananananananananananananananananananananananananchchcicicicicicicicicicibncVamblaxanananananananananananananananananananananananananaOaOaOanaCananananananan +ananananananaCanananananananaqanananananananananananananananananchciciciciciciamagagamcicicNchananananananananananananananananananananananananananananananananananananananananchchcicickcidOcrcicicicibfamblaxanananananananananananananananananananananananananaXaXaXanaCananananananan +ananananananaCananananananavawaxanananbmananananananananananananchchaiciamcNckagamamamcicochchanananananananananananananananananananananananananananananananananananananananananchchcicicicicicicickcibfamblaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCanananananananaAanananananananananananananananananchchdRciciamamamamamagcicichanananbmananchchchchchchchananananananananananananananananananananananananananananananchchchchchchchchchchbfamblaxanaVanananananananananananananananananananananananananananaCananananananan +ananananananaCananaoaoaoaoaoaoaoanananananananananananbmanananchchciciciciciciciciciamamcichchanananananchcicicicicichanananananananananananananananananananananananananananananananananananananananavdgamcZaxanananananananananananananbmanananananananananananananananaCananananananan +ananananananaCanarasamamamcZbedSatananananananananananananananchchcicicicicicrcNcjciamagcicrchanananananchcidQcicicichanananananananananananananananananananananananananbmanananananananananeSanaqanavamamamaxanaqanananananananananananananananananananananananananananaCananananananan +ananananananaCanaramamamamameIeJatanananananananananananananananchchclcicjcpciciciciadciaechchanananananchchchcicicpchananananananananbmanananananananananananananananananananananananananananavawaxavamamamaxavawaxananananananananananananananananananananananananananaCananananananan +ananananananaCananakakakakakakajanananananananananananananananananchchchchchchchchchaYdUbechanaVananananananchchchchchanananananananananananananananaVanananananananananananananananananananananaAanavamamamaxanaAanananananananananananananananananananananananananananaCananananananan +ananananananaCanbOalalalalalbCalananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananalalalalalalalanananananbmananananananananananananananananaqanbsanananbQanananananananananananananananananananananananananananananananananananananananananananananbmanananananananavamamamaxananananananananananananananananananananbmananananananananaCananananananan +ananananananaCananalalalalalalalanananananananananananananananbmanananananavawaxanchbndVbrchchchchchchanananananananchchchchchchchanananananananananananananananananananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananalalalalalalalananananananananananananananananananananananaAananchahdWaicichchcicichanananchchchchchcicicicicichchanchchchchanananananananananananananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananalalalalalalalanananananananananananananananananananananananananchcicicicicicicicichchchchchcicicichchchchchchchchchchcichchchchchchchanananananananananbmanananananananananaVananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananapapapapapapapanananananananananananbsanananananananananananananchchcicicicicickcicicickcicicicicicicickcicichchchchcicicicicicicicichchchchchchanananananananananananananananananavamamamaxananananananananananananananananananananananananbsananananaCananananananan +ananananananaCananauauauauauauauanananananananananananananananananananananananananchchciamamciciciciciciciciclcicicicicicicicicicicicicicicicicicicicicicicicicichanananananananananananananananananavamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCananazazazazazazazanananananananananananananananananananananananananchcsciciciciciclcickcicicicicicicicicicicicicicicicicicicicicicicockcicicicicichchchchchchananananananananananaqanavamamamaxanaqanananananananbmanananananananananananananananananananaCananananananan +ananananananaCananaUaUaUaUaUaUaUananananbmanananananananananananananananbmananananchchcicicicjciamcicicicicicicicicickcicrcicicicicjcicicjcicicicicicicicickciclcicicicicichchananananananananavawaxavamamamaxavawaxananananananananananananananananananananananananananaCananananananan +ananananananaCananaOaOaOaOaOaOaOanananananananananananananananananananananananananchchcicicicicicicicicNcickamcicicicicicicicicocicicjcicicicicicicicicicicicicicickcicicicichchanbsananananananaAanavamamamaxanaAanananananananananananananananananananananananananananaCananananananan +ananananananaCananaXaXaXaXaXaXaXanananananananaoaoaoaoaoaoaoaoananananananananananchchdYdmcicicicicocNcicaamcicicicicNcpcicicqciciclcicicicicjcicicicicicicicicicicicidTcicicichaqaqaqaqaqaqaqaqaqaqetamamamaxanananananananananananananananananananananananananananananaCananananananan +ananananananaCanananananananananananaqanananareAcYcYcYcYeEbeeFatananananaoaoanananchchahdXcickcicicicicicicicicjciamciciamamciciciamcicicicickcicickcicicjciciciamamcicicrciciamamamamamamamamamamamamamamamaxananananananananananananaoaoaoaoaoaoaoaoaoaoaoaoaoananananaCananananananan +ananananananaCananananananananananavawaxananarbfamagamamamcZbeatanananaragagatananchchcicicicicicicicicicicicNcicicickciciciamcicicpcicickciciciambSciciciamciciamamcicicidTamamamamamamagagamamamamamamamamaxanananananananananananareDaRaRaRaRaRaRaRaRaRaRaReHatanananaCananananananan +ananananananaCananananananananbsananaAanananarbfamamagamamagblatanananarbRagatananchchcicicicicickcicpcicicicicicicicicicicrcuamciciamamamcicicpamamcicicjcidTcicicickciamamcpamamamamamagdKagamamamamcVamcWaxanananananananananananarewcicjciciciciciciciagamexatanananaCananananananan +ananananananaCananananananananananaoaoaoananareKeGbwbwbwbwbweLatananananakajananananchchchcicickcBciclcicjcicicockcicicjcicicicjclciciciciciciaecicrcicicicicicicicicocicicicichbbbbbbbbbbbbbbbbbbbbbcbfamblaxanananananananananaoaodIewcpcicicicidTcicrciamamexatanananaCananananananan +ananananananaCanananananananananaramamamatananakakakakakakakakananananbOapapananananchchchchciciciciciciciciciciciciciclciciciciciciciciciciciciciciciciciciciciclcicicicicichchanananananbmananaqanavbfamblaxanaqananananananeaaiaYcYeycidZcickelemenciciciciexatanananaCananananananan +ananananananaCananananbmananananaragamamatancPalalalbtalalalaWanananananauauananananananchchchchcicicicickcicicicickcicicicicicicicicicicicicicicicicicicickcicicicicicicichchchanananananananavawaxavbfamblaxavawaxanananananarecdmambgcicicicieoasepciciciciexatanananaCananananananan +ananananananaCananananananananananakakakanananalalalalalalalalananaqananazazanananananananchchchchchcicicicickcicicicicicicicickcicjcicjcicickcicicicjcicicicicichchchchchchchanananananananananaAanavbnbxbraxanaAananananananaraebnedezcicocrcieqerescicpckamexatanananaCananananananan +ananananananaCananananananananancPalalalanananalalalalalalalalanavawaxanaUaUananananchchananbsananchchchchchcicqcicicickcicicicicicicjcicicicicicicicicicicichchchchchchchanananananananananananananavaeeeadaxanananananananananakakefewckclcicicicicicichchamexatanananaCananananananan +ananananananaCananananananananananalalalanananalalalalalalalalananaAananaOaOanananchchcichchananeTananananchchchchcicicicickcicicicicicicicicicicicicicicichchananananananananananananananananananananbbbbbbanbmananananananananbCalegewcicidTebcragcichchchamexatbmananaCananananananan +ananananananaCananananananananananalalalanananalalalalalalalalanananananaXaXanananchcicicichchchchchanananchchchchchcicicocicjciclcicicjcicocicicicickcichchchanananananananananananananananananananananaqanananananananananananalalegeMeBeBeBeBeBeBeBeBeCeBeBeNatanananaCananananananan +ananananananaCananananananananananapapapanananalalalalalalalalananananananananbmanchcicicicicicicichanananananchchchchcicicicicicickcicicicicicjcicicichchchchanananananbmananananananananananananananavawaxananananananananananalalalakakakakakakakakakakakakakananananaCananananananan +ananananananaCananananananananananauauauanananalalalalalalalalananananananananananchchcicicicicicichanananananchchchchchcicicicicicicicicicicicicichchchchchanananananananananananananbsananananananananaAanananananananananananapapapalalalalaWalalalbCalalalalananananaCananananananan +ananananananaCananananananananananazazazananbmalalalalalalalalanbmanananananananananchchchcicicichchanananbmananchchchchchchchchcicicicicichchchchchananananananananananananananananananananananananananananananananananananananauauaualalalalalalalalalalalalalananananaCananananananan +ananananananaCananananananananananaUaUaUanananalalalalalalalalanananananananananananananchchchchchananananananananananananchchchchchchchchchchananbsananananananananananananananananananananananananananananananananananananananazazazalalalalalalalalalalalalalananananaCananananananan +ananananananaCananananananananbmanaOaOaOanananapapapapapapapapanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananbsanananananaUaUaUapapapapapapapcIapapapapapananananaCananananananan +ananananananaCananananananananananaXaXaXanananauauauauauauauauanananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananbmanananananananananananananananaOaOaOauauauauauauauauauauauauauananananaCananananananan +ananananananaCananananananananananananananananazazazazazazazazananananananbmanananananananananananananananananananananananananananananananananananananananbmananananananananananananananananananananananananananananananananananaXaXaXazazazazazazazazazazazazazananananaCananananananan +ananananananaCananananananananananananananananaUaUaUaUaUaUaUaUananananananananananananananananananananananananananananananananaVanananananananananananananananananananananananananananananananananananananananananananananananbsanananaUaUaUaUaUaUaUaUaUaUaUaUaUananananaCananananananan +ananananananaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCeieieieieieieieiaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCaCeieieieieieieieieieieieieiaCaCaCaCaCananananananan +anananananananananananananananananananananananaXaXaXaXaXaXaXaXananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananaXaXaXaXaXaXaXaXaXaXaXaXaXanaqanananananananananan +anananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananbsavawaxananananananananan +ananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananbmananananananananananaAanananananananananan +anananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananan +anananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananan +anananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananananan +"} diff --git a/maps/atoll/atoll_decals.dm b/maps/atoll/atoll_decals.dm new file mode 100644 index 0000000000..625c757814 --- /dev/null +++ b/maps/atoll/atoll_decals.dm @@ -0,0 +1,62 @@ +/obj/effect/floor_decal/atoll + name = "mural" + icon = 'maps/atoll/icons/decals/marble_decals.dmi' + icon_state = "mural" + +/obj/effect/floor_decal/atoll/damage + name = "damaged flooring" + icon_state = "damage" + +/obj/effect/floor_decal/atoll/bronze + name = "bronze filling" + icon_state = "bronze" + +/obj/effect/floor_decal/atoll/border + name = "marble border" + icon_state = "border" + +/obj/effect/floor_decal/atoll/border/invert + icon_state = "border_invert" + +/obj/effect/floor_decal/atoll/stairs + name = "marble stairs" + icon_state = "stairs" + +/obj/effect/floor_decal/atoll/stairs/Initialize() + dir = pick(cardinal) + . = ..() + +/obj/effect/floor_decal/atoll/moss + name = "moss" + icon_state = "moss" + +/obj/effect/floor_decal/atoll/moss/random/Initialize() + dir = pick(alldirs) + . = ..() + +/obj/effect/floor_decal/atoll/power + icon_state = "power" + +//Fake shadows and silhouettes +/obj/effect/decal/shadow + icon = 'maps/atoll/icons/decals/wall_decals.dmi' + icon_state = "wall_shadow" + anchored = 1 + +/obj/effect/decal/shadow/silhouette + icon_state = "silhouette" + +/obj/effect/decal/shadow/silhouette/pillar + icon_state = "silhouette_p" + +/obj/effect/decal/shadow/floor + icon_state = "floor_shadow" + +/obj/effect/decal/whitecaps + icon = 'maps/atoll/icons/turfs/water.dmi' + icon_state = "1" + anchored = 1 + +/obj/effect/decal/whitecaps/Initialize() + icon_state = pick("1","2","3") + . = ..() \ No newline at end of file diff --git a/maps/atoll/atoll_objs.dm b/maps/atoll/atoll_objs.dm new file mode 100644 index 0000000000..5b2a3e1652 --- /dev/null +++ b/maps/atoll/atoll_objs.dm @@ -0,0 +1,69 @@ +//THESE AREN'T OBJECTS THESE ARE AREAS WTF BURRITO!!! +/area/gateway/atoll + name = "Away Site - Lake" + dynamic_lighting = 0 + +/area/gateway/atoll/powered + requires_power = 0 + +/obj/structure/railing/overhang + name = "bronze ledge" + desc = "An overhang made of a bronze-looking material. There's a lip on it to conveniently stub a toe into." + icon = 'maps/atoll/icons/objs/bronze_overhang.dmi' + icon_modifier = "bronze_" + icon_state = "bronze_railing0" + +//Escape most railing interactions besides tackling people over them +/obj/structure/railing/overhang/attackby(obj/item/W) + if(!istype(W, /obj/item/weapon/grab)) + return + return ..() + +/obj/structure/railing/overhang/waterless + icon_modifier = "wbronze_" + icon_state = "wbronze_railing0" + climbable = 0 + +/obj/structure/bed/chair/sofa/bench/marble + name = "marble bench" + desc = "Somewhat of an ornate looking bench with faded blue etchings." + icon = 'maps/atoll/icons/objs/benches.dmi' + icon_state = "bench" + +/obj/structure/lightpost/atoll + name = "rusty light post" + desc = "Actually it's more like a rod with a trapped gas glowing inside some glass, but that's basically what a lightpost is." + icon = 'maps/atoll/icons/objs/lamp.dmi' + icon_state = "lamp" + +/obj/structure/flora/tree/atoll + name = "mossy tree" + desc = "A messy looking tree with a purple trunk. Vines seem to droop from it." + icon = 'maps/atoll/icons/objs/trees.dmi' + icon_state = "green" + +/obj/structure/flora/tree/atoll/huge + name = "massive mossy tree" + icon = 'maps/atoll/icons/objs/trees_xl.dmi' + icon_state = "tree" + +/obj/structure/flora/moss + name = "hanging moss" + desc = "Some unsightly moss. Clearly the groundskeepers here aren't doing their jobs." + icon = 'maps/atoll/icons/objs/moss.dmi' + icon_state = "moss" + +//TELEPORTER +/obj/effect/step_trigger/teleporter/atoll + name = "strange field" + desc = "A prismatic field of... energy, probably. You should definitely rub your face in it." + icon = 'maps/atoll/icons/decals/marble_decals.dmi' + icon_state = "teleporter" + invisibility = 0 + +//teleport_z must be populated but this is a gateway map +//switching between tether or another map will change the z +//so we just do this lmfao +/obj/effect/step_trigger/teleporter/atoll/Initialize() + . = ..() + teleport_z = z \ No newline at end of file diff --git a/maps/atoll/atoll_turfs.dm b/maps/atoll/atoll_turfs.dm new file mode 100644 index 0000000000..48a375671d --- /dev/null +++ b/maps/atoll/atoll_turfs.dm @@ -0,0 +1,55 @@ +//All turfs here meant to be "fullbright", no lighting. +/turf/simulated/floor/atoll + name = "marble floor" + desc = "Scuff and weathered etchings make these floors out to be pretty old." + icon = 'maps/atoll/icons/turfs/marble.dmi' + icon_state = "1" + dynamic_lighting = 0 + +//Pick random sprite states and generate damage decals randomly +/turf/simulated/floor/atoll/Initialize() + . = ..() + icon_state = "[rand(1,5)]" + if(prob(5)) + new /obj/effect/floor_decal/atoll/damage(src, pick(alldirs)) + +//holycrapshitcode +/turf/simulated/floor/atoll/vertical + name = "marble wall" + desc = "A sheer face wall, extending up to who-knows-how-high." + icon_state = "wall" + density = 1 + +/turf/simulated/floor/atoll/vertical/Initialize() + . = ..() + icon_state = "wall" + +/turf/simulated/floor/water/atoll + name = "shallow lake" + desc = "This water looks pretty shallow and calm. You'd almost feel bad for hopping in and disturbing it's serene flatness." + icon = 'maps/atoll/icons/turfs/water.dmi' + icon_state = "shallow" + under_state = "shallow" + dynamic_lighting = 0 + outdoors = OUTDOORS_NO + +//Cut out the caustics overlay and replace with nothing +/turf/simulated/floor/water/atoll/update_icon() + ..() + cut_overlays() + var/image/water_sprite = image(icon = 'maps/atoll/icons/turfs/water.dmi', icon_state = "shallow", layer = 3.0) + add_overlay(water_sprite) + +//Spawn animated whitecaps around +/turf/simulated/floor/water/atoll/Initialize() + . = ..() + if(prob(25)) + new /obj/effect/decal/whitecaps(src) + +/turf/simulated/floor/outdoors/grass/heavy/atoll + dynamic_lighting = 0 + outdoors = OUTDOORS_NO + +/turf/simulated/floor/beach/sand/outdoors/atoll + dynamic_lighting = 0 + outdoors = OUTDOORS_NO \ No newline at end of file diff --git a/maps/atoll/icons/decals/marble_decals.dmi b/maps/atoll/icons/decals/marble_decals.dmi new file mode 100644 index 0000000000..f7b7a071e9 Binary files /dev/null and b/maps/atoll/icons/decals/marble_decals.dmi differ diff --git a/maps/atoll/icons/decals/wall_decals.dmi b/maps/atoll/icons/decals/wall_decals.dmi new file mode 100644 index 0000000000..e05099cf80 Binary files /dev/null and b/maps/atoll/icons/decals/wall_decals.dmi differ diff --git a/maps/atoll/icons/objs/benches.dmi b/maps/atoll/icons/objs/benches.dmi new file mode 100644 index 0000000000..5228bfa261 Binary files /dev/null and b/maps/atoll/icons/objs/benches.dmi differ diff --git a/maps/atoll/icons/objs/bronze_overhang.dmi b/maps/atoll/icons/objs/bronze_overhang.dmi new file mode 100644 index 0000000000..730c09e4ed Binary files /dev/null and b/maps/atoll/icons/objs/bronze_overhang.dmi differ diff --git a/maps/atoll/icons/objs/lamp.dmi b/maps/atoll/icons/objs/lamp.dmi new file mode 100644 index 0000000000..204fb5d432 Binary files /dev/null and b/maps/atoll/icons/objs/lamp.dmi differ diff --git a/maps/atoll/icons/objs/moss.dmi b/maps/atoll/icons/objs/moss.dmi new file mode 100644 index 0000000000..4798478559 Binary files /dev/null and b/maps/atoll/icons/objs/moss.dmi differ diff --git a/maps/atoll/icons/objs/trees.dmi b/maps/atoll/icons/objs/trees.dmi new file mode 100644 index 0000000000..e60d10d270 Binary files /dev/null and b/maps/atoll/icons/objs/trees.dmi differ diff --git a/maps/atoll/icons/objs/trees_xl.dmi b/maps/atoll/icons/objs/trees_xl.dmi new file mode 100644 index 0000000000..d3f7c66e02 Binary files /dev/null and b/maps/atoll/icons/objs/trees_xl.dmi differ diff --git a/maps/atoll/icons/turfs/marble.dmi b/maps/atoll/icons/turfs/marble.dmi new file mode 100644 index 0000000000..939da7768b Binary files /dev/null and b/maps/atoll/icons/turfs/marble.dmi differ diff --git a/maps/atoll/icons/turfs/water.dmi b/maps/atoll/icons/turfs/water.dmi new file mode 100644 index 0000000000..05b828bdbf Binary files /dev/null and b/maps/atoll/icons/turfs/water.dmi differ diff --git a/maps/example/example.dm b/maps/example/example.dm index d2639b8885..e88eaaf7ba 100644 --- a/maps/example/example.dm +++ b/maps/example/example.dm @@ -10,6 +10,8 @@ #define USING_MAP_DATUM /datum/map/example + #warn Please uncheck example before committing + #elif !defined(MAP_OVERRIDE) #warn A map has already been included, ignoring Northern Star diff --git a/maps/expedition_vr/beach/beach.dmm b/maps/expedition_vr/beach/beach.dmm index 86e53b1aaa..1d3c1e6233 100644 --- a/maps/expedition_vr/beach/beach.dmm +++ b/maps/expedition_vr/beach/beach.dmm @@ -873,6 +873,10 @@ "nD" = ( /turf/simulated/mineral/ignore_mapgen, /area/tether_away/beach/water) +"nP" = ( +/mob/living/simple_mob/animal/passive/fennec/faux, +/turf/simulated/floor/wood, +/area/tether_away/beach/resort/kitchen) "of" = ( /obj/structure/table/woodentable, /obj/random/drinkbottle, @@ -1345,16 +1349,16 @@ /turf/simulated/floor/beach/sand/outdoors, /area/tether_away/beach/resort) "Bk" = ( -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, @@ -1363,10 +1367,10 @@ /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/material/knife/butch, /obj/item/weapon/material/minihoe, /obj/item/weapon/material/knife/machete/hatchet, @@ -11048,7 +11052,7 @@ ad ad ad ad -ad +nP ad ad ad diff --git a/maps/gateway_vr/eggnogtown.dmm b/maps/gateway_vr/eggnogtown.dmm index 175934ba36..02654d71f2 100644 --- a/maps/gateway_vr/eggnogtown.dmm +++ b/maps/gateway_vr/eggnogtown.dmm @@ -1000,16 +1000,16 @@ /turf/simulated/floor/tiled, /area/gateway/eggnogtown/loniabode) "gk" = ( -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, @@ -1018,10 +1018,10 @@ /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/structure/closet, /obj/item/weapon/reagent_containers/food/condiment/small/saltshaker, /obj/item/weapon/reagent_containers/food/condiment/small/peppermill, @@ -3649,7 +3649,7 @@ "Rd" = ( /obj/structure/table/marble, /obj/item/weapon/material/kitchen/rollingpin, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/reagent_containers/food/condiment/yeast, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/yeast, diff --git a/maps/gateway_vr/variable/arynthilake_a.dmm b/maps/gateway_vr/variable/arynthilake_a.dmm index afc16589d3..41a8be8f09 100644 --- a/maps/gateway_vr/variable/arynthilake_a.dmm +++ b/maps/gateway_vr/variable/arynthilake_a.dmm @@ -1143,16 +1143,16 @@ /obj/random/meat, /obj/random/meat, /obj/random/meat, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, diff --git a/maps/gateway_vr/variable/arynthilake_b.dmm b/maps/gateway_vr/variable/arynthilake_b.dmm index bbba59342e..53c799bac4 100644 --- a/maps/gateway_vr/variable/arynthilake_b.dmm +++ b/maps/gateway_vr/variable/arynthilake_b.dmm @@ -157,16 +157,16 @@ /obj/random/meat, /obj/random/meat, /obj/random/meat, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, diff --git a/maps/gateway_vr/wildwest.dmm b/maps/gateway_vr/wildwest.dmm index 2a42bcc14f..12a79c33ec 100644 --- a/maps/gateway_vr/wildwest.dmm +++ b/maps/gateway_vr/wildwest.dmm @@ -676,9 +676,9 @@ /area/awaymission/wwmines) "fb" = ( /obj/structure/closet/secure_closet/freezer/kitchen, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /turf/simulated/floor/tiled/eris/cafe, /area/awaymission/wwmines) "fc" = ( diff --git a/maps/groundbase/eastwilds/eastwilds1.dmm b/maps/groundbase/eastwilds/eastwilds1.dmm index 28b038004a..fc499a6095 100644 --- a/maps/groundbase/eastwilds/eastwilds1.dmm +++ b/maps/groundbase/eastwilds/eastwilds1.dmm @@ -29,6 +29,12 @@ "r" = ( /turf/simulated/mineral/cave/virgo3c, /area/submap/groundbase/wilderness/east/cave) +"u" = ( +/obj/effect/landmark{ + name = "carpspawn" + }, +/turf/simulated/floor/water/deep/virgo3c, +/area/submap/groundbase/wilderness/east) "w" = ( /obj/effect/map_effect/portal/master/side_a/gb_wilds/east_west{ dir = 8 @@ -38,6 +44,12 @@ "y" = ( /turf/simulated/mineral/ignore_cavegen, /area/submap/groundbase/wilderness/east) +"B" = ( +/obj/effect/landmark{ + name = "carpspawn" + }, +/turf/simulated/floor/water/virgo3c, +/area/submap/groundbase/wilderness/east) "F" = ( /turf/simulated/floor/outdoors/grass/virgo3c, /area/submap/groundbase/wilderness/east/unexplored) @@ -2383,7 +2395,7 @@ U U U U -U +u U U U @@ -2653,7 +2665,7 @@ U U U U -U +u U U U @@ -2972,7 +2984,7 @@ U U U U -U +u U U U @@ -3421,7 +3433,7 @@ U U U U -U +u U U U @@ -3513,7 +3525,7 @@ U U U U -U +u U U U @@ -4779,7 +4791,7 @@ Z U U U -U +u U U U @@ -4839,7 +4851,7 @@ Z Z Z U -U +u U U U @@ -5212,7 +5224,7 @@ U U U U -U +u U U U @@ -5245,7 +5257,7 @@ Z Z U U -U +u U U U @@ -5414,7 +5426,7 @@ U U U U -U +u Z Z Z @@ -6518,7 +6530,7 @@ F V V Z -Z +B Z Z Z @@ -6908,7 +6920,7 @@ Z Z Z Z -U +u U U U @@ -6971,7 +6983,7 @@ U U U U -U +u U U Z @@ -8192,7 +8204,7 @@ U U U U -U +u U U U @@ -8245,7 +8257,7 @@ U U U U -U +u U U U @@ -9461,7 +9473,7 @@ r r Z Z -Z +B Z U U @@ -10186,7 +10198,7 @@ U U U U -U +u U U Z @@ -10322,7 +10334,7 @@ U U U U -U +u U U U @@ -10374,7 +10386,7 @@ U U U U -U +u U U U @@ -11219,7 +11231,7 @@ Z U U U -U +u U U U @@ -11373,7 +11385,7 @@ U U U U -U +u U Z Z @@ -11476,7 +11488,7 @@ U U U U -U +u U U U @@ -12337,7 +12349,7 @@ U U U U -U +u U U U @@ -12456,7 +12468,7 @@ Z Z Z U -U +u U U U @@ -12778,7 +12790,7 @@ U U U U -U +u U U U @@ -13757,7 +13769,7 @@ Z Z U U -U +u U U U @@ -13767,7 +13779,7 @@ Z Z Z Z -Z +B Z Z Z @@ -15893,7 +15905,7 @@ V V V Z -Z +B Z Z Z diff --git a/maps/groundbase/eastwilds/eastwilds2.dmm b/maps/groundbase/eastwilds/eastwilds2.dmm index fdd0dfdc34..33a58e58c2 100644 --- a/maps/groundbase/eastwilds/eastwilds2.dmm +++ b/maps/groundbase/eastwilds/eastwilds2.dmm @@ -37,6 +37,12 @@ }, /turf/simulated/floor/water/deep/virgo3c, /area/submap/groundbase/wilderness/east) +"B" = ( +/obj/effect/landmark{ + name = "carpspawn" + }, +/turf/simulated/floor/water/deep/virgo3c, +/area/submap/groundbase/wilderness/east) "F" = ( /turf/simulated/floor/outdoors/grass/virgo3c, /area/submap/groundbase/wilderness/east/unexplored) @@ -46,6 +52,12 @@ }, /turf/simulated/floor/outdoors/grass/forest/virgo3c, /area/submap/groundbase/wilderness/east) +"I" = ( +/obj/effect/landmark{ + name = "carpspawn" + }, +/turf/simulated/floor/water/virgo3c, +/area/submap/groundbase/wilderness/east) "V" = ( /obj/effect/map_effect/portal/master/side_b/gb_wilds/east{ dir = 4 @@ -949,7 +961,7 @@ Z Z Z t -t +B t t Z @@ -3082,7 +3094,7 @@ l l Z Z -Z +I t t t @@ -5071,7 +5083,7 @@ Z Z Z Z -Z +I n n F @@ -5917,7 +5929,7 @@ F n Z Z -Z +I Z Z Z @@ -6912,7 +6924,7 @@ t t t t -Z +I Z Z Z @@ -7225,7 +7237,7 @@ Z t t t -t +B t t t @@ -7515,7 +7527,7 @@ t t Z Z -Z +I Z n n @@ -8044,7 +8056,7 @@ Z Z Z Z -t +B t t t @@ -8128,7 +8140,7 @@ n n Z Z -Z +I Z Z Z @@ -8341,7 +8353,7 @@ t t t t -Z +I Z Z Z @@ -8695,7 +8707,7 @@ Z Z t t -t +B t t t @@ -8904,7 +8916,7 @@ t t Z Z -Z +I Z Z Z @@ -8992,7 +9004,7 @@ t t t t -t +B Z Z Z @@ -9067,7 +9079,7 @@ n n Z Z -Z +I t Z Z @@ -9412,7 +9424,7 @@ t t t t -t +B t t t @@ -9689,7 +9701,7 @@ n Z Z Z -Z +I t t t @@ -10688,7 +10700,7 @@ Z t t t -t +B t t t @@ -11336,7 +11348,7 @@ n n n Z -Z +I Z n n @@ -11513,7 +11525,7 @@ t t t t -t +B t t t @@ -11532,7 +11544,7 @@ Z Z t t -t +B t t t @@ -12521,7 +12533,7 @@ t t t t -t +B t t t @@ -12787,7 +12799,7 @@ t t t t -t +B t t t @@ -13531,7 +13543,7 @@ t t t t -t +B t Z Z @@ -13953,7 +13965,7 @@ t t t t -t +B t t t @@ -14303,7 +14315,7 @@ Z Z Z Z -Z +I Z Z Z @@ -14620,7 +14632,7 @@ n n Z Z -Z +I Z Z Z @@ -14644,6 +14656,7 @@ Z Z Z t +B t t t @@ -14655,8 +14668,7 @@ t t t t -t -t +B t t t @@ -16027,7 +16039,7 @@ Z Z Z Z -Z +I Z Z Z @@ -16081,6 +16093,7 @@ t t t t +B t t t @@ -16090,8 +16103,7 @@ t t t t -t -t +B t t t @@ -16352,7 +16364,7 @@ Z Z t t -t +B t t t @@ -16937,7 +16949,7 @@ t t t t -t +B t t t @@ -17085,7 +17097,7 @@ t t t t -t +B Z Z Z diff --git a/maps/groundbase/gb-wilds.dm b/maps/groundbase/gb-wilds.dm index ad2969a473..dee73d1c51 100644 --- a/maps/groundbase/gb-wilds.dm +++ b/maps/groundbase/gb-wilds.dm @@ -47,6 +47,7 @@ name = "wilderness" icon = 'icons/turf/areas_vr.dmi' icon_state = "greblacir" + base_turf = /turf/simulated/mineral/floor/virgo3c /area/submap/groundbase/wilderness/north /area/submap/groundbase/wilderness/north/unexplored icon_state = "orablacir" diff --git a/maps/groundbase/gb-z1.dmm b/maps/groundbase/gb-z1.dmm index 9961fcef26..dc9a91d3c3 100644 --- a/maps/groundbase/gb-z1.dmm +++ b/maps/groundbase/gb-z1.dmm @@ -885,7 +885,7 @@ /obj/random/tech_supply, /obj/random/tech_supply, /obj/random/tech_supply, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled, /area/groundbase/civilian/toolstorage) "cs" = ( @@ -2033,6 +2033,8 @@ /obj/structure/sign/painting/library_secure{ pixel_y = -30 }, +/obj/item/weapon/reagent_containers/food/drinks/shaker, +/obj/item/weapon/reagent_containers/glass/rag, /turf/simulated/floor/lino, /area/groundbase/civilian/cafe) "fk" = ( @@ -4302,6 +4304,11 @@ }, /turf/simulated/floor/tiled/dark, /area/groundbase/security/equipment) +"kP" = ( +/obj/structure/table/glass, +/obj/random/paicard, +/turf/simulated/floor/tiled/dark, +/area/groundbase/civilian/bar) "kQ" = ( /obj/machinery/power/apc{ dir = 8; @@ -5546,7 +5553,7 @@ /area/groundbase/security/lobby) "nU" = ( /obj/structure/table/reinforced, -/obj/item/device/paicard, +/obj/random/paicard, /obj/machinery/recharger{ pixel_y = 5 }, @@ -6658,8 +6665,8 @@ /obj/machinery/cell_charger{ pixel_y = 7 }, -/obj/item/weapon/cell/high, -/obj/item/weapon/cell/high, +/obj/item/weapon/cell/super, +/obj/item/weapon/cell/super, /turf/simulated/floor/tiled, /area/groundbase/cargo/mining) "qG" = ( @@ -7379,7 +7386,7 @@ /area/groundbase/security/lobby) "sq" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /obj/structure/cable/yellow{ icon_state = "2-8" }, @@ -13713,6 +13720,7 @@ /obj/structure/cable/yellow{ icon_state = "0-4" }, +/obj/random/paicard, /turf/simulated/floor/tiled/dark, /area/groundbase/security/hos) "Hw" = ( @@ -14170,6 +14178,7 @@ /obj/machinery/atmospherics/unary/vent_pump/on{ dir = 8 }, +/obj/random/paicard, /turf/simulated/floor/tiled, /area/groundbase/engineering/ce) "Iy" = ( @@ -16174,6 +16183,8 @@ pixel_x = -3; pixel_y = 26 }, +/obj/item/weapon/reagent_containers/food/drinks/shaker, +/obj/item/weapon/reagent_containers/glass/rag, /turf/simulated/floor/lino, /area/groundbase/civilian/bar) "Nh" = ( @@ -17892,6 +17903,12 @@ }, /turf/simulated/floor/outdoors/sidewalk/side/virgo3c, /area/groundbase/level1/centsquare) +"RB" = ( +/obj/effect/landmark{ + name = "carpspawn" + }, +/turf/simulated/floor/water/deep/virgo3c, +/area/groundbase/level1/eastspur) "RC" = ( /obj/machinery/atmospherics/unary/vent_scrubber/on, /obj/machinery/light{ @@ -25353,7 +25370,7 @@ ht ht KR GF -GF +kP oQ uF pj @@ -39104,7 +39121,7 @@ df uH uH eW -eW +RB eW uH uH @@ -39657,7 +39674,7 @@ eW eW eW eW -eW +RB eW eW eW @@ -39688,11 +39705,11 @@ eW eW eW eW +RB eW eW eW -eW -eW +RB eW eW eW @@ -39959,13 +39976,13 @@ eW eW eW eW +RB eW eW eW eW eW -eW -eW +RB eW eW eW @@ -40236,7 +40253,7 @@ uH uH uH eW -eW +RB eW eW eW diff --git a/maps/groundbase/gb-z2.dmm b/maps/groundbase/gb-z2.dmm index 0b91d7cd41..48d050d8cf 100644 --- a/maps/groundbase/gb-z2.dmm +++ b/maps/groundbase/gb-z2.dmm @@ -673,6 +673,7 @@ /obj/machinery/light/small{ dir = 8 }, +/obj/random/paicard, /turf/simulated/floor/wood, /area/groundbase/command/captainq) "bQ" = ( @@ -809,11 +810,8 @@ }, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/machinery/atmospherics/pipe/simple/hidden/supply, -/obj/structure/cable/yellow{ - icon_state = "1-8" - }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "cm" = ( /obj/structure/closet/secure_closet/freezer/meat, /turf/simulated/floor/tiled/eris/cafe, @@ -869,7 +867,7 @@ dir = 8 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "ct" = ( /obj/effect/floor_decal/industrial/outline/yellow, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ @@ -1025,7 +1023,7 @@ "cU" = ( /obj/structure/table/bench/padded, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "cV" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ dir = 8 @@ -2672,7 +2670,7 @@ "hL" = ( /obj/machinery/light, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "hM" = ( /obj/structure/grille, /obj/structure/window/reinforced/full, @@ -2719,7 +2717,7 @@ /area/rnd/xenobiology) "hU" = ( /obj/machinery/button/remote/blast_door{ - id = "rndshutters"; + id = "robshutters"; name = "Privacy Shutter Control"; pixel_y = 30 }, @@ -2766,7 +2764,7 @@ pixel_x = 32 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "hZ" = ( /obj/structure/cable/yellow{ icon_state = "4-8" @@ -2795,7 +2793,7 @@ dir = 8 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "ic" = ( /obj/structure/extinguisher_cabinet{ dir = 8; @@ -2870,7 +2868,7 @@ icon_state = "2-4" }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "ir" = ( /obj/machinery/firealarm{ dir = 8 @@ -2958,7 +2956,7 @@ }, /obj/effect/landmark/start/intern, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "iC" = ( /turf/simulated/wall/r_wall, /area/groundbase/command/hop) @@ -3271,7 +3269,7 @@ dir = 4 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "jv" = ( /obj/machinery/appliance/cooker/grill, /turf/simulated/floor/tiled/eris/cafe, @@ -3450,7 +3448,7 @@ }, /obj/machinery/light, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "jU" = ( /obj/machinery/door/firedoor/glass, /obj/machinery/door/airlock/glass_research{ @@ -3780,7 +3778,7 @@ pixel_y = 5 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "kY" = ( /obj/machinery/power/apc{ dir = 8 @@ -3946,7 +3944,7 @@ }, /obj/effect/landmark/start/visitor, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "lv" = ( /obj/structure/closet/secure_closet/hydroponics{ req_access = list(77) @@ -4446,7 +4444,7 @@ }, /obj/random/coin/sometimes, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "mN" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 9 @@ -4953,7 +4951,9 @@ /turf/simulated/floor/tiled, /area/groundbase/science/xenobot) "oy" = ( -/turf/simulated/floor/outdoors/sidewalk/slab/virgo3c, +/turf/simulated/floor/outdoors/sidewalk/slab/virgo3c{ + outdoors = 0 + }, /area/groundbase/level2/se) "oA" = ( /obj/machinery/door/blast/regular{ @@ -5295,7 +5295,7 @@ /area/groundbase/command/hop) "pA" = ( /obj/structure/table/hardwoodtable, -/obj/item/device/paicard, +/obj/random/paicard, /obj/structure/cable/yellow{ icon_state = "1-2" }, @@ -5491,6 +5491,15 @@ /obj/machinery/atmospherics/pipe/simple/hidden/supply, /turf/simulated/floor/tiled/dark, /area/groundbase/dorms) +"qc" = ( +/obj/structure/railing/grey{ + dir = 1 + }, +/obj/structure/railing/grey{ + dir = 8 + }, +/turf/simulated/open, +/area/groundbase/civilian/bar) "qd" = ( /obj/structure/sign/nanotrasen{ pixel_x = 32; @@ -5768,10 +5777,18 @@ /turf/simulated/floor/tiled, /area/groundbase/science/xenobot) "qQ" = ( -/obj/structure/table/standard, -/obj/structure/noticeboard{ - pixel_y = -32 +/obj/structure/closet/chefcloset, +/obj/item/glass_jar, +/obj/item/device/retail_scanner/civilian, +/obj/item/weapon/soap/nanotrasen, +/obj/item/device/destTagger{ + pixel_x = 4; + pixel_y = 3 }, +/obj/item/weapon/packageWrap, +/obj/item/weapon/packageWrap, +/obj/item/weapon/packageWrap, +/obj/item/weapon/tool/wrench, /turf/simulated/floor/tiled/eris/cafe, /area/groundbase/civilian/kitchen) "qS" = ( @@ -5909,7 +5926,7 @@ /obj/structure/window/reinforced/full, /obj/machinery/door/firedoor, /turf/simulated/floor, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "rm" = ( /obj/machinery/light, /turf/simulated/floor/wood, @@ -6222,7 +6239,7 @@ }, /obj/effect/landmark/start/intern, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "se" = ( /obj/item/device/aicard, /obj/structure/table/reinforced, @@ -6266,7 +6283,7 @@ dir = 8 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "sn" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply, /obj/machinery/light{ @@ -6405,7 +6422,7 @@ dir = 5 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "sI" = ( /obj/structure/disposalpipe/sortjunction/flipped{ dir = 1; @@ -7175,18 +7192,18 @@ /turf/simulated/floor/outdoors/sidewalk/slab/virgo3c, /area/groundbase/level2/northspur) "uU" = ( -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/saltshaker, /obj/item/weapon/reagent_containers/food/condiment/small/saltshaker, /obj/item/weapon/reagent_containers/food/condiment/small/peppermill, @@ -7556,7 +7573,7 @@ /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/effect/landmark/start/visitor, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "we" = ( /obj/structure/cable/yellow{ icon_state = "2-4" @@ -7663,7 +7680,7 @@ }, /obj/machinery/hologram/holopad, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "ww" = ( /obj/structure/cable/yellow{ icon_state = "1-2" @@ -7907,7 +7924,7 @@ icon_state = "1-2" }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "xo" = ( /obj/structure/table/reinforced, /obj/machinery/chemical_dispenser/biochemistry/full, @@ -8105,7 +8122,7 @@ }, /obj/machinery/alarm, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "xN" = ( /obj/machinery/door/blast/regular{ density = 0; @@ -8131,7 +8148,7 @@ /obj/structure/table/glass, /obj/machinery/atmospherics/unary/vent_scrubber/on, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "xU" = ( /obj/structure/grille, /obj/machinery/door/firedoor, @@ -8230,13 +8247,13 @@ }, /obj/effect/landmark/start/intern, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "yh" = ( /obj/structure/cable/yellow{ icon_state = "4-8" }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "yj" = ( /obj/structure/sign/directions/kitchen{ pixel_x = -32 @@ -8424,7 +8441,7 @@ pixel_x = 32 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "yJ" = ( /obj/structure/bed/chair{ dir = 4 @@ -9003,7 +9020,7 @@ pixel_y = 4 }, /obj/item/weapon/circuitboard/teleporter, -/obj/item/device/paicard{ +/obj/random/paicard{ pixel_x = 4 }, /obj/item/device/taperecorder{ @@ -9066,14 +9083,8 @@ "AK" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/machinery/atmospherics/pipe/simple/hidden/supply, -/obj/machinery/power/apc{ - dir = 8 - }, -/obj/structure/cable/yellow{ - icon_state = "0-2" - }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "AL" = ( /obj/effect/floor_decal/industrial/outline/yellow, /turf/simulated/floor/tiled/dark, @@ -9197,7 +9208,7 @@ }, /obj/effect/landmark/start/visitor, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "AT" = ( /turf/simulated/floor/outdoors/grass/virgo3c, /area/groundbase/science/xenobot) @@ -9485,7 +9496,7 @@ icon_state = "4-8" }, /turf/simulated/floor/bmarble, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "BU" = ( /obj/structure/cable/yellow{ icon_state = "4-8" @@ -9627,7 +9638,7 @@ icon_state = "4-8" }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Cj" = ( /obj/structure/sign/directions/stairs_down{ pixel_y = 38 @@ -9737,7 +9748,7 @@ dir = 1 }, /turf/simulated/open, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "CE" = ( /turf/simulated/floor/wood, /area/groundbase/dorms/room8) @@ -10044,7 +10055,7 @@ pixel_x = 25 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Du" = ( /turf/simulated/wall, /area/groundbase/medical/lobby) @@ -10067,7 +10078,7 @@ }, /obj/machinery/camera/network/civilian, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Dy" = ( /obj/structure/table/reinforced, /obj/machinery/requests_console{ @@ -10243,7 +10254,7 @@ dir = 1 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "DV" = ( /obj/structure/table/rack, /obj/item/weapon/tank/jetpack/oxygen, @@ -10305,13 +10316,13 @@ }, /obj/effect/landmark/start/entertainer, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Ea" = ( /obj/structure/table/bench/padded, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/machinery/atmospherics/pipe/simple/hidden/supply, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Eb" = ( /obj/machinery/atmospherics/portables_connector{ dir = 1 @@ -10828,7 +10839,7 @@ /area/groundbase/cargo/office) "FF" = ( /turf/simulated/wall, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "FI" = ( /obj/structure/fence/corner{ dir = 5 @@ -10894,7 +10905,7 @@ }, /obj/machinery/hologram/holopad, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "FU" = ( /obj/structure/table/bench/standard, /obj/effect/landmark/start/rd, @@ -11024,7 +11035,7 @@ icon_state = "1-4" }, /turf/simulated/wall, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Gp" = ( /turf/simulated/wall, /area/groundbase/level2/nw) @@ -11130,6 +11141,12 @@ /obj/machinery/atmospherics/pipe/manifold4w/visible, /turf/simulated/floor/tiled, /area/groundbase/science/xenobot/storage) +"Gz" = ( +/obj/structure/railing/grey{ + dir = 1 + }, +/turf/simulated/open, +/area/groundbase/civilian/bar) "GA" = ( /obj/structure/table/wooden_reinforced, /obj/machinery/atmospherics/unary/vent_scrubber/on{ @@ -11646,6 +11663,14 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/dorms/room7) +"HW" = ( +/obj/machinery/door/firedoor, +/obj/machinery/door/airlock/freezer{ + name = "Kitchen"; + req_access = list(28) + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "HX" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 4 @@ -12250,7 +12275,7 @@ dir = 8 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "JD" = ( /obj/machinery/appliance/cooker/oven, /turf/simulated/floor/tiled/eris/cafe, @@ -13092,7 +13117,7 @@ "LS" = ( /obj/structure/table/glass, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "LT" = ( /obj/machinery/light/small{ dir = 8 @@ -13113,7 +13138,7 @@ dir = 4 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "LW" = ( /obj/structure/sign/xenobio{ plane = -34 @@ -13391,7 +13416,7 @@ icon_state = "1-2" }, /turf/simulated/wall, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "ME" = ( /turf/simulated/wall, /area/groundbase/civilian/chapel/office) @@ -13684,7 +13709,7 @@ /area/groundbase/level2/ne) "Ny" = ( /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Nz" = ( /obj/structure/bed/chair/sofa/pew/right{ dir = 1 @@ -14086,7 +14111,7 @@ /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/machinery/computer/timeclock/premade/east, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "OD" = ( /obj/structure/table/reinforced, /obj/machinery/atmospherics/pipe/simple/hidden/supply, @@ -14521,7 +14546,7 @@ dir = 8 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "PT" = ( /obj/machinery/button/remote/airlock{ dir = 8; @@ -14547,7 +14572,7 @@ dir = 4 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "PV" = ( /turf/simulated/floor/wood, /area/groundbase/dorms/room2) @@ -15049,7 +15074,7 @@ dir = 1 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Rn" = ( /obj/machinery/atmospherics/binary/pump{ dir = 1; @@ -15276,7 +15301,7 @@ }, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "RV" = ( /obj/structure/cable/yellow{ icon_state = "1-8" @@ -15448,20 +15473,10 @@ /turf/simulated/floor/tiled/dark, /area/groundbase/dorms) "Ss" = ( -/obj/structure/closet/chefcloset, -/obj/item/glass_jar, -/obj/item/device/retail_scanner/civilian, -/obj/item/weapon/soap/nanotrasen, -/obj/item/device/destTagger{ - pixel_x = 4; - pixel_y = 3 +/turf/simulated/floor/outdoors/sidewalk/slab/virgo3c{ + outdoors = 0 }, -/obj/item/weapon/packageWrap, -/obj/item/weapon/packageWrap, -/obj/item/weapon/packageWrap, -/obj/item/weapon/tool/wrench, -/turf/simulated/floor/tiled/eris/cafe, -/area/groundbase/civilian/kitchen) +/area/groundbase/level2/nw) "St" = ( /obj/structure/fence/corner{ dir = 8 @@ -15963,7 +15978,7 @@ /obj/machinery/light, /obj/effect/landmark/start/entertainer, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "TO" = ( /obj/structure/bed/chair/office/dark, /obj/effect/landmark/start/cargo, @@ -15981,7 +15996,7 @@ dir = 4 }, /turf/simulated/floor/bmarble, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "TT" = ( /obj/structure/railing/grey{ dir = 8 @@ -16031,6 +16046,10 @@ }, /turf/simulated/floor/tiled, /area/rnd/xenobiology) +"Uc" = ( +/obj/structure/stairs/spawner/south, +/turf/simulated/floor/outdoors/sidewalk/slab/virgo3c, +/area/groundbase/level2/nw) "Ud" = ( /turf/simulated/floor/tiled/white, /area/groundbase/medical/lobby) @@ -16072,6 +16091,9 @@ }, /turf/simulated/floor/tiled, /area/groundbase/science/xenobot/storage) +"Ui" = ( +/turf/simulated/open, +/area/groundbase/civilian/bar) "Uj" = ( /obj/machinery/door/blast/regular{ id = "xenobiodiv4"; @@ -16094,7 +16116,7 @@ icon_state = "1-8" }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Un" = ( /obj/machinery/atmospherics/pipe/simple/hidden/cyan{ dir = 10 @@ -16158,14 +16180,14 @@ "Ux" = ( /obj/machinery/hologram/holopad, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Uy" = ( /obj/machinery/light{ dir = 4 }, /obj/random/vendorall, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Uz" = ( /obj/structure/cable/yellow{ icon_state = "4-8" @@ -16749,7 +16771,7 @@ dir = 4 }, /turf/simulated/wall, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Wd" = ( /obj/structure/railing/grey{ dir = 8 @@ -16801,7 +16823,7 @@ }, /obj/effect/landmark/start/entertainer, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Wj" = ( /turf/simulated/wall, /area/groundbase/cargo/office) @@ -16887,6 +16909,7 @@ dir = 4; pixel_x = -30 }, +/obj/random/paicard, /turf/simulated/floor/tiled, /area/groundbase/science/rd) "Wz" = ( @@ -17110,7 +17133,7 @@ /obj/structure/table/bench/padded, /obj/effect/landmark/start/visitor, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Xl" = ( /obj/machinery/atmospherics/unary/vent_scrubber/on{ dir = 8 @@ -17202,7 +17225,7 @@ pixel_x = -25 }, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "XB" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /obj/machinery/atmospherics/pipe/manifold/hidden/supply{ @@ -17417,7 +17440,7 @@ }, /obj/machinery/firealarm, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Yg" = ( /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 6 @@ -17441,7 +17464,7 @@ /obj/machinery/atmospherics/pipe/simple/hidden/supply, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Ym" = ( /obj/machinery/atmospherics/pipe/manifold/hidden/scrubbers{ dir = 1 @@ -17473,7 +17496,7 @@ "Yr" = ( /obj/structure/sign/department/bar, /turf/simulated/wall, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "Ys" = ( /obj/machinery/atmospherics/unary/vent_scrubber/on{ dir = 1 @@ -17612,7 +17635,7 @@ /obj/machinery/atmospherics/pipe/simple/hidden/supply, /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /turf/simulated/floor/tiled/dark, -/area/groundbase/civilian/bar/upper) +/area/groundbase/civilian/bar) "YS" = ( /obj/machinery/biogenerator, /turf/simulated/floor/tiled, @@ -18466,7 +18489,7 @@ UH UH UH wS -Hn +Ss Kl Kl xE @@ -18608,7 +18631,7 @@ UH UH wS wS -Hn +Ss Kl Kl xE @@ -21477,11 +21500,11 @@ il il LA bY -Jc -Jc -Jc bY bY +Jc +Jc +Jc bY bY bY @@ -21617,12 +21640,12 @@ bY bY bY bY -bY -bY -bY -bY -bY -bY +Hn +Ja +Hn +Hn +Uc +Gp bY bY bY @@ -21761,7 +21784,7 @@ Vl Vl Vl Vl -Vl +HW Vl Vl JJ @@ -21903,7 +21926,7 @@ Oz Ej ep LD -Ss +Gc Wp JO JJ @@ -23452,7 +23475,7 @@ Bb Bb Iz yh -GS +qc CD FF bY @@ -23594,8 +23617,8 @@ Ny Ny Ny yh -YO -Gl +Gz +Ui rl bY sZ @@ -28289,7 +28312,7 @@ fp to to to -VR +fp fp fp fp diff --git a/maps/groundbase/gb-z3.dmm b/maps/groundbase/gb-z3.dmm index f8b3d9e6fa..95d87ed2be 100644 --- a/maps/groundbase/gb-z3.dmm +++ b/maps/groundbase/gb-z3.dmm @@ -19,6 +19,12 @@ }, /turf/simulated/floor/carpet, /area/groundbase/medical/cmo) +"aN" = ( +/obj/item/weapon/stool/padded{ + dir = 4 + }, +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "aQ" = ( /obj/structure/table/glass, /obj/item/weapon/folder/white_cmo, @@ -121,6 +127,12 @@ }, /turf/simulated/floor/outdoors/sidewalk/slab/virgo3c, /area/groundbase/level3/escapepad) +"cD" = ( +/obj/structure/railing{ + dir = 4 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "cE" = ( /obj/structure/bed/chair/shuttle{ dir = 8 @@ -291,6 +303,11 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/medical/uhallway) +"fn" = ( +/obj/structure/table/steel, +/obj/random/paicard, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "fu" = ( /obj/structure/table/bench/marble, /obj/effect/landmark/start/chemist, @@ -393,6 +410,11 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/medical/uhallway) +"gV" = ( +/obj/structure/railing, +/obj/structure/table/steel, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "gZ" = ( /obj/structure/cable/yellow{ icon_state = "1-4" @@ -418,6 +440,13 @@ }, /turf/simulated/floor/tiled, /area/shuttle/groundbase/exploration) +"hM" = ( +/obj/structure/railing, +/obj/structure/bed/chair/backed_red{ + dir = 4 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "hN" = ( /obj/structure/table/bench/marble, /obj/effect/landmark/start/medical, @@ -697,6 +726,16 @@ }, /turf/simulated/wall/r_wall, /area/groundbase/level3/escapepad) +"mS" = ( +/obj/structure/railing{ + dir = 1 + }, +/obj/structure/table/steel, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) +"mT" = ( +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "mU" = ( /obj/structure/cable/yellow{ icon_state = "1-4" @@ -712,6 +751,12 @@ }, /turf/simulated/floor/outdoors/sidewalk/slab/virgo3c, /area/groundbase/level3/nw) +"mY" = ( +/obj/machinery/light{ + dir = 8 + }, +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "nb" = ( /obj/structure/grille, /obj/structure/window/reinforced/polarized/full{ @@ -720,6 +765,15 @@ /obj/machinery/door/firedoor, /turf/simulated/floor, /area/groundbase/medical/cmo) +"nh" = ( +/obj/structure/railing{ + dir = 8 + }, +/obj/machinery/light{ + dir = 1 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "nq" = ( /obj/structure/railing/grey{ dir = 1 @@ -766,8 +820,18 @@ dir = 1 }, /obj/machinery/light, +/obj/random/paicard, /turf/simulated/floor/wood, /area/groundbase/medical/cmo) +"nD" = ( +/obj/structure/railing{ + dir = 1 + }, +/obj/structure/bed/chair/backed_red{ + dir = 4 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "nN" = ( /obj/machinery/light, /obj/effect/landmark/start/explorer, @@ -824,6 +888,12 @@ /obj/effect/overmap/visitable/sector/virgo3c, /turf/unsimulated/wall/planetary/virgo3c, /area/groundbase/level3/sw) +"oI" = ( +/obj/machinery/light{ + dir = 1 + }, +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "oK" = ( /obj/structure/grille, /obj/structure/window/reinforced/full, @@ -877,6 +947,10 @@ /obj/machinery/atmospherics/unary/vent_scrubber/on, /turf/simulated/floor/tiled/white, /area/groundbase/medical/patient4) +"pa" = ( +/obj/item/weapon/stool/padded, +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "pd" = ( /obj/machinery/portable_atmospherics/canister/oxygen, /obj/machinery/alarm{ @@ -945,6 +1019,12 @@ edge_blending_priority = -1 }, /area/groundbase/exploration/shuttlepad) +"pP" = ( +/obj/machinery/light{ + dir = 4 + }, +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "pY" = ( /obj/structure/sign/nanotrasen, /turf/simulated/wall, @@ -1025,6 +1105,13 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/medical/patient1) +"rr" = ( +/obj/structure/railing, +/obj/machinery/light{ + dir = 8 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "rD" = ( /obj/structure/bookcase, /obj/machinery/atmospherics/unary/vent_pump/on, @@ -1051,6 +1138,13 @@ }, /turf/simulated/floor/tiled, /area/groundbase/exploration/equipment) +"sa" = ( +/obj/structure/railing{ + dir = 8 + }, +/obj/machinery/light, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "sb" = ( /obj/machinery/power/apc{ dir = 8 @@ -1141,6 +1235,12 @@ "tZ" = ( /turf/simulated/mineral/cave/virgo3c, /area/groundbase/level3/se) +"ud" = ( +/obj/structure/bed/chair/backed_red{ + dir = 8 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "ui" = ( /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers, /turf/simulated/floor/tiled/white, @@ -1228,6 +1328,15 @@ /obj/structure/bed/chair/sofa/corp/left, /turf/simulated/floor/carpet/retro, /area/groundbase/dorms/bathroom) +"vJ" = ( +/obj/structure/railing{ + dir = 1 + }, +/obj/machinery/light{ + dir = 4 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "vV" = ( /obj/structure/bed/chair/shuttle{ dir = 8 @@ -1392,6 +1501,10 @@ /obj/machinery/atmospherics/pipe/manifold/hidden/supply, /turf/simulated/floor/tiled/white, /area/groundbase/medical/paramedic) +"yu" = ( +/obj/machinery/light, +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "yy" = ( /obj/structure/dispenser{ phorontanks = 0 @@ -1529,6 +1642,9 @@ }, /turf/simulated/floor/wood, /area/groundbase/dorms/bathroom) +"AZ" = ( +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Bd" = ( /obj/effect/floor_decal/industrial/warning{ dir = 1 @@ -1591,6 +1707,12 @@ "BH" = ( /turf/simulated/wall, /area/groundbase/medical/patient3) +"BM" = ( +/obj/structure/catwalk, +/turf/simulated/floor/virgo3c{ + edge_blending_priority = -1 + }, +/area/groundbase/level3/nw/open) "Ck" = ( /obj/structure/table/woodentable, /obj/machinery/computer/security/telescreen/entertainment{ @@ -1598,6 +1720,12 @@ }, /turf/simulated/floor/carpet/retro, /area/groundbase/dorms/bathroom) +"Ct" = ( +/obj/structure/bed/chair/backed_red{ + dir = 4 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Cv" = ( /obj/structure/bed/padded, /obj/item/weapon/bedsheet/medical, @@ -1722,6 +1850,15 @@ }, /turf/simulated/floor/tiled, /area/groundbase/exploration/equipment) +"Em" = ( +/obj/structure/railing{ + dir = 1 + }, +/obj/machinery/light{ + dir = 8 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Es" = ( /obj/structure/closet/secure_closet/pathfinder{ req_access = list(62,43,67) @@ -1830,6 +1967,12 @@ "Fh" = ( /turf/simulated/floor/wood, /area/groundbase/dorms/bathroom) +"Fr" = ( +/obj/item/weapon/stool/padded{ + dir = 1 + }, +/turf/simulated/floor/wmarble, +/area/groundbase/civilian/kitchen) "Fx" = ( /obj/structure/cable/yellow{ icon_state = "4-8" @@ -1862,6 +2005,9 @@ }, /turf/simulated/floor/bmarble, /area/groundbase/civilian/pilot) +"FU" = ( +/turf/simulated/floor/lino, +/area/groundbase/civilian/kitchen) "FV" = ( /obj/item/weapon/storage/backpack/parachute{ pixel_x = 4; @@ -1912,6 +2058,13 @@ /obj/machinery/light/bigfloorlamp, /turf/simulated/floor/outdoors/grass/virgo3c, /area/groundbase/level3/escapepad) +"Gk" = ( +/obj/structure/railing{ + dir = 4 + }, +/obj/machinery/light, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Gn" = ( /obj/effect/floor_decal/industrial/warning{ dir = 1 @@ -1942,6 +2095,9 @@ "GW" = ( /turf/simulated/floor/outdoors/sidewalk/virgo3c, /area/groundbase/level3/escapepad) +"Ha" = ( +/turf/simulated/wall, +/area/groundbase/civilian/kitchen) "Hm" = ( /turf/simulated/floor/reinforced/virgo3c{ edge_blending_priority = -1 @@ -2088,6 +2244,15 @@ "IS" = ( /turf/simulated/open/virgo3c, /area/groundbase/level3/ne/open) +"IW" = ( +/obj/structure/railing{ + dir = 1 + }, +/obj/structure/bed/chair/backed_red{ + dir = 8 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Jf" = ( /obj/machinery/hologram/holopad, /turf/simulated/floor/tiled/virgo3c, @@ -2179,6 +2344,10 @@ "KG" = ( /turf/simulated/floor/tiled, /area/groundbase/exploration/equipment) +"KN" = ( +/obj/structure/table/steel, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "KZ" = ( /obj/structure/cable/yellow{ icon_state = "4-8" @@ -2226,6 +2395,12 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/medical/patient3) +"Ms" = ( +/obj/structure/railing{ + dir = 8 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Mz" = ( /obj/structure/railing{ dir = 4 @@ -2294,6 +2469,12 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/medical/paramedic) +"NR" = ( +/obj/structure/sink/kitchen{ + pixel_y = 16 + }, +/turf/simulated/floor/lino, +/area/groundbase/civilian/kitchen) "NT" = ( /obj/machinery/alarm{ dir = 8 @@ -2408,6 +2589,10 @@ /obj/machinery/atmospherics/pipe/simple/hidden/supply, /turf/simulated/floor/tiled/white, /area/groundbase/medical/uhallway) +"Pq" = ( +/obj/structure/table/steel, +/turf/simulated/floor/lino, +/area/groundbase/civilian/kitchen) "Pr" = ( /obj/machinery/button/remote/airlock{ dir = 8; @@ -2488,6 +2673,15 @@ "Qa" = ( /turf/simulated/floor/outdoors/grass/forest/virgo3c, /area/groundbase/level3/nw) +"Qb" = ( +/obj/structure/railing{ + dir = 4 + }, +/obj/machinery/light{ + dir = 1 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Qe" = ( /obj/structure/table/bench/marble, /obj/structure/cable/yellow{ @@ -2523,6 +2717,13 @@ outdoors = 0 }, /area/groundbase/level3/escapepad) +"Qs" = ( +/obj/structure/railing, +/obj/structure/bed/chair/backed_red{ + dir = 8 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "QC" = ( /obj/machinery/door/airlock/multi_tile/glass/polarized{ id_tag = null; @@ -2582,6 +2783,12 @@ "Rq" = ( /turf/simulated/wall/r_wall, /area/groundbase/dorms/bathroom) +"RH" = ( +/obj/structure/railing{ + dir = 1 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "RM" = ( /obj/machinery/photocopier/faxmachine{ department = "Exploration" @@ -2876,6 +3083,10 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/medical/patient1) +"Wh" = ( +/obj/structure/railing, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "Wm" = ( /obj/structure/closet/secure_closet/pilot, /obj/machinery/light{ @@ -2912,6 +3123,13 @@ "Xv" = ( /turf/simulated/floor/outdoors/grass/forest/virgo3c, /area/groundbase/unexplored/outdoors) +"Xz" = ( +/obj/structure/railing, +/obj/machinery/light{ + dir = 4 + }, +/turf/simulated/floor/bmarble, +/area/groundbase/civilian/kitchen) "XB" = ( /obj/structure/bed/padded, /obj/item/weapon/bedsheet/medical, @@ -2952,6 +3170,11 @@ }, /turf/simulated/floor/tiled/white, /area/groundbase/dorms/bathroom) +"YG" = ( +/turf/simulated/floor/virgo3c{ + edge_blending_priority = -1 + }, +/area/groundbase/civilian/kitchen) "YJ" = ( /obj/structure/cable/yellow{ icon_state = "2-8" @@ -5979,7 +6202,7 @@ QD QD QD QD -Kr +ly Kr pf pf @@ -6121,7 +6344,7 @@ QD QD QD QD -Kr +ly Kr pf pf @@ -6263,7 +6486,7 @@ QD QD QD QD -Kr +ly Kr Kr Kr @@ -6401,6 +6624,7 @@ Kr Kr Kr Kr +Kr QO By By @@ -6410,7 +6634,6 @@ Kr Kr Kr Kr -Kr pf pf pf @@ -6543,6 +6766,7 @@ Kr Kr Kr Kr +Kr QO By By @@ -6552,7 +6776,6 @@ Kr Kr Kr Kr -Kr pf pf pf @@ -6685,8 +6908,9 @@ Kr Kr Kr Kr -QO -By +Kr +QD +BM By ly Kr @@ -6694,7 +6918,6 @@ Kr Kr Kr Kr -Kr pf pf pf @@ -6819,22 +7042,22 @@ nU Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG Kr Kr pf @@ -6961,22 +7184,22 @@ pe Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +Ha +nh +Ms +Ms +Ms +Ms +Ms +Ms +Ms +AZ +AZ +Ms +sa +Ha +YG Kr Kr pf @@ -7103,22 +7326,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +Em +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +rr +YG Kr Kr pf @@ -7245,22 +7468,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +nD +Ct +Ct +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +Ct +Ct +hM +YG Kr Kr pf @@ -7387,22 +7610,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +mS +KN +KN +mT +mT +mT +mT +mT +mT +mT +mT +KN +KN +gV +YG Kr Kr pf @@ -7529,22 +7752,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +IW +ud +ud +mT +mT +pP +aN +aN +pP +mT +mT +ud +ud +Qs +YG Kr Kr pf @@ -7671,22 +7894,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +RH +AZ +AZ +mT +yu +Ha +Pq +Pq +Ha +oI +mT +AZ +AZ +Wh +YG Kr Kr pf @@ -7813,22 +8036,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +RH +AZ +AZ +mT +pa +Pq +FU +FU +Pq +Fr +mT +AZ +AZ +Wh +YG Kr Kr pf @@ -7955,22 +8178,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +RH +AZ +AZ +mT +pa +Pq +FU +FU +Pq +Fr +mT +AZ +AZ +Wh +YG Kr Kr pf @@ -8097,22 +8320,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +RH +AZ +AZ +mT +pa +Pq +FU +FU +Pq +Fr +mT +AZ +AZ +Wh +YG Kr Kr pf @@ -8239,22 +8462,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +RH +AZ +AZ +mT +yu +Ha +NR +FU +Ha +oI +mT +AZ +AZ +Wh +YG Kr Kr pf @@ -8381,22 +8604,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +nD +Ct +Ct +mT +mT +mY +mT +mT +mY +mT +mT +Ct +Ct +hM +YG Kr Kr pf @@ -8523,22 +8746,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +mS +fn +KN +mT +mT +mT +mT +mT +mT +mT +mT +KN +KN +gV +YG Kr Kr pf @@ -8665,22 +8888,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +IW +ud +ud +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +ud +ud +Qs +YG Kr Kr pf @@ -8807,22 +9030,22 @@ BE Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +vJ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +AZ +Xz +YG Kr Kr pf @@ -8949,22 +9172,22 @@ pe Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +Ha +Qb +cD +cD +cD +cD +cD +cD +AZ +AZ +cD +cD +Gk +Ha +YG Kr Kr pf @@ -9091,22 +9314,22 @@ VR Kr Kr Kr -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD -QD +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG +YG Kr Kr pf @@ -14891,9 +15114,9 @@ Lf Lf Lf Lf -Lf -Lf -Lf +IS +IS +IS IS IS IS @@ -15033,9 +15256,9 @@ Lf Lf Lf Lf -Lf -Lf -Lf +IS +IS +IS IS IS IS @@ -15175,9 +15398,9 @@ Lf Lf Lf Lf -Lf -Lf -Lf +IS +IS +IS IS IS IS @@ -15317,9 +15540,9 @@ Lf Lf Lf Lf -Lf -Lf -Lf +IS +IS +IS IS IS IS diff --git a/maps/groundbase/groundbase_poi_stuff.dm b/maps/groundbase/groundbase_poi_stuff.dm index c2ff36f9bc..23826a464a 100644 --- a/maps/groundbase/groundbase_poi_stuff.dm +++ b/maps/groundbase/groundbase_poi_stuff.dm @@ -965,6 +965,7 @@ ambience = AMBIENCE_OUTPOST flags = AREA_FLAG_IS_NOT_PERSISTENT requires_power = 0 + base_turf = /turf/simulated/mineral/floor/virgo3c /area/submap/groundbase/poi/wildvillage/plot icon_state = "grewhisqu" diff --git a/maps/groundbase/southwilds/southwilds1.dmm b/maps/groundbase/southwilds/southwilds1.dmm index f8fe87f859..f2c050a458 100644 --- a/maps/groundbase/southwilds/southwilds1.dmm +++ b/maps/groundbase/southwilds/southwilds1.dmm @@ -10,6 +10,12 @@ edge_blending_priority = -1 }, /area/submap/groundbase/wilderness/south) +"o" = ( +/obj/effect/landmark{ + name = "carpspawn" + }, +/turf/simulated/floor/water/deep/virgo3c, +/area/submap/groundbase/wilderness/south) "p" = ( /turf/simulated/floor/outdoors/grass/virgo3c, /area/submap/groundbase/wilderness/south) @@ -34,6 +40,12 @@ "H" = ( /turf/simulated/floor/outdoors/grass/virgo3c, /area/submap/groundbase/wilderness/south/unexplored) +"I" = ( +/obj/effect/landmark{ + name = "carpspawn" + }, +/turf/simulated/floor/water/virgo3c, +/area/submap/groundbase/wilderness/south) "K" = ( /obj/effect/map_effect/portal/line/side_b, /turf/simulated/floor/outdoors/grass/forest/virgo3c, @@ -1243,7 +1255,7 @@ d d c c -F +o c c c @@ -4771,7 +4783,7 @@ H d d c -c +I c F F @@ -4917,7 +4929,7 @@ c c F F -F +o c c c @@ -6511,7 +6523,7 @@ d d c c -c +I c c c @@ -7085,7 +7097,7 @@ c c c c -F +o F c c @@ -10217,7 +10229,7 @@ d c c c -F +o c c c @@ -11761,7 +11773,7 @@ d c F F -c +I c d d @@ -12927,7 +12939,7 @@ d d c c -c +I c c c @@ -13156,7 +13168,7 @@ c c c c -c +I c c d @@ -13784,7 +13796,7 @@ d d c c -c +I c c c @@ -13931,7 +13943,7 @@ d c c c -F +o F F c @@ -14023,7 +14035,7 @@ F F F F -F +o F F F @@ -14878,7 +14890,7 @@ c c F F -F +o F c c @@ -15234,7 +15246,7 @@ d d c F -F +o c c c @@ -16689,7 +16701,7 @@ d c c F -F +o c c d @@ -17687,7 +17699,7 @@ H d c c -F +o c c d @@ -18497,7 +18509,7 @@ d c c F -F +o F F F @@ -18643,7 +18655,7 @@ F F F F -F +o F c c diff --git a/maps/groundbase/southwilds/villagepois/square7.dmm b/maps/groundbase/southwilds/villagepois/square7.dmm index 5cc3728a75..e92257e2a1 100644 --- a/maps/groundbase/southwilds/villagepois/square7.dmm +++ b/maps/groundbase/southwilds/villagepois/square7.dmm @@ -119,18 +119,18 @@ /turf/simulated/floor/tiled/eris/cafe, /area/submap/groundbase/poi/wildvillage/square/square7) "G" = ( -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/saltshaker, /obj/item/weapon/reagent_containers/food/condiment/small/saltshaker, /obj/item/weapon/reagent_containers/food/condiment/small/peppermill, diff --git a/maps/offmap_vr/om_ships/abductor.dmm b/maps/offmap_vr/om_ships/abductor.dmm index 11f3098ca6..c96d1b8994 100644 --- a/maps/offmap_vr/om_ships/abductor.dmm +++ b/maps/offmap_vr/om_ships/abductor.dmm @@ -105,8 +105,8 @@ /area/abductor/interior) "hj" = ( /obj/structure/table/alien, -/obj/item/device/paicard, -/obj/item/device/paicard, +/obj/random/paicard, +/obj/random/paicard, /turf/simulated/shuttle/floor/alienplating, /area/abductor/interior) "hq" = ( diff --git a/maps/offmap_vr/om_ships/itglight.dmm b/maps/offmap_vr/om_ships/itglight.dmm index 99c70134b5..8a11ecf0b9 100644 --- a/maps/offmap_vr/om_ships/itglight.dmm +++ b/maps/offmap_vr/om_ships/itglight.dmm @@ -1985,16 +1985,16 @@ /turf/simulated/floor, /area/itglight/portengi) "lO" = ( -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/sugar, @@ -2003,10 +2003,10 @@ /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/material/knife/butch, /obj/item/weapon/material/minihoe, /obj/item/weapon/material/knife/machete/hatchet, @@ -6353,7 +6353,7 @@ /area/itglight/common) "MF" = ( /obj/structure/table/wooden_reinforced, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/glass/reinforced, /area/itglight/common) "MG" = ( diff --git a/maps/offmap_vr/talon/talon_v2.dmm b/maps/offmap_vr/talon/talon_v2.dmm index 43f02d7f6e..368368c3a9 100644 --- a/maps/offmap_vr/talon/talon_v2.dmm +++ b/maps/offmap_vr/talon/talon_v2.dmm @@ -190,7 +190,7 @@ /obj/machinery/light/small{ dir = 1 }, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled/techfloor, /area/talon_v2/workroom) "aw" = ( @@ -15549,7 +15549,7 @@ /area/talon_v2/crew_quarters/eng_room) "Xi" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/wood, /area/talon_v2/crew_quarters/meditation) "Xl" = ( diff --git a/maps/stellardelight/stellar_delight1.dmm b/maps/stellardelight/stellar_delight1.dmm index c4e5b2dfaa..6e65aa5629 100644 --- a/maps/stellardelight/stellar_delight1.dmm +++ b/maps/stellardelight/stellar_delight1.dmm @@ -21786,7 +21786,7 @@ /area/library) "Uj" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /obj/machinery/atmospherics/pipe/simple/hidden/supply{ dir = 4 }, diff --git a/maps/stellardelight/stellar_delight2.dmm b/maps/stellardelight/stellar_delight2.dmm index 61649271d2..56fef9d146 100644 --- a/maps/stellardelight/stellar_delight2.dmm +++ b/maps/stellardelight/stellar_delight2.dmm @@ -5862,7 +5862,7 @@ /area/maintenance/stellardelight/deck2/atmos) "mJ" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled/eris/steel/brown_platform, /area/crew_quarters/bar) "mK" = ( @@ -7931,18 +7931,18 @@ pixel_x = -1; pixel_y = -42 }, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /obj/item/weapon/reagent_containers/food/condiment/spacespice, /obj/item/weapon/reagent_containers/food/condiment/spacespice, -/obj/item/weapon/reagent_containers/food/condiment/sugar, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /obj/item/weapon/reagent_containers/food/condiment/small/saltshaker, /obj/item/weapon/reagent_containers/food/condiment/small/saltshaker, /obj/item/weapon/reagent_containers/food/condiment/small/peppermill, @@ -8066,7 +8066,7 @@ /obj/random/tech_supply, /obj/random/tech_supply, /obj/random/tech_supply, -/obj/item/device/paicard, +/obj/random/paicard, /obj/machinery/atmospherics/unary/vent_pump/on, /obj/machinery/newscaster{ pixel_y = 28 @@ -21981,7 +21981,7 @@ /area/medical/reception) "WR" = ( /obj/structure/table/wooden_reinforced, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled/eris/dark/cargo, /area/crew_quarters/recreation_area) "WS" = ( diff --git a/maps/stellardelight/stellar_delight3.dmm b/maps/stellardelight/stellar_delight3.dmm index 067cf7585b..418fa680b0 100644 --- a/maps/stellardelight/stellar_delight3.dmm +++ b/maps/stellardelight/stellar_delight3.dmm @@ -1606,7 +1606,7 @@ /area/maintenance/stellardelight/deck3/foreportroomb) "gk" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/wood, /area/stellardelight/deck3/cafe) "gl" = ( @@ -5054,7 +5054,7 @@ /area/space) "sI" = ( /obj/structure/table/standard, -/obj/item/device/paicard, +/obj/random/paicard, /obj/machinery/atmospherics/unary/vent_scrubber/on, /turf/simulated/floor/bluegrid, /area/ai_cyborg_station) @@ -10254,7 +10254,7 @@ /turf/simulated/floor, /area/maintenance/stellardelight/deck3/starboardaft) "Lm" = ( -/obj/item/device/paicard, +/obj/random/paicard, /obj/structure/table/glass, /obj/machinery/camera/network/civilian{ dir = 8 @@ -13710,7 +13710,7 @@ name = "station intercom (Science)"; pixel_y = 24 }, -/obj/item/device/paicard{ +/obj/random/paicard{ pixel_x = 4 }, /obj/item/weapon/paper_bin{ diff --git a/maps/submaps/admin_use_vr/dojo.dmm b/maps/submaps/admin_use_vr/dojo.dmm index fd50bcf52f..a2bd85f37f 100644 --- a/maps/submaps/admin_use_vr/dojo.dmm +++ b/maps/submaps/admin_use_vr/dojo.dmm @@ -432,7 +432,7 @@ /area/ninja_dojo/dojo) "bc" = ( /obj/structure/table/steel_reinforced, -/obj/item/device/paicard, +/obj/random/paicard, /obj/item/device/pda/syndicate, /turf/simulated/shuttle/floor/voidcraft/light, /area/shuttle/ninja) diff --git a/maps/submaps/admin_use_vr/skipjack.dmm b/maps/submaps/admin_use_vr/skipjack.dmm index 2c252f6ad8..2e12a90bc4 100644 --- a/maps/submaps/admin_use_vr/skipjack.dmm +++ b/maps/submaps/admin_use_vr/skipjack.dmm @@ -14,7 +14,7 @@ /area/skipjack_station) "ad" = ( /obj/structure/table/standard, -/obj/item/device/paicard, +/obj/random/paicard, /turf/unsimulated/floor{ icon_state = "steel" }, diff --git a/maps/submaps/admin_use_vr/wizard.dmm b/maps/submaps/admin_use_vr/wizard.dmm index b85af43205..aa97a3f082 100644 --- a/maps/submaps/admin_use_vr/wizard.dmm +++ b/maps/submaps/admin_use_vr/wizard.dmm @@ -240,7 +240,7 @@ /area/wizard_station) "aB" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /turf/unsimulated/floor{ dir = 8; icon_state = "wood" diff --git a/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm b/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm index c641f359b8..5e8cc12496 100644 --- a/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm +++ b/maps/submaps/surface_submaps/mountains/quarantineshuttle.dmm @@ -344,7 +344,7 @@ }, /area/submap/cave/qShuttle) "aX" = ( -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/shuttle/floor{ icon_state = "floor_yellow" }, diff --git a/maps/submaps/surface_submaps/plains/Diner.dmm b/maps/submaps/surface_submaps/plains/Diner.dmm index 69f2b63996..d6fa3316b4 100644 --- a/maps/submaps/surface_submaps/plains/Diner.dmm +++ b/maps/submaps/surface_submaps/plains/Diner.dmm @@ -43,7 +43,7 @@ /turf/simulated/floor/tiled/white, /area/submap/Diner) "ak" = ( -/obj/machinery/vending/cola, +/obj/machinery/vending/hotfood, /turf/simulated/floor/tiled/white, /area/submap/Diner) "al" = ( @@ -113,8 +113,8 @@ "aw" = ( /obj/machinery/appliance/mixer/cereal, /obj/machinery/light{ - icon_state = "tube1"; - dir = 4 + dir = 4; + icon_state = "tube1" }, /turf/simulated/floor/tiled/white, /area/submap/Diner) @@ -134,7 +134,7 @@ /obj/item/weapon/reagent_containers/food/drinks/bottle/cream, /obj/item/weapon/reagent_containers/food/drinks/bottle/cream, /obj/item/weapon/reagent_containers/food/drinks/bottle/cream, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /turf/simulated/floor/tiled/white, /area/submap/Diner) "az" = ( @@ -180,13 +180,13 @@ /area/submap/Diner) "aI" = ( /obj/structure/closet/crate/freezer, -/obj/item/weapon/reagent_containers/food/snacks/meat, -/obj/item/weapon/reagent_containers/food/snacks/meat, -/obj/item/weapon/reagent_containers/food/snacks/meat, -/obj/item/weapon/reagent_containers/food/snacks/meat, -/obj/item/weapon/reagent_containers/food/snacks/meat, -/obj/item/weapon/reagent_containers/food/snacks/meat, -/obj/item/weapon/reagent_containers/food/snacks/meat, +/obj/random/meat, +/obj/random/meat, +/obj/random/meat, +/obj/random/meat, +/obj/random/meat, +/obj/random/meat, +/obj/random/meat, /turf/simulated/floor/tiled/freezer, /area/submap/Diner) "aJ" = ( @@ -198,8 +198,8 @@ "aK" = ( /obj/effect/floor_decal/corner/red/diagonal, /obj/machinery/light{ - icon_state = "tube1"; - dir = 4 + dir = 4; + icon_state = "tube1" }, /turf/simulated/floor/tiled/white, /area/submap/Diner) @@ -250,13 +250,13 @@ /area/submap/Diner) "aQ" = ( /obj/structure/closet/crate/freezer, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /turf/simulated/floor/tiled/freezer, /area/submap/Diner) "aR" = ( @@ -317,8 +317,8 @@ /area/submap/Diner) "bc" = ( /obj/machinery/light{ - icon_state = "tube1"; - dir = 4 + dir = 4; + icon_state = "tube1" }, /turf/simulated/floor/tiled/white, /area/submap/Diner) @@ -361,6 +361,11 @@ "bi" = ( /turf/simulated/floor/lino, /area/submap/Diner) +"bj" = ( +/obj/structure/table/standard, +/obj/random/mug, +/turf/simulated/floor/tiled/white, +/area/submap/Diner) "bk" = ( /obj/machinery/light/small{ dir = 8 @@ -374,6 +379,7 @@ /area/submap/Diner) "bm" = ( /obj/structure/table/woodentable, +/obj/random/mug, /turf/simulated/floor/lino, /area/submap/Diner) "bn" = ( @@ -389,8 +395,8 @@ "bp" = ( /obj/effect/floor_decal/corner/red/diagonal, /obj/structure/windoor_assembly{ - icon_state = "l_windoor_assembly01"; - dir = 2 + dir = 2; + icon_state = "l_windoor_assembly01" }, /turf/simulated/floor/tiled/white, /area/submap/Diner) @@ -406,8 +412,8 @@ /area/submap/Diner) "bs" = ( /obj/structure/sink{ - icon_state = "sink"; dir = 8; + icon_state = "sink"; pixel_x = -12; pixel_y = 2 }, @@ -452,6 +458,7 @@ /area/submap/Diner) "bA" = ( /obj/structure/table/standard, +/obj/random/soap/anom, /turf/simulated/floor/tiled/hydro, /area/submap/Diner) @@ -863,7 +870,7 @@ aa (16,1,1) = {" aa ad -ah +bj ap ap ap diff --git a/maps/submaps/surface_submaps/plains/Diner_vr.dmm b/maps/submaps/surface_submaps/plains/Diner_vr.dmm index 1ef47715cb..a2eedcc00d 100644 --- a/maps/submaps/surface_submaps/plains/Diner_vr.dmm +++ b/maps/submaps/surface_submaps/plains/Diner_vr.dmm @@ -130,7 +130,7 @@ /obj/item/weapon/reagent_containers/food/drinks/bottle/cream, /obj/item/weapon/reagent_containers/food/drinks/bottle/cream, /obj/item/weapon/reagent_containers/food/drinks/bottle/cream, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /turf/simulated/floor/tiled/white, /area/submap/Diner) "az" = ( @@ -245,13 +245,13 @@ /area/submap/Diner) "aQ" = ( /obj/structure/closet/crate/freezer, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, -/obj/item/weapon/reagent_containers/food/condiment/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, +/obj/item/weapon/reagent_containers/food/condiment/carton/flour, /turf/simulated/floor/tiled/freezer, /area/submap/Diner) "aR" = ( diff --git a/maps/submaps/surface_submaps/plains/lonehome.dmm b/maps/submaps/surface_submaps/plains/lonehome.dmm index 2b0fca5b59..b610c17a87 100644 --- a/maps/submaps/surface_submaps/plains/lonehome.dmm +++ b/maps/submaps/surface_submaps/plains/lonehome.dmm @@ -86,7 +86,7 @@ "zN" = (/obj/item/weapon/paper{info = "Seems to be filled with odd runes drawn with blood"; name = "Blood stained paper"},/turf/simulated/floor/wood,/area/submap/lonehome) "AQ" = (/obj/structure/window/reinforced{dir = 8; health = 1e+006},/obj/item/weapon/material/shard,/turf/simulated/floor/outdoors/dirt,/area/submap/lonehome) "BH" = (/obj/item/weapon/paper{info = "F%$K this detective, I swear I can see them peeking from behind the window, these tinted glasses help but I swear I can still see them snooping around. His days are counted... I am so close to figuring this out, just need a few more days. Then Shepiffany will be part of the family once again, then our two kids and I can go back on having a normal life... a normal life..."; name = "Stained sheet of paper"},/obj/effect/decal/cleanable/dirt,/obj/effect/decal/cleanable/dirt,/turf/simulated/floor/carpet/sblucarpet,/area/submap/lonehome) -"Dd" = (/obj/machinery/button/windowtint{id = "h_kitchen"},/obj/item/weapon/storage/box/donkpockets,/obj/item/weapon/storage/box/condimentbottles,/obj/item/weapon/reagent_containers/food/condiment/sugar,/obj/structure/curtain/open/bed,/obj/structure/table/rack,/obj/structure/window/reinforced/polarized{dir = 4; id = "h_kitchen"},/turf/simulated/floor/tiled/white,/area/submap/lonehome) +"Dd" = (/obj/machinery/button/windowtint{id = "h_kitchen"},/obj/item/weapon/storage/box/donkpockets,/obj/item/weapon/storage/box/condimentbottles,/obj/item/weapon/reagent_containers/food/condiment/carton/sugar,/obj/structure/curtain/open/bed,/obj/structure/table/rack,/obj/structure/window/reinforced/polarized{dir = 4; id = "h_kitchen"},/turf/simulated/floor/tiled/white,/area/submap/lonehome) "Dk" = (/obj/effect/decal/cleanable/dirt,/obj/effect/decal/cleanable/dirt,/turf/simulated/floor/tiled/white,/area/submap/lonehome) "DE" = (/obj/structure/window/reinforced/tinted/frosted{dir = 8},/obj/item/weapon/storage/box/glasses/square{pixel_y = -2},/obj/item/weapon/storage/box/glass_extras/sticks{pixel_y = 4},/obj/item/weapon/storage/box/glass_extras/straws{pixel_y = 7},/obj/item/weapon/packageWrap,/obj/structure/curtain/open/bed,/obj/item/weapon/material/kitchen/utensil/fork,/obj/item/weapon/material/kitchen/utensil/fork,/obj/item/weapon/material/kitchen/utensil/spoon{pixel_x = 2},/obj/item/weapon/material/kitchen/utensil/spoon{pixel_x = 2},/obj/structure/table/rack,/turf/simulated/floor/tiled/white,/area/submap/lonehome) "DG" = (/obj/structure/simple_door/wood,/turf/simulated/floor,/area/submap/lonehome) diff --git a/maps/submaps/surface_submaps/plains/lonehome_vr.dmm b/maps/submaps/surface_submaps/plains/lonehome_vr.dmm index 776d5a25a2..6cee63decc 100644 --- a/maps/submaps/surface_submaps/plains/lonehome_vr.dmm +++ b/maps/submaps/surface_submaps/plains/lonehome_vr.dmm @@ -166,7 +166,7 @@ }, /obj/item/weapon/storage/box/donkpockets, /obj/item/weapon/storage/box/condimentbottles, -/obj/item/weapon/reagent_containers/food/condiment/sugar, +/obj/item/weapon/reagent_containers/food/condiment/carton/sugar, /obj/structure/curtain/open/bed, /obj/structure/table/rack, /obj/structure/window/reinforced/polarized{ diff --git a/maps/tether/tether-01-surface1.dmm b/maps/tether/tether-01-surface1.dmm index 9cd83ea37d..6d8e8206b1 100644 --- a/maps/tether/tether-01-surface1.dmm +++ b/maps/tether/tether-01-surface1.dmm @@ -7759,7 +7759,7 @@ pixel_x = 32 }, /obj/structure/table/standard, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled, /area/storage/primary) "amW" = ( @@ -28761,7 +28761,7 @@ dir = 8 }, /obj/structure/table/standard, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled, /area/crew_quarters/visitor_laundry) "aWW" = ( @@ -33813,7 +33813,7 @@ d2 = 8; icon_state = "4-8" }, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled, /area/crew_quarters/locker) "hTw" = ( diff --git a/maps/tether/tether-02-surface2.dmm b/maps/tether/tether-02-surface2.dmm index f67738972f..6484c366c3 100644 --- a/maps/tether/tether-02-surface2.dmm +++ b/maps/tether/tether-02-surface2.dmm @@ -11238,6 +11238,8 @@ /obj/effect/floor_decal/corner/red/border{ dir = 8 }, +/obj/item/clothing/shoes/magboots, +/obj/item/clothing/shoes/magboots, /turf/simulated/floor/tiled, /area/tether/surfacebase/security/evastorage) "arK" = ( @@ -13264,7 +13266,7 @@ /obj/machinery/recharger, /obj/item/weapon/storage/toolbox/mechanical, /obj/random/drinkbottle, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled/techfloor, /area/maintenance/lower/bar) "avg" = ( @@ -22134,7 +22136,7 @@ /obj/item/device/taperecorder{ pixel_x = -3 }, -/obj/item/device/paicard{ +/obj/random/paicard{ pixel_x = 4 }, /obj/item/weapon/circuitboard/teleporter, @@ -31053,7 +31055,7 @@ /obj/structure/sign/painting/public{ pixel_x = -30 }, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/wood, /area/tether/surfacebase/reading_room) "biK" = ( @@ -31624,7 +31626,7 @@ pixel_x = -23 }, /obj/random/junk, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/plating, /area/maintenance/lower/atmos) "bkE" = ( diff --git a/maps/tether/tether-03-surface3.dmm b/maps/tether/tether-03-surface3.dmm index b91bb8c2bc..d21a316752 100644 --- a/maps/tether/tether-03-surface3.dmm +++ b/maps/tether/tether-03-surface3.dmm @@ -8328,7 +8328,7 @@ /obj/machinery/atmospherics/unary/vent_pump/on{ dir = 8 }, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/wood, /area/crew_quarters/recreation_area) "anb" = ( @@ -9784,7 +9784,7 @@ /obj/random/maintenance/medical, /obj/random/maintenance/clean, /obj/random/maintenance/clean, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/plating, /area/tether/surfacebase/surface_three_hall) "apA" = ( @@ -40756,7 +40756,7 @@ /area/tether/surfacebase/security/hos) "lHr" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/wood, /area/tether/surfacebase/entertainment) "lIe" = ( diff --git a/maps/tether/tether-04-transit.dmm b/maps/tether/tether-04-transit.dmm index 72748da792..2f5e075c78 100644 --- a/maps/tether/tether-04-transit.dmm +++ b/maps/tether/tether-04-transit.dmm @@ -683,7 +683,7 @@ /area/tether/midpoint) "Ud" = ( /obj/structure/table/woodentable, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/midpoint_glass/reinf, /area/tether/midpoint) "Ui" = ( diff --git a/maps/tether/tether-05-station1.dmm b/maps/tether/tether-05-station1.dmm index 6f79a19b09..095f1c4720 100644 --- a/maps/tether/tether-05-station1.dmm +++ b/maps/tether/tether-05-station1.dmm @@ -1120,7 +1120,7 @@ /obj/machinery/atmospherics/pipe/simple/hidden/scrubbers{ dir = 4 }, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/tiled, /area/teleporter/departing) "acc" = ( @@ -15345,6 +15345,7 @@ /obj/machinery/light{ dir = 1 }, +/obj/item/clothing/shoes/magboots, /turf/simulated/floor/tiled, /area/quartermaster/belterdock/gear) "bPl" = ( @@ -22625,6 +22626,7 @@ /obj/effect/floor_decal/corner/brown/bordercorner2{ dir = 4 }, +/obj/item/clothing/shoes/magboots, /turf/simulated/floor/tiled, /area/quartermaster/belterdock/gear) "iEn" = ( @@ -23204,6 +23206,7 @@ /obj/effect/floor_decal/corner/brown/border{ dir = 5 }, +/obj/item/clothing/shoes/magboots, /turf/simulated/floor/tiled, /area/quartermaster/belterdock/gear) "jnG" = ( @@ -26600,7 +26603,7 @@ /obj/random/maintenance/clean, /obj/random/maintenance/clean, /obj/random/junk, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor, /area/maintenance/station/exploration) "lVJ" = ( @@ -28245,7 +28248,7 @@ layer = 2.9 }, /obj/random/junk, -/obj/item/device/paicard, +/obj/random/paicard, /turf/simulated/floor/plating, /area/maintenance/station/cargo) "nCS" = ( diff --git a/maps/tether/tether_turfs.dm b/maps/tether/tether_turfs.dm index b219e15f44..bfaa882bc3 100644 --- a/maps/tether/tether_turfs.dm +++ b/maps/tether/tether_turfs.dm @@ -276,6 +276,9 @@ VIRGO3B_TURF_CREATE(/turf/simulated/mineral/floor) for(var/obj/effect/step_trigger/teleporter/planetary_fall/virgo3b/F in src) qdel(F) +/turf/space/v3b_midpoint/CanZPass(atom, direction) + return 0 // We're not Space + // Tram transit floor /turf/simulated/floor/tiled/techfloor/grid/transit icon = 'icons/turf/transit_vr.dmi' diff --git a/sound/effects/weather/indoorrain_end.ogg b/sound/effects/weather/indoorrain_end.ogg index c82039d95b..2b83d6b6a4 100644 Binary files a/sound/effects/weather/indoorrain_end.ogg and b/sound/effects/weather/indoorrain_end.ogg differ diff --git a/sound/effects/weather/indoorrain_mid.ogg b/sound/effects/weather/indoorrain_mid.ogg index 60ab5f1624..088aec86ef 100644 Binary files a/sound/effects/weather/indoorrain_mid.ogg and b/sound/effects/weather/indoorrain_mid.ogg differ diff --git a/sound/effects/weather/indoorrain_start.ogg b/sound/effects/weather/indoorrain_start.ogg index 23165836a1..ca43575d5f 100644 Binary files a/sound/effects/weather/indoorrain_start.ogg and b/sound/effects/weather/indoorrain_start.ogg differ diff --git a/sound/voice/roboboop.ogg b/sound/voice/roboboop.ogg new file mode 100644 index 0000000000..6a2d9fd0af Binary files /dev/null and b/sound/voice/roboboop.ogg differ diff --git a/sound/voice/robochirp.ogg b/sound/voice/robochirp.ogg new file mode 100644 index 0000000000..51d534662d Binary files /dev/null and b/sound/voice/robochirp.ogg differ diff --git a/tgui/.eslintrc.yml b/tgui/.eslintrc.yml index 78354939c2..31d1b345d4 100644 --- a/tgui/.eslintrc.yml +++ b/tgui/.eslintrc.yml @@ -12,7 +12,7 @@ env: plugins: - radar - react - ## - unused-imports + - unused-imports settings: react: version: '16.10' @@ -376,6 +376,7 @@ rules: ignoreUrls: true, ignoreRegExpLiterals: true, ignoreStrings: true, + ignoreTemplateLiterals: true, }] ## Enforce a maximum number of lines per file # max-lines: error @@ -669,7 +670,7 @@ rules: # react/sort-prop-types: error ## Enforce the state initialization style to be either in a constructor or ## with a class property - react/state-in-constructor: error + # react/state-in-constructor: error ## Enforces where React component static properties should be positioned. # react/static-property-placement: error ## Enforce style prop value being an object @@ -759,4 +760,4 @@ rules: react/jsx-wrap-multilines: error ## Prevents the use of unused imports. ## This could be done by enabling no-unused-vars, but we're doing this for now - ## unused-imports/no-unused-imports: error + unused-imports/no-unused-imports: error diff --git a/tgui/.gitignore b/tgui/.gitignore index 56cb38b290..bcdbd1c91a 100644 --- a/tgui/.gitignore +++ b/tgui/.gitignore @@ -14,6 +14,7 @@ package-lock.json ## Build artifacts /public/.tmp/**/* +/public/*.map ## Previously ignored locations that are kept to avoid confusing git ## while transitioning to a new project structure. diff --git a/tgui/.yarn/sdks/eslint/bin/eslint.js b/tgui/.yarn/sdks/eslint/bin/eslint.js index 4e7554dc1a..4d327a49a0 100644 --- a/tgui/.yarn/sdks/eslint/bin/eslint.js +++ b/tgui/.yarn/sdks/eslint/bin/eslint.js @@ -4,7 +4,7 @@ const {existsSync} = require(`fs`); const {createRequire, createRequireFromPath} = require(`module`); const {resolve} = require(`path`); -const relPnpApiPath = "../../../../.pnp.js"; +const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); diff --git a/tgui/.yarn/sdks/eslint/lib/api.js b/tgui/.yarn/sdks/eslint/lib/api.js index ac3c9fc064..97a052442a 100644 --- a/tgui/.yarn/sdks/eslint/lib/api.js +++ b/tgui/.yarn/sdks/eslint/lib/api.js @@ -4,7 +4,7 @@ const {existsSync} = require(`fs`); const {createRequire, createRequireFromPath} = require(`module`); const {resolve} = require(`path`); -const relPnpApiPath = "../../../../.pnp.js"; +const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); diff --git a/tgui/.yarn/sdks/eslint/package.json b/tgui/.yarn/sdks/eslint/package.json index 4b0d99dbb9..744a773210 100644 --- a/tgui/.yarn/sdks/eslint/package.json +++ b/tgui/.yarn/sdks/eslint/package.json @@ -1,6 +1,6 @@ { "name": "eslint", - "version": "7.21.0-pnpify", + "version": "7.32.0-sdk", "main": "./lib/api.js", "type": "commonjs" } diff --git a/tgui/.yarn/sdks/integrations.yml b/tgui/.yarn/sdks/integrations.yml index 76ed42ba94..aa9d0d0ad8 100644 --- a/tgui/.yarn/sdks/integrations.yml +++ b/tgui/.yarn/sdks/integrations.yml @@ -1,5 +1,5 @@ -# This file is automatically generated by PnPify. -# Manual changes will be lost! +# This file is automatically generated by @yarnpkg/sdks. +# Manual changes might be lost! integrations: - vscode diff --git a/tgui/.yarn/sdks/typescript/bin/tsc b/tgui/.yarn/sdks/typescript/bin/tsc index 06e51d0d97..5608e57430 100644 --- a/tgui/.yarn/sdks/typescript/bin/tsc +++ b/tgui/.yarn/sdks/typescript/bin/tsc @@ -4,7 +4,7 @@ const {existsSync} = require(`fs`); const {createRequire, createRequireFromPath} = require(`module`); const {resolve} = require(`path`); -const relPnpApiPath = "../../../../.pnp.js"; +const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); diff --git a/tgui/.yarn/sdks/typescript/bin/tsserver b/tgui/.yarn/sdks/typescript/bin/tsserver index 2d03f3d97b..cd7d557d52 100644 --- a/tgui/.yarn/sdks/typescript/bin/tsserver +++ b/tgui/.yarn/sdks/typescript/bin/tsserver @@ -4,7 +4,7 @@ const {existsSync} = require(`fs`); const {createRequire, createRequireFromPath} = require(`module`); const {resolve} = require(`path`); -const relPnpApiPath = "../../../../.pnp.js"; +const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); diff --git a/tgui/.yarn/sdks/typescript/lib/tsc.js b/tgui/.yarn/sdks/typescript/lib/tsc.js index e030711c5a..16042d01d4 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsc.js +++ b/tgui/.yarn/sdks/typescript/lib/tsc.js @@ -4,7 +4,7 @@ const {existsSync} = require(`fs`); const {createRequire, createRequireFromPath} = require(`module`); const {resolve} = require(`path`); -const relPnpApiPath = "../../../../.pnp.js"; +const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); diff --git a/tgui/.yarn/sdks/typescript/lib/tsserver.js b/tgui/.yarn/sdks/typescript/lib/tsserver.js index 1d6dfb6115..4d90f3879d 100644 --- a/tgui/.yarn/sdks/typescript/lib/tsserver.js +++ b/tgui/.yarn/sdks/typescript/lib/tsserver.js @@ -4,15 +4,22 @@ const {existsSync} = require(`fs`); const {createRequire, createRequireFromPath} = require(`module`); const {resolve} = require(`path`); -const relPnpApiPath = "../../../../.pnp.js"; +const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + const {isAbsolute} = require(`path`); const pnpApi = require(`pnpapi`); + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { return `${locator.name}@${locator.reference}`; })); @@ -23,9 +30,9 @@ const moduleWrapper = tsserver => { function toEditorPath(str) { // We add the `zip:` prefix to both `.zip/` paths and virtual paths - if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || str.match(/\$\$virtual\//))) { + if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) { // We also take the opportunity to turn virtual paths into physical ones; - // this makes is much easier to work with workspaces that list peer + // this makes it much easier to work with workspaces that list peer // dependencies, since otherwise Ctrl+Click would bring us to the virtual // file instances instead of the real ones. // @@ -34,26 +41,49 @@ const moduleWrapper = tsserver => { // with peer dep (otherwise jumping into react-dom would show resolution // errors on react). // - const resolved = pnpApi.resolveVirtual(str); + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; if (resolved) { const locator = pnpApi.findPackageLocator(resolved); if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { - str = resolved; + str = resolved; } } - str = str.replace(/\\/g, `/`) - str = str.replace(/^\/?/, `/`); + str = normalize(str); - // Absolute VSCode `Uri.fsPath`s need to start with a slash. - // VSCode only adds it automatically for supported schemes, - // so we have to do it manually for the `zip` scheme. - // The path needs to start with a caret otherwise VSCode doesn't handle the protocol - // - // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 - // if (str.match(/\.zip\//)) { - str = `${isVSCode ? `^` : ``}zip:${str}`; + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + case `vscode`: { + str = `^zip:${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile:${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } } } @@ -66,15 +96,29 @@ const moduleWrapper = tsserver => { : str.replace(/^\^?zip:/, ``); } + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + // And here is the point where we hijack the VSCode <-> TS communications // by adding ourselves in the middle. We locate everything that looks // like an absolute path of ours and normalize it. const Session = tsserver.server.Session; const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; - let isVSCode = false; + let hostInfo = `unknown`; - return Object.assign(Session.prototype, { + Object.assign(Session.prototype, { onMessage(/** @type {string} */ message) { const parsedMessage = JSON.parse(message) @@ -82,9 +126,9 @@ const moduleWrapper = tsserver => { parsedMessage != null && typeof parsedMessage === `object` && parsedMessage.arguments && - parsedMessage.arguments.hostInfo === `vscode` + typeof parsedMessage.arguments.hostInfo === `string` ) { - isVSCode = true; + hostInfo = parsedMessage.arguments.hostInfo; } return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => { @@ -98,6 +142,8 @@ const moduleWrapper = tsserver => { }))); } }); + + return tsserver; }; if (existsSync(absPnpApiPath)) { diff --git a/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js new file mode 100644 index 0000000000..c3de4ff5d7 --- /dev/null +++ b/tgui/.yarn/sdks/typescript/lib/tsserverlibrary.js @@ -0,0 +1,157 @@ +#!/usr/bin/env node + +const {existsSync} = require(`fs`); +const {createRequire, createRequireFromPath} = require(`module`); +const {resolve} = require(`path`); + +const relPnpApiPath = "../../../../.pnp.cjs"; + +const absPnpApiPath = resolve(__dirname, relPnpApiPath); +const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); + +const moduleWrapper = tsserver => { + if (!process.versions.pnp) { + return tsserver; + } + + const {isAbsolute} = require(`path`); + const pnpApi = require(`pnpapi`); + + const isVirtual = str => str.match(/\/(\$\$virtual|__virtual__)\//); + const normalize = str => str.replace(/\\/g, `/`).replace(/^\/?/, `/`); + + const dependencyTreeRoots = new Set(pnpApi.getDependencyTreeRoots().map(locator => { + return `${locator.name}@${locator.reference}`; + })); + + // VSCode sends the zip paths to TS using the "zip://" prefix, that TS + // doesn't understand. This layer makes sure to remove the protocol + // before forwarding it to TS, and to add it back on all returned paths. + + function toEditorPath(str) { + // We add the `zip:` prefix to both `.zip/` paths and virtual paths + if (isAbsolute(str) && !str.match(/^\^zip:/) && (str.match(/\.zip\//) || isVirtual(str))) { + // We also take the opportunity to turn virtual paths into physical ones; + // this makes it much easier to work with workspaces that list peer + // dependencies, since otherwise Ctrl+Click would bring us to the virtual + // file instances instead of the real ones. + // + // We only do this to modules owned by the the dependency tree roots. + // This avoids breaking the resolution when jumping inside a vendor + // with peer dep (otherwise jumping into react-dom would show resolution + // errors on react). + // + const resolved = isVirtual(str) ? pnpApi.resolveVirtual(str) : str; + if (resolved) { + const locator = pnpApi.findPackageLocator(resolved); + if (locator && dependencyTreeRoots.has(`${locator.name}@${locator.reference}`)) { + str = resolved; + } + } + + str = normalize(str); + + if (str.match(/\.zip\//)) { + switch (hostInfo) { + // Absolute VSCode `Uri.fsPath`s need to start with a slash. + // VSCode only adds it automatically for supported schemes, + // so we have to do it manually for the `zip` scheme. + // The path needs to start with a caret otherwise VSCode doesn't handle the protocol + // + // Ref: https://github.com/microsoft/vscode/issues/105014#issuecomment-686760910 + // + case `vscode`: { + str = `^zip:${str}`; + } break; + + // To make "go to definition" work, + // We have to resolve the actual file system path from virtual path + // and convert scheme to supported by [vim-rzip](https://github.com/lbrayner/vim-rzip) + case `coc-nvim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = resolve(`zipfile:${str}`); + } break; + + // Support neovim native LSP and [typescript-language-server](https://github.com/theia-ide/typescript-language-server) + // We have to resolve the actual file system path from virtual path, + // everything else is up to neovim + case `neovim`: { + str = normalize(resolved).replace(/\.zip\//, `.zip::`); + str = `zipfile:${str}`; + } break; + + default: { + str = `zip:${str}`; + } break; + } + } + } + + return str; + } + + function fromEditorPath(str) { + return process.platform === `win32` + ? str.replace(/^\^?zip:\//, ``) + : str.replace(/^\^?zip:/, ``); + } + + // Force enable 'allowLocalPluginLoads' + // TypeScript tries to resolve plugins using a path relative to itself + // which doesn't work when using the global cache + // https://github.com/microsoft/TypeScript/blob/1b57a0395e0bff191581c9606aab92832001de62/src/server/project.ts#L2238 + // VSCode doesn't want to enable 'allowLocalPluginLoads' due to security concerns but + // TypeScript already does local loads and if this code is running the user trusts the workspace + // https://github.com/microsoft/vscode/issues/45856 + const ConfiguredProject = tsserver.server.ConfiguredProject; + const {enablePluginsWithOptions: originalEnablePluginsWithOptions} = ConfiguredProject.prototype; + ConfiguredProject.prototype.enablePluginsWithOptions = function() { + this.projectService.allowLocalPluginLoads = true; + return originalEnablePluginsWithOptions.apply(this, arguments); + }; + + // And here is the point where we hijack the VSCode <-> TS communications + // by adding ourselves in the middle. We locate everything that looks + // like an absolute path of ours and normalize it. + + const Session = tsserver.server.Session; + const {onMessage: originalOnMessage, send: originalSend} = Session.prototype; + let hostInfo = `unknown`; + + Object.assign(Session.prototype, { + onMessage(/** @type {string} */ message) { + const parsedMessage = JSON.parse(message) + + if ( + parsedMessage != null && + typeof parsedMessage === `object` && + parsedMessage.arguments && + typeof parsedMessage.arguments.hostInfo === `string` + ) { + hostInfo = parsedMessage.arguments.hostInfo; + } + + return originalOnMessage.call(this, JSON.stringify(parsedMessage, (key, value) => { + return typeof value === `string` ? fromEditorPath(value) : value; + })); + }, + + send(/** @type {any} */ msg) { + return originalSend.call(this, JSON.parse(JSON.stringify(msg, (key, value) => { + return typeof value === `string` ? toEditorPath(value) : value; + }))); + } + }); + + return tsserver; +}; + +if (existsSync(absPnpApiPath)) { + if (!process.versions.pnp) { + // Setup the environment to be able to require typescript/lib/tsserverlibrary.js + require(absPnpApiPath).setup(); + } +} + +// Defer to the real typescript/lib/tsserverlibrary.js your application uses +module.exports = moduleWrapper(absRequire(`typescript/lib/tsserverlibrary.js`)); diff --git a/tgui/.yarn/sdks/typescript/lib/typescript.js b/tgui/.yarn/sdks/typescript/lib/typescript.js index 7e3c852fe1..cbdbf1500f 100644 --- a/tgui/.yarn/sdks/typescript/lib/typescript.js +++ b/tgui/.yarn/sdks/typescript/lib/typescript.js @@ -4,7 +4,7 @@ const {existsSync} = require(`fs`); const {createRequire, createRequireFromPath} = require(`module`); const {resolve} = require(`path`); -const relPnpApiPath = "../../../../.pnp.js"; +const relPnpApiPath = "../../../../.pnp.cjs"; const absPnpApiPath = resolve(__dirname, relPnpApiPath); const absRequire = (createRequire || createRequireFromPath)(absPnpApiPath); diff --git a/tgui/.yarn/sdks/typescript/package.json b/tgui/.yarn/sdks/typescript/package.json index 254e665561..ea85e133e8 100644 --- a/tgui/.yarn/sdks/typescript/package.json +++ b/tgui/.yarn/sdks/typescript/package.json @@ -1,6 +1,6 @@ { "name": "typescript", - "version": "4.2.3-pnpify", + "version": "4.3.5-sdk", "main": "./lib/typescript.js", "type": "commonjs" } diff --git a/tgui/README.md b/tgui/README.md index bfa9ec3d92..d0f1d1e08d 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -33,6 +33,13 @@ If you were already familiar with an older, Ractive-based tgui, and want to translate concepts between old and new tgui, read this [interface conversion guide](docs/converting-old-tgui-interfaces.md). +### Other Documentation + +- [Component Reference](docs/component-reference.md) - UI building blocks +- [Using TGUI and Byond API for custom HTML popups](docs/tgui-for-custom-html-popups.md) +- [Chat Embedded Components](docs/chat-embedded-components.md) +- [Writing Tests](docs/writing-tests.md) + ## Pre-requisites You will need these programs to start developing in tgui: @@ -40,6 +47,7 @@ You will need these programs to start developing in tgui: - [Node v16.13+](https://nodejs.org/en/download/) - **LTS** release is recommended instead of latest - [Yarn v1.22.4+](https://yarnpkg.com/getting-started/install) (optional) + - You can run `npm install -g yarn` to install it. - [Git Bash](https://git-scm.com/downloads) or [MSys2](https://www.msys2.org/) (optional) @@ -190,10 +198,6 @@ Add stylesheets here if you really need a fine control over your UI styles. - `/packages/tgui/styles/themes` - Contains all the various themes you can use in tgui. Each theme must be registered in `webpack.config.js` file. -## Component Reference - -See: [Component Reference](docs/component-reference.md). - ## License Source code is covered by /tg/station's parent license - **AGPL-3.0** diff --git a/tgui/babel.config.js b/tgui/babel.config.js index 46fc0b0768..d8ddb75721 100644 --- a/tgui/babel.config.js +++ b/tgui/babel.config.js @@ -5,7 +5,7 @@ */ const createBabelConfig = options => { - const { mode, presets = [], plugins = [] } = options; + const { presets = [], plugins = [], removeConsole } = options; return { presets: [ [require.resolve('@babel/preset-typescript'), { @@ -14,23 +14,23 @@ const createBabelConfig = options => { [require.resolve('@babel/preset-env'), { modules: 'commonjs', useBuiltIns: 'entry', - corejs: '3.10', + corejs: '3', spec: false, loose: true, targets: [], }], ...presets, - ], + ].filter(Boolean), plugins: [ [require.resolve('@babel/plugin-proposal-class-properties'), { loose: true, }], require.resolve('@babel/plugin-transform-jscript'), require.resolve('babel-plugin-inferno'), - require.resolve('babel-plugin-transform-remove-console'), + removeConsole && require.resolve('babel-plugin-transform-remove-console'), require.resolve('common/string.babel-plugin.cjs'), ...plugins, - ], + ].filter(Boolean), }; }; diff --git a/tgui/bin/tgui b/tgui/bin/tgui index 40579fbe47..256f0e579e 100644 --- a/tgui/bin/tgui +++ b/tgui/bin/tgui @@ -59,7 +59,7 @@ task-webpack() { ## Runs a development server task-dev-server() { cd "${base_dir}" - yarn node packages/tgui-dev-server/index.esm.js "${@}" + yarn node --experimental-modules packages/tgui-dev-server/index.js "${@}" } ## Run a linter through all packages @@ -92,7 +92,7 @@ task-clean() { rm -f .yarn/build-state.yml rm -f .yarn/install-state.gz rm -f .yarn/install-target - rm -f .pnp.js + rm -f .pnp.* ## NPM artifacts rm -rf **/node_modules rm -f **/package-lock.json diff --git a/tgui/bin/tgui-bench.bat b/tgui/bin/tgui-bench.bat new file mode 100644 index 0000000000..da22a7b2ae --- /dev/null +++ b/tgui/bin/tgui-bench.bat @@ -0,0 +1,9 @@ +@echo off +rem Copyright (c) 2020 Aleksej Komarov +rem SPDX-License-Identifier: MIT +call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --bench %* +rem Pause if launched in a separate shell unless initiated from powershell +echo %PSModulePath% | findstr %USERPROFILE% >NUL +if %errorlevel% equ 0 exit 0 +echo %cmdcmdline% | find /i "/c" +if %errorlevel% equ 0 pause diff --git a/tgui/bin/tgui_.ps1 b/tgui/bin/tgui_.ps1 index c8f74b2d21..a1909fe70e 100644 --- a/tgui/bin/tgui_.ps1 +++ b/tgui/bin/tgui_.ps1 @@ -47,7 +47,11 @@ function task-webpack { ## Runs a development server function task-dev-server { - yarn node "packages/tgui-dev-server/index.esm.js" @Args + yarn node --experimental-modules "packages/tgui-dev-server/index.js" @Args +} + +function task-bench { + yarn tgui:bench @Args } ## Run a linter through all packages @@ -75,7 +79,7 @@ function task-clean { Remove-Quiet -Force ".yarn\build-state.yml" Remove-Quiet -Force ".yarn\install-state.gz" Remove-Quiet -Force ".yarn\install-target" - Remove-Quiet -Force ".pnp.js" + Remove-Quiet -Force ".pnp.*" ## NPM artifacts Get-ChildItem -Path "." -Include "node_modules" -Recurse -File:$false | Remove-Item -Recurse -Force Remove-Quiet -Force "package-lock.json" @@ -132,6 +136,13 @@ if ($Args.Length -gt 0) { task-webpack --mode=production --analyze exit 0 } + + if ($Args[0] -eq "--bench") { + $Rest = $Args | Select-Object -Skip 1 + task-install + task-bench --wait-on-error + exit 0 + } } ## Make a production webpack build diff --git a/tgui/docs/tgui-for-custom-html-popups.md b/tgui/docs/tgui-for-custom-html-popups.md new file mode 100644 index 0000000000..f25060e9fa --- /dev/null +++ b/tgui/docs/tgui-for-custom-html-popups.md @@ -0,0 +1,259 @@ +# Using TGUI and Byond API for custom HTML popups + +TGUI in its current form would not exist without a very robust underlying layer that interfaces TGUI code with the BYOND browser component. This very layer can also be used to write simple and robust HTML popups, with access to many convenient APIs. In this article, you'll learn how to make a TGUI powered HTML popup and leverage all APIs that it provides. + +## How to create a window + +TGUI in order to create a window (popup) uses the `/datum/tgui_window` class. Feel free to take a look at its [source code](../../code/modules/tgui/tgui_window.dm), as all of its procs are very well documented. This class takes care of spawning the BYOND's browser element, normalizes the browser environment (because users might have IE8 on their system, or in future, it might be Microsoft Edge) and specifies a very rigid communication protocol between DM and JS. + +> **Notice:** Because `/datum/tgui_window` includes a lot of boilerplate in the final html that it displays in the browser, it is somewhat more expensive to render than a traditional, dumb popup using a `browse()` proc call. Therefore, its best to use it with static popups or very custom pieces of client-side code, e.g. stat panel, chat or a background music player. +Create a window that prints hello world. + +```dm +var/datum/tgui_window/window = new(usr.client, "custom_popup") +window.initialize( + inline_html = "

Hello world!

", +) +``` + +Here, `custom_popup` is a unique id for the BYOND skin element that this window uses, and it can be anything you want. If you want to reference a specific element from `interface/skin.dmf`, you can use that id instead, and UI will initialize inside of that element. This is how for example chat initializes itself, by using a `browseroutput` id, which is also specified in `interface/skin.dmf`. + +In case you want to re-initialize it with different content, you can do that as well by calling `initialize` again with different arguments. + +```dm +window.initialize( + inline_html = "

Hello world, but smaller!

", +) +``` + +You can close the window as easily as you've opened it. + +```dm +window.close() +``` + +## Sending assets + +TGUI in /tg/station codebase has `/datum/asset`, that packs scripts and stylesheets for delivery via CDN for efficiency. TGUI internally uses this asset system to render TGUI interfaces *proper* and TGUI chat. This is a snippet from internal TGUI code: + +```dm +window.initialize( + fancy = user.client.prefs.read_preference( + /datum/preference/toggle/tgui_fancy + ), + assets = list( + get_asset_datum(/datum/asset/simple/tgui), + )) +``` + +You can see two new arguments: + +- `fancy` - See [Fancy mode](#fancy-mode) +- `assets` - This is a list of asset datums, and all JS and CSS in the assets will be loaded in the page. + +Using asset datums has a big benefit over including ` +``` + +## Inlined HTML, CSS and JS + +You can also make a popup that doesn't rely on network requests to get JS and CSS. In the following case, the entirety of the page will be contained in a single HTML file. + +```dm +window.initialize( + inline_html = "

Hello world!

", + inline_js = "window.alert('Warning!')", + inline_css = "h1 { color: red }", +) +``` + +You can also do the same by splitting your code into separate files, and then leveraging tgui window to serve it all as one big HTML file. + +```dm +window.initialize( + inline_html = file2text('code/modules/thing/thing.html'), + inline_js = file2text('code/modules/thing/thing.js'), + inline_css = file2text('code/modules/thing/thing.css'), +) +``` + +If you need to inline multiple JS or CSS files, you can concatenate them for now, and separate contents of each file with an `\n` symbol. *This can be a point of improvement (add support for file lists)*. + +## Fancy mode + +You may have noticed the fancy mode in previous snippets: + +```dm +window.initialize(fancy = TRUE) +``` + +This removes the native window titlebar and border, which effectively turns window into a floating panel. TGUI heavily uses this option to draw completely custom, fancy windows. You can use it too, but not having the default titlebar limits usability of the browser window, since you can't even close it or drag around without implementing that functionality yourself. This mode might be useful for creating popups and tooltips. + +## Communication + +It is very often necessary to exchange data between DM and JS, and in vanilla BYOND programming it is a huge pain in the butt, because the `browse()` API is very convoluted, out of box it can send only strings, and sending data back to DM requires using hrefs. + +``` +location.href = '?src=12345¶m=1' +``` + +If you're familiar with the href syntax of BYOND topic calls, then perhaps this doesn't surprise you, but this API artificially limits you to sending 2048 characters of string-typed data; you need to reinvent the wheel if you want to send something more complex than strings. It differs from the way you send messages from DM. And it's very hard to read as well. + +Thankfully, TGUI implements a very robust protocol that makes this slightly less of an eye sore and very convenient to use in the long run. + +### Message structure + +```ts +{ + type: string; + payload?: any; + // ... +} +``` + +Each message always has a **type**, which is usually (but not always) the first argument on all message sending functions. The next property is the **payload**, which contains all the data sent in the message. + +You can think of it in these terms: + +- **type** - function name +- **payload** - function arguments + +Of course we're not working with functions here, but hopefully this analogy makes the concept easier to understand. + +Finally, message can contain custom properties, and how you use them is *completely up to you*. They have an important limitation - all additional properties are string-typed, and require you to use a slightly more verbose API for sending them (more about it in the next section). + +```js +Byond.sendMessage({ + type: 'click', + payload: { buttonId: 1 }, + popup_section: 'left', +}); +``` + +### DM âž¡ JS + +To send a message from DM, you can use the `window.send_message()` proc. + +```dm +window.send_message("alert", list( + text = "Hello, world!", +)) +``` + +To receive it in JS, you have two different syntaxes. First one is the most verbose one, but allows receiving all types of messages, and deciding what to do via `if` conditions. + +> NOTE: We're using ECMAScript 5 syntax here, because this is the version that is supported by IE 11 natively without any additional compilation. If you're coding in a compiled environment (TGUI/Webpack), then feel free to use arrow functions and other fancy syntaxes. +```js +Byond.subscribe(function (type, payload) { + if (type === 'alert') { + window.alert(payload.text); + return; + } + if (type === 'other') { + // ... + return; + } + // ... +}); +``` + +Second one is more compact, because it already filters messages by type and passes the payload directly to the callback. + +```js +Byond.subscribeTo('alert', function (payload) { + window.alert(payload.text); +}); +``` + +### JS âž¡ DM + +To send a message from JS, you can use the `Byond.sendMessage()` function. + +```js +Byond.sendMessage('click', { + button: 'explode-mech', +}); +``` + +To receive it in DM, you must register a delegate proc (callback) that will receive the messages (usually called `on_message`), and handle the message in that proc. + +```dm +/datum/my_object/proc/initialize() + // ... + window.subscribe(src, .proc/on_message) +/datum/my_object/proc/on_message(type, payload) + if (type == "click") + process_button_click(payload["button"]) + return +``` + +**Advanced variant** + +You can send messages with custom fields in case if you want to bypass JSON serialization of the **payload**. Not sending the **payload** is a little bit faster if you send a lot of messages (because BYOND is slow in general with proc calls, especially `json_decode`). All raw message fields are available in the third argument `href_list`. + +```js +Byond.sendMessage({ + type: "something", + ref: "[0x12345678]", +}); +``` + +```dm +/datum/my_object/proc/on_message(type, payload, href_list) + if (type == "something") + process_something(locate(href_list["ref"])) + return +``` + +## BYOND Skin API + +There is a full assortment of BYOND client-side features that you can access via the `Byond` API object. + +Full reference of the `Byond` API object is here: [global.d.ts](../global.d.ts). It's a global type definition file, which provides auto-completion in VSCode when coding TGUI interfaces. When writing custom popups outside of TGUI, autocompletion doesn't work, so you might need to peek into this file sometimes. + +Here's the summary of what it has. + +- `Byond.winget()` - Returns a property of a skin element. This is an async function call, more on that later. +- `Byond.winset()` - Sets a property of a skin element. +- `Byond.topic()` - Makes a Topic call to the server. Similar to `sendMessage`, but all topic calls are native to BYOND, string typed and processed in `/client/Topic()` proc. +- `Byond.command()` - Runs a command on the client, as if you typed it into the command bar yourself. Can be any verb, or a special client-side command, such as `.output`. + +> As of now, `Byond.winget()` requires a Promise polyfill, which is only available in compiled TGUI, but not in plain popups, and if you try using it, you'll get a bluescreen error. If you'd like to have winget in non-compiled contexts, then ping maintainers on Discord to request this feature. +When working with `winset` and `winget`, it can be very useful to consult [BYOND 5.0 controls and parameters guide](https://secure.byond.com/docs/ref/skinparams.html) to figure out what you can control in the BYOND client. Via these controls and parameters, you can do many interesting things, such as dynamically define BYOND macros, or show/hide and reposition various skin elements. + +Another source of information is the official [BYOND Reference](https://secure.byond.com/docs/ref/info.html#/{skin}), which is a much larger, but a more comprehensive doc. + +Id of the current tgui window can be accessed via `Byond.windowId`, and below in an example of changing its `size`. + +```js +Byond.winset(Byond.windowId, { + size: '1280x640', +}); +``` + +Id of the main SS13 window is `'mainwindow'`, as defined in [skin.dmf](../../interface/skin.dmf). + +Little known feature, but you can also get non-UI parameters on the client by using a `null` id. + +```js +// Fetch URL of a server client is currently connected to +Byond.winget(null, 'url').then((serverUrl) => { + // Connect to this server + Byond.call(serverUrl); + // Close our client because it is now connecting in background + Byond.command('.quit'); +}); +``` diff --git a/tgui/global.d.ts b/tgui/global.d.ts index 7f2cc8f728..0f69e37153 100644 --- a/tgui/global.d.ts +++ b/tgui/global.d.ts @@ -4,155 +4,180 @@ * @license MIT */ -declare global { - // Webpack asset modules. - // Should match extensions used in webpack config. - declare module '*.png' { - const content: string; - export default content; - } - - declare module '*.jpg' { - const content: string; - export default content; - } - - declare module '*.svg' { - const content: string; - export default content; - } - - type ByondType = { - /** - * True if javascript is running in BYOND. - */ - IS_BYOND: boolean; - - /** - * True if browser is IE8 or lower. - */ - IS_LTE_IE8: boolean; - - /** - * True if browser is IE9 or lower. - */ - IS_LTE_IE9: boolean; - - /** - * True if browser is IE10 or lower. - */ - IS_LTE_IE10: boolean; - - /** - * True if browser is IE11 or lower. - */ - IS_LTE_IE11: boolean; - - /** - * Makes a BYOND call. - * - * If path is empty, this will trigger a Topic call. - * You can reference a specific object by setting the "src" parameter. - * - * See: https://secure.byond.com/docs/ref/skinparams.html - */ - call(path: string, params: object): void; - - /** - * Makes an asynchronous BYOND call. Returns a promise. - */ - callAsync(path: string, params: object): Promise; - - /** - * Makes a Topic call. - * - * You can reference a specific object by setting the "src" parameter. - */ - topic(params: object): void; - - /** - * Runs a command or a verb. - */ - command(command: string): void; - - /** - * Retrieves all properties of the BYOND skin element. - * - * Returns a promise with a key-value object containing all properties. - */ - winget(id: string): Promise; - - /** - * Retrieves all properties of the BYOND skin element. - * - * Returns a promise with a key-value object containing all properties. - */ - winget(id: string, propName: '*'): Promise; - - /** - * Retrieves an exactly one property of the BYOND skin element, - * as defined in `propName`. - * - * Returns a promise with the value of that property. - */ - winget(id: string, propName: string): Promise; - - /** - * Retrieves multiple properties of the BYOND skin element, - * as defined in the `propNames` array. - * - * Returns a promise with a key-value object containing listed properties. - */ - winget(id: string, propNames: string[]): Promise; - - /** - * Assigns properties to BYOND skin elements. - */ - winset(props: object): void; - - /** - * Assigns properties to the BYOND skin element. - */ - winset(id: string, props: object): void; - - /** - * Sets a property on the BYOND skin element to a certain value. - */ - winset(id: string, propName: string, propValue: any): void; - - /** - * Parses BYOND JSON. - * - * Uses a special encoding to preverse Infinity and NaN. - */ - parseJson(text: string): any; - - /** - * Loads a stylesheet into the document. - */ - loadCss(url: string): void; - - /** - * Loads a script into the document. - */ - loadJs(url: string): void; - }; - - /** - * Object that provides access to Byond Skin API and is available in - * any tgui application. - */ - const Byond: ByondType; - - interface Window { - /** - * ID of the Byond window this script is running on. - * Should be used as a parameter to winget/winset. - */ - __windowId__: string; - Byond: ByondType; - } - +// Webpack asset modules. +// Should match extensions used in webpack config. +declare module '*.png' { + const content: string; + export default content; } -export {}; +declare module '*.jpg' { + const content: string; + export default content; +} + +declare module '*.svg' { + const content: string; + export default content; +} + +type TguiMessage = { + type: string; + payload?: any; + [key: string]: any; +}; + + +type ByondType = { + /** + * ID of the Byond window this script is running on. + * Can be used as a parameter to winget/winset. + */ + windowId: string; + + /** + * True if javascript is running in BYOND. + */ + IS_BYOND: boolean; + + /** + * Version of Trident engine of Internet Explorer. Null if N/A. + */ + TRIDENT: number | null; + + /** + * True if browser is IE8 or lower. + */ + IS_LTE_IE8: boolean; + + /** + * True if browser is IE9 or lower. + */ + IS_LTE_IE9: boolean; + + /** + * True if browser is IE10 or lower. + */ + IS_LTE_IE10: boolean; + + /** + * True if browser is IE11 or lower. + */ + IS_LTE_IE11: boolean; + + /** + * Makes a BYOND call. + * + * If path is empty, this will trigger a Topic call. + * You can reference a specific object by setting the "src" parameter. + * + * See: https://secure.byond.com/docs/ref/skinparams.html + */ + call(path: string, params: object): void; + + /** + * Makes an asynchronous BYOND call. Returns a promise. + */ + callAsync(path: string, params: object): Promise; + + /** + * Makes a Topic call. + * + * You can reference a specific object by setting the "src" parameter. + */ + topic(params: object): void; + + /** + * Runs a command or a verb. + */ + command(command: string): void; + + /** + * Retrieves all properties of the BYOND skin element. + * + * Returns a promise with a key-value object containing all properties. + */ + winget(id: string | null): Promise; + + /** + * Retrieves all properties of the BYOND skin element. + * + * Returns a promise with a key-value object containing all properties. + */ + winget(id: string | null, propName: '*'): Promise; + + /** + * Retrieves an exactly one property of the BYOND skin element, + * as defined in `propName`. + * + * Returns a promise with the value of that property. + */ + winget(id: string | null, propName: string): Promise; + + /** + * Retrieves multiple properties of the BYOND skin element, + * as defined in the `propNames` array. + * + * Returns a promise with a key-value object containing listed properties. + */ + winget(id: string | null, propNames: string[]): Promise; + + /** + * Assigns properties to BYOND skin elements in bulk. + */ + winset(props: object): void; + + /** + * Assigns properties to the BYOND skin element. + */ + winset(id: string | null, props: object): void; + + /** + * Sets a property on the BYOND skin element to a certain value. + */ + winset(id: string | null, propName: string, propValue: any): void; + + /** + * Parses BYOND JSON. + * + * Uses a special encoding to preserve `Infinity` and `NaN`. + */ + parseJson(text: string): any; + + /** + * Sends a message to `/datum/tgui_window` which hosts this window instance. + */ + sendMessage(type: string, payload?: any): void; + sendMessage(message: TguiMessage): void; + + /** + * Subscribe to incoming messages that were sent from `/datum/tgui_window`. + */ + subscribe(listener: (type: string, payload: any) => void): void; + + /** + * Subscribe to incoming messages *of some specific type* + * that were sent from `/datum/tgui_window`. + */ + subscribeTo(type: string, listener: (payload: any) => void): void; + + /** + * Loads a stylesheet into the document. + */ + loadCss(url: string): void; + + /** + * Loads a script into the document. + */ + loadJs(url: string): void; +}; + +/** + * Object that provides access to Byond Skin API and is available in + * any tgui application. + */ +const Byond: ByondType; + +interface Window { + Byond: ByondType; +} diff --git a/tgui/jest.config.js b/tgui/jest.config.js index 5802332817..e654f0089b 100644 --- a/tgui/jest.config.js +++ b/tgui/jest.config.js @@ -4,6 +4,9 @@ module.exports = { '/packages/**/__tests__/*.{js,ts,tsx}', '/packages/**/*.{spec,test}.{js,ts,tsx}', ], + testPathIgnorePatterns: [ + '/packages/tgui-bench', + ], testEnvironment: 'jsdom', testRunner: require.resolve('jest-circus/runner'), transform: { diff --git a/tgui/package.json b/tgui/package.json index a8c4c80f46..7fc6d67cd2 100644 --- a/tgui/package.json +++ b/tgui/package.json @@ -2,9 +2,22 @@ "private": true, "name": "tgui-workspace", "version": "4.4.0", + "packageManager": "yarn@3.2.1", "workspaces": [ "packages/*" ], + "scripts": { + "tgui:build": "webpack", + "tgui:analyze": "webpack --analyze", + "tgui:dev": "node --experimental-modules packages/tgui-dev-server/index.js", + "tgui:lint": "eslint packages --ext .js,.cjs,.ts,.tsx", + "tgui:sonar": "eslint packages --ext .js,.cjs,.ts,.tsx -c .eslintrc-harder.yml", + "tgui:tsc": "tsc", + "tgui:test": "jest --watch", + "tgui:test-simple": "CI=true jest --color", + "tgui:test-ci": "CI=true jest --color --collect-coverage", + "tgui:bench": "webpack --env TGUI_BENCH=1 && node packages/tgui-bench/index.js" + }, "dependencies": { "@babel/core": "^7.18.0", "@babel/eslint-parser": "^7.17.0", @@ -15,6 +28,8 @@ "@types/jest": "^27.5.1", "@types/jsdom": "^16.2.14", "@types/node": "^17.0.35", + "@types/webpack": "^5.28.0", + "@types/webpack-env": "^1.17.0", "@typescript-eslint/parser": "^5.25.0", "babel-jest": "^28.1.0", "babel-loader": "^8.2.5", diff --git a/tgui/packages/common/collections.ts b/tgui/packages/common/collections.ts index 7abe49ff23..f0a8bfeba3 100644 --- a/tgui/packages/common/collections.ts +++ b/tgui/packages/common/collections.ts @@ -4,64 +4,6 @@ * @license MIT */ -/** - * Converts a given collection to an array. - * - * - Arrays are returned unmodified; - * - If object was provided, keys will be discarded; - * - Everything else will result in an empty array. - * - * @returns {any[]} - */ -export const toArray = collection => { - if (Array.isArray(collection)) { - return collection; - } - if (typeof collection === 'object') { - const hasOwnProperty = Object.prototype.hasOwnProperty; - const result = []; - for (let i in collection) { - if (hasOwnProperty.call(collection, i)) { - result.push(collection[i]); - } - } - return result; - } - return []; -}; - -/** - * Converts a given object to an array, and appends a key to every - * object inside of that array. - * - * Example input (object): - * ``` - * { - * 'Foo': { info: 'Hello world!' }, - * 'Bar': { info: 'Hello world!' }, - * } - * ``` - * - * Example output (array): - * ``` - * [ - * { key: 'Foo', info: 'Hello world!' }, - * { key: 'Bar', info: 'Hello world!' }, - * ] - * ``` - * - * @template T - * @param {{ [key: string]: T }} obj Object, or in DM terms, an assoc array - * @param {string} keyProp Property, to which key will be assigned - * @returns {T[]} Array of keyed objects - */ -export const toKeyedArray = (obj, keyProp = 'key') => { - return map((item, key) => ({ - [keyProp]: key, - ...item, - }))(obj); -}; - /** * Iterates over elements of collection, returning an array of all elements * iteratee returns truthy for. The predicate is invoked with three @@ -72,21 +14,40 @@ export const toKeyedArray = (obj, keyProp = 'key') => { * * @returns {any[]} */ -export const filter = iterateeFn => collection => { - if (collection === null || collection === undefined) { - return collection; - } - if (Array.isArray(collection)) { - const result = []; - for (let i = 0; i < collection.length; i++) { - const item = collection[i]; - if (iterateeFn(item, i, collection)) { - result.push(item); +export const filter = (iterateeFn: ( + input: T, + index: number, + collection: T[], +) => boolean) => + (collection: T[]): T[] => { + if (collection === null || collection === undefined) { + return collection; } - } - return result; - } - throw new Error(`filter() can't iterate on type ${typeof collection}`); + if (Array.isArray(collection)) { + const result: T[] = []; + for (let i = 0; i < collection.length; i++) { + const item = collection[i]; + if (iterateeFn(item, i, collection)) { + result.push(item); + } + } + return result; + } + throw new Error(`filter() can't iterate on type ${typeof collection}`); + }; + +type MapFunction = { + (iterateeFn: ( + value: T, + index: number, + collection: T[], + ) => U): (collection: T[]) => U[]; + + (iterateeFn: ( + value: T, + index: K, + collection: Record, + ) => U): (collection: Record) => U[]; }; /** @@ -96,32 +57,25 @@ export const filter = iterateeFn => collection => { * * If collection is 'null' or 'undefined', it will be returned "as is" * without emitting any errors (which can be useful in some cases). - * - * @returns {any[]} */ -export const map = iterateeFn => collection => { - if (collection === null || collection === undefined) { - return collection; - } - if (Array.isArray(collection)) { - const result = []; - for (let i = 0; i < collection.length; i++) { - result.push(iterateeFn(collection[i], i, collection)); +export const map: MapFunction = (iterateeFn) => + (collection: T[]): U[] => { + if (collection === null || collection === undefined) { + return collection; } - return result; - } - if (typeof collection === 'object') { - const hasOwnProperty = Object.prototype.hasOwnProperty; - const result = []; - for (let i in collection) { - if (hasOwnProperty.call(collection, i)) { - result.push(iterateeFn(collection[i], i, collection)); - } + + if (Array.isArray(collection)) { + return collection.map(iterateeFn); } - return result; - } - throw new Error(`map() can't iterate on type ${typeof collection}`); -}; + + if (typeof collection === 'object') { + return Object.entries(collection).map(([key, value]) => { + return iterateeFn(value, key, collection); + }); + } + + throw new Error(`map() can't iterate on type ${typeof collection}`); + }; const COMPARATOR = (objA, objB) => { const criteriaA = objA.criteria; @@ -148,28 +102,35 @@ const COMPARATOR = (objA, objB) => { * * @returns {any[]} */ -export const sortBy = (...iterateeFns) => array => { - if (!Array.isArray(array)) { - return array; - } - let length = array.length; - // Iterate over the array to collect criteria to sort it by - let mappedArray = []; - for (let i = 0; i < length; i++) { - const value = array[i]; - mappedArray.push({ - criteria: iterateeFns.map(fn => fn(value)), - value, - }); - } - // Sort criteria using the base comparator - mappedArray.sort(COMPARATOR); - // Unwrap values - while (length--) { - mappedArray[length] = mappedArray[length].value; - } - return mappedArray; -}; +export const sortBy = ( + ...iterateeFns: ((input: T) => unknown)[] +) => (array: T[]): T[] => { + if (!Array.isArray(array)) { + return array; + } + let length = array.length; + // Iterate over the array to collect criteria to sort it by + let mappedArray: { + criteria: unknown[], + value: T, + }[] = []; + for (let i = 0; i < length; i++) { + const value = array[i]; + mappedArray.push({ + criteria: iterateeFns.map(fn => fn(value)), + value, + }); + } + // Sort criteria using the base comparator + mappedArray.sort(COMPARATOR); + + // Unwrap values + const values: T[] = []; + while (length--) { + values[length] = mappedArray[length].value; + } + return values; + }; export const sort = sortBy(); @@ -212,40 +173,38 @@ export const reduce = (reducerFn, initialValue) => array => { * is determined by the order they occur in the array. The iteratee is * invoked with one argument: value. */ -/* eslint-disable indent */ export const uniqBy = ( iterateeFn?: (value: T) => unknown -) => (array: T[]) => { - const { length } = array; - const result = []; - const seen = iterateeFn ? [] : result; - let index = -1; - outer: - while (++index < length) { - let value: T | 0 = array[index]; - const computed = iterateeFn ? iterateeFn(value) : value; - value = value !== 0 ? value : 0; - if (computed === computed) { - let seenIndex = seen.length; - while (seenIndex--) { - if (seen[seenIndex] === computed) { - continue outer; +) => (array: T[]): T[] => { + const { length } = array; + const result: T[] = []; + const seen: unknown[] = iterateeFn ? [] : result; + let index = -1; + outer: + while (++index < length) { + let value: T | 0 = array[index]; + const computed = iterateeFn ? iterateeFn(value) : value; + if (computed === computed) { + let seenIndex = seen.length; + while (seenIndex--) { + if (seen[seenIndex] === computed) { + continue outer; + } } + if (iterateeFn) { + seen.push(computed); + } + result.push(value); } - if (iterateeFn) { - seen.push(computed); + else if (!seen.includes(computed)) { + if (seen !== result) { + seen.push(computed); + } + result.push(value); } - result.push(value); } - else if (!seen.includes(computed)) { - if (seen !== result) { - seen.push(computed); - } - result.push(value); - } - } - return result; -}; + return result; + }; /* eslint-enable indent */ export const uniq = uniqBy(); @@ -261,17 +220,19 @@ type Zip = { */ export const zip = (...arrays: T): Zip => { if (arrays.length === 0) { - return; + return []; } const numArrays = arrays.length; const numValues = arrays[0].length; - const result = []; + const result: Zip = []; for (let valueIndex = 0; valueIndex < numValues; valueIndex++) { - const entry = []; + const entry: unknown[] = []; for (let arrayIndex = 0; arrayIndex < numArrays; arrayIndex++) { entry.push(arrays[arrayIndex][valueIndex]); } - result.push(entry); + + // I tried everything to remove this any, and have no idea how to do it. + result.push(entry as any); } return result; }; @@ -280,9 +241,8 @@ export const zip = (...arrays: T): Zip => { * This method is like "zip" except that it accepts iteratee to * specify how grouped values should be combined. The iteratee is * invoked with the elements of each group. - * - * @returns {any[]} */ -export const zipWith = iterateeFn => (...arrays) => { - return map(values => iterateeFn(...values))(zip(...arrays)); -}; +export const zipWith = (iterateeFn: (...values: T[]) => U) => + (...arrays: T[][]): U[] => { + return map((values: T[]) => iterateeFn(...values))(zip(...arrays)); + }; diff --git a/tgui/packages/common/types.ts b/tgui/packages/common/types.ts new file mode 100644 index 0000000000..a92ac122d9 --- /dev/null +++ b/tgui/packages/common/types.ts @@ -0,0 +1,5 @@ +/** + * Returns the arguments of a function F as an array. + */ +export type ArgumentsOf + = F extends (...args: infer A) => unknown ? A : never; diff --git a/tgui/packages/tgfont/dist/tgfont.css b/tgui/packages/tgfont/dist/tgfont.css index 6295dcce8f..ad732b8fc0 100644 --- a/tgui/packages/tgfont/dist/tgfont.css +++ b/tgui/packages/tgfont/dist/tgfont.css @@ -1,7 +1,7 @@ @font-face { font-family: "tgfont"; - src: url("./tgfont.woff2?8fcc44d209cc0a286e2fedc5edea15e7") format("woff2"), -url("./tgfont.eot?8fcc44d209cc0a286e2fedc5edea15e7#iefix") format("embedded-opentype"); + src: url("./tgfont.woff2?45c3c7acc69dd413375d77898d24e41e") format("woff2"), +url("./tgfont.eot?45c3c7acc69dd413375d77898d24e41e#iefix") format("embedded-opentype"); } i[class^="tg-"]:before, i[class*=" tg-"]:before { @@ -21,21 +21,30 @@ i[class^="tg-"]:before, i[class*=" tg-"]:before { .tg-air-tank:before { content: "\f102"; } -.tg-image-minus:before { +.tg-bad-touch:before { content: "\f103"; } -.tg-image-plus:before { +.tg-image-minus:before { content: "\f104"; } -.tg-nanotrasen-logo:before { +.tg-image-plus:before { content: "\f105"; } -.tg-sound-minus:before { +.tg-nanotrasen-logo:before { content: "\f106"; } -.tg-sound-plus:before { +.tg-non-binary:before { content: "\f107"; } -.tg-syndicate-logo:before { +.tg-prosthetic-leg:before { content: "\f108"; } +.tg-sound-minus:before { + content: "\f109"; +} +.tg-sound-plus:before { + content: "\f10a"; +} +.tg-syndicate-logo:before { + content: "\f10b"; +} diff --git a/tgui/packages/tgfont/dist/tgfont.eot b/tgui/packages/tgfont/dist/tgfont.eot index a1ffb10df4..ba307e0672 100644 Binary files a/tgui/packages/tgfont/dist/tgfont.eot and b/tgui/packages/tgfont/dist/tgfont.eot differ diff --git a/tgui/packages/tgfont/dist/tgfont.woff2 b/tgui/packages/tgfont/dist/tgfont.woff2 index 4e2dae731a..3f54573899 100644 Binary files a/tgui/packages/tgfont/dist/tgfont.woff2 and b/tgui/packages/tgfont/dist/tgfont.woff2 differ diff --git a/tgui/packages/tgfont/icons/ATTRIBUTIONS.md b/tgui/packages/tgfont/icons/ATTRIBUTIONS.md new file mode 100644 index 0000000000..2f218388d3 --- /dev/null +++ b/tgui/packages/tgfont/icons/ATTRIBUTIONS.md @@ -0,0 +1,6 @@ +bad-touch.svg contains: +- hug by Phạm Thanh Lá»™c from the Noun Project +- Fight by Rudez Studio from the Noun Project + +prosthetic-leg.svg contains: +- prosthetic leg by Gan Khoon Lay from the Noun Project diff --git a/tgui/packages/tgfont/icons/bad-touch.svg b/tgui/packages/tgfont/icons/bad-touch.svg new file mode 100644 index 0000000000..a2566c1670 --- /dev/null +++ b/tgui/packages/tgfont/icons/bad-touch.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + diff --git a/tgui/packages/tgfont/icons/non-binary.svg b/tgui/packages/tgfont/icons/non-binary.svg new file mode 100644 index 0000000000..9aaec674bb --- /dev/null +++ b/tgui/packages/tgfont/icons/non-binary.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + diff --git a/tgui/packages/tgfont/icons/prosthetic-leg.svg b/tgui/packages/tgfont/icons/prosthetic-leg.svg new file mode 100644 index 0000000000..c1f6ceee3f --- /dev/null +++ b/tgui/packages/tgfont/icons/prosthetic-leg.svg @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/tgui/packages/tgfont/package.json b/tgui/packages/tgfont/package.json index 90c38f82db..5e5a3f060c 100644 --- a/tgui/packages/tgfont/package.json +++ b/tgui/packages/tgfont/package.json @@ -2,10 +2,10 @@ "private": true, "name": "tgfont", "version": "1.0.0", - "dependencies": { - "fantasticon": "^1.2.3" - }, "scripts": { "build": "node mkdist.cjs && fantasticon --config config.cjs" + }, + "dependencies": { + "fantasticon": "^1.2.3" } } diff --git a/tgui/packages/tgui-bench/entrypoint.tsx b/tgui/packages/tgui-bench/entrypoint.tsx new file mode 100644 index 0000000000..d41d667894 --- /dev/null +++ b/tgui/packages/tgui-bench/entrypoint.tsx @@ -0,0 +1,73 @@ +/** + * @file + * @copyright 2021 Aleksej Komarov + * @license MIT + */ + +import { setupGlobalEvents } from 'tgui/events'; +import 'tgui/styles/main.scss'; +import Benchmark from './lib/benchmark'; + +const sendMessage = (obj: any) => { + const req = new XMLHttpRequest(); + req.open('POST', `/message`, false); + req.setRequestHeader('Content-Type', 'application/json;charset=UTF-8'); + // req.timeout = 250; + req.send(JSON.stringify(obj)); +}; + +const setupApp = async () => { + // Delay setup + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', setupApp); + return; + } + + setupGlobalEvents({ + ignoreWindowFocus: true, + }); + + const requireTest = require.context('./tests', false, /\.test\./); + + for (const file of requireTest.keys()) { + sendMessage({ type: 'suite-start', file }); + try { + const tests = requireTest(file); + await new Promise((resolve) => { + const suite = new Benchmark.Suite(file, { + onCycle(e) { + sendMessage({ + type: 'suite-cycle', + message: String(e.target), + }); + }, + onComplete() { + // This message is somewhat useless, but leaving it here in case + // someone has an idea how to show more useful data. + // sendMessage({ + // type: 'suite-complete', + // message: 'Fastest is ' + this.filter('fastest').map('name'), + // }); + resolve(); + }, + onError(e) { + sendMessage({ type: 'error', e }); + resolve(); + }, + }); + for (const [name, fn] of Object.entries(tests)) { + if (typeof fn === 'function') { + suite.add(name, fn); + } + } + suite.run(); + }); + } + catch (error) { + sendMessage({ type: 'error', error }); + } + } + sendMessage({ type: 'finished' }); +}; + +setupApp(); diff --git a/tgui/packages/tgui-bench/index.js b/tgui/packages/tgui-bench/index.js new file mode 100644 index 0000000000..e29ad9a5c2 --- /dev/null +++ b/tgui/packages/tgui-bench/index.js @@ -0,0 +1,93 @@ +/** + * @file + * @copyright 2021 Aleksej Komarov + * @license MIT + */ + +const fs = require('fs'); +const path = require('path'); +const { exec } = require('child_process'); +const { fastify } = require('fastify'); + +process.chdir(__dirname); + +const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)); + +const IE_TIMEOUT_SECONDS = 60; + +const setup = async () => { + const server = fastify(); + + let hasResponded = false; + + let assets = ''; + assets += `\n`; + + const publicDir = path.resolve(__dirname, '../../public'); + const page = fs.readFileSync(path.join(publicDir, 'tgui.html'), 'utf-8') + .replace('\n', assets); + + server.register(require('@fastify/static'), { + root: publicDir, + }); + + server.get('/', async (req, res) => { + return res.type('text/html').send(page); + }); + + server.post('/message', async (req, res) => { + if (!hasResponded) { + process.stdout.write('\n'); + hasResponded = true; + } + const { type, ...rest } = req.body; + if (type === 'suite-start') { + console.log(`=> Test '${rest.file}'`); + return res.send(); + } + if (type === 'suite-cycle') { + console.log(rest.message); + return res.send(); + } + if (type === 'suite-complete') { + console.log(rest.message); + return res.send(); + } + if (type === 'finished') { + await res.send(); + process.exit(0); + } + // Unhandled message + console.log(req.body); + return res.send(); + }); + + try { + await server.listen(3002, '0.0.0.0'); + } + catch (err) { + console.error(err); + process.exit(1); + } + + if (process.platform === 'win32') { + exec(`start "" "iexplore" "http://127.0.0.1:3002"`); + } + + console.log('Waiting for Internet Explorer to respond.'); + for (let i = 0; i < IE_TIMEOUT_SECONDS; i++) { + await sleep(1000); + if (hasResponded) { + return; + } + process.stdout.write('.'); + } + process.stdout.write('\n'); + console.error('Did not receive a response, exiting.'); + process.exit(1); +}; + +setup(); diff --git a/tgui/packages/tgui-bench/lib/benchmark.d.ts b/tgui/packages/tgui-bench/lib/benchmark.d.ts new file mode 100644 index 0000000000..7f3310005f --- /dev/null +++ b/tgui/packages/tgui-bench/lib/benchmark.d.ts @@ -0,0 +1,219 @@ +/* eslint-disable */ +// Type definitions for Benchmark v2.1.4 +// Project: https://benchmarkjs.com +// Definitions by: Asana +// Charlie Fish +// Blair Zajac +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare class Benchmark { + static filter(arr: T[], callback: (value: T) => any, thisArg?: any): T[]; + static filter(arr: T[], filter: string, thisArg?: any): T[]; + static formatNumber(num: number): string; + static join(obj: Object, separator1?: string, separator2?: string): string; + static invoke( + benches: Benchmark[], + name: string | Object, + ...args: any[] + ): any[]; + static runInContext(context: Object): Function; + + static each(obj: Object | any[], callback: Function, thisArg?: any): void; + static forEach(arr: T[], callback: (value: T) => any, thisArg?: any): void; + static forOwn(obj: Object, callback: Function, thisArg?: any): void; + static has(obj: Object, path: any[] | string): boolean; + static indexOf(arr: T[], value: T, fromIndex?: number): number; + static map(arr: T[], callback: (value: T) => K, thisArg?: any): K[]; + static reduce( + arr: T[], + callback: (accumulator: K, value: T) => K, + thisArg?: any + ): K; + + static options: Benchmark.Options; + static platform: Benchmark.Platform; + static support: Benchmark.Support; + static version: string; + + constructor(fn: Function | string, options?: Benchmark.Options); + constructor(name: string, fn: Function | string, options?: Benchmark.Options); + constructor(name: string, options?: Benchmark.Options); + constructor(options: Benchmark.Options); + + id: number; + name: string; + count: number; + cycles: number; + hz: number; + compiled: Function | string; + error: Error; + fn: Function | string; + aborted: boolean; + running: boolean; + setup: Function | string; + teardown: Function | string; + + stats: Benchmark.Stats; + times: Benchmark.Times; + + abort(): Benchmark; + clone(options: Benchmark.Options): Benchmark; + compare(benchmark: Benchmark): number; + emit(type: string | Object): any; + listeners(type: string): Function[]; + off(type?: string, listener?: Function): Benchmark; + off(types: string[]): Benchmark; + on(type?: string, listener?: Function): Benchmark; + on(types: string[]): Benchmark; + reset(): Benchmark; + run(options?: Benchmark.Options): Benchmark; + toString(): string; +} + +declare namespace Benchmark { + export interface Options { + async?: boolean | undefined; + defer?: boolean | undefined; + delay?: number | undefined; + id?: string | undefined; + initCount?: number | undefined; + maxTime?: number | undefined; + minSamples?: number | undefined; + minTime?: number | undefined; + name?: string | undefined; + onAbort?: Function | undefined; + onComplete?: Function | undefined; + onCycle?: Function | undefined; + onError?: Function | undefined; + onReset?: Function | undefined; + onStart?: Function | undefined; + setup?: Function | string | undefined; + teardown?: Function | string | undefined; + fn?: Function | string | undefined; + queued?: boolean | undefined; + } + + export interface Platform { + description: string; + layout: string; + product: string; + name: string; + manufacturer: string; + os: string; + prerelease: string; + version: string; + toString(): string; + } + + export interface Support { + browser: boolean; + timeout: boolean; + decompilation: boolean; + } + + export interface Stats { + moe: number; + rme: number; + sem: number; + deviation: number; + mean: number; + sample: any[]; + variance: number; + } + + export interface Times { + cycle: number; + elapsed: number; + period: number; + timeStamp: number; + } + + export class Deferred { + constructor(clone: Benchmark); + + benchmark: Benchmark; + cycles: number; + elapsed: number; + timeStamp: number; + + resolve(): void; + } + + export interface Target { + options: Options; + async?: boolean | undefined; + defer?: boolean | undefined; + delay?: number | undefined; + initCount?: number | undefined; + maxTime?: number | undefined; + minSamples?: number | undefined; + minTime?: number | undefined; + name?: string | undefined; + fn?: Function | undefined; + id: number; + stats?: Stats | undefined; + times?: Times | undefined; + running: boolean; + count?: number | undefined; + compiled?: Function | undefined; + cycles?: number | undefined; + hz?: number | undefined; + } + + export class Event { + constructor(type: string | Object); + + aborted: boolean; + cancelled: boolean; + currentTarget: Object; + result: any; + target: Target; + timeStamp: number; + type: string; + } + + export class Suite { + static options: { name: string }; + + constructor(name?: string, options?: Options); + + length: number; + aborted: boolean; + running: boolean; + + abort(): Suite; + add(name: string, fn: Function | string, options?: Options): Suite; + add(fn: Function | string, options?: Options): Suite; + add(name: string, options?: Options): Suite; + add(options: Options): Suite; + clone(options: Options): Suite; + emit(type: string | Object): any; + filter(callback: Function | string): Suite; + join(separator?: string): string; + listeners(type: string): Function[]; + off(type?: string, callback?: Function): Suite; + off(types: string[]): Suite; + on(type?: string, callback?: Function): Suite; + on(types: string[]): Suite; + push(benchmark: Benchmark): number; + reset(): Suite; + run(options?: Options): Suite; + reverse(): any[]; + sort(compareFn: (a: any, b: any) => number): any[]; + splice(start: number, deleteCount?: number): any[]; + unshift(benchmark: Benchmark): number; + + each(callback: Function): Suite; + forEach(callback: Function): Suite; + indexOf(value: any): number; + map(callback: Function | string): any[]; + reduce(callback: Function, accumulator: T): T; + + pop(): Function; + shift(): Benchmark; + slice(start: number, end: number): any[]; + slice(start: number, deleteCount: number, ...values: any[]): any[]; + } +} + +export = Benchmark; diff --git a/tgui/packages/tgui-bench/lib/benchmark.js b/tgui/packages/tgui-bench/lib/benchmark.js new file mode 100644 index 0000000000..1e76cec708 --- /dev/null +++ b/tgui/packages/tgui-bench/lib/benchmark.js @@ -0,0 +1,2759 @@ +/* eslint-disable */ +/*! + * Benchmark.js + * Copyright 2010-2016 Mathias Bynens + * Based on JSLitmus.js, copyright Robert Kieffer + * Modified by John-David Dalton + * Manually stripped from useless junk by /tg/station13 maintainers. + * Available under MIT license + */ +module.exports = (function() { + 'use strict'; + + /** Used as a safe reference for `undefined` in pre ES5 environments. */ + var undefined; + + /** Used to determine if values are of the language type Object. */ + var objectTypes = { + 'function': true, + 'object': true + }; + + /** Used as a reference to the global object. */ + var root = (objectTypes[typeof window] && window) || this; + + /** Detect free variable `define`. */ + var freeDefine = false; + + /** Used to assign each benchmark an incremented id. */ + var counter = 0; + + /** Used to detect primitive types. */ + var rePrimitive = /^(?:boolean|number|string|undefined)$/; + + /** Used to make every compiled test unique. */ + var uidCounter = 0; + + /** Used to assign default `context` object properties. */ + var contextProps = [ + 'Array', 'Date', 'Function', 'Math', 'Object', 'RegExp', 'String', '_', + 'clearTimeout', 'chrome', 'chromium', 'document', 'navigator', 'phantom', + 'platform', 'process', 'runtime', 'setTimeout' + ]; + + /** Used to avoid hz of Infinity. */ + var divisors = { + '1': 4096, + '2': 512, + '3': 64, + '4': 8, + '5': 0 + }; + + /** + * T-Distribution two-tailed critical values for 95% confidence. + * For more info see http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm. + */ + var tTable = { + '1': 12.706, '2': 4.303, '3': 3.182, '4': 2.776, '5': 2.571, '6': 2.447, + '7': 2.365, '8': 2.306, '9': 2.262, '10': 2.228, '11': 2.201, '12': 2.179, + '13': 2.16, '14': 2.145, '15': 2.131, '16': 2.12, '17': 2.11, '18': 2.101, + '19': 2.093, '20': 2.086, '21': 2.08, '22': 2.074, '23': 2.069, '24': 2.064, + '25': 2.06, '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042, + 'infinity': 1.96 + }; + + /** + * Critical Mann-Whitney U-values for 95% confidence. + * For more info see http://www.saburchill.com/IBbiology/stats/003.html. + */ + var uTable = { + '5': [0, 1, 2], + '6': [1, 2, 3, 5], + '7': [1, 3, 5, 6, 8], + '8': [2, 4, 6, 8, 10, 13], + '9': [2, 4, 7, 10, 12, 15, 17], + '10': [3, 5, 8, 11, 14, 17, 20, 23], + '11': [3, 6, 9, 13, 16, 19, 23, 26, 30], + '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37], + '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45], + '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55], + '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64], + '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75], + '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87], + '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99], + '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113], + '20': [8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127], + '21': [8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142], + '22': [9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158], + '23': [9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175], + '24': [10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192], + '25': [10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211], + '26': [11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230], + '27': [11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250], + '28': [12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272], + '29': [13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294], + '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317] + }; + + /*--------------------------------------------------------------------------*/ + + /** + * Create a new `Benchmark` function using the given `context` object. + * + * @static + * @memberOf Benchmark + * @param {Object} [context=root] The context object. + * @returns {Function} Returns a new `Benchmark` function. + */ + function runInContext(context) { + // Exit early if unable to acquire lodash. + var _ = context && context._ || require('lodash') || root._; + if (!_) { + Benchmark.runInContext = runInContext; + return Benchmark; + } + // Avoid issues with some ES3 environments that attempt to use values, named + // after built-in constructors like `Object`, for the creation of literals. + // ES5 clears this up by stating that literals must use built-in constructors. + // See http://es5.github.io/#x11.1.5. + context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root; + + /** Native constructor references. */ + var Array = context.Array, + Date = context.Date, + Function = context.Function, + Math = context.Math, + Object = context.Object, + RegExp = context.RegExp, + String = context.String; + + /** Used for `Array` and `Object` method references. */ + var arrayRef = [], + objectProto = Object.prototype; + + /** Native method shortcuts. */ + var abs = Math.abs, + clearTimeout = context.clearTimeout, + floor = Math.floor, + log = Math.log, + max = Math.max, + min = Math.min, + pow = Math.pow, + push = arrayRef.push, + setTimeout = context.setTimeout, + shift = arrayRef.shift, + slice = arrayRef.slice, + sqrt = Math.sqrt, + toString = objectProto.toString, + unshift = arrayRef.unshift; + + /** Detect DOM document object. */ + var doc = isHostType(context, 'document') && context.document; + + /** Used to access Node.js's high resolution timer. */ + var processObject = isHostType(context, 'process') && context.process; + + /** Used to prevent a `removeChild` memory leak in IE < 9. */ + var trash = doc && doc.createElement('div'); + + /** Used to integrity check compiled tests. */ + var uid = 'uid' + _.now(); + + /** Used to avoid infinite recursion when methods call each other. */ + var calledBy = {}; + + /** + * An object used to flag environments/features. + * + * @static + * @memberOf Benchmark + * @type Object + */ + var support = {}; + + (function() { + + /** + * Detect if running in a browser environment. + * + * @memberOf Benchmark.support + * @type boolean + */ + support.browser = doc && isHostType(context, 'navigator') && !isHostType(context, 'phantom'); + + /** + * Detect if the Timers API exists. + * + * @memberOf Benchmark.support + * @type boolean + */ + support.timeout = isHostType(context, 'setTimeout') && isHostType(context, 'clearTimeout'); + + /** + * Detect if function decompilation is support. + * + * @name decompilation + * @memberOf Benchmark.support + * @type boolean + */ + try { + // Safari 2.x removes commas in object literals from `Function#toString` results. + // See http://webk.it/11609 for more details. + // Firefox 3.6 and Opera 9.25 strip grouping parentheses from `Function#toString` results. + // See http://bugzil.la/559438 for more details. + support.decompilation = Function( + ('return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')') + // Avoid issues with code added by Istanbul. + .replace(/__cov__[^;]+;/g, '') + )()(0).x === '1'; + } catch(e) { + support.decompilation = false; + } + }()); + + /** + * Timer object used by `clock()` and `Deferred#resolve`. + * + * @private + * @type Object + */ + var timer = { + + /** + * The timer namespace object or constructor. + * + * @private + * @memberOf timer + * @type {Function|Object} + */ + 'ns': Date, + + /** + * Starts the deferred timer. + * + * @private + * @memberOf timer + * @param {Object} deferred The deferred instance. + */ + 'start': null, // Lazy defined in `clock()`. + + /** + * Stops the deferred timer. + * + * @private + * @memberOf timer + * @param {Object} deferred The deferred instance. + */ + 'stop': null // Lazy defined in `clock()`. + }; + + /*------------------------------------------------------------------------*/ + + /** + * The Benchmark constructor. + * + * Note: The Benchmark constructor exposes a handful of lodash methods to + * make working with arrays, collections, and objects easier. The lodash + * methods are: + * [`each/forEach`](https://lodash.com/docs#forEach), [`forOwn`](https://lodash.com/docs#forOwn), + * [`has`](https://lodash.com/docs#has), [`indexOf`](https://lodash.com/docs#indexOf), + * [`map`](https://lodash.com/docs#map), and [`reduce`](https://lodash.com/docs#reduce) + * + * @constructor + * @param {string} name A name to identify the benchmark. + * @param {Function|string} fn The test to benchmark. + * @param {Object} [options={}] Options object. + * @example + * + * // basic usage (the `new` operator is optional) + * var bench = new Benchmark(fn); + * + * // or using a name first + * var bench = new Benchmark('foo', fn); + * + * // or with options + * var bench = new Benchmark('foo', fn, { + * + * // displayed by `Benchmark#toString` if `name` is not available + * 'id': 'xyz', + * + * // called when the benchmark starts running + * 'onStart': onStart, + * + * // called after each run cycle + * 'onCycle': onCycle, + * + * // called when aborted + * 'onAbort': onAbort, + * + * // called when a test errors + * 'onError': onError, + * + * // called when reset + * 'onReset': onReset, + * + * // called when the benchmark completes running + * 'onComplete': onComplete, + * + * // compiled/called before the test loop + * 'setup': setup, + * + * // compiled/called after the test loop + * 'teardown': teardown + * }); + * + * // or name and options + * var bench = new Benchmark('foo', { + * + * // a flag to indicate the benchmark is deferred + * 'defer': true, + * + * // benchmark test function + * 'fn': function(deferred) { + * // call `Deferred#resolve` when the deferred test is finished + * deferred.resolve(); + * } + * }); + * + * // or options only + * var bench = new Benchmark({ + * + * // benchmark name + * 'name': 'foo', + * + * // benchmark test as a string + * 'fn': '[1,2,3,4].sort()' + * }); + * + * // a test's `this` binding is set to the benchmark instance + * var bench = new Benchmark('foo', function() { + * 'My name is '.concat(this.name); // "My name is foo" + * }); + */ + function Benchmark(name, fn, options) { + var bench = this; + + // Allow instance creation without the `new` operator. + if (!(bench instanceof Benchmark)) { + return new Benchmark(name, fn, options); + } + // Juggle arguments. + if (_.isPlainObject(name)) { + // 1 argument (options). + options = name; + } + else if (_.isFunction(name)) { + // 2 arguments (fn, options). + options = fn; + fn = name; + } + else if (_.isPlainObject(fn)) { + // 2 arguments (name, options). + options = fn; + fn = null; + bench.name = name; + } + else { + // 3 arguments (name, fn [, options]). + bench.name = name; + } + setOptions(bench, options); + + bench.id || (bench.id = ++counter); + bench.fn == null && (bench.fn = fn); + + bench.stats = cloneDeep(bench.stats); + bench.times = cloneDeep(bench.times); + } + + /** + * The Deferred constructor. + * + * @constructor + * @memberOf Benchmark + * @param {Object} clone The cloned benchmark instance. + */ + function Deferred(clone) { + var deferred = this; + if (!(deferred instanceof Deferred)) { + return new Deferred(clone); + } + deferred.benchmark = clone; + clock(deferred); + } + + /** + * The Event constructor. + * + * @constructor + * @memberOf Benchmark + * @param {Object|string} type The event type. + */ + function Event(type) { + var event = this; + if (type instanceof Event) { + return type; + } + return (event instanceof Event) + ? _.assign(event, { 'timeStamp': _.now() }, typeof type == 'string' ? { 'type': type } : type) + : new Event(type); + } + + /** + * The Suite constructor. + * + * Note: Each Suite instance has a handful of wrapped lodash methods to + * make working with Suites easier. The wrapped lodash methods are: + * [`each/forEach`](https://lodash.com/docs#forEach), [`indexOf`](https://lodash.com/docs#indexOf), + * [`map`](https://lodash.com/docs#map), and [`reduce`](https://lodash.com/docs#reduce) + * + * @constructor + * @memberOf Benchmark + * @param {string} name A name to identify the suite. + * @param {Object} [options={}] Options object. + * @example + * + * // basic usage (the `new` operator is optional) + * var suite = new Benchmark.Suite; + * + * // or using a name first + * var suite = new Benchmark.Suite('foo'); + * + * // or with options + * var suite = new Benchmark.Suite('foo', { + * + * // called when the suite starts running + * 'onStart': onStart, + * + * // called between running benchmarks + * 'onCycle': onCycle, + * + * // called when aborted + * 'onAbort': onAbort, + * + * // called when a test errors + * 'onError': onError, + * + * // called when reset + * 'onReset': onReset, + * + * // called when the suite completes running + * 'onComplete': onComplete + * }); + */ + function Suite(name, options) { + var suite = this; + + // Allow instance creation without the `new` operator. + if (!(suite instanceof Suite)) { + return new Suite(name, options); + } + // Juggle arguments. + if (_.isPlainObject(name)) { + // 1 argument (options). + options = name; + } else { + // 2 arguments (name [, options]). + suite.name = name; + } + setOptions(suite, options); + } + + /*------------------------------------------------------------------------*/ + + /** + * A specialized version of `_.cloneDeep` which only clones arrays and plain + * objects assigning all other values by reference. + * + * @private + * @param {*} value The value to clone. + * @returns {*} The cloned value. + */ + var cloneDeep = _.partial(_.cloneDeepWith, _, function(value) { + // Only clone primitives, arrays, and plain objects. + return (_.isObject(value) && !_.isArray(value) && !_.isPlainObject(value)) + ? value + : undefined; + }); + + /** + * Creates a function from the given arguments string and body. + * + * @private + * @param {string} args The comma separated function arguments. + * @param {string} body The function body. + * @returns {Function} The new function. + */ + function createFunction() { + // Lazy define. + createFunction = function(args, body) { + var result, + anchor = freeDefine ? freeDefine.amd : Benchmark, + prop = uid + 'createFunction'; + + runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}'); + result = anchor[prop]; + delete anchor[prop]; + return result; + }; + // Fix JaegerMonkey bug. + // For more information see http://bugzil.la/639720. + createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || _.noop)() == uid ? createFunction : Function; + return createFunction.apply(null, arguments); + } + + /** + * Delay the execution of a function based on the benchmark's `delay` property. + * + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} fn The function to execute. + */ + function delay(bench, fn) { + bench._timerId = _.delay(fn, bench.delay * 1e3); + } + + /** + * Destroys the given element. + * + * @private + * @param {Element} element The element to destroy. + */ + function destroyElement(element) { + trash.appendChild(element); + trash.innerHTML = ''; + } + + /** + * Gets the name of the first argument from a function's source. + * + * @private + * @param {Function} fn The function. + * @returns {string} The argument name. + */ + function getFirstArgument(fn) { + return (!_.has(fn, 'toString') && + (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || ''; + } + + /** + * Computes the arithmetic mean of a sample. + * + * @private + * @param {Array} sample The sample. + * @returns {number} The mean. + */ + function getMean(sample) { + return (_.reduce(sample, function(sum, x) { + return sum + x; + }) / sample.length) || 0; + } + + /** + * Gets the source code of a function. + * + * @private + * @param {Function} fn The function. + * @returns {string} The function's source code. + */ + function getSource(fn) { + var result = ''; + if (isStringable(fn)) { + result = String(fn); + } else if (support.decompilation) { + // Escape the `{` for Firefox 1. + result = _.result(/^[^{]+\{([\s\S]*)\}\s*$/.exec(fn), 1); + } + // Trim string. + result = (result || '').replace(/^\s+|\s+$/g, ''); + + // Detect strings containing only the "use strict" directive. + return /^(?:\/\*+[\w\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result) + ? '' + : result; + } + + /** + * Checks if an object is of the specified class. + * + * @private + * @param {*} value The value to check. + * @param {string} name The name of the class. + * @returns {boolean} Returns `true` if the value is of the specified class, else `false`. + */ + function isClassOf(value, name) { + return value != null && toString.call(value) == '[object ' + name + ']'; + } + + /** + * Host objects can return type values that are different from their actual + * data type. The objects we are concerned with usually return non-primitive + * types of "object", "function", or "unknown". + * + * @private + * @param {*} object The owner of the property. + * @param {string} property The property to check. + * @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`. + */ + function isHostType(object, property) { + if (object == null) { + return false; + } + var type = typeof object[property]; + return !rePrimitive.test(type) && (type != 'object' || !!object[property]); + } + + /** + * Checks if a value can be safely coerced to a string. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the value can be coerced, else `false`. + */ + function isStringable(value) { + return _.isString(value) || (_.has(value, 'toString') && _.isFunction(value.toString)); + } + + /** + * Runs a snippet of JavaScript via script injection. + * + * @private + * @param {string} code The code to run. + */ + function runScript(code) { + var anchor = freeDefine ? define.amd : Benchmark, + script = doc.createElement('script'), + sibling = doc.getElementsByTagName('script')[0], + parent = sibling.parentNode, + prop = uid + 'runScript', + prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();'; + + // Firefox 2.0.0.2 cannot use script injection as intended because it executes + // asynchronously, but that's OK because script injection is only used to avoid + // the previously commented JaegerMonkey bug. + try { + // Remove the inserted script *before* running the code to avoid differences + // in the expected script element count/order of the document. + script.appendChild(doc.createTextNode(prefix + code)); + anchor[prop] = function() { destroyElement(script); }; + } catch(e) { + parent = parent.cloneNode(false); + sibling = null; + script.text = code; + } + parent.insertBefore(script, sibling); + delete anchor[prop]; + } + + /** + * A helper function for setting options/event handlers. + * + * @private + * @param {Object} object The benchmark or suite instance. + * @param {Object} [options={}] Options object. + */ + function setOptions(object, options) { + options = object.options = _.assign({}, cloneDeep(object.constructor.options), cloneDeep(options)); + + _.forOwn(options, function(value, key) { + if (value != null) { + // Add event listeners. + if (/^on[A-Z]/.test(key)) { + _.each(key.split(' '), function(key) { + object.on(key.slice(2).toLowerCase(), value); + }); + } else if (!_.has(object, key)) { + object[key] = cloneDeep(value); + } + } + }); + } + + /*------------------------------------------------------------------------*/ + + /** + * Handles cycling/completing the deferred benchmark. + * + * @memberOf Benchmark.Deferred + */ + function resolve() { + var deferred = this, + clone = deferred.benchmark, + bench = clone._original; + + if (bench.aborted) { + // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete. + deferred.teardown(); + clone.running = false; + cycle(deferred); + } + else if (++deferred.cycles < clone.count) { + clone.compiled.call(deferred, context, timer); + } + else { + timer.stop(deferred); + deferred.teardown(); + delay(clone, function() { cycle(deferred); }); + } + } + + /*------------------------------------------------------------------------*/ + + /** + * A generic `Array#filter` like method. + * + * @static + * @memberOf Benchmark + * @param {Array} array The array to iterate over. + * @param {Function|string} callback The function/alias called per iteration. + * @returns {Array} A new array of values that passed callback filter. + * @example + * + * // get odd numbers + * Benchmark.filter([1, 2, 3, 4, 5], function(n) { + * return n % 2; + * }); // -> [1, 3, 5]; + * + * // get fastest benchmarks + * Benchmark.filter(benches, 'fastest'); + * + * // get slowest benchmarks + * Benchmark.filter(benches, 'slowest'); + * + * // get benchmarks that completed without erroring + * Benchmark.filter(benches, 'successful'); + */ + function filter(array, callback) { + if (callback === 'successful') { + // Callback to exclude those that are errored, unrun, or have hz of Infinity. + callback = function(bench) { + return bench.cycles && _.isFinite(bench.hz) && !bench.error; + }; + } + else if (callback === 'fastest' || callback === 'slowest') { + // Get successful, sort by period + margin of error, and filter fastest/slowest. + var result = filter(array, 'successful').sort(function(a, b) { + a = a.stats; b = b.stats; + return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback === 'fastest' ? 1 : -1); + }); + + return _.filter(result, function(bench) { + return result[0].compare(bench) == 0; + }); + } + return _.filter(array, callback); + } + + /** + * Converts a number to a more readable comma-separated string representation. + * + * @static + * @memberOf Benchmark + * @param {number} number The number to convert. + * @returns {string} The more readable string representation. + */ + function formatNumber(number) { + number = String(number).split('.'); + return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') + + (number[1] ? '.' + number[1] : ''); + } + + /** + * Invokes a method on all items in an array. + * + * @static + * @memberOf Benchmark + * @param {Array} benches Array of benchmarks to iterate over. + * @param {Object|string} name The name of the method to invoke OR options object. + * @param {...*} [args] Arguments to invoke the method with. + * @returns {Array} A new array of values returned from each method invoked. + * @example + * + * // invoke `reset` on all benchmarks + * Benchmark.invoke(benches, 'reset'); + * + * // invoke `emit` with arguments + * Benchmark.invoke(benches, 'emit', 'complete', listener); + * + * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks + * Benchmark.invoke(benches, { + * + * // invoke the `run` method + * 'name': 'run', + * + * // pass a single argument + * 'args': true, + * + * // treat as queue, removing benchmarks from front of `benches` until empty + * 'queued': true, + * + * // called before any benchmarks have been invoked. + * 'onStart': onStart, + * + * // called between invoking benchmarks + * 'onCycle': onCycle, + * + * // called after all benchmarks have been invoked. + * 'onComplete': onComplete + * }); + */ + function invoke(benches, name) { + var args, + bench, + queued, + index = -1, + eventProps = { 'currentTarget': benches }, + options = { 'onStart': _.noop, 'onCycle': _.noop, 'onComplete': _.noop }, + result = _.toArray(benches); + + /** + * Invokes the method of the current object and if synchronous, fetches the next. + */ + function execute() { + var listeners, + async = isAsync(bench); + + if (async) { + // Use `getNext` as the first listener. + bench.on('complete', getNext); + listeners = bench.events.complete; + listeners.splice(0, 0, listeners.pop()); + } + // Execute method. + result[index] = _.isFunction(bench && bench[name]) ? bench[name].apply(bench, args) : undefined; + // If synchronous return `true` until finished. + return !async && getNext(); + } + + /** + * Fetches the next bench or executes `onComplete` callback. + */ + function getNext(event) { + var cycleEvent, + last = bench, + async = isAsync(last); + + if (async) { + last.off('complete', getNext); + last.emit('complete'); + } + // Emit "cycle" event. + eventProps.type = 'cycle'; + eventProps.target = last; + cycleEvent = Event(eventProps); + options.onCycle.call(benches, cycleEvent); + + // Choose next benchmark if not exiting early. + if (!cycleEvent.aborted && raiseIndex() !== false) { + bench = queued ? benches[0] : result[index]; + if (isAsync(bench)) { + delay(bench, execute); + } + else if (async) { + // Resume execution if previously asynchronous but now synchronous. + while (execute()) {} + } + else { + // Continue synchronous execution. + return true; + } + } else { + // Emit "complete" event. + eventProps.type = 'complete'; + options.onComplete.call(benches, Event(eventProps)); + } + // When used as a listener `event.aborted = true` will cancel the rest of + // the "complete" listeners because they were already called above and when + // used as part of `getNext` the `return false` will exit the execution while-loop. + if (event) { + event.aborted = true; + } else { + return false; + } + } + + /** + * Checks if invoking `Benchmark#run` with asynchronous cycles. + */ + function isAsync(object) { + // Avoid using `instanceof` here because of IE memory leak issues with host objects. + var async = args[0] && args[0].async; + return name == 'run' && (object instanceof Benchmark) && + ((async == null ? object.options.async : async) && support.timeout || object.defer); + } + + /** + * Raises `index` to the next defined index or returns `false`. + */ + function raiseIndex() { + index++; + + // If queued remove the previous bench. + if (queued && index > 0) { + shift.call(benches); + } + // If we reached the last index then return `false`. + return (queued ? benches.length : index < result.length) + ? index + : (index = false); + } + // Juggle arguments. + if (_.isString(name)) { + // 2 arguments (array, name). + args = slice.call(arguments, 2); + } else { + // 2 arguments (array, options). + options = _.assign(options, name); + name = options.name; + args = _.isArray(args = 'args' in options ? options.args : []) ? args : [args]; + queued = options.queued; + } + // Start iterating over the array. + if (raiseIndex() !== false) { + // Emit "start" event. + bench = result[index]; + eventProps.type = 'start'; + eventProps.target = bench; + options.onStart.call(benches, Event(eventProps)); + + // End early if the suite was aborted in an "onStart" listener. + if (name == 'run' && (benches instanceof Suite) && benches.aborted) { + // Emit "cycle" event. + eventProps.type = 'cycle'; + options.onCycle.call(benches, Event(eventProps)); + // Emit "complete" event. + eventProps.type = 'complete'; + options.onComplete.call(benches, Event(eventProps)); + } + // Start method execution. + else { + if (isAsync(bench)) { + delay(bench, execute); + } else { + while (execute()) {} + } + } + } + return result; + } + + /** + * Creates a string of joined array values or object key-value pairs. + * + * @static + * @memberOf Benchmark + * @param {Array|Object} object The object to operate on. + * @param {string} [separator1=','] The separator used between key-value pairs. + * @param {string} [separator2=': '] The separator used between keys and values. + * @returns {string} The joined result. + */ + function join(object, separator1, separator2) { + var result = [], + length = (object = Object(object)).length, + arrayLike = length === length >>> 0; + + separator2 || (separator2 = ': '); + _.each(object, function(value, key) { + result.push(arrayLike ? value : key + separator2 + value); + }); + return result.join(separator1 || ','); + } + + /*------------------------------------------------------------------------*/ + + /** + * Aborts all benchmarks in the suite. + * + * @name abort + * @memberOf Benchmark.Suite + * @returns {Object} The suite instance. + */ + function abortSuite() { + var event, + suite = this, + resetting = calledBy.resetSuite; + + if (suite.running) { + event = Event('abort'); + suite.emit(event); + if (!event.cancelled || resetting) { + // Avoid infinite recursion. + calledBy.abortSuite = true; + suite.reset(); + delete calledBy.abortSuite; + + if (!resetting) { + suite.aborted = true; + invoke(suite, 'abort'); + } + } + } + return suite; + } + + /** + * Adds a test to the benchmark suite. + * + * @memberOf Benchmark.Suite + * @param {string} name A name to identify the benchmark. + * @param {Function|string} fn The test to benchmark. + * @param {Object} [options={}] Options object. + * @returns {Object} The suite instance. + * @example + * + * // basic usage + * suite.add(fn); + * + * // or using a name first + * suite.add('foo', fn); + * + * // or with options + * suite.add('foo', fn, { + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + * + * // or name and options + * suite.add('foo', { + * 'fn': fn, + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + * + * // or options only + * suite.add({ + * 'name': 'foo', + * 'fn': fn, + * 'onCycle': onCycle, + * 'onComplete': onComplete + * }); + */ + function add(name, fn, options) { + var suite = this, + bench = new Benchmark(name, fn, options), + event = Event({ 'type': 'add', 'target': bench }); + + if (suite.emit(event), !event.cancelled) { + suite.push(bench); + } + return suite; + } + + /** + * Creates a new suite with cloned benchmarks. + * + * @name clone + * @memberOf Benchmark.Suite + * @param {Object} options Options object to overwrite cloned options. + * @returns {Object} The new suite instance. + */ + function cloneSuite(options) { + var suite = this, + result = new suite.constructor(_.assign({}, suite.options, options)); + + // Copy own properties. + _.forOwn(suite, function(value, key) { + if (!_.has(result, key)) { + result[key] = value && _.isFunction(value.clone) + ? value.clone() + : cloneDeep(value); + } + }); + return result; + } + + /** + * An `Array#filter` like method. + * + * @name filter + * @memberOf Benchmark.Suite + * @param {Function|string} callback The function/alias called per iteration. + * @returns {Object} A new suite of benchmarks that passed callback filter. + */ + function filterSuite(callback) { + var suite = this, + result = new suite.constructor(suite.options); + + result.push.apply(result, filter(suite, callback)); + return result; + } + + /** + * Resets all benchmarks in the suite. + * + * @name reset + * @memberOf Benchmark.Suite + * @returns {Object} The suite instance. + */ + function resetSuite() { + var event, + suite = this, + aborting = calledBy.abortSuite; + + if (suite.running && !aborting) { + // No worries, `resetSuite()` is called within `abortSuite()`. + calledBy.resetSuite = true; + suite.abort(); + delete calledBy.resetSuite; + } + // Reset if the state has changed. + else if ((suite.aborted || suite.running) && + (suite.emit(event = Event('reset')), !event.cancelled)) { + suite.aborted = suite.running = false; + if (!aborting) { + invoke(suite, 'reset'); + } + } + return suite; + } + + /** + * Runs the suite. + * + * @name run + * @memberOf Benchmark.Suite + * @param {Object} [options={}] Options object. + * @returns {Object} The suite instance. + * @example + * + * // basic usage + * suite.run(); + * + * // or with options + * suite.run({ 'async': true, 'queued': true }); + */ + function runSuite(options) { + var suite = this; + + suite.reset(); + suite.running = true; + options || (options = {}); + + invoke(suite, { + 'name': 'run', + 'args': options, + 'queued': options.queued, + 'onStart': function(event) { + suite.emit(event); + }, + 'onCycle': function(event) { + var bench = event.target; + if (bench.error) { + suite.emit({ 'type': 'error', 'target': bench }); + } + suite.emit(event); + event.aborted = suite.aborted; + }, + 'onComplete': function(event) { + suite.running = false; + suite.emit(event); + } + }); + return suite; + } + + /*------------------------------------------------------------------------*/ + + /** + * Executes all registered listeners of the specified event type. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {Object|string} type The event type or object. + * @param {...*} [args] Arguments to invoke the listener with. + * @returns {*} Returns the return value of the last listener executed. + */ + function emit(type) { + var listeners, + object = this, + event = Event(type), + events = object.events, + args = (arguments[0] = event, arguments); + + event.currentTarget || (event.currentTarget = object); + event.target || (event.target = object); + delete event.result; + + if (events && (listeners = _.has(events, event.type) && events[event.type])) { + _.each(listeners.slice(), function(listener) { + if ((event.result = listener.apply(object, args)) === false) { + event.cancelled = true; + } + return !event.aborted; + }); + } + return event.result; + } + + /** + * Returns an array of event listeners for a given type that can be manipulated + * to add or remove listeners. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {string} type The event type. + * @returns {Array} The listeners array. + */ + function listeners(type) { + var object = this, + events = object.events || (object.events = {}); + + return _.has(events, type) ? events[type] : (events[type] = []); + } + + /** + * Unregisters a listener for the specified event type(s), + * or unregisters all listeners for the specified event type(s), + * or unregisters all listeners for all event types. + * + * @memberOf Benchmark, Benchmark.Suite + * @param {string} [type] The event type. + * @param {Function} [listener] The function to unregister. + * @returns {Object} The current instance. + * @example + * + * // unregister a listener for an event type + * bench.off('cycle', listener); + * + * // unregister a listener for multiple event types + * bench.off('start cycle', listener); + * + * // unregister all listeners for an event type + * bench.off('cycle'); + * + * // unregister all listeners for multiple event types + * bench.off('start cycle complete'); + * + * // unregister all listeners for all event types + * bench.off(); + */ + function off(type, listener) { + var object = this, + events = object.events; + + if (!events) { + return object; + } + _.each(type ? type.split(' ') : events, function(listeners, type) { + var index; + if (typeof listeners == 'string') { + type = listeners; + listeners = _.has(events, type) && events[type]; + } + if (listeners) { + if (listener) { + index = _.indexOf(listeners, listener); + if (index > -1) { + listeners.splice(index, 1); + } + } else { + listeners.length = 0; + } + } + }); + return object; + } + + /** + * Registers a listener for the specified event type(s). + * + * @memberOf Benchmark, Benchmark.Suite + * @param {string} type The event type. + * @param {Function} listener The function to register. + * @returns {Object} The current instance. + * @example + * + * // register a listener for an event type + * bench.on('cycle', listener); + * + * // register a listener for multiple event types + * bench.on('start cycle', listener); + */ + function on(type, listener) { + var object = this, + events = object.events || (object.events = {}); + + _.each(type.split(' '), function(type) { + (_.has(events, type) + ? events[type] + : (events[type] = []) + ).push(listener); + }); + return object; + } + + /*------------------------------------------------------------------------*/ + + /** + * Aborts the benchmark without recording times. + * + * @memberOf Benchmark + * @returns {Object} The benchmark instance. + */ + function abort() { + var event, + bench = this, + resetting = calledBy.reset; + + if (bench.running) { + event = Event('abort'); + bench.emit(event); + if (!event.cancelled || resetting) { + // Avoid infinite recursion. + calledBy.abort = true; + bench.reset(); + delete calledBy.abort; + + if (support.timeout) { + clearTimeout(bench._timerId); + delete bench._timerId; + } + if (!resetting) { + bench.aborted = true; + bench.running = false; + } + } + } + return bench; + } + + /** + * Creates a new benchmark using the same test and options. + * + * @memberOf Benchmark + * @param {Object} options Options object to overwrite cloned options. + * @returns {Object} The new benchmark instance. + * @example + * + * var bizarro = bench.clone({ + * 'name': 'doppelganger' + * }); + */ + function clone(options) { + var bench = this, + result = new bench.constructor(_.assign({}, bench, options)); + + // Correct the `options` object. + result.options = _.assign({}, cloneDeep(bench.options), cloneDeep(options)); + + // Copy own custom properties. + _.forOwn(bench, function(value, key) { + if (!_.has(result, key)) { + result[key] = cloneDeep(value); + } + }); + + return result; + } + + /** + * Determines if a benchmark is faster than another. + * + * @memberOf Benchmark + * @param {Object} other The benchmark to compare. + * @returns {number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate. + */ + function compare(other) { + var bench = this; + + // Exit early if comparing the same benchmark. + if (bench == other) { + return 0; + } + var critical, + zStat, + sample1 = bench.stats.sample, + sample2 = other.stats.sample, + size1 = sample1.length, + size2 = sample2.length, + maxSize = max(size1, size2), + minSize = min(size1, size2), + u1 = getU(sample1, sample2), + u2 = getU(sample2, sample1), + u = min(u1, u2); + + function getScore(xA, sampleB) { + return _.reduce(sampleB, function(total, xB) { + return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5); + }, 0); + } + + function getU(sampleA, sampleB) { + return _.reduce(sampleA, function(total, xA) { + return total + getScore(xA, sampleB); + }, 0); + } + + function getZ(u) { + return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12); + } + // Reject the null hypothesis the two samples come from the + // same population (i.e. have the same median) if... + if (size1 + size2 > 30) { + // ...the z-stat is greater than 1.96 or less than -1.96 + // http://www.statisticslectures.com/topics/mannwhitneyu/ + zStat = getZ(u); + return abs(zStat) > 1.96 ? (u == u1 ? 1 : -1) : 0; + } + // ...the U value is less than or equal the critical U value. + critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3]; + return u <= critical ? (u == u1 ? 1 : -1) : 0; + } + + /** + * Reset properties and abort if running. + * + * @memberOf Benchmark + * @returns {Object} The benchmark instance. + */ + function reset() { + var bench = this; + if (bench.running && !calledBy.abort) { + // No worries, `reset()` is called within `abort()`. + calledBy.reset = true; + bench.abort(); + delete calledBy.reset; + return bench; + } + var event, + index = 0, + changes = [], + queue = []; + + // A non-recursive solution to check if properties have changed. + // For more information see http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4. + var data = { + 'destination': bench, + 'source': _.assign({}, cloneDeep(bench.constructor.prototype), cloneDeep(bench.options)) + }; + + do { + _.forOwn(data.source, function(value, key) { + var changed, + destination = data.destination, + currValue = destination[key]; + + // Skip pseudo private properties like `_timerId` which could be a + // Java object in environments like RingoJS. + if (key.charAt(0) == '_') { + return; + } + if (value && typeof value == 'object') { + if (_.isArray(value)) { + // Check if an array value has changed to a non-array value. + if (!_.isArray(currValue)) { + changed = currValue = []; + } + // Check if an array has changed its length. + if (currValue.length != value.length) { + changed = currValue = currValue.slice(0, value.length); + currValue.length = value.length; + } + } + // Check if an object has changed to a non-object value. + else if (!currValue || typeof currValue != 'object') { + changed = currValue = {}; + } + // Register a changed object. + if (changed) { + changes.push({ 'destination': destination, 'key': key, 'value': currValue }); + } + queue.push({ 'destination': currValue, 'source': value }); + } + // Register a changed primitive. + else if (value !== currValue && !(value == null || _.isFunction(value))) { + changes.push({ 'destination': destination, 'key': key, 'value': value }); + } + }); + } + while ((data = queue[index++])); + + // If changed emit the `reset` event and if it isn't cancelled reset the benchmark. + if (changes.length && (bench.emit(event = Event('reset')), !event.cancelled)) { + _.each(changes, function(data) { + data.destination[data.key] = data.value; + }); + } + return bench; + } + + /** + * Displays relevant benchmark information when coerced to a string. + * + * @name toString + * @memberOf Benchmark + * @returns {string} A string representation of the benchmark instance. + */ + function toStringBench() { + var bench = this, + error = bench.error, + hz = bench.hz, + id = bench.id, + stats = bench.stats, + size = stats.sample.length, + pm = '\xb1', + result = bench.name || (_.isNaN(id) ? id : ''); + + if (error) { + var errorStr; + if (!_.isObject(error)) { + errorStr = String(error); + } else if (!_.isError(Error)) { + errorStr = join(error); + } else { + // Error#name and Error#message properties are non-enumerable. + errorStr = join(_.assign({ 'name': error.name, 'message': error.message }, error)); + } + result += ': ' + errorStr; + } + else { + result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm + + stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)'; + } + return result; + } + + /*------------------------------------------------------------------------*/ + + /** + * Clocks the time taken to execute a test per cycle (secs). + * + * @private + * @param {Object} bench The benchmark instance. + * @returns {number} The time taken. + */ + function clock() { + var options = Benchmark.options, + templateData = {}, + timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }]; + + // Lazy define for hi-res timers. + clock = function(clone) { + var deferred; + + if (clone instanceof Deferred) { + deferred = clone; + clone = deferred.benchmark; + } + var bench = clone._original, + stringable = isStringable(bench.fn), + count = bench.count = clone.count, + decompilable = stringable || (support.decompilation && (clone.setup !== _.noop || clone.teardown !== _.noop)), + id = bench.id, + name = bench.name || (typeof id == 'number' ? '' : id), + result = 0; + + // Init `minTime` if needed. + clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime); + + // Compile in setup/teardown functions and the test loop. + // Create a new compiled test, instead of using the cached `bench.compiled`, + // to avoid potential engine optimizations enabled over the life of the test. + var funcBody = deferred + ? 'var d#=this,${fnArg}=d#,m#=d#.benchmark._original,f#=m#.fn,su#=m#.setup,td#=m#.teardown;' + + // When `deferred.cycles` is `0` then... + 'if(!d#.cycles){' + + // set `deferred.fn`, + 'd#.fn=function(){var ${fnArg}=d#;if(typeof f#=="function"){try{${fn}\n}catch(e#){f#(d#)}}else{${fn}\n}};' + + // set `deferred.teardown`, + 'd#.teardown=function(){d#.cycles=0;if(typeof td#=="function"){try{${teardown}\n}catch(e#){td#()}}else{${teardown}\n}};' + + // execute the benchmark's `setup`, + 'if(typeof su#=="function"){try{${setup}\n}catch(e#){su#()}}else{${setup}\n};' + + // start timer, + 't#.start(d#);' + + // and then execute `deferred.fn` and return a dummy object. + '}d#.fn();return{uid:"${uid}"}' + + : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count,n#=t#.ns;${setup}\n${begin};' + + 'while(i#--){${fn}\n}${end};${teardown}\nreturn{elapsed:r#,uid:"${uid}"}'; + + var compiled = bench.compiled = clone.compiled = createCompiled(bench, decompilable, deferred, funcBody), + isEmpty = !(templateData.fn || stringable); + + try { + if (isEmpty) { + // Firefox may remove dead code from `Function#toString` results. + // For more information see http://bugzil.la/536085. + throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.'); + } + else if (!deferred) { + // Pretest to determine if compiled code exits early, usually by a + // rogue `return` statement, by checking for a return object with the uid. + bench.count = 1; + compiled = decompilable && (compiled.call(bench, context, timer) || {}).uid == templateData.uid && compiled; + bench.count = count; + } + } catch(e) { + compiled = null; + clone.error = e || new Error(String(e)); + bench.count = count; + } + // Fallback when a test exits early or errors during pretest. + if (!compiled && !deferred && !isEmpty) { + funcBody = ( + stringable || (decompilable && !clone.error) + ? 'function f#(){${fn}\n}var r#,s#,m#=this,i#=m#.count' + : 'var r#,s#,m#=this,f#=m#.fn,i#=m#.count' + ) + + ',n#=t#.ns;${setup}\n${begin};m#.f#=f#;while(i#--){m#.f#()}${end};' + + 'delete m#.f#;${teardown}\nreturn{elapsed:r#}'; + + compiled = createCompiled(bench, decompilable, deferred, funcBody); + + try { + // Pretest one more time to check for errors. + bench.count = 1; + compiled.call(bench, context, timer); + bench.count = count; + delete clone.error; + } + catch(e) { + bench.count = count; + if (!clone.error) { + clone.error = e || new Error(String(e)); + } + } + } + // If no errors run the full test loop. + if (!clone.error) { + compiled = bench.compiled = clone.compiled = createCompiled(bench, decompilable, deferred, funcBody); + result = compiled.call(deferred || bench, context, timer).elapsed; + } + return result; + }; + + /*----------------------------------------------------------------------*/ + + /** + * Creates a compiled function from the given function `body`. + */ + function createCompiled(bench, decompilable, deferred, body) { + var fn = bench.fn, + fnArg = deferred ? getFirstArgument(fn) || 'deferred' : ''; + + templateData.uid = uid + uidCounter++; + + _.assign(templateData, { + 'setup': decompilable ? getSource(bench.setup) : interpolate('m#.setup()'), + 'fn': decompilable ? getSource(fn) : interpolate('m#.fn(' + fnArg + ')'), + 'fnArg': fnArg, + 'teardown': decompilable ? getSource(bench.teardown) : interpolate('m#.teardown()') + }); + + // Use API of chosen timer. + if (timer.unit == 'ns') { + _.assign(templateData, { + 'begin': interpolate('s#=n#()'), + 'end': interpolate('r#=n#(s#);r#=r#[0]+(r#[1]/1e9)') + }); + } + else if (timer.unit == 'us') { + if (timer.ns.stop) { + _.assign(templateData, { + 'begin': interpolate('s#=n#.start()'), + 'end': interpolate('r#=n#.microseconds()/1e6') + }); + } else { + _.assign(templateData, { + 'begin': interpolate('s#=n#()'), + 'end': interpolate('r#=(n#()-s#)/1e6') + }); + } + } + else if (timer.ns.now) { + _.assign(templateData, { + 'begin': interpolate('s#=n#.now()'), + 'end': interpolate('r#=(n#.now()-s#)/1e3') + }); + } + else { + _.assign(templateData, { + 'begin': interpolate('s#=new n#().getTime()'), + 'end': interpolate('r#=(new n#().getTime()-s#)/1e3') + }); + } + // Define `timer` methods. + timer.start = createFunction( + interpolate('o#'), + interpolate('var n#=this.ns,${begin};o#.elapsed=0;o#.timeStamp=s#') + ); + + timer.stop = createFunction( + interpolate('o#'), + interpolate('var n#=this.ns,s#=o#.timeStamp,${end};o#.elapsed=r#') + ); + + // Create compiled test. + return createFunction( + interpolate('window,t#'), + 'var global = window, clearTimeout = global.clearTimeout, setTimeout = global.setTimeout;\n' + + interpolate(body) + ); + } + + /** + * Gets the current timer's minimum resolution (secs). + */ + function getRes(unit) { + var measured, + begin, + count = 30, + divisor = 1e3, + ns = timer.ns, + sample = []; + + // Get average smallest measurable time. + while (count--) { + if (unit == 'us') { + divisor = 1e6; + if (ns.stop) { + ns.start(); + while (!(measured = ns.microseconds())) {} + } else { + begin = ns(); + while (!(measured = ns() - begin)) {} + } + } + else if (unit == 'ns') { + divisor = 1e9; + begin = (begin = ns())[0] + (begin[1] / divisor); + while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) {} + divisor = 1; + } + else if (ns.now) { + begin = ns.now(); + while (!(measured = ns.now() - begin)) {} + } + else { + begin = new ns().getTime(); + while (!(measured = new ns().getTime() - begin)) {} + } + // Check for broken timers. + if (measured > 0) { + sample.push(measured); + } else { + sample.push(Infinity); + break; + } + } + // Convert to seconds. + return getMean(sample) / divisor; + } + + /** + * Interpolates a given template string. + */ + function interpolate(string) { + // Replaces all occurrences of `#` with a unique number and template tokens with content. + return _.template(string.replace(/\#/g, /\d+/.exec(templateData.uid)))(templateData); + } + + /*----------------------------------------------------------------------*/ + + // Detect Chrome's microsecond timer: + // enable benchmarking via the --enable-benchmarking command + // line switch in at least Chrome 7 to use chrome.Interval + try { + if ((timer.ns = new (context.chrome || context.chromium).Interval)) { + timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' }); + } + } catch(e) {} + + // Detect Node.js's nanosecond resolution timer available in Node.js >= 0.8. + if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') { + timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' }); + } + // Pick timer with highest resolution. + timer = _.minBy(timers, 'res'); + + // Error if there are no working timers. + if (timer.res == Infinity) { + throw new Error('Benchmark.js was unable to find a working timer.'); + } + // Resolve time span required to achieve a percent uncertainty of at most 1%. + // For more information see http://spiff.rit.edu/classes/phys273/uncert/uncert.html. + options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05)); + return clock.apply(null, arguments); + } + + /*------------------------------------------------------------------------*/ + + /** + * Computes stats on benchmark results. + * + * @private + * @param {Object} bench The benchmark instance. + * @param {Object} options The options object. + */ + function compute(bench, options) { + options || (options = {}); + + var async = options.async, + elapsed = 0, + initCount = bench.initCount, + minSamples = bench.minSamples, + queue = [], + sample = bench.stats.sample; + + /** + * Adds a clone to the queue. + */ + function enqueue() { + queue.push(bench.clone({ + '_original': bench, + 'events': { + 'abort': [update], + 'cycle': [update], + 'error': [update], + 'start': [update] + } + })); + } + + /** + * Updates the clone/original benchmarks to keep their data in sync. + */ + function update(event) { + var clone = this, + type = event.type; + + if (bench.running) { + if (type == 'start') { + // Note: `clone.minTime` prop is inited in `clock()`. + clone.count = bench.initCount; + } + else { + if (type == 'error') { + bench.error = clone.error; + } + if (type == 'abort') { + bench.abort(); + bench.emit('cycle'); + } else { + event.currentTarget = event.target = bench; + bench.emit(event); + } + } + } else if (bench.aborted) { + // Clear abort listeners to avoid triggering bench's abort/cycle again. + clone.events.abort.length = 0; + clone.abort(); + } + } + + /** + * Determines if more clones should be queued or if cycling should stop. + */ + function evaluate(event) { + var critical, + df, + mean, + moe, + rme, + sd, + sem, + variance, + clone = event.target, + done = bench.aborted, + now = _.now(), + size = sample.push(clone.times.period), + maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime, + times = bench.times, + varOf = function(sum, x) { return sum + pow(x - mean, 2); }; + + // Exit early for aborted or unclockable tests. + if (done || clone.hz == Infinity) { + maxedOut = !(size = sample.length = queue.length = 0); + } + + if (!done) { + // Compute the sample mean (estimate of the population mean). + mean = getMean(sample); + // Compute the sample variance (estimate of the population variance). + variance = _.reduce(sample, varOf, 0) / (size - 1) || 0; + // Compute the sample standard deviation (estimate of the population standard deviation). + sd = sqrt(variance); + // Compute the standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean). + sem = sd / sqrt(size); + // Compute the degrees of freedom. + df = size - 1; + // Compute the critical value. + critical = tTable[Math.round(df) || 1] || tTable.infinity; + // Compute the margin of error. + moe = sem * critical; + // Compute the relative margin of error. + rme = (moe / mean) * 100 || 0; + + _.assign(bench.stats, { + 'deviation': sd, + 'mean': mean, + 'moe': moe, + 'rme': rme, + 'sem': sem, + 'variance': variance + }); + + // Abort the cycle loop when the minimum sample size has been collected + // and the elapsed time exceeds the maximum time allowed per benchmark. + // We don't count cycle delays toward the max time because delays may be + // increased by browsers that clamp timeouts for inactive tabs. For more + // information see https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs. + if (maxedOut) { + // Reset the `initCount` in case the benchmark is rerun. + bench.initCount = initCount; + bench.running = false; + done = true; + times.elapsed = (now - times.timeStamp) / 1e3; + } + if (bench.hz != Infinity) { + bench.hz = 1 / mean; + times.cycle = mean * bench.count; + times.period = mean; + } + } + // If time permits, increase sample size to reduce the margin of error. + if (queue.length < 2 && !maxedOut) { + enqueue(); + } + // Abort the `invoke` cycle when done. + event.aborted = done; + } + + // Init queue and begin. + enqueue(); + invoke(queue, { + 'name': 'run', + 'args': { 'async': async }, + 'queued': true, + 'onCycle': evaluate, + 'onComplete': function() { bench.emit('complete'); } + }); + } + + /*------------------------------------------------------------------------*/ + + /** + * Cycles a benchmark until a run `count` can be established. + * + * @private + * @param {Object} clone The cloned benchmark instance. + * @param {Object} options The options object. + */ + function cycle(clone, options) { + options || (options = {}); + + var deferred; + if (clone instanceof Deferred) { + deferred = clone; + clone = clone.benchmark; + } + var clocked, + cycles, + divisor, + event, + minTime, + period, + async = options.async, + bench = clone._original, + count = clone.count, + times = clone.times; + + // Continue, if not aborted between cycles. + if (clone.running) { + // `minTime` is set to `Benchmark.options.minTime` in `clock()`. + cycles = ++clone.cycles; + clocked = deferred ? deferred.elapsed : clock(clone); + minTime = clone.minTime; + + if (cycles > bench.cycles) { + bench.cycles = cycles; + } + if (clone.error) { + event = Event('error'); + event.message = clone.error; + clone.emit(event); + if (!event.cancelled) { + clone.abort(); + } + } + } + // Continue, if not errored. + if (clone.running) { + // Compute the time taken to complete last test cycle. + bench.times.cycle = times.cycle = clocked; + // Compute the seconds per operation. + period = bench.times.period = times.period = clocked / count; + // Compute the ops per second. + bench.hz = clone.hz = 1 / period; + // Avoid working our way up to this next time. + bench.initCount = clone.initCount = count; + // Do we need to do another cycle? + clone.running = clocked < minTime; + + if (clone.running) { + // Tests may clock at `0` when `initCount` is a small number, + // to avoid that we set its count to something a bit higher. + if (!clocked && (divisor = divisors[clone.cycles]) != null) { + count = floor(4e6 / divisor); + } + // Calculate how many more iterations it will take to achieve the `minTime`. + if (count <= clone.count) { + count += Math.ceil((minTime - clocked) / period); + } + clone.running = count != Infinity; + } + } + // Should we exit early? + event = Event('cycle'); + clone.emit(event); + if (event.aborted) { + clone.abort(); + } + // Figure out what to do next. + if (clone.running) { + // Start a new cycle. + clone.count = count; + if (deferred) { + clone.compiled.call(deferred, context, timer); + } else if (async) { + delay(clone, function() { cycle(clone, options); }); + } else { + cycle(clone); + } + } + else { + // Fix TraceMonkey bug associated with clock fallbacks. + // For more information see http://bugzil.la/509069. + if (support.browser) { + runScript(uid + '=1;delete ' + uid); + } + // We're done. + clone.emit('complete'); + } + } + + /*------------------------------------------------------------------------*/ + + /** + * Runs the benchmark. + * + * @memberOf Benchmark + * @param {Object} [options={}] Options object. + * @returns {Object} The benchmark instance. + * @example + * + * // basic usage + * bench.run(); + * + * // or with options + * bench.run({ 'async': true }); + */ + function run(options) { + var bench = this, + event = Event('start'); + + // Set `running` to `false` so `reset()` won't call `abort()`. + bench.running = false; + bench.reset(); + bench.running = true; + + bench.count = bench.initCount; + bench.times.timeStamp = _.now(); + bench.emit(event); + + if (!event.cancelled) { + options = { 'async': ((options = options && options.async) == null ? bench.async : options) && support.timeout }; + + // For clones created within `compute()`. + if (bench._original) { + if (bench.defer) { + Deferred(bench); + } else { + cycle(bench, options); + } + } + // For original benchmarks. + else { + compute(bench, options); + } + } + return bench; + } + + /*------------------------------------------------------------------------*/ + + // Firefox 1 erroneously defines variable and argument names of functions on + // the function itself as non-configurable properties with `undefined` values. + // The bugginess continues as the `Benchmark` constructor has an argument + // named `options` and Firefox 1 will not assign a value to `Benchmark.options`, + // making it non-writable in the process, unless it is the first property + // assigned by for-in loop of `_.assign()`. + _.assign(Benchmark, { + + /** + * The default options copied by benchmark instances. + * + * @static + * @memberOf Benchmark + * @type Object + */ + 'options': { + + /** + * A flag to indicate that benchmark cycles will execute asynchronously + * by default. + * + * @memberOf Benchmark.options + * @type boolean + */ + 'async': false, + + /** + * A flag to indicate that the benchmark clock is deferred. + * + * @memberOf Benchmark.options + * @type boolean + */ + 'defer': false, + + /** + * The delay between test cycles (secs). + * @memberOf Benchmark.options + * @type number + */ + 'delay': 0.005, + + /** + * Displayed by `Benchmark#toString` when a `name` is not available + * (auto-generated if absent). + * + * @memberOf Benchmark.options + * @type string + */ + 'id': undefined, + + /** + * The default number of times to execute a test on a benchmark's first cycle. + * + * @memberOf Benchmark.options + * @type number + */ + 'initCount': 1, + + /** + * The maximum time a benchmark is allowed to run before finishing (secs). + * + * Note: Cycle delays aren't counted toward the maximum time. + * + * @memberOf Benchmark.options + * @type number + */ + 'maxTime': 5, + + /** + * The minimum sample size required to perform statistical analysis. + * + * @memberOf Benchmark.options + * @type number + */ + 'minSamples': 5, + + /** + * The time needed to reduce the percent uncertainty of measurement to 1% (secs). + * + * @memberOf Benchmark.options + * @type number + */ + 'minTime': 0, + + /** + * The name of the benchmark. + * + * @memberOf Benchmark.options + * @type string + */ + 'name': undefined, + + /** + * An event listener called when the benchmark is aborted. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onAbort': undefined, + + /** + * An event listener called when the benchmark completes running. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onComplete': undefined, + + /** + * An event listener called after each run cycle. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onCycle': undefined, + + /** + * An event listener called when a test errors. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onError': undefined, + + /** + * An event listener called when the benchmark is reset. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onReset': undefined, + + /** + * An event listener called when the benchmark starts running. + * + * @memberOf Benchmark.options + * @type Function + */ + 'onStart': undefined + }, + + /** + * Platform object with properties describing things like browser name, + * version, and operating system. See [`platform.js`](https://mths.be/platform). + * + * @static + * @memberOf Benchmark + * @type Object + */ + 'platform': context.platform || require('platform') || ({ + 'description': context.navigator && context.navigator.userAgent || null, + 'layout': null, + 'product': null, + 'name': null, + 'manufacturer': null, + 'os': null, + 'prerelease': null, + 'version': null, + 'toString': function() { + return this.description || ''; + } + }), + + /** + * The semantic version number. + * + * @static + * @memberOf Benchmark + * @type string + */ + 'version': '2.1.2' + }); + + _.assign(Benchmark, { + 'filter': filter, + 'formatNumber': formatNumber, + 'invoke': invoke, + 'join': join, + 'runInContext': runInContext, + 'support': support + }); + + // Add lodash methods to Benchmark. + _.each(['each', 'forEach', 'forOwn', 'has', 'indexOf', 'map', 'reduce'], function(methodName) { + Benchmark[methodName] = _[methodName]; + }); + + /*------------------------------------------------------------------------*/ + + _.assign(Benchmark.prototype, { + + /** + * The number of times a test was executed. + * + * @memberOf Benchmark + * @type number + */ + 'count': 0, + + /** + * The number of cycles performed while benchmarking. + * + * @memberOf Benchmark + * @type number + */ + 'cycles': 0, + + /** + * The number of executions per second. + * + * @memberOf Benchmark + * @type number + */ + 'hz': 0, + + /** + * The compiled test function. + * + * @memberOf Benchmark + * @type {Function|string} + */ + 'compiled': undefined, + + /** + * The error object if the test failed. + * + * @memberOf Benchmark + * @type Object + */ + 'error': undefined, + + /** + * The test to benchmark. + * + * @memberOf Benchmark + * @type {Function|string} + */ + 'fn': undefined, + + /** + * A flag to indicate if the benchmark is aborted. + * + * @memberOf Benchmark + * @type boolean + */ + 'aborted': false, + + /** + * A flag to indicate if the benchmark is running. + * + * @memberOf Benchmark + * @type boolean + */ + 'running': false, + + /** + * Compiled into the test and executed immediately **before** the test loop. + * + * @memberOf Benchmark + * @type {Function|string} + * @example + * + * // basic usage + * var bench = Benchmark({ + * 'setup': function() { + * var c = this.count, + * element = document.getElementById('container'); + * while (c--) { + * element.appendChild(document.createElement('div')); + * } + * }, + * 'fn': function() { + * element.removeChild(element.lastChild); + * } + * }); + * + * // compiles to something like: + * var c = this.count, + * element = document.getElementById('container'); + * while (c--) { + * element.appendChild(document.createElement('div')); + * } + * var start = new Date; + * while (count--) { + * element.removeChild(element.lastChild); + * } + * var end = new Date - start; + * + * // or using strings + * var bench = Benchmark({ + * 'setup': '\ + * var a = 0;\n\ + * (function() {\n\ + * (function() {\n\ + * (function() {', + * 'fn': 'a += 1;', + * 'teardown': '\ + * }())\n\ + * }())\n\ + * }())' + * }); + * + * // compiles to something like: + * var a = 0; + * (function() { + * (function() { + * (function() { + * var start = new Date; + * while (count--) { + * a += 1; + * } + * var end = new Date - start; + * }()) + * }()) + * }()) + */ + 'setup': _.noop, + + /** + * Compiled into the test and executed immediately **after** the test loop. + * + * @memberOf Benchmark + * @type {Function|string} + */ + 'teardown': _.noop, + + /** + * An object of stats including mean, margin or error, and standard deviation. + * + * @memberOf Benchmark + * @type Object + */ + 'stats': { + + /** + * The margin of error. + * + * @memberOf Benchmark#stats + * @type number + */ + 'moe': 0, + + /** + * The relative margin of error (expressed as a percentage of the mean). + * + * @memberOf Benchmark#stats + * @type number + */ + 'rme': 0, + + /** + * The standard error of the mean. + * + * @memberOf Benchmark#stats + * @type number + */ + 'sem': 0, + + /** + * The sample standard deviation. + * + * @memberOf Benchmark#stats + * @type number + */ + 'deviation': 0, + + /** + * The sample arithmetic mean (secs). + * + * @memberOf Benchmark#stats + * @type number + */ + 'mean': 0, + + /** + * The array of sampled periods. + * + * @memberOf Benchmark#stats + * @type Array + */ + 'sample': [], + + /** + * The sample variance. + * + * @memberOf Benchmark#stats + * @type number + */ + 'variance': 0 + }, + + /** + * An object of timing data including cycle, elapsed, period, start, and stop. + * + * @memberOf Benchmark + * @type Object + */ + 'times': { + + /** + * The time taken to complete the last cycle (secs). + * + * @memberOf Benchmark#times + * @type number + */ + 'cycle': 0, + + /** + * The time taken to complete the benchmark (secs). + * + * @memberOf Benchmark#times + * @type number + */ + 'elapsed': 0, + + /** + * The time taken to execute the test once (secs). + * + * @memberOf Benchmark#times + * @type number + */ + 'period': 0, + + /** + * A timestamp of when the benchmark started (ms). + * + * @memberOf Benchmark#times + * @type number + */ + 'timeStamp': 0 + } + }); + + _.assign(Benchmark.prototype, { + 'abort': abort, + 'clone': clone, + 'compare': compare, + 'emit': emit, + 'listeners': listeners, + 'off': off, + 'on': on, + 'reset': reset, + 'run': run, + 'toString': toStringBench + }); + + /*------------------------------------------------------------------------*/ + + _.assign(Deferred.prototype, { + + /** + * The deferred benchmark instance. + * + * @memberOf Benchmark.Deferred + * @type Object + */ + 'benchmark': null, + + /** + * The number of deferred cycles performed while benchmarking. + * + * @memberOf Benchmark.Deferred + * @type number + */ + 'cycles': 0, + + /** + * The time taken to complete the deferred benchmark (secs). + * + * @memberOf Benchmark.Deferred + * @type number + */ + 'elapsed': 0, + + /** + * A timestamp of when the deferred benchmark started (ms). + * + * @memberOf Benchmark.Deferred + * @type number + */ + 'timeStamp': 0 + }); + + _.assign(Deferred.prototype, { + 'resolve': resolve + }); + + /*------------------------------------------------------------------------*/ + + _.assign(Event.prototype, { + + /** + * A flag to indicate if the emitters listener iteration is aborted. + * + * @memberOf Benchmark.Event + * @type boolean + */ + 'aborted': false, + + /** + * A flag to indicate if the default action is cancelled. + * + * @memberOf Benchmark.Event + * @type boolean + */ + 'cancelled': false, + + /** + * The object whose listeners are currently being processed. + * + * @memberOf Benchmark.Event + * @type Object + */ + 'currentTarget': undefined, + + /** + * The return value of the last executed listener. + * + * @memberOf Benchmark.Event + * @type Mixed + */ + 'result': undefined, + + /** + * The object to which the event was originally emitted. + * + * @memberOf Benchmark.Event + * @type Object + */ + 'target': undefined, + + /** + * A timestamp of when the event was created (ms). + * + * @memberOf Benchmark.Event + * @type number + */ + 'timeStamp': 0, + + /** + * The event type. + * + * @memberOf Benchmark.Event + * @type string + */ + 'type': '' + }); + + /*------------------------------------------------------------------------*/ + + /** + * The default options copied by suite instances. + * + * @static + * @memberOf Benchmark.Suite + * @type Object + */ + Suite.options = { + + /** + * The name of the suite. + * + * @memberOf Benchmark.Suite.options + * @type string + */ + 'name': undefined + }; + + /*------------------------------------------------------------------------*/ + + _.assign(Suite.prototype, { + + /** + * The number of benchmarks in the suite. + * + * @memberOf Benchmark.Suite + * @type number + */ + 'length': 0, + + /** + * A flag to indicate if the suite is aborted. + * + * @memberOf Benchmark.Suite + * @type boolean + */ + 'aborted': false, + + /** + * A flag to indicate if the suite is running. + * + * @memberOf Benchmark.Suite + * @type boolean + */ + 'running': false + }); + + _.assign(Suite.prototype, { + 'abort': abortSuite, + 'add': add, + 'clone': cloneSuite, + 'emit': emit, + 'filter': filterSuite, + 'join': arrayRef.join, + 'listeners': listeners, + 'off': off, + 'on': on, + 'pop': arrayRef.pop, + 'push': push, + 'reset': resetSuite, + 'run': runSuite, + 'reverse': arrayRef.reverse, + 'shift': shift, + 'slice': slice, + 'sort': arrayRef.sort, + 'splice': arrayRef.splice, + 'unshift': unshift + }); + + /*------------------------------------------------------------------------*/ + + // Expose Deferred, Event, and Suite. + _.assign(Benchmark, { + 'Deferred': Deferred, + 'Event': Event, + 'Suite': Suite + }); + + /*------------------------------------------------------------------------*/ + + // Add lodash methods as Suite methods. + _.each(['each', 'forEach', 'indexOf', 'map', 'reduce'], function(methodName) { + var func = _[methodName]; + Suite.prototype[methodName] = function() { + var args = [this]; + push.apply(args, arguments); + return func.apply(_, args); + }; + }); + + // Avoid array-like object bugs with `Array#shift` and `Array#splice` + // in Firefox < 10 and IE < 9. + _.each(['pop', 'shift', 'splice'], function(methodName) { + var func = arrayRef[methodName]; + + Suite.prototype[methodName] = function() { + var value = this, + result = func.apply(value, arguments); + + if (value.length === 0) { + delete value[0]; + } + return result; + }; + }); + + // Avoid buggy `Array#unshift` in IE < 8 which doesn't return the new + // length of the array. + Suite.prototype.unshift = function() { + var value = this; + unshift.apply(value, arguments); + return value.length; + }; + + return Benchmark; + } + + /*--------------------------------------------------------------------------*/ + + var Benchmark = runInContext(); + + window.Benchmark = Benchmark; + + return Benchmark; + +}.call(this)); diff --git a/tgui/packages/tgui-bench/package.json b/tgui/packages/tgui-bench/package.json new file mode 100644 index 0000000000..ae83ca8666 --- /dev/null +++ b/tgui/packages/tgui-bench/package.json @@ -0,0 +1,15 @@ +{ + "private": true, + "name": "tgui-bench", + "version": "4.3.0", + "dependencies": { + "@fastify/static": "^5.0.2", + "common": "workspace:*", + "fastify": "^3.20.2", + "inferno": "^7.4.8", + "inferno-vnode-flags": "^7.4.8", + "lodash": "^4.17.21", + "platform": "^1.3.6", + "tgui": "workspace:*" + } +} diff --git a/tgui/packages/tgui-bench/tests/Button.test.tsx b/tgui/packages/tgui-bench/tests/Button.test.tsx new file mode 100644 index 0000000000..e3472cbbbf --- /dev/null +++ b/tgui/packages/tgui-bench/tests/Button.test.tsx @@ -0,0 +1,99 @@ +import { linkEvent } from 'inferno'; +import { Button } from 'tgui/components'; +import { createRenderer } from 'tgui/renderer'; + +const render = createRenderer(); + +const handleClick = () => undefined; + +export const SingleButton = () => { + const node = ( + + ); + render(node); +}; + +export const SingleButtonWithCallback = () => { + const node = ( + + ); + render(node); +}; + +export const SingleButtonWithLinkEvent = () => { + const node = ( + + ); + render(node); +}; + +export const ListOfButtons = () => { + const nodes: JSX.Element[] = []; + for (let i = 0; i < 100; i++) { + const node = ( + + ); + nodes.push(node); + } + render(
{nodes}
); +}; + +export const ListOfButtonsWithCallback = () => { + const nodes: JSX.Element[] = []; + for (let i = 0; i < 100; i++) { + const node = ( + + ); + nodes.push(node); + } + render(
{nodes}
); +}; + +export const ListOfButtonsWithLinkEvent = () => { + const nodes: JSX.Element[] = []; + for (let i = 0; i < 100; i++) { + const node = ( + + ); + nodes.push(node); + } + render(
{nodes}
); +}; + +export const ListOfButtonsWithIcons = () => { + const nodes: JSX.Element[] = []; + for (let i = 0; i < 100; i++) { + const node = ( + + ); + nodes.push(node); + } + render(
{nodes}
); +}; + +export const ListOfButtonsWithTooltips = () => { + const nodes: JSX.Element[] = []; + for (let i = 0; i < 100; i++) { + const node = ( + + ); + nodes.push(node); + } + render(
{nodes}
); +}; diff --git a/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx new file mode 100644 index 0000000000..99367d2016 --- /dev/null +++ b/tgui/packages/tgui-bench/tests/DisposalBin.test.tsx @@ -0,0 +1,28 @@ +import { backendUpdate } from 'tgui/backend'; +import { DisposalBin } from 'tgui/interfaces/DisposalBin'; +import { createRenderer } from 'tgui/renderer'; +import { configureStore, StoreProvider } from 'tgui/store'; + +const store = configureStore({ sideEffets: false }); + +const renderUi = createRenderer((dataJson: string) => { + store.dispatch(backendUpdate({ + data: Byond.parseJson(dataJson), + })); + return ( + + + + ); +}); + +export const data = JSON.stringify({ + flush: 0, + full_pressure: 1, + pressure_charging: 0, + panel_open: 0, + per: 1, + isai: 0, +}); + +export const Default = () => renderUi(data); diff --git a/tgui/packages/tgui-bench/tests/Flex.test.tsx b/tgui/packages/tgui-bench/tests/Flex.test.tsx new file mode 100644 index 0000000000..e81ebc6f61 --- /dev/null +++ b/tgui/packages/tgui-bench/tests/Flex.test.tsx @@ -0,0 +1,18 @@ +import { Flex } from 'tgui/components'; +import { createRenderer } from 'tgui/renderer'; + +const render = createRenderer(); + +export const Default = () => { + const node = ( + + + Text {Math.random()} + + + Text {Math.random()} + + + ); + render(node); +}; diff --git a/tgui/packages/tgui-bench/tests/Stack.test.tsx b/tgui/packages/tgui-bench/tests/Stack.test.tsx new file mode 100644 index 0000000000..952dba2029 --- /dev/null +++ b/tgui/packages/tgui-bench/tests/Stack.test.tsx @@ -0,0 +1,18 @@ +import { Stack } from 'tgui/components'; +import { createRenderer } from 'tgui/renderer'; + +const render = createRenderer(); + +export const Default = () => { + const node = ( + + + Text {Math.random()} + + + Text {Math.random()} + + + ); + render(node); +}; diff --git a/tgui/packages/tgui-bench/tests/Tooltip.test.tsx b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx new file mode 100644 index 0000000000..b953fc911d --- /dev/null +++ b/tgui/packages/tgui-bench/tests/Tooltip.test.tsx @@ -0,0 +1,20 @@ +import { Box, Tooltip } from "tgui/components"; +import { createRenderer } from "tgui/renderer"; + +const render = createRenderer(); + +export const ListOfTooltips = () => { + const nodes: JSX.Element[] = []; + + for (let i = 0; i < 100; i++) { + nodes.push( + + + Tooltip #{i} + + + ); + } + + render(
{nodes}
); +}; diff --git a/tgui/packages/tgui-dev-server/dreamseeker.js b/tgui/packages/tgui-dev-server/dreamseeker.js index 60654219b0..3d4149cf25 100644 --- a/tgui/packages/tgui-dev-server/dreamseeker.js +++ b/tgui/packages/tgui-dev-server/dreamseeker.js @@ -4,11 +4,12 @@ * @license MIT */ -import axios from 'axios'; import { exec } from 'child_process'; -import { createLogger } from 'common/logging.js'; import { promisify } from 'util'; +import { createLogger } from './logging.js'; +import { require } from './require.js'; +const axios = require('axios'); const logger = createLogger('dreamseeker'); const instanceByPid = new Map(); diff --git a/tgui/packages/tgui-dev-server/index.esm.js b/tgui/packages/tgui-dev-server/index.esm.js deleted file mode 100644 index 903fc9c17c..0000000000 --- a/tgui/packages/tgui-dev-server/index.esm.js +++ /dev/null @@ -1,5 +0,0 @@ -const esmRequire = require('esm')(module, { - cache: false, -}); - -esmRequire('./index.js'); diff --git a/tgui/packages/tgui-dev-server/link/client.js b/tgui/packages/tgui-dev-server/link/client.cjs similarity index 94% rename from tgui/packages/tgui-dev-server/link/client.js rename to tgui/packages/tgui-dev-server/link/client.cjs index 5a4d2f6112..fe75314acd 100644 --- a/tgui/packages/tgui-dev-server/link/client.js +++ b/tgui/packages/tgui-dev-server/link/client.cjs @@ -37,7 +37,7 @@ if (process.env.NODE_ENV !== 'production') { window.onunload = () => socket && socket.close(); } -export const subscribe = fn => subscribers.push(fn); +const subscribe = fn => subscribers.push(fn); /** * A json serializer which handles circular references and other junk. @@ -91,7 +91,7 @@ const serializeObject = obj => { return json; }; -export const sendMessage = msg => { +const sendMessage = msg => { if (process.env.NODE_ENV !== 'production') { const json = serializeObject(msg); // Send message using WebSocket @@ -119,7 +119,7 @@ export const sendMessage = msg => { } }; -export const sendLogEntry = (level, ns, ...args) => { +const sendLogEntry = (level, ns, ...args) => { if (process.env.NODE_ENV !== 'production') { try { sendMessage({ @@ -135,7 +135,7 @@ export const sendLogEntry = (level, ns, ...args) => { } }; -export const setupHotReloading = () => { +const setupHotReloading = () => { if (process.env.NODE_ENV !== 'production' && process.env.WEBPACK_HMR_ENABLED && window.WebSocket) { @@ -168,3 +168,10 @@ export const setupHotReloading = () => { } } }; + +module.exports = { + subscribe, + sendMessage, + sendLogEntry, + setupHotReloading, +}; diff --git a/tgui/packages/tgui-dev-server/link/retrace.js b/tgui/packages/tgui-dev-server/link/retrace.js index c86b40456c..c10ba9cb17 100644 --- a/tgui/packages/tgui-dev-server/link/retrace.js +++ b/tgui/packages/tgui-dev-server/link/retrace.js @@ -4,13 +4,15 @@ * @license MIT */ -import { createLogger } from 'common/logging.js'; import fs from 'fs'; import { basename } from 'path'; -import SourceMap from 'source-map'; -import { parse as parseStackTrace } from 'stacktrace-parser'; +import { createLogger } from '../logging.js'; +import { require } from '../require.js'; import { resolveGlob } from '../util.js'; +const SourceMap = require('source-map'); +const { parse: parseStackTrace } = require('stacktrace-parser'); + const logger = createLogger('retrace'); const { SourceMapConsumer } = SourceMap; diff --git a/tgui/packages/tgui-dev-server/link/server.js b/tgui/packages/tgui-dev-server/link/server.js index 2586d77779..87a8a5911b 100644 --- a/tgui/packages/tgui-dev-server/link/server.js +++ b/tgui/packages/tgui-dev-server/link/server.js @@ -4,11 +4,13 @@ * @license MIT */ -import { createLogger, directLog } from 'common/logging.js'; import http from 'http'; import { inspect } from 'util'; -import WebSocket from 'ws'; -import { retrace, loadSourceMaps } from './retrace.js'; +import { createLogger, directLog } from '../logging.js'; +import { require } from '../require.js'; +import { loadSourceMaps, retrace } from './retrace.js'; + +const WebSocket = require('ws'); const logger = createLogger('link'); diff --git a/tgui/packages/common/logging.js b/tgui/packages/tgui-dev-server/logging.js similarity index 100% rename from tgui/packages/common/logging.js rename to tgui/packages/tgui-dev-server/logging.js diff --git a/tgui/packages/tgui-dev-server/package.json b/tgui/packages/tgui-dev-server/package.json index 7c41d0caee..4076ef805f 100644 --- a/tgui/packages/tgui-dev-server/package.json +++ b/tgui/packages/tgui-dev-server/package.json @@ -2,10 +2,9 @@ "private": true, "name": "tgui-dev-server", "version": "4.3.0", + "type": "module", "dependencies": { "axios": "^0.27.2", - "common": "workspace:*", - "esm": "^3.2.25", "glob": "^8.0.3", "source-map": "^0.7.3", "stacktrace-parser": "^0.1.10", diff --git a/tgui/packages/tgui-dev-server/reloader.js b/tgui/packages/tgui-dev-server/reloader.js index 9bc40d6484..5722cee644 100644 --- a/tgui/packages/tgui-dev-server/reloader.js +++ b/tgui/packages/tgui-dev-server/reloader.js @@ -4,13 +4,13 @@ * @license MIT */ -import { createLogger } from 'common/logging.js'; import fs from 'fs'; import os from 'os'; import { basename } from 'path'; +import { DreamSeeker } from './dreamseeker.js'; +import { createLogger } from './logging.js'; import { resolveGlob, resolvePath } from './util.js'; import { regQuery } from './winreg.js'; -import { DreamSeeker } from './dreamseeker.js'; const logger = createLogger('reloader'); diff --git a/tgui/packages/tgui-dev-server/require.js b/tgui/packages/tgui-dev-server/require.js new file mode 100644 index 0000000000..0551d630fc --- /dev/null +++ b/tgui/packages/tgui-dev-server/require.js @@ -0,0 +1,9 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +import { createRequire } from 'module'; + +export const require = createRequire(import.meta.url); diff --git a/tgui/packages/tgui-dev-server/util.js b/tgui/packages/tgui-dev-server/util.js index 50c0baad5a..0fc255ed67 100644 --- a/tgui/packages/tgui-dev-server/util.js +++ b/tgui/packages/tgui-dev-server/util.js @@ -4,26 +4,26 @@ * @license MIT */ -import glob from 'glob'; -import { resolve as resolvePath } from 'path'; import fs from 'fs'; -import { promisify } from 'util'; +import path from 'path'; +import { require } from './require.js'; -export { resolvePath }; +const globPkg = require('glob'); + +export const resolvePath = path.resolve; /** * Combines path.resolve with glob patterns. */ -export const resolveGlob = async (...sections) => { - const unsafePaths = await promisify(glob)( - resolvePath(...sections), { - strict: false, - silent: true, - }); +export const resolveGlob = (...sections) => { + const unsafePaths = globPkg.sync(path.resolve(...sections), { + strict: false, + silent: true, + }); const safePaths = []; for (let path of unsafePaths) { try { - await promisify(fs.stat)(path); + fs.statSync(path); safePaths.push(path); } catch {} diff --git a/tgui/packages/tgui-dev-server/webpack.js b/tgui/packages/tgui-dev-server/webpack.js index 4aba3af6d5..8cba68afcb 100644 --- a/tgui/packages/tgui-dev-server/webpack.js +++ b/tgui/packages/tgui-dev-server/webpack.js @@ -4,11 +4,11 @@ * @license MIT */ -import { createLogger } from 'common/logging.js'; import fs from 'fs'; import { createRequire } from 'module'; import { dirname } from 'path'; import { loadSourceMaps, setupLink } from './link/server.js'; +import { createLogger } from './logging.js'; import { reloadByondCache } from './reloader.js'; import { resolveGlob } from './util.js'; diff --git a/tgui/packages/tgui-dev-server/winreg.js b/tgui/packages/tgui-dev-server/winreg.js index 974135e76d..669e2aad55 100644 --- a/tgui/packages/tgui-dev-server/winreg.js +++ b/tgui/packages/tgui-dev-server/winreg.js @@ -7,8 +7,8 @@ */ import { exec } from 'child_process'; -import { createLogger } from 'common/logging.js'; import { promisify } from 'util'; +import { createLogger } from './logging.js'; const logger = createLogger('winreg'); diff --git a/tgui/packages/tgui-panel/index.js b/tgui/packages/tgui-panel/index.js index fd0f73b0d8..721fa97fb5 100644 --- a/tgui/packages/tgui-panel/index.js +++ b/tgui/packages/tgui-panel/index.js @@ -10,7 +10,7 @@ import './styles/themes/light.scss'; import { perf } from 'common/perf'; import { combineReducers } from 'common/redux'; -import { setupHotReloading } from 'tgui-dev-server/link/client'; +import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; import { setupGlobalEvents } from 'tgui/events'; import { captureExternalLinks } from 'tgui/links'; import { createRenderer } from 'tgui/renderer'; @@ -68,20 +68,11 @@ const setupApp = () => { setupPanelFocusHacks(); captureExternalLinks(); - // Subscribe for Redux state updates + // Re-render UI on store updates store.subscribe(renderApp); - // Subscribe for bankend updates - window.update = msg => store.dispatch(Byond.parseJson(msg)); - - // Process the early update queue - while (true) { - const msg = window.__updateQueue__.shift(); - if (!msg) { - break; - } - window.update(msg); - } + // Dispatch incoming messages as store actions + Byond.subscribe((type, payload) => store.dispatch({ type, payload })); // Unhide the panel Byond.winset('output', { diff --git a/tgui/packages/tgui-panel/ping/middleware.js b/tgui/packages/tgui-panel/ping/middleware.js index b7d8e25a76..2d607c8417 100644 --- a/tgui/packages/tgui-panel/ping/middleware.js +++ b/tgui/packages/tgui-panel/ping/middleware.js @@ -4,7 +4,6 @@ * @license MIT */ -import { sendMessage } from 'tgui/backend'; import { pingFail, pingSuccess } from './actions'; import { PING_INTERVAL, PING_QUEUE_SIZE, PING_TIMEOUT } from './constants'; @@ -23,10 +22,7 @@ export const pingMiddleware = store => { } const ping = { index, sentAt: Date.now() }; pings[index] = ping; - sendMessage({ - type: 'ping', - payload: { index }, - }); + Byond.sendMessage('ping', { index }); index = (index + 1) % PING_QUEUE_SIZE; }; return next => action => { diff --git a/tgui/packages/tgui-panel/telemetry.js b/tgui/packages/tgui-panel/telemetry.js index 31b8541c21..afad245c48 100644 --- a/tgui/packages/tgui-panel/telemetry.js +++ b/tgui/packages/tgui-panel/telemetry.js @@ -4,7 +4,6 @@ * @license MIT */ -import { sendMessage } from 'tgui/backend'; import { storage } from 'common/storage'; import { createLogger } from 'tgui/logging'; @@ -34,14 +33,8 @@ export const telemetryMiddleware = store => { logger.debug('sending'); const limits = payload?.limits || {}; // Trim connections according to the server limit - const connections = telemetry.connections - .slice(0, limits.connections); - sendMessage({ - type: 'telemetry', - payload: { - connections, - }, - }); + const connections = telemetry.connections.slice(0, limits.connections); + Byond.sendMessage('telemetry', { connections }); return; } // Keep telemetry up to date diff --git a/tgui/packages/tgui-polyfill/html5shiv.js b/tgui/packages/tgui-polyfill/00-html5shiv.js similarity index 100% rename from tgui/packages/tgui-polyfill/html5shiv.js rename to tgui/packages/tgui-polyfill/00-html5shiv.js diff --git a/tgui/packages/tgui-polyfill/ie8.js b/tgui/packages/tgui-polyfill/01-ie8.js similarity index 99% rename from tgui/packages/tgui-polyfill/ie8.js rename to tgui/packages/tgui-polyfill/01-ie8.js index af4b3a5ee8..3da4e2393d 100644 --- a/tgui/packages/tgui-polyfill/ie8.js +++ b/tgui/packages/tgui-polyfill/01-ie8.js @@ -743,7 +743,7 @@ ontype = 'on' + type, handlers = (window[ontype] || Object)[SECRET], i = handlers ? find(handlers, handler) : -1 - ; + ; if (-1 < i) handlers.splice(i, 1); }), pageXOffset: {get: getter('scrollLeft')}, diff --git a/tgui/packages/tgui-polyfill/dom4.js b/tgui/packages/tgui-polyfill/02-dom4.js similarity index 100% rename from tgui/packages/tgui-polyfill/dom4.js rename to tgui/packages/tgui-polyfill/02-dom4.js diff --git a/tgui/packages/tgui-polyfill/css-om.js b/tgui/packages/tgui-polyfill/03-css-om.js similarity index 100% rename from tgui/packages/tgui-polyfill/css-om.js rename to tgui/packages/tgui-polyfill/03-css-om.js diff --git a/tgui/packages/tgui-polyfill/10-misc.js b/tgui/packages/tgui-polyfill/10-misc.js new file mode 100644 index 0000000000..5a1849e135 --- /dev/null +++ b/tgui/packages/tgui-polyfill/10-misc.js @@ -0,0 +1,61 @@ +/** + * @file + * @copyright 2020 Aleksej Komarov + * @license MIT + */ + +/* eslint-disable */ +(function () { + 'use strict'; + + // Necessary polyfill to make Webpack code splitting work on IE8 + if (!Function.prototype.bind) (function () { + var slice = Array.prototype.slice; + Function.prototype.bind = function () { + var thatFunc = this, thatArg = arguments[0]; + var args = slice.call(arguments, 1); + if (typeof thatFunc !== 'function') { + // closest thing possible to the ECMAScript 5 + // internal IsCallable function + throw new TypeError('Function.prototype.bind - ' + + 'what is trying to be bound is not callable'); + } + return function () { + var funcArgs = args.concat(slice.call(arguments)) + return thatFunc.apply(thatArg, funcArgs); + }; + }; + })(); + + if (!Array.prototype['forEach']) { + Array.prototype.forEach = function (callback, thisArg) { + if (this == null) { + throw new TypeError('Array.prototype.forEach called on null or undefined'); + } + var T, k; + var O = Object(this); + var len = O.length >>> 0; + if (typeof callback !== "function") { + throw new TypeError(callback + ' is not a function'); + } + if (arguments.length > 1) { + T = thisArg; + } + k = 0; + while (k < len) { + var kValue; + if (k in O) { + kValue = O[k]; + callback.call(T, kValue, k, O); + } + k++; + } + }; + } + + // Inferno needs Int32Array, and it is not covered by core-js. + if (!window.Int32Array) { + window.Int32Array = Array; + } + +})(); diff --git a/tgui/packages/tgui-polyfill/index.js b/tgui/packages/tgui-polyfill/index.js index ab69510366..7c45386d8b 100644 --- a/tgui/packages/tgui-polyfill/index.js +++ b/tgui/packages/tgui-polyfill/index.js @@ -4,14 +4,12 @@ * @license MIT */ +// NOTE: There are numbered polyfills, which are baked and injected directly +// into `tgui.html`. See how they're baked in `package.json`. + import 'core-js/es'; import 'core-js/web/immediate'; import 'core-js/web/queue-microtask'; import 'core-js/web/timers'; import 'regenerator-runtime/runtime'; -import './html5shiv'; -import './ie8'; -import './dom4'; -import './css-om'; -import './inferno'; import 'unfetch/polyfill'; diff --git a/tgui/packages/tgui-polyfill/inferno.js b/tgui/packages/tgui-polyfill/inferno.js deleted file mode 100644 index f9182cedb3..0000000000 --- a/tgui/packages/tgui-polyfill/inferno.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -// Inferno needs Int32Array, and it is not covered by core-js. -if (!window.Int32Array) { - window.Int32Array = Array; -} diff --git a/tgui/packages/tgui-polyfill/package.json b/tgui/packages/tgui-polyfill/package.json index 244bdca358..85d004bf99 100644 --- a/tgui/packages/tgui-polyfill/package.json +++ b/tgui/packages/tgui-polyfill/package.json @@ -2,9 +2,15 @@ "private": true, "name": "tgui-polyfill", "version": "4.3.0", + "scripts": { + "tgui-polyfill:build": "terser 00-html5shiv.js 01-ie8.js 02-dom4.js 03-css-om.js 10-misc.js --ie8 -f ascii_only,comments=false -o ../../public/tgui-polyfill.min.js" + }, "dependencies": { "core-js": "^3.22.5", "regenerator-runtime": "^0.13.9", "unfetch": "^4.2.0" + }, + "devDependencies": { + "terser": "^5.13.1" } } diff --git a/tgui/packages/tgui/backend.ts b/tgui/packages/tgui/backend.ts index 41b2404333..3e57bca72b 100644 --- a/tgui/packages/tgui/backend.ts +++ b/tgui/packages/tgui/backend.ts @@ -134,19 +134,15 @@ export const backendMiddleware = store => { } if (type === 'ping') { - sendMessage({ - type: 'pingReply', - }); + Byond.sendMessage('pingReply'); return; } if (type === 'backend/suspendStart' && !suspendInterval) { - logger.log(`suspending (${window.__windowId__})`); + logger.log(`suspending (${Byond.windowId})`); // Keep sending suspend messages until it succeeds. // It may fail multiple times due to topic rate limiting. - const suspendFn = () => sendMessage({ - type: 'suspend', - }); + const suspendFn = () => Byond.sendMessage('suspend'); suspendFn(); suspendInterval = setInterval(suspendFn, 2000); } @@ -155,7 +151,7 @@ export const backendMiddleware = store => { suspendRenderer(); clearInterval(suspendInterval); suspendInterval = undefined; - Byond.winset(window.__windowId__, { + Byond.winset(Byond.windowId, { 'is-visible': false, }); setImmediate(() => focusMap()); @@ -171,7 +167,7 @@ export const backendMiddleware = store => { else if (fancyState !== fancy) { logger.log('changing fancy mode to', fancy); fancyState = fancy; - Byond.winset(window.__windowId__, { + Byond.winset(Byond.windowId, { titlebar: !fancy, 'can-resize': !fancy, }); @@ -195,7 +191,7 @@ export const backendMiddleware = store => { if (suspended) { return; } - Byond.winset(window.__windowId__, { + Byond.winset(Byond.windowId, { 'is-visible': true, }); perf.mark('resume/finish'); @@ -210,25 +206,6 @@ export const backendMiddleware = store => { }; }; -/** - * Sends a message to /datum/tgui_window. - */ -export const sendMessage = (message: any = {}) => { - const { payload, ...rest } = message; - const data: any = { - // Message identifying header - tgui: 1, - window_id: window.__windowId__, - // Message body - ...rest, - }; - // JSON-encode the payload - if (payload !== null && payload !== undefined) { - data.payload = JSON.stringify(payload); - } - Byond.topic(data); -}; - /** * Sends an action to `ui_act` on `src_object` that this tgui window * is associated with. @@ -242,10 +219,7 @@ export const sendAct = (action: string, payload: object = {}) => { logger.error(`Payload for act() must be an object, got this:`, payload); return; } - sendMessage({ - type: 'act/' + action, - payload, - }); + Byond.sendMessage('act/' + action, payload); }; type BackendState = { @@ -273,7 +247,7 @@ type BackendState = { shared: Record, suspending: boolean, suspended: boolean, -} +}; /** * Selects a backend-related slice of Redux state @@ -283,12 +257,9 @@ export const selectBackend = (state: any): BackendState => ( ); /** - * A React hook (sort of) for getting tgui state and related functions. + * Get data from tgui backend. * - * This is supposed to be replaced with a real React Hook, which can only - * be used in functional components. - * - * You can make + * Includes the `act` function for performing DM actions. */ export const useBackend = (context: any) => { const { store } = context; @@ -371,7 +342,7 @@ export const useSharedState = ( return [ sharedState, nextState => { - sendMessage({ + Byond.sendMessage({ type: 'setSharedState', key, value: JSON.stringify( diff --git a/tgui/packages/tgui/components/Autofocus.tsx b/tgui/packages/tgui/components/Autofocus.tsx new file mode 100644 index 0000000000..f5ae00650a --- /dev/null +++ b/tgui/packages/tgui/components/Autofocus.tsx @@ -0,0 +1,19 @@ +import { Component, createRef } from "inferno"; + +export class Autofocus extends Component { + ref = createRef(); + + componentDidMount() { + setTimeout(() => { + this.ref.current?.focus(); + }, 1); + } + + render() { + return ( +
+ {this.props.children} +
+ ); + } +} diff --git a/tgui/packages/tgui/components/Box.tsx b/tgui/packages/tgui/components/Box.tsx index 015d07b23f..bcd8447c8b 100644 --- a/tgui/packages/tgui/components/Box.tsx +++ b/tgui/packages/tgui/components/Box.tsx @@ -5,7 +5,7 @@ */ import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { createVNode, InfernoNode } from 'inferno'; +import { createVNode, InfernoNode, SFC } from 'inferno'; import { ChildFlags, VNodeFlags } from 'inferno-vnode-flags'; import { CSS_COLORS } from '../constants'; @@ -109,9 +109,7 @@ export const halfUnit = (value: unknown): string | undefined => { const isColorCode = (str: unknown) => !isColorClass(str); const isColorClass = (str: unknown): boolean => { - if (typeof str === 'string') { - return CSS_COLORS.includes(str); - } + return typeof str === "string" && CSS_COLORS.includes(str); }; const mapRawPropTo = attrName => (style, value) => { @@ -290,7 +288,7 @@ export const computeBoxClassName = (props: BoxProps) => { ]); }; -export const Box = (props: BoxProps) => { +export const Box: SFC = (props: BoxProps) => { const { as = 'div', className, @@ -312,7 +310,9 @@ export const Box = (props: BoxProps) => { computedClassName, children, ChildFlags.UnknownChildren, - computedProps); + computedProps, + undefined, + ); }; Box.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/Button.js b/tgui/packages/tgui/components/Button.js index 6c5d1736b3..58c3dc7697 100644 --- a/tgui/packages/tgui/components/Button.js +++ b/tgui/packages/tgui/components/Button.js @@ -8,7 +8,7 @@ import { KEY_ENTER, KEY_ESCAPE, KEY_SPACE } from 'common/keycodes'; import { classes, pureComponentHooks } from 'common/react'; import { Component, createRef } from 'inferno'; import { createLogger } from '../logging'; -import { Box } from './Box'; +import { Box, computeBoxClassName, computeBoxProps } from './Box'; import { Icon } from './Icon'; import { Tooltip } from './Tooltip'; @@ -47,10 +47,17 @@ export const Button = props => { + `'onClick' instead and read: ` + `https://infernojs.org/docs/guides/event-handling`); } - // IE8: Use a lowercase "onclick" because synthetic events are fucked. - // IE8: Use an "unselectable" prop because "user-select" doesn't work. + rest.onClick = e => { + if (!disabled && onClick) { + onClick(e); + } + }; + // IE8: Use "unselectable" because "user-select" doesn't work. + if (Byond.IS_LTE_IE8) { + rest.unselectable = true; + } let buttonContent = ( - { ? 'Button--color--' + color : 'Button--color--default', className, + computeBoxClassName(rest), ])} tabIndex={!disabled && '0'} - unselectable={Byond.IS_LTE_IE8} - onClick={e => { - if (!disabled && onClick) { - onClick(e); - } - }} onKeyDown={e => { + if (props.captureKeys === false) { + return; + } + const keyCode = window.event ? e.which : e.keyCode; // Simulate a click when pressing space or enter. if (keyCode === KEY_SPACE || keyCode === KEY_ENTER) { @@ -89,7 +95,7 @@ export const Button = props => { return; } }} - {...rest}> + {...computeBoxProps(rest)}> {(icon && iconPosition !== 'right') && ( { fontSize={iconSize} // VOREStation Addition /> )} - + ); if (tooltip) { diff --git a/tgui/packages/tgui/components/ByondUi.js b/tgui/packages/tgui/components/ByondUi.js index 07be451e9d..5b4f04dd0e 100644 --- a/tgui/packages/tgui/components/ByondUi.js +++ b/tgui/packages/tgui/components/ByondUi.js @@ -54,18 +54,19 @@ window.addEventListener('beforeunload', () => { }); /** - * Get the bounding box of the DOM element. + * Get the bounding box of the DOM element in display-pixels. */ const getBoundingBox = element => { + const pixelRatio = window.devicePixelRatio ?? 1; const rect = element.getBoundingClientRect(); return { pos: [ - rect.left, - rect.top, + rect.left * pixelRatio, + rect.top * pixelRatio, ], size: [ - rect.right - rect.left, - rect.bottom - rect.top, + (rect.right - rect.left) * pixelRatio, + (rect.bottom - rect.top) * pixelRatio, ], }; }; @@ -114,7 +115,7 @@ export class ByondUi extends Component { const box = getBoundingBox(this.containerRef.current); logger.debug('bounding box', box); this.byondUiElement.render({ - parent: window.__windowId__, + parent: Byond.windowId, ...params, pos: box.pos[0] + ',' + box.pos[1], size: box.size[0] + 'x' + box.size[1], @@ -132,11 +133,10 @@ export class ByondUi extends Component { render() { const { params, ...rest } = this.props; - const boxProps = computeBoxProps(rest); return (
+ {...computeBoxProps(rest)}> {/* Filler */}
diff --git a/tgui/packages/tgui/components/Flex.tsx b/tgui/packages/tgui/components/Flex.tsx index 172fcad93d..95379ea587 100644 --- a/tgui/packages/tgui/components/Flex.tsx +++ b/tgui/packages/tgui/components/Flex.tsx @@ -5,16 +5,26 @@ */ import { BooleanLike, classes, pureComponentHooks } from 'common/react'; -import { Box, BoxProps, unit } from './Box'; +import { BoxProps, computeBoxClassName, computeBoxProps, unit } from './Box'; -export interface FlexProps extends BoxProps { +export type FlexProps = BoxProps & { direction?: string | BooleanLike; wrap?: string | BooleanLike; align?: string | BooleanLike; alignContent?: string | BooleanLike; // VOREStation Addition justify?: string | BooleanLike; inline?: BooleanLike; -} +}; + +export const computeFlexClassName = (props: FlexProps) => { + return classes([ + 'Flex', + props.inline && 'Flex--inline', + Byond.IS_LTE_IE10 && 'Flex--iefix', + Byond.IS_LTE_IE10 && props.direction === 'column' && 'Flex--iefix--column', + computeBoxClassName(props), + ]); +}; export const computeFlexProps = (props: FlexProps) => { const { @@ -27,17 +37,7 @@ export const computeFlexProps = (props: FlexProps) => { inline, ...rest } = props; - return { - className: classes([ - 'Flex', - Byond.IS_LTE_IE10 && ( - direction === 'column' - ? 'Flex--iefix--column' - : 'Flex--iefix' - ), - inline && 'Flex--inline', - className, - ]), + return computeBoxProps({ style: { ...rest.style, 'flex-direction': direction, @@ -47,22 +47,39 @@ export const computeFlexProps = (props: FlexProps) => { 'justify-content': justify, }, ...rest, - }; + }); }; -export const Flex = props => ( - -); +export const Flex = props => { + const { className, ...rest } = props; + return ( +
+ ); +}; Flex.defaultHooks = pureComponentHooks; -export interface FlexItemProps extends BoxProps { +export type FlexItemProps = BoxProps & { grow?: number; order?: number; shrink?: number; basis?: string | BooleanLike; align?: string | BooleanLike; -} +}; + +export const computeFlexItemClassName = (props: FlexItemProps) => { + return classes([ + 'Flex__item', + Byond.IS_LTE_IE10 && 'Flex__item--iefix', + computeBoxClassName(props), + ]); +}; export const computeFlexItemProps = (props: FlexItemProps) => { const { @@ -77,13 +94,7 @@ export const computeFlexItemProps = (props: FlexItemProps) => { align, ...rest } = props; - return { - className: classes([ - 'Flex__item', - Byond.IS_LTE_IE10 && 'Flex__item--iefix', - Byond.IS_LTE_IE10 && grow > 0 && 'Flex__item--iefix--grow', - className, - ]), + return computeBoxProps({ style: { ...style, 'flex-grow': grow !== undefined && Number(grow), @@ -93,12 +104,22 @@ export const computeFlexItemProps = (props: FlexItemProps) => { 'align-self': align, }, ...rest, - }; + }); }; -const FlexItem = props => ( - -); +const FlexItem = props => { + const { className, ...rest } = props; + return ( +
+ ); +}; FlexItem.defaultHooks = pureComponentHooks; diff --git a/tgui/packages/tgui/components/Icon.js b/tgui/packages/tgui/components/Icon.js index 13efaffca7..98a40bf5d9 100644 --- a/tgui/packages/tgui/components/Icon.js +++ b/tgui/packages/tgui/components/Icon.js @@ -7,7 +7,7 @@ */ import { classes, pureComponentHooks } from 'common/react'; -import { Box } from './Box'; +import { computeBoxClassName, computeBoxProps } from './Box'; const FA_OUTLINE_REGEX = /-o$/; @@ -17,17 +17,26 @@ export const Icon = props => { size, spin, className, - style = {}, rotation, inverse, ...rest } = props; + if (size) { - style['font-size'] = (size * 100) + '%'; + if (!rest.style) { + rest.style = {}; + } + rest.style['font-size'] = (size * 100) + '%'; } if (typeof rotation === 'number') { - style['transform'] = `rotate(${rotation}deg)`; + if (!rest.style) { + rest.style = {}; + } + rest.style['transform'] = `rotate(${rotation}deg)`; } + + const boxProps = computeBoxProps(rest); + let iconClass = ""; if (name.startsWith("tg-")) { // tgfont icon @@ -39,15 +48,14 @@ export const Icon = props => { iconClass = (faRegular ? 'far ' : 'fas ') + 'fa-'+ faName + (spin ? " fa-spin" : ""); } return ( - + {...boxProps} /> ); }; @@ -56,21 +64,19 @@ Icon.defaultHooks = pureComponentHooks; export const IconStack = props => { const { className, - style = {}, children, ...rest } = props; return ( - + {...computeBoxProps(rest)}> {children} - + ); }; diff --git a/tgui/packages/tgui/components/Input.js b/tgui/packages/tgui/components/Input.js index e09c015d3e..a95dbab614 100644 --- a/tgui/packages/tgui/components/Input.js +++ b/tgui/packages/tgui/components/Input.js @@ -73,6 +73,11 @@ export class Input extends Component { return; } if (e.keyCode === KEY_ESCAPE) { + if (this.props.onEscape) { + this.props.onEscape(e); + return; + } + this.setEditing(false); e.target.value = toInputValue(this.props.value); e.target.blur(); @@ -87,8 +92,15 @@ export class Input extends Component { if (input) { input.value = toInputValue(nextValue); } - if (this.props.autoFocus) { - setTimeout(() => input.focus(), 1); + + if (this.props.autoFocus || this.props.autoSelect) { + setTimeout(() => { + input.focus(); + + if (this.props.autoSelect) { + input.select(); + } + }, 1); } } diff --git a/tgui/packages/tgui/components/KeyListener.tsx b/tgui/packages/tgui/components/KeyListener.tsx new file mode 100644 index 0000000000..46b5276536 --- /dev/null +++ b/tgui/packages/tgui/components/KeyListener.tsx @@ -0,0 +1,39 @@ +import { Component } from "inferno"; +import { KeyEvent } from "../events"; +import { listenForKeyEvents } from "../hotkeys"; + +type KeyListenerProps = Partial<{ + onKey: (key: KeyEvent) => void, + onKeyDown: (key: KeyEvent) => void, + onKeyUp: (key: KeyEvent) => void, +}>; + +export class KeyListener extends Component { + dispose: () => void; + + constructor() { + super(); + + this.dispose = listenForKeyEvents(key => { + if (this.props.onKey) { + this.props.onKey(key); + } + + if (key.isDown() && this.props.onKeyDown) { + this.props.onKeyDown(key); + } + + if (key.isUp() && this.props.onKeyUp) { + this.props.onKeyUp(key); + } + }); + } + + componentWillUnmount() { + this.dispose(); + } + + render() { + return null; + } +} diff --git a/tgui/packages/tgui/components/Popper.tsx b/tgui/packages/tgui/components/Popper.tsx index 5922e9a569..f288dc67e7 100644 --- a/tgui/packages/tgui/components/Popper.tsx +++ b/tgui/packages/tgui/components/Popper.tsx @@ -1,10 +1,11 @@ -import { createPopper, OptionsGeneric } from "@popperjs/core"; -import { Component, findDOMfromVNode, InfernoNode, render } from "inferno"; +import { createPopper } from '@popperjs/core'; +import { ArgumentsOf } from 'common/types'; +import { Component, findDOMfromVNode, InfernoNode, render } from 'inferno'; type PopperProps = { popperContent: InfernoNode; - options?: Partial>; - additionalStyles?: CSSProperties, + options?: ArgumentsOf[2]; + additionalStyles?: CSSProperties; }; export class Popper extends Component { @@ -20,12 +21,9 @@ export class Popper extends Component { } componentDidMount() { - const { - additionalStyles, - options, - } = this.props; + const { additionalStyles, options } = this.props; - this.renderedContent = document.createElement("div"); + this.renderedContent = document.createElement('div'); if (additionalStyles) { for (const [attribute, value] of Object.entries(additionalStyles)) { this.renderedContent.style[attribute] = value; @@ -35,19 +33,24 @@ export class Popper extends Component { this.renderPopperContent(() => { document.body.appendChild(this.renderedContent); + // HACK: We don't want to create a wrapper, as it could break the layout + // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. + // This is usually bad as refs are usually better, but refs did + // not work in this case, as they weren't propagating correctly. + // A previous attempt was made as a render prop that passed an ID, + // but this made consuming use too unwieldly. + // This code is copied from `findDOMNode` in inferno-extras. + // Because this component is written in TypeScript, we will know + // immediately if this internal variable is removed. + const domNode = findDOMfromVNode(this.$LI, true); + if (!domNode) { + return; + } + this.popperInstance = createPopper( - // HACK: We don't want to create a wrapper, as it could break the layout - // of consumers, so we do the inferno equivalent of `findDOMNode(this)`. - // This is usually bad as refs are usually better, but refs did - // not work in this case, as they weren't propagating correctly. - // A previous attempt was made as a render prop that passed an ID, - // but this made consuming use too unwieldly. - // This code is copied from `findDOMNode` in inferno-extras. - // Because this component is written in TypeScript, we will know - // immediately if this internal variable is removed. - findDOMfromVNode(this.$LI, true), + domNode, this.renderedContent, - options, + options ); }); } @@ -58,12 +61,20 @@ export class Popper extends Component { componentWillUnmount() { this.popperInstance?.destroy(); - this.renderedContent.remove(); - this.renderedContent = null; + render(null, this.renderedContent, () => { + this.renderedContent.remove(); + }); } renderPopperContent(callback: () => void) { - render(this.props.popperContent, this.renderedContent, callback); + // `render` errors when given false, so we convert it to `null`, + // which is supported. + render( + this.props.popperContent || null, + this.renderedContent, + callback, + this.context, + ); } render() { diff --git a/tgui/packages/tgui/components/Section.tsx b/tgui/packages/tgui/components/Section.tsx index 43317fd43b..d49043ee8a 100644 --- a/tgui/packages/tgui/components/Section.tsx +++ b/tgui/packages/tgui/components/Section.tsx @@ -40,7 +40,11 @@ export class Section extends Component { addScrollableNode(this.scrollableRef.current); } if (this.props.autoFocus) { - setTimeout(() => this.scrollableRef.current.focus(), 1); + setTimeout(() => { + if (this.scrollableRef.current) { + return this.scrollableRef.current.focus(); + } + }, 1); } } diff --git a/tgui/packages/tgui/components/Stack.tsx b/tgui/packages/tgui/components/Stack.tsx index 1801c3a145..66de1b2557 100644 --- a/tgui/packages/tgui/components/Stack.tsx +++ b/tgui/packages/tgui/components/Stack.tsx @@ -5,6 +5,7 @@ */ import { classes } from 'common/react'; +import { RefObject } from 'inferno'; import { Flex, FlexItemProps, FlexProps } from './Flex'; interface StackProps extends FlexProps { @@ -29,14 +30,19 @@ export const Stack = (props: StackProps) => { ); }; -const StackItem = (props: FlexProps) => { - const { className, ...rest } = props; +type StackItemProps = FlexProps & { + innerRef?: RefObject, +}; + +const StackItem = (props: StackItemProps) => { + const { className, innerRef, ...rest } = props; return ( ); }; diff --git a/tgui/packages/tgui/components/Tooltip.tsx b/tgui/packages/tgui/components/Tooltip.tsx index 7638ce07de..0cf61ed04f 100644 --- a/tgui/packages/tgui/components/Tooltip.tsx +++ b/tgui/packages/tgui/components/Tooltip.tsx @@ -7,7 +7,7 @@ const DEFAULT_PLACEMENT = "top"; type TooltipProps = { children?: InfernoNode; - content: string; + content: InfernoNode; position?: Placement, }; @@ -15,14 +15,15 @@ type TooltipState = { hovered: boolean; }; -export class Tooltip extends Component { - constructor() { - super(); +const DISABLE_EVENT_LISTENERS = [{ + name: "eventListeners", + enabled: false, +}]; - this.state = { - hovered: false, - }; - } +export class Tooltip extends Component { + state = { + hovered: false, + }; componentDidMount() { // HACK: We don't want to create a wrapper, as it could break the layout @@ -35,6 +36,10 @@ export class Tooltip extends Component { // immediately if this internal variable is removed. const domNode = findDOMfromVNode(this.$LI, true); + if (!domNode) { + return; + } + domNode.addEventListener("mouseenter", () => { this.setState({ hovered: true, @@ -53,6 +58,7 @@ export class Tooltip extends Component { { } additionalStyles={{ "pointer-events": "none", + "z-index": 2, }}> {this.props.children} diff --git a/tgui/packages/tgui/components/TrackOutsideClicks.tsx b/tgui/packages/tgui/components/TrackOutsideClicks.tsx new file mode 100644 index 0000000000..abf8286c7f --- /dev/null +++ b/tgui/packages/tgui/components/TrackOutsideClicks.tsx @@ -0,0 +1,37 @@ +import { Component, createRef } from "inferno"; + +export class TrackOutsideClicks extends Component<{ + onOutsideClick: () => void, +}> { + ref = createRef(); + + constructor() { + super(); + + this.handleOutsideClick = this.handleOutsideClick.bind(this); + + document.addEventListener("click", this.handleOutsideClick); + } + + componentWillUnmount() { + document.removeEventListener("click", this.handleOutsideClick); + } + + handleOutsideClick(event: MouseEvent) { + if (!(event.target instanceof Node)) { + return; + } + + if (this.ref.current && !this.ref.current.contains(event.target)) { + this.props.onOutsideClick(); + } + } + + render() { + return ( +
+ {this.props.children} +
+ ); + } +} diff --git a/tgui/packages/tgui/components/index.js b/tgui/packages/tgui/components/index.js index 93457d1234..d27639a6c1 100644 --- a/tgui/packages/tgui/components/index.js +++ b/tgui/packages/tgui/components/index.js @@ -5,6 +5,7 @@ */ export { AnimatedNumber } from './AnimatedNumber'; +export { Autofocus } from './Autofocus'; export { Blink } from './Blink'; export { BlockQuote } from './BlockQuote'; export { Box } from './Box'; @@ -22,6 +23,7 @@ export { Grid } from './Grid'; export { Icon } from './Icon'; export { InfinitePlane } from './InfinitePlane'; export { Input } from './Input'; +export { KeyListener } from './KeyListener'; export { Knob } from './Knob'; export { LabeledControls } from './LabeledControls'; export { LabeledList } from './LabeledList'; @@ -39,4 +41,5 @@ export { Table } from './Table'; export { Tabs } from './Tabs'; export { TextArea } from './TextArea'; export { TimeDisplay } from './TimeDisplay'; +export { TrackOutsideClicks } from './TrackOutsideClicks'; export { Tooltip } from './Tooltip'; diff --git a/tgui/packages/tgui/debug/middleware.js b/tgui/packages/tgui/debug/middleware.js index 6c11d32e0f..92e64fe3bd 100644 --- a/tgui/packages/tgui/debug/middleware.js +++ b/tgui/packages/tgui/debug/middleware.js @@ -39,12 +39,12 @@ export const debugMiddleware = store => { }; export const relayMiddleware = store => { - const devServer = require('tgui-dev-server/link/client'); + const devServer = require('tgui-dev-server/link/client.cjs'); const externalBrowser = location.search === '?external'; if (externalBrowser) { devServer.subscribe(msg => { const { type, payload } = msg; - if (type === 'relay' && payload.windowId === window.__windowId__) { + if (type === 'relay' && payload.windowId === Byond.windowId) { store.dispatch({ ...payload.action, relayed: true, @@ -70,7 +70,7 @@ export const relayMiddleware = store => { devServer.sendMessage({ type: 'relay', payload: { - windowId: window.__windowId__, + windowId: Byond.windowId, action, }, }); diff --git a/tgui/packages/tgui/drag.js b/tgui/packages/tgui/drag.js index d6f0967a8d..406b730b1a 100644 --- a/tgui/packages/tgui/drag.js +++ b/tgui/packages/tgui/drag.js @@ -5,12 +5,13 @@ */ import { storage } from 'common/storage'; -import { vecAdd, vecInverse, vecMultiply, vecScale } from 'common/vector'; +import { vecAdd, vecSubtract, vecMultiply, vecScale } from 'common/vector'; import { createLogger } from './logging'; const logger = createLogger('drag'); +const pixelRatio = window.devicePixelRatio ?? 1; -let windowKey = window.__windowId__; +let windowKey = Byond.windowId; let dragging = false; let resizing = false; let screenOffset = [0, 0]; @@ -24,37 +25,37 @@ export const setWindowKey = key => { windowKey = key; }; -export const getWindowPosition = () => [ - window.screenLeft, - window.screenTop, +const getWindowPosition = () => [ + window.screenLeft * pixelRatio, + window.screenTop * pixelRatio, ]; -export const getWindowSize = () => [ - window.innerWidth, - window.innerHeight, +const getWindowSize = () => [ + window.innerWidth * pixelRatio, + window.innerHeight * pixelRatio, ]; -export const setWindowPosition = vec => { +const setWindowPosition = vec => { const byondPos = vecAdd(vec, screenOffset); - return Byond.winset(window.__windowId__, { + return Byond.winset(Byond.windowId, { pos: byondPos[0] + ',' + byondPos[1], }); }; -export const setWindowSize = vec => { - return Byond.winset(window.__windowId__, { +const setWindowSize = vec => { + return Byond.winset(Byond.windowId, { size: vec[0] + 'x' + vec[1], }); }; -export const getScreenPosition = () => [ +const getScreenPosition = () => [ 0 - screenOffset[0], 0 - screenOffset[1], ]; -export const getScreenSize = () => [ - window.screen.availWidth, - window.screen.availHeight, +const getScreenSize = () => [ + window.screen.availWidth * pixelRatio, + window.screen.availHeight * pixelRatio, ]; /** @@ -83,7 +84,7 @@ const touchRecents = (recents, touchedItem, limit = 50) => { return [nextRecents, trimmedItem]; }; -export const storeWindowGeometry = async () => { +const storeWindowGeometry = async () => { logger.log('storing geometry'); const geometry = { pos: getWindowPosition(), @@ -106,14 +107,19 @@ export const recallWindowGeometry = async (options = {}) => { if (geometry) { logger.log('recalled geometry:', geometry); } + // options.pos is assumed to already be in display-pixels let pos = geometry?.pos || options.pos; let size = options.size; + // Convert size from css-pixels to display-pixels + if (size) { + size = [ + size[0] * pixelRatio, + size[1] * pixelRatio, + ]; + } // Wait until screen offset gets resolved await screenOffsetPromise; - const areaAvailable = [ - window.screen.availWidth, - window.screen.availHeight, - ]; + const areaAvailable = getScreenSize(); // Set window size if (size) { // Constraint size to not exceed available screen area. @@ -143,10 +149,12 @@ export const recallWindowGeometry = async (options = {}) => { export const setupDrag = async () => { // Calculate screen offset caused by the windows taskbar - screenOffsetPromise = Byond.winget(window.__windowId__, 'pos') + let windowPosition = getWindowPosition(); + + screenOffsetPromise = Byond.winget(Byond.windowId, 'pos') .then(pos => [ - pos.x - window.screenLeft, - pos.y - window.screenTop, + pos.x - windowPosition[0], + pos.y - windowPosition[1], ]); screenOffset = await screenOffsetPromise; logger.debug('screen offset', screenOffset); @@ -179,10 +187,10 @@ const constraintPosition = (pos, size) => { export const dragStartHandler = event => { logger.log('drag start'); dragging = true; - dragPointOffset = [ - window.screenLeft - event.screenX, - window.screenTop - event.screenY, - ]; + let windowPosition = getWindowPosition(); + dragPointOffset = vecSubtract( + [event.screenX, event.screenY], + getWindowPosition()); // Focus click target event.target?.focus(); document.addEventListener('mousemove', dragMoveHandler); @@ -204,7 +212,7 @@ const dragMoveHandler = event => { return; } event.preventDefault(); - setWindowPosition(vecAdd( + setWindowPosition(vecSubtract( [event.screenX, event.screenY], dragPointOffset)); }; @@ -213,14 +221,10 @@ export const resizeStartHandler = (x, y) => event => { resizeMatrix = [x, y]; logger.log('resize start', resizeMatrix); resizing = true; - dragPointOffset = [ - window.screenLeft - event.screenX, - window.screenTop - event.screenY, - ]; - initialSize = [ - window.innerWidth, - window.innerHeight, - ]; + dragPointOffset = vecSubtract( + [event.screenX, event.screenY], + getWindowPosition()); + initialSize = getWindowSize(); // Focus click target event.target?.focus(); document.addEventListener('mousemove', resizeMoveHandler); @@ -242,13 +246,19 @@ const resizeMoveHandler = event => { return; } event.preventDefault(); - size = vecAdd(initialSize, vecMultiply(resizeMatrix, vecAdd( + const currentOffset = vecSubtract( [event.screenX, event.screenY], - vecInverse([window.screenLeft, window.screenTop]), - dragPointOffset, - [1, 1]))); + getWindowPosition()); + const delta = vecSubtract( + currentOffset, + dragPointOffset); + // Extra 1x1 area is added to ensure the browser can see the cursor + size = vecAdd( + initialSize, + vecMultiply(resizeMatrix, delta), + [1, 1]); // Sane window size values - size[0] = Math.max(size[0], 150); - size[1] = Math.max(size[1], 50); + size[0] = Math.max(size[0], 150 * pixelRatio); + size[1] = Math.max(size[1], 50 * pixelRatio); setWindowSize(size); }; diff --git a/tgui/packages/tgui/events.js b/tgui/packages/tgui/events.js index 42812af83d..18af3c0a4a 100644 --- a/tgui/packages/tgui/events.js +++ b/tgui/packages/tgui/events.js @@ -8,7 +8,6 @@ import { EventEmitter } from 'common/events'; import { KEY_ALT, KEY_CTRL, KEY_F1, KEY_F12, KEY_SHIFT } from 'common/keycodes'; -import { logger } from './logging'; export const globalEvents = new EventEmitter(); let ignoreWindowFocus = false; diff --git a/tgui/packages/tgui/focus.js b/tgui/packages/tgui/focus.js index 60c6e430a2..bb97b75d3b 100644 --- a/tgui/packages/tgui/focus.js +++ b/tgui/packages/tgui/focus.js @@ -19,7 +19,7 @@ export const focusMap = () => { * Moves focus to the browser window. */ export const focusWindow = () => { - Byond.winset(window.__windowId__, { + Byond.winset(Byond.windowId, { focus: true, }); }; diff --git a/tgui/packages/tgui/hotkeys.ts b/tgui/packages/tgui/hotkeys.ts index 3334c892cb..b11c8cfcee 100644 --- a/tgui/packages/tgui/hotkeys.ts +++ b/tgui/packages/tgui/hotkeys.ts @@ -31,6 +31,9 @@ const hotKeysAcquired = [ // State of passed-through keys. const keyState: Record = {}; +// Custom listeners for key events +const keyListeners: ((key: KeyEvent) => void)[] = []; + /** * Converts a browser keycode to BYOND keycode. */ @@ -178,6 +181,38 @@ export const setupHotKeys = () => { releaseHeldKeys(); }); globalEvents.on('key', (key: KeyEvent) => { + for (const keyListener of keyListeners) { + keyListener(key); + } + handlePassthrough(key); }); }; + +/** + * Registers for any key events, such as key down or key up. + * This should be preferred over directly connecting to keydown/keyup + * as it lets tgui prevent the key from reaching BYOND. + * + * If using in a component, prefer KeyListener, which automatically handles + * stopping listening when unmounting. + * + * @param callback The function to call whenever a key event occurs + * @returns A callback to stop listening + */ +export const listenForKeyEvents = ( + callback: (key: KeyEvent) => void, +): () => void => { + keyListeners.push(callback); + + let removed = false; + + return () => { + if (removed) { + return; + } + + removed = true; + keyListeners.splice(keyListeners.indexOf(callback), 1); + }; +}; diff --git a/tgui/packages/tgui/http.ts b/tgui/packages/tgui/http.ts new file mode 100644 index 0000000000..96a2d9b56d --- /dev/null +++ b/tgui/packages/tgui/http.ts @@ -0,0 +1,17 @@ +/** + * An equivalent to `fetch`, except will automatically retry. + */ +export const fetchRetry + = ( + url: string, + options?: RequestInit, + retryTimer: number = 1000, + ): Promise => { + return fetch(url, options).catch(() => { + return new Promise(resolve => { + setTimeout(() => { + fetchRetry(url, options, retryTimer).then(resolve); + }, retryTimer); + }); + }); + }; diff --git a/tgui/packages/tgui/index.js b/tgui/packages/tgui/index.js index 742032ab57..c08f725698 100644 --- a/tgui/packages/tgui/index.js +++ b/tgui/packages/tgui/index.js @@ -20,7 +20,7 @@ import './styles/themes/wizard.scss'; import './styles/themes/abstract.scss'; import { perf } from 'common/perf'; -import { setupHotReloading } from 'tgui-dev-server/link/client'; +import { setupHotReloading } from 'tgui-dev-server/link/client.cjs'; import { setupHotKeys } from './hotkeys'; import { captureExternalLinks } from './links'; import { createRenderer } from './renderer'; @@ -53,20 +53,11 @@ const setupApp = () => { setupHotKeys(); captureExternalLinks(); - // Subscribe for state updates + // Re-render UI on store updates store.subscribe(renderApp); - // Dispatch incoming messages - window.update = msg => store.dispatch(Byond.parseJson(msg)); - - // Process the early update queue - while (true) { - const msg = window.__updateQueue__.shift(); - if (!msg) { - break; - } - window.update(msg); - } + // Dispatch incoming messages as store actions + Byond.subscribe((type, payload) => store.dispatch({ type, payload })); // Enable hot module reloading if (module.hot) { diff --git a/tgui/packages/tgui/interfaces/APC.js b/tgui/packages/tgui/interfaces/APC.js index 1ee7419b47..f25fbe42aa 100644 --- a/tgui/packages/tgui/interfaces/APC.js +++ b/tgui/packages/tgui/interfaces/APC.js @@ -1,6 +1,6 @@ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; -import { Box, Button, Dimmer, Icon, LabeledList, NoticeBox, ProgressBar, Section, Flex } from '../components'; +import { Box, Button, Dimmer, Icon, LabeledList, ProgressBar, Section } from '../components'; import { Window } from '../layouts'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; import { FullscreenNotice } from './common/FullscreenNotice'; @@ -285,4 +285,4 @@ const ApcFailure = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AccountsTerminal.js b/tgui/packages/tgui/interfaces/AccountsTerminal.js index 7f4f203044..57a934e183 100644 --- a/tgui/packages/tgui/interfaces/AccountsTerminal.js +++ b/tgui/packages/tgui/interfaces/AccountsTerminal.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend, useSharedState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, Input, Section, Table, Tabs } from "../components"; +import { Box, Button, LabeledList, Input, Section, Table, Tabs } from "../components"; import { Window } from "../layouts"; export const AccountsTerminal = (props, context) => { @@ -126,7 +124,7 @@ const DetailedView = (props, context) => { suspended, transactions, } = data; - + return (
{ const { accounts, } = data; - + return (
{accounts.length && ( diff --git a/tgui/packages/tgui/interfaces/AdminShuttleController.js b/tgui/packages/tgui/interfaces/AdminShuttleController.js index 24e498e8d3..e3bbf3d9e2 100644 --- a/tgui/packages/tgui/interfaces/AdminShuttleController.js +++ b/tgui/packages/tgui/interfaces/AdminShuttleController.js @@ -1,7 +1,6 @@ import { sortBy } from 'common/collections'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table } from "../components"; +import { Button, Section, Table } from "../components"; import { Window } from "../layouts"; export const AdminShuttleController = (props, context) => { @@ -86,4 +85,4 @@ const shuttleStatusToWords = status => { default: return "UNK"; } -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AgentCard.js b/tgui/packages/tgui/interfaces/AgentCard.js index e073739661..a9fc9ad6ae 100644 --- a/tgui/packages/tgui/interfaces/AgentCard.js +++ b/tgui/packages/tgui/interfaces/AgentCard.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table } from "../components"; +import { Button, Section, Table } from "../components"; import { Window } from "../layouts"; export const AgentCard = (props, context) => { @@ -45,4 +43,4 @@ export const AgentCard = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AiSupermatter.js b/tgui/packages/tgui/interfaces/AiSupermatter.js index 9587bf99a5..6479ac9d28 100644 --- a/tgui/packages/tgui/interfaces/AiSupermatter.js +++ b/tgui/packages/tgui/interfaces/AiSupermatter.js @@ -1,8 +1,6 @@ -import { round } from 'common/math'; import { useBackend } from "../backend"; -import { Box, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Icon, LabeledList, ProgressBar, Section } from "../components"; import { Window } from "../layouts"; -import { formatSiUnit, formatPower } from "../format"; import { FullscreenNotice } from './common/FullscreenNotice'; export const AiSupermatter = (props, context) => { @@ -87,4 +85,4 @@ const AiSupermatterContent = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AirAlarm.js b/tgui/packages/tgui/interfaces/AirAlarm.js index b3555cc4d2..1f4e83f131 100644 --- a/tgui/packages/tgui/interfaces/AirAlarm.js +++ b/tgui/packages/tgui/interfaces/AirAlarm.js @@ -1,8 +1,7 @@ import { toFixed } from 'common/math'; -import { decodeHtmlEntities } from 'common/string'; import { Fragment } from 'inferno'; import { useBackend, useLocalState } from '../backend'; -import { Box, Button, LabeledList, NumberInput, Section } from '../components'; +import { Box, Button, LabeledList, Section } from '../components'; import { getGasLabel, getGasColor } from '../constants'; import { Window } from '../layouts'; import { InterfaceLockNoticeBox } from './common/InterfaceLockNoticeBox'; diff --git a/tgui/packages/tgui/interfaces/AlertModal.js b/tgui/packages/tgui/interfaces/AlertModal.js index c660c89786..2815bd93d5 100644 --- a/tgui/packages/tgui/interfaces/AlertModal.js +++ b/tgui/packages/tgui/interfaces/AlertModal.js @@ -4,7 +4,7 @@ * @license MIT */ -import { clamp01 } from 'common/math'; +import { Loader } from "./common/Loader"; import { useBackend } from '../backend'; import { Component, createRef } from 'inferno'; import { Box, Flex, Section } from '../components'; @@ -135,15 +135,3 @@ export class AlertModal extends Component { } } - -export const Loader = props => { - const { value } = props; - - return ( -
- -
- ); -}; diff --git a/tgui/packages/tgui/interfaces/AlgaeFarm.js b/tgui/packages/tgui/interfaces/AlgaeFarm.js index 257156c9d4..9da3caac99 100644 --- a/tgui/packages/tgui/interfaces/AlgaeFarm.js +++ b/tgui/packages/tgui/interfaces/AlgaeFarm.js @@ -1,8 +1,6 @@ import { useBackend } from '../backend'; -import { Box, Button, NoticeBox, LabeledList, ProgressBar, Section, Slider, Table } from '../components'; -import { formatPower } from '../format'; +import { Box, Button, NoticeBox, LabeledList, ProgressBar, Section, Table } from '../components'; import { Window } from '../layouts'; -import { round } from 'common/math'; import { capitalize } from 'common/string'; export const AlgaeFarm = (props, context) => { @@ -18,7 +16,7 @@ export const AlgaeFarm = (props, context) => { output, errorText, } = data; - + return ( { ) : ( No connection detected. - )} + )}
diff --git a/tgui/packages/tgui/interfaces/AppearanceChanger.js b/tgui/packages/tgui/interfaces/AppearanceChanger.js index 647e6f66ff..1a54b04f87 100644 --- a/tgui/packages/tgui/interfaces/AppearanceChanger.js +++ b/tgui/packages/tgui/interfaces/AppearanceChanger.js @@ -1,8 +1,8 @@ -import { filter, sortBy } from 'common/collections'; +import { sortBy } from 'common/collections'; import { capitalize, decodeHtmlEntities } from 'common/string'; import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, ByondUi, Flex, Icon, LabeledList, ProgressBar, Section, Tabs, ColorBox } from "../components"; +import { Box, Button, ByondUi, Flex, LabeledList, Section, Tabs, ColorBox } from "../components"; import { Window } from "../layouts"; export const AppearanceChanger = (props, context) => { @@ -324,7 +324,7 @@ const AppearanceChangerHair = (props, context) => { return (
{hair_styles.map(hair => ( -
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AssemblyInfrared.js b/tgui/packages/tgui/interfaces/AssemblyInfrared.js index 82e87e39ed..9eeeab38ad 100644 --- a/tgui/packages/tgui/interfaces/AssemblyInfrared.js +++ b/tgui/packages/tgui/interfaces/AssemblyInfrared.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const AssemblyInfrared = (props, context) => { @@ -38,4 +36,4 @@ export const AssemblyInfrared = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AssemblyProx.js b/tgui/packages/tgui/interfaces/AssemblyProx.js index d16bc83cad..0a77d2d149 100644 --- a/tgui/packages/tgui/interfaces/AssemblyProx.js +++ b/tgui/packages/tgui/interfaces/AssemblyProx.js @@ -1,7 +1,6 @@ import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, NumberInput, Section } from "../components"; +import { Button, LabeledList, NumberInput, Section } from "../components"; import { Window } from "../layouts"; import { formatTime } from '../format'; @@ -62,4 +61,4 @@ export const AssemblyProx = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AssemblyTimer.js b/tgui/packages/tgui/interfaces/AssemblyTimer.js index 4a60e668b6..db1872bfc8 100644 --- a/tgui/packages/tgui/interfaces/AssemblyTimer.js +++ b/tgui/packages/tgui/interfaces/AssemblyTimer.js @@ -1,7 +1,6 @@ import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, NumberInput, Section } from "../components"; +import { Button, LabeledList, NumberInput, Section } from "../components"; import { Window } from "../layouts"; import { formatTime } from '../format'; @@ -38,4 +37,4 @@ export const AssemblyTimer = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/AtmosControl.js b/tgui/packages/tgui/interfaces/AtmosControl.js index 31b5cbe698..0bcd264b1a 100644 --- a/tgui/packages/tgui/interfaces/AtmosControl.js +++ b/tgui/packages/tgui/interfaces/AtmosControl.js @@ -1,7 +1,7 @@ import { sortBy } from 'common/collections'; import { Window } from '../layouts'; import { Fragment } from 'inferno'; -import { Button, Box, NumberInput, Tabs, Icon, Section, NanoMap } from '../components'; +import { Button, Box, Tabs, Icon, Section, NanoMap } from '../components'; import { useBackend, useLocalState } from '../backend'; import { createLogger } from '../logging'; const logger = createLogger("fuck"); @@ -40,10 +40,10 @@ export const AtmosControlContent = (props, context) => { - ) || ( + ) || ( No target seed packet loaded. )} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/Canister.js b/tgui/packages/tgui/interfaces/Canister.js index c7b6274309..63f5437d12 100644 --- a/tgui/packages/tgui/interfaces/Canister.js +++ b/tgui/packages/tgui/interfaces/Canister.js @@ -1,5 +1,4 @@ import { toFixed } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { AnimatedNumber, Box, Button, Icon, Knob, LabeledControls, LabeledList, Section, Tooltip } from '../components'; import { formatSiUnit } from '../format'; diff --git a/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.js b/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.js index 12f2838faf..68d2c84459 100644 --- a/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.js +++ b/tgui/packages/tgui/interfaces/CasinoPrizeDispenser.js @@ -1,10 +1,9 @@ import { createSearch } from 'common/string'; import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Collapsible, Dropdown, Flex, Input, NoticeBox, Section } from '../components'; +import { Box, Button, Collapsible, Dropdown, Flex, Input, Section } from '../components'; import { Window } from "../layouts"; import { refocusLayout } from '../layouts'; -import { logger } from '../logging'; const sortTypes = { 'Alphabetical': (a, b) => a - b, @@ -19,7 +18,7 @@ export const CasinoPrizeDispenser = (props, context) => { - + @@ -177,4 +176,4 @@ const CasinoPrizeDispenserItemsCategory = (properties, context) => { ))} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/CharacterDirectory.js b/tgui/packages/tgui/interfaces/CharacterDirectory.js index 64999d619d..573f4a6f24 100644 --- a/tgui/packages/tgui/interfaces/CharacterDirectory.js +++ b/tgui/packages/tgui/interfaces/CharacterDirectory.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, Modal, ProgressBar, Section, Table } from "../components"; +import { Box, Button, Icon, LabeledList, Section, Table } from "../components"; import { Window } from "../layouts"; const getTagColor = tag => { @@ -208,4 +207,4 @@ const SortButton = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/ChemDispenser.js b/tgui/packages/tgui/interfaces/ChemDispenser.js index b6a9b496d2..886bae8096 100644 --- a/tgui/packages/tgui/interfaces/ChemDispenser.js +++ b/tgui/packages/tgui/interfaces/ChemDispenser.js @@ -1,6 +1,6 @@ import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, LabeledList, ProgressBar, Slider, Section } from "../components"; +import { Box, Button, Flex, LabeledList, Slider, Section } from "../components"; import { BeakerContents } from "../interfaces/common/BeakerContents"; import { Window } from "../layouts"; diff --git a/tgui/packages/tgui/interfaces/ClawMachine.js b/tgui/packages/tgui/interfaces/ClawMachine.js index 6eb372b0c3..1af21a1326 100644 --- a/tgui/packages/tgui/interfaces/ClawMachine.js +++ b/tgui/packages/tgui/interfaces/ClawMachine.js @@ -1,5 +1,5 @@ import { useBackend } from "../backend"; -import { Button, ProgressBar, Flex, Box, LabeledList } from "../components"; +import { Button, ProgressBar, Box, LabeledList } from "../components"; import { Window } from "../layouts"; export const ClawMachine = (props, context) => { @@ -15,8 +15,8 @@ export const ClawMachine = (props, context) => { if (gameStatus === 'CLAWMACHINE_NEW') { body = ( - +

Pay to Play!

{instructions} @@ -28,9 +28,9 @@ export const ClawMachine = (props, context) => { } else if (gameStatus === 'CLAWMACHINE_END') { body = ( - -

+

Thank you for playing!

{winscreen}


@@ -56,7 +56,7 @@ export const ClawMachine = (props, context) => { + align="center">


{instructions}




@@ -75,11 +75,11 @@ export const ClawMachine = (props, context) => { ); } return ( -
{body}
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/Cleanbot.js b/tgui/packages/tgui/interfaces/Cleanbot.js index 33a810b024..d7b9a6a2ca 100644 --- a/tgui/packages/tgui/interfaces/Cleanbot.js +++ b/tgui/packages/tgui/interfaces/Cleanbot.js @@ -1,5 +1,5 @@ import { useBackend } from "../backend"; -import { Box, Button, LabeledList, ProgressBar, Section, NumberInput } from "../components"; +import { Box, Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const Cleanbot = (props, context) => { diff --git a/tgui/packages/tgui/interfaces/ColorMate.js b/tgui/packages/tgui/interfaces/ColorMate.js index 546438278f..702e9699c5 100644 --- a/tgui/packages/tgui/interfaces/ColorMate.js +++ b/tgui/packages/tgui/interfaces/ColorMate.js @@ -1,6 +1,6 @@ import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, Flex, Section } from "../components"; import { Window } from "../layouts"; export const ColorMate = (props, context) => { @@ -63,4 +63,4 @@ export const ColorMate = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/Communicator.js b/tgui/packages/tgui/interfaces/Communicator.js index d97f01937b..26f9ffd9ff 100644 --- a/tgui/packages/tgui/interfaces/Communicator.js +++ b/tgui/packages/tgui/interfaces/Communicator.js @@ -2,7 +2,7 @@ import { filter } from 'common/collections'; import { decodeHtmlEntities, toTitleCase } from 'common/string'; import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, ByondUi, Button, Flex, Icon, LabeledList, Input, ProgressBar, Section, Table } from "../components"; +import { Box, ByondUi, Button, Flex, Icon, LabeledList, Input, Section, Table } from "../components"; import { Window } from "../layouts"; import { CrewManifestContent } from './CrewManifest'; diff --git a/tgui/packages/tgui/interfaces/ComputerFabricator.js b/tgui/packages/tgui/interfaces/ComputerFabricator.js index acc991cf14..e31e869154 100644 --- a/tgui/packages/tgui/interfaces/ComputerFabricator.js +++ b/tgui/packages/tgui/interfaces/ComputerFabricator.js @@ -1,5 +1,4 @@ import { multiline } from 'common/string'; -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Box, Button, Grid, Section, Table, Tooltip } from '../components'; import { Window } from '../layouts'; diff --git a/tgui/packages/tgui/interfaces/CookingAppliance.js b/tgui/packages/tgui/interfaces/CookingAppliance.js index 2a2166c86b..ed919629a4 100644 --- a/tgui/packages/tgui/interfaces/CookingAppliance.js +++ b/tgui/packages/tgui/interfaces/CookingAppliance.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, AnimatedNumber } from "../components"; +import { Button, Flex, LabeledList, ProgressBar, Section, AnimatedNumber } from "../components"; import { Window } from "../layouts"; export const CookingAppliance = (props, context) => { @@ -75,4 +73,4 @@ export const CookingAppliance = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/CrewMonitor.js b/tgui/packages/tgui/interfaces/CrewMonitor.js index 62d9774169..e21a43ce87 100644 --- a/tgui/packages/tgui/interfaces/CrewMonitor.js +++ b/tgui/packages/tgui/interfaces/CrewMonitor.js @@ -2,7 +2,7 @@ import { sortBy } from 'common/collections'; import { flow } from 'common/fp'; import { useBackend, useLocalState } from "../backend"; import { Window } from "../layouts"; -import { NanoMap, Box, Table, Button, Tabs, Icon, NumberInput } from "../components"; +import { NanoMap, Box, Table, Button, Tabs, Icon } from "../components"; import { Fragment } from 'inferno'; const getStatText = cm => { @@ -27,7 +27,7 @@ const getStatColor = cm => { export const CrewMonitor = () => { return ( - @@ -161,7 +161,7 @@ const CrewMonitorMapView = (props, context) => { return ( setZoom(v)}> - {data.crewmembers.filter(x => + {data.crewmembers.filter(x => (x.sensor_type === 3 && ~~x.realZ === ~~config.mapZLevel) ).map(cm => ( { )} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/DNAForensics.js b/tgui/packages/tgui/interfaces/DNAForensics.js index 0bc2c39ef2..a7415eaa82 100644 --- a/tgui/packages/tgui/interfaces/DNAForensics.js +++ b/tgui/packages/tgui/interfaces/DNAForensics.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, LabeledList, ProgressBar, Section } from "../components"; import { Window } from "../layouts"; export const DNAForensics = (props, context) => { @@ -61,4 +60,4 @@ export const DNAForensics = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger.js b/tgui/packages/tgui/interfaces/DestinationTagger.js index 5a70ede6d6..23b3236b60 100644 --- a/tgui/packages/tgui/interfaces/DestinationTagger.js +++ b/tgui/packages/tgui/interfaces/DestinationTagger.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table } from "../components"; +import { Button, Flex, Section } from "../components"; import { Window } from "../layouts"; export const DestinationTagger = (props, context) => { @@ -31,4 +29,4 @@ export const DestinationTagger = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/DiseaseSplicer.js b/tgui/packages/tgui/interfaces/DiseaseSplicer.js index 8bab6dae3d..86029afefe 100644 --- a/tgui/packages/tgui/interfaces/DiseaseSplicer.js +++ b/tgui/packages/tgui/interfaces/DiseaseSplicer.js @@ -1,8 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; -import { formatCommaNumber } from '../format'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, LabeledList, ProgressBar, Section } from "../components"; import { Window } from "../layouts"; export const DiseaseSplicer = (props, context) => { @@ -168,4 +166,4 @@ const DiseaseSplicerStorage = (props, context) => { ) : null)} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/DishIncubator.js b/tgui/packages/tgui/interfaces/DishIncubator.js index 0ccf5d0716..5d1924df04 100644 --- a/tgui/packages/tgui/interfaces/DishIncubator.js +++ b/tgui/packages/tgui/interfaces/DishIncubator.js @@ -1,8 +1,7 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { formatCommaNumber } from '../format'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, Flex, LabeledList, ProgressBar, Section } from "../components"; import { Window } from "../layouts"; export const DishIncubator = (props, context) => { @@ -161,4 +160,4 @@ export const DishIncubator = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/DroneConsole.js b/tgui/packages/tgui/interfaces/DroneConsole.js index 055a4b3422..e24a84683b 100644 --- a/tgui/packages/tgui/interfaces/DroneConsole.js +++ b/tgui/packages/tgui/interfaces/DroneConsole.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Dropdown } from "../components"; +import { Box, Button, LabeledList, Section, Dropdown } from "../components"; import { Window } from "../layouts"; export const DroneConsole = (props, context) => { @@ -99,4 +98,4 @@ export const DroneConsole = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/EmbeddedController.js b/tgui/packages/tgui/interfaces/EmbeddedController.js index 049a2df185..1c0562426b 100644 --- a/tgui/packages/tgui/interfaces/EmbeddedController.js +++ b/tgui/packages/tgui/interfaces/EmbeddedController.js @@ -1,12 +1,10 @@ -import { capitalize } from 'common/string'; -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; import { - Box, - Button, - Flex, - Icon, + Box, + Button, + Flex, + Icon, LabeledList, ProgressBar, Section } from "../components"; @@ -124,7 +122,7 @@ export const EmbeddedController = (props, context) => { /** * Used for the upper status display that is used on 90% of these UIs. - * @param {StatusDisplayProps} props + * @param {StatusDisplayProps} props */ const StatusDisplay = (props, context) => { const { @@ -169,7 +167,7 @@ const StandardControls = (props, context) => { data["external_pressure"] - data["chamber_pressure"]) > 5); } - + let internalForceSafe = true; if (data["exterior_status"] && data.exterior_status.state === "open") { internalForceSafe = false; @@ -552,7 +550,7 @@ primaryRoutes["AirlockConsoleDocking"] = AirlockConsoleDocking; */ const DockingConsoleSimple = (props, context) => { const { act, data } = useBackend(context); - + let dockHatch = ERROR; if (data.exterior_status.state === "open") { @@ -662,8 +660,8 @@ const DoorAccessConsole = (props, context) => { {/* Interior Button */}
)); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/ICDetailer.js b/tgui/packages/tgui/interfaces/ICDetailer.js index 6b9d283349..c2319cac81 100644 --- a/tgui/packages/tgui/interfaces/ICDetailer.js +++ b/tgui/packages/tgui/interfaces/ICDetailer.js @@ -1,6 +1,5 @@ -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, Divider } from "../components"; +import { Button, Section } from "../components"; import { Window } from "../layouts"; import { toTitleCase } from 'common/string'; @@ -40,4 +39,4 @@ export const ICDetailer = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/ICPrinter.js b/tgui/packages/tgui/interfaces/ICPrinter.js index a2cd6709c8..2fa3b1a70c 100644 --- a/tgui/packages/tgui/interfaces/ICPrinter.js +++ b/tgui/packages/tgui/interfaces/ICPrinter.js @@ -1,10 +1,7 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend, useSharedState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Divider, Tabs, Stack } from "../components"; +import { Box, Button, LabeledList, ProgressBar, Section, Tabs, Stack } from "../components"; import { Window } from "../layouts"; import { sortBy, filter } from 'common/collections'; -import { logger } from '../logging'; export const ICPrinter = (props, context) => { const { act, data } = useBackend(context); @@ -115,4 +112,4 @@ const ICPrinterCategories = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/IDCard.js b/tgui/packages/tgui/interfaces/IDCard.js index fc28fb08a4..027277621f 100644 --- a/tgui/packages/tgui/interfaces/IDCard.js +++ b/tgui/packages/tgui/interfaces/IDCard.js @@ -1,7 +1,6 @@ import { Box, Flex, LabeledList, Section, Icon } from "../components"; import { useBackend } from "../backend"; import { Window } from "../layouts"; -import { Fragment } from "inferno"; import { RankIcon } from "./common/RankIcon"; export const IDCard = (props, context) => { diff --git a/tgui/packages/tgui/interfaces/IdentificationComputer.js b/tgui/packages/tgui/interfaces/IdentificationComputer.js index 0bfee7333e..60fb4142d4 100644 --- a/tgui/packages/tgui/interfaces/IdentificationComputer.js +++ b/tgui/packages/tgui/interfaces/IdentificationComputer.js @@ -4,7 +4,6 @@ import { useBackend } from "../backend"; import { Box, Button, Flex, Input, LabeledList, Section, Table, Tabs } from "../components"; import { Window } from "../layouts"; import { decodeHtmlEntities } from 'common/string'; -import { COLORS } from "../constants"; import { CrewManifestContent } from './CrewManifest'; export const IdentificationComputer = (props, context) => { @@ -31,7 +30,7 @@ export const IdentificationComputerContent = (props, context) => { has_modify, printing, } = data; - + let body = ; if (ntos && !data.have_id_slot) { body = ; @@ -122,19 +121,19 @@ export const IdentificationComputerAccessModification = (props, context) => {
- + act("reg", { reg: val })} /> - + act("account", { account: val })} /> - + { {sortBy(r => r.name)(regions).map(region => ( -
+
{sortBy(a => a.desc)(region.accesses).map(access => (
- +
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/Microwave.js b/tgui/packages/tgui/interfaces/Microwave.js index 5f789ccf14..b461eb793c 100644 --- a/tgui/packages/tgui/interfaces/Microwave.js +++ b/tgui/packages/tgui/interfaces/Microwave.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const Microwave = (props, context) => { @@ -70,4 +69,4 @@ export const Microwave = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.js b/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.js index 1b3721b230..81c553d079 100644 --- a/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.js +++ b/tgui/packages/tgui/interfaces/MiningOreProcessingConsole.js @@ -1,10 +1,8 @@ import { toTitleCase } from 'common/string'; import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Collapsible, Dropdown, Flex, Input, NoticeBox, Section, LabeledList, AnimatedNumber } from '../components'; +import { useBackend } from "../backend"; +import { Box, Button, Dropdown, Section, LabeledList, AnimatedNumber } from '../components'; import { Window } from "../layouts"; -import { refocusLayout } from '../layouts'; -import { sortBy } from 'common/collections'; import { MiningUser } from './common/Mining'; export const MiningOreProcessingConsole = (props, context) => { diff --git a/tgui/packages/tgui/interfaces/MiningStackingConsole.js b/tgui/packages/tgui/interfaces/MiningStackingConsole.js index 0aac5ca35c..5cd4d362bb 100644 --- a/tgui/packages/tgui/interfaces/MiningStackingConsole.js +++ b/tgui/packages/tgui/interfaces/MiningStackingConsole.js @@ -1,9 +1,7 @@ import { toTitleCase } from 'common/string'; -import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Collapsible, Dropdown, Flex, Input, NoticeBox, Section, LabeledList, AnimatedNumber, NumberInput } from '../components'; +import { useBackend } from "../backend"; +import { Button, Section, LabeledList, AnimatedNumber, NumberInput } from '../components'; import { Window } from "../layouts"; -import { sortBy } from 'common/collections'; export const MiningStackingConsole = (props, context) => { const { act, data } = useBackend(context); diff --git a/tgui/packages/tgui/interfaces/MiningVendor.js b/tgui/packages/tgui/interfaces/MiningVendor.js index 854908581a..e5ec5f68d9 100644 --- a/tgui/packages/tgui/interfaces/MiningVendor.js +++ b/tgui/packages/tgui/interfaces/MiningVendor.js @@ -1,7 +1,6 @@ import { createSearch } from 'common/string'; -import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Collapsible, Dropdown, Flex, Input, NoticeBox, Section } from '../components'; +import { Box, Button, Collapsible, Dropdown, Flex, Input, Section } from '../components'; import { Window } from "../layouts"; import { refocusLayout } from '../layouts'; import { MiningUser } from './common/Mining'; diff --git a/tgui/packages/tgui/interfaces/MuleBot.js b/tgui/packages/tgui/interfaces/MuleBot.js index 57794e0426..912ad17949 100644 --- a/tgui/packages/tgui/interfaces/MuleBot.js +++ b/tgui/packages/tgui/interfaces/MuleBot.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const MuleBot = (props, context) => { @@ -136,4 +135,4 @@ const MuleBotOpen = (props, context) => { onClick={() => act("safety")} />
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/NIF.js b/tgui/packages/tgui/interfaces/NIF.js index db94a4c009..f3e69ca523 100644 --- a/tgui/packages/tgui/interfaces/NIF.js +++ b/tgui/packages/tgui/interfaces/NIF.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Modal, Section, Dropdown, AnimatedNumber, NoticeBox, Table } from "../components"; +import { Box, Button, LabeledList, ProgressBar, Modal, Section, Dropdown, AnimatedNumber, NoticeBox, Table } from "../components"; import { Window } from "../layouts"; const NIF_WORKING = 0; diff --git a/tgui/packages/tgui/interfaces/NTNetRelay.js b/tgui/packages/tgui/interfaces/NTNetRelay.js index 793e35397b..b941616060 100644 --- a/tgui/packages/tgui/interfaces/NTNetRelay.js +++ b/tgui/packages/tgui/interfaces/NTNetRelay.js @@ -1,5 +1,5 @@ import { useBackend } from "../backend"; -import { Button, Box, Icon, Flex, LabeledList, Section } from "../components"; +import { Button, Box, Icon, LabeledList, Section } from "../components"; import { Window } from "../layouts"; import { FullscreenNotice } from './common/FullscreenNotice'; @@ -14,7 +14,7 @@ export const NTNetRelay = (props, context) => { } = data; let body = ; - + if (dos_crashed) { body = ; } @@ -68,7 +68,7 @@ const NTNetRelayContent = (props, context) => { const NTNetRelayCrash = (props, context) => { const { act, data } = useBackend(context); - + return ( @@ -97,4 +97,4 @@ const NTNetRelayCrash = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/Newscaster.js b/tgui/packages/tgui/interfaces/Newscaster.js index fab49af424..88dd05b0f7 100644 --- a/tgui/packages/tgui/interfaces/Newscaster.js +++ b/tgui/packages/tgui/interfaces/Newscaster.js @@ -1,7 +1,7 @@ import { decodeHtmlEntities } from 'common/string'; import { Fragment } from 'inferno'; import { useBackend, useSharedState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, Input, ProgressBar, Section, NoticeBox } from "../components"; +import { Box, Button, Flex, LabeledList, Input, Section } from "../components"; import { Window } from "../layouts"; import { TemporaryNotice } from './common/TemporaryNotice'; diff --git a/tgui/packages/tgui/interfaces/NoticeBoard.js b/tgui/packages/tgui/interfaces/NoticeBoard.js index fc097727a7..20b7d2aba8 100644 --- a/tgui/packages/tgui/interfaces/NoticeBoard.js +++ b/tgui/packages/tgui/interfaces/NoticeBoard.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const NoticeBoard = (props, context) => { @@ -50,4 +49,4 @@ export const NoticeBoard = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/NtosDigitalWarrant.js b/tgui/packages/tgui/interfaces/NtosDigitalWarrant.js index 83819f5d56..7fe2e15167 100644 --- a/tgui/packages/tgui/interfaces/NtosDigitalWarrant.js +++ b/tgui/packages/tgui/interfaces/NtosDigitalWarrant.js @@ -1,5 +1,5 @@ import { useBackend } from '../backend'; -import { Button, Box, LabeledList, ProgressBar, Section, Icon, Table } from '../components'; +import { Button, LabeledList, Section, Table } from '../components'; import { NtosWindow } from '../layouts'; import { filter } from 'common/collections'; import { Fragment } from 'inferno'; @@ -16,7 +16,7 @@ export const NtosDigitalWarrant = (props, context) => { } = data; let body = ; - + if (warrantauth) { body = ; } @@ -46,10 +46,10 @@ const AllWarrants = (props, context) => { onClick={() => act("addwarrant")}> Create New Warrant -
+
-
+
diff --git a/tgui/packages/tgui/interfaces/NtosEmailAdministration.js b/tgui/packages/tgui/interfaces/NtosEmailAdministration.js index f7c3909a79..98775749ac 100644 --- a/tgui/packages/tgui/interfaces/NtosEmailAdministration.js +++ b/tgui/packages/tgui/interfaces/NtosEmailAdministration.js @@ -1,8 +1,6 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table } from "../components"; -import { Window, NtosWindow } from "../layouts"; +import { Box, Button, LabeledList, Section, Table } from "../components"; +import { NtosWindow } from "../layouts"; import { NtosEmailClientViewMessage } from './NtosEmailClient'; export const NtosEmailAdministration = (props, context) => { diff --git a/tgui/packages/tgui/interfaces/NtosEmailClient.js b/tgui/packages/tgui/interfaces/NtosEmailClient.js index 15f6825187..0d051b8a99 100644 --- a/tgui/packages/tgui/interfaces/NtosEmailClient.js +++ b/tgui/packages/tgui/interfaces/NtosEmailClient.js @@ -1,7 +1,7 @@ /* eslint react/no-danger: "off" */ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; -import { Button, Box, Section, Table, LabeledList, Input, Tabs, TextArea, Flex, AnimatedNumber, ProgressBar } from '../components'; +import { Button, Box, Section, Table, LabeledList, Input, Tabs, Flex, AnimatedNumber, ProgressBar } from '../components'; import { NtosWindow } from '../layouts'; import { round } from 'common/math'; @@ -429,4 +429,4 @@ const NtosEmailClientLogin = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/NtosFileManager.js b/tgui/packages/tgui/interfaces/NtosFileManager.js index 9ce8b2fe62..8da7d9b07f 100644 --- a/tgui/packages/tgui/interfaces/NtosFileManager.js +++ b/tgui/packages/tgui/interfaces/NtosFileManager.js @@ -1,9 +1,8 @@ /* eslint react/no-danger: "off" */ import { Fragment } from 'inferno'; import { useBackend } from '../backend'; -import { Button, Box, Section, Table } from '../components'; +import { Button, Section, Table } from '../components'; import { NtosWindow } from '../layouts'; -import { decodeHtmlEntities } from 'common/string'; export const NtosFileManager = (props, context) => { const { act, data } = useBackend(context); diff --git a/tgui/packages/tgui/interfaces/NtosIdentificationComputer.js b/tgui/packages/tgui/interfaces/NtosIdentificationComputer.js index 9118a9c1ab..f176d27bde 100644 --- a/tgui/packages/tgui/interfaces/NtosIdentificationComputer.js +++ b/tgui/packages/tgui/interfaces/NtosIdentificationComputer.js @@ -1,10 +1,5 @@ -import { sortBy } from 'common/collections'; -import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Input, LabeledList, Section, Table, Tabs } from "../components"; -import { Window, NtosWindow } from "../layouts"; -import { decodeHtmlEntities } from 'common/string'; -import { COLORS } from "../constants"; +import { NtosWindow } from "../layouts"; import { IdentificationComputerContent } from "./IdentificationComputer"; export const NtosIdentificationComputer = (props, context) => { @@ -17,4 +12,4 @@ export const NtosIdentificationComputer = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/NtosWordProcessor.js b/tgui/packages/tgui/interfaces/NtosWordProcessor.js index a1ef22dca0..3dee099712 100644 --- a/tgui/packages/tgui/interfaces/NtosWordProcessor.js +++ b/tgui/packages/tgui/interfaces/NtosWordProcessor.js @@ -1,5 +1,4 @@ /* eslint react/no-danger: "off" */ -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Button, Box, Section, Table } from '../components'; import { NtosWindow } from '../layouts'; @@ -103,7 +102,7 @@ export const NtosWordProcessor = (props, context) => { {/* This dangerouslySetInnerHTML is only ever passed data that has passed through pencode2html * It should be safe enough to support pencode in this way. */} -
+
@@ -111,4 +110,4 @@ export const NtosWordProcessor = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OmniFilter.js b/tgui/packages/tgui/interfaces/OmniFilter.js index c7d03204f0..3205b61b96 100644 --- a/tgui/packages/tgui/interfaces/OmniFilter.js +++ b/tgui/packages/tgui/interfaces/OmniFilter.js @@ -1,11 +1,11 @@ import { useBackend } from "../backend"; import { Fragment } from "inferno"; -import { Box, Button, LabeledList, ProgressBar, Section, LabeledControls, AnimatedNumber } from "../components"; +import { Box, Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; const getStatusText = port => { if (port.input) { return "Input"; } - if (port.output) { return "Output"; } + if (port.output) { return "Output"; } if (port.f_type) { return port.f_type; } return "Disabled"; }; @@ -96,4 +96,4 @@ export const OmniFilter = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OmniMixer.js b/tgui/packages/tgui/interfaces/OmniMixer.js index 20555593bc..479b16e389 100644 --- a/tgui/packages/tgui/interfaces/OmniMixer.js +++ b/tgui/packages/tgui/interfaces/OmniMixer.js @@ -1,11 +1,11 @@ import { useBackend } from "../backend"; import { Fragment } from "inferno"; -import { Box, Button, LabeledList, ProgressBar, Section, LabeledControls, AnimatedNumber, Table } from "../components"; +import { Box, Button, LabeledList, Section, Table } from "../components"; import { Window } from "../layouts"; const getStatusText = port => { if (port.input) { return "Input"; } - if (port.output) { return "Output"; } + if (port.output) { return "Output"; } if (port.f_type) { return port.f_type; } return "Disabled"; }; @@ -56,7 +56,7 @@ export const OmniMixer = (props, context) => { )} Concentration {config ? ( - Lock + Lock ) : null} {ports ? ports.map(port => ( @@ -145,4 +145,4 @@ const PortRow = (props, context) => { ) : null} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OvermapDisperser.js b/tgui/packages/tgui/interfaces/OvermapDisperser.js index 7f231bcb3a..31f8d9a434 100644 --- a/tgui/packages/tgui/interfaces/OvermapDisperser.js +++ b/tgui/packages/tgui/interfaces/OvermapDisperser.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, AnimatedNumber } from "../components"; +import { useBackend } from "../backend"; +import { Box, Button, Flex, LabeledList, Section, AnimatedNumber } from "../components"; import { Window } from "../layouts"; import { OvermapPanControls } from './common/Overmap'; @@ -126,4 +124,4 @@ const OvermapDisperserContent = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OvermapEngines.js b/tgui/packages/tgui/interfaces/OvermapEngines.js index 5347455595..1ffa8237b6 100644 --- a/tgui/packages/tgui/interfaces/OvermapEngines.js +++ b/tgui/packages/tgui/interfaces/OvermapEngines.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, AnimatedNumber, Collapsible } from "../components"; +import { Box, Button, Flex, LabeledList, Section, AnimatedNumber, Collapsible } from "../components"; import { Window } from "../layouts"; export const OvermapEngines = (props, context) => { @@ -115,4 +114,4 @@ export const OvermapEnginesContent = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OvermapFull.js b/tgui/packages/tgui/interfaces/OvermapFull.js index 758a0fd18d..d6e288b462 100644 --- a/tgui/packages/tgui/interfaces/OvermapFull.js +++ b/tgui/packages/tgui/interfaces/OvermapFull.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, AnimatedNumber, Tabs } from "../components"; +import { useLocalState } from "../backend"; +import { Tabs } from "../components"; import { Window } from "../layouts"; import { OvermapEnginesContent } from './OvermapEngines'; import { OvermapHelmContent } from './OvermapHelm'; @@ -36,4 +34,4 @@ export const OvermapFull = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OvermapHelm.js b/tgui/packages/tgui/interfaces/OvermapHelm.js index 0895a7a93e..17e2cc9124 100644 --- a/tgui/packages/tgui/interfaces/OvermapHelm.js +++ b/tgui/packages/tgui/interfaces/OvermapHelm.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table } from "../components"; +import { useBackend } from "../backend"; +import { Box, Button, Flex, LabeledList, Section, Table } from "../components"; import { Window } from "../layouts"; import { OvermapFlightData, OvermapPanControls } from './common/Overmap'; @@ -239,4 +238,4 @@ const OvermapNavComputer = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OvermapNavigation.js b/tgui/packages/tgui/interfaces/OvermapNavigation.js index 3516fb1978..0cfd774a0e 100644 --- a/tgui/packages/tgui/interfaces/OvermapNavigation.js +++ b/tgui/packages/tgui/interfaces/OvermapNavigation.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; import { OvermapFlightData } from "./common/Overmap"; diff --git a/tgui/packages/tgui/interfaces/OvermapShieldGenerator.js b/tgui/packages/tgui/interfaces/OvermapShieldGenerator.js index e1beae750d..d9ecd01a2e 100644 --- a/tgui/packages/tgui/interfaces/OvermapShieldGenerator.js +++ b/tgui/packages/tgui/interfaces/OvermapShieldGenerator.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, AnimatedNumber } from "../components"; +import { useBackend } from "../backend"; +import { Box, Button, LabeledList, ProgressBar, Section, AnimatedNumber } from "../components"; import { Window } from "../layouts"; export const OvermapShieldGenerator = (props, context) => { @@ -209,4 +208,4 @@ const OvermapShieldGeneratorControls = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/OvermapShipSensors.js b/tgui/packages/tgui/interfaces/OvermapShipSensors.js index a5377ba621..9b148ebda0 100644 --- a/tgui/packages/tgui/interfaces/OvermapShipSensors.js +++ b/tgui/packages/tgui/interfaces/OvermapShipSensors.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, LabeledList, ProgressBar, Section } from "../components"; import { Window } from "../layouts"; export const OvermapShipSensors = (props, context) => { @@ -116,4 +115,4 @@ export const OvermapShipSensorsContent = (props, context) => { ) || null} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/ParticleAccelerator.js b/tgui/packages/tgui/interfaces/ParticleAccelerator.js index 480fdbe05e..7a49222a5e 100644 --- a/tgui/packages/tgui/interfaces/ParticleAccelerator.js +++ b/tgui/packages/tgui/interfaces/ParticleAccelerator.js @@ -1,4 +1,3 @@ -import { Fragment } from 'inferno'; import { useBackend } from '../backend'; import { Box, Button, LabeledList, Section } from '../components'; import { Window } from '../layouts'; diff --git a/tgui/packages/tgui/interfaces/PartsLathe.js b/tgui/packages/tgui/interfaces/PartsLathe.js index d94a1ffadf..3375a4e235 100644 --- a/tgui/packages/tgui/interfaces/PartsLathe.js +++ b/tgui/packages/tgui/interfaces/PartsLathe.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, NoticeBox } from "../components"; +import { Box, Button, LabeledList, ProgressBar, Section, NoticeBox } from "../components"; import { Window } from "../layouts"; import { toTitleCase } from "common/string"; import { Materials } from "./ExosuitFabricator"; @@ -99,4 +98,4 @@ export const PartsLathe = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/PathogenicIsolator.js b/tgui/packages/tgui/interfaces/PathogenicIsolator.js index 4395c1ac9b..7ff82fd36f 100644 --- a/tgui/packages/tgui/interfaces/PathogenicIsolator.js +++ b/tgui/packages/tgui/interfaces/PathogenicIsolator.js @@ -1,9 +1,7 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; -import { formatCommaNumber } from '../format'; import { useBackend, useLocalState } from "../backend"; -import { ComplexModal, modalOpen, modalRegisterBodyOverride } from "../interfaces/common/ComplexModal"; -import { Box, Button, Flex, Icon, NoticeBox, LabeledList, ProgressBar, Section, Tabs } from "../components"; +import { ComplexModal, modalRegisterBodyOverride } from "../interfaces/common/ComplexModal"; +import { Box, Button, Flex, NoticeBox, LabeledList, Section, Tabs } from "../components"; import { Window } from "../layouts"; const virusModalBodyOverride = (modal, context) => { @@ -51,8 +49,8 @@ const virusModalBodyOverride = (modal, context) => { {virus.symptoms.map(s => ( - Strength: {s.strength}  - Aggressiveness: {s.aggressiveness} + Strength: {s.strength}  + Aggressiveness: {s.aggressiveness} ))} diff --git a/tgui/packages/tgui/interfaces/Pda.js b/tgui/packages/tgui/interfaces/Pda.js index 6c4428c05b..cb9a20039f 100644 --- a/tgui/packages/tgui/interfaces/Pda.js +++ b/tgui/packages/tgui/interfaces/Pda.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, Modal, ProgressBar, Section } from "../components"; +import { Box, Button, Flex, Icon, LabeledList, Section } from "../components"; import { Window } from "../layouts"; /* This is all basically stolen from routes.js. */ @@ -210,4 +208,4 @@ const PDAFooter = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/PipeDispenser.js b/tgui/packages/tgui/interfaces/PipeDispenser.js index 8204279259..37aabc3381 100644 --- a/tgui/packages/tgui/interfaces/PipeDispenser.js +++ b/tgui/packages/tgui/interfaces/PipeDispenser.js @@ -1,5 +1,5 @@ import { useBackend, useLocalState } from '../backend'; -import { Box, Button, ColorBox, Flex, LabeledList, Section, Tabs } from '../components'; +import { Box, Button, Section, Tabs } from '../components'; import { Window } from '../layouts'; import { ICON_BY_CATEGORY_NAME } from './RapidPipeDispenser'; @@ -72,4 +72,4 @@ export const PipeDispenser = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/PlantAnalyzer.js b/tgui/packages/tgui/interfaces/PlantAnalyzer.js index 90d1d03294..a2ff147bb9 100644 --- a/tgui/packages/tgui/interfaces/PlantAnalyzer.js +++ b/tgui/packages/tgui/interfaces/PlantAnalyzer.js @@ -1,12 +1,11 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, NoticeBox } from "../components"; +import { Box, Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const PlantAnalyzer = (props, context) => { const { data } = useBackend(context); - + let calculatedHeight = 250; if (data.seed) { calculatedHeight += (18 * data.seed.trait_info.length); @@ -95,4 +94,4 @@ const PlantAnalyzerContent = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/PointDefenseControl.js b/tgui/packages/tgui/interfaces/PointDefenseControl.js index 68bcd56d55..84f0365344 100644 --- a/tgui/packages/tgui/interfaces/PointDefenseControl.js +++ b/tgui/packages/tgui/interfaces/PointDefenseControl.js @@ -1,7 +1,5 @@ -import { round } from 'common/math'; -import { Fragment } from 'inferno'; -import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Table, AnimatedNumber } from "../components"; +import { useBackend } from "../backend"; +import { Box, Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const PointDefenseControl = (props, context) => { @@ -44,4 +42,4 @@ export const PointDefenseControl = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/PortableScrubber.js b/tgui/packages/tgui/interfaces/PortableScrubber.js index 1876e7b87d..501819a492 100644 --- a/tgui/packages/tgui/interfaces/PortableScrubber.js +++ b/tgui/packages/tgui/interfaces/PortableScrubber.js @@ -1,6 +1,5 @@ import { useBackend } from '../backend'; -import { Button, Slider, Section, LabeledList } from '../components'; -import { getGasLabel } from '../constants'; +import { Slider, Section, LabeledList } from '../components'; import { Window } from '../layouts'; import { PortableBasicInfo } from './common/PortableAtmos'; diff --git a/tgui/packages/tgui/interfaces/PressureRegulator.js b/tgui/packages/tgui/interfaces/PressureRegulator.js index 4dc23eb843..c5ba1418a6 100644 --- a/tgui/packages/tgui/interfaces/PressureRegulator.js +++ b/tgui/packages/tgui/interfaces/PressureRegulator.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Knob, Slider, LabeledControls, AnimatedNumber } from "../components"; +import { Button, LabeledList, Section, AnimatedNumber } from "../components"; import { Window } from "../layouts"; export const PressureRegulator = (props, context) => { @@ -113,4 +112,4 @@ export const PressureRegulator = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/PrisonerManagement.js b/tgui/packages/tgui/interfaces/PrisonerManagement.js index 118de9cce7..6afa1d977b 100644 --- a/tgui/packages/tgui/interfaces/PrisonerManagement.js +++ b/tgui/packages/tgui/interfaces/PrisonerManagement.js @@ -1,9 +1,7 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from '../backend'; -import { Button, Section, NumberInput, Flex, Box, Table } from '../components'; +import { Button, Section, Box, Table } from '../components'; import { Window } from '../layouts'; -import { formatTime } from '../format'; export const PrisonerManagement = (props, context) => { const { act, data } = useBackend(context); @@ -98,4 +96,4 @@ export const PrisonerManagement = (props, context) => { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/RCON.js b/tgui/packages/tgui/interfaces/RCON.js index 46477d60ca..70787280db 100644 --- a/tgui/packages/tgui/interfaces/RCON.js +++ b/tgui/packages/tgui/interfaces/RCON.js @@ -2,7 +2,7 @@ import { round } from 'common/math'; import { formatPower } from '../format'; import { Fragment } from 'inferno'; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Stack, Section, Tabs, Slider, AnimatedNumber } from "../components"; +import { Box, Button, Icon, LabeledList, ProgressBar, Stack, Section, Tabs, Slider } from "../components"; import { Window } from "../layouts"; import { capitalize } from 'common/string'; @@ -14,7 +14,7 @@ export const RCON = (props, context) => { return ( @@ -61,10 +61,16 @@ const RCONSmesList = (props, context) => { const { smes_info, + pages, + current_page, } = data; + const runCallback = (cb) => { + return cb(); + }; + return ( -
+
{smes_info.map(smes => ( @@ -72,6 +78,23 @@ const RCONSmesList = (props, context) => { ))} + Page Selection:
+ {runCallback(() => { + const row = []; + for (let i = 1; i < pages; i++) { + row.push( + + ); + } + return row; + })}
); }; @@ -285,4 +308,4 @@ const RCONBreakerList = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/RIGSuit.js b/tgui/packages/tgui/interfaces/RIGSuit.js index cc7ec9abe3..167fc1fe1c 100644 --- a/tgui/packages/tgui/interfaces/RIGSuit.js +++ b/tgui/packages/tgui/interfaces/RIGSuit.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section } from "../components"; +import { Box, Button, Flex, LabeledList, ProgressBar, Section } from "../components"; import { Window } from "../layouts"; import { capitalize, toTitleCase } from 'common/string'; diff --git a/tgui/packages/tgui/interfaces/Radio.js b/tgui/packages/tgui/interfaces/Radio.js index 8b91dd756f..298bffb93d 100644 --- a/tgui/packages/tgui/interfaces/Radio.js +++ b/tgui/packages/tgui/interfaces/Radio.js @@ -1,7 +1,6 @@ -import { map } from 'common/collections'; import { toFixed, round } from 'common/math'; import { useBackend } from '../backend'; -import { Box, Button, LabeledList, NumberInput, Section, ColorBox } from '../components'; +import { Box, Button, LabeledList, NumberInput, Section } from '../components'; import { RADIO_CHANNELS } from '../constants'; import { Window } from '../layouts'; diff --git a/tgui/packages/tgui/interfaces/RequestConsole.js b/tgui/packages/tgui/interfaces/RequestConsole.js index 04b7fb402c..831360ce75 100644 --- a/tgui/packages/tgui/interfaces/RequestConsole.js +++ b/tgui/packages/tgui/interfaces/RequestConsole.js @@ -1,7 +1,7 @@ import { decodeHtmlEntities } from 'common/string'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Tabs } from "../components"; +import { Box, Button, LabeledList, Section, Tabs } from "../components"; import { Window } from "../layouts"; const RCS_MAINMENU = 0; // Settings menu @@ -329,4 +329,4 @@ export const RequestConsole = (props, context) => {
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/ResearchServerController.js b/tgui/packages/tgui/interfaces/ResearchServerController.js index 21264d5dc8..da7b8b1cb1 100644 --- a/tgui/packages/tgui/interfaces/ResearchServerController.js +++ b/tgui/packages/tgui/interfaces/ResearchServerController.js @@ -1,7 +1,6 @@ -import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend, useSharedState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Tabs } from "../components"; +import { Box, Button, LabeledList, Section, Tabs } from "../components"; import { Window } from "../layouts"; import { filter } from 'common/collections'; @@ -111,7 +110,7 @@ const ResearchServerAccess = (props, context) => { } return false; }; - + const hasDownloadAccess = (server, console) => { if (server.id_with_download.indexOf(console.id) !== -1) { return true; @@ -209,4 +208,4 @@ const ResearchServerTransfer = (props, context) => { ))} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/ResleevingConsole.js b/tgui/packages/tgui/interfaces/ResleevingConsole.js index 35eca0f677..3eb8403404 100644 --- a/tgui/packages/tgui/interfaces/ResleevingConsole.js +++ b/tgui/packages/tgui/interfaces/ResleevingConsole.js @@ -2,7 +2,6 @@ import { round } from 'common/math'; import { Fragment } from 'inferno'; import { useBackend } from "../backend"; import { Box, Button, Dimmer, Flex, Icon, LabeledList, NoticeBox, ProgressBar, Section, Tabs } from "../components"; -import { COLORS } from '../constants'; import { ComplexModal, modalRegisterBodyOverride } from '../interfaces/common/ComplexModal'; import { Window } from "../layouts"; diff --git a/tgui/packages/tgui/interfaces/RoboticsControlConsole.js b/tgui/packages/tgui/interfaces/RoboticsControlConsole.js index 9e3eea44c9..8085a5b3e6 100644 --- a/tgui/packages/tgui/interfaces/RoboticsControlConsole.js +++ b/tgui/packages/tgui/interfaces/RoboticsControlConsole.js @@ -1,6 +1,6 @@ import { Fragment } from 'inferno'; -import { useBackend, useSharedState } from '../backend'; -import { Box, Button, LabeledList, ProgressBar, NoticeBox, Section, Tabs } from '../components'; +import { useBackend } from '../backend'; +import { Box, Button, LabeledList, ProgressBar, NoticeBox, Section } from '../components'; import { Window } from '../layouts'; export const RoboticsControlConsole = (props, context) => { diff --git a/tgui/packages/tgui/interfaces/RustFuelControl.js b/tgui/packages/tgui/interfaces/RustFuelControl.js index cbcb4f0523..7c8446f769 100644 --- a/tgui/packages/tgui/interfaces/RustFuelControl.js +++ b/tgui/packages/tgui/interfaces/RustFuelControl.js @@ -1,6 +1,6 @@ import { useBackend } from "../backend"; import { Window } from "../layouts"; -import { Button, Section, Table, Knob } from "../components"; +import { Button, Section, Table } from "../components"; export const RustFuelControl = (props, context) => ( { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/Secbot.js b/tgui/packages/tgui/interfaces/Secbot.js index 1257187f13..a0f320c4e5 100644 --- a/tgui/packages/tgui/interfaces/Secbot.js +++ b/tgui/packages/tgui/interfaces/Secbot.js @@ -1,5 +1,5 @@ import { useBackend } from "../backend"; -import { Box, Button, LabeledList, ProgressBar, Section, NumberInput, AnimatedNumber, Dropdown } from "../components"; +import { Button, LabeledList, Section } from "../components"; import { Window } from "../layouts"; export const Secbot = (props, context) => { diff --git a/tgui/packages/tgui/interfaces/SecurityRecords.js b/tgui/packages/tgui/interfaces/SecurityRecords.js index 9fb296750e..c4ae5bada7 100644 --- a/tgui/packages/tgui/interfaces/SecurityRecords.js +++ b/tgui/packages/tgui/interfaces/SecurityRecords.js @@ -1,12 +1,11 @@ import { Fragment } from 'inferno'; import { useBackend } from "../backend"; -import { Box, Button, Collapsible, Flex, Icon, Input, LabeledList, Section, Tabs } from "../components"; -import { ComplexModal, modalOpen, modalRegisterBodyOverride } from "../interfaces/common/ComplexModal"; +import { Box, Button, Flex, Input, LabeledList, Section, Tabs } from "../components"; +import { ComplexModal, modalOpen } from "../interfaces/common/ComplexModal"; import { Window } from "../layouts"; import { LoginInfo } from './common/LoginInfo'; import { LoginScreen } from './common/LoginScreen'; import { TemporaryNotice } from './common/TemporaryNotice'; -import { decodeHtmlEntities } from 'common/string'; const doEdit = (context, field) => { modalOpen(context, 'edit', { @@ -141,7 +140,7 @@ const SecurityRecordsView = (_properties, context) => { content="Delete Record (All)" color="bad" onClick={() => act('del_r_2')} - /> + />
); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/SupermatterMonitor.js b/tgui/packages/tgui/interfaces/SupermatterMonitor.js index 28563c4e50..ea4033d077 100644 --- a/tgui/packages/tgui/interfaces/SupermatterMonitor.js +++ b/tgui/packages/tgui/interfaces/SupermatterMonitor.js @@ -1,11 +1,10 @@ import { useBackend } from '../backend'; -import { Box, Button, NoticeBox, LabeledList, ProgressBar, Section, Slider, Table, Flex, AnimatedNumber } from '../components'; -import { formatPower } from '../format'; +import { Box, Button, LabeledList, ProgressBar, Section, Flex, AnimatedNumber } from '../components'; import { Window } from '../layouts'; import { round } from 'common/math'; import { toTitleCase } from 'common/string'; -// As of 2020-08-06 this isn't actually ever used, but it needs to exist because that's what tgui_modules expect +// As of 2020-08-06 this isn't actually ever used, but it needs to exist because that's what tgui_modules expect export const SupermatterMonitor = (props, context) => ( { ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/SupplyConsole.js b/tgui/packages/tgui/interfaces/SupplyConsole.js index de0a37a1d9..2309e4aa8e 100644 --- a/tgui/packages/tgui/interfaces/SupplyConsole.js +++ b/tgui/packages/tgui/interfaces/SupplyConsole.js @@ -1,9 +1,8 @@ import { filter, sortBy } from 'common/collections'; -import { round } from "common/math"; import { Fragment } from "inferno"; import { formatTime } from "../format"; import { useBackend, useLocalState } from "../backend"; -import { Box, Button, Flex, Icon, LabeledList, ProgressBar, Section, Tabs, Collapsible, AnimatedNumber, Stack } from "../components"; +import { Box, Button, LabeledList, Section, Tabs, AnimatedNumber, Stack } from "../components"; import { ComplexModal, modalRegisterBodyOverride } from '../interfaces/common/ComplexModal'; import { Window } from "../layouts"; import { flow } from 'common/fp'; @@ -436,4 +435,4 @@ const SupplyConsoleMenuHistoryExport = (props, context) => { ))} ); -}; \ No newline at end of file +}; diff --git a/tgui/packages/tgui/interfaces/TelecommsLogBrowser.js b/tgui/packages/tgui/interfaces/TelecommsLogBrowser.js index 0d4180db03..f93ad8fe16 100644 --- a/tgui/packages/tgui/interfaces/TelecommsLogBrowser.js +++ b/tgui/packages/tgui/interfaces/TelecommsLogBrowser.js @@ -21,10 +21,10 @@ export const TelecommsLogBrowser = (props, context) => { height={450} resizable> - {(temp && temp.length) ? ( - + {temp ? ( + - {temp} + {temp.text}