diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f10fceae2..ab7c849f12 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,31 +8,58 @@ env: BASENAME: "vorestation" jobs: - file_tests: + + run_linters: name: Run Linters runs-on: ubuntu-20.04 + steps: - uses: actions/checkout@v4 - - name: Ensure +x on CI directory - run: | - chmod -R +x ./tools/ci - - name: Install Tools - run: | - bash tools/ci/install_build_deps.sh + # Caches - name: Restore Yarn cache - if: "${{ contains(github.event.pull_request.labels.*.name, 'Type: TGUI Bundle') }}" uses: actions/cache@v4 with: path: tgui/.yarn/cache - key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }} + key: ${{ runner.os }}-yarn-${{ hashFiles('tgui/yarn.lock') }} restore-keys: | - ${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}- - ${{ runner.os }}-build- - ${{ runner.os }}- - - name: Run Tests + ${{ runner.os }}-yarn- + - name: Restore Node cache + uses: actions/cache@v4 + with: + path: ~/.nvm + key: ${{ runner.os }}-node-${{ hashFiles('dependencies.sh') }} + restore-keys: | + ${{ runner.os }}-node- + - name: Restore Bootstrap cache + uses: actions/cache@v4 + with: + path: tools/bootstrap/.cache + key: ${{ runner.os }}-bootstrap-${{ hashFiles('tools/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-bootstrap- + - name: Restore Rust cache + uses: actions/cache@v4 + with: + path: ~/.cargo + key: ${{ runner.os }}-rust-${{ hashFiles('tools/ci/ci_dependencies.sh')}} + restore-keys: | + ${{ runner.os }}-rust- + - name: Restore Cutter cache + uses: actions/cache@v4 + with: + path: tools/icon_cutter/cache + key: ${{ runner.os }}-cutter-${{ hashFiles('dependencies.sh') }} + # End Caches + - name: Install Tools run: | - tools/ci/validate_files.sh - tools/ci/build_tgui.sh + pip3 install setuptools + bash tools/ci/install_node.sh + bash tools/ci/install_ripgrep.sh + tools/bootstrap/python -c '' + - name: Run Grep Checks + run: tools/ci/validate_files.sh + - name: Run TGUI Checks + run: tools/build/build --ci lint tgui-test dreamchecker: name: DreamChecker @@ -68,7 +95,7 @@ jobs: map: ['tether', 'stellar_delight', 'groundbase'] # name: Integration Tests (${{ matrix.map }}) name: Integration Tests - # needs: ['file_tests', 'dreamchecker'] + # needs: ['run_linters', 'dreamchecker'] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 @@ -119,7 +146,7 @@ jobs: tests_successful: name: Integration Tests - needs: ['file_tests', 'dreamchecker', 'unit_tests'] + needs: ['run_linters', 'dreamchecker', 'unit_tests'] runs-on: ubuntu-20.04 steps: - name: Report Success diff --git a/.gitignore b/.gitignore index 2833549fe5..f5ab48609c 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ temp.dmi node_modules/ package-lock.json + +# CBT Things +.cache diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000000..2789c260b7 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v20.13.0 diff --git a/.vscode/launch.json b/.vscode/launch.json index 24779e223e..80b1d58f75 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,13 +4,59 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - { - "type": "byond", - "request": "launch", - "name": "Launch DreamDaemon", - "preLaunchTask": "dm: build - ${command:CurrentDME}", - "dmb": "${workspaceFolder}/${command:CurrentDMB}", - "dreamDaemon": true - } + { + "type": "byond", + "request": "launch", + "name": "Launch DreamSeeker", + "preLaunchTask": "Build All", + "dmb": "${workspaceFolder}/${command:CurrentDMB}" + }, + { + "type": "byond", + "request": "launch", + "name": "Launch DreamSeeker (low memory mode)", + "preLaunchTask": "Build All (low memory mode)", + "dmb": "${workspaceFolder}/${command:CurrentDMB}" + }, + { + "type": "byond", + "request": "launch", + "name": "Launch DreamDaemon", + "preLaunchTask": "Build All", + "dmb": "${workspaceFolder}/${command:CurrentDMB}", + "dreamDaemon": true + }, + { + "type": "byond", + "request": "launch", + "name": "Launch DreamDaemon (low memory mode)", + "preLaunchTask": "Build All (low memory mode)", + "dmb": "${workspaceFolder}/${command:CurrentDMB}", + "dreamDaemon": true + }, + { + "name": "Debug External Libraries", + "type": "cppvsdbg", + "request": "launch", + "program": "${command:dreammaker.returnDreamDaemonPath}", + "cwd": "${workspaceRoot}", + "args": [ + "${command:dreammaker.getFilenameDmb}", + "-trusted" + ], + "preLaunchTask": "Build All" + }, + { + "name": "Debug External Libraries (low memory mode)", + "type": "cppvsdbg", + "request": "launch", + "program": "${command:dreammaker.returnDreamDaemonPath}", + "cwd": "${workspaceRoot}", + "args": [ + "${command:dreammaker.getFilenameDmb}", + "-trusted" + ], + "preLaunchTask": "Build All (low memory mode)" + } ] -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 2b0aed0e81..4634cc4698 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,6 +1,53 @@ { "version": "2.0.0", "tasks": [ + { + "type": "process", + "command": "tools/build/build", + "windows": { + "command": ".\\tools\\build\\build.bat" + }, + "options": { + "env": { + "DM_EXE": "${config:dreammaker.byondPath}" + } + }, + "problemMatcher": [ + "$dreammaker", + "$tsc", + "$eslint-stylish" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "dependsOn": "dm: reparse", + "label": "Build All" + }, + { + "type": "process", + "command": "tools/build/build", + "args": ["-DLOWMEMORYMODE"], + "windows": { + "command": ".\\tools\\build\\build.bat", + "args": ["-DLOWMEMORYMODE"] + }, + "options": { + "env": { + "DM_EXE": "${config:dreammaker.byondPath}" + } + }, + "problemMatcher": [ + "$dreammaker", + "$tsc", + "$eslint-stylish" + ], + "group": { + "kind": "build" + }, + "dependsOn": "dm: reparse", + "label": "Build All (low memory mode)" + }, { "type": "dreammaker", "dme": "vorestation.dme", @@ -10,11 +57,16 @@ "group": "build", "label": "dm: build - vorestation.dme" }, + { + "command": "${command:dreammaker.reparse}", + "group": "build", + "label": "dm: reparse" + }, { "type": "shell", - "command": "tgui/bin/tgui", + "command": "bin/tgui-build", "windows": { - "command": ".\\tgui\\bin\\tgui.bat" + "command": ".\\bin\\tgui-build.cmd" }, "problemMatcher": [ "$tsc", @@ -25,43 +77,55 @@ }, { "type": "shell", - "command": "yarn build", - "options": { - "cwd": "tgui/packages/tgfont/", - }, - "group": "build", - "problemMatcher": [], - "label": "tgui: build tgfont", - "detail": "node mkdist.cjs && fantasticon --config config.cjs" - }, - { - "type": "shell", - "command": "tgui/bin/tgui", + "command": "bin/tgui-dev", "windows": { - "command": ".\\tgui\\bin\\tgui-prettybuild.bat" + "command": ".\\bin\\tgui-dev.cmd" }, "problemMatcher": [ "$tsc", "$eslint-stylish" ], "group": "build", - "label": "tgui: prettybuild" - }, - { - "type": "shell", - "command": "bin/tgui --dev", - "windows": { - "command": ".\\bin\\tgui.bat --dev" - }, - "problemMatcher": [ - "$tsc", - "$eslint-stylish" - ], - "options": { - "cwd": ".\\tgui\\", - }, - "group": "build", "label": "tgui: dev server" + }, + { + "type": "shell", + "command": "bin/tgui-bench", + "windows": { + "command": ".\\bin\\tgui-bench.cmd" + }, + "problemMatcher": [ + "$tsc", + "$eslint-stylish" + ], + "group": "build", + "label": "tgui: bench" + }, + { + "type": "shell", + "command": "bin/tgui-sonar", + "windows": { + "command": ".\\bin\\tgui-sonar.cmd" + }, + "problemMatcher": [ + "$tsc", + "$eslint-stylish" + ], + "group": "build", + "label": "tgui: sonar" + }, + { + "type": "shell", + "command": "bin/tgfont", + "windows": { + "command": ".\\bin\\tgfont.cmd" + }, + "problemMatcher": [ + "$tsc", + "$eslint-stylish" + ], + "group": "build", + "label": "tgui: rebuild tgfont" } ] } diff --git a/README.md b/README.md index b878df4d7e..28724e3d8c 100644 --- a/README.md +++ b/README.md @@ -54,11 +54,11 @@ This will take a while to download, but it provides an easier method for updatin First-time installation should be fairly straightforward. First, you'll need BYOND installed. You can get it from [here](http://www.byond.com/). -This is a sourcecode-only release, so the next step is to compile the server files. Open vorestation.dme by double-clicking it, open the Build menu, and click compile. This'll take a little while, and if everything's done right you'll get a message like this: +**The quick way**. Find `bin/server.cmd` in this folder and double click it to automatically build and host the server on port 1337. - saving vorestation.dmb (DEBUG mode) +**The long way**. Find `bin/build.cmd` in this folder, and double click it to initiate the build. It consists of multiple steps and might take around 1-5 minutes to compile. If it closes, it means it has finished its job. You can then [setup the server](.github/guides/RUNNING_A_SERVER.md) normally by opening `tgstation.dmb` in DreamDaemon. - vorestation.dmb - 0 errors, 0 warnings +**Building vorestation in DreamMaker directly is deprecated and might produce errors**, such as `'tgui.bundle.js': cannot find file`. If you see any errors or warnings, something has gone wrong - possibly a corrupt download or the files extracted wrong, or a code issue on the main repo. Ask on IRC. diff --git a/_build_dependencies.sh b/_build_dependencies.sh index 8b6659c3a9..2edcacda85 100644 --- a/_build_dependencies.sh +++ b/_build_dependencies.sh @@ -6,6 +6,6 @@ export NODE_VERSION=20 # Byond Major export BYOND_MAJOR=515 # Byond Minor -export BYOND_MINOR=1630 +export BYOND_MINOR=1637 # Macro Count -export MACRO_COUNT=4 +export MACRO_COUNT=8 diff --git a/bin/build.cmd b/bin/build.cmd new file mode 100644 index 0000000000..98c2ef45e1 --- /dev/null +++ b/bin/build.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error build %* diff --git a/bin/clean.cmd b/bin/clean.cmd new file mode 100644 index 0000000000..8eacd92ebd --- /dev/null +++ b/bin/clean.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error clean-all %* diff --git a/bin/server.cmd b/bin/server.cmd new file mode 100644 index 0000000000..c6e6642baf --- /dev/null +++ b/bin/server.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error server %* diff --git a/bin/test.cmd b/bin/test.cmd new file mode 100644 index 0000000000..a76a9c6745 --- /dev/null +++ b/bin/test.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error dm-test %* diff --git a/bin/tgfont.cmd b/bin/tgfont.cmd new file mode 100644 index 0000000000..b768c81d65 --- /dev/null +++ b/bin/tgfont.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error tg-font %* diff --git a/bin/tgui-bench.cmd b/bin/tgui-bench.cmd new file mode 100644 index 0000000000..333115f795 --- /dev/null +++ b/bin/tgui-bench.cmd @@ -0,0 +1,3 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-bench %* +pause diff --git a/bin/tgui-build.cmd b/bin/tgui-build.cmd new file mode 100644 index 0000000000..7804fc6daa --- /dev/null +++ b/bin/tgui-build.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui tgui-lint tgui-test %* diff --git a/bin/tgui-dev.cmd b/bin/tgui-dev.cmd new file mode 100644 index 0000000000..25ff3495d4 --- /dev/null +++ b/bin/tgui-dev.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-dev %* diff --git a/bin/tgui-sonar.cmd b/bin/tgui-sonar.cmd new file mode 100644 index 0000000000..e083f65362 --- /dev/null +++ b/bin/tgui-sonar.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\..\tools\build\build.bat" --wait-on-error tgui-sonar %* diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000000..dc791f60c9 --- /dev/null +++ b/build.cmd @@ -0,0 +1,2 @@ +@echo off +call "%~dp0\tools\build\build.bat" --wait-on-error build %* diff --git a/code/__defines/cooldowns.dm b/code/__defines/cooldowns.dm index a04c83a4e2..d177ae1e81 100644 --- a/code/__defines/cooldowns.dm +++ b/code/__defines/cooldowns.dm @@ -25,6 +25,9 @@ * Cooldown system based on an datum-level associative lazylist using timers. */ +// random cooldowns +#define COOLDOWN_VENOM_MILKING "venom_milking" + // admin verb cooldowns #define COOLDOWN_INTERNET_SOUND "internet_sound" diff --git a/code/controllers/subsystems/media_tracks.dm b/code/controllers/subsystems/media_tracks.dm index 9bd4f3ca1a..7fbffa65b6 100644 --- a/code/controllers/subsystems/media_tracks.dm +++ b/code/controllers/subsystems/media_tracks.dm @@ -172,7 +172,7 @@ SUBSYSTEM_DEF(media_tracks) sort_tracks() return - to_chat(C, "") + to_chat(C, "Couldn't find a track matching the specified parameters.") /datum/controller/subsystem/media_tracks/proc/add_track(var/mob/user, var/new_url, var/new_title, var/new_duration, var/new_artist, var/new_genre, var/new_secret, var/new_lobby) if(!check_rights(R_DEBUG|R_FUN)) diff --git a/code/datums/datacore.dm b/code/datums/datacore.dm index 23e358730b..cfe18eba25 100644 --- a/code/datums/datacore.dm +++ b/code/datums/datacore.dm @@ -356,7 +356,10 @@ var/global/list/PDA_Manifest = list() G.fields["p_stat"] = "Active" G.fields["m_stat"] = "Stable" G.fields["sex"] = gender2text(H.gender) - G.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit + if(H.species.name == SPECIES_HANNER) + G.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]" + else + G.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" G.fields["home_system"] = H.home_system G.fields["birthplace"] = H.birthplace G.fields["citizenship"] = H.citizenship @@ -367,7 +370,10 @@ var/global/list/PDA_Manifest = list() //Medical Record var/datum/data/record/M = CreateMedicalRecord(H.real_name, id, hidden) - M.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit + if(H.species.name == SPECIES_HANNER) + M.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]" + else + M.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" M.fields["b_type"] = H.b_type M.fields["blood_reagent"] = H.species.blood_reagents M.fields["b_dna"] = H.dna.unique_enzymes @@ -381,7 +387,10 @@ var/global/list/PDA_Manifest = list() //Security Record var/datum/data/record/S = CreateSecurityRecord(H.real_name, id, hidden) - S.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit + if(H.species.name == SPECIES_HANNER) + S.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]" + else + S.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" if(H.get_FBP_type()) S.fields["brain_type"] = H.get_FBP_type() else @@ -407,7 +416,10 @@ var/global/list/PDA_Manifest = list() L.fields["b_dna"] = H.dna.unique_enzymes L.fields["enzymes"] = H.dna.SE // Used in respawning L.fields["identity"] = H.dna.UI // " - L.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" //VOREStation Edit + if(H.species.name == SPECIES_HANNER) + L.fields["species"] = "[H.custom_species ? "[H.custom_species]" : H.species.name]" + else + L.fields["species"] = "[H.custom_species ? "[H.custom_species] ([H.species.name])" : H.species.name]" L.fields["home_system"] = H.home_system L.fields["birthplace"] = H.birthplace L.fields["citizenship"] = H.citizenship diff --git a/code/datums/underwear/undershirts.dm b/code/datums/underwear/undershirts.dm index 9b7571e1d1..41b0499b3a 100644 --- a/code/datums/underwear/undershirts.dm +++ b/code/datums/underwear/undershirts.dm @@ -252,3 +252,33 @@ name = "Leotard" icon_state = "leotard" has_color = TRUE + +/datum/category_item/underwear/undershirt/tshirt + name = "Feminine T-Shirt" + icon_state = "t-shirt_full" + has_color = TRUE + +/datum/category_item/underwear/undershirt/tshirt_midriff + name = "Feminine T-Shirt (midriff)" + icon_state = "t-shirt_midriff" + has_color = TRUE + +/datum/category_item/underwear/undershirt/tshirt_shoulders + name = "Feminine T-Shirt (black with shoulders)" + icon_state = "t-shirt_full_shoulders" + has_color = TRUE + +/datum/category_item/underwear/undershirt/tshirt_midriff_shoulders + name = "Feminine T-Shirt (midriff, black with shoulders)" + icon_state = "t-shirt_midriff_shoulders" + has_color = TRUE + +/datum/category_item/underwear/undershirt/tshirt_shoulders_text + name = "Feminine T-Shirt (black with shoulders and text)" + icon_state = "t-shirt_full_shoulders_text" + has_color = TRUE + +/datum/category_item/underwear/undershirt/tshirt_midriff_shoulders_text + name = "Feminine T-Shirt (midriff, black with shoulders and text)" + icon_state = "t-shirt_midriff_shoulders_text" + has_color = TRUE diff --git a/code/game/machinery/jukebox.dm b/code/game/machinery/jukebox.dm index caaaaef648..38948cff0f 100644 --- a/code/game/machinery/jukebox.dm +++ b/code/game/machinery/jukebox.dm @@ -441,7 +441,7 @@ qdel(T) return - to_chat(C, "") + to_chat(C, "Couldn't find a track matching the specified parameters.") /obj/machinery/media/jukebox/ghost/vv_get_dropdown() . = ..() diff --git a/code/game/objects/items/devices/body_snatcher_vr.dm b/code/game/objects/items/devices/body_snatcher_vr.dm index 8239ec65c3..f2a31437da 100644 --- a/code/game/objects/items/devices/body_snatcher_vr.dm +++ b/code/game/objects/items/devices/body_snatcher_vr.dm @@ -29,6 +29,12 @@ to_chat(usr,"The target's mind is too complex to be affected!") return + if(ishuman(M)) + var/mob/living/carbon/human/H = M + if(H.resleeve_lock && usr.ckey != H.resleeve_lock) + to_chat(src, span_danger("[H] cannot be impersonated!")) + return + if(M.stat == DEAD) //Are they dead? to_chat(usr,"A warning pops up on the device, informing you that [M] is dead, and, as such, the mind transfer can not be done.") return diff --git a/code/game/objects/items/weapons/id cards/station_ids.dm b/code/game/objects/items/weapons/id cards/station_ids.dm index c628a4bd8a..63ed6b6aeb 100644 --- a/code/game/objects/items/weapons/id cards/station_ids.dm +++ b/code/game/objects/items/weapons/id cards/station_ids.dm @@ -74,7 +74,10 @@ /mob/living/carbon/human/set_id_info(var/obj/item/weapon/card/id/id_card) ..() id_card.age = age - id_card.species = "[custom_species ? "[custom_species] ([species.name])" : species.name]" + if(species.name == SPECIES_HANNER) + id_card.species = "[custom_species ? "[custom_species]" : species.name]" + else + id_card.species = "[custom_species ? "[custom_species] ([species.name])" : species.name]" id_card.sex = capitalize(name_gender()) /obj/item/weapon/card/id/tgui_data(mob/user) diff --git a/code/modules/admin/modify_robot.dm b/code/modules/admin/modify_robot.dm index 3c1682f40e..1ffcf78be2 100644 --- a/code/modules/admin/modify_robot.dm +++ b/code/modules/admin/modify_robot.dm @@ -84,7 +84,7 @@ .["target"]["availalbe_channels"] = availalbe_channels // Components .["target"]["components"] = get_components() - .["cell"] = target.cell?.name + .["cell"] = list("name" = target.cell?.name, "charge" = target.cell?.charge, "maxcharge" = target.cell?.maxcharge) .["cell_options"] = get_cells() // Access .["id_icon"] = icon2html(target.idcard, user, sourceonly=TRUE) @@ -368,6 +368,17 @@ if(istype(C, /datum/robot_component/cell)) target.cell = null return TRUE + if("adjust_cell_charge") + target.cell.charge = text2num(params["charge"]) + return TRUE + if("adjust_brute") + var/datum/robot_component/C = locate(params["component"]) + C.brute_damage = text2num(params["damage"]) + return TRUE + if("adjust_electronics") + var/datum/robot_component/C = locate(params["component"]) + C.electronics_damage = text2num(params["damage"]) + return TRUE if("add_access") target.idcard.access += text2num(params["access"]) return TRUE diff --git a/code/modules/client/preference_setup/loadout/loadout_accessories.dm b/code/modules/client/preference_setup/loadout/loadout_accessories.dm index 600e33e5c2..39ac6ae97e 100644 --- a/code/modules/client/preference_setup/loadout/loadout_accessories.dm +++ b/code/modules/client/preference_setup/loadout/loadout_accessories.dm @@ -555,60 +555,17 @@ Talon pin ) gear_tweaks += new/datum/gear_tweak/path(insignia) -/datum/gear/accessory/plushie - display_name = "Plushie Selection" - description = "A single plushie of your selection!" - path = /obj/item/toy/plushie/nymph +/datum/gear/accessory/belt_selection + display_name = "belts (selection, colorable)" + path = /obj/item/clothing/accessory/belt -/datum/gear/accessory/plushie/New() +/datum/gear/accessory/belt_selection/New() ..() - var/plushie = list( - "Nymph" = /obj/item/toy/plushie/nymph, - "Mouse" = /obj/item/toy/plushie/mouse, - "Kitten" = /obj/item/toy/plushie/kitten, - "Lizard" = /obj/item/toy/plushie/lizard, - "Black cat" = /obj/item/toy/plushie/black_cat, - "Black fox" = /obj/item/toy/plushie/black_fox, - "Blue fox" = /obj/item/toy/plushie/blue_fox, - "Carp plushie" = /obj/random/carp_plushie, - "Coffee fox" = /obj/item/toy/plushie/coffee_fox, - "Corgi" = /obj/item/toy/plushie/corgi, - "Crimson fox" = /obj/item/toy/plushie/crimson_fox, - "Deer" = /obj/item/toy/plushie/deer, - "Girly corgi" = /obj/item/toy/plushie/girly_corgi, - "Grey cat" = /obj/item/toy/plushie/grey_cat, - "Marble fox" = /obj/item/toy/plushie/marble_fox, - "Octopus" = /obj/item/toy/plushie/octopus, - "Orange cat" = /obj/item/toy/plushie/orange_cat, - "Orange fox" = /obj/item/toy/plushie/orange_fox, - "Pink fox" = /obj/item/toy/plushie/pink_fox, - "Purple fox" = /obj/item/toy/plushie/purple_fox, - "Red fox" = /obj/item/toy/plushie/red_fox, - "Robo corgi" = /obj/item/toy/plushie/robo_corgi, - "Siamese cat" = /obj/item/toy/plushie/siamese_cat, - "Spider" = /obj/item/toy/plushie/spider, - "Tabby cat" = /obj/item/toy/plushie/tabby_cat, - "Tuxedo cat" = /obj/item/toy/plushie/tuxedo_cat, - "White cat" = /obj/item/toy/plushie/white_cat, - "Lizard" = /obj/item/toy/plushie/lizardplushie, - "Kobold" = /obj/item/toy/plushie/lizardplushie/kobold, - "Resh" = /obj/item/toy/plushie/lizardplushie/resh, - "Slime" = /obj/item/toy/plushie/slimeplushie, - "Box" = /obj/item/toy/plushie/box, - "Robot" = /obj/item/toy/plushie/borgplushie, - "Medihound" = /obj/item/toy/plushie/borgplushie/medihound, - "Scrubpuppy" = /obj/item/toy/plushie/borgplushie/scrubpuppy, - "Foxbear" = /obj/item/toy/plushie/foxbear, - "Operative" = /obj/item/toy/plushie/nukeplushie, - "Otter" = /obj/item/toy/plushie/otter, - "Vox" = /obj/item/toy/plushie/vox, - "Shark" = /obj/item/toy/plushie/shark, - "Sec Drake" = /obj/item/toy/plushie/borgplushie/drake/sec, - "Med Drake" = /obj/item/toy/plushie/borgplushie/drake/med, - "Sci Drake" = /obj/item/toy/plushie/borgplushie/drake/sci, - "Jani Drake" = /obj/item/toy/plushie/borgplushie/drake/jani, - "Eng Drake" = /obj/item/toy/plushie/borgplushie/drake/eng, - "Mine Drake" = /obj/item/toy/plushie/borgplushie/drake/mine, - "Trauma Drake" = /obj/item/toy/plushie/borgplushie/drake/trauma, + var/list/wristband_lists = list( + "Thin Belt" = /obj/item/clothing/accessory/belt, + "Thick Belt" = /obj/item/clothing/accessory/belt/thick, + "Strap Belt" = /obj/item/clothing/accessory/belt/strap, + "Studded Belt" = /obj/item/clothing/accessory/belt/studded ) - gear_tweaks += new/datum/gear_tweak/path(plushie) + gear_tweaks += new/datum/gear_tweak/path(wristband_lists) + gear_tweaks += gear_tweak_free_color_choice \ No newline at end of file diff --git a/code/modules/client/preference_setup/loadout/loadout_uniform_vr.dm b/code/modules/client/preference_setup/loadout/loadout_uniform_vr.dm index d40ea6cf6e..587a6635fb 100644 --- a/code/modules/client/preference_setup/loadout/loadout_uniform_vr.dm +++ b/code/modules/client/preference_setup/loadout/loadout_uniform_vr.dm @@ -185,7 +185,9 @@ Talon jumpsuit "high skirt"=/obj/item/clothing/under/skirt/colorable/high, "swept skirt"=/obj/item/clothing/under/skirt/colorable/swept, "jumper skirt"=/obj/item/clothing/under/skirt/colorable/jumper, - "jumper dress"=/obj/item/clothing/under/skirt/colorable/jumperdress + "jumper dress"=/obj/item/clothing/under/skirt/colorable/jumperdress, + "short skirt"=/obj/item/clothing/under/skirt/colorable/short, + "short skirt (split)"=/obj/item/clothing/under/skirt/colorable/short_split ) gear_tweaks += list(new/datum/gear_tweak/path(skirts), gear_tweak_free_color_choice) diff --git a/code/modules/clothing/under/accessories/accessory_vr.dm b/code/modules/clothing/under/accessories/accessory_vr.dm index d5837ebce4..f03ae21b0a 100644 --- a/code/modules/clothing/under/accessories/accessory_vr.dm +++ b/code/modules/clothing/under/accessories/accessory_vr.dm @@ -1181,3 +1181,31 @@ flags_inv = HIDEHOLSTER to_chat(user, "You adjust your poncho.") update_clothing_icon() + +/obj/item/clothing/accessory/belt + name = "Thin Belt" + desc = "A thin belt for holding your pants up." + icon = 'icons/inventory/accessory/item.dmi' + icon_override = 'icons/inventory/accessory/mob.dmi' + icon_state = "belt_thin" + item_state = "belt_thin" + slot_flags = SLOT_TIE | SLOT_BELT + slot = ACCESSORY_SLOT_DECOR + +/obj/item/clothing/accessory/belt/thick + name = "Thick Belt" + desc = "A thick belt for holding your pants up." + icon_state = "belt_thick" + item_state = "belt_thick" + +/obj/item/clothing/accessory/belt/strap + name = "Strap Belt" + desc = "A belt with no bucklet for holding your pants up." + icon_state = "belt_strap" + item_state = "belt_strap" + +/obj/item/clothing/accessory/belt/studded + name = "Studded Belt" + desc = "A studded belt for holding your pants up and looking cool." + icon_state = "belt_studded" + item_state = "belt_studded" diff --git a/code/modules/clothing/under/miscellaneous_vr.dm b/code/modules/clothing/under/miscellaneous_vr.dm index eb12168713..80f32da015 100644 --- a/code/modules/clothing/under/miscellaneous_vr.dm +++ b/code/modules/clothing/under/miscellaneous_vr.dm @@ -524,6 +524,20 @@ item_state = "skirt_jumperdress" worn_state = "skirt_jumperdress" +/obj/item/clothing/under/skirt/colorable/short + name = "short skirt" + desc = "A far too short pleated skirt." + icon_state = "skirt_short" + item_state = "skirt_short" + worn_state = "skirt_short" + +/obj/item/clothing/under/skirt/colorable/short_split + name = "short skirt (split)" + desc = "A far too short pleated skirt with an open split down one side." + icon_state = "skirt_short_split" + item_state = "skirt_short_split" + worn_state = "skirt_short_split" + // Gwen Beedell's clown outfit /obj/item/clothing/under/stripeddungarees diff --git a/code/modules/economy/vending_machines_vr.dm b/code/modules/economy/vending_machines_vr.dm index 5005e39af9..402f1ed8aa 100644 --- a/code/modules/economy/vending_machines_vr.dm +++ b/code/modules/economy/vending_machines_vr.dm @@ -621,6 +621,10 @@ /obj/item/clothing/accessory/wristbandcollection/trans = 5, /obj/item/clothing/accessory/wristbandcollection/ace = 5, /obj/item/clothing/accessory/wristband_spiked = 5, + /obj/item/clothing/accessory/belt = 5, + /obj/item/clothing/accessory/belt/thick = 5, + /obj/item/clothing/accessory/belt/strap = 5, + /obj/item/clothing/accessory/belt/studded = 5, /obj/item/weapon/storage/wallet = 5, /obj/item/weapon/storage/wallet/poly = 5, /obj/item/weapon/storage/wallet/womens = 5, @@ -762,6 +766,10 @@ /obj/item/clothing/accessory/wristbandcollection/trans = 30, /obj/item/clothing/accessory/wristbandcollection/ace = 30, /obj/item/clothing/accessory/wristband_spiked = 20, + /obj/item/clothing/accessory/belt = 50, + /obj/item/clothing/accessory/belt/thick = 50, + /obj/item/clothing/accessory/belt/strap = 50, + /obj/item/clothing/accessory/belt/studded = 50, /obj/item/weapon/storage/wallet = 50, /obj/item/weapon/storage/wallet/poly = 50, /obj/item/weapon/storage/wallet/womens = 50, @@ -909,6 +917,8 @@ /obj/item/clothing/under/ascetic = 5, /obj/item/clothing/under/dress/white3 = 5, /obj/item/clothing/under/skirt/pleated = 5, + /obj/item/clothing/under/skirt/colorable/short = 5, + /obj/item/clothing/under/skirt/colorable/short_split = 5, /obj/item/clothing/under/dress/darkred = 5, /obj/item/clothing/under/dress/redeveninggown = 5, /obj/item/clothing/under/dress/red_swept_dress = 5, @@ -1093,6 +1103,8 @@ /obj/item/clothing/under/ascetic = 50, /obj/item/clothing/under/dress/white3 = 50, /obj/item/clothing/under/skirt/pleated = 50, + /obj/item/clothing/under/skirt/colorable/short = 50, + /obj/item/clothing/under/skirt/colorable/short_split = 50, /obj/item/clothing/under/dress/darkred = 50, /obj/item/clothing/under/dress/redeveninggown = 50, /obj/item/clothing/under/dress/red_swept_dress = 50, @@ -2125,6 +2137,8 @@ /obj/item/clothing/under/skirt/red = 5, /obj/item/clothing/under/skirt/denim = 5, /obj/item/clothing/under/skirt/pleated = 5, + /obj/item/clothing/under/skirt/colorable/short = 5, + /obj/item/clothing/under/skirt/colorable/short_split = 5, /obj/item/clothing/under/skirt/outfit/plaid_blue = 5, /obj/item/clothing/under/skirt/outfit/plaid_red = 5, /obj/item/clothing/under/skirt/outfit/plaid_purple = 5, diff --git a/code/modules/food/kitchen/cooking_machines/_appliance.dm b/code/modules/food/kitchen/cooking_machines/_appliance.dm index c58bed83e3..3cc1cf57f7 100644 --- a/code/modules/food/kitchen/cooking_machines/_appliance.dm +++ b/code/modules/food/kitchen/cooking_machines/_appliance.dm @@ -77,7 +77,7 @@ string += "-\a [CI.container.label(null, CI.combine_target)], [report_progress(CI)]
" return string else - to_chat(user, "") + to_chat(user, "It is empty.") /obj/machinery/appliance/proc/report_progress_tgui(datum/cooking_item/CI) if(!CI || !CI.max_cookwork) @@ -812,4 +812,4 @@ return food_safety = !food_safety - to_chat(usr, "You flip \the [src]'s safe mode switch. Safe mode is now [food_safety ? "on" : "off"].") \ No newline at end of file + to_chat(usr, "You flip \the [src]'s safe mode switch. Safe mode is now [food_safety ? "on" : "off"].") diff --git a/code/modules/mob/living/carbon/human/species/lleill/hanner.dm b/code/modules/mob/living/carbon/human/species/lleill/hanner.dm index 1a684a477b..f63369d232 100644 --- a/code/modules/mob/living/carbon/human/species/lleill/hanner.dm +++ b/code/modules/mob/living/carbon/human/species/lleill/hanner.dm @@ -8,7 +8,6 @@ transmutation and energy sharing abilities not unlike they lleill, but considerably more limited. Unlike \ other shapeshifter species, such as proteans or prometheans, Hanner have a typical humanoid set of organs and can not regenerate." - flags = NO_SCAN color_mult = 1 appearance_flags = HAS_SKIN_COLOR | HAS_EYE_COLOR | HAS_HAIR_COLOR | HAS_UNDERWEAR | HAS_LIPS spawn_flags = SPECIES_CAN_JOIN | SPECIES_IS_WHITELISTED | SPECIES_WHITELIST_SELECTABLE diff --git a/code/modules/mob/living/carbon/human/species/lleill/lleill_items.dm b/code/modules/mob/living/carbon/human/species/lleill/lleill_items.dm index 9f4c9afa8a..21ab540fb9 100644 --- a/code/modules/mob/living/carbon/human/species/lleill/lleill_items.dm +++ b/code/modules/mob/living/carbon/human/species/lleill/lleill_items.dm @@ -130,7 +130,7 @@ for(var/mob/living/carbon/human/M in mob_list) if(M.z != user.z || get_dist(user,M) > 10) continue - if(istype(M) && M.resleeve_lock && M.ckey != M.resleeve_lock) + if(!M.allow_mimicry) continue targets |= M 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 a7c4296fd6..321788b775 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 @@ -210,11 +210,6 @@ if(pred.prey_controlled) to_chat(prey, "\The [pred] is already dominated, and cannot be controlled at this time.") return - if(ishuman(pred)) - var/mob/living/carbon/human/h = pred - if(h.resleeve_lock && ckey != h.resleeve_lock) - to_chat(src, "\The [h] cannot be impersonated!") - return if(tgui_alert(prey, "You are attempting to take over [pred], are you sure? Ensure that their preferences align with this kind of play.", "Take Over Predator",list("No","Yes")) != "Yes") return to_chat(prey, "You attempt to exert your control over \the [pred]...") diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/morph/morph.dm b/code/modules/mob/living/simple_mob/subtypes/vore/morph/morph.dm index 50d8df8085..041eb8fe9f 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/morph/morph.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/morph/morph.dm @@ -99,7 +99,7 @@ /mob/living/simple_mob/vore/morph/proc/assume(atom/movable/target) var/mob/living/carbon/human/humantarget = target - if(istype(humantarget) && humantarget.resleeve_lock && ckey != humantarget.resleeve_lock) + if(istype(humantarget) && !humantarget.allow_mimicry) to_chat(src, "[target] cannot be impersonated!") return if(morphed) @@ -272,12 +272,9 @@ var/mob/living/L = tgui_input_list(src, "Select a mob to take over:", "Take Over Prey", possible_mobs) if(!L) return - // Adding a ishuman check here, since silicon mobs don't have a resleeve_lock from what I can tell. - if(ishuman(L)) - var/mob/living/carbon/human/M = L - if(M.resleeve_lock && ckey != M.resleeve_lock) - to_chat(src, "\The [M] cannot be impersonated!") - return + if(!L.allow_mimicry) + to_chat(src, "\The [L] cannot be impersonated!") + return if(tgui_alert(src, "You selected [L] to attempt to take over. Are you sure?", "Take Over Prey",list("No","Yes")) == "Yes") log_admin("[key_name_admin(src)] offered [L] to swap bodies as a morph.") if(tgui_alert(L, "\The [src] has elected to attempt to take over your body and control you. Is this something you will allow to happen?", "Allow Morph To Take Over",list("No","Yes")) == "Yes") diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm b/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm index ed1467d269..21b17eca5e 100644 --- a/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm +++ b/code/modules/mob/living/simple_mob/subtypes/vore/vore.dm @@ -36,6 +36,7 @@ noisy = client.prefs_vr.noisy selective_preference = client.prefs_vr.selective_preference eating_privacy_global = client.prefs_vr.eating_privacy_global + allow_mimicry = client.prefs_vr.allow_mimicry drop_vore = client.prefs_vr.drop_vore stumble_vore = client.prefs_vr.stumble_vore diff --git a/code/modules/player_tips_vr/player_tips_list_vr.dm b/code/modules/player_tips_vr/player_tips_list_vr.dm index e9b4752f45..f8e956f9f5 100644 --- a/code/modules/player_tips_vr/player_tips_list_vr.dm +++ b/code/modules/player_tips_vr/player_tips_list_vr.dm @@ -52,7 +52,8 @@ When editing the list, please try and keep similar probabilities near each other prob(25); "[info] Emergency Fire Doors seal breaches and keep active fires out. Please do not open them without good reason.", prob(25); "[info] The kitchen's Oven can fit multiple ingredients in one slot if you pull the baking tray out first. This is required for most recipes, and the Grille and Deep Frier work the same way!", prob(10); "[info] You can keep a single item between rounds using secure lockboxes! Beware! You can only store 1 item across all characters! To find these lockboxes, feel free to ask over radio!", - prob(10); "[info] Not every hostile NPC you encounter while mining or exploring need to be defeated. Sometimes, it's better to avoid or run away from them. For example, star-treaders are slow and weak but have lots of HP - it is better to just run away." + prob(10); "[info] Not every hostile NPC you encounter while mining or exploring need to be defeated. Sometimes, it's better to avoid or run away from them. For example, star-treaders are slow and weak but have lots of HP - it is better to just run away.", + prob(10); "[info] Clicking on a venomous animal/person with a closed beaker will express their venom into the beaker!", ) if("roleplay") diff --git a/code/modules/reagents/reagent_containers/glass.dm b/code/modules/reagents/reagent_containers/glass.dm index ef71cc5987..b33c85592d 100644 --- a/code/modules/reagents/reagent_containers/glass.dm +++ b/code/modules/reagents/reagent_containers/glass.dm @@ -18,6 +18,7 @@ unacidable = TRUE //glass doesn't dissolve in acid drop_sound = 'sound/items/drop/bottle.ogg' pickup_sound = 'sound/items/pickup/bottle.ogg' + description_info = "Clicking on a venomous animal (or person) with the lid closed will express their venom into the beaker!" var/label_text = "" @@ -85,15 +86,16 @@ if(force && !(flags & NOBLUDGEON) && user.a_intent == I_HURT) return ..() + // If the container is *closed* we do snake milking!~ + if(!is_open_container() && isliving(M)) + return attempt_snake_milking(user, M) + if(standard_feed_mob(user, M)) return return 0 /obj/item/weapon/reagent_containers/glass/standard_feed_mob(var/mob/user, var/mob/target) - if(!is_open_container()) - to_chat(user, "You need to open \the [src] first.") - return 1 if(user.a_intent == I_HURT) return 1 return ..() @@ -101,8 +103,33 @@ /obj/item/weapon/reagent_containers/glass/self_feed_message(var/mob/user) to_chat(user, "You swallow a gulp from \the [src].") +/obj/item/weapon/reagent_containers/glass/proc/attempt_snake_milking(mob/living/user, mob/living/target) + var/reagent + var/amount + + if(target.trait_injection_selected) + reagent = target.trait_injection_selected + amount = target.trait_injection_amount + else if(istype(target, /mob/living/simple_mob/animal/giant_spider)) + var/mob/living/simple_mob/animal/giant_spider/spider = target + reagent = spider.poison_type + amount = spider.poison_per_bite + + if(!reagent || !amount) + to_chat(user, span_warning("[target] does not have venom you can express. Open the beaker to drink from it.")) + return TRUE + + if(TIMER_COOLDOWN_RUNNING(target, COOLDOWN_VENOM_MILKING)) + user.visible_message(span_warning("[user] attempts to express venom from [target], but nothing happens."), span_warning("[target] had their venom expressed too recently, try again later.")) + return TRUE + + TIMER_COOLDOWN_START(target, COOLDOWN_VENOM_MILKING, 30 SECONDS) + user.visible_message(span_notice("[user] expresses venom from [target].")) + reagents.add_reagent(reagent, amount) + return TRUE + /obj/item/weapon/reagent_containers/glass/afterattack(var/obj/target, var/mob/user, var/proximity) - if(!is_open_container() || !proximity) //Is the container open & are they next to whatever they're clicking? + if(!proximity || !is_open_container()) //Is the container open & are they next to whatever they're clicking? return 1 //If not, do nothing. for(var/type in can_be_placed_into) //Is it something it can be placed into? if(istype(target, type)) diff --git a/code/modules/resleeving/infocore_records.dm b/code/modules/resleeving/infocore_records.dm index 2ef362ec4d..6a11ff6083 100644 --- a/code/modules/resleeving/infocore_records.dm +++ b/code/modules/resleeving/infocore_records.dm @@ -112,10 +112,11 @@ //Person OOCly doesn't want people impersonating them locked = ckeylock - //Prevent people from printing restricted and whitelisted species var/datum/species/S = GLOB.all_species["[M.dna.species]"] if(S) - toocomplex = (S.spawn_flags & SPECIES_IS_WHITELISTED) || (S.spawn_flags & SPECIES_IS_RESTRICTED) + // Force ckey locking if species is whitelisted + if((S.spawn_flags & SPECIES_IS_WHITELISTED) || (S.spawn_flags & SPECIES_IS_RESTRICTED)) + locked = TRUE //General stuff about them synthetic = M.isSynthetic() diff --git a/code/modules/resleeving/machines.dm b/code/modules/resleeving/machines.dm index 2ed4a34cd3..436271e78c 100644 --- a/code/modules/resleeving/machines.dm +++ b/code/modules/resleeving/machines.dm @@ -33,7 +33,11 @@ var/datum/dna2/record/R = current_project.mydna var/mob/living/carbon/human/H = new /mob/living/carbon/human(src, R.dna.species) if(current_project.locked) - H.resleeve_lock = current_project.ckey + if(current_project.ckey) + H.resleeve_lock = current_project.ckey + else + // Ensure even body scans without an attached ckey respect locking + H.resleeve_lock = "@badckey" //Fix the external organs for(var/part in current_project.limb_data) @@ -180,6 +184,7 @@ occupant = null if(locked) locked = 0 + update_icon() return return @@ -189,6 +194,12 @@ return 100 * ((occupant.health + abs(config.health_threshold_dead)) / (occupant.maxHealth + abs(config.health_threshold_dead))) return 0 +/obj/machinery/clonepod/transhuman/examine(mob/user, infix, suffix) + . = ..() + if(occupant) + var/completion = get_completion() + . += "Progress: [round(completion)]% [chat_progress_bar(round(completion), TRUE)]" + //Synthetic version /obj/machinery/transhuman/synthprinter name = "SynthFab 3000" @@ -282,7 +293,11 @@ var/datum/dna2/record/R = current_project.mydna var/mob/living/carbon/human/H = new /mob/living/carbon/human(src, R.dna.species) if(current_project.locked) - H.resleeve_lock = current_project.ckey + if(current_project.ckey) + H.resleeve_lock = current_project.ckey + else + // Ensure even body scans without an attached ckey respect locking + H.resleeve_lock = "@badckey" //Fix the external organs for(var/part in current_project.limb_data) diff --git a/code/modules/vore/chat_healthbars.dm b/code/modules/vore/chat_healthbars.dm index 583435dea3..7442d99d59 100644 --- a/code/modules/vore/chat_healthbars.dm +++ b/code/modules/vore/chat_healthbars.dm @@ -1,5 +1,64 @@ //Health bars in the game window would be pretty challenging and I don't know how to do that, so I thought this would be a good alternative +// Generates the progress bar text +/proc/chat_progress_bar(percentage, add_color = FALSE) + switch(percentage) + if(100) + . = "|▓▓▓▓▓▓▓▓▓▓|" + if(95 to 99) + . = "|▓▓▓▓▓▓▓▓▓▒|" + if(90 to 94) + . = "|▓▓▓▓▓▓▓▓▓░|" + if(85 to 89) + . = "|▓▓▓▓▓▓▓▓▒░|" + if(80 to 84) + . = "|▓▓▓▓▓▓▓▓░░|" + if(75 to 79) + . = "|▓▓▓▓▓▓▓▒░░|" + if(70 to 74) + . = "|▓▓▓▓▓▓▓░░░|" + if(65 to 69) + . = "|▓▓▓▓▓▓▒░░░|" + if(60 to 64) + . = "|▓▓▓▓▓▓░░░░|" + if(55 to 59) + . = "|▓▓▓▓▓▒░░░░|" + if(50 to 54) + . = "|▓▓▓▓▓░░░░░|" + if(45 to 49) + . = "|▓▓▓▓▒░░░░░|" + if(40 to 44) + . = "|▓▓▓▓░░░░░░|" + if(35 to 39) + . = "|▓▓▓▒░░░░░░|" + if(30 to 34) + . = "|▓▓▓░░░░░░░|" + if(25 to 29) + . = "|▓▓▒░░░░░░░|" + if(20 to 24) + . = "|▓▓░░░░░░░░|" + if(15 to 19) + . = "|▓▒░░░░░░░░|" + if(10 to 14) + . = "|▓░░░░░░░░░|" + if(5 to 9) + . = "|▒░░░░░░░░░|" + if(0) + . = "|░░░░░░░░░░|" + else + . = "!░░░░░░░░░░!" + + if(add_color) + switch(percentage) + if(75 to 100) + . = span_green(.) + if(50 to 75) + . = span_yellow(.) + if(25 to 50) + . = span_orange(.) + else + . = span_red(.) + /mob/living/proc/chat_healthbar(var/mob/living/reciever, onExamine = FALSE, override = FALSE) if(!reciever) //No one to send it to, don't bother return @@ -26,52 +85,7 @@ ourpercent = round(ourpercent) - switch(ourpercent) //I thought about trying to do this in a more automated way but my brain isn't very large so enjoy my stupid switch statement - if(100) - ourbar = "|▓▓▓▓▓▓▓▓▓▓|" - if(95 to 99) - ourbar = "|▓▓▓▓▓▓▓▓▓▒|" - if(90 to 94) - ourbar = "|▓▓▓▓▓▓▓▓▓░|" - if(85 to 89) - ourbar = "|▓▓▓▓▓▓▓▓▒░|" - if(80 to 84) - ourbar = "|▓▓▓▓▓▓▓▓░░|" - if(75 to 79) - ourbar = "|▓▓▓▓▓▓▓▒░░|" - if(70 to 74) - ourbar = "|▓▓▓▓▓▓▓░░░|" - if(65 to 69) - ourbar = "|▓▓▓▓▓▓▒░░░|" - if(60 to 64) - ourbar = "|▓▓▓▓▓▓░░░░|" - if(55 to 59) - ourbar = "|▓▓▓▓▓▒░░░░|" - if(50 to 54) - ourbar = "|▓▓▓▓▓░░░░░|" - if(45 to 49) - ourbar = "|▓▓▓▓▒░░░░░|" - if(40 to 44) - ourbar = "|▓▓▓▓░░░░░░|" - if(35 to 39) - ourbar = "|▓▓▓▒░░░░░░|" - if(30 to 34) - ourbar = "|▓▓▓░░░░░░░|" - if(25 to 29) - ourbar = "|▓▓▒░░░░░░░|" - if(20 to 24) - ourbar = "|▓▓░░░░░░░░|" - if(15 to 19) - ourbar = "|▓▒░░░░░░░░|" - if(10 to 14) - ourbar = "|▓░░░░░░░░░|" - if(5 to 9) - ourbar = "|▒░░░░░░░░░|" - if(0) - ourbar = "|░░░░░░░░░░|" - else - ourbar = "!░░░░░░░░░░!" - + ourbar = chat_progress_bar(ourpercent, FALSE) ourbar = "[ourbar] [which_var] - [src.name]" if(stat == UNCONSCIOUS) diff --git a/code/modules/vore/eating/living_vr.dm b/code/modules/vore/eating/living_vr.dm index d398fa3e40..f987730c8c 100644 --- a/code/modules/vore/eating/living_vr.dm +++ b/code/modules/vore/eating/living_vr.dm @@ -259,6 +259,7 @@ P.digest_pain = src.digest_pain P.stumble_vore = src.stumble_vore P.eating_privacy_global = src.eating_privacy_global + P.allow_mimicry = src.allow_mimicry P.nutrition_message_visible = src.nutrition_message_visible P.nutrition_messages = src.nutrition_messages @@ -312,6 +313,7 @@ food_vore = P.food_vore digest_pain = P.digest_pain eating_privacy_global = P.eating_privacy_global + allow_mimicry = P.allow_mimicry nutrition_message_visible = P.nutrition_message_visible nutrition_messages = P.nutrition_messages @@ -1212,6 +1214,7 @@ dispvoreprefs += "Can be stepped on/over: [step_mechanics_pref ? "Allowed" : "Disallowed"]
" dispvoreprefs += "Can be picked up: [pickup_pref ? "Allowed" : "Disallowed"]
" dispvoreprefs += "Global Vore Privacy is: [eating_privacy_global ? "Subtle" : "Loud"]
" + dispvoreprefs += "Allow Mimicry: [allow_mimicry ? "Yes" : "No"]
" user << browse("Vore prefs: [src]
[dispvoreprefs]
", "window=[name]mvp;size=300x400;can_resize=1;can_minimize=0") onclose(user, "[name]") return diff --git a/code/modules/vore/eating/mob_vr.dm b/code/modules/vore/eating/mob_vr.dm index a49de81205..5a3d89cd30 100644 --- a/code/modules/vore/eating/mob_vr.dm +++ b/code/modules/vore/eating/mob_vr.dm @@ -27,6 +27,7 @@ var/selective_preference = DM_DEFAULT // Preference for selective bellymode var/text_warnings = TRUE // Allows us to dismiss the text limit warning messages after viewing it once per round var/eating_privacy_global = FALSE // Makes eating attempt/success messages only reach for subtle range if true, overwritten by belly-specific var + var/allow_mimicry = TRUE // Allows mimicking their character var/allow_mind_transfer = FALSE //Allows ones mind to be taken over or swapped var/nutrition_message_visible = TRUE var/list/nutrition_messages = list( diff --git a/code/modules/vore/eating/vore_vr.dm b/code/modules/vore/eating/vore_vr.dm index 3a25510876..492389b267 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/permit_healbelly = TRUE var/noisy = FALSE var/eating_privacy_global = FALSE //Makes eating attempt/success messages only reach for subtle range if true, overwritten by belly-specific var + var/allow_mimicry = TRUE // These are 'modifier' prefs, do nothing on their own but pair with drop_prey/drop_pred settings. var/drop_vore = TRUE @@ -196,6 +197,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE weight_message_visible = json_from_file["weight_message_visible"] weight_messages = json_from_file["weight_messages"] eating_privacy_global = json_from_file["eating_privacy_global"] + allow_mimicry = json_from_file["allow_mimicry"] vore_sprite_color = json_from_file["vore_sprite_color"] allow_mind_transfer = json_from_file["allow_mind_transfer"] @@ -254,6 +256,8 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE weight_message_visible = TRUE if(isnull(eating_privacy_global)) eating_privacy_global = FALSE + if(isnull(allow_mimicry)) + allow_mimicry = TRUE if(isnull(nutrition_messages)) nutrition_messages = list( "They are starving! You can hear their stomach snarling from across the room!", @@ -329,6 +333,7 @@ V::::::V V::::::VO:::::::OOO:::::::ORR:::::R R:::::REE::::::EEEEEE "weight_message_visible" = weight_message_visible, "weight_messages" = weight_messages, "eating_privacy_global" = eating_privacy_global, + "allow_mimicry" = allow_mimicry, "vore_sprite_color" = vore_sprite_color, ) diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index 20332e2dc1..91d8ad6e7b 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -347,6 +347,7 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", "weight_message_visible" = host.weight_message_visible, "weight_messages" = host.weight_messages, "eating_privacy_global" = host.eating_privacy_global, + "allow_mimicry" = host.allow_mimicry, ) return data @@ -535,6 +536,12 @@ var/global/list/belly_colorable_only_fullscreens = list("a_synth_flesh_mono", host.eating_privacy_global = host.eating_privacy_global unsaved_changes = TRUE return TRUE + if("toggle_mimicry") + host.allow_mimicry = !host.allow_mimicry + if(host.client.prefs_vr) + host.client.prefs_vr.allow_mimicry = host.allow_mimicry + unsaved_changes = TRUE + return TRUE if("toggle_devour") host.devourable = !host.devourable if(host.client.prefs_vr) diff --git a/code/modules/vore/mouseray.dm b/code/modules/vore/mouseray.dm index 997f79d67a..b2d932f23c 100644 --- a/code/modules/vore/mouseray.dm +++ b/code/modules/vore/mouseray.dm @@ -249,6 +249,7 @@ new_mob.nutrition_message_visible = nutrition_message_visible new_mob.allow_spontaneous_tf = allow_spontaneous_tf new_mob.eating_privacy_global = eating_privacy_global + new_mob.allow_mimicry = allow_mimicry new_mob.text_warnings = text_warnings new_mob.allow_mind_transfer = allow_mind_transfer diff --git a/config/alienwhitelist.txt b/config/alienwhitelist.txt index 79be051a63..72481432c8 100644 --- a/config/alienwhitelist.txt +++ b/config/alienwhitelist.txt @@ -114,6 +114,7 @@ mewchild - Vox milu - Xenochimera mrsebbi - Xenochimera mullins111 - Black-Eyed Shadekin +murchant - Teppi natje - Xenochimera nerdass - Protean newyorks - Protean diff --git a/config/jobwhitelist.txt b/config/jobwhitelist.txt index 9c4290b7c6..df8628cbbc 100644 --- a/config/jobwhitelist.txt +++ b/config/jobwhitelist.txt @@ -13,6 +13,7 @@ h0lysquirr3l - clown joey4298 - mime lm40 - clown losstinspess - mime +murchant - mime pastelprincedan - mime pastelprincedan - clown radishfriend - clown diff --git a/dependencies.sh b/dependencies.sh new file mode 100644 index 0000000000..57e14e9c8c --- /dev/null +++ b/dependencies.sh @@ -0,0 +1,34 @@ +#!/bin/sh + +#Project dependencies file +#Final authority on what's required to fully build the project + +# byond version +export BYOND_MAJOR=515 +export BYOND_MINOR=1637 + +#rust_g git tag +export RUST_G_VERSION=3.3.0 + +#node version +export NODE_VERSION_LTS=20.13.0 +# compatiblility mode MUST work with windows 7 +export NODE_VERSION_COMPAT=20.2.0 + +# SpacemanDMM git tag +export SPACEMAN_DMM_VERSION=suite-1.8 + +# Python version for mapmerge and other tools +export PYTHON_VERSION=3.8.10 + +#dreamluau repo +export DREAMLUAU_REPO="tgstation/dreamluau" + +#dreamluau git tag +export DREAMLUAU_VERSION=0.1.2 + +#hypnagogic repo +export CUTTER_REPO=spacestation13/hypnagogic + +#hypnagogic git tag +export CUTTER_VERSION=v4.0.0 diff --git a/icons/inventory/accessory/item.dmi b/icons/inventory/accessory/item.dmi index b2f5303784..2782922a7a 100644 Binary files a/icons/inventory/accessory/item.dmi and b/icons/inventory/accessory/item.dmi differ diff --git a/icons/inventory/accessory/mob.dmi b/icons/inventory/accessory/mob.dmi index b2bd322770..587ec87128 100644 Binary files a/icons/inventory/accessory/mob.dmi and b/icons/inventory/accessory/mob.dmi differ diff --git a/icons/inventory/uniform/item_vr.dmi b/icons/inventory/uniform/item_vr.dmi index 6e0d72e90a..59176816e6 100644 Binary files a/icons/inventory/uniform/item_vr.dmi and b/icons/inventory/uniform/item_vr.dmi differ diff --git a/icons/inventory/uniform/mob_vr.dmi b/icons/inventory/uniform/mob_vr.dmi index 487a0d9e17..aad2c467f9 100644 Binary files a/icons/inventory/uniform/mob_vr.dmi and b/icons/inventory/uniform/mob_vr.dmi differ diff --git a/icons/mob/human.dmi b/icons/mob/human.dmi index 1fc987773e..22bfb58bd1 100644 Binary files a/icons/mob/human.dmi and b/icons/mob/human.dmi differ diff --git a/maps/expedition_vr/aerostat/aerostat_science_outpost.dmm b/maps/expedition_vr/aerostat/aerostat_science_outpost.dmm index 499219e3dd..5310c85abc 100644 --- a/maps/expedition_vr/aerostat/aerostat_science_outpost.dmm +++ b/maps/expedition_vr/aerostat/aerostat_science_outpost.dmm @@ -318,7 +318,6 @@ master_tag = "aerostat_n_n_airlock"; pixel_x = 24; pixel_y = 24; - tag = "aerostat_n_n_sensint" }, /turf/simulated/floor/tiled/techfloor, /area/offmap/aerostat/inside/airlock/north) @@ -924,7 +923,6 @@ master_tag = "aerostat_s_s_airlock"; pixel_x = -24; pixel_y = -24; - tag = "aerostat_s_s_sensint" }, /turf/simulated/floor/tiled/techfloor, /area/offmap/aerostat/inside/airlock/south) @@ -1969,7 +1967,6 @@ dir = 8; master_tag = "aerostat_n_e_airlock"; pixel_x = 32; - tag = "aerostat_n_e_sensint" }, /turf/simulated/floor/tiled/techfloor, /area/offmap/aerostat/inside/airlock/north) @@ -8673,7 +8670,6 @@ dir = 8; master_tag = "aerostat_s_e_airlock"; pixel_x = 24; - tag = "aerostat_s_e_sensint" }, /turf/simulated/floor/tiled/techfloor, /area/offmap/aerostat/inside/airlock/south) @@ -11336,7 +11332,6 @@ dir = 4; master_tag = "aerostat_n_w_airlock"; pixel_x = -32; - tag = "aerostat_n_w_sensint" }, /turf/simulated/floor/tiled/techfloor, /area/offmap/aerostat/inside/airlock/north) @@ -13479,7 +13474,6 @@ dir = 4; master_tag = "aerostat_s_w_airlock"; pixel_x = -24; - tag = "aerostat_s_w_sensint" }, /turf/simulated/floor/tiled/techfloor, /area/offmap/aerostat/inside/airlock/south) diff --git a/tgui/.gitattributes b/tgui/.gitattributes index ce30adb9e2..3103a46c24 100644 --- a/tgui/.gitattributes +++ b/tgui/.gitattributes @@ -13,7 +13,6 @@ *.md text eol=lf *.bat text eol=lf yarn.lock text eol=lf -bin/tgui text eol=lf ## Treat bundles as binary *.bundle.* binary diff --git a/tgui/README.md b/tgui/README.md index ad79758279..2bcb6d06af 100644 --- a/tgui/README.md +++ b/tgui/README.md @@ -4,21 +4,15 @@ tgui is a robust user interface framework of /tg/station. -tgui is very different from most UIs you will encounter in BYOND programming. -It is heavily reliant on Javascript and web technologies as opposed to DM. -If you are familiar with NanoUI (a library which can be found on almost -every other SS13 codebase), tgui should be fairly easy to pick up. +tgui is very different from most UIs you will encounter in BYOND programming. It is heavily reliant on Javascript and web technologies as opposed to DM. If you are familiar with NanoUI (a library which can be found on almost every other SS13 codebase), tgui should be fairly easy to pick up. ## Learn tgui -People come to tgui from different backgrounds and with different -learning styles. Whether you prefer a more theoretical or a practical -approach, we hope you’ll find this section helpful. +People come to tgui from different backgrounds and with different learning styles. Whether you prefer a more theoretical or a practical approach, we hope you’ll find this section helpful. ### Practical Tutorial -If you are completely new to frontend and prefer to **learn by doing**, -start with our [practical tutorial](docs/tutorial-and-examples.md). +If you are completely new to frontend and prefer to **learn by doing**, start with our [practical tutorial](docs/tutorial-and-examples.md). ### Guides @@ -26,9 +20,7 @@ This project uses React. Take your time to read the guide: - [React guide](https://react.dev/learn) -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). +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 @@ -39,97 +31,84 @@ to translate concepts between old and new tgui, read this ## Pre-requisites -You will need these programs to start developing in tgui: +If you are using the tooling provided in this repo, everything is included! Feel free to skip this step. -- [Node v16.13+](https://nodejs.org/en/download/) +However, if you want finer control over the installation or build process, you will need these: + +- [Node v20.2+](https://nodejs.org/en/download/) - **LTS** release is recommended instead of latest -- [Yarn v1.22.4+](https://yarnpkg.com/getting-started/install) (optional) + - **DO NOT install Chocolatey if Node installer asks you to!** +- [Yarn v4.1.1+](https://yarnpkg.com/getting-started/install) - You can run `npm install -g yarn` to install it. -- [Git Bash](https://git-scm.com/downloads) - or [MSys2](https://www.msys2.org/) (optional) - -**DO NOT install Chocolatey if Node installer asks you to!** ## Usage -**For Git Bash, MSys2, WSL, Linux or macOS users:** +**Via provided cmd scripts (Windows)**: -Change your directory to `tgui`. +- `bin/tgui-build` - Build tgui in production mode and run a full suite of code checks. +- `bin/tgui-dev` - Launch a development server. + - `bin/tgui-dev --reload` - Reload byond cache once. + - `bin/tgui-dev --debug` - Run server with debug logging enabled. + - `bin/tgui-dev --no-hot` - Disable hot module replacement (helps when doing development on IE8). +- `bin/tgui-sonar` - Analyze code with SonarQube. +- `bin/tgui-bench` - Run benchmarks. -Run `bin/tgui --install-git-hooks` to install merge drivers which will -assist you in conflict resolution when rebasing your branches. Only has -to be done once. +> To open a CMD or PowerShell window in any open folder, right click **while holding Shift** on any free space in the folder, then click on either `Open command window here` or `Open PowerShell window here`. -Run `bin/tgui` with any of the options listed below. +**Via Juke Build (cross-platform)**: -**For Windows CMD or PowerShell users:** +- `tools/build/build tgui` - Build tgui in production mode. +- `tools/build/build tgui-dev` - Build tgui in production mode. + - `tools/build/build tgui-dev --reload` - Reload byond cache once. + - `tools/build/build tgui-dev --debug` - Run server with debug logging enabled. + - `tools/build/build tgui-dev --no-hot` - Disable hot module replacement (helps when doing development on IE8). +- `tools/build/build tgui-lint` - Show (and auto-fix) problems with the code. +- `tools/build/build tgui-sonar` - Analyze code with SonarQube. +- `tools/build/build tgui-test` - Run unit and integration tests. +- `tools/build/build tgui-analyze` - Run a bundle analyzer. +- `tools/build/build tgui-bench` - Run benchmarks. +- `tools/build/build tgui-clean` - Clean up tgui folder. -If you haven't opened the console already, you can do that by holding -Shift and right clicking on the `tgui` folder, then pressing -either `Open command window here` or `Open PowerShell window here`. +> With Juke Build, you can run multiple targets together, e.g.: +> +> ``` +> tools/build/build tgui tgui-lint tgui-tsc tgui-test +> ``` -Run `.\bin\tgui.bat` with any of the options listed below. +**Via Yarn (cross-platform)**: -> If using PowerShell, you will receive errors if trying to run -> `.\bin\tgui.ps1`, because default Windows policy does not allow direct -> execution of PS1 scripts. Run `.\bin\tgui.bat` instead. +Run `yarn install` once to install tgui dependencies. -**Available commands:** +- `yarn tgui:build` - Build tgui in production mode. + - `yarn tgui:build [options]` - Build tgui with custom webpack options. +- `yarn tgui:dev` - Launch a development server. + - `yarn tgui:dev --reload` - Reload byond cache once. + - `yarn tgui:dev --debug` - Run server with debug logging enabled. + - `yarn tgui:dev --no-hot` - Disable hot module replacement (helps when doing development on IE8). +- `yarn tgui:lint` - Show (and auto-fix) problems with the code. +- `yarn tgui:sonar` - Analyze code with SonarQube. +- `yarn tgui:tsc` - Check code with TypeScript compiler. +- `yarn tgui:test` - Run unit and integration tests. +- `yarn tgui:analyze` - Run a bundle analyzer. +- `yarn tgui:bench` - Run benchmarks. +- `yarn tgfont:build` - Build icon fonts. +- `yarn tgui-polyfill:build` - Build polyfills. You need to run it when updating any of the static (numbered) polyfills. -- `bin/tgui` - (Recommended build option) Build the project in production mode. -- `bin/tgui --pretty` - Check/Prettify all files inside of the packages folder (Mainly Interfaces/Components). Build the project in production mode afterwards. -- `bin/tgui --dev` - Launch a development server. - - tgui development server provides you with incremental compilation, - hot module replacement and logging facilities in all running instances - of tgui. In short, this means that you will instantly see changes in the - game as you code it. Very useful, highly recommended. - - In order to use it, you should start the game server first, connect to it - and wait until the world has been properly loaded and you are no longer - in the lobby. Start tgui dev server, and once it has finished building, - press F5 on any tgui window. You'll know that it's hooked correctly if - you see a green bug icon in titlebar and data gets dumped to the console. - - `bin/tgui --dev --reload` - Reload byond cache once. - - `bin/tgui --dev --debug` - Run server with debug logging enabled. - - `bin/tgui --dev --no-hot` - Disable hot module replacement (helps when -doing development on IE8). -- `bin/tgui --lint` - Show problems with the code. -- `bin/tgui --fix` - Auto-fix problems with the code. -- `bin/tgui --test` - Run tests. -- `bin/tgui --analyze` - Run a bundle analyzer. -- `bin/tgui --clean` - Clean up project repo. -- `bin/tgui [webpack options]` - Build the project with custom webpack -options. +## Important Memo -**For virgins:** - -You can double-click these batch files to achieve the same thing: - -- `bin\tgui.bat` - (Recommended build option) Build the project in production mode. -- `bin\tgui-prettybuild.bat` - Prettify all the files inside the packages folder. Build the project in production mode afterwards. -- `bin\tgui-dev-server.bat` - Launch a development server. - -> Remember to always run a full build before submitting a PR. It creates -> a compressed javascript bundle which is then referenced from DM code. -> We prefer to keep it version controlled, so that people could build the -> game just by using Dream Maker. +Remember to always run a full build of tgui before submitting a PR, because it comes with the full suite of CI checks, and runs much faster on your computer than on GitHub servers. It will save you some time and possibly a few broken commits! Address the issues that are reported by the tooling as much as possible, because maintainers will beat you with a ruler and force you to address them anyway (unless it's a false positive or something unfixable). ## Troubleshooting **Development server is crashing** -Make sure path to your working directory does not contain spaces or special -unicode characters. If so, move codebase to a location which does not contain -spaces or unicode characters. +Make sure path to your working directory does not contain spaces, special unicode characters, exclamation marks or any other special symbols. If so, move codebase to a location which does not contain these characters. -This is a known issue with Yarn Berry, and fix is going to happen someday. +This is a known issue with Yarn (and some other tools, like Webpack), and fix is going to happen eventually. **Development server doesn't find my BYOND cache!** -This happens if your Documents folder in Windows has a custom location, for -example in `E:\Libraries\Documents`. Development server tries its best to find -this non-standard location (searches for a Windows Registry key), but it can -fail. You have to run the dev server with an additional environmental -variable, with a full path to BYOND cache. +This happens if your Documents folder in Windows has a custom location, for example in `E:\Libraries\Documents`. Development server tries its best to find this non-standard location (searches for a Windows Registry key), but it can fail. You have to run the dev server with an additional environmental variable, with a full path to BYOND cache. ``` BYOND_CACHE="E:/Libraries/Documents/BYOND/cache" @@ -139,14 +118,11 @@ BYOND_CACHE="E:/Libraries/Documents/BYOND/cache" > Example: `No template for dependency: PureExpressionDependency` -Webpack stores its cache on disk since tgui 4.3, and it is very sensitive -to build configuration. So if you update webpack, or share the same cache -directory between development and production build, it will start -hallucinating. +Webpack stores its cache on disk since tgui 4.3, and it is very sensitive to build configuration. So if you update webpack, or share the same cache directory between development and production build, it will start hallucinating. To fix this kind of problem, run `bin/tgui --clean` and try again. -## Developer Tools +## Dev Server Tools When developing with `tgui-dev-server`, you will have access to certain development only features. @@ -157,7 +133,7 @@ logs and time spent on rendering. Use this information to optimize your code, and try to keep re-renders below 16ms. **Kitchen Sink.** -Press `F12` to open the KitchenSink interface. This interface is a +Press `F12` or click the green bug to open the KitchenSink interface. This interface is a playground to test various tgui components. **Layout Debugger.** @@ -168,12 +144,13 @@ together, and can reveal certain layout bugs which are not normally visible. ## Browser Developer Tools To debug TGUI interfaces with browser-style developer tools, there exists a utility -that Microsoft bundles with Windows to debug any Internet Explorer/Trident-using interface, +that Microsoft bundles with Windows called IEChooser/F12 to debug any Internet Explorer/Trident-using interface, which BYOND uses. This provides invaluable tools such as a local console, a DOM viewer, an interactive debugger, and more. -The 64-bit version that we use is located at `%windir%\SysWOW64\F12\IEChooser.exe`. +You can access the `IEChooser.exe` by pressing Win + R, then typing `f12`, then pressing enter. +To manually go there: 64-bit version that we use is located at `%windir%\SysWOW64\F12\IEChooser.exe`. There's also a 32-bit one in `system32\`. Simply launch the application after you've opened a TGUI window, and choose the .html name. @@ -185,42 +162,24 @@ so you'll need to restart it if it disconnects from the window. ## Project Structure - `/packages` - Each folder here represents a self-contained Node module. -- `/packages/common` - Helper functions +- `/packages/common` - Helper functions that are used throughout all packages. - `/packages/tgui/index.js` - Application entry point. - `/packages/tgui/components` - Basic UI building blocks. - `/packages/tgui/interfaces` - Actual in-game interfaces. -Interface takes data via the `state` prop and outputs an html-like stucture, -which you can build using existing UI components. -- `/packages/tgui/layouts` - Root level UI components, that affect the final -look and feel of the browser window. They usually hold various window -elements, like the titlebar and resize handlers, and control the UI theme. -- `/packages/tgui/routes.js` - This is where tgui decides which interface to -pull and render. -- `/packages/tgui/layout.js` - A root-level component, holding the -window elements, like the titlebar, buttons, resize handlers. Calls -`routes.js` to decide which component to render. +- `/packages/tgui/layouts` - Root level UI components, that affect the final look and feel of the browser window. These hold various window elements, like the titlebar and resize handlers, and control the UI theme. +- `/packages/tgui/routes.js` - This is where tgui decides which interface to pull and render. - `/packages/tgui/styles/main.scss` - CSS entry point. -- `/packages/tgui/styles/functions.scss` - Useful SASS functions. -Stuff like `lighten`, `darken`, `luminance` are defined here. -- `/packages/tgui/styles/atomic` - Atomic CSS classes. -These are very simple, tiny, reusable CSS classes which you can use and -combine to change appearance of your elements. Keep them small. -- `/packages/tgui/styles/components` - CSS classes which are used -in UI components. These stylesheets closely follow the -[BEM](https://en.bem.info/methodology/) methodology. -- `/packages/tgui/styles/interfaces` - Custom stylesheets for your interfaces. -Add stylesheets here if you really need a fine control over your UI styles. +- `/packages/tgui/styles/functions.scss` - Useful SASS functions. Stuff like `lighten`, `darken`, `luminance` are defined here. +- `/packages/tgui/styles/atomic` - Atomic CSS classes. These are very simple, tiny, reusable CSS classes which you can use and combine to change appearance of your elements. Keep them small. +- `/packages/tgui/styles/components` - CSS classes which are used in UI components. These stylesheets closely follow the [BEM](https://en.bem.info/methodology/) methodology. +- `/packages/tgui/styles/interfaces` - Custom stylesheets for your interfaces. Add stylesheets here if you really need a fine control over your UI styles. - `/packages/tgui/styles/layouts` - Layout-related 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. +- `/packages/tgui/styles/themes` - Contains themes that you can use in tgui. Each theme must be registered in `/packages/tgui/index.js` file. ## License -Source code is covered by /tg/station's parent license - **AGPL-3.0** -(see the main [README](../README.md)), unless otherwise indicated. +Source code is covered by /tg/station's parent license - **AGPL-3.0** (see the main [README](../README.md)), unless otherwise indicated. -Some files are annotated with a copyright header, which explicitly states -the copyright holder and license of the file. Most of the core tgui -source code is available under the **MIT** license. +Some files are annotated with a copyright header, which explicitly states the copyright holder and license of the file. Most of the core tgui source code is available under the **MIT** license. The Authors retain all copyright to their respective work here submitted. diff --git a/tgui/bin/tgui b/tgui/bin/tgui deleted file mode 100644 index 256f0e579e..0000000000 --- a/tgui/bin/tgui +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env bash -## Copyright (c) 2020 Aleksej Komarov -## SPDX-License-Identifier: MIT - -set -e -shopt -s globstar -shopt -s expand_aliases - -## Initial set-up -## -------------------------------------------------------- - -## Returns an absolute path to file -alias tgui-realpath="readlink -f" - -## Fallbacks for GNU readlink -## Detecting GNU coreutils http://stackoverflow.com/a/8748344/319952 -if ! readlink --version >/dev/null 2>&1; then - if hash greadlink 2>/dev/null; then - alias tgui-realpath="greadlink -f" - else - alias tgui-realpath="perl -MCwd -le 'print Cwd::abs_path(shift)'" - fi -fi - -## Find a canonical path to project root -base_dir="$(dirname "$(tgui-realpath "${0}")")/.." -base_dir="$(tgui-realpath "${base_dir}")" - -## Make use of nvm if it exists -if [[ -e "${HOME}/.nvm/nvm.sh" ]]; then - source "${HOME}/.nvm/nvm.sh" -fi - -## Fall back to running Yarn from the repo -if ! hash yarn 2>/dev/null; then - yarn_releases=("${base_dir}"/.yarn/releases/yarn-*.cjs) - yarn_release="${yarn_releases[0]}" - yarn() { - node "${yarn_release}" "${@}" - } -fi - - -## Functions -## -------------------------------------------------------- - -## Installs node modules -task-install() { - cd "${base_dir}" - yarn install -} - -## Runs webpack -task-webpack() { - cd "${base_dir}" - yarn run webpack-cli "${@}" -} - -## Runs a development server -task-dev-server() { - cd "${base_dir}" - yarn node --experimental-modules packages/tgui-dev-server/index.js "${@}" -} - -## Run a linter through all packages -task-lint() { - cd "${base_dir}" - yarn run tsc - echo "tgui: type check passed" - yarn run eslint packages --ext .js,.cjs,.ts,.tsx "${@}" - echo "tgui: eslint check passed" -} - -task-test() { - cd "${base_dir}" - yarn run jest -} - -## Mr. Proper -task-clean() { - cd "${base_dir}" - ## Build artifacts - rm -rf public/.tmp - rm -f public/*.map - rm -f public/*.chunk.* - rm -f public/*.bundle.* - rm -f public/*.hot-update.* - ## Yarn artifacts - rm -rf .yarn/cache - rm -rf .yarn/unplugged - rm -rf .yarn/webpack - rm -f .yarn/build-state.yml - rm -f .yarn/install-state.gz - rm -f .yarn/install-target - rm -f .pnp.* - ## NPM artifacts - rm -rf **/node_modules - rm -f **/package-lock.json -} - -## Validates current build against the build stored in git -## VOREStation Addition Start -task-validate-build() { - cd "${base_dir}" - local diff - diff="$(git diff packages/tgui/public/tgui.bundle.*)" - if [[ -n ${diff} ]]; then - echo "Error: our build differs from the build committed into git." - echo "Please rebuild tgui." - exit 1 - fi - echo "tgui: build is ok" -} -## VOREStation Addition End - -## Installs merge drivers and git hooks -task-install-git-hooks() { - cd "${base_dir}" - local git_root - local git_base_dir - git_root="$(git rev-parse --show-toplevel)" - git_base_dir="${base_dir/${git_root}/.}" - git config --replace-all merge.tgui-merge-bundle.driver \ - "${git_base_dir}/bin/tgui --merge=bundle %O %A %B %L" - echo "tgui: Merge drivers have been successfully installed!" -} - -## Bundle merge driver -task-merge-bundle() { - local file_ancestor="${1}" - local file_current="${2}" - local file_other="${3}" - local conflict_marker_size="${4}" - echo "tgui: Discarding a local tgui build" - ## Do nothing (file_current will be merged and is what we want to keep). - exit 0 -} - - -## Main -## -------------------------------------------------------- - -if [[ ${1} == "--merge"* ]]; then - if [[ ${1} == "--merge=bundle" ]]; then - shift 1 - task-merge-bundle "${@}" - fi - echo "Unknown merge strategy: ${1}" - exit 1 -fi - -if [[ ${1} == "--install-git-hooks" ]]; then - shift 1 - task-install-git-hooks - exit 0 -fi - -if [[ ${1} == "--clean" ]]; then - task-clean - exit 0 -fi - -if [[ ${1} == "--dev" ]]; then - shift - task-install - task-dev-server "${@}" - exit 0 -fi - -## VOREStation Addition Start -## Continuous integration scenario -if [[ ${1} == "--ci" ]]; then - task-clean - task-install - task-lint - task-test - task-webpack --mode=production - task-validate-build - exit 0 -fi -## VOREStation Addition End - -if [[ ${1} == '--lint' ]]; then - shift 1 - task-install - task-lint "${@}" - exit 0 -fi - -if [[ ${1} == '--lint-harder' ]]; then - shift 1 - task-install - task-lint -c .eslintrc-harder.yml "${@}" - exit 0 -fi - -if [[ ${1} == '--fix' ]]; then - shift 1 - task-install - task-lint --fix "${@}" - exit 0 -fi - -if [[ ${1} == '--test' ]]; then - shift 1 - task-install - task-test "${@}" - exit 0 -fi - -## Analyze the bundle -if [[ ${1} == '--analyze' ]]; then - task-install - task-webpack --mode=production --analyze - exit 0 -fi - -## Make a production webpack build -if [[ ${1} == '--build' ]]; then - task-install - task-webpack --mode=production - exit 0 -fi - -## Make a production webpack build + Run eslint -if [[ -z ${1} ]]; then - task-install - task-lint --fix - task-webpack --mode=production - exit 0 -fi - -## Run webpack with custom flags -task-install -task-webpack "${@}" diff --git a/tgui/bin/tgui-bench.bat b/tgui/bin/tgui-bench.bat deleted file mode 100644 index da22a7b2ae..0000000000 --- a/tgui/bin/tgui-bench.bat +++ /dev/null @@ -1,9 +0,0 @@ -@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-dev-server.bat b/tgui/bin/tgui-dev-server.bat deleted file mode 100644 index 21b2ea4e96..0000000000 --- a/tgui/bin/tgui-dev-server.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -rem Copyright (c) 2020 Aleksej Komarov -rem SPDX-License-Identifier: MIT -call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --dev %* -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-prettybuild.bat b/tgui/bin/tgui-prettybuild.bat deleted file mode 100644 index fe827e1657..0000000000 --- a/tgui/bin/tgui-prettybuild.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -rem Copyright (c) 2020 Aleksej Komarov -rem SPDX-License-Identifier: MIT -call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" --pretty %* -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.bat b/tgui/bin/tgui.bat deleted file mode 100644 index 39aa279dca..0000000000 --- a/tgui/bin/tgui.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo off -rem Copyright (c) 2020 Aleksej Komarov -rem SPDX-License-Identifier: MIT -call powershell.exe -NoLogo -ExecutionPolicy Bypass -File "%~dp0\tgui_.ps1" %* -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 deleted file mode 100644 index eb9c7a1a97..0000000000 --- a/tgui/bin/tgui_.ps1 +++ /dev/null @@ -1,177 +0,0 @@ -## Copyright (c) 2020 Aleksej Komarov -## SPDX-License-Identifier: MIT - -## Initial set-up -## -------------------------------------------------------- - -## Enable strict mode and stop of first cmdlet error -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" -$PSDefaultParameterValues['*:ErrorAction'] = 'Stop' - -## Validates exit code of external commands -function Throw-On-Native-Failure { - if (-not $?) { - exit 1 - } -} - -## Normalize current directory -$basedir = Split-Path $MyInvocation.MyCommand.Path -$basedir = Resolve-Path "$($basedir)\.." -Set-Location $basedir -[Environment]::CurrentDirectory = $basedir - - -## Functions -## -------------------------------------------------------- - -function yarn { - $YarnRelease = Get-ChildItem -Filter ".yarn\releases\yarn-*.cjs" | Select-Object -First 1 - node ".yarn\releases\$YarnRelease" @Args - Throw-On-Native-Failure -} - -function Remove-Quiet { - Remove-Item -ErrorAction SilentlyContinue @Args -} - -function task-install { - yarn install -} - -## Runs webpack -function task-webpack { - yarn run webpack-cli @Args -} - -## Runs a development server -function task-dev-server { - yarn node --experimental-modules "packages/tgui-dev-server/index.js" @Args -} - -function task-bench { - yarn tgui:bench @Args -} - -function task-prettier { - yarn tgui:prettier @Args -} - -function task-prettify { - yarn prettier --write packages @Args -} - -## Run a linter through all packages -function task-lint { - yarn run tsc - Write-Output "tgui: type check passed" - yarn run eslint packages --ext ".js,.cjs,.ts,.tsx" @Args - Write-Output "tgui: eslint check passed" -} - -function task-test { - yarn run jest -} - -## Mr. Proper -function task-clean { - ## Build artifacts - Remove-Quiet -Recurse -Force "public\.tmp" - Remove-Quiet -Force "public\*.map" - Remove-Quiet -Force "public\*.hot-update.*" - ## Yarn artifacts - Remove-Quiet -Recurse -Force ".yarn\cache" - Remove-Quiet -Recurse -Force ".yarn\unplugged" - Remove-Quiet -Recurse -Force ".yarn\webpack" - Remove-Quiet -Force ".yarn\build-state.yml" - Remove-Quiet -Force ".yarn\install-state.gz" - Remove-Quiet -Force ".yarn\install-target" - 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" -} - - -## Main -## -------------------------------------------------------- - -if ($Args.Length -gt 0) { - if ($Args[0] -eq "--clean") { - task-clean - exit 0 - } - - if ($Args[0] -eq "--dev") { - $Rest = $Args | Select-Object -Skip 1 - task-install - task-dev-server @Rest - exit 0 - } - - if ($Args[0] -eq "--lint") { - $Rest = $Args | Select-Object -Skip 1 - task-install - task-lint @Rest - exit 0 - } - - if ($Args[0] -eq "--lint-harder") { - $Rest = $Args | Select-Object -Skip 1 - task-install - task-lint -c ".eslintrc-harder.yml" @Rest - exit 0 - } - - if ($Args[0] -eq "--fix") { - $Rest = $Args | Select-Object -Skip 1 - task-install - task-lint --fix @Rest - exit 0 - } - - if ($Args[0] -eq "--test") { - $Rest = $Args | Select-Object -Skip 1 - task-install - task-test @Rest - exit 0 - } - - if ($Args[0] -eq "--pretty") { - $Rest = $Args | Select-Object -Skip 1 - task-install - task-prettify - task-prettier - task-lint - task-webpack --mode=production - exit 0 - } - - ## Analyze the bundle - if ($Args[0] -eq "--analyze") { - task-install - 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 -if ($Args.Length -eq 0) { - task-install - task-prettier - task-lint - task-webpack --mode=production - exit 0 -} - -## Run webpack with custom flags -task-install -task-webpack @Args diff --git a/tgui/packages/tgfont/package.json b/tgui/packages/tgfont/package.json index 5e5a3f060c..56bb38bf68 100644 --- a/tgui/packages/tgfont/package.json +++ b/tgui/packages/tgfont/package.json @@ -3,7 +3,7 @@ "name": "tgfont", "version": "1.0.0", "scripts": { - "build": "node mkdist.cjs && fantasticon --config config.cjs" + "tgfont:build": "node mkdist.cjs && fantasticon --config config.cjs" }, "dependencies": { "fantasticon": "^1.2.3" diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx index 4450c6009c..8423630acd 100644 --- a/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx +++ b/tgui/packages/tgui/interfaces/ModifyRobot/ModifyRobotTabs/ModifyRobotComponent.tsx @@ -1,9 +1,11 @@ +import { toFixed } from 'common/math'; import { capitalize } from 'common/string'; import { useState } from 'react'; import { useBackend } from 'tgui/backend'; import { Box, Button, + Collapsible, Divider, Dropdown, Flex, @@ -11,24 +13,26 @@ import { Image, Input, Section, + Slider, Stack, } from 'tgui/components'; import { NoSpriteWarning } from '../components'; import { prepareSearch } from '../functions'; -import { Cell, Component, Target } from '../types'; +import { Cell, Component, InstalledCell, Target } from '../types'; export const ModifyRobotComponent = (props: { target: Target; - cell: string | null; + cell: InstalledCell; cells: Record; }) => { + const { act } = useBackend(); const { target, cell, cells } = props; const [searchComponentReplaceText, setSearchComponentReplaceText] = useState(''); const [searchComponentRemoveText, setSearchComponentRemoveText] = useState(''); - const [selectedCell, setSelectedCell] = useState(cell || ''); + const [selectedCell, setSelectedCell] = useState(cell.name || ''); const cell_options = Object.keys(cells) as Array; return ( @@ -46,7 +50,7 @@ export const ModifyRobotComponent = (props: { buttonIcon="arrows-spin" celltype={cells[selectedCell]?.path} selected_cell={selectedCell} - cell={cell || undefined} + cell={cell.name || undefined} /> @@ -65,13 +69,32 @@ export const ModifyRobotComponent = (props: {
Current cell:{' '} - {cell ? ( - capitalize(cell) + {cell.name ? ( + capitalize(cell.name) ) : ( No cell installed! )} + toFixed(value, 2)} + disabled={!cell.charge} + minValue={0} + maxValue={100} + value={((cell.charge || 0) / (cell.maxcharge || 1)) * 100} + onChange={(e, value) => + act('adjust_cell_charge', { + charge: (value / 100) * (cell.maxcharge || 0), + }) + } + > + + Current charge + + {cell.charge} + +
+
+ + {target.components.map((component, i) => { + return ( + + + act('adjust_brute', { + component: component.ref, + damage: value, + }) + } + > + + Brute damage + + {component.brute_damage} + + + + act('adjust_electronics', { + component: component.ref, + damage: value, + }) + } + > + + Electronics damage + + + {component.electronics_damage} + + + + + ); + })} + +
diff --git a/tgui/packages/tgui/interfaces/ModifyRobot/types.ts b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts index 02b840948b..d5bb7aeadf 100644 --- a/tgui/packages/tgui/interfaces/ModifyRobot/types.ts +++ b/tgui/packages/tgui/interfaces/ModifyRobot/types.ts @@ -5,7 +5,7 @@ export type Data = { target: Target | null; all_robots: DropdownEntry[]; model_options: string[] | null; - cell: string | null; + cell: InstalledCell; cell_options: Record; id_icon: string; access_options: Access[] | undefined; @@ -98,6 +98,12 @@ export type PKA = { max_capacity: number; }; +export type InstalledCell = { + name: string | null; + charge: number | null; + maxcharge: number | null; +}; + export type Cell = { path: string; charge: number; diff --git a/tgui/packages/tgui/interfaces/Pda/pda_screens/pda_main_menu.tsx b/tgui/packages/tgui/interfaces/Pda/pda_screens/pda_main_menu.tsx index f28d9dc497..804d3f31ba 100644 --- a/tgui/packages/tgui/interfaces/Pda/pda_screens/pda_main_menu.tsx +++ b/tgui/packages/tgui/interfaces/Pda/pda_screens/pda_main_menu.tsx @@ -1,6 +1,7 @@ import { BooleanLike } from 'common/react'; +import { useState } from 'react'; import { useBackend } from 'tgui/backend'; -import { Box, Button, LabeledList, Section } from 'tgui/components'; +import { Box, Button, Icon, LabeledList, Section } from 'tgui/components'; type Data = { owner: string; @@ -19,13 +20,43 @@ type category = { ref: string; }; +const specialIconColors = { + 'Enable Flashlight': '#0f0', + 'Disable Flashlight': '#f00', +}; + export const pda_main_menu = (props) => { const { act, data } = useBackend(); + const [showTransition, setShowTransition] = useState(''); + + const startProgram = (program: category) => { + if ( + program.name.startsWith('Enable') || + program.name.startsWith('Disable') + ) { + // Special case, instant + act('StartProgram', { program: program.ref }); + return; + } + + setShowTransition(program.icon); + + setTimeout(() => { + setShowTransition(''); + act('StartProgram', { program: program.ref }); + }, 200); + }; + const { owner, ownjob, idInserted, categories, pai, notifying, apps } = data; return ( <> + {showTransition && ( + + + + )} @@ -57,8 +88,13 @@ export const pda_main_menu = (props) => { key={app.ref} icon={app.ref in notifying ? app.notify_icon : app.icon} iconSpin={app.ref in notifying} + iconColor={ + app.name in specialIconColors + ? specialIconColors[app.name] + : null + } color={app.ref in notifying ? 'red' : 'transparent'} - onClick={() => act('StartProgram', { program: app.ref })} + onClick={() => startProgram(app)} > {app.name} diff --git a/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferences.tsx b/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferences.tsx index e6a44e74b0..0f10b6e0e3 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferences.tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferences.tsx @@ -41,6 +41,7 @@ export const VoreUserPreferences = (props: { nutrition_message_visible, weight_message_visible, eating_privacy_global, + allow_mimicry, } = prefs; const preferences = { @@ -425,6 +426,19 @@ export const VoreUserPreferences = (props: { disabled: 'Global Vore Privacy: Loud', }, }, + allow_mimicry: { + action: 'toggle_mimicry', + test: allow_mimicry, + tooltip: { + main: 'Allows some creatures to mimick your apperance.', + enable: ' Click here to allow mimicry.', + disable: ' Click here to forbid mimicry.', + }, + content: { + enabled: 'Allow Mimicry: Yes', + disabled: 'Allow Mimicry: No', + }, + }, }; return ( diff --git a/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesMechanical .tsx b/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesMechanical .tsx index 8433d9dab9..51957ada93 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesMechanical .tsx +++ b/tgui/packages/tgui/interfaces/VorePanel/VoreUserPreferencesTabs/VoreUserPreferencesMechanical .tsx @@ -118,6 +118,9 @@ export const VoreUserPreferencesMechanical = (props: { + + + ); diff --git a/tgui/packages/tgui/interfaces/VorePanel/types.ts b/tgui/packages/tgui/interfaces/VorePanel/types.ts index 508d0fb6f0..0ae4e710e5 100644 --- a/tgui/packages/tgui/interfaces/VorePanel/types.ts +++ b/tgui/packages/tgui/interfaces/VorePanel/types.ts @@ -172,6 +172,7 @@ export type prefData = { weight_message_visible: BooleanLike; weight_messages: string[]; eating_privacy_global: BooleanLike; + allow_mimicry: BooleanLike; vore_sprite_color: { stomach: string; 'taur belly': string }; vore_sprite_multiply: { stomach: BooleanLike; 'taur belly': BooleanLike }; }; @@ -203,6 +204,7 @@ export type localPrefs = { examine_nutrition: preferenceData; examine_weight: preferenceData; eating_privacy_global: preferenceData; + allow_mimicry: preferenceData; }; export type preferenceData = { diff --git a/tgui/packages/tgui/styles/interfaces/Pda.scss b/tgui/packages/tgui/styles/interfaces/Pda.scss new file mode 100644 index 0000000000..39ff88c3fa --- /dev/null +++ b/tgui/packages/tgui/styles/interfaces/Pda.scss @@ -0,0 +1,37 @@ +.Pda__Transition { + display: flex; + justify-content: center; + align-items: center; + // Fill positioned parent + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1; + // Dim + animation: dim 0.4s forwards; +} + +.Pda__Transition > i { + animation: center-scale 0.4s forwards; + transform-origin: center center; +} + +@keyframes dim { + from { + background: rgba(0, 0, 0, 0); + } + to { + background: rgba(0, 0, 0, 0.5); + } +} + +@keyframes center-scale { + from { + transform: scale(1); + } + to { + transform: scale(4); + } +} diff --git a/tgui/packages/tgui/styles/main.scss b/tgui/packages/tgui/styles/main.scss index 3e2b99c02d..4f115f75d9 100644 --- a/tgui/packages/tgui/styles/main.scss +++ b/tgui/packages/tgui/styles/main.scss @@ -58,6 +58,7 @@ @include meta.load-css('./interfaces/ExperimentConfigure.scss'); @include meta.load-css('./interfaces/NuclearBomb.scss'); @include meta.load-css('./interfaces/Paper.scss'); +@include meta.load-css('./interfaces/Pda.scss'); @include meta.load-css('./interfaces/Roulette.scss'); @include meta.load-css('./interfaces/Safe.scss'); @include meta.load-css('./interfaces/TachyonArray.scss'); diff --git a/tgui/public/tgui-panel.bundle.js b/tgui/public/tgui-panel.bundle.js index 705a833353..0247b0147d 100644 --- a/tgui/public/tgui-panel.bundle.js +++ b/tgui/public/tgui-panel.bundle.js @@ -6,7 +6,7 @@ * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */function n(e,r){return r!=null&&typeof Symbol!="undefined"&&r[Symbol.hasInstance]?!!r[Symbol.hasInstance](e):e instanceof r}function o(e){"@swc/helpers - typeof";return e&&typeof Symbol!="undefined"&&e.constructor===Symbol?"symbol":typeof e}var i=t(61358),s=t(20686);function u(e){for(var r="https://reactjs.org/docs/error-decoder.html?invariant="+e,a=1;ar}return!1}function j(e,r,a,c,T,M,$){this.acceptsBooleans=r===2||r===3||r===4,this.attributeName=c,this.attributeNamespace=T,this.mustUseProperty=a,this.propertyName=e,this.type=r,this.sanitizeURL=M,this.removeEmptyString=$}var B={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){B[e]=new j(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var r=e[0];B[r]=new j(r,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){B[e]=new j(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){B[e]=new j(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){B[e]=new j(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){B[e]=new j(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){B[e]=new j(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){B[e]=new j(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){B[e]=new j(e,5,!1,e.toLowerCase(),null,!1,!1)});var F=/[\-:]([a-z])/g;function P(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var r=e.replace(F,P);B[r]=new j(r,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var r=e.replace(F,P);B[r]=new j(r,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var r=e.replace(F,P);B[r]=new j(r,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){B[e]=new j(e,1,!1,e.toLowerCase(),null,!1,!1)}),B.xlinkHref=new j("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){B[e]=new j(e,1,!1,e.toLowerCase(),null,!0,!0)});function b(e,r,a,c){var T=B.hasOwnProperty(r)?B[r]:null;(T!==null?T.type!==0:c||!(2q||T[$]!==M[q]){var mt="\n"+T[$].replace(" at new "," at ");return e.displayName&&mt.includes("")&&(mt=mt.replace("",e.displayName)),mt}while(1<=$&&0<=q);break}}}finally{ht=!1,Error.prepareStackTrace=a}return(e=e?e.displayName||e.name:"")?rt(e):""}function bt(e){switch(e.tag){case 5:return rt(e.type);case 16:return rt("Lazy");case 13:return rt("Suspense");case 19:return rt("SuspenseList");case 0:case 2:case 15:return e=vt(e.type,!1),e;case 11:return e=vt(e.type.render,!1),e;case 1:return e=vt(e.type,!0),e;default:return""}}function gt(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case K:return"Fragment";case W:return"Portal";case J:return"Profiler";case k:return"StrictMode";case at:return"Suspense";case et:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case X:return(e.displayName||"Context")+".Consumer";case H:return(e._context.displayName||"Context")+".Provider";case Y:var r=e.render;return e=e.displayName,e||(e=r.displayName||r.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case it:return r=e.displayName||null,r!==null?r:gt(e.type)||"Memo";case z:r=e._payload,e=e._init;try{return gt(e(r))}catch(a){}}return null}function pt(e){var r=e.type;switch(e.tag){case 24:return"Cache";case 9:return(r.displayName||"Context")+".Consumer";case 10:return(r._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=r.render,e=e.displayName||e.name||"",r.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return r;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return gt(r);case 8:return r===k?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof r=="function")return r.displayName||r.name||null;if(typeof r=="string")return r}return null}function Ut(e){switch(typeof e=="undefined"?"undefined":o(e)){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function xe(e){var r=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(r==="checkbox"||r==="radio")}function Se(e){var r=xe(e)?"checked":"value",a=Object.getOwnPropertyDescriptor(e.constructor.prototype,r),c=""+e[r];if(!e.hasOwnProperty(r)&&typeof a!="undefined"&&typeof a.get=="function"&&typeof a.set=="function"){var T=a.get,M=a.set;return Object.defineProperty(e,r,{configurable:!0,get:function(){return T.call(this)},set:function(q){c=""+q,M.call(this,q)}}),Object.defineProperty(e,r,{enumerable:a.enumerable}),{getValue:function(){return c},setValue:function(q){c=""+q},stopTracking:function(){e._valueTracker=null,delete e[r]}}}}function ee(e){e._valueTracker||(e._valueTracker=Se(e))}function jt(e){if(!e)return!1;var r=e._valueTracker;if(!r)return!0;var a=r.getValue(),c="";return e&&(c=xe(e)?e.checked?"true":"false":e.value),e=c,e!==a?(r.setValue(e),!0):!1}function Ft(e){if(e=e||(typeof document!="undefined"?document:void 0),typeof e=="undefined")return null;try{return e.activeElement||e.body}catch(r){return e.body}}function Gt(e,r){var a=r.checked;return Q({},r,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:a!=null?a:e._wrapperState.initialChecked})}function Ht(e,r){var a=r.defaultValue==null?"":r.defaultValue,c=r.checked!=null?r.checked:r.defaultChecked;a=Ut(r.value!=null?r.value:a),e._wrapperState={initialChecked:c,initialValue:a,controlled:r.type==="checkbox"||r.type==="radio"?r.checked!=null:r.value!=null}}function ve(e,r){r=r.checked,r!=null&&b(e,"checked",r,!1)}function he(e,r){ve(e,r);var a=Ut(r.value),c=r.type;if(a!=null)c==="number"?(a===0&&e.value===""||e.value!=a)&&(e.value=""+a):e.value!==""+a&&(e.value=""+a);else if(c==="submit"||c==="reset"){e.removeAttribute("value");return}r.hasOwnProperty("value")?zt(e,r.type,a):r.hasOwnProperty("defaultValue")&&zt(e,r.type,Ut(r.defaultValue)),r.checked==null&&r.defaultChecked!=null&&(e.defaultChecked=!!r.defaultChecked)}function ie(e,r,a){if(r.hasOwnProperty("value")||r.hasOwnProperty("defaultValue")){var c=r.type;if(!(c!=="submit"&&c!=="reset"||r.value!==void 0&&r.value!==null))return;r=""+e._wrapperState.initialValue,a||r===e.value||(e.value=r),e.defaultValue=r}a=e.name,a!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,a!==""&&(e.name=a)}function zt(e,r,a){(r!=="number"||Ft(e.ownerDocument)!==e)&&(a==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+a&&(e.defaultValue=""+a))}var Vt=Array.isArray;function wt(e,r,a,c){if(e=e.options,r){r={};for(var T=0;T"+r.valueOf().toString()+"",r=Oe.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;r.firstChild;)e.appendChild(r.firstChild)}});function de(e,r){if(r){var a=e.firstChild;if(a&&a===e.lastChild&&a.nodeType===3){a.nodeValue=r;return}}e.textContent=r}var Et={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Rt=["Webkit","ms","Moz","O"];Object.keys(Et).forEach(function(e){Rt.forEach(function(r){r=r+e.charAt(0).toUpperCase()+e.substring(1),Et[r]=Et[e]})});function te(e,r,a){return r==null||typeof r=="boolean"||r===""?"":a||typeof r!="number"||r===0||Et.hasOwnProperty(e)&&Et[e]?(""+r).trim():r+"px"}function Ie(e,r){e=e.style;for(var a in r)if(r.hasOwnProperty(a)){var c=a.indexOf("--")===0,T=te(a,r[a],c);a==="float"&&(a="cssFloat"),c?e.setProperty(a,T):e[a]=T}}var Ke=Q({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function Fe(e,r){if(r){if(Ke[e]&&(r.children!=null||r.dangerouslySetInnerHTML!=null))throw Error(u(137,e));if(r.dangerouslySetInnerHTML!=null){if(r.children!=null)throw Error(u(60));if(typeof r.dangerouslySetInnerHTML!="object"||!("__html"in r.dangerouslySetInnerHTML))throw Error(u(61))}if(r.style!=null&&typeof r.style!="object")throw Error(u(62))}}function ir(e,r){if(e.indexOf("-")===-1)return typeof r.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var ar=null;function dr(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var nt=null,St=null,ut=null;function xt(e){if(e=Ma(e)){if(typeof nt!="function")throw Error(u(280));var r=e.stateNode;r&&(r=ja(r),nt(e.stateNode,e.type,r))}}function Pt(e){St?ut?ut.push(e):ut=[e]:St=e}function Kt(){if(St){var e=St,r=ut;if(ut=St=null,xt(e),r)for(e=0;e>>=0,e===0?32:31-(Hr(e)/En|0)|0}var sn=64,wr=4194304;function hn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Gr(e,r){var a=e.pendingLanes;if(a===0)return 0;var c=0,T=e.suspendedLanes,M=e.pingedLanes,$=a&268435455;if($!==0){var q=$&~T;q!==0?c=hn(q):(M&=$,M!==0&&(c=hn(M)))}else $=a&~T,$!==0?c=hn($):M!==0&&(c=hn(M));if(c===0)return 0;if(r!==0&&r!==c&&!(r&T)&&(T=c&-c,M=r&-r,T>=M||T===16&&(M&4194240)!==0))return r;if(c&4&&(c|=a&16),r=e.entangledLanes,r!==0)for(e=e.entanglements,r&=c;0a;a++)r.push(e);return r}function kr(e,r,a){e.pendingLanes|=r,r!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,r=31-Br(r),e[r]=a}function To(e,r){var a=e.pendingLanes&~r;e.pendingLanes=r,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=r,e.mutableReadLanes&=r,e.entangledLanes&=r,r=e.entanglements;var c=e.eventTimes;for(e=e.expirationTimes;0=_a),Pi=" ",Ci=!1;function ms(e,r){switch(e){case"keyup":return mu.indexOf(r.keyCode)!==-1;case"keydown":return r.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function gs(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var so=!1;function ys(e,r){switch(e){case"compositionend":return gs(r);case"keypress":return r.which!==32?null:(Ci=!0,Pi);case"textInput":return e=r.data,e===Pi&&Ci?null:e;default:return null}}function xs(e,r){if(so)return e==="compositionend"||!Ai&&ms(e,r)?(e=jr(),Ge=ye=Jt=null,so=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(r.ctrlKey||r.altKey||r.metaKey)||r.ctrlKey&&r.altKey){if(r.char&&1=r)return{node:a,offset:r-e};e=c}t:{for(;a;){if(a.nextSibling){a=a.nextSibling;break t}a=a.parentNode}a=void 0}a=Es(a)}}function Ea(e,r){return e&&r?e===r?!0:e&&e.nodeType===3?!1:r&&r.nodeType===3?Ea(e,r.parentNode):"contains"in e?e.contains(r):e.compareDocumentPosition?!!(e.compareDocumentPosition(r)&16):!1:!1}function Ts(){for(var e=window,r=Ft();n(r,e.HTMLIFrameElement);){try{var a=typeof r.contentWindow.location.href=="string"}catch(c){a=!1}if(a)e=r.contentWindow;else break;r=Ft(e.document)}return r}function bi(e){var r=e&&e.nodeName&&e.nodeName.toLowerCase();return r&&(r==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||r==="textarea"||e.contentEditable==="true")}function ra(e){var r=Ts(),a=e.focusedElem,c=e.selectionRange;if(r!==a&&a&&a.ownerDocument&&Ea(a.ownerDocument.documentElement,a)){if(c!==null&&bi(a)){if(r=c.start,e=c.end,e===void 0&&(e=r),"selectionStart"in a)a.selectionStart=r,a.selectionEnd=Math.min(e,a.value.length);else if(e=(r=a.ownerDocument||document)&&r.defaultView||window,e.getSelection){e=e.getSelection();var T=a.textContent.length,M=Math.min(c.start,T);c=c.end===void 0?M:Math.min(c.end,T),!e.extend&&M>c&&(T=c,c=M,M=T),T=Os(a,M);var $=Os(a,c);T&&$&&(e.rangeCount!==1||e.anchorNode!==T.node||e.anchorOffset!==T.offset||e.focusNode!==$.node||e.focusOffset!==$.offset)&&(r=r.createRange(),r.setStart(T.node,T.offset),e.removeAllRanges(),M>c?(e.addRange(r),e.extend($.node,$.offset)):(r.setEnd($.node,$.offset),e.addRange(r)))}}for(r=[],e=a;e=e.parentNode;)e.nodeType===1&&r.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof a.focus=="function"&&a.focus(),a=0;a=document.documentMode,Mo=null,Di=null,Oa=null,Is=!1;function Bi(e,r,a){var c=a.window===a?a.document:a.nodeType===9?a:a.ownerDocument;Is||Mo==null||Mo!==Ft(c)||(c=Mo,"selectionStart"in c&&bi(c)?c={start:c.selectionStart,end:c.selectionEnd}:(c=(c.ownerDocument&&c.ownerDocument.defaultView||window).getSelection(),c={anchorNode:c.anchorNode,anchorOffset:c.anchorOffset,focusNode:c.focusNode,focusOffset:c.focusOffset}),Oa&&Sa(Oa,c)||(Oa=c,c=Pa(Di,"onSelect"),0Na||(e.current=js[Na],js[Na]=null,Na--)}function xr(e,r){Na++,js[Na]=e.current,e.current=r}var Ko={},un=eo(Ko),gn=eo(!1),Vo=Ko;function Ho(e,r){var a=e.type.contextTypes;if(!a)return Ko;var c=e.stateNode;if(c&&c.__reactInternalMemoizedUnmaskedChildContext===r)return c.__reactInternalMemoizedMaskedChildContext;var T={},M;for(M in a)T[M]=r[M];return c&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=r,e.__reactInternalMemoizedMaskedChildContext=T),T}function In(e){return e=e.childContextTypes,e!=null}function Zi(){Ir(gn),Ir(un)}function ia(e,r,a){if(un.current!==Ko)throw Error(u(168));xr(un,r),xr(gn,a)}function Ns(e,r,a){var c=e.stateNode;if(r=r.childContextTypes,typeof c.getChildContext!="function")return a;c=c.getChildContext();for(var T in c)if(!(T in r))throw Error(u(108,pt(e)||"Unknown",T));return Q({},a,c)}function Ji(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ko,Vo=un.current,xr(un,e),xr(gn,gn.current),!0}function qi(e,r,a){var c=e.stateNode;if(!c)throw Error(u(169));a?(e=Ns(e,r,Vo),c.__reactInternalMemoizedMergedChildContext=e,Ir(gn),Ir(un),xr(un,e)):Ir(gn),xr(gn,a)}var Hn=null,ii=!1,_i=!1;function si(e){Hn===null?Hn=[e]:Hn.push(e)}function Mu(e){ii=!0,si(e)}function vo(){if(!_i&&Hn!==null){_i=!0;var e=0,r=or;try{var a=Hn;for(or=1;e>=$,T-=$,po=1<<32-Br(r)+T|a<qe?(cn=Ye,Ye=null):cn=Ye.sibling;var mr=_t(At,Ye,Ct[qe],pe);if(mr===null){Ye===null&&(Ye=cn);break}e&&Ye&&mr.alternate===null&&r(At,Ye),yt=M(mr,yt,qe),ke===null?Be=mr:ke.sibling=mr,ke=mr,Ye=cn}if(qe===Ct.length)return a(At,Ye),br&&sa(At,qe),Be;if(Ye===null){for(;qeqe?(cn=Ye,Ye=null):cn=Ye.sibling;var ha=_t(At,Ye,mr.value,pe);if(ha===null){Ye===null&&(Ye=cn);break}e&&Ye&&ha.alternate===null&&r(At,Ye),yt=M(ha,yt,qe),ke===null?Be=ha:ke.sibling=ha,ke=ha,Ye=cn}if(mr.done)return a(At,Ye),br&&sa(At,qe),Be;if(Ye===null){for(;!mr.done;qe++,mr=Ct.next())mr=re(At,mr.value,pe),mr!==null&&(yt=M(mr,yt,qe),ke===null?Be=mr:ke.sibling=mr,ke=mr);return br&&sa(At,qe),Be}for(Ye=c(At,Ye);!mr.done;qe++,mr=Ct.next())mr=Ce(Ye,At,qe,mr.value,pe),mr!==null&&(e&&mr.alternate!==null&&Ye.delete(mr.key===null?qe:mr.key),yt=M(mr,yt,qe),ke===null?Be=mr:ke.sibling=mr,ke=mr);return e&&Ye.forEach(function(mf){return r(At,mf)}),br&&sa(At,qe),Be}function Yr(At,yt,Ct,pe){if(typeof Ct=="object"&&Ct!==null&&Ct.type===K&&Ct.key===null&&(Ct=Ct.props.children),typeof Ct=="object"&&Ct!==null){switch(Ct.$$typeof){case L:t:{for(var Be=Ct.key,ke=yt;ke!==null;){if(ke.key===Be){if(Be=Ct.type,Be===K){if(ke.tag===7){a(At,ke.sibling),yt=T(ke,Ct.props.children),yt.return=At,At=yt;break t}}else if(ke.elementType===Be||typeof Be=="object"&&Be!==null&&Be.$$typeof===z&&st(Be)===ke.type){a(At,ke.sibling),yt=T(ke,Ct.props),yt.ref=lt(At,ke,Ct),yt.return=At,At=yt;break t}a(At,ke);break}else r(At,ke);ke=ke.sibling}Ct.type===K?(yt=Ha(Ct.props.children,At.mode,pe,Ct.key),yt.return=At,At=yt):(pe=qs(Ct.type,Ct.key,Ct.props,null,At.mode,pe),pe.ref=lt(At,yt,Ct),pe.return=At,At=pe)}return $(At);case W:t:{for(ke=Ct.key;yt!==null;){if(yt.key===ke)if(yt.tag===4&&yt.stateNode.containerInfo===Ct.containerInfo&&yt.stateNode.implementation===Ct.implementation){a(At,yt.sibling),yt=T(yt,Ct.children||[]),yt.return=At,At=yt;break t}else{a(At,yt);break}else r(At,yt);yt=yt.sibling}yt=cl(Ct,At.mode,pe),yt.return=At,At=yt}return $(At);case z:return ke=Ct._init,Yr(At,yt,ke(Ct._payload),pe)}if(Vt(Ct))return Ne(At,yt,Ct,pe);if(G(Ct))return be(At,yt,Ct,pe);tt(At,Ct)}return typeof Ct=="string"&&Ct!==""||typeof Ct=="number"?(Ct=""+Ct,yt!==null&&yt.tag===6?(a(At,yt.sibling),yt=T(yt,Ct),yt.return=At,At=yt):(a(At,yt),yt=ll(Ct,At.mode,pe),yt.return=At,At=yt),$(At)):a(At,yt)}return Yr}var Wt=Dt(!0),Nt=Dt(!1),kt=eo(null),se=null,ue=null,ce=null;function Ae(){ce=ue=se=null}function De(e){var r=kt.current;Ir(kt),e._currentValue=r}function Yt(e,r,a){for(;e!==null;){var c=e.alternate;if((e.childLanes&r)!==r?(e.childLanes|=r,c!==null&&(c.childLanes|=r)):c!==null&&(c.childLanes&r)!==r&&(c.childLanes|=r),e===a)break;e=e.return}}function Ee(e,r){se=e,ce=ue=null,e=e.dependencies,e!==null&&e.firstContext!==null&&(e.lanes&r&&(Bn=!0),e.firstContext=null)}function Pe(e){var r=e._currentValue;if(ce!==e)if(e={context:e,memoizedValue:r,next:null},ue===null){if(se===null)throw Error(u(308));ue=e,se.dependencies={lanes:0,firstContext:e}}else ue=ue.next=e;return r}var Ze=null;function $e(e){Ze===null?Ze=[e]:Ze.push(e)}function tr(e,r,a,c){var T=r.interleaved;return T===null?(a.next=a,$e(r)):(a.next=T.next,T.next=a),r.interleaved=a,oe(e,c)}function oe(e,r){e.lanes|=r;var a=e.alternate;for(a!==null&&(a.lanes|=r),a=e,e=e.return;e!==null;)e.childLanes|=r,a=e.alternate,a!==null&&(a.childLanes|=r),a=e,e=e.return;return a.tag===3?a.stateNode:null}var Ue=!1;function He(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function cr(e,r){e=e.updateQueue,r.updateQueue===e&&(r.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function yr(e,r){return{eventTime:e,lane:r,tag:0,payload:null,callback:null,next:null}}function Wr(e,r,a){var c=e.updateQueue;if(c===null)return null;if(c=c.shared,hr&2){var T=c.pending;return T===null?r.next=r:(r.next=T.next,T.next=r),c.pending=r,oe(e,a)}return T=c.interleaved,T===null?(r.next=r,$e(c)):(r.next=T.next,T.next=r),c.interleaved=r,oe(e,a)}function Sr(e,r,a){if(r=r.updateQueue,r!==null&&(r=r.shared,(a&4194240)!==0)){var c=r.lanes;c&=e.pendingLanes,a|=c,r.lanes=a,Xr(e,a)}}function rn(e,r){var a=e.updateQueue,c=e.alternate;if(c!==null&&(c=c.updateQueue,a===c)){var T=null,M=null;if(a=a.firstBaseUpdate,a!==null){do{var $={eventTime:a.eventTime,lane:a.lane,tag:a.tag,payload:a.payload,callback:a.callback,next:null};M===null?T=M=$:M=M.next=$,a=a.next}while(a!==null);M===null?T=M=r:M=M.next=r}else T=M=r;a={baseState:c.baseState,firstBaseUpdate:T,lastBaseUpdate:M,shared:c.shared,effects:c.effects},e.updateQueue=a;return}e=a.lastBaseUpdate,e===null?a.firstBaseUpdate=r:e.next=r,a.lastBaseUpdate=r}function Ln(e,r,a,c){var T=e.updateQueue;Ue=!1;var M=T.firstBaseUpdate,$=T.lastBaseUpdate,q=T.shared.pending;if(q!==null){T.shared.pending=null;var mt=q,Lt=mt.next;mt.next=null,$===null?M=Lt:$.next=Lt,$=mt;var $t=e.alternate;$t!==null&&($t=$t.updateQueue,q=$t.lastBaseUpdate,q!==$&&(q===null?$t.firstBaseUpdate=Lt:q.next=Lt,$t.lastBaseUpdate=mt))}if(M!==null){var re=T.baseState;$=0,$t=Lt=mt=null,q=M;do{var _t=q.lane,Ce=q.eventTime;if((c&_t)===_t){$t!==null&&($t=$t.next={eventTime:Ce,lane:0,tag:q.tag,payload:q.payload,callback:q.callback,next:null});t:{var Ne=e,be=q;switch(_t=r,Ce=a,be.tag){case 1:if(Ne=be.payload,typeof Ne=="function"){re=Ne.call(Ce,re,_t);break t}re=Ne;break t;case 3:Ne.flags=Ne.flags&-65537|128;case 0:if(Ne=be.payload,_t=typeof Ne=="function"?Ne.call(Ce,re,_t):Ne,_t==null)break t;re=Q({},re,_t);break t;case 2:Ue=!0}}q.callback!==null&&q.lane!==0&&(e.flags|=64,_t=T.effects,_t===null?T.effects=[q]:_t.push(q))}else Ce={eventTime:Ce,lane:_t,tag:q.tag,payload:q.payload,callback:q.callback,next:null},$t===null?(Lt=$t=Ce,mt=re):$t=$t.next=Ce,$|=_t;if(q=q.next,q===null){if(q=T.shared.pending,q===null)break;_t=q,q=_t.next,_t.next=null,T.lastBaseUpdate=_t,T.shared.pending=null}}while(!0);if($t===null&&(mt=re),T.baseState=mt,T.firstBaseUpdate=Lt,T.lastBaseUpdate=$t,r=T.shared.interleaved,r!==null){T=r;do $|=T.lane,T=T.next;while(T!==r)}else M===null&&(T.shared.lanes=0);Wa|=$,e.lanes=$,e.memoizedState=re}}function pr(e,r,a){if(e=r.effects,r.effects=null,e!==null)for(r=0;ra?a:4,e(!0);var c=es.transition;es.transition={};try{e(!1),r()}finally{or=a,es.transition=c}}function zl(){return Pn().memoizedState}function zc(e,r,a){var c=fa(e);if(a={lane:c,action:a,hasEagerState:!1,eagerState:null,next:null},Wl(e))$l(r,a);else if(a=tr(e,r,a,c),a!==null){var T=Rn();So(a,e,c,T),Kl(a,r,c)}}function Wc(e,r,a){var c=fa(e),T={lane:c,action:a,hasEagerState:!1,eagerState:null,next:null};if(Wl(e))$l(r,T);else{var M=e.alternate;if(e.lanes===0&&(M===null||M.lanes===0)&&(M=r.lastRenderedReducer,M!==null))try{var $=r.lastRenderedState,q=M($,a);if(T.hasEagerState=!0,T.eagerState=q,Mn(q,$)){var mt=r.interleaved;mt===null?(T.next=T,$e(r)):(T.next=mt.next,mt.next=T),r.interleaved=T;return}}catch(Lt){}finally{}a=tr(e,r,T,c),a!==null&&(T=Rn(),So(a,e,c,T),Kl(a,r,c))}}function Wl(e){var r=e.alternate;return e===Cr||r!==null&&r===Cr}function $l(e,r){Gn=ci=!0;var a=e.pending;a===null?r.next=r:(r.next=a.next,a.next=r),e.pending=r}function Kl(e,r,a){if(a&4194240){var c=r.lanes;c&=e.pendingLanes,a|=c,r.lanes=a,Xr(e,a)}}var Us={readContext:Pe,useCallback:on,useContext:on,useEffect:on,useImperativeHandle:on,useInsertionEffect:on,useLayoutEffect:on,useMemo:on,useReducer:on,useRef:on,useState:on,useDebugValue:on,useDeferredValue:on,useTransition:on,useMutableSource:on,useSyncExternalStore:on,useId:on,unstable_isNewReconciler:!1},$c={readContext:Pe,useCallback:function(r,a){return kn().memoizedState=[r,a===void 0?null:a],r},useContext:Pe,useEffect:jl,useImperativeHandle:function(r,a,c){return c=c!=null?c.concat([r]):null,Bs(4194308,4,Ll.bind(null,a,r),c)},useLayoutEffect:function(r,a){return Bs(4194308,4,r,a)},useInsertionEffect:function(r,a){return Bs(4,2,r,a)},useMemo:function(r,a){var c=kn();return a=a===void 0?null:a,r=r(),c.memoizedState=[r,a],r},useReducer:function(r,a,c){var T=kn();return a=c!==void 0?c(a):a,T.memoizedState=T.baseState=a,r={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:r,lastRenderedState:a},T.queue=r,r=r.dispatch=zc.bind(null,Cr,r),[T.memoizedState,r]},useRef:function(r){var a=kn();return r={current:r},a.memoizedState=r},useState:wl,useDebugValue:Bu,useDeferredValue:function(r){return kn().memoizedState=r},useTransition:function(){var r=wl(!1),a=r[0];return r=Uc.bind(null,r[1]),kn().memoizedState=r,[a,r]},useMutableSource:function(){},useSyncExternalStore:function(r,a,c){var T=Cr,M=kn();if(br){if(c===void 0)throw Error(u(407));c=c()}else{if(c=a(),ln===null)throw Error(u(349));Go&30||Il(T,a,c)}M.memoizedState=c;var $={value:c,getSnapshot:a};return M.queue=$,jl(Pl.bind(null,T,$,r),[r]),T.flags|=2048,ns(9,Al.bind(null,T,$,c,a),void 0,null),c},useId:function(){var r=kn(),a=ln.identifierPrefix;if(br){var c=bo,T=po;c=(T&~(1<<32-Br(T)-1)).toString(32)+c,a=":"+a+"R"+c,c=Do++,0<\/script>",e=e.removeChild(e.firstChild)):typeof c.is=="string"?e=$.createElement(a,{is:c.is}):(e=$.createElement(a),a==="select"&&($=e,c.multiple?$.multiple=!0:c.size&&($.size=c.size))):e=$.createElementNS(e,a),e[Vn]=r,e[$o]=c,uc(e,r,!1,!1),r.stateNode=e;t:{switch($=ir(a,c),a){case"dialog":Tr("cancel",e),Tr("close",e),T=c;break;case"iframe":case"object":case"embed":Tr("load",e),T=c;break;case"video":case"audio":for(T=0;Tmi&&(r.flags|=128,c=!0,os(M,!1),r.lanes=4194304)}else{if(!c)if(e=li($),e!==null){if(r.flags|=128,c=!0,a=e.updateQueue,a!==null&&(r.updateQueue=a,r.flags|=4),os(M,!0),M.tail===null&&M.tailMode==="hidden"&&!$.alternate&&!br)return xn(r),null}else 2*Qe()-M.renderingStartTime>mi&&a!==1073741824&&(r.flags|=128,c=!0,os(M,!1),r.lanes=4194304);M.isBackwards?($.sibling=r.child,r.child=$):(a=M.last,a!==null?a.sibling=$:r.child=$,M.last=$)}return M.tail!==null?(r=M.tail,M.rendering=r,M.tail=r.sibling,M.renderingStartTime=Qe(),r.sibling=null,a=Ar.current,xr(Ar,c?a&1|2:a&1),r):(xn(r),null);case 22:case 23:return il(),c=r.memoizedState!==null,e!==null&&e.memoizedState!==null!==c&&(r.flags|=8192),c&&r.mode&1?Yn&1073741824&&(xn(r),r.subtreeFlags&6&&(r.flags|=8192)):xn(r),null;case 24:return null;case 25:return null}throw Error(u(156,r.tag))}function Qc(e,r){switch(Ls(r),r.tag){case 1:return In(r.type)&&Zi(),e=r.flags,e&65536?(r.flags=e&-65537|128,r):null;case 3:return no(),Ir(gn),Ir(un),ts(),e=r.flags,e&65536&&!(e&128)?(r.flags=e&-65537|128,r):null;case 5:return mo(r),null;case 13:if(Ir(Ar),e=r.memoizedState,e!==null&&e.dehydrated!==null){if(r.alternate===null)throw Error(u(340));U()}return e=r.flags,e&65536?(r.flags=e&-65537|128,r):null;case 19:return Ir(Ar),null;case 4:return no(),null;case 10:return De(r.type._context),null;case 22:case 23:return il(),null;case 24:return null;default:return null}}var Ks=!1,Sn=!1,Zc=typeof WeakSet=="function"?WeakSet:Set,je=null;function hi(e,r){var a=e.ref;if(a!==null)if(typeof a=="function")try{a(null)}catch(c){Kr(e,r,c)}else a.current=null}function Xu(e,r,a){try{a()}catch(c){Kr(e,r,c)}}var fc=!1;function Jc(e,r){if(Ki=$n,e=Ts(),bi(e)){if("selectionStart"in e)var a={start:e.selectionStart,end:e.selectionEnd};else t:{a=(a=e.ownerDocument)&&a.defaultView||window;var c=a.getSelection&&a.getSelection();if(c&&c.rangeCount!==0){a=c.anchorNode;var T=c.anchorOffset,M=c.focusNode;c=c.focusOffset;try{a.nodeType,M.nodeType}catch(pe){a=null;break t}var $=0,q=-1,mt=-1,Lt=0,$t=0,re=e,_t=null;e:for(;;){for(var Ce;re!==a||T!==0&&re.nodeType!==3||(q=$+T),re!==M||c!==0&&re.nodeType!==3||(mt=$+c),re.nodeType===3&&($+=re.nodeValue.length),(Ce=re.firstChild)!==null;)_t=re,re=Ce;for(;;){if(re===e)break e;if(_t===a&&++Lt===T&&(q=$),_t===M&&++$t===c&&(mt=$),(Ce=re.nextSibling)!==null)break;re=_t,_t=re.parentNode}re=Ce}a=q===-1||mt===-1?null:{start:q,end:mt}}else a=null}a=a||{start:0,end:0}}else a=null;for(Vi={focusedElem:e,selectionRange:a},$n=!1,je=r;je!==null;)if(r=je,e=r.child,(r.subtreeFlags&1028)!==0&&e!==null)e.return=r,je=e;else for(;je!==null;){r=je;try{var Ne=r.alternate;if(r.flags&1024)switch(r.tag){case 0:case 11:case 15:break;case 1:if(Ne!==null){var be=Ne.memoizedProps,Yr=Ne.memoizedState,At=r.stateNode,yt=At.getSnapshotBeforeUpdate(r.elementType===r.type?be:go(r.type,be),Yr);At.__reactInternalSnapshotBeforeUpdate=yt}break;case 3:var Ct=r.stateNode.containerInfo;Ct.nodeType===1?Ct.textContent="":Ct.nodeType===9&&Ct.documentElement&&Ct.removeChild(Ct.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(u(163))}}catch(pe){Kr(r,r.return,pe)}if(e=r.sibling,e!==null){e.return=r.return,je=e;break}je=r.return}return Ne=fc,fc=!1,Ne}function as(e,r,a){var c=r.updateQueue;if(c=c!==null?c.lastEffect:null,c!==null){var T=c=c.next;do{if((T.tag&e)===e){var M=T.destroy;T.destroy=void 0,M!==void 0&&Xu(r,a,M)}T=T.next}while(T!==c)}}function Vs(e,r){if(r=r.updateQueue,r=r!==null?r.lastEffect:null,r!==null){var a=r=r.next;do{if((a.tag&e)===e){var c=a.create;a.destroy=c()}a=a.next}while(a!==r)}}function Qu(e){var r=e.ref;if(r!==null){var a=e.stateNode;switch(e.tag){case 5:e=a;break;default:e=a}typeof r=="function"?r(e):r.current=e}}function dc(e){var r=e.alternate;r!==null&&(e.alternate=null,dc(r)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(r=e.stateNode,r!==null&&(delete r[Vn],delete r[$o],delete r[Xi],delete r[Qi],delete r[wu])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function vc(e){return e.tag===5||e.tag===3||e.tag===4}function hc(e){t:for(;;){for(;e.sibling===null;){if(e.return===null||vc(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue t;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function Zu(e,r,a){var c=e.tag;if(c===5||c===6)e=e.stateNode,r?a.nodeType===8?a.parentNode.insertBefore(e,r):a.insertBefore(e,r):(a.nodeType===8?(r=a.parentNode,r.insertBefore(e,a)):(r=a,r.appendChild(e)),a=a._reactRootContainer,a!=null||r.onclick!==null||(r.onclick=ai));else if(c!==4&&(e=e.child,e!==null))for(Zu(e,r,a),e=e.sibling;e!==null;)Zu(e,r,a),e=e.sibling}function Ju(e,r,a){var c=e.tag;if(c===5||c===6)e=e.stateNode,r?a.insertBefore(e,r):a.appendChild(e);else if(c!==4&&(e=e.child,e!==null))for(Ju(e,r,a),e=e.sibling;e!==null;)Ju(e,r,a),e=e.sibling}var dn=null,yo=!1;function ua(e,r,a){for(a=a.child;a!==null;)pc(e,r,a),a=a.sibling}function pc(e,r,a){if(Pr&&typeof Pr.onCommitFiberUnmount=="function")try{Pr.onCommitFiberUnmount(vr,a)}catch(q){}switch(a.tag){case 5:Sn||hi(a,r);case 6:var c=dn,T=yo;dn=null,ua(e,r,a),dn=c,yo=T,dn!==null&&(yo?(e=dn,a=a.stateNode,e.nodeType===8?e.parentNode.removeChild(a):e.removeChild(a)):dn.removeChild(a.stateNode));break;case 18:dn!==null&&(yo?(e=dn,a=a.stateNode,e.nodeType===8?wa(e.parentNode,a):e.nodeType===1&&wa(e,a),Wn(e)):wa(dn,a.stateNode));break;case 4:c=dn,T=yo,dn=a.stateNode.containerInfo,yo=!0,ua(e,r,a),dn=c,yo=T;break;case 0:case 11:case 14:case 15:if(!Sn&&(c=a.updateQueue,c!==null&&(c=c.lastEffect,c!==null))){T=c=c.next;do{var M=T,$=M.destroy;M=M.tag,$!==void 0&&(M&2||M&4)&&Xu(a,r,$),T=T.next}while(T!==c)}ua(e,r,a);break;case 1:if(!Sn&&(hi(a,r),c=a.stateNode,typeof c.componentWillUnmount=="function"))try{c.props=a.memoizedProps,c.state=a.memoizedState,c.componentWillUnmount()}catch(q){Kr(a,r,q)}ua(e,r,a);break;case 21:ua(e,r,a);break;case 22:a.mode&1?(Sn=(c=Sn)||a.memoizedState!==null,ua(e,r,a),Sn=c):ua(e,r,a);break;default:ua(e,r,a)}}function mc(e){var r=e.updateQueue;if(r!==null){e.updateQueue=null;var a=e.stateNode;a===null&&(a=e.stateNode=new Zc),r.forEach(function(c){var T=sf.bind(null,e,c);a.has(c)||(a.add(c),c.then(T,T))})}}function xo(e,r){var a=r.deletions;if(a!==null)for(var c=0;cT&&(T=$),c&=~M}if(c=T,c=Qe()-c,c=(120>c?120:480>c?480:1080>c?1080:1920>c?1920:3e3>c?3e3:4320>c?4320:1960*_c(c/1960))-c,10e?16:e,ca===null)var c=!1;else{if(e=ca,ca=null,Xs=0,hr&6)throw Error(u(331));var T=hr;for(hr|=4,je=e.current;je!==null;){var M=je,$=M.child;if(je.flags&16){var q=M.deletions;if(q!==null){for(var mt=0;mtQe()-tl?Ka(e,0):_u|=a),Un(e,r)}function wc(e,r){r===0&&(e.mode&1?(r=wr,wr<<=1,!(wr&130023424)&&(wr=4194304)):r=1);var a=Rn();e=oe(e,r),e!==null&&(kr(e,r,a),Un(e,a))}function af(e){var r=e.memoizedState,a=0;r!==null&&(a=r.retryLane),wc(e,a)}function sf(e,r){var a=0;switch(e.tag){case 13:var c=e.stateNode,T=e.memoizedState;T!==null&&(a=T.retryLane);break;case 19:c=e.stateNode;break;default:throw Error(u(314))}c!==null&&c.delete(r),wc(e,a)}var Mc;Mc=function(r,a,c){if(r!==null)if(r.memoizedProps!==a.pendingProps||gn.current)Bn=!0;else{if(!(r.lanes&c)&&!(a.flags&128))return Bn=!1,Yc(r,a,c);Bn=!!(r.flags&131072)}else Bn=!1,br&&a.flags&1048576&&ui(a,Da,a.index);switch(a.lanes=0,a.tag){case 2:var T=a.type;$s(r,a),r=a.pendingProps;var M=Ho(a,un.current);Ee(a,c),M=fi(null,a,T,r,M,c);var $=di();return a.flags|=1,typeof M=="object"&&M!==null&&typeof M.render=="function"&&M.$$typeof===void 0?(a.tag=1,a.memoizedState=null,a.updateQueue=null,In(T)?($=!0,Ji(a)):$=!1,a.memoizedState=M.state!==null&&M.state!==void 0?M.state:null,He(a),M.updater=zs,a.stateNode=M,M._reactInternals=a,Uu(a,T,r,c),a=Ku(null,a,T,!0,$,c)):(a.tag=0,br&&$&&bs(a),Cn(null,a,M,c),a=a.child),a;case 16:T=a.elementType;t:{switch($s(r,a),r=a.pendingProps,M=T._init,T=M(T._payload),a.type=T,M=a.tag=lf(T),r=go(T,r),M){case 0:a=$u(null,a,T,r,c);break t;case 1:a=rc(null,a,T,r,c);break t;case 11:a=Jl(null,a,T,r,c);break t;case 14:a=ql(null,a,T,go(T.type,r),c);break t}throw Error(u(306,T,""))}return a;case 0:return T=a.type,M=a.pendingProps,M=a.elementType===T?M:go(T,M),$u(r,a,T,M,c);case 1:return T=a.type,M=a.pendingProps,M=a.elementType===T?M:go(T,M),rc(r,a,T,M,c);case 3:t:{if(nc(a),r===null)throw Error(u(387));T=a.pendingProps,$=a.memoizedState,M=$.element,cr(r,a),Ln(a,T,null,c);var q=a.memoizedState;if(T=q.element,$.isDehydrated)if($={element:T,isDehydrated:!1,cache:q.cache,pendingSuspenseBoundaries:q.pendingSuspenseBoundaries,transitions:q.transitions},a.updateQueue.baseState=$,a.memoizedState=$,a.flags&256){M=vi(Error(u(423)),a),a=oc(r,a,T,c,M);break t}else if(T!==M){M=vi(Error(u(424)),a),a=oc(r,a,T,c,M);break t}else for(bn=Kn(a.stateNode.containerInfo.firstChild),Nn=a,br=!0,An=null,c=Nt(a,null,T,c),a.child=c;c;)c.flags=c.flags&-3|4096,c=c.sibling;else{if(U(),T===M){a=ko(r,a,c);break t}Cn(r,a,T,c)}a=a.child}return a;case 5:return Ba(a),r===null&&y(a),T=a.type,M=a.pendingProps,$=r!==null?r.memoizedProps:null,q=M.children,Ra(T,M)?q=null:$!==null&&Ra(T,$)&&(a.flags|=32),ec(r,a),Cn(r,a,q,c),a.child;case 6:return r===null&&y(a),null;case 13:return ac(r,a,c);case 4:return ro(a,a.stateNode.containerInfo),T=a.pendingProps,r===null?a.child=Wt(a,null,T,c):Cn(r,a,T,c),a.child;case 11:return T=a.type,M=a.pendingProps,M=a.elementType===T?M:go(T,M),Jl(r,a,T,M,c);case 7:return Cn(r,a,a.pendingProps,c),a.child;case 8:return Cn(r,a,a.pendingProps.children,c),a.child;case 12:return Cn(r,a,a.pendingProps.children,c),a.child;case 10:t:{if(T=a.type._context,M=a.pendingProps,$=a.memoizedProps,q=M.value,xr(kt,T._currentValue),T._currentValue=q,$!==null)if(Mn($.value,q)){if($.children===M.children&&!gn.current){a=ko(r,a,c);break t}}else for($=a.child,$!==null&&($.return=a);$!==null;){var mt=$.dependencies;if(mt!==null){q=$.child;for(var Lt=mt.firstContext;Lt!==null;){if(Lt.context===T){if($.tag===1){Lt=yr(-1,c&-c),Lt.tag=2;var $t=$.updateQueue;if($t!==null){$t=$t.shared;var re=$t.pending;re===null?Lt.next=Lt:(Lt.next=re.next,re.next=Lt),$t.pending=Lt}}$.lanes|=c,Lt=$.alternate,Lt!==null&&(Lt.lanes|=c),Yt($.return,c,a),mt.lanes|=c;break}Lt=Lt.next}}else if($.tag===10)q=$.type===a.type?null:$.child;else if($.tag===18){if(q=$.return,q===null)throw Error(u(341));q.lanes|=c,mt=q.alternate,mt!==null&&(mt.lanes|=c),Yt(q,c,a),q=$.sibling}else q=$.child;if(q!==null)q.return=$;else for(q=$;q!==null;){if(q===a){q=null;break}if($=q.sibling,$!==null){$.return=q.return,q=$;break}q=q.return}$=q}Cn(r,a,M.children,c),a=a.child}return a;case 9:return M=a.type,T=a.pendingProps.children,Ee(a,c),M=Pe(M),T=T(M),a.flags|=1,Cn(r,a,T,c),a.child;case 14:return T=a.type,M=go(T,a.pendingProps),M=go(T.type,M),ql(r,a,T,M,c);case 15:return _l(r,a,a.type,a.pendingProps,c);case 17:return T=a.type,M=a.pendingProps,M=a.elementType===T?M:go(T,M),$s(r,a),a.tag=1,In(T)?(r=!0,Ji(a)):r=!1,Ee(a,c),Hl(a,T,M),Uu(a,T,M,c),Ku(null,a,T,!0,r,c);case 19:return sc(r,a,c);case 22:return tc(r,a,c)}throw Error(u(156,a.tag))};function jc(e,r){return ae(e,r)}function uf(e,r,a,c){this.tag=e,this.key=a,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=r,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=c,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function ao(e,r,a,c){return new uf(e,r,a,c)}function ul(e){return e=e.prototype,!(!e||!e.isReactComponent)}function lf(e){if(typeof e=="function")return ul(e)?1:0;if(e!=null){if(e=e.$$typeof,e===Y)return 11;if(e===it)return 14}return 2}function va(e,r){var a=e.alternate;return a===null?(a=ao(e.tag,r,e.key,e.mode),a.elementType=e.elementType,a.type=e.type,a.stateNode=e.stateNode,a.alternate=e,e.alternate=a):(a.pendingProps=r,a.type=e.type,a.flags=0,a.subtreeFlags=0,a.deletions=null),a.flags=e.flags&14680064,a.childLanes=e.childLanes,a.lanes=e.lanes,a.child=e.child,a.memoizedProps=e.memoizedProps,a.memoizedState=e.memoizedState,a.updateQueue=e.updateQueue,r=e.dependencies,a.dependencies=r===null?null:{lanes:r.lanes,firstContext:r.firstContext},a.sibling=e.sibling,a.index=e.index,a.ref=e.ref,a}function qs(e,r,a,c,T,M){var $=2;if(c=e,typeof e=="function")ul(e)&&($=1);else if(typeof e=="string")$=5;else t:switch(e){case K:return Ha(a.children,T,M,r);case k:$=8,T|=8;break;case J:return e=ao(12,a,r,T|2),e.elementType=J,e.lanes=M,e;case at:return e=ao(13,a,r,T),e.elementType=at,e.lanes=M,e;case et:return e=ao(19,a,r,T),e.elementType=et,e.lanes=M,e;case V:return _s(a,T,M,r);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case H:$=10;break t;case X:$=9;break t;case Y:$=11;break t;case it:$=14;break t;case z:$=16,c=null;break t}throw Error(u(130,e==null?e:typeof e=="undefined"?"undefined":o(e),""))}return r=ao($,a,r,T),r.elementType=e,r.type=c,r.lanes=M,r}function Ha(e,r,a,c){return e=ao(7,e,c,r),e.lanes=a,e}function _s(e,r,a,c){return e=ao(22,e,c,r),e.elementType=V,e.lanes=a,e.stateNode={isHidden:!1},e}function ll(e,r,a){return e=ao(6,e,null,r),e.lanes=a,e}function cl(e,r,a){return r=ao(4,e.children!==null?e.children:[],e.key,r),r.lanes=a,r.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},r}function cf(e,r,a,c,T){this.tag=r,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Oo(0),this.expirationTimes=Oo(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Oo(0),this.identifierPrefix=c,this.onRecoverableError=T,this.mutableSourceEagerHydrationData=null}function fl(e,r,a,c,T,M,$,q,mt){return e=new cf(e,r,a,q,mt),r===1?(r=1,M===!0&&(r|=8)):r=0,M=ao(3,null,null,r),e.current=M,M.stateNode=e,M.memoizedState={element:c,isDehydrated:a,cache:null,transitions:null,pendingSuspenseBoundaries:null},He(M),e}function ff(e,r,a){var c=3Tt.length)&&(Mt=Tt.length);for(var Xt=0,ge=new Array(Mt);Xt1?Xt-1:0),Oe=1;Oe/gm),jt=w(/\${[\w\W]*}/gm),Ft=w(/^data-[\-\w.\u00B7-\uFFFF]/),Gt=w(/^aria-[\-\w]+$/),Ht=w(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),ve=w(/^(?:\w+script|data):/i),he=w(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),ie=w(/^html$/i),zt=w(/^[a-z][.\w]*(-[.\w]+)+$/i),Vt=function(){return typeof window=="undefined"?null:window},wt=function(Mt,Xt){if(t(Mt)!=="object"||typeof Mt.createPolicy!="function")return null;var ge=null,Oe="data-tt-policy-suffix";Xt.currentScript&&Xt.currentScript.hasAttribute(Oe)&&(ge=Xt.currentScript.getAttribute(Oe));var Te="dompurify"+(ge?"#"+ge:"");try{return Mt.createPolicy(Te,{createHTML:function(Et){return Et},createScriptURL:function(Et){return Et}})}catch(de){return console.warn("TrustedTypes policy "+Te+" could not be created."),null}};function qt(){var Tt=arguments.length>0&&arguments[0]!==void 0?arguments[0]:Vt(),Mt=function(ft){return qt(ft)};if(Mt.version="2.5.3",Mt.removed=[],!Tt||!Tt.document||Tt.document.nodeType!==9)return Mt.isSupported=!1,Mt;var Xt=Tt.document,ge=Tt.document,Oe=Tt.DocumentFragment,Te=Tt.HTMLTemplateElement,de=Tt.Node,Et=Tt.Element,Rt=Tt.NodeFilter,te=Tt.NamedNodeMap,Ie=te===void 0?Tt.NamedNodeMap||Tt.MozNamedAttrMap:te,Ke=Tt.HTMLFormElement,Fe=Tt.DOMParser,ir=Tt.trustedTypes,ar=Et.prototype,dr=_(ar,"cloneNode"),nt=_(ar,"nextSibling"),St=_(ar,"childNodes"),ut=_(ar,"parentNode");if(typeof Te=="function"){var xt=ge.createElement("template");xt.content&&xt.content.ownerDocument&&(ge=xt.content.ownerDocument)}var Pt=wt(ir,Xt),Kt=Pt?Pt.createHTML(""):"",fe=ge,le=fe.implementation,Re=fe.createNodeIterator,ne=fe.createDocumentFragment,Ve=fe.getElementsByTagName,We=Xt.importNode,Je={};try{Je=V(ge).documentMode?ge.documentMode:{}}catch(Me){}var Le={};Mt.isSupported=typeof ut=="function"&&le&&le.createHTMLDocument!==void 0&&Je!==9;var Xe=Se,er=ee,ze=jt,sr=Ft,ur=Gt,Dr=ve,_r=he,dt=zt,Qt=Ht,It=null,Ot=z({},[].concat(s(G),s(Q),s(ct),s(ht),s(bt))),Bt=null,we=z({},[].concat(s(gt),s(pt),s(Ut),s(xe))),ae=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),_e=null,fr=null,lr=!0,Qe=!0,Zt=!1,rr=!0,gr=!1,Vr=!0,Rr=!1,zr=!1,vr=!1,Pr=!1,wn=!1,Br=!1,Hr=!0,En=!1,Xn="user-content-",sn=!0,wr=!1,hn={},Gr=null,Xo=z({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),io=null,pn=z({},["audio","video","img","source","image","track"]),Eo=null,Oo=z({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),kr="http://www.w3.org/1998/Math/MathML",To="http://www.w3.org/2000/svg",Xr="http://www.w3.org/1999/xhtml",or=Xr,Io=!1,zn=null,Qo=z({},[kr,To,Xr],W),Qn,Ga=["application/xhtml+xml","text/html"],ka="text/html",Mr,On=null,mn=255,Zn=ge.createElement("form"),Tn=function(ft){return m(ft,RegExp)||m(ft,Function)},Jn=function(ft){On&&On===ft||((!ft||t(ft)!=="object")&&(ft={}),ft=V(ft),Qn=Ga.indexOf(ft.PARSER_MEDIA_TYPE)===-1?Qn=ka:Qn=ft.PARSER_MEDIA_TYPE,Mr=Qn==="application/xhtml+xml"?W:L,It="ALLOWED_TAGS"in ft?z({},ft.ALLOWED_TAGS,Mr):Ot,Bt="ALLOWED_ATTR"in ft?z({},ft.ALLOWED_ATTR,Mr):we,zn="ALLOWED_NAMESPACES"in ft?z({},ft.ALLOWED_NAMESPACES,W):Qo,Eo="ADD_URI_SAFE_ATTR"in ft?z(V(Oo),ft.ADD_URI_SAFE_ATTR,Mr):Oo,io="ADD_DATA_URI_TAGS"in ft?z(V(pn),ft.ADD_DATA_URI_TAGS,Mr):pn,Gr="FORBID_CONTENTS"in ft?z({},ft.FORBID_CONTENTS,Mr):Xo,_e="FORBID_TAGS"in ft?z({},ft.FORBID_TAGS,Mr):{},fr="FORBID_ATTR"in ft?z({},ft.FORBID_ATTR,Mr):{},hn="USE_PROFILES"in ft?ft.USE_PROFILES:!1,lr=ft.ALLOW_ARIA_ATTR!==!1,Qe=ft.ALLOW_DATA_ATTR!==!1,Zt=ft.ALLOW_UNKNOWN_PROTOCOLS||!1,rr=ft.ALLOW_SELF_CLOSE_IN_ATTR!==!1,gr=ft.SAFE_FOR_TEMPLATES||!1,Vr=ft.SAFE_FOR_XML!==!1,Rr=ft.WHOLE_DOCUMENT||!1,Pr=ft.RETURN_DOM||!1,wn=ft.RETURN_DOM_FRAGMENT||!1,Br=ft.RETURN_TRUSTED_TYPE||!1,vr=ft.FORCE_BODY||!1,Hr=ft.SANITIZE_DOM!==!1,En=ft.SANITIZE_NAMED_PROPS||!1,sn=ft.KEEP_CONTENT!==!1,wr=ft.IN_PLACE||!1,Qt=ft.ALLOWED_URI_REGEXP||Qt,or=ft.NAMESPACE||Xr,ae=ft.CUSTOM_ELEMENT_HANDLING||{},ft.CUSTOM_ELEMENT_HANDLING&&Tn(ft.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(ae.tagNameCheck=ft.CUSTOM_ELEMENT_HANDLING.tagNameCheck),ft.CUSTOM_ELEMENT_HANDLING&&Tn(ft.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(ae.attributeNameCheck=ft.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),ft.CUSTOM_ELEMENT_HANDLING&&typeof ft.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements=="boolean"&&(ae.allowCustomizedBuiltInElements=ft.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),gr&&(Qe=!1),wn&&(Pr=!0),hn&&(It=z({},s(bt)),Bt=[],hn.html===!0&&(z(It,G),z(Bt,gt)),hn.svg===!0&&(z(It,Q),z(Bt,pt),z(Bt,xe)),hn.svgFilters===!0&&(z(It,ct),z(Bt,pt),z(Bt,xe)),hn.mathMl===!0&&(z(It,ht),z(Bt,Ut),z(Bt,xe))),ft.ADD_TAGS&&(It===Ot&&(It=V(It)),z(It,ft.ADD_TAGS,Mr)),ft.ADD_ATTR&&(Bt===we&&(Bt=V(Bt)),z(Bt,ft.ADD_ATTR,Mr)),ft.ADD_URI_SAFE_ATTR&&z(Eo,ft.ADD_URI_SAFE_ATTR,Mr),ft.FORBID_CONTENTS&&(Gr===Xo&&(Gr=V(Gr)),z(Gr,ft.FORBID_CONTENTS,Mr)),sn&&(It["#text"]=!0),Rr&&z(It,["html","head","body"]),It.table&&(z(It,["tbody"]),delete _e.tbody),I&&I(ft),On=ft)},Ao=z({},["mi","mo","mn","ms","mtext"]),tn=z({},["foreignobject","annotation-xml"]),Ya=z({},["title","style","font","a","script"]),Po=z({},Q);z(Po,ct),z(Po,rt);var fn=z({},ht);z(fn,vt);var Fo=function(ft){var Jt=ut(ft);(!Jt||!Jt.tagName)&&(Jt={namespaceURI:or,tagName:"template"});var ye=L(ft.tagName),Ge=L(Jt.tagName);return zn[ft.namespaceURI]?ft.namespaceURI===To?Jt.namespaceURI===Xr?ye==="svg":Jt.namespaceURI===kr?ye==="svg"&&(Ge==="annotation-xml"||Ao[Ge]):!!Po[ye]:ft.namespaceURI===kr?Jt.namespaceURI===Xr?ye==="math":Jt.namespaceURI===To?ye==="math"&&tn[Ge]:!!fn[ye]:ft.namespaceURI===Xr?Jt.namespaceURI===To&&!tn[Ge]||Jt.namespaceURI===kr&&!Ao[Ge]?!1:!fn[ye]&&(Ya[ye]||!Po[ye]):!!(Qn==="application/xhtml+xml"&&zn[ft.namespaceURI]):!1},Qr=function(ft){D(Mt.removed,{element:ft});try{ft.parentNode.removeChild(ft)}catch(Jt){try{ft.outerHTML=Kt}catch(ye){ft.remove()}}},Fr=function(ft,Jt){try{D(Mt.removed,{attribute:Jt.getAttributeNode(ft),from:Jt})}catch(ye){D(Mt.removed,{attribute:null,from:Jt})}if(Jt.removeAttribute(ft),ft==="is"&&!Bt[ft])if(Pr||wn)try{Qr(Jt)}catch(ye){}else try{Jt.setAttribute(ft,"")}catch(ye){}},pa=function(ft){var Jt,ye;if(vr)ft=""+ft;else{var Ge=K(ft,/^[\r\n\t ]+/);ye=Ge&&Ge[0]}Qn==="application/xhtml+xml"&&or===Xr&&(ft=''+ft+"");var jr=Pt?Pt.createHTML(ft):ft;if(or===Xr)try{Jt=new Fe().parseFromString(jr,Qn)}catch(Er){}if(!Jt||!Jt.documentElement){Jt=le.createDocument(or,"template",null);try{Jt.documentElement.innerHTML=Io?Kt:jr}catch(Er){}}var Ur=Jt.body||Jt.documentElement;return ft&&ye&&Ur.insertBefore(ge.createTextNode(ye),Ur.childNodes[0]||null),or===Xr?Ve.call(Jt,Rr?"html":"body")[0]:Rr?Jt.documentElement:Ur},Xa=function(ft){return Re.call(ft.ownerDocument||ft,ft,Rt.SHOW_ELEMENT|Rt.SHOW_COMMENT|Rt.SHOW_TEXT|Rt.SHOW_PROCESSING_INSTRUCTION|Rt.SHOW_CDATA_SECTION,null,!1)},Co=function(ft){return m(ft,Ke)&&(typeof ft.__depth!="undefined"&&typeof ft.__depth!="number"||typeof ft.__removalCount!="undefined"&&typeof ft.__removalCount!="number"||typeof ft.nodeName!="string"||typeof ft.textContent!="string"||typeof ft.removeChild!="function"||!m(ft.attributes,Ie)||typeof ft.removeAttribute!="function"||typeof ft.setAttribute!="function"||typeof ft.namespaceURI!="string"||typeof ft.insertBefore!="function"||typeof ft.hasChildNodes!="function")},Wn=function(ft){return t(de)==="object"?m(ft,de):ft&&t(ft)==="object"&&typeof ft.nodeType=="number"&&typeof ft.nodeName=="string"},en=function(ft,Jt,ye){Le[ft]&&P(Le[ft],function(Ge){Ge.call(Mt,Jt,ye,On)})},$n=function(ft){var Jt;if(en("beforeSanitizeElements",ft,null),Co(ft)||X(/[\u0080-\uFFFF]/,ft.nodeName))return Qr(ft),!0;var ye=Mr(ft.nodeName);if(en("uponSanitizeElement",ft,{tagName:ye,allowedTags:It}),ft.hasChildNodes()&&!Wn(ft.firstElementChild)&&(!Wn(ft.content)||!Wn(ft.content.firstElementChild))&&X(/<[/\w]/g,ft.innerHTML)&&X(/<[/\w]/g,ft.textContent)||ye==="select"&&X(/