diff --git a/code/_global_vars/lists/misc.dm b/code/_global_vars/lists/misc.dm index 1b76b17d75..ae78365597 100644 --- a/code/_global_vars/lists/misc.dm +++ b/code/_global_vars/lists/misc.dm @@ -6,7 +6,7 @@ GLOBAL_LIST_EMPTY(wire_color_directory) // This is an associative list with the GLOBAL_LIST_EMPTY(wire_name_directory) // This is an associative list // Reference list for disposal sort junctions. Filled up by sorting junction's New() -GLOBAL_LIST_EMPTY(tagger_locations) +GLOBAL_ALIST_EMPTY(tagger_locations) GLOBAL_LIST_INIT(char_directory_tags, list("Pred", "Pred-Pref", "Prey", "Prey-Pref", "Switch", "Non-Vore", "Unset")) GLOBAL_LIST_INIT(char_directory_erptags, list("Dominant", "Dom-Pref", "Submissive", "Sub-Pref", "Switch", "No ERP", "Unset")) diff --git a/code/modules/client/preference_setup/general/07_antagonism.dm b/code/modules/client/preference_setup/general/07_antagonism.dm index 48f0c4a7fa..cb05db0036 100644 --- a/code/modules/client/preference_setup/general/07_antagonism.dm +++ b/code/modules/client/preference_setup/general/07_antagonism.dm @@ -114,19 +114,19 @@ if("desc") t = sanitize(params["desc"], MAX_MESSAGE_LEN * 4) if(!isnull(t) && CanUseTopic(user)) - pref.update_preference_by_type(/datum/preference/text/pai_description, sanitize(t)) + pref.update_preference_by_type(/datum/preference/text/pai_description, t) if("ad") t = sanitize(params["ad"]) if(!isnull(t) && CanUseTopic(user)) - pref.update_preference_by_type(/datum/preference/text/pai_ad, sanitize(t)) + pref.update_preference_by_type(/datum/preference/text/pai_ad, t) if("role") t = sanitize(params["role"]) if(!isnull(t) && CanUseTopic(user)) - pref.update_preference_by_type(/datum/preference/text/pai_role, sanitize(t)) + pref.update_preference_by_type(/datum/preference/text/pai_role, t) if("ooc") t = sanitize(params["ooc"]) if(!isnull(t) && CanUseTopic(user)) - pref.update_preference_by_type(/datum/preference/text/pai_comments, sanitize(t)) + pref.update_preference_by_type(/datum/preference/text/pai_comments, t) if("color") var/new_color = sanitize_hexcolor(params["color"]) if(new_color && CanUseTopic(user)) diff --git a/code/modules/mob/living/carbon/human/phobias.dm b/code/modules/mob/living/carbon/human/phobias.dm index f92ea2464b..87fc5bf8cd 100644 --- a/code/modules/mob/living/carbon/human/phobias.dm +++ b/code/modules/mob/living/carbon/human/phobias.dm @@ -11,9 +11,7 @@ fear = min((fear + 6), 102) if(phobias & HEMOPHOBIA) for(var/obj/effect/decal/cleanable/blood/B in view(7, src)) - var/obj/effect/decal/cleanable/blood/oil/O = B - var/obj/effect/decal/cleanable/blood/tracks/T = B - if(istype(O) || istype(T)) + if(istype(B, /obj/effect/decal/cleanable/blood/oil) || istype(B, /obj/effect/decal/cleanable/blood/tracks) || istype(B, /obj/effect/decal/cleanable/blood/gibs/robot)) continue fear = min((fear + 2), 102) for(var/turf/simulated/floor/water/blood/T in view(7, src)) 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 5de3a13b33..298406e6b0 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 @@ -86,7 +86,7 @@ to_chat(src, span_danger("You begin to resist \the [prey_name]'s control!!!")) to_chat(pred_body, span_danger("You feel the captive mind of [src] begin to resist your control.")) - if(do_after(src, 10 SECONDS, target = prey_name)) + if(do_after(src, 10 SECONDS, target = pred_body)) restore_control() else to_chat(src, span_notice("Your attempt to regain control has been interrupted...")) diff --git a/code/modules/recycling/destination_tagger.dm b/code/modules/recycling/destination_tagger.dm index 3ccf1dd97b..499fe7df22 100644 --- a/code/modules/recycling/destination_tagger.dm +++ b/code/modules/recycling/destination_tagger.dm @@ -21,16 +21,13 @@ ui.open() /obj/item/destTagger/tgui_static_data(mob/user) - var/list/data = ..() - var/list/taggers = list() - var/list/tagger_levels = list() - for(var/tag in GLOB.tagger_locations) - var/z_level = GLOB.tagger_locations[tag] - taggers += list(list("tag" = tag, "level" = z_level)) - tagger_levels += list(list("z" = z_level, "location" = using_map.get_zlevel_name(z_level))) - data["taggerLevels"] = tagger_levels - data["taggerLocs"] = taggers + . = ..() + .["level_names"] = using_map.zlevels +/obj/item/destTagger/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) + var/list/data = list( + "taggerLocs" = GLOB.tagger_locations + ) return data /obj/item/destTagger/tgui_data(mob/user, datum/tgui/ui) @@ -56,4 +53,13 @@ if(!(new_tag in GLOB.tagger_locations)) return FALSE currTag = new_tag - . = TRUE + return TRUE + if("new_tag") + var/dest_tag = sanitizeName(params["tag"], allow_numbers = TRUE) + if(!istext(dest_tag) || length(dest_tag) < 3) + return FALSE + if(dest_tag in GLOB.tagger_locations) + return FALSE + GLOB.tagger_locations[dest_tag] = null + currTag = dest_tag + return TRUE diff --git a/code/modules/recycling/disposal_junctions.dm b/code/modules/recycling/disposal_junctions.dm index 6c73589732..058b2c17df 100644 --- a/code/modules/recycling/disposal_junctions.dm +++ b/code/modules/recycling/disposal_junctions.dm @@ -25,25 +25,25 @@ var/flipdir = turn(fromdir, 180) if(flipdir != dir) // came from secondary dir return dir // so exit through primary - else // came from primary + // came from primary // so need to choose either secondary exit - var/mask = ..(fromdir) + var/mask = ..(fromdir) - // find a bit which is set - var/setbit = 0 - if(mask & NORTH) - setbit = NORTH - else if(mask & SOUTH) - setbit = SOUTH - else if(mask & EAST) - setbit = EAST - else - setbit = WEST + // find a bit which is set + var/setbit = 0 + if(mask & NORTH) + setbit = NORTH + else if(mask & SOUTH) + setbit = SOUTH + else if(mask & EAST) + setbit = EAST + else + setbit = WEST - if(prob(50)) // 50% chance to choose the found bit or the other one - return setbit - else - return mask & (~setbit) + if(prob(50)) // 50% chance to choose the found bit or the other one + return setbit + + return mask & (~setbit) //a three-way junction that sorts objects /obj/structure/disposalpipe/sortjunction @@ -66,11 +66,13 @@ /obj/structure/disposalpipe/sortjunction/proc/updatename() if(sortType) name = "[initial(name)] ([sortType])" - else - name = initial(name) + return + name = initial(name) /obj/structure/disposalpipe/sortjunction/Destroy() QDEL_NULL(wires) + if(sortType) + LAZYREMOVE(GLOB.tagger_locations["[sortType]"], get_z(src)) . = ..() /obj/structure/disposalpipe/sortjunction/proc/updatedir() @@ -85,7 +87,8 @@ /obj/structure/disposalpipe/sortjunction/Initialize(mapload) . = ..() - if(sortType) GLOB.tagger_locations |= list("[sortType]" = get_z(src)) + if(sortType) + LAZYADD(GLOB.tagger_locations["[sortType]"], get_z(src)) wires = new /datum/wires/disposals(src) @@ -113,7 +116,11 @@ var/obj/item/destTagger/O = I if(O.currTag)// Tag set + var/current_z = get_z(src) + if(sortType) + LAZYREMOVE(GLOB.tagger_locations["[sortType]"], current_z) sortType = O.currTag + LAZYADD(GLOB.tagger_locations["[sortType]"], current_z) playsound(src, 'sound/machines/twobeep.ogg', 100, 1) to_chat(user, span_blue("Changed filter to '[sortType]'.")) updatename() diff --git a/code/modules/recycling/disposal_tagger.dm b/code/modules/recycling/disposal_tagger.dm index 4a2ba5d9dc..d4a293943c 100644 --- a/code/modules/recycling/disposal_tagger.dm +++ b/code/modules/recycling/disposal_tagger.dm @@ -12,16 +12,22 @@ /obj/structure/disposalpipe/tagger/proc/updatename() if(sort_tag) name = "[initial(name)] ([sort_tag])" - else - name = initial(name) + return + name = initial(name) /obj/structure/disposalpipe/tagger/Initialize(mapload) . = ..() dpdir = dir | turn(dir, 180) - if(sort_tag) GLOB.tagger_locations |= list("[sort_tag]" = get_z(src)) + if(sort_tag) + LAZYADD(GLOB.tagger_locations["[sort_tag]"], get_z(src)) updatename() updatedesc() update() +/obj/structure/disposalpipe/tagger/Destroy() + . = ..() + if(sort_tag) + LAZYREMOVE(GLOB.tagger_locations["[sort_tag]"], get_z(src)) + /obj/structure/disposalpipe/tagger/attackby(obj/item/I, mob/user) if(..()) @@ -31,9 +37,13 @@ var/obj/item/destTagger/O = I if(O.currTag)// Tag set + var/current_z = get_z(src) + if(sort_tag) + LAZYREMOVE(GLOB.tagger_locations["[sort_tag]"], current_z) sort_tag = O.currTag + LAZYADD(GLOB.tagger_locations["[sort_tag]"], current_z) playsound(src, 'sound/machines/twobeep.ogg', 100, 1) - to_chat(user, span_blue("Changed tag to '[sort_tag]'.")) + to_chat(user, span_notice("Changed tag to '[sort_tag]'.")) updatename() updatedesc() diff --git a/tgui/packages/common/storage.ts b/tgui/packages/common/storage.ts index f149c6ff54..8dc8c22eb1 100644 --- a/tgui/packages/common/storage.ts +++ b/tgui/packages/common/storage.ts @@ -72,11 +72,10 @@ class IFrameIndexedDbBackend implements StorageBackend { this.impl = IMPL_IFRAME_INDEXED_DB; } - async ready(): Promise { + async ready(): Promise { const iframe = document.createElement('iframe'); const iframeStore = `${Byond.storageCdn}?store=${KEY_NAME}`; iframe.style.display = 'none'; - this.documentElement = document.body.appendChild(iframe); iframe.src = iframeStore; const completePromise: Promise = new Promise((resolve) => { @@ -90,13 +89,17 @@ class IFrameIndexedDbBackend implements StorageBackend { resolve(false); }); - window.addEventListener('message', (message) => { - if (message.data === 'ready') { + const handler = (message: MessageEvent) => { + if (message.source === this.iframeWindow && message.data === 'ready') { + window.removeEventListener('message', handler); resolve(true); } - }); + }; + + window.addEventListener('message', handler); }); + this.documentElement = document.body.appendChild(iframe); if (!this.documentElement.contentWindow) { return new Promise((res) => res(false)); } @@ -107,16 +110,17 @@ class IFrameIndexedDbBackend implements StorageBackend { } async get(key: string): Promise { - const promise = new Promise((resolve) => { - window.addEventListener('message', (message) => { - if (message.data.key && message.data.key === key) { + return new Promise((resolve) => { + const handler = (message: MessageEvent) => { + if (message.source === this.iframeWindow && message.data?.key === key) { + window.removeEventListener('message', handler); resolve(message.data.value); } - }); - }); + }; - this.iframeWindow.postMessage({ type: 'get', key: key }, '*'); - return promise; + window.addEventListener('message', handler); + this.iframeWindow.postMessage({ type: 'get', key: key }, '*'); + }); } async set(key: string, value: any): Promise { @@ -132,7 +136,7 @@ class IFrameIndexedDbBackend implements StorageBackend { } async destroy(): Promise { - document.body.removeChild(this.documentElement); + this.documentElement?.remove(); } } @@ -162,27 +166,32 @@ class StorageProxy implements StorageBackend { Byond.winset(null, 'browser-options', '+byondstorage'); await new Promise((resolve) => { - document.addEventListener('byondstorageupdated', async () => { - setTimeout(() => { + const handler = async () => { + document.removeEventListener('byondstorageupdated', handler); + + setTimeout(async () => { const hub = new HubStorageBackend(); - // Migrate these existing settings from byondstorage to the IFrame for (const setting of [ 'panel-settings', 'chat-state', 'chat-messages', ]) { - hub - .get(setting) - .then((settings) => iframe.set(setting, settings)); + const settings = await hub.get(setting); + if (settings !== undefined) { + await iframe.set(setting, settings); + } } - iframe.set('byondstorage-migrated', true); + await iframe.set('byondstorage-migrated', true); + Byond.winset(null, 'browser-options', '-byondstorage'); resolve(); }, 1); - }); + }; + + document.addEventListener('byondstorageupdated', handler); }); return iframe; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger.tsx b/tgui/packages/tgui/interfaces/DestinationTagger.tsx deleted file mode 100644 index fa86370369..0000000000 --- a/tgui/packages/tgui/interfaces/DestinationTagger.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { useBackend } from 'tgui/backend'; -import { Window } from 'tgui/layouts'; -import { Button, Section, Stack } from 'tgui-core/components'; - -type Data = { - currTag: string; - taggerLevels: { z: number; location: string }[]; - taggerLocs: { tag: string; level: number }[]; -}; - -export const DestinationTagger = (props) => { - const { act, data } = useBackend(); - - const { currTag, taggerLevels = [], taggerLocs } = data; - - const unique_levels = taggerLevels.filter((obj, index) => { - return index === taggerLevels.findIndex((o) => obj.location === o.location); - }); - - return ( - - -
- {unique_levels.map((level) => ( -
- - {taggerLocs.map( - (tag) => - level.z === tag.level && ( - - - - ), - )} - -
- ))} -
-
-
- ); -}; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger/DestinationTaggerLevels.tsx b/tgui/packages/tgui/interfaces/DestinationTagger/DestinationTaggerLevels.tsx new file mode 100644 index 0000000000..964889edf2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DestinationTagger/DestinationTaggerLevels.tsx @@ -0,0 +1,96 @@ +import { useBackend } from 'tgui/backend'; +import { Button, Divider, Section, Stack } from 'tgui-core/components'; +import { createSearch } from 'tgui-core/string'; +import type { Data } from './types'; + +export const DestinationTaggerLevels = (props: { tagSearch: string }) => { + const { act, data } = useBackend(); + + const { level_names, taggerLocs } = data; + const { tagSearch } = props; + + const temp: Record> = Object.entries(taggerLocs).reduce( + (acc, [tag, locLevels]) => { + if (!locLevels?.length) { + if (!acc.Unused) { + acc.Unused = new Set(); + } + acc.Unused.add(tag); + return acc; + } + + locLevels.forEach((levelId) => { + const level = level_names[levelId]; + if (!level) return; + + if (!acc[level]) { + acc[level] = new Set(); + } + + acc[level].add(tag); + }); + + return acc; + }, + {} as Record>, + ); + + const levelOrder = Object.fromEntries( + Object.entries(level_names).map(([id, name]) => [name, Number(id)]), + ); + + const result: Record = Object.fromEntries( + Object.entries(temp) + .sort(([a], [b]) => { + if (a === 'Unused') return -1; + if (b === 'Unused') return 1; + + return (levelOrder[a] ?? Infinity) - (levelOrder[b] ?? Infinity); + }) + .map(([k, v]) => [k, [...v].sort()]), + ); + + return Object.keys(result).map((level, index) => ( + <> + {index !== 0 && } + + + )); +}; + +const LevelEntry = (props: { + level: string; + result: string[]; + tagSearch: string; +}) => { + const { act, data } = useBackend(); + + const { currTag } = data; + const { level, result, tagSearch } = props; + + const tagSearcher = createSearch(tagSearch, (tag) => tag); + const toDisplay = result.filter(tagSearcher); + + return ( +
+ + {toDisplay.map((tag) => ( + + act('set_tag', { tag: tag })} + > + {tag} + + + ))} + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger/DestintationTaggerTags.tsx b/tgui/packages/tgui/interfaces/DestinationTagger/DestintationTaggerTags.tsx new file mode 100644 index 0000000000..61165a0ebd --- /dev/null +++ b/tgui/packages/tgui/interfaces/DestinationTagger/DestintationTaggerTags.tsx @@ -0,0 +1,31 @@ +import { useBackend } from 'tgui/backend'; +import { Button, Stack } from 'tgui-core/components'; +import { createSearch } from 'tgui-core/string'; +import type { Data } from './types'; + +export const DestintationTaggerTags = (props: { tagSearch: string }) => { + const { act, data } = useBackend(); + + const { currTag, taggerLocs } = data; + const { tagSearch } = props; + + const tagSearcher = createSearch(tagSearch, (tag) => tag); + const toDisplay = Object.keys(taggerLocs) + .filter(tagSearcher) + .sort((a, b) => a.localeCompare(b)); + return ( + + {toDisplay.map((tag) => ( + + act('set_tag', { tag: tag })} + > + {tag} + + + ))} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger/index.tsx b/tgui/packages/tgui/interfaces/DestinationTagger/index.tsx new file mode 100644 index 0000000000..9eab84c1a8 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DestinationTagger/index.tsx @@ -0,0 +1,91 @@ +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { Window } from 'tgui/layouts'; +import { Button, Input, Section, Stack, Tabs } from 'tgui-core/components'; +import { DestinationTaggerLevels } from './DestinationTaggerLevels'; +import { DestintationTaggerTags } from './DestintationTaggerTags'; +import type { Data } from './types'; + +export const DestinationTagger = (props) => { + const { act, data } = useBackend(); + + const [newTag, setNewTag] = useState(''); + const [currentTab, setCurrentTab] = useState(0); + const [tagSearch, setTagSearch] = useState(''); + + const tabs: React.JSX.Element[] = []; + + tabs[0] = ; + tabs[1] = ; + + return ( + + + + +
+ + + Name: + + + + + + + + +
+
+ + + setCurrentTab(0)} + selected={currentTab === 0} + > + Destinations + + setCurrentTab(1)} + selected={currentTab === 1} + > + Destination By Level + + + + +
+ } + > + {tabs[currentTab]} +
+
+
+
+
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/DestinationTagger/types.ts b/tgui/packages/tgui/interfaces/DestinationTagger/types.ts new file mode 100644 index 0000000000..dd8d149898 --- /dev/null +++ b/tgui/packages/tgui/interfaces/DestinationTagger/types.ts @@ -0,0 +1,5 @@ +export type Data = { + currTag: string; + level_names: Record; + taggerLocs: Record; +};