diff --git a/.github/workflows/ci_suite.yml b/.github/workflows/ci_suite.yml index f36055dab0..34c1ec1b0e 100644 --- a/.github/workflows/ci_suite.yml +++ b/.github/workflows/ci_suite.yml @@ -13,11 +13,20 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - name: Setup cache + - name: Restore SpacemanDMM cache uses: actions/cache@v2 with: - path: $HOME/SpacemanDMM - key: ${{ runner.os }}-spacemandmm + path: ~/SpacemanDMM + key: ${{ runner.os }}-spacemandmm-${{ secrets.CACHE_PURGE_KEY }} + - name: Restore Yarn cache + uses: actions/cache@v2 + with: + path: tgui/.yarn/cache + key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}- + ${{ runner.os }}-build- + ${{ runner.os }}- - name: Install Tools run: | pip3 install setuptools @@ -28,11 +37,10 @@ jobs: run: | bash tools/ci/check_filedirs.sh tgstation.dme bash tools/ci/check_changelogs.sh + bash tools/ci/check_grep.sh find . -name "*.php" -print0 | xargs -0 -n1 php -l find . -name "*.json" -not -path "*/node_modules/*" -print0 | xargs -0 python3 ./tools/json_verifier.py - tgui/bin/tgui --lint - tgui/bin/tgui --test - bash tools/ci/check_grep.sh + tools/build/build --ci lint tools/bootstrap/python -m dmi.test tools/bootstrap/python -m mapmerge2.dmm_test ~/dreamchecker > ${GITHUB_WORKSPACE}/output-annotations.txt 2>&1 @@ -48,19 +56,16 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - name: Setup cache + - name: Restore BYOND cache uses: actions/cache@v2 with: - path: $HOME/BYOND - key: ${{ runner.os }}-byond + path: ~/BYOND + key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }} - name: Compile All Maps run: | bash tools/ci/install_byond.sh source $HOME/BYOND/byond/bin/byondsetup - python3 tools/ci/template_dm_generator.py - tools/build/build - env: - CBT_BUILD_MODE : ALL_MAPS + tools/build/build --ci dm -DCIBUILDING -DCITESTING -DALL_MAPS run_all_tests: if: "!contains(github.event.head_commit.message, '[ci skip]')" @@ -76,11 +81,20 @@ jobs: options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - uses: actions/checkout@v2 - - name: Setup cache + - name: Restore BYOND cache uses: actions/cache@v2 with: - path: $HOME/BYOND - key: ${{ runner.os }}-byond + path: ~/BYOND + key: ${{ runner.os }}-byond-${{ secrets.CACHE_PURGE_KEY }} + - name: Restore Yarn cache + uses: actions/cache@v2 + with: + path: tgui/.yarn/cache + key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}- + ${{ runner.os }}-build- + ${{ runner.os }}- - name: Setup database run: | sudo systemctl start mysql @@ -88,7 +102,7 @@ jobs: mysql -u root -proot tg_ci < SQL/tgstation_schema.sql mysql -u root -proot -e 'CREATE DATABASE tg_ci_prefixed;' mysql -u root -proot tg_ci_prefixed < SQL/tgstation_schema_prefixed.sql - - name: Install rust dependencies + - name: Install rust-g run: | sudo dpkg --add-architecture i386 sudo apt update || true @@ -96,15 +110,14 @@ jobs: bash tools/ci/install_rust_g.sh - name: Install auxmos run: | + sudo apt update || true bash tools/ci/install_auxmos.sh - name: Compile and run tests run: | bash tools/ci/install_byond.sh source $HOME/BYOND/byond/bin/byondsetup - tools/build/build -DCIBUILDING + tools/build/build --ci -DCIBUILDING bash tools/ci/run_server.sh - env: - CBT_BUILD_MODE: TEST_RUN test_windows: if: "!contains(github.event.head_commit.message, '[ci skip]')" @@ -112,6 +125,15 @@ jobs: runs-on: windows-latest steps: - uses: actions/checkout@v2 + - name: Restore Yarn cache + uses: actions/cache@v2 + with: + path: tgui/.yarn/cache + key: ${{ runner.os }}-yarn-${{ secrets.CACHE_PURGE_KEY }}-${{ hashFiles('tgui/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-build-${{ secrets.CACHE_PURGE_KEY }}- + ${{ runner.os }}-build- + ${{ runner.os }}- - name: Compile run: pwsh tools/ci/build.ps1 env: diff --git a/.github/workflows/generate_documentation.yml b/.github/workflows/generate_documentation.yml index 0fa3f315be..265e179ff0 100644 --- a/.github/workflows/generate_documentation.yml +++ b/.github/workflows/generate_documentation.yml @@ -12,8 +12,8 @@ jobs: - name: Setup cache uses: actions/cache@v2 with: - path: $HOME/SpacemanDMM - key: ${{ runner.os }}-spacemandmm + path: ~/SpacemanDMM + key: ${{ runner.os }}-spacemandmm-${{ secrets.CACHE_PURGE_KEY }} - name: Install SpacemanDMM run: bash tools/ci/install_spaceman_dmm.sh dmdoc - name: Generate documentation diff --git a/.gitignore b/.gitignore index 91a0ef08eb..56a447fdd1 100644 --- a/.gitignore +++ b/.gitignore @@ -7,14 +7,16 @@ #Ignore byond config folder. /cfg/**/* -#Ignore rust-g and auxmos libraries which are compiled with scripts -*.so -/tools/build/binaries/**/* +# Ignore compiled linux libs in the root folder, e.g. librust_g.so +/*.so #Ignore compiled files and other files generated during compilation. *.mdme +*.mdme.* *.dmb *.rsc +*.m.dme +*.test.dme *.lk *.int *.backup @@ -53,27 +55,6 @@ __pycache__/ *.py[cod] *$py.class -# C extensions -#*.so - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. @@ -81,8 +62,7 @@ var/ *.spec # Installer logs -pip-log.txt -pip-delete-this-directory.txt +pip-*.txt # Unit test / coverage reports htmlcov/ @@ -92,7 +72,6 @@ htmlcov/ .cache nosetests.xml coverage.xml -*,cover .hypothesis/ # Translations @@ -101,10 +80,6 @@ coverage.xml # Django stuff: *.log -local_settings.py - -# Flask instance folder -instance/ # Scrapy stuff: .scrapy @@ -112,9 +87,6 @@ instance/ # Sphinx documentation docs/_build/ -# PyBuilder -target/ - # IPython Notebook .ipynb_checkpoints @@ -127,10 +99,6 @@ celerybeat-schedule # dotenv .env -# virtualenv -venv/ -ENV/ - # IntelliJ IDEA / PyCharm (with plugin) .idea @@ -153,12 +121,6 @@ Desktop.ini # Recycle Bin used on file shares $RECYCLE.BIN/ -# Windows Installer files -#*.cab -#*.msi -#*.msm -#*.msp - # Windows shortcuts *.lnk @@ -199,11 +161,10 @@ Temporary Items #Visual studio stuff *.vscode/* -!/.vscode/extensions.json -tools/MapAtmosFixer/MapAtmosFixer/obj/* -tools/MapAtmosFixer/MapAtmosFixer/bin/* -tools/CreditsTool/bin/* -tools/CreditsTool/obj/* +/tools/MapAtmosFixer/MapAtmosFixer/obj/* +/tools/MapAtmosFixer/MapAtmosFixer/bin/* +/tools/CreditsTool/bin/* +/tools/CreditsTool/obj/* #GitHub Atom .atom-build.json @@ -228,13 +189,10 @@ tools/CreditsTool/obj/* !/config/title_screens/images/exclude #Linux docker -tools/LinuxOneShot/SetupProgram/obj/* -tools/LinuxOneShot/SetupProgram/bin/* -tools/LinuxOneShot/SetupProgram/.vs -tools/LinuxOneShot/Database -tools/LinuxOneShot/TGS_Config -tools/LinuxOneShot/TGS_Instances -tools/LinuxOneShot/TGS_Logs - -# Common build tooling -!/tools/build +/tools/LinuxOneShot/SetupProgram/obj/* +/tools/LinuxOneShot/SetupProgram/bin/* +/tools/LinuxOneShot/SetupProgram/.vs +/tools/LinuxOneShot/Database +/tools/LinuxOneShot/TGS_Config +/tools/LinuxOneShot/TGS_Instances +/tools/LinuxOneShot/TGS_Logs diff --git a/Build.bat b/Build.bat index dd3a6fd9d9..68eaef0c2d 100644 --- a/Build.bat +++ b/Build.bat @@ -1,2 +1,3 @@ -@call tools\build\build -@pause +@echo off +call "%~dp0\tools\build\build.bat" %* +pause diff --git a/CLEAN.bat b/CLEAN.bat new file mode 100644 index 0000000000..47293bb769 --- /dev/null +++ b/CLEAN.bat @@ -0,0 +1,3 @@ +@echo off +call "%~dp0\tools\build\build.bat" dist-clean +pause diff --git a/README.md b/README.md index bbceb0f05b..cc485278cc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Citadel Station 13 Based and maintained from /tg/station. -[![Build Status](https://api.travis-ci.org/Citadel-Station-13/Citadel-Station-13.png)](https://travis-ci.org/Citadel-Station-13/Citadel-Station-13) +[![Build Status](https://github.com/Citadel-Station-13/Citadel-Station-13/workflows/CI%20Suite/badge.svg)](https://github.com/Citadel-Station-13/Citadel-Station-13/actions?query=workflow%3A%22CI+Suite%22) [![Percentage of issues still open](http://isitmaintained.com/badge/open/Citadel-Station-13/Citadel-Station-13.svg)](http://isitmaintained.com/project/Citadel-Station-13/Citadel-Station-13 "Percentage of issues still open") [![Average time to resolve an issue](http://isitmaintained.com/badge/resolution/Citadel-Station-13/Citadel-Station-13.svg)](http://isitmaintained.com/project/Citadel-Station-13/Citadel-Station-13 "Average time to resolve an issue") @@ -100,6 +100,14 @@ install, overwriting when prompted except if we've specified otherwise, and recompile the game. Once you start the server up again, you should be running the new version. +## :exclamation: How to compile :exclamation: + +On **2021-01-04** we have changed the way to compile the codebase. + +Find `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 normally by opening `tgstation.dmb` in DreamDaemon. + +**Building tgstation in DreamMaker directly is now deprecated and might produce errors**, such as `'tgui.bundle.js': cannot find file`. + ## HOSTING If you'd like a more robust server hosting option for tgstation and its diff --git a/RUN_SERVER.bat b/RUN_SERVER.bat new file mode 100644 index 0000000000..93438f0c2e --- /dev/null +++ b/RUN_SERVER.bat @@ -0,0 +1,3 @@ +@echo off +call "%~dp0\tools\build\build.bat" server %* +pause diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm index d1dcf9f11d..829761b042 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_library.dmm @@ -64,10 +64,6 @@ /obj/item/paper/fluff/ruins/oldstation/protosing, /turf/open/floor/plating/asteroid/basalt/lava_land_surface, /area/ruin/unpowered/buried_library) -"av" = ( -/obj/structure/bookcase/random, -/turf/open/space/basic, -/area/ruin/unpowered/buried_library) "aw" = ( /turf/open/floor/plating, /area/ruin/unpowered/buried_library) @@ -220,7 +216,6 @@ /area/ruin/unpowered/buried_library) "bq" = ( /obj/structure/mineral_door/wood, -/obj/structure/barricade/wooden/crude/snow, /turf/open/floor/wood, /area/ruin/unpowered/buried_library) "br" = ( @@ -369,7 +364,7 @@ bi aC ae aQ -aq +ah bi aq bg @@ -394,7 +389,7 @@ ae bi aM ae -aq +ah bm aq aA @@ -440,7 +435,7 @@ aa ad bi bi -av +aq aA bi aG @@ -465,7 +460,7 @@ aa ad ac as -av +aq aF aD aD @@ -550,7 +545,7 @@ aD bi ae bm -ah +aq ag br ac @@ -573,9 +568,9 @@ ae af bp bi -ah +aq bi -ah +aq bp ac ad @@ -596,9 +591,9 @@ ah ao ah az -ah +aq bi -ah +aq bi bn ao @@ -621,7 +616,7 @@ ah aA ah ao -ah +aq bi bi bl @@ -646,7 +641,7 @@ ah ao aV bi -ah +aq bp bi bm diff --git a/_maps/RandomRuins/LavaRuins/lavaland_surface_mining_site.dmm b/_maps/RandomRuins/LavaRuins/lavaland_surface_mining_site.dmm index b4f7fec16f..dfe6a1b335 100644 --- a/_maps/RandomRuins/LavaRuins/lavaland_surface_mining_site.dmm +++ b/_maps/RandomRuins/LavaRuins/lavaland_surface_mining_site.dmm @@ -53,8 +53,8 @@ /turf/open/floor/wood, /area/ruin/unpowered) "m" = ( -/obj/structure/barricade/wooden/crude/snow, /obj/structure/mineral_door/wood, +/obj/effect/decal/cleanable/trail_holder, /turf/open/floor/wood, /area/ruin/unpowered) "n" = ( @@ -68,6 +68,7 @@ /obj/machinery/light/broken{ dir = 4 }, +/obj/effect/decal/cleanable/trail_holder, /turf/open/floor/wood, /area/ruin/unpowered) "q" = ( @@ -95,6 +96,10 @@ /obj/effect/decal/cleanable/trail_holder, /turf/open/floor/wood, /area/ruin/unpowered) +"N" = ( +/obj/effect/decal/cleanable/trail_holder, +/turf/open/floor/plating/asteroid/basalt/lava_land_surface, +/area/lavaland/surface/outdoors) (1,1,1) = {" i @@ -569,7 +574,7 @@ b l f f -f +s s b b @@ -595,7 +600,7 @@ b b b f -f +s p b b @@ -622,7 +627,7 @@ i b b b -f +s b b b @@ -649,7 +654,7 @@ i i i b -f +s b i i @@ -676,7 +681,7 @@ i i i b -f +s b i i @@ -703,7 +708,7 @@ i i i b -f +s b i i @@ -757,7 +762,7 @@ i i n h -n +N h n i diff --git a/_maps/map_files/PubbyStation/PubbyStation.dmm b/_maps/map_files/PubbyStation/PubbyStation.dmm index 9b77a606ca..2287ac292d 100644 --- a/_maps/map_files/PubbyStation/PubbyStation.dmm +++ b/_maps/map_files/PubbyStation/PubbyStation.dmm @@ -54557,8 +54557,8 @@ id = "xenoigniter"; luminosity = 2 }, -/obj/machinery/atmospherics/components/unary/vent_pump/on{ - dir = 8 +/obj/machinery/atmospherics/pipe/manifold/supply/hidden{ + dir = 4 }, /turf/open/floor/engine, /area/science/xenobiology) @@ -55493,7 +55493,7 @@ /obj/structure/cable{ icon_state = "1-2" }, -/obj/machinery/atmospherics/pipe/manifold/supply/hidden, +/obj/machinery/atmospherics/pipe/manifold4w/supply/hidden, /turf/open/floor/plasteel/dark, /area/science/xenobiology) "hVx" = ( @@ -55915,6 +55915,7 @@ id = "xenoigniter"; luminosity = 2 }, +/obj/machinery/atmospherics/pipe/simple/supply/hidden, /turf/open/floor/engine, /area/science/xenobiology) "iPz" = ( @@ -59469,6 +59470,7 @@ /obj/machinery/atmospherics/components/unary/vent_scrubber/on{ dir = 8 }, +/obj/machinery/atmospherics/pipe/simple/supply/hidden, /turf/open/floor/engine, /area/science/xenobiology) "qAx" = ( @@ -60673,6 +60675,7 @@ /area/maintenance/department/science) "tfP" = ( /obj/item/beacon, +/obj/machinery/atmospherics/pipe/simple/supply/hidden, /turf/open/floor/engine, /area/science/xenobiology) "tfZ" = ( @@ -114727,13 +114730,13 @@ abI aby aaa xKc -blX +lCN qAk iPj tfP geN qAk -blX +uzB xKc aaa aby diff --git a/auxmos.dll b/auxmos.dll index 54fb0c6c98..1506ba75e7 100644 Binary files a/auxmos.dll and b/auxmos.dll differ diff --git a/auxmos.pdb b/auxmos.pdb index 031e66d103..a8ec5f5a67 100644 Binary files a/auxmos.pdb and b/auxmos.pdb differ diff --git a/code/__DEFINES/_flags/obj_flags.dm b/code/__DEFINES/_flags/obj_flags.dm index e48146a1d0..ac772c6e8e 100644 --- a/code/__DEFINES/_flags/obj_flags.dm +++ b/code/__DEFINES/_flags/obj_flags.dm @@ -15,6 +15,7 @@ #define BLOCK_Z_IN_UP (1<<12) // Should this object block z uprise from below? #define SHOVABLE_ONTO (1<<13)//called on turf.shove_act() to consider whether an object should have a niche effect (defined in their own shove_act()) when someone is pushed onto it, or do a sanity CanPass() check. #define EXAMINE_SKIP (1<<14) /// Makes the Examine proc not read out this item. +#define IN_STORAGE (1<<15) //is this item in the storage item, such as backpack? used for tooltips /// Integrity defines for clothing (not flags but close enough) #define CLOTHING_PRISTINE 0 // We have no damage on the clothing diff --git a/code/__DEFINES/atmospherics.dm b/code/__DEFINES/atmospherics.dm index 67d16e6687..1c6b7c21d4 100644 --- a/code/__DEFINES/atmospherics.dm +++ b/code/__DEFINES/atmospherics.dm @@ -155,10 +155,10 @@ //LAVALAND #define LAVALAND_EQUIPMENT_EFFECT_PRESSURE 50 //what pressure you have to be under to increase the effect of equipment meant for lavaland -#define LAVALAND_DEFAULT_ATMOS "o2=14;n2=23;TEMP=300" +#define LAVALAND_DEFAULT_ATMOS "LAVALAND_ATMOS" //SNOSTATION -#define ICEMOON_DEFAULT_ATMOS "o2=17;n2=63;TEMP=180" +#define ICEMOON_DEFAULT_ATMOS "ICEMOON_ATMOS" //ATMOSIA GAS MONITOR TAGS #define ATMOS_GAS_MONITOR_INPUT_O2 "o2_in" diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 3ab70126e3..66912fa787 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -437,8 +437,9 @@ #define COMPONENT_BLOCK_SHARPEN_BLOCKED 2 #define COMPONENT_BLOCK_SHARPEN_ALREADY 4 #define COMPONENT_BLOCK_SHARPEN_MAXED 8 -#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M) +#define COMSIG_ITEM_MICROWAVE_ACT "microwave_act" //called on item when microwaved (): (obj/machinery/microwave/M) #define COMSIG_ITEM_WORN_OVERLAYS "item_worn_overlays" //from base of obj/item/worn_overlays(): (isinhands, icon_file, used_state, style_flags, list/overlays) +#define COMSIG_ARMOR_PLATED "armor_plated" //called when an armor plate is successfully applied to an object // THE FOLLOWING TWO BLOCKS SHOULD RETURN BLOCK FLAGS AS DEFINED IN __DEFINES/combat.dm! #define COMSIG_ITEM_CHECK_BLOCK "check_block" //from base of obj/item/check_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) #define COMSIG_ITEM_RUN_BLOCK "run_block" //from base of obj/item/run_block(): (mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) diff --git a/code/__DEFINES/footsteps.dm b/code/__DEFINES/footsteps.dm index 215e287adf..cc0c6547b0 100644 --- a/code/__DEFINES/footsteps.dm +++ b/code/__DEFINES/footsteps.dm @@ -8,6 +8,7 @@ #define FOOTSTEP_LAVA "lava" #define FOOTSTEP_MEAT "meat" #define FOOTSTEP_RUST "rust" +#define FOOTSTEP_CATWALK "catwalk" //barefoot sounds #define FOOTSTEP_WOOD_BAREFOOT "woodbarefoot" @@ -94,6 +95,12 @@ GLOBAL_LIST_INIT(footstep, list( 'sound/effects/footstep/lava3.ogg'), 100, 0), FOOTSTEP_MEAT = list(list( 'sound/effects/meatslap.ogg'), 100, 0), + FOOTSTEP_CATWALK = list(list( + 'sound/effects/footstep/catwalk1.ogg', + 'sound/effects/footstep/catwalk2.ogg', + 'sound/effects/footstep/catwalk3.ogg', + 'sound/effects/footstep/catwalk4.ogg', + 'sound/effects/footstep/catwalk5.ogg'), 100, 1), FOOTSTEP_RUST = list(list( 'sound/effects/footstep/rustystep1.ogg'), 100, 0) )) diff --git a/code/__DEFINES/layers_planes.dm b/code/__DEFINES/layers_planes.dm index ea7b1b2e0a..c7be89721e 100644 --- a/code/__DEFINES/layers_planes.dm +++ b/code/__DEFINES/layers_planes.dm @@ -64,8 +64,9 @@ #define GAS_PIPE_VISIBLE_LAYER 2.47 #define GAS_FILTER_LAYER 2.48 #define GAS_PUMP_LAYER 2.49 - #define LOW_OBJ_LAYER 2.5 +///catwalk overlay of /turf/open/floor/plating/plating_catwalk +#define CATWALK_LAYER 2.51 #define LOW_SIGIL_LAYER 2.52 #define SIGIL_LAYER 2.54 #define HIGH_SIGIL_LAYER 2.56 diff --git a/code/__DEFINES/mobs.dm b/code/__DEFINES/mobs.dm index 850a2b0f39..6a63ced634 100644 --- a/code/__DEFINES/mobs.dm +++ b/code/__DEFINES/mobs.dm @@ -347,3 +347,7 @@ // / Breathing types. Lungs can access either by these or by a string, which will be considered a gas ID. #define BREATH_OXY /datum/breathing_class/oxygen #define BREATH_PLASMA /datum/breathing_class/plasma + +//Gremlins +#define NPC_TAMPER_ACT_FORGET 1 //Don't try to tamper with this again +#define NPC_TAMPER_ACT_NOMSG 2 //Don't produce a visible message diff --git a/code/__DEFINES/qdel.dm b/code/__DEFINES/qdel.dm index 63259774fa..32e0025ab2 100644 --- a/code/__DEFINES/qdel.dm +++ b/code/__DEFINES/qdel.dm @@ -1,30 +1,44 @@ -//defines that give qdel hints. these can be given as a return in destory() or by calling +//! Defines that give qdel hints. +//! +//! These can be given as a return in [/atom/proc/Destroy] or by calling [/proc/qdel]. + +/// `qdel` should queue the object for deletion. +#define QDEL_HINT_QUEUE 0 +/// `qdel` should let the object live after calling [/atom/proc/Destroy]. +#define QDEL_HINT_LETMELIVE 1 +/// Functionally the same as the above. `qdel` should assume the object will gc on its own, and not check it. +#define QDEL_HINT_IWILLGC 2 +/// Qdel should assume this object won't GC, and queue a hard delete using a hard reference. +#define QDEL_HINT_HARDDEL 3 +// Qdel should assume this object won't gc, and hard delete it posthaste. +#define QDEL_HINT_HARDDEL_NOW 4 -#define QDEL_HINT_QUEUE 0 //qdel should queue the object for deletion. -#define QDEL_HINT_LETMELIVE 1 //qdel should let the object live after calling destory. -#define QDEL_HINT_IWILLGC 2 //functionally the same as the above. qdel should assume the object will gc on its own, and not check it. -#define QDEL_HINT_HARDDEL 3 //qdel should assume this object won't gc, and queue a hard delete using a hard reference. -#define QDEL_HINT_HARDDEL_NOW 4 //qdel should assume this object won't gc, and hard del it post haste. - -#ifdef LEGACY_REFERENCE_TRACKING -/** If LEGACY_REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb. - * - * Functionally identical to QDEL_HINT_QUEUE if GC_FAILURE_HARD_LOOKUP is not enabled in _compiler_options.dm. +#ifdef REFERENCE_TRACKING +/** If REFERENCE_TRACKING is enabled, qdel will call this object's find_references() verb. + * + * Functionally identical to [QDEL_HINT_QUEUE] if [GC_FAILURE_HARD_LOOKUP] is not enabled in _compiler_options.dm. */ -#define QDEL_HINT_FINDREFERENCE 5 -/// Behavior as QDEL_HINT_FINDREFERENCE, but only if the GC fails and a hard delete is forced. +#define QDEL_HINT_FINDREFERENCE 5 +/// Behavior as [QDEL_HINT_FINDREFERENCE], but only if the GC fails and a hard delete is forced. #define QDEL_HINT_IFFAIL_FINDREFERENCE 6 #endif - #define GC_QUEUE_CHECK 1 #define GC_QUEUE_HARDDELETE 2 #define GC_QUEUE_COUNT 2 //increase this when adding more steps. +#define QDEL_ITEM_ADMINS_WARNED (1<<0) //! Set when admins are told about lag causing qdels in this type. +#define QDEL_ITEM_SUSPENDED_FOR_LAG (1<<1) //! Set when a type can no longer be hard deleted on failure because of lag it causes while this happens. + +// Defines for the [gc_destroyed][/datum/var/gc_destroyed] var. #define GC_QUEUED_FOR_QUEUING -1 #define GC_CURRENTLY_BEING_QDELETED -2 +// Defines for the time left for an item to get its reference cleaned +#define GC_FILTER_QUEUE 5 MINUTES +#define GC_DEL_QUEUE 10 SECONDS + #define QDELING(X) (X.gc_destroyed) #define QDELETED(X) (!X || QDELING(X)) #define QDESTROYING(X) (!X || X.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) diff --git a/code/__DEFINES/rust_g.dm b/code/__DEFINES/rust_g.dm index 988acd3dae..0da05e7c1d 100644 --- a/code/__DEFINES/rust_g.dm +++ b/code/__DEFINES/rust_g.dm @@ -38,6 +38,9 @@ #define RUST_G (__rust_g || __detect_rust_g()) #endif +/// Gets the version of rust_g +/proc/rustg_get_version() return call(RUST_G, "get_version")() + /** * This proc generates a cellular automata noise grid which can be used in procedural generation methods. * @@ -77,8 +80,8 @@ #define RUSTG_HTTP_METHOD_PATCH "patch" #define RUSTG_HTTP_METHOD_HEAD "head" #define RUSTG_HTTP_METHOD_POST "post" -#define rustg_http_request_blocking(method, url, body, headers) call(RUST_G, "http_request_blocking")(method, url, body, headers) -#define rustg_http_request_async(method, url, body, headers) call(RUST_G, "http_request_async")(method, url, body, headers) +#define rustg_http_request_blocking(method, url, body, headers, options) call(RUST_G, "http_request_blocking")(method, url, body, headers, options) +#define rustg_http_request_async(method, url, body, headers, options) call(RUST_G, "http_request_async")(method, url, body, headers, options) #define rustg_http_check_request(req_id) call(RUST_G, "http_check_request")(req_id) #define RUSTG_JOB_NO_RESULTS_YET "NO RESULTS YET" @@ -99,3 +102,11 @@ #define rustg_sql_disconnect_pool(handle) call(RUST_G, "sql_disconnect_pool")(handle) #define rustg_sql_check_query(job_id) call(RUST_G, "sql_check_query")("[job_id]") +#define rustg_url_encode(text) call(RUST_G, "url_encode")(text) +#define rustg_url_decode(text) call(RUST_G, "url_decode")(text) + +#ifdef RUSTG_OVERRIDE_BUILTINS + #define url_encode(text) rustg_url_encode(text) + #define url_decode(text) rustg_url_decode(text) +#endif + diff --git a/code/__DEFINES/rust_g_overrides.dm b/code/__DEFINES/rust_g_overrides.dm new file mode 100644 index 0000000000..57de7d96ac --- /dev/null +++ b/code/__DEFINES/rust_g_overrides.dm @@ -0,0 +1,3 @@ +// RUSTG_OVERRIDE_BUILTINS is not used since the file APIs don't work well over Linux. +#define url_encode(text) rustg_url_encode("[text]") +#define url_decode(text) rustg_url_decode("[text]") diff --git a/code/__DEFINES/time.dm b/code/__DEFINES/time.dm index 01990ddc17..252fe0910e 100644 --- a/code/__DEFINES/time.dm +++ b/code/__DEFINES/time.dm @@ -49,10 +49,16 @@ When using time2text(), please use "DDD" to find the weekday. Refrain from using #define TICKS *world.tick_lag +#define MILLISECONDS * 0.01 + #define DS2TICKS(DS) ((DS)/world.tick_lag) #define TICKS2DS(T) ((T) TICKS) +#define MS2DS(T) ((T) MILLISECONDS) + +#define DS2MS(T) ((T) * 100) + #define GAMETIMESTAMP(format, wtime) time2text(wtime, format) #define WORLDTIME2TEXT(format) GAMETIMESTAMP(format, world.time) #define WORLDTIMEOFDAY2TEXT(format) GAMETIMESTAMP(format, world.timeofday) diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index eb9bdccc22..cd94e5790b 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -343,3 +343,7 @@ #define STICKY_NODROP "sticky-nodrop" //sticky nodrop sounds like a bad soundcloud rapper's name #define TRAIT_SACRIFICED "sacrificed" //Makes sure that people cant be cult sacrificed twice. #define TRAIT_SPACEWALK "spacewalk" + + +/// obtained from mapping helper +#define MAPPING_HELPER_TRAIT "mapping-helper" diff --git a/code/__HELPERS/_logging.dm b/code/__HELPERS/_logging.dm index 8464e373d5..d61adf4337 100644 --- a/code/__HELPERS/_logging.dm +++ b/code/__HELPERS/_logging.dm @@ -1,9 +1,12 @@ //wrapper macros for easier grepping #define DIRECT_OUTPUT(A, B) A << B +#define DIRECT_INPUT(A, B) A >> B #define SEND_IMAGE(target, image) DIRECT_OUTPUT(target, image) #define SEND_SOUND(target, sound) DIRECT_OUTPUT(target, sound) #define SEND_TEXT(target, text) DIRECT_OUTPUT(target, text) #define WRITE_FILE(file, text) DIRECT_OUTPUT(file, text) +#define READ_FILE(file, text) DIRECT_INPUT(file, text) + #ifdef EXTOOLS_LOGGING // proc hooked, so we can just put in standard TRUE and FALSE #define WRITE_LOG(log, text) extools_log_write(log,text,TRUE) @@ -13,6 +16,7 @@ #define WRITE_LOG(log, text) rustg_log_write(log, text, "true") #define WRITE_LOG_NO_FORMAT(log, text) rustg_log_write(log, text, "false") #endif + //print a warning message to world.log #define WARNING(MSG) warning("[MSG] in [__FILE__] at line [__LINE__] src: [UNLINT(src)] usr: [usr].") /proc/warning(msg) @@ -38,6 +42,12 @@ SEND_TEXT(world.log, text) #endif +#ifdef REFERENCE_TRACKING_LOG +#define log_reftracker(msg) log_world("## REF SEARCH [msg]") +#else +#define log_reftracker(msg) +#endif + /* Items with ADMINPRIVATE prefixed are stripped from public logs. */ /proc/log_admin(text) diff --git a/code/__HELPERS/global_lists.dm b/code/__HELPERS/global_lists.dm index 0a17f0d1df..19aac1aee0 100644 --- a/code/__HELPERS/global_lists.dm +++ b/code/__HELPERS/global_lists.dm @@ -78,6 +78,12 @@ var/datum/emote/E = new path() E.emote_list[E.key] = E + // Hair Gradients - Initialise all /datum/sprite_accessory/hair_gradient into an list indexed by gradient-style name + for(var/path in subtypesof(/datum/sprite_accessory/hair_gradient)) + var/datum/sprite_accessory/hair_gradient/H = new path() + GLOB.hair_gradients_list[H.name] = H + + // Keybindings init_keybindings() //Uplink Items diff --git a/code/__HELPERS/markov.dm b/code/__HELPERS/markov.dm new file mode 100644 index 0000000000..7e195f8b9c --- /dev/null +++ b/code/__HELPERS/markov.dm @@ -0,0 +1,61 @@ +#define MAXIMUM_MARKOV_LENGTH 25000 + +/proc/markov_chain(var/text, var/order = 4, var/length = 250) + if(!text || order < 0 || order > 20 || length < 1 || length > MAXIMUM_MARKOV_LENGTH) + return + + var/table = markov_table(text, order) + var/markov = markov_text(length, table, order) + return markov + +/proc/markov_table(var/text, var/look_forward = 4) + if(!text) + return + var/list/table = list() + + for(var/i = 1, i <= length(text), i++) + var/char = copytext(text, i, look_forward+i) + if(!table[char]) + table[char] = list() + + for(var/i = 1, i <= (length(text) - look_forward), i++) + var/char_index = copytext(text, i, look_forward+i) + var/char_count = copytext(text, i+look_forward, (look_forward*2)+i) + + if(table[char_index][char_count]) + table[char_index][char_count]++ + else + table[char_index][char_count] = 1 + + return table + +/proc/markov_text(var/length = 250, var/table, var/look_forward = 4) + if(!table) + return + var/char = pick(table) + var/o = char + + for(var/i = 0, i <= (length / look_forward), i++) + var/newchar = markov_weighted_char(table[char]) + + if(newchar) + char = newchar + o += "[newchar]" + else + char = pick(table) + + return o + +/proc/markov_weighted_char(var/list/array) + if(!array || !array.len) + return + + var/total = 0 + for(var/i in array) + total += array[i] + var/r = rand(1, total) + for(var/i in array) + var/weight = array[i] + if(r <= weight) + return i + r -= weight diff --git a/code/__HELPERS/roundend.dm b/code/__HELPERS/roundend.dm index 2c13f30707..9a59b2b0a9 100644 --- a/code/__HELPERS/roundend.dm +++ b/code/__HELPERS/roundend.dm @@ -218,20 +218,14 @@ to_chat(world, "


The round has ended.") log_game("The round has ended.") - if(LAZYLEN(GLOB.round_end_notifiees)) - world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE) + + CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed for(var/I in round_end_events) var/datum/callback/cb = I cb.InvokeAsync() LAZYCLEARLIST(round_end_events) - for(var/client/C in GLOB.clients) - if(!C.credits) - C.RollCredits() - C.playtitlemusic(40) - CONFIG_SET(flag/suicide_allowed,TRUE) // EORG suicides allowed - var/speed_round = FALSE if(world.time - SSticker.round_start_time <= 300 SECONDS) speed_round = TRUE @@ -264,12 +258,15 @@ send2adminchat("Server", "A round of [mode.name] just ended[mode_result == "undefined" ? "." : " with a [mode_result]."] Survival rate: [survival_rate]") + if(LAZYLEN(GLOB.round_end_notifiees)) + world.TgsTargetedChatBroadcast("[GLOB.round_end_notifiees.Join(", ")] the round has ended.", FALSE) + if(length(CONFIG_GET(keyed_list/cross_server))) send_news_report() //tell the nice people on discord what went on before the salt cannon happens. world.TgsTargetedChatBroadcast("The current round has ended. Please standby for your shift interlude Nanotrasen News Network's report!", FALSE) - world.TgsTargetedChatBroadcast(send_news_report(),FALSE) + world.TgsTargetedChatBroadcast(send_news_report(), FALSE) CHECK_TICK @@ -543,21 +540,24 @@ ///Generate a report for how much money is on station, as well as the richest crewmember on the station. /datum/controller/subsystem/ticker/proc/market_report() var/list/parts = list() - parts += "Station Economic Summary:" + ///This is the richest account on station at roundend. var/datum/bank_account/mr_moneybags ///This is the station's total wealth at the end of the round. var/station_vault = 0 ///How many players joined the round. var/total_players = GLOB.joined_player_list.len - var/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote)) - for(var/datum/bank_account/current_acc in SSeconomy.generated_accounts) + var/static/list/typecache_bank = typecacheof(list(/datum/bank_account/department, /datum/bank_account/remote)) + for(var/i in SSeconomy.generated_accounts) + var/datum/bank_account/current_acc = SSeconomy.generated_accounts[i] if(typecache_bank[current_acc.type]) continue station_vault += current_acc.account_balance if(!mr_moneybags || mr_moneybags.account_balance < current_acc.account_balance) mr_moneybags = current_acc - parts += "
There were [station_vault] credits collected by crew this shift.
" + parts += "
Station Economic Summary:
" + parts += "General Statistics:
" + parts += "There were [station_vault] credits collected by crew this shift.
" if(total_players > 0) parts += "An average of [station_vault/total_players] credits were collected.
" // log_econ("Roundend credit total: [station_vault] credits. Average Credits: [station_vault/total_players]") diff --git a/code/_compile_options.dm b/code/_compile_options.dm index 64b4129024..1aca8959c2 100644 --- a/code/_compile_options.dm +++ b/code/_compile_options.dm @@ -1,7 +1,7 @@ -//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this +//#define TESTING //By using the testing("message") proc you can create debug-feedback for people with this //uncommented, but not visible in the release version) -//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed. +//#define DATUMVAR_DEBUGGING_MODE //Enables the ability to cache datum vars and retrieve later for debugging which vars changed. // Comment this out if you are debugging problems that might be obscured by custom error handling in world/Error #ifdef DEBUG @@ -11,34 +11,44 @@ #ifdef TESTING #define DATUMVAR_DEBUGGING_MODE -/* -* Enables extools-powered reference tracking system, letting you see what is referencing objects that refuse to hard delete. -* -* * Requires TESTING to be defined to work. -*/ +///Used to find the sources of harddels, quite laggy, don't be surpised if it freezes your client for a good while //#define REFERENCE_TRACKING +#ifdef REFERENCE_TRACKING -///Method of tracking references without using extools. Slower, kept to avoid over-reliance on extools. -//#define LEGACY_REFERENCE_TRACKING -#ifdef LEGACY_REFERENCE_TRACKING +///alternate to reftracking, extool variant +//#define EXTOOLS_REFERENCE_TRACKING -///Use the legacy reference on things hard deleting by default. +///Should we be logging our findings or not +#define REFERENCE_TRACKING_LOG + +///Used for doing dry runs of the reference finder, to test for feature completeness +//#define REFERENCE_TRACKING_DEBUG + +///Run a lookup on things hard deleting by default. //#define GC_FAILURE_HARD_LOOKUP #ifdef GC_FAILURE_HARD_LOOKUP #define FIND_REF_NO_CHECK_TICK #endif //ifdef GC_FAILURE_HARD_LOOKUP -#endif //ifdef LEGACY_REFERENCE_TRACKING +#endif //ifdef REFERENCE_TRACKING -//#define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green +/* +* Enables debug messages for every single reaction step. This is 1 message per 0.5s for a SINGLE reaction. Useful for tracking down bugs/asking me for help in the main reaction handiler (equilibrium.dm). +* +* * Requires TESTING to be defined to work. +*/ +//#define REAGENTS_TESTING +// #define VISUALIZE_ACTIVE_TURFS //Highlights atmos active turfs in green +// #define TRACK_MAX_SHARE //Allows max share tracking, for use in the atmos debugging ui #endif //ifdef TESTING -//#define UNIT_TESTS //Enables unit tests via TEST_RUN_PARAMETER -#ifndef PRELOAD_RSC //set to: -#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour; -#endif // 1 to use the default behaviour; - // 2 for preloading absolutely everything; +//#define UNIT_TESTS //If this is uncommented, we do a single run though of the game setup and tear down process with unit tests in between + +#ifndef PRELOAD_RSC //set to: +#define PRELOAD_RSC 2 // 0 to allow using external resources or on-demand behaviour; +#endif // 1 to use the default behaviour; + // 2 for preloading absolutely everything; #ifdef LOWMEMORYMODE #define FORCE_MAP "_maps/runtimestation.json" @@ -47,7 +57,7 @@ //Update this whenever you need to take advantage of more recent byond features #define MIN_COMPILER_VERSION 513 #define MIN_COMPILER_BUILD 1514 -#if DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD +#if (DM_VERSION < MIN_COMPILER_VERSION || DM_BUILD < MIN_COMPILER_BUILD) && !defined(SPACEMAN_DMM) //Don't forget to update this part #error Your version of BYOND is too out-of-date to compile this project. Go to https://secure.byond.com/download and update. #error You need version 513.1514 or higher @@ -58,10 +68,6 @@ #warn compiling in TESTING mode. testing() debug messages will be visible. #endif -#ifdef GC_FAILURE_HARD_LOOKUP -#define FIND_REF_NO_CHECK_TICK -#endif - #ifdef CIBUILDING #define UNIT_TESTS #endif @@ -70,6 +76,24 @@ #define TESTING #endif +#if defined(UNIT_TESTS) +//Hard del testing defines +#define REFERENCE_TRACKING +#define REFERENCE_TRACKING_DEBUG +#define FIND_REF_NO_CHECK_TICK +#endif + +#ifdef TGS +// TGS performs its own build of dm.exe, but includes a prepended TGS define. +#define CBT +#endif + // A reasonable number of maximum overlays an object needs // If you think you need more, rethink it #define MAX_ATOM_OVERLAYS 100 + +#if !defined(CBT) && !defined(SPACEMAN_DMM) +#warn Building with Dream Maker is no longer supported and will result in errors. +#warn In order to build, run BUILD.bat in the root directory. +#warn Consider switching to VSCode editor instead, where you can press Ctrl+Shift+B to build. +#endif diff --git a/code/_globalvars/bitfields.dm b/code/_globalvars/bitfields.dm index 781fa38b94..c8e5b72f26 100644 --- a/code/_globalvars/bitfields.dm +++ b/code/_globalvars/bitfields.dm @@ -71,6 +71,7 @@ GLOBAL_LIST_INIT(bitfields, list( "DROPDEL" = DROPDEL, "NOBLUDGEON" = NOBLUDGEON, "ABSTRACT" = ABSTRACT, + "IN_STORAGE" = IN_STORAGE, "ITEM_CAN_BLOCK" = ITEM_CAN_BLOCK, "ITEM_CAN_PARRY" = ITEM_CAN_PARRY ), diff --git a/code/_globalvars/lists/flavor_misc.dm b/code/_globalvars/lists/flavor_misc.dm index 44101bea70..0110d43e04 100644 --- a/code/_globalvars/lists/flavor_misc.dm +++ b/code/_globalvars/lists/flavor_misc.dm @@ -6,6 +6,7 @@ GLOBAL_LIST_EMPTY(hair_styles_female_list) //stores only hair names GLOBAL_LIST_EMPTY(facial_hair_styles_list) //stores /datum/sprite_accessory/facial_hair indexed by name GLOBAL_LIST_EMPTY(facial_hair_styles_male_list) //stores only hair names GLOBAL_LIST_EMPTY(facial_hair_styles_female_list) //stores only hair names +GLOBAL_LIST_EMPTY(hair_gradients_list) //stores /datum/sprite_accessory/hair_gradient indexed by name //Underwear GLOBAL_LIST_EMPTY_TYPED(underwear_list, /datum/sprite_accessory/underwear/bottom) //stores bottoms indexed by name GLOBAL_LIST_EMPTY(underwear_m) //stores only underwear name diff --git a/code/_globalvars/lists/maintenance_loot.dm b/code/_globalvars/lists/maintenance_loot.dm index daa4f5879d..b41b7356a8 100644 --- a/code/_globalvars/lists/maintenance_loot.dm +++ b/code/_globalvars/lists/maintenance_loot.dm @@ -112,7 +112,6 @@ GLOBAL_LIST_INIT(maintenance_loot, list( /obj/item/storage/box/marshmallow = 2, /obj/item/clothing/gloves/tackler/offbrand = 1, /obj/item/stack/sticky_tape = 1, - /obj/effect/spawner/lootdrop/grille_or_trash = 15, "" = 3 )) diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index 9321427387..1b72cc71b1 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -63,6 +63,8 @@ return ShiftClickOn(A) if(modifiers["alt"]) // alt and alt-gr (rightalt) return AltClickOn(A) + if(modifiers["ctrl"] && modifiers["right"]) //CIT CHANGE - right click ctrl for a new form of dropping items + return CtrlRightClickOn(A, params) //CIT CHANGE if(modifiers["ctrl"]) return CtrlClickOn(A) @@ -295,6 +297,46 @@ if(!(flags & COMPONENT_DENY_EXAMINATE) && user.client && (user.client.eye == user || user.client.eye == user.loc || flags & COMPONENT_ALLOW_EXAMINATE)) user.examinate(src) +/* + Ctrl + Right click + Combat mode feature + Drop item in hand at position. +*/ +/atom/proc/CtrlRightClickOn(atom/A, params) + if(isliving(src) && Adjacent(A)) //honestly only humans can do this given it's combat mode but if it's implemented for any other mobs... + var/mob/living/L = src + if(L.incapacitated()) + return + var/obj/item/I = L.get_active_held_item() + var/turf/T = get_turf(A) + if(T) + if(I) //drop item at cursor. + if(T.density) //no, you can't use your funny blue cube or red cube to clip into the fucking wall. + return + for(var/atom/C in T.contents) //nor can you clip into a window or a door/false wall that's not open. + if(C.opacity || (((C.flags_1 & PREVENT_CLICK_UNDER_1) > 0) != (istype(C,/obj/machinery/door) && !C.density))) //XOR operation within because doors always have PREVENT_CLICK_UNDER_1 flag enabled. Dumb, I know. + return + if(L.transferItemToLoc(I, T)) + var/list/click_params = params2list(params) + //Center the icon where the user clicked. (shamelessly stole code from tables) + if(!click_params || !click_params["icon-x"] || !click_params["icon-y"]) + return + //Clamp it so that the icon never moves more than 16 pixels in either direction + I.pixel_x = clamp(text2num(click_params["icon-x"]) - 16, -(world.icon_size/2), world.icon_size/2) + I.pixel_y = clamp(text2num(click_params["icon-y"]) - 16, -(world.icon_size/2), world.icon_size/2) + return TRUE + else if(isitem(A) && L.has_active_hand()) //if they have an open hand they'll rotate the item instead. + var/obj/item/I2 = A + if(!I2.anchored) + var/matrix/ntransform = matrix(I2.transform) + ntransform.Turn(15) + animate(I2, transform = ntransform, time = 2) + return TRUE + else + A.CtrlClick(src) + + + /* Ctrl click For most objects, pull diff --git a/code/_onclick/cyborg.dm b/code/_onclick/cyborg.dm index cf3f45e196..743d75b557 100644 --- a/code/_onclick/cyborg.dm +++ b/code/_onclick/cyborg.dm @@ -43,7 +43,7 @@ INVOKE_ASYNC(aicamera, /obj/item/camera.proc/captureimage, A, usr) return - var/obj/item/W = get_active_held_item() + var/obj/item/W = get_active_held_item(TRUE) if(!W && A.Adjacent(src) && (isobj(A) || ismob(A))) var/atom/movable/C = A diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm index bfbdce41fd..56db868a21 100644 --- a/code/_onclick/hud/screen_objects.dm +++ b/code/_onclick/hud/screen_objects.dm @@ -141,11 +141,29 @@ /atom/movable/screen/inventory/MouseEntered() ..() add_overlays() + //Apply the outline affect + add_stored_outline() /atom/movable/screen/inventory/MouseExited() ..() cut_overlay(object_overlays) object_overlays.Cut() + remove_stored_outline() + +/atom/movable/screen/inventory/proc/add_stored_outline() + if(hud?.mymob && slot_id) + var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id) + if(inv_item) + if(hud?.mymob.incapacitated()) + inv_item.apply_outline(COLOR_RED_GRAY) + else + inv_item.apply_outline() + +/atom/movable/screen/inventory/proc/remove_stored_outline() + if(hud?.mymob && slot_id) + var/obj/item/inv_item = hud.mymob.get_item_by_slot(slot_id) + if(inv_item) + inv_item.remove_outline() /atom/movable/screen/inventory/update_icon_state() if(!icon_empty) diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm index 19fef28597..5c767ecb1b 100644 --- a/code/controllers/admin.dm +++ b/code/controllers/admin.dm @@ -1,14 +1,25 @@ // Clickable stat() button. /obj/effect/statclick name = "Initializing..." + // blocks_emissive = NONE var/target INITIALIZE_IMMEDIATE(/obj/effect/statclick) -/obj/effect/statclick/Initialize(mapload, text, target) //Don't port this to Initialize it's too critical +/obj/effect/statclick/Initialize(mapload, text, target) . = ..() name = text src.target = target + if(istype(target, /datum)) //Harddel man bad + RegisterSignal(target, COMSIG_PARENT_QDELETING, .proc/cleanup) + +/obj/effect/statclick/Destroy() + target = null + return ..() + +/obj/effect/statclick/proc/cleanup() + SIGNAL_HANDLER + qdel(src) /obj/effect/statclick/proc/update(text) name = text @@ -51,3 +62,30 @@ INITIALIZE_IMMEDIATE(/obj/effect/statclick) SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller") message_admins("Admin [key_name_admin(usr)] has restarted the [controller] controller.") + +/client/proc/debug_controller() + set category = "Debug" + set name = "Debug Controller" + set desc = "Debug the various periodic loop controllers for the game (be careful!)" + + if(!holder) + return + + var/list/controllers = list() + var/list/controller_choices = list() + + for (var/datum/controller/controller in world) + if (istype(controller, /datum/controller/subsystem)) + continue + controllers["[controller] (controller.type)"] = controller //we use an associated list to ensure clients can't hold references to controllers + controller_choices += "[controller] (controller.type)" + + var/datum/controller/controller_string = input("Select controller to debug", "Debug Controller") as null|anything in controller_choices + var/datum/controller/controller = controllers[controller_string] + + if (!istype(controller)) + return + debug_variables(controller) + + SSblackbox.record_feedback("tally", "admin_verb", 1, "Restart Failsafe Controller") + message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.") diff --git a/code/controllers/configuration/entries/admin.dm b/code/controllers/configuration/entries/admin.dm index 3b5b437742..1f9b5d460f 100644 --- a/code/controllers/configuration/entries/admin.dm +++ b/code/controllers/configuration/entries/admin.dm @@ -39,7 +39,9 @@ /datum/config_entry/flag/announce_admin_login -/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API +/datum/config_entry/string/centcom_ban_db // URL for the CentCom Galactic Ban DB API + +/datum/config_entry/string/centcom_source_whitelist /datum/config_entry/flag/autoadmin // if autoadmin is enabled protection = CONFIG_ENTRY_LOCKED diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 8ecd7c0c60..d828457fa7 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -333,3 +333,12 @@ config_entry_value = 0.333 min_val = 0 integer = FALSE + +/datum/config_entry/number/hard_deletes_overrun_threshold + integer = FALSE + min_val = 0 + default = 0.5 + +/datum/config_entry/number/hard_deletes_overrun_limit + default = 0 + min_val = 0 diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index a15056e442..04b88eb3be 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -15,7 +15,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe) // The alert level. For every failed poke, we drop a DEFCON level. Once we hit DEFCON 1, restart the MC. var/defcon = 5 //the world.time of the last check, so the mc can restart US if we hang. - // (Real friends look out for *eachother*) + // (Real friends look out for *eachother*) var/lasttick = 0 // Track the MC iteration to make sure its still on track. @@ -33,6 +33,22 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe) /datum/controller/failsafe/Initialize() set waitfor = FALSE Failsafe.Loop() + if (!Master || defcon == 0) //Master is gone/not responding and Failsafe just exited its loop + defcon = 3 //Reset defcon level as its used inside the emergency loop + while (defcon > 0) + var/recovery_result = emergency_loop() + if (recovery_result == 1) //Exit emergency loop and delete self if it was able to recover MC + break + else if (defcon == 1) //Exit Failsafe if we weren't able to recover the MC in the last stage + log_game("FailSafe: Failed to recover MC while in emergency state. Failsafe exiting.") + message_admins(span_boldannounce("Failsafe failed criticaly while trying to recreate broken MC. Please manually fix the MC or reboot the server. Failsafe exiting now.")) + message_admins(span_boldannounce("You can try manually calling these two procs:.")) + message_admins(span_boldannounce("/proc/recover_all_SS_and_recreate_master: Most stuff should still function but expect instability/runtimes/broken stuff.")) + message_admins(span_boldannounce("/proc/delete_all_SS_and_recreate_master: Most stuff will be broken but basic stuff like movement and chat should still work.")) + else if (recovery_result == -1) //Failed to recreate MC + defcon-- + sleep(initial(processing_interval)) //Wait a bit until the next try + if(!QDELETED(src)) qdel(src) //when Loop() returns, we delete ourselves and let the mc recreate us @@ -45,42 +61,56 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe) while(running) lasttick = world.time if(!Master) - // Replace the missing Master! This should never, ever happen. - new /datum/controller/master() + // Break out of the main loop so we go into emergency state + break // Only poke it if overrides are not in effect. if(processing_interval > 0) if(Master.processing && Master.iteration) + if (defcon > 1 && (!Master.stack_end_detector || !Master.stack_end_detector.check())) + + to_chat(GLOB.admins, span_boldannounce("ERROR: The Master Controller code stack has exited unexpectedly, Restarting...")) + defcon = 0 + var/rtn = Recreate_MC() + if(rtn > 0) + master_iteration = 0 + to_chat(GLOB.admins, span_adminnotice("MC restarted successfully")) + else if(rtn < 0) + log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") + to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.")) // Check if processing is done yet. if(Master.iteration == master_iteration) switch(defcon) if(4,5) --defcon - if(3) - message_admins("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.") - --defcon - if(2) - to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.") - --defcon - if(1) - to_chat(GLOB.admins, "Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...") + if(3) + message_admins(span_adminnotice("Notice: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks.")) + --defcon + + if(2) + to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has not fired in the last [(5-defcon) * processing_interval] ticks. Automatic restart in [processing_interval] ticks.")) + --defcon + + if(1) + to_chat(GLOB.admins, span_boldannounce("Warning: DEFCON [defcon_pretty()]. The Master Controller has still not fired within the last [(5-defcon) * processing_interval] ticks. Killing and restarting...")) --defcon var/rtn = Recreate_MC() if(rtn > 0) defcon = 4 master_iteration = 0 - to_chat(GLOB.admins, "MC restarted successfully") + to_chat(GLOB.admins, span_adminnotice("MC restarted successfully")) else if(rtn < 0) log_game("FailSafe: Could not restart MC, runtime encountered. Entering defcon 0") - to_chat(GLOB.admins, "ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.") + to_chat(GLOB.admins, span_boldannounce("ERROR: DEFCON [defcon_pretty()]. Could not restart MC, runtime encountered. I will silently keep retrying.")) //if the return number was 0, it just means the mc was restarted too recently, and it just needs some time before we try again //no need to handle that specially when defcon 0 can handle it + if(0) //DEFCON 0! (mc failed to restart) var/rtn = Recreate_MC() if(rtn > 0) defcon = 4 master_iteration = 0 - to_chat(GLOB.admins, "MC restarted successfully") + to_chat(GLOB.admins, span_adminnotice("MC restarted successfully")) else defcon = min(defcon + 1,5) master_iteration = Master.iteration @@ -92,6 +122,57 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe) defcon = 5 sleep(initial(processing_interval)) +//Emergency loop used when Master got deleted or the main loop exited while Defcon == 0 +//Loop is driven externally so runtimes only cancel the current recovery attempt +/datum/controller/failsafe/proc/emergency_loop() + //The code in this proc should be kept as simple as possible, anything complicated like to_chat might rely on master existing and runtime + //The goal should always be to get a new Master up and running before anything else + . = -1 + switch (defcon) //The lower defcon goes the harder we try to fix the MC + if (2 to 3) //Try to normally recreate the MC two times + . = Recreate_MC() + if (1) //Delete the old MC first so we don't transfer any info, in case that caused any issues + del(Master) + . = Recreate_MC() + + if (. == 1) //We were able to create a new master + master_iteration = 0 + SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set + Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this + to_chat(GLOB.admins, span_adminnotice("Failsafe recovered MC while in emergency state [defcon_pretty()]")) + else + log_game("FailSafe: Failsafe in emergency state and was unable to recreate MC while in defcon state [defcon_pretty()].") + message_admins(span_boldannounce("Failsafe in emergency state and master down, trying to recreate MC while in defcon level [defcon_pretty()] failed.")) + +///Recreate all SSs which will still cause data survive due to Recover(), the new Master will then find and take them from global.vars +/proc/recover_all_SS_and_recreate_master() + del(Master) + var/list/subsytem_types = subtypesof(/datum/controller/subsystem) + sortTim(subsytem_types, /proc/cmp_subsystem_init) + for(var/I in subsytem_types) + new I + . = Recreate_MC() + if (. == 1) //We were able to create a new master + SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set + Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this + to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after recovering all subsystems!")) + else + message_admins(span_boldannounce("Failed to create new MC!")) + +///Delete all existing SS to basically start over +/proc/delete_all_SS_and_recreate_master() + del(Master) + for(var/global_var in global.vars) + if (istype(global.vars[global_var], /datum/controller/subsystem)) + del(global.vars[global_var]) + . = Recreate_MC() + if (. == 1) //We were able to create a new master + SSticker.Recover(); //Recover the ticket system so the Masters runlevel gets set + Master.Initialize(10, FALSE, TRUE) //Need to manually start the MC, normally world.new would do this + to_chat(GLOB.admins, span_adminnotice("MC successfully recreated after deleting and recreating all subsystems!")) + else + message_admins(span_boldannounce("Failed to create new MC!")) + /datum/controller/failsafe/proc/defcon_pretty() return defcon diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm index 21f022acfd..7b5cc94d36 100644 --- a/code/controllers/globals.dm +++ b/code/controllers/globals.dm @@ -14,7 +14,7 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars) var/datum/controller/exclude_these = new gvars_datum_in_built_vars = exclude_these.vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) - QDEL_IN(exclude_these, 0) //signal logging isn't ready + QDEL_IN(exclude_these, 0) //signal logging isn't ready log_world("[vars.len - gvars_datum_in_built_vars.len] global variables") diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 59ac68960c..055271fd86 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -18,15 +18,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /datum/controller/master name = "Master" - // Are we processing (higher values increase the processing delay by n ticks) + /// Are we processing (higher values increase the processing delay by n ticks) var/processing = TRUE - // How many times have we ran + /// How many times have we ran var/iteration = 0 + /// Stack end detector to detect stack overflows that kill the mc's main loop + var/datum/stack_end_detector/stack_end_detector - // world.time of last fire, for tracking lag outside of the mc + /// world.time of last fire, for tracking lag outside of the mc var/last_run - // List of subsystems to process(). + /// List of subsystems to process(). var/list/subsystems // Vars for keeping track of tick drift. @@ -34,25 +36,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/init_time var/tickdrift = 0 + /// How long is the MC sleeping between runs, read only (set by Loop() based off of anti-tick-contention heuristics) var/sleep_delta = 1 - ///Only run ticker subsystems for the next n ticks. + /// Only run ticker subsystems for the next n ticks. var/skip_ticks = 0 + /// makes the mc main loop runtime var/make_runtime = FALSE - var/initializations_finished_with_no_players_logged_in //I wonder what this could be? + var/initializations_finished_with_no_players_logged_in //I wonder what this could be? - // The type of the last subsystem to be process()'d. + /// The type of the last subsystem to be fire()'d. var/last_type_processed - var/datum/controller/subsystem/queue_head //Start of queue linked list - var/datum/controller/subsystem/queue_tail //End of queue linked list (used for appending to the list) + var/datum/controller/subsystem/queue_head //!Start of queue linked list + var/datum/controller/subsystem/queue_tail //!End of queue linked list (used for appending to the list) var/queue_priority_count = 0 //Running total so that we don't have to loop thru the queue each run to split up the tick var/queue_priority_count_bg = 0 //Same, but for background subsystems - var/map_loading = FALSE //Are we loading in a new map? + var/map_loading = FALSE //!Are we loading in a new map? - var/current_runlevel //for scheduling different subsystems for different stages of the round + var/current_runlevel //!for scheduling different subsystems for different stages of the round var/sleep_offline_after_initializations = TRUE var/static/restart_clear = 0 @@ -61,8 +65,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/static/random_seed - //current tick limit, assigned before running a subsystem. - //used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits + ///current tick limit, assigned before running a subsystem. + ///used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits var/static/current_ticklimit = TICK_LIMIT_RUNNING /datum/controller/master/New() @@ -81,15 +85,27 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/list/_subsystems = list() subsystems = _subsystems if (Master != src) - if (istype(Master)) + if (istype(Master)) //If there is an existing MC take over his stuff and delete it Recover() qdel(Master) + Master = src else + //Code used for first master on game boot or if existing master got deleted + Master = src var/list/subsytem_types = subtypesof(/datum/controller/subsystem) sortTim(subsytem_types, /proc/cmp_subsystem_init) + //Find any abandoned subsystem from the previous master (if there was any) + var/list/existing_subsystems = list() + for(var/global_var in global.vars) + if (istype(global.vars[global_var], /datum/controller/subsystem)) + existing_subsystems += global.vars[global_var] + //Either init a new SS or if an existing one was found use that for(var/I in subsytem_types) - _subsystems += new I - Master = src + var/datum/controller/subsystem/existing_subsystem = locate(I) in existing_subsystems + if (istype(existing_subsystem)) + _subsystems += existing_subsystem + else + _subsystems += new I if(!GLOB) new /datum/controller/global_vars @@ -109,7 +125,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new log_world("Shutdown complete") // Returns 1 if we created a new mc, 0 if we couldn't due to a recent restart, -// -1 if we encountered a runtime trying to recreate it +// -1 if we encountered a runtime trying to recreate it /proc/Recreate_MC() . = -1 //so if we runtime, things know we failed if (world.time < Master.restart_timeout) @@ -120,7 +136,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/delay = 50 * ++Master.restart_count Master.restart_timeout = world.time + delay Master.restart_clear = world.time + (delay * 2) - Master.processing = FALSE //stop ticking this one + if (Master) //Can only do this if master hasn't been deleted + Master.processing = FALSE //stop ticking this one try new/datum/controller/master() catch @@ -156,22 +173,22 @@ GLOBAL_REAL(Master, /datum/controller/master) = new msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined." BadBoy.flags |= SS_NO_FIRE if(msg) - to_chat(GLOB.admins, "[msg]") + to_chat(GLOB.admins, span_boldannounce("[msg]")) log_world(msg) if (istype(Master.subsystems)) if(FireHim) - Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one + Master.subsystems += new BadBoy.type //NEW_SS_GLOBAL will remove the old one subsystems = Master.subsystems current_runlevel = Master.current_runlevel StartProcessing(10) else - to_chat(world, "The Master Controller is having some issues, we will need to re-initialize EVERYTHING") + to_chat(world, span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING")) Initialize(20, TRUE) // Please don't stuff random bullshit here, -// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize() +// Make a subsystem, give it the SS_NO_FIRE flag, and do your work in it's Initialize() /datum/controller/master/Initialize(delay, init_sss, tgs_prime) set waitfor = 0 @@ -181,7 +198,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(init_sss) init_subtypes(/datum/controller/subsystem, subsystems) - to_chat(world, "Initializing subsystems...") + to_chat(world, span_boldannounce("Initializing subsystems...")) // Sort subsystems by init_order, so they initialize in the correct order. sortTim(subsystems, /proc/cmp_subsystem_init) @@ -190,7 +207,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new // Initialize subsystems. current_ticklimit = CONFIG_GET(number/tick_limit_mc_init) for (var/datum/controller/subsystem/SS in subsystems) - if (SS.flags & SS_NO_INIT) + if (SS.flags & SS_NO_INIT || SS.initialized) //Don't init SSs with the correspondig flag or if they already are initialzized continue SS.Initialize(REALTIMEOFDAY) CHECK_TICK @@ -198,7 +215,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/time = (REALTIMEOFDAY - start_timeofday) / 10 var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!" - to_chat(world, "[msg]") + to_chat(world, span_boldannounce("[msg]")) log_world(msg) if (!current_runlevel) @@ -255,11 +272,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /datum/controller/master/proc/Loop() . = -1 //Prep the loop (most of this is because we want MC restarts to reset as much state as we can, and because - // local vars rock + // local vars rock //all this shit is here so that flag edits can be refreshed by restarting the MC. (and for speed) var/list/tickersubsystems = list() - var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel + var/list/runlevel_sorted_subsystems = list(list()) //ensure we always have at least one runlevel var/timer = world.time for (var/thing in subsystems) var/datum/controller/subsystem/SS = thing @@ -305,8 +322,12 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/error_level = 0 var/sleep_delta = 1 var/list/subsystems_to_check - //the actual loop. + //setup the stack overflow detector + stack_end_detector = new() + var/datum/stack_canary/canary = stack_end_detector.prime_canary() + canary.use_variable() + //the actual loop. while (1) tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, (((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag))) var/starting_tick_usage = TICK_USAGE @@ -317,7 +338,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new //Anti-tick-contention heuristics: //if there are mutiple sleeping procs running before us hogging the cpu, we have to run later. - // (because sleeps are processed in the order received, longer sleeps are more likely to run first) + // (because sleeps are processed in the order received, longer sleeps are more likely to run first) if (starting_tick_usage > TICK_LIMIT_MC) //if there isn't enough time to bother doing anything this tick, sleep a bit. sleep_delta *= 2 current_ticklimit = TICK_LIMIT_RUNNING * 0.5 @@ -423,6 +444,10 @@ GLOBAL_REAL(Master, /datum/controller/master) = new continue if ((SS_flags & (SS_TICKER|SS_KEEP_TIMING)) == SS_KEEP_TIMING && SS.last_fire + (SS.wait * 0.75) > world.time) continue + if (SS.postponed_fires >= 1) + SS.postponed_fires-- + SS.update_nextfire() + continue SS.enqueue() . = 1 @@ -439,12 +464,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/tick_precentage var/tick_remaining var/ran = TRUE //this is right - var/ran_non_ticker = FALSE var/bg_calc //have we swtiched current_tick_budget to background mode yet? var/tick_usage //keep running while we have stuff to run and we haven't gone over a tick - // this is so subsystems paused eariler can use tick time that later subsystems never used + // this is so subsystems paused eariler can use tick time that later subsystems never used while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC) ran = FALSE bg_calc = FALSE @@ -459,20 +483,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if (!(queue_node_flags & SS_TICKER) && skip_ticks) queue_node = queue_node.queue_next continue - //super special case, subsystems where we can't make them pause mid way through - //if we can't run them this tick (without going over a tick) - //we bump up their priority and attempt to run them next tick - //(unless we haven't even ran anything this tick, since its unlikely they will ever be able run - // in those cases, so we just let them run) - if (queue_node_flags & SS_NO_TICK_CHECK) - if (queue_node.tick_usage > TICK_LIMIT_RUNNING - TICK_USAGE && ran_non_ticker) - if (!(queue_node_flags & SS_BACKGROUND)) - queue_node.queued_priority += queue_priority_count * 0.1 - queue_priority_count -= queue_node_priority - queue_priority_count += queue_node.queued_priority - current_tick_budget -= queue_node_priority - queue_node = queue_node.queue_next - continue if (!bg_calc && (queue_node_flags & SS_BACKGROUND)) current_tick_budget = queue_priority_count_bg @@ -481,7 +491,8 @@ GLOBAL_REAL(Master, /datum/controller/master) = new tick_remaining = TICK_LIMIT_RUNNING - TICK_USAGE if (current_tick_budget > 0 && queue_node_priority > 0) - tick_precentage = tick_remaining / (current_tick_budget / queue_node_priority) + //Give the subsystem a precentage of the remaining tick based on the remaning priority + tick_precentage = tick_remaining * (queue_node_priority / current_tick_budget) else tick_precentage = tick_remaining @@ -489,8 +500,6 @@ GLOBAL_REAL(Master, /datum/controller/master) = new current_ticklimit = round(TICK_USAGE + tick_precentage) - if (!(queue_node_flags & SS_TICKER)) - ran_non_ticker = TRUE ran = TRUE queue_node_paused = (queue_node.state == SS_PAUSED || queue_node.state == SS_PAUSING) @@ -535,14 +544,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node.last_fire = world.time queue_node.times_fired++ - if (queue_node_flags & SS_TICKER) - queue_node.next_fire = world.time + (world.tick_lag * queue_node.wait) - else if (queue_node_flags & SS_POST_FIRE_TIMING) - queue_node.next_fire = world.time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) - else if (queue_node_flags & SS_KEEP_TIMING) - queue_node.next_fire += queue_node.wait - else - queue_node.next_fire = queue_node.queued_time + queue_node.wait + (world.tick_lag * (queue_node.tick_overrun/100)) + queue_node.update_nextfire() queue_node.queued_time = 0 @@ -554,7 +556,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new . = 1 //resets the queue, and all subsystems, while filtering out the subsystem lists -// called if any mc's queue procs runtime or exit improperly. +// called if any mc's queue procs runtime or exit improperly. /datum/controller/master/proc/SoftReset(list/ticker_SS, list/runlevel_SS) . = 0 log_world("MC: SoftReset called, resetting MC queue state.") diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 12798f3863..db780ca05b 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -29,11 +29,11 @@ var/initialized = FALSE /// Set to 0 to prevent fire() calls, mostly for admin use or subsystems that may be resumed later - /// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick + /// use the [SS_NO_FIRE] flag instead for systems that never fire to keep it from even being added to list that is checked every tick var/can_fire = TRUE ///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details. - var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire + var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire /* * The following variables are managed by the MC and should not be modified directly. @@ -69,6 +69,9 @@ /// Tracks the amount of completed runs for the subsystem var/times_fired = 0 + /// How many fires have we been requested to postpone + var/postponed_fires = 0 + /// Time the subsystem entered the queue, (for timing and priority reasons) var/queued_time = 0 @@ -122,12 +125,38 @@ dequeue() can_fire = 0 flags |= SS_NO_FIRE - Master.subsystems -= src + if (Master) + Master.subsystems -= src return ..() + +/** Update next_fire for the next run. + * reset_time (bool) - Ignore things that would normally alter the next fire, like tick_overrun, and last_fire. (also resets postpone) + */ +/datum/controller/subsystem/proc/update_nextfire(reset_time = FALSE) + var/queue_node_flags = flags + + if (reset_time) + postponed_fires = 0 + if (queue_node_flags & SS_TICKER) + next_fire = world.time + (world.tick_lag * wait) + else + next_fire = world.time + wait + return + + if (queue_node_flags & SS_TICKER) + next_fire = world.time + (world.tick_lag * wait) + else if (queue_node_flags & SS_POST_FIRE_TIMING) + next_fire = world.time + wait + (world.tick_lag * (tick_overrun/100)) + else if (queue_node_flags & SS_KEEP_TIMING) + next_fire += wait + else + next_fire = queued_time + wait + (world.tick_lag * (tick_overrun/100)) + + //Queue it to run. -// (we loop thru a linked list until we get to the end or find the right point) -// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) +// (we loop thru a linked list until we get to the end or find the right point) +// (this lets us sort our run order correctly without having to re-sort the entire already sorted list) /datum/controller/subsystem/proc/enqueue() var/SS_priority = priority var/SS_flags = flags @@ -191,9 +220,9 @@ queue_next.queue_prev = queue_prev if (queue_prev) queue_prev.queue_next = queue_next - if (src == Master.queue_tail) + if (Master && (src == Master.queue_tail)) Master.queue_tail = queue_prev - if (src == Master.queue_head) + if (Master && (src == Master.queue_head)) Master.queue_head = queue_next queued_time = 0 if (state == SS_QUEUED) @@ -217,10 +246,11 @@ //used to initialize the subsystem AFTER the map has loaded /datum/controller/subsystem/Initialize(start_timeofday) initialized = TRUE + // SEND_SIGNAL(src, COMSIG_SUBSYSTEM_POST_INITIALIZE, start_timeofday) var/time = (REALTIMEOFDAY - start_timeofday) / 10 var/msg = "Initialized [name] subsystem within [time] second[time == 1 ? "" : "s"]!" - to_chat(world, "[msg]") - log_subsystem("INIT", msg) + to_chat(world, span_boldannounce("[msg]")) + log_subsystem(msg) return time /datum/controller/subsystem/stat_entry(msg) @@ -243,11 +273,10 @@ if (SS_IDLE) . = " " -//could be used to postpone a costly subsystem for (default one) var/cycles, cycles -//for instance, during cpu intensive operations like explosions +/// Causes the next "cycle" fires to be missed. Effect is accumulative but can reset by calling update_nextfire(reset_time = TRUE) /datum/controller/subsystem/proc/postpone(cycles = 1) - if(next_fire - world.time < wait) - next_fire += (wait*cycles) + if (can_fire && cycles >= 1) + postponed_fires += cycles //usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash) //should attempt to salvage what it can from the old instance of subsystem @@ -258,7 +287,7 @@ if (NAMEOF(src, can_fire)) //this is so the subsystem doesn't rapid fire to make up missed ticks causing more lag if (var_value) - next_fire = world.time + wait + update_nextfire(reset_time = TRUE) if (NAMEOF(src, queued_priority)) //editing this breaks things. return FALSE . = ..() diff --git a/code/controllers/subsystem/air.dm b/code/controllers/subsystem/air.dm index 583cf98259..91e92feee9 100644 --- a/code/controllers/subsystem/air.dm +++ b/code/controllers/subsystem/air.dm @@ -30,6 +30,7 @@ SUBSYSTEM_DEF(air) var/list/networks = list() var/list/pipenets_needing_rebuilt = list() var/list/deferred_airs = list() + var/cur_deferred_airs = 0 var/max_deferred_airs = 0 var/list/obj/machinery/atmos_machinery = list() var/list/obj/machinery/atmos_air_machinery = list() @@ -37,7 +38,8 @@ SUBSYSTEM_DEF(air) //atmos singletons var/list/gas_reactions = list() - + var/list/atmos_gen + var/list/planetary = list() //auxmos already caches static planetary mixes but could be convenient to do so here too //Special functions lists var/list/turf/open/high_pressure_delta = list() @@ -53,14 +55,24 @@ SUBSYSTEM_DEF(air) var/equalize_turf_limit = 10 // Max number of turfs to look for a space turf, and max number of turfs that will be decompressed. var/equalize_hard_turf_limit = 2000 - // Whether equalization should be enabled at all. + // Whether equalization is enabled. Can be disabled for performance reasons. var/equalize_enabled = TRUE + // Whether equalization should be enabled. + var/should_do_equalization = TRUE + // When above 0, won't equalize; performance handling + var/eq_cooldown = 0 // Whether turf-to-turf heat exchanging should be enabled. var/heat_enabled = FALSE // Max number of times process_turfs will share in a tick. var/share_max_steps = 3 + // Target for share_max_steps; can go below this, if it determines the thread is taking too long. + var/share_max_steps_target = 3 // Excited group processing will try to equalize groups with total pressure difference less than this amount. var/excited_group_pressure_goal = 1 + // Target for excited_group_pressure_goal; can go below this, if it determines the thread is taking too long. + var/excited_group_pressure_goal_target = 1 + // If this is set to 0, monstermos won't process planet atmos + var/planet_equalize_enabled = 0 /datum/controller/subsystem/air/stat_entry(msg) msg += "C:{" @@ -218,6 +230,7 @@ SUBSYSTEM_DEF(air) // This also happens to do all the commented out stuff below, all in a single separate thread. This is mostly so that the // waiting is consistent. if(currentpart == SSAIR_ACTIVETURFS) + run_delay_heuristics() timer = TICK_USAGE_REAL process_turfs(resumed) cost_turfs = MC_AVERAGE(cost_turfs, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) @@ -275,7 +288,8 @@ SUBSYSTEM_DEF(air) pipenets_needing_rebuilt += atmos_machine /datum/controller/subsystem/air/proc/process_deferred_airs(resumed = 0) - max_deferred_airs = max(deferred_airs.len,max_deferred_airs) + cur_deferred_airs = deferred_airs.len + max_deferred_airs = max(cur_deferred_airs,max_deferred_airs) while(deferred_airs.len) var/list/cur_op = deferred_airs[deferred_airs.len] deferred_airs.len-- @@ -375,6 +389,24 @@ SUBSYSTEM_DEF(air) return */ +/datum/controller/subsystem/air/proc/run_delay_heuristics() + if(!equalize_enabled) + cost_equalize = 0 + if(should_do_equalization) + eq_cooldown-- + if(eq_cooldown <= 0) + equalize_enabled = TRUE + var/total_thread_time = cost_turfs + cost_equalize + cost_groups + cost_post_process + if(total_thread_time) + var/wait_ms = wait * 100 + var/delay_threshold = 1-(total_thread_time/wait_ms + cur_deferred_airs / 50) + share_max_steps = max(1,round(share_max_steps_target * delay_threshold, 1)) + eq_cooldown += (1-delay_threshold) * (cost_equalize / total_thread_time) * 2 + if(eq_cooldown > 0.5) + equalize_enabled = FALSE + excited_group_pressure_goal = max(0,excited_group_pressure_goal_target * (1 - delay_threshold)) + + /datum/controller/subsystem/air/proc/process_turfs(resumed = 0) if(process_turfs_auxtools(resumed,TICK_REMAINING_MS)) pause() @@ -399,7 +431,7 @@ SUBSYSTEM_DEF(air) pause() /datum/controller/subsystem/air/proc/finish_turf_processing(resumed = 0) - if(finish_turf_processing_auxtools(TICK_REMAINING_MS)) + if(finish_turf_processing_auxtools(TICK_REMAINING_MS) || thread_running()) pause() /datum/controller/subsystem/air/proc/post_process_turfs(resumed = 0) @@ -473,6 +505,20 @@ SUBSYSTEM_DEF(air) return pipe_init_dirs_cache[type]["[dir]"] +/datum/controller/subsystem/air/proc/generate_atmos() + atmos_gen = list() + for(var/T in subtypesof(/datum/atmosphere)) + var/datum/atmosphere/atmostype = T + atmos_gen[initial(atmostype.id)] = new atmostype + +/datum/controller/subsystem/air/proc/preprocess_gas_string(gas_string) + if(!atmos_gen) + generate_atmos() + if(!atmos_gen[gas_string]) + return gas_string + var/datum/atmosphere/mix = atmos_gen[gas_string] + return mix.gas_string + #undef SSAIR_PIPENETS #undef SSAIR_ATMOSMACHINERY #undef SSAIR_EXCITEDGROUPS diff --git a/code/controllers/subsystem/garbage.dm b/code/controllers/subsystem/garbage.dm index 2d2fac1d13..7f7d301a1d 100644 --- a/code/controllers/subsystem/garbage.dm +++ b/code/controllers/subsystem/garbage.dm @@ -29,37 +29,35 @@ SUBSYSTEM_DEF(garbage) runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY init_order = INIT_ORDER_GARBAGE - var/list/collection_timeout = list(2 MINUTES, 10 SECONDS) // deciseconds to wait before moving something up in the queue to the next level + var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level //Stat tracking - var/delslasttick = 0 // number of del()'s we've done this tick - var/gcedlasttick = 0 // number of things that gc'ed last tick + var/delslasttick = 0 // number of del()'s we've done this tick + var/gcedlasttick = 0 // number of things that gc'ed last tick var/totaldels = 0 var/totalgcs = 0 - var/highest_del_time = 0 - var/highest_del_tickusage = 0 + var/highest_del_ms = 0 + var/highest_del_type_string = "" var/list/pass_counts var/list/fail_counts - var/list/items = list() // Holds our qdel_item statistics datums + var/list/items = list() // Holds our qdel_item statistics datums //Queue var/list/queues - #ifdef LEGACY_REFERENCE_TRACKING + #ifdef REFERENCE_TRACKING var/list/reference_find_on_fail = list() + #ifdef REFERENCE_TRACKING_DEBUG + //Should we save found refs. Used for unit testing + var/should_save_refs = FALSE + #endif #endif /datum/controller/subsystem/garbage/PreInit() - queues = new(GC_QUEUE_COUNT) - pass_counts = new(GC_QUEUE_COUNT) - fail_counts = new(GC_QUEUE_COUNT) - for(var/i in 1 to GC_QUEUE_COUNT) - queues[i] = list() - pass_counts[i] = 0 - fail_counts[i] = 0 + InitQueues() /datum/controller/subsystem/garbage/stat_entry(msg) var/list/counts = list() @@ -90,13 +88,18 @@ SUBSYSTEM_DEF(garbage) for(var/path in items) var/datum/qdel_item/I = items[path] dellog += "Path: [path]" + if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG) + dellog += "\tSUSPENDED FOR LAG" if (I.failures) dellog += "\tFailures: [I.failures]" dellog += "\tqdel() Count: [I.qdels]" dellog += "\tDestroy() Cost: [I.destroy_time]ms" if (I.hard_deletes) - dellog += "\tTotal Hard Deletes [I.hard_deletes]" + dellog += "\tTotal Hard Deletes: [I.hard_deletes]" dellog += "\tTime Spent Hard Deleting: [I.hard_delete_time]ms" + dellog += "\tHighest Time Spent Hard Deleting: [I.hard_delete_max]ms" + if (I.hard_deletes_over_threshold) + dellog += "\tHard Deletes Over Threshold: [I.hard_deletes_over_threshold]" if (I.slept_destroy) dellog += "\tSleeps: [I.slept_destroy]" if (I.no_respect_force) @@ -122,6 +125,15 @@ SUBSYSTEM_DEF(garbage) +/datum/controller/subsystem/garbage/proc/InitQueues() + if (isnull(queues)) // Only init the queues if they don't already exist, prevents overriding of recovered lists + queues = new(GC_QUEUE_COUNT) + pass_counts = new(GC_QUEUE_COUNT) + fail_counts = new(GC_QUEUE_COUNT) + for(var/i in 1 to GC_QUEUE_COUNT) + queues[i] = list() + pass_counts[i] = 0 + fail_counts[i] = 0 /datum/controller/subsystem/garbage/proc/HandleQueue(level = GC_QUEUE_CHECK) if (level == GC_QUEUE_CHECK) @@ -153,7 +165,6 @@ SUBSYSTEM_DEF(garbage) if(GCd_at_time > cut_off_time) break // Everything else is newer, skip them count++ - var/refID = L[2] var/datum/D D = locate(refID) @@ -162,8 +173,8 @@ SUBSYSTEM_DEF(garbage) ++gcedlasttick ++totalgcs pass_counts[level]++ - #ifdef LEGACY_REFERENCE_TRACKING - reference_find_on_fail -= refID //It's deleted we don't care anymore. + #ifdef REFERENCE_TRACKING + reference_find_on_fail -= refID //It's deleted we don't care anymore. #endif if (MC_TICK_CHECK) return @@ -171,35 +182,43 @@ SUBSYSTEM_DEF(garbage) // Something's still referring to the qdel'd object. fail_counts[level]++ + + #ifdef REFERENCE_TRACKING + var/ref_searching = FALSE + #endif + switch (level) if (GC_QUEUE_CHECK) #ifdef REFERENCE_TRACKING - D.find_references() - #elif defined(LEGACY_REFERENCE_TRACKING) if(reference_find_on_fail[refID]) - D.find_references_legacy() + INVOKE_ASYNC(D, /datum/proc/find_references) + ref_searching = TRUE #ifdef GC_FAILURE_HARD_LOOKUP else - D.find_references_legacy() + INVOKE_ASYNC(D, /datum/proc/find_references) + ref_searching = TRUE #endif reference_find_on_fail -= refID #endif var/type = D.type var/datum/qdel_item/I = items[type] - #ifdef TESTING + log_world("## TESTING: GC: -- \ref[D] | [type] was unable to be GC'd --") + #ifdef TESTING for(var/c in GLOB.admins) //Using testing() here would fill the logs with ADMIN_VV garbage var/client/admin = c if(!check_rights_for(admin, R_ADMIN)) continue to_chat(admin, "## TESTING: GC: -- [ADMIN_VV(D)] | [type] was unable to be GC'd --") - testing("GC: -- \ref[src] | [type] was unable to be GC'd --") - #endif - #ifdef REFERENCE_TRACKING - GLOB.deletion_failures += D //It should no longer be bothered by the GC, manual deletion only. - continue #endif I.failures++ + + if (I.qdel_flags & QDEL_ITEM_SUSPENDED_FOR_LAG) + #ifdef REFERENCE_TRACKING + if(ref_searching) + return //ref searching intentionally cancels all further fires while running so things that hold references don't end up getting deleted, so we want to return here instead of continue + #endif + continue if (GC_QUEUE_HARDDELETE) HardDelete(D) if (MC_TICK_CHECK) @@ -208,27 +227,17 @@ SUBSYSTEM_DEF(garbage) Queue(D, level+1) + #ifdef REFERENCE_TRACKING + if(ref_searching) + return + #endif + if (MC_TICK_CHECK) return if (count) queue.Cut(1,count+1) count = 0 -#ifdef LEGACY_REFERENCE_TRACKING -/datum/controller/subsystem/garbage/proc/add_type_to_findref(type) - if(!ispath(type)) - return "NOT A VAILD PATH" - reference_find_on_fail_types |= typecacheof(type) - -/datum/controller/subsystem/garbage/proc/remove_type_from_findref(type) - if(!ispath(type)) - return "NOT A VALID PATH" - reference_find_on_fail_types -= typesof(type) - -/datum/controller/subsystem/garbage/proc/clear_findref_types() - reference_find_on_fail_types = list() -#endif - /datum/controller/subsystem/garbage/proc/Queue(datum/D, level = GC_QUEUE_CHECK) if (isnull(D)) return @@ -238,63 +247,66 @@ SUBSYSTEM_DEF(garbage) var/gctime = world.time var/refid = "\ref[D]" -#ifdef LEGACY_REFERENCE_TRACKING - if(reference_find_on_fail_types[D.type]) - SSgarbage.reference_find_on_fail[REF(D)] = TRUE -#endif - D.gc_destroyed = gctime var/list/queue = queues[level] + queue[++queue.len] = list(gctime, refid) // not += for byond reasons //this is mainly to separate things profile wise. /datum/controller/subsystem/garbage/proc/HardDelete(datum/D) - var/time = world.timeofday - var/tick = TICK_USAGE - var/ticktime = world.time ++delslasttick ++totaldels var/type = D.type var/refID = "\ref[D]" + var/tick_usage = TICK_USAGE del(D) - - tick = (TICK_USAGE-tick+((world.time-ticktime)/world.tick_lag*100)) + tick_usage = TICK_USAGE_TO_MS(tick_usage) var/datum/qdel_item/I = items[type] - I.hard_deletes++ - I.hard_delete_time += TICK_DELTA_TO_MS(tick) + I.hard_delete_time += tick_usage + if (tick_usage > I.hard_delete_max) + I.hard_delete_max = tick_usage + if (tick_usage > highest_del_ms) + highest_del_ms = tick_usage + highest_del_type_string = "[type]" + var/time = MS2DS(tick_usage) - if (tick > highest_del_tickusage) - highest_del_tickusage = tick - time = world.timeofday - time - if (!time && TICK_DELTA_TO_MS(tick) > 1) - time = TICK_DELTA_TO_MS(tick)/100 - if (time > highest_del_time) - highest_del_time = time - if (time > 10) - log_game("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete)") - message_admins("Error: [type]([refID]) took longer than 1 second to delete (took [time/10] seconds to delete).") + if (time > 0.1 SECONDS) postpone(time) + var/threshold = CONFIG_GET(number/hard_deletes_overrun_threshold) + if (threshold && (time > threshold SECONDS)) + if (!(I.qdel_flags & QDEL_ITEM_ADMINS_WARNED)) + log_game("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete)") + message_admins("Error: [type]([refID]) took longer than [threshold] seconds to delete (took [round(time/10, 0.1)] seconds to delete).") + I.qdel_flags |= QDEL_ITEM_ADMINS_WARNED + I.hard_deletes_over_threshold++ + var/overrun_limit = CONFIG_GET(number/hard_deletes_overrun_limit) + if (overrun_limit && I.hard_deletes_over_threshold >= overrun_limit) + I.qdel_flags |= QDEL_ITEM_SUSPENDED_FOR_LAG /datum/controller/subsystem/garbage/Recover() + InitQueues() //We first need to create the queues before recovering data if (istype(SSgarbage.queues)) for (var/i in 1 to SSgarbage.queues.len) queues[i] |= SSgarbage.queues[i] - +/// Qdel Item: Holds statistics on each type that passes thru qdel /datum/qdel_item - var/name = "" - var/qdels = 0 //Total number of times it's passed thru qdel. - var/destroy_time = 0 //Total amount of milliseconds spent processing this type's Destroy() - var/failures = 0 //Times it was queued for soft deletion but failed to soft delete. - var/hard_deletes = 0 //Different from failures because it also includes QDEL_HINT_HARDDEL deletions - var/hard_delete_time = 0//Total amount of milliseconds spent hard deleting this type. - var/no_respect_force = 0//Number of times it's not respected force=TRUE - var/no_hint = 0 //Number of times it's not even bother to give a qdel hint - var/slept_destroy = 0 //Number of times it's slept in its destroy + var/name = "" //!Holds the type as a string for this type + var/qdels = 0 //!Total number of times it's passed thru qdel. + var/destroy_time = 0 //!Total amount of milliseconds spent processing this type's Destroy() + var/failures = 0 //!Times it was queued for soft deletion but failed to soft delete. + var/hard_deletes = 0 //!Different from failures because it also includes QDEL_HINT_HARDDEL deletions + var/hard_delete_time = 0 //!Total amount of milliseconds spent hard deleting this type. + var/hard_delete_max = 0 //!Highest time spent hard_deleting this in ms. + var/hard_deletes_over_threshold = 0 //!Number of times hard deletes took longer than the configured threshold + var/no_respect_force = 0 //!Number of times it's not respected force=TRUE + var/no_hint = 0 //!Number of times it's not even bother to give a qdel hint + var/slept_destroy = 0 //!Number of times it's slept in its destroy + var/qdel_flags = 0 //!Flags related to this type's trip thru qdel. /datum/qdel_item/New(mytype) name = "[mytype]" @@ -307,12 +319,12 @@ SUBSYSTEM_DEF(garbage) if(!istype(D)) del(D) return + var/datum/qdel_item/I = SSgarbage.items[D.type] if (!I) I = SSgarbage.items[D.type] = new /datum/qdel_item(D.type) I.qdels++ - if(isnull(D.gc_destroyed)) if (SEND_SIGNAL(D, COMSIG_PARENT_PREQDELETED, force)) // Give the components a chance to prevent their parent from being deleted return @@ -328,12 +340,12 @@ SUBSYSTEM_DEF(garbage) if(!D) return switch(hint) - if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion. + if (QDEL_HINT_QUEUE) //qdel should queue the object for deletion. SSgarbage.Queue(D) if (QDEL_HINT_IWILLGC) D.gc_destroyed = world.time return - if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory. + if (QDEL_HINT_LETMELIVE) //qdel should let the object live after calling destory. if(!force) D.gc_destroyed = null //clear the gc variable (important!) return @@ -350,17 +362,17 @@ SUBSYSTEM_DEF(garbage) I.no_respect_force++ SSgarbage.Queue(D) - if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete + if (QDEL_HINT_HARDDEL) //qdel should assume this object won't gc, and queue a hard delete SSgarbage.Queue(D, GC_QUEUE_HARDDELETE) - if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste. + if (QDEL_HINT_HARDDEL_NOW) //qdel should assume this object won't gc, and hard del it post haste. SSgarbage.HardDelete(D) - #ifdef LEGACY_REFERENCE_TRACKING - if (QDEL_HINT_FINDREFERENCE) //qdel will, if LEGACY_REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion. + #ifdef REFERENCE_TRACKING + if (QDEL_HINT_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled, display all references to this object, then queue the object for deletion. SSgarbage.Queue(D) - D.find_references_legacy() - if (QDEL_HINT_IFFAIL_FINDREFERENCE) + D.find_references() + if (QDEL_HINT_IFFAIL_FINDREFERENCE) //qdel will, if REFERENCE_TRACKING is enabled and the object fails to collect, display all references to this object. SSgarbage.Queue(D) - SSgarbage.reference_find_on_fail[REF(D)] = TRUE + SSgarbage.reference_find_on_fail["\ref[D]"] = TRUE #endif else #ifdef TESTING @@ -371,18 +383,3 @@ SUBSYSTEM_DEF(garbage) SSgarbage.Queue(D) else if(D.gc_destroyed == GC_CURRENTLY_BEING_QDELETED) CRASH("[D.type] destroy proc was called multiple times, likely due to a qdel loop in the Destroy logic") - -#ifdef TESTING -/proc/writeDatumCount() - var/list/datums = list() - for(var/datum/D in world) - datums[D.type] += 1 - for(var/datum/D) - datums[D.type] += 1 - datums = sortTim(datums, /proc/cmp_numeric_dsc, associative = TRUE) - if(fexists("data/DATUMCOUNT.txt")) - fdel("data/DATUMCOUNT.txt") - var/outfile = file("data/DATUMCOUNT.txt") - for(var/path in datums) - outfile << "[datums[path]]\t\t\t\t\t[path]" -#endif diff --git a/code/controllers/subsystem/research.dm b/code/controllers/subsystem/research.dm index 946980389c..410e21d419 100644 --- a/code/controllers/subsystem/research.dm +++ b/code/controllers/subsystem/research.dm @@ -366,10 +366,16 @@ SUBSYSTEM_DEF(research) techweb_categories[I.category] = list(I.id = TRUE) /datum/controller/subsystem/research/proc/techweb_node_by_id(id) - return techweb_nodes[id] || error_node + if(techweb_nodes[id]) + return techweb_nodes[id] + stack_trace("Attempted to access node ID [id] which didn't exist") + return error_node /datum/controller/subsystem/research/proc/techweb_design_by_id(id) - return techweb_designs[id] || error_design + if(techweb_designs[id]) + return techweb_designs[id] + stack_trace("Attempted to access design ID [id] which didn't exist") + return error_design /datum/controller/subsystem/research/proc/on_design_deletion(datum/design/D) for(var/i in techweb_nodes) diff --git a/code/controllers/subsystem/vote.dm b/code/controllers/subsystem/vote.dm index 2474a6f272..1153088a91 100644 --- a/code/controllers/subsystem/vote.dm +++ b/code/controllers/subsystem/vote.dm @@ -68,7 +68,7 @@ SUBSYSTEM_DEF(vote) //get the highest number of votes var/greatest_votes = 0 var/total_votes = 0 - if(mode == "gamemode" && CONFIG_GET(flag/must_be_readied_to_vote_gamemode)) + if((mode == "gamemode" || mode == "roundtype") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode)) for(var/mob/dead/new_player/P in GLOB.player_list) if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey]) choices[choices[voted[P.ckey]]]-- @@ -105,7 +105,7 @@ SUBSYSTEM_DEF(vote) /datum/controller/subsystem/vote/proc/calculate_condorcet_votes(var/blackbox_text) // https://en.wikipedia.org/wiki/Schulze_method#Implementation - if((mode == "gamemode" || mode == "dynamic") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode)) + if((mode == "gamemode" || mode == "dynamic" || mode == "roundtype") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode)) for(var/mob/dead/new_player/P in GLOB.player_list) if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey]) voted -= P.ckey @@ -155,7 +155,7 @@ SUBSYSTEM_DEF(vote) for(var/choice in choices) scores_by_choice += "[choice]" scores_by_choice["[choice]"] = list() - if((mode == "gamemode" || mode == "dynamic") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode)) + if((mode == "gamemode" || mode == "dynamic" || mode == "roundtype") && CONFIG_GET(flag/must_be_readied_to_vote_gamemode)) for(var/mob/dead/new_player/P in GLOB.player_list) if(P.ready != PLAYER_READY_TO_PLAY && voted[P.ckey]) voted -= P.ckey diff --git a/code/datums/atmosphere/_atmosphere.dm b/code/datums/atmosphere/_atmosphere.dm new file mode 100644 index 0000000000..5323b070cd --- /dev/null +++ b/code/datums/atmosphere/_atmosphere.dm @@ -0,0 +1,60 @@ +/datum/atmosphere + var/gas_string + var/id + + var/list/base_gases // A list of gases to always have + var/list/normal_gases // A list of allowed gases:base_amount + var/list/restricted_gases // A list of allowed gases like normal_gases but each can only be selected a maximum of one time + var/restricted_chance = 10 // Chance per iteration to take from restricted gases + + var/minimum_pressure + var/maximum_pressure + + var/minimum_temp + var/maximum_temp + +/datum/atmosphere/New() + generate_gas_string() + +/datum/atmosphere/proc/generate_gas_string() + var/list/spicy_gas = restricted_gases.Copy() + var/target_pressure = rand(minimum_pressure, maximum_pressure) + var/pressure_scale = target_pressure / maximum_pressure + + // First let's set up the gasmix and base gases for this template + // We make the string from a gasmix in this proc because gases need to calculate their pressure + var/datum/gas_mixture/gasmix = new + gasmix.set_temperature(rand(minimum_temp, maximum_temp)) + for(var/i in base_gases) + gasmix.set_moles(i, base_gases[i]) + + // Now let the random choices begin + var/gastype + var/amount + while(gasmix.return_pressure() < target_pressure) + if(!prob(restricted_chance) || !length(spicy_gas)) + gastype = pick(normal_gases) + amount = normal_gases[gastype] + else + gastype = pick(spicy_gas) + amount = spicy_gas[gastype] + spicy_gas -= gastype //You can only pick each restricted gas once + + amount *= rand(50, 200) / 100 // Randomly modifes the amount from half to double the base for some variety + amount *= pressure_scale // If we pick a really small target pressure we want roughly the same mix but less of it all + amount = CEILING(amount, 0.1) + + gasmix.adjust_moles(gastype, amount) + + // That last one put us over the limit, remove some of it + if(gasmix.return_pressure() > target_pressure) + var/moles_to_remove = (1 - target_pressure / gasmix.return_pressure()) * gasmix.total_moles() + gasmix.adjust_moles(gastype, -moles_to_remove) + gasmix.set_moles(gastype, FLOOR(gasmix.get_moles(gastype), 0.1)) + + // Now finally lets make that string + var/list/gas_string_builder = list() + for(var/id in gasmix.get_gases()) + gas_string_builder += "[id]=[gasmix.get_moles(id)]" + gas_string_builder += "TEMP=[gasmix.return_temperature()]" + gas_string = gas_string_builder.Join(";") diff --git a/code/datums/atmosphere/planetary.dm b/code/datums/atmosphere/planetary.dm new file mode 100644 index 0000000000..a8b77f7394 --- /dev/null +++ b/code/datums/atmosphere/planetary.dm @@ -0,0 +1,48 @@ +// Atmos types used for planetary airs +/datum/atmosphere/lavaland + id = LAVALAND_DEFAULT_ATMOS + + base_gases = list( + GAS_O2=5, + GAS_N2=10, + ) + normal_gases = list( + GAS_O2=10, + GAS_N2=10, + GAS_CO2=10, + ) + restricted_gases = list( + GAS_BZ=0.1, + GAS_METHYL_BROMIDE=0.1, + ) + restricted_chance = 30 + + minimum_pressure = HAZARD_LOW_PRESSURE + 10 + maximum_pressure = LAVALAND_EQUIPMENT_EFFECT_PRESSURE - 1 + + minimum_temp = BODYTEMP_COLD_DAMAGE_LIMIT + 1 + maximum_temp = 320 + +/datum/atmosphere/icemoon + id = ICEMOON_DEFAULT_ATMOS + + base_gases = list( + GAS_O2=5, + GAS_N2=10, + ) + normal_gases = list( + GAS_O2=10, + GAS_N2=10, + GAS_CO2=10, + ) + restricted_gases = list( + GAS_METHYL_BROMIDE=0.1, + ) + restricted_chance = 10 + + minimum_pressure = HAZARD_LOW_PRESSURE + 10 + maximum_pressure = LAVALAND_EQUIPMENT_EFFECT_PRESSURE - 1 + + minimum_temp = 180 + maximum_temp = 180 + diff --git a/code/datums/components/armor_plate.dm b/code/datums/components/armor_plate.dm index f026c89c60..763aef6a70 100644 --- a/code/datums/components/armor_plate.dm +++ b/code/datums/components/armor_plate.dm @@ -71,6 +71,7 @@ R.update_icon() to_chat(user, "You strengthen [R], improving its resistance against melee, bullet and laser damage.") else + SEND_SIGNAL(O, COMSIG_ARMOR_PLATED, amount, maxamount) to_chat(user, "You strengthen [O], improving its resistance against melee attacks.") diff --git a/code/datums/components/storage/concrete/_concrete.dm b/code/datums/components/storage/concrete/_concrete.dm index 55c3e1d083..bb226bbfdf 100644 --- a/code/datums/components/storage/concrete/_concrete.dm +++ b/code/datums/components/storage/concrete/_concrete.dm @@ -135,6 +135,8 @@ var/obj/item/I = AM var/mob/M = parent.loc I.dropped(M) + I.item_flags &= ~IN_STORAGE + I.remove_outline() if(new_location) AM.forceMove(new_location) // exited comsig will handle removal reset. //We don't want to call this if the item is being destroyed @@ -180,6 +182,7 @@ I.forceMove(parent.drop_location()) return FALSE I.on_enter_storage(master) + I.item_flags |= IN_STORAGE refresh_mob_views() I.mouse_opacity = MOUSE_OPACITY_OPAQUE //So you can click on the area around the item to equip it, instead of having to pixel hunt if(M) diff --git a/code/datums/components/storage/storage.dm b/code/datums/components/storage/storage.dm index 752fafe6bd..97d6748dc7 100644 --- a/code/datums/components/storage/storage.dm +++ b/code/datums/components/storage/storage.dm @@ -423,6 +423,9 @@ var/atom/A = parent if(ismob(M)) //all the check for item manipulation are in other places, you can safely open any storages as anything and its not buggy, i checked A.add_fingerprint(M) + if(istype(A, /obj/item)) + var/obj/item/I = A + I.remove_outline() //Removes the outline when we drag if(!over_object) return FALSE if(ismecha(M.loc)) // stops inventory actions in a mech diff --git a/code/datums/components/waddling.dm b/code/datums/components/waddling.dm index f09a92c91c..7b94a14285 100644 --- a/code/datums/components/waddling.dm +++ b/code/datums/components/waddling.dm @@ -10,6 +10,7 @@ var/mob/living/L = parent if(L.incapacitated() || L.lying) return + var/matrix/otransform = matrix(L.transform) //make a copy of the current transform animate(L, pixel_z = 4, time = 0) - animate(pixel_z = 0, transform = turn(matrix(), pick(-12, 0, 12)), time=2) - animate(pixel_z = 0, transform = matrix(), time = 0) + animate(pixel_z = 0, transform = turn(L.transform, pick(-12, 0, 12)), time=2) //waddle. + animate(pixel_z = 0, transform = otransform, time = 0) //return to previous transform. diff --git a/code/datums/datum.dm b/code/datums/datum.dm index 42580425ce..164cda63e0 100644 --- a/code/datums/datum.dm +++ b/code/datums/datum.dm @@ -57,9 +57,13 @@ */ var/list/cooldowns -#ifdef TESTING +#ifdef REFERENCE_TRACKING var/running_find_references var/last_find_references = 0 + #ifdef REFERENCE_TRACKING_DEBUG + ///Stores info about where refs are found, used for sanity checks and testing + var/list/found_refs + #endif #endif #ifdef DATUMVAR_DEBUGGING_MODE diff --git a/code/datums/dna.dm b/code/datums/dna.dm index a2982a3caf..43dad6cef8 100644 --- a/code/datums/dna.dm +++ b/code/datums/dna.dm @@ -386,6 +386,14 @@ qdel(language_holder) var/species_holder = initial(mrace.species_language_holder) language_holder = new species_holder(src) + + var/mob/living/carbon/human/H = src + //provide the user's additional language to the new language holder even if they change species + if(H.additional_language && H.additional_language != "None") + var/language_entry = GLOB.roundstart_languages[H.additional_language] + if(language_entry) + grant_language(language_entry, TRUE, TRUE) + update_atom_languages() /mob/living/carbon/human/set_species(datum/species/mrace, icon_update = TRUE, pref_load = FALSE) diff --git a/code/datums/helper_datums/stack_end_detector.dm b/code/datums/helper_datums/stack_end_detector.dm new file mode 100644 index 0000000000..0d621f51b3 --- /dev/null +++ b/code/datums/helper_datums/stack_end_detector.dm @@ -0,0 +1,32 @@ +/** + Stack End Detector. + Can detect if a given code stack has exited, used by the mc for stack overflow detection. + + **/ +/datum/stack_end_detector + var/datum/weakref/_WF + var/datum/stack_canary/_canary + +/datum/stack_end_detector/New() + _canary = new() + _WF = WEAKREF(_canary) + +/** Prime the stack overflow detector. + Store the return value of this proc call in a proc level var. + Can only be called once. +**/ +/datum/stack_end_detector/proc/prime_canary() + if (!_canary) + CRASH("Prime_canary called twice") + . = _canary + _canary = null + +/// Returns true if the stack is still going. Calling before the canary has been primed also returns true +/datum/stack_end_detector/proc/check() + return !!_WF.resolve() + +/// Stack canary. Will go away if the stack it was primed by is ended by byond for return or stack overflow reasons. +/datum/stack_canary + +/// empty proc to avoid warnings about unused variables. Call this proc on your canary in the stack it's watching. +/datum/stack_canary/proc/use_variable() diff --git a/code/datums/http.dm b/code/datums/http.dm index 2a9b53f131..49b183fde6 100644 --- a/code/datums/http.dm +++ b/code/datums/http.dm @@ -6,10 +6,12 @@ var/body var/headers var/url + /// If present response body will be saved to this file. + var/output_file var/_raw_response -/datum/http_request/proc/prepare(method, url, body = "", list/headers) +/datum/http_request/proc/prepare(method, url, body = "", list/headers, output_file) if (!length(headers)) headers = "" else @@ -19,15 +21,16 @@ src.url = url src.body = body src.headers = headers + src.output_file = output_file /datum/http_request/proc/execute_blocking() - _raw_response = rustg_http_request_blocking(method, url, body, headers) + _raw_response = rustg_http_request_blocking(method, url, body, headers, build_options()) /datum/http_request/proc/begin_async() if (in_progress) CRASH("Attempted to re-use a request object.") - id = rustg_http_request_async(method, url, body, headers) + id = rustg_http_request_async(method, url, body, headers, build_options()) if (isnull(text2num(id))) stack_trace("Proc error: [id]") @@ -35,6 +38,11 @@ else in_progress = TRUE +/datum/http_request/proc/build_options() + if(output_file) + return json_encode(list("output_filename"=output_file,"body_filename"=null)) + return null + /datum/http_request/proc/is_complete() if (isnull(id)) return TRUE diff --git a/code/datums/traits/neutral.dm b/code/datums/traits/neutral.dm index 2f0667d942..36712ea6bb 100644 --- a/code/datums/traits/neutral.dm +++ b/code/datums/traits/neutral.dm @@ -162,3 +162,20 @@ gain_text = "You feel like munching on a can of soda." lose_text = "You no longer feel like you should be eating trash." mob_trait = TRAIT_TRASHCAN + +/datum/quirk/colorist + name = "Colorist" + desc = "You like carrying around a hair dye spray to quickly apply color patterns to your hair." + value = 0 + medical_record_text = "Patient enjoys dyeing their hair with pretty colors." + +/datum/quirk/colorist/on_spawn() + var/mob/living/carbon/human/H = quirk_holder + var/obj/item/dyespray/spraycan = new(get_turf(quirk_holder)) + H.equip_to_slot(spraycan, SLOT_IN_BACKPACK) + H.regenerate_icons() + +/datum/quirk/colorist/post_add() + var/mob/living/carbon/human/H = quirk_holder + SEND_SIGNAL(H.back, COMSIG_TRY_STORAGE_SHOW, H) + to_chat(quirk_holder, "You brought some extra dye with you! It's in your bag if you forgot.") diff --git a/code/datums/wires/_wires.dm b/code/datums/wires/_wires.dm index 81f99cfa69..abee5ada06 100644 --- a/code/datums/wires/_wires.dm +++ b/code/datums/wires/_wires.dm @@ -329,3 +329,15 @@ to_chat(L, "You need an attachable assembly!") #undef MAXIMUM_EMP_WIRES + +//gremlins +/datum/wires/proc/npc_tamper(mob/living/L) + if(!wires.len) + return + + var/wire_to_screw = pick(wires) + + if(is_color_cut(wire_to_screw) || prob(50)) //CutWireColour() proc handles both cutting and mending wires. If the wire is already cut, always mend it back. Otherwise, 50% to cut it and 50% to pulse it + cut(wire_to_screw) + else + pulse(wire_to_screw, L) diff --git a/code/game/gamemodes/clock_cult/clock_cult.dm b/code/game/gamemodes/clock_cult/clock_cult.dm index 7b6eaa6fa5..8ff26d5125 100644 --- a/code/game/gamemodes/clock_cult/clock_cult.dm +++ b/code/game/gamemodes/clock_cult/clock_cult.dm @@ -151,14 +151,14 @@ Credit where due: var/datum/team/clockcult/main_clockcult /datum/game_mode/clockwork_cult/pre_setup() //Gamemode and job code is pain. Have fun codediving all of that stuff, whoever works on this next - Delta - /*var/list/errorList = list() + var/list/errorList = list() var/list/reebes = SSmapping.LoadGroup(errorList, "Reebe", "map_files/generic", "City_of_Cogs.dmm", default_traits = ZTRAITS_REEBE, silent = TRUE) if(errorList.len) // reebe failed to load message_admins("Reebe failed to load!") log_game("Reebe failed to load!") return FALSE for(var/datum/parsed_map/PM in reebes) //Temporarily commented because of z-level loading reliably segfaulting the server. - PM.initTemplateBounds()*/ + PM.initTemplateBounds() if(CONFIG_GET(flag/protect_roles_from_antagonist)) restricted_jobs += protected_jobs if(CONFIG_GET(flag/protect_assistant_from_antagonist)) @@ -275,7 +275,7 @@ Credit where due: ears = /obj/item/radio/headset gloves = /obj/item/clothing/gloves/color/yellow belt = /obj/item/storage/belt/utility/servant - backpack_contents = list(/obj/item/storage/box/engineer = 1, \ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/clockwork/replica_fabricator = 1, /obj/item/stack/tile/brass/fifty = 1, /obj/item/reagent_containers/food/drinks/bottle/holyoil = 1) id = /obj/item/pda var/plasmaman //We use this to determine if we should activate internals in post_equip() diff --git a/code/game/gamemodes/clown_ops/clown_ops.dm b/code/game/gamemodes/clown_ops/clown_ops.dm index 659d2de105..fa73e2cca9 100644 --- a/code/game/gamemodes/clown_ops/clown_ops.dm +++ b/code/game/gamemodes/clown_ops/clown_ops.dm @@ -43,7 +43,7 @@ l_pocket = /obj/item/pinpointer/nuke/syndicate r_pocket = /obj/item/bikehorn id = /obj/item/card/id/syndicate - backpack_contents = list(/obj/item/storage/box/syndie=1,\ + backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\ /obj/item/kitchen/knife/combat/survival, /obj/item/reagent_containers/spray/waterflower/lube) implants = list(/obj/item/implant/sad_trombone) diff --git a/code/game/gamemodes/nuclear/nuclear.dm b/code/game/gamemodes/nuclear/nuclear.dm index dcf84e84db..a82abe9b3d 100644 --- a/code/game/gamemodes/nuclear/nuclear.dm +++ b/code/game/gamemodes/nuclear/nuclear.dm @@ -127,7 +127,7 @@ l_pocket = /obj/item/pinpointer/nuke/syndicate id = /obj/item/card/id/syndicate belt = /obj/item/gun/ballistic/automatic/pistol - backpack_contents = list(/obj/item/storage/box/syndie=1,\ + backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\ /obj/item/kitchen/knife/combat/survival) var/tc = 25 @@ -173,7 +173,7 @@ internals_slot = SLOT_R_STORE belt = /obj/item/storage/belt/military r_hand = /obj/item/gun/ballistic/automatic/shotgun/bulldog - backpack_contents = list(/obj/item/storage/box/syndie=1,\ + backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\ /obj/item/tank/jetpack/oxygen/harness=1,\ /obj/item/gun/ballistic/automatic/pistol=1,\ /obj/item/kitchen/knife/combat/survival) @@ -188,7 +188,7 @@ r_pocket = /obj/item/tank/internals/emergency_oxygen/engi internals_slot = SLOT_R_STORE belt = /obj/item/storage/belt/military - backpack_contents = list(/obj/item/storage/box/syndie=1,\ + backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\ /obj/item/tank/jetpack/oxygen/harness=1,\ /obj/item/gun/ballistic/automatic/pistol=1,\ /obj/item/kitchen/knife/combat/survival) diff --git a/code/game/machinery/computer/arcade/minesweeper.dm b/code/game/machinery/computer/arcade/minesweeper.dm index dbd9890c81..cd6ff2359c 100644 --- a/code/game/machinery/computer/arcade/minesweeper.dm +++ b/code/game/machinery/computer/arcade/minesweeper.dm @@ -191,6 +191,7 @@ var/x2 = x1 work_squares(y2, x2) //Work squares while in this loop so there's less load reset_board = FALSE + CHECK_TICK web += "" //Start setting up the html table web += "" @@ -235,6 +236,7 @@ web += "" if(19) web += "" + CHECK_TICK web += "" web += "
[MINESWEEPERIMG(7)][MINESWEEPERIMG(8)]
" web += "" diff --git a/code/game/machinery/recharger.dm b/code/game/machinery/recharger.dm index 62696a0853..d58edbd025 100755 --- a/code/game/machinery/recharger.dm +++ b/code/game/machinery/recharger.dm @@ -1,7 +1,7 @@ /obj/machinery/recharger name = "recharger" icon = 'icons/obj/stationobjs.dmi' - icon_state = "recharger0" + icon_state = "recharger" base_icon_state = "recharger" desc = "A charging dock for energy based weaponry." use_power = IDLE_POWER_USE @@ -12,6 +12,8 @@ var/obj/item/charging = null var/recharge_coeff = 1 var/using_power = FALSE //Did we put power into "charging" last process()? + ///Did we finish recharging the currently inserted item? + var/finished_recharging = FALSE var/static/list/allowed_devices = typecacheof(list( /obj/item/gun/energy, @@ -48,13 +50,14 @@ charging = new_charging if (new_charging) START_PROCESSING(SSmachines, src) + finished_recharging = FALSE use_power = ACTIVE_POWER_USE using_power = TRUE - update_icon() + update_appearance() else use_power = IDLE_POWER_USE using_power = FALSE - update_icon() + update_appearance() /obj/machinery/recharger/Exited(atom/movable/M, atom/newloc) . = ..() @@ -100,7 +103,8 @@ return 1 if(anchored && !charging) - if(default_deconstruction_screwdriver(user, "rechargeropen", "recharger0", G)) + if(default_deconstruction_screwdriver(user, "recharger", "recharger", G)) + update_appearance() return if(panel_open && G.tool_behaviour == TOOL_CROWBAR) @@ -133,7 +137,7 @@ C.give(C.chargerate * recharge_coeff) use_power(250 * recharge_coeff) using_power = TRUE - update_icon() + update_appearance() if(istype(charging, /obj/item/ammo_box/magazine/recharge)) var/obj/item/ammo_box/magazine/recharge/R = charging @@ -141,7 +145,7 @@ R.stored_ammo += new R.ammo_type(R) use_power(200 * recharge_coeff) using_power = TRUE - update_icon() + update_appearance() return if(istype(charging, /obj/item/ammo_casing/mws_batt)) @@ -152,7 +156,7 @@ using_power = 1 if(R.BB == null) R.chargeshot() - update_icon(using_power) + update_appearance() if(istype(charging, /obj/item/ammo_box/magazine/mws_mag)) var/obj/item/ammo_box/magazine/mws_mag/R = charging @@ -164,14 +168,19 @@ using_power = 1 if(batt.BB == null) batt.chargeshot() - update_icon(using_power) + update_appearance() + + if(!using_power && !finished_recharging) //Inserted thing is at max charge/ammo, notify those around us + finished_recharging = TRUE + playsound(src, 'sound/machines/ping.ogg', 30, TRUE) + say("[charging] has finished recharging!") else return PROCESS_KILL /obj/machinery/recharger/power_change() ..() - update_icon() + update_appearance() /obj/machinery/recharger/emp_act(severity) . = ..() diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index cd486ccf6b..7177249dbc 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -146,6 +146,9 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb var/list/grind_results //A reagent list containing the reagents this item produces when ground up in a grinder - this can be an empty list to allow for reagent transferring only var/list/juice_results //A reagent list containing blah blah... but when JUICED in a grinder! + //the outline filter on hover + var/outline_filter + /* Our block parry data. Should be set in init, or something if you are using it. * This won't be accessed without ITEM_CAN_BLOCK or ITEM_CAN_PARRY so do not set it unless you have to to save memory. * If you decide it's a good idea to leave this unset while turning the flags on, you will runtime. Enjoy. @@ -175,6 +178,12 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb if(force_string) item_flags |= FORCE_STRING_OVERRIDE + if(istype(loc, /obj/item/storage)) + item_flags |= IN_STORAGE + + if(istype(loc, /obj/item/robot_module)) + item_flags |= IN_INVENTORY + if(!hitsound) if(damtype == "fire") hitsound = 'sound/items/welder.ogg' @@ -373,6 +382,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb if(!allow_attack_hand_drop(user) || !user.temporarilyRemoveItemFromInventory(src)) return + remove_outline() pickup(user) add_fingerprint(user) if(!user.put_in_active_hand(src, FALSE, FALSE)) @@ -443,6 +453,7 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb qdel(src) item_flags &= ~IN_INVENTORY SEND_SIGNAL(src, COMSIG_ITEM_DROPPED,user) + remove_outline() // if(!silent) // playsound(src, drop_sound, DROP_SOUND_VOLUME, ignore_walls = FALSE) user?.update_equipment_speed_mods() @@ -557,6 +568,12 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb if(usr.incapacitated() || !Adjacent(usr) || usr.lying) return + if(iscyborg(usr)) + var/obj/item/gripper/gripper = usr.get_active_held_item(TRUE) + if(istype(gripper)) + gripper.pre_attack(src, usr, get_dist(src, usr)) + return + if(usr.get_active_held_item() == null) // Let me know if this has any problems -Yota usr.UnarmedAttack(src) @@ -867,18 +884,49 @@ GLOBAL_VAR_INIT(embedpocalypse, FALSE) // if true, all items will be able to emb openToolTip(user,src,params,title = name,content = "[desc]
Force: [force_string]",theme = "") /obj/item/MouseEntered(location, control, params) - if((item_flags & IN_INVENTORY) && usr.client.prefs.enable_tips && !QDELETED(src)) + SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_ENTER, location, control, params) + if((item_flags & IN_INVENTORY || item_flags & IN_STORAGE) && usr.client.prefs.enable_tips && !QDELETED(src)) var/timedelay = usr.client.prefs.tip_delay/100 var/user = usr tip_timer = addtimer(CALLBACK(src, .proc/openTip, location, control, params, user), timedelay, TIMER_STOPPABLE)//timer takes delay in deciseconds, but the pref is in milliseconds. dividing by 100 converts it. + var/mob/living/L = usr + if(istype(L) && L.incapacitated()) + apply_outline(COLOR_RED_GRAY) + else + apply_outline() + +/obj/item/MouseDrop(atom/over, src_location, over_location, src_control, over_control, params) + . = ..() + remove_outline() /obj/item/MouseExited(location,control,params) SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_EXIT, location, control, params) deltimer(tip_timer)//delete any in-progress timer if the mouse is moved off the item before it finishes closeToolTip(usr) + remove_outline() -/obj/item/MouseEntered(location,control,params) - SEND_SIGNAL(src, COMSIG_ITEM_MOUSE_ENTER, location, control, params) +/obj/item/proc/apply_outline(colour = null) + if(!(item_flags & IN_INVENTORY || item_flags & IN_STORAGE) || QDELETED(src) || isobserver(usr)) + return + if(usr.client) + if(!usr.client.prefs.outline_enabled) + return + if(!colour) + if(usr.client) + colour = usr.client.prefs.outline_color + if(!colour) + colour = COLOR_BLUE_GRAY + else + colour = COLOR_BLUE_GRAY + if(outline_filter) + filters -= outline_filter + outline_filter = filter(type="outline", size=1, color=colour) + filters += outline_filter + +/obj/item/proc/remove_outline() + if(outline_filter) + filters -= outline_filter + outline_filter = null // Called when a mob tries to use the item as a tool. // Handles most checks. diff --git a/code/game/objects/items/dyekit.dm b/code/game/objects/items/dyekit.dm new file mode 100644 index 0000000000..43a05c7939 --- /dev/null +++ b/code/game/objects/items/dyekit.dm @@ -0,0 +1,40 @@ +/obj/item/dyespray + name = "hair dye spray" + desc = "A spray to dye your hair any gradients you'd like." + icon = 'icons/obj/dyespray.dmi' + icon_state = "dyespray" + +/obj/item/dyespray/attack_self(mob/user) + dye(user) + +/obj/item/dyespray/pre_attack(atom/target, mob/living/user, params) + dye(target) + return ..() + +/** + * Applies a gradient and a gradient color to a mob. + * + * Arguments: + * * target - The mob who we will apply the gradient and gradient color to. + */ + +/obj/item/dyespray/proc/dye(mob/target) + if(!ishuman(target)) + return + var/mob/living/carbon/human/human_target = target + + var/new_grad_style = input(usr, "Choose a color pattern:", "Character Preference") as null|anything in GLOB.hair_gradients_list + if(!new_grad_style) + return + + var/new_grad_color = input(usr, "Choose a secondary hair color:", "Character Preference","#"+human_target.grad_color) as color|null + if(!new_grad_color) + return + + human_target.grad_style = new_grad_style + human_target.grad_color = sanitize_hexcolor(new_grad_color) + to_chat(human_target, "You start applying the hair dye...") + if(!do_after(usr, 30, target = human_target)) + return + playsound(src, 'sound/effects/spray.ogg', 5, TRUE, 5) + human_target.update_hair() diff --git a/code/game/objects/items/melee/energy.dm b/code/game/objects/items/melee/energy.dm index 1be570b62b..275536d370 100644 --- a/code/game/objects/items/melee/energy.dm +++ b/code/game/objects/items/melee/energy.dm @@ -184,7 +184,7 @@ hitcost = 75 //Costs more than a standard cyborg esword w_class = WEIGHT_CLASS_NORMAL sharpness = SHARP_EDGED - light_color = "#40ceff" + light_color = LIGHT_COLOR_RED tool_behaviour = TOOL_SAW toolspeed = 0.7 diff --git a/code/game/objects/items/robot/robot_items.dm b/code/game/objects/items/robot/robot_items.dm index 6513b53e1d..cbfdc85f6d 100644 --- a/code/game/objects/items/robot/robot_items.dm +++ b/code/game/objects/items/robot/robot_items.dm @@ -745,9 +745,9 @@ Grippers oh god oh fuck ***********************************************************************/ -/obj/item/weapon/gripper +/obj/item/gripper name = "engineering gripper" - desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more. Alt click to drop instead of use." + desc = "A simple grasping tool for interacting with various engineering related items, such as circuits, gas tanks, conveyer belts and more." icon = 'icons/obj/device.dmi' icon_state = "gripper" @@ -776,18 +776,29 @@ var/obj/item/wrapped = null // Item currently being held. -//Used to interact with UI's of held items, such as gas tanks and airlock electronics. -/obj/item/weapon/gripper/AltClick(mob/user) +// Used to drop whatever's in the gripper. +/obj/item/gripper/proc/drop_held(silent = FALSE) if(wrapped) wrapped.forceMove(get_turf(wrapped)) - to_chat(user, "You drop the [wrapped].") + if(!silent) + to_chat(usr, "You drop the [wrapped].") + modify_appearance(wrapped, FALSE) wrapped = null - return ..() + update_appearance() + return TRUE + return FALSE -/obj/item/weapon/gripper/pre_attack(var/atom/target, var/mob/living/silicon/robot/user, proximity, params) +/obj/item/gripper/proc/takeitem(obj/item/item, silent = FALSE) + if(!silent) + to_chat(usr, "You collect \the [item].") + item.loc = src + wrapped = item + update_appearance() - if(!proximity) - return +/obj/item/gripper/pre_attack(atom/target, mob/living/silicon/robot/user, params) + var/proximity = get_dist(user, target) + if(proximity > 1) + return STOP_ATTACK_PROC_CHAIN if(!wrapped) for(var/obj/item/thing in src.contents) @@ -795,21 +806,24 @@ break if(wrapped) //Already have an item. + var/obj/item/item = wrapped + drop_held(TRUE) //Temporary put wrapped into user so target's attackby() checks pass. - wrapped.loc = user + item.loc = user //Pass the attack on to the target. This might delete/relocate wrapped. - var/resolved = target.attackby(wrapped,user) - if(!resolved && wrapped && target) - wrapped.afterattack(target,user,1) + var/resolved = target.attackby(item, user, params) + if(!resolved && item && target) + item.afterattack(target, user, proximity, params) //If wrapped was neither deleted nor put into target, put it back into the gripper. - if(wrapped && user && (wrapped.loc == user)) - wrapped.loc = src - else - wrapped = null + if(item && user && (item.loc == user)) + takeitem(item, TRUE) return + else + item = null + return STOP_ATTACK_PROC_CHAIN - else if(istype(target,/obj/item)) + else if(isitem(target)) var/obj/item/I = target var/grab = 0 @@ -824,24 +838,94 @@ //We can grab the item, finally. if(grab) - to_chat(user, "You collect \the [I].") - I.loc = src - wrapped = I + takeitem(I) return else to_chat(user, "Your gripper cannot hold \the [target].") -/obj/item/weapon/gripper/mining +// Rare cases - meant to be handled by code\modules\mob\living\silicon\robot\robot.dm:584 and the weirdness of get_active_held_item() of borgs. +/obj/item/gripper/attack_self(mob/user) + if(wrapped) + wrapped.attack_self(user) + return + . = ..() + +// Splitable items +/obj/item/gripper/AltClick(mob/user) + if(wrapped) + wrapped.AltClick(user) + return + . = ..() + +// Even rarer cases +/obj/item/gripper/CtrlClick(mob/user) + if(wrapped) + wrapped.CtrlClick(user) + return + . = ..() + +// At this point you're just kidding me, but have this one as well. +/obj/item/gripper/CtrlShiftClick(mob/user) + if(wrapped) + wrapped.CtrlShiftClick(user) + return + . = ..() + +// Make it clear what we can do with it. +/obj/item/gripper/examine(mob/user) + . = ..() + if(wrapped) + . += "It is holding [icon2html(wrapped, user)] [wrapped]." + . += "Examine the little preview to examine it." + . += "Attempting to drop the gripper will only drop [wrapped]." + +// Resets vis_contents and if holding something, add it to vis_contents. +/obj/item/gripper/update_appearance(updates) + . = ..() + vis_contents = list() + if(wrapped) + modify_appearance(wrapped, TRUE) + vis_contents += wrapped + +// Generates the "minified" version of the item being held and adjust it's position. +/obj/item/gripper/proc/modify_appearance(obj/item, minify = FALSE) + if(minify) + var/matrix/new_transform = new + new_transform.Scale(0.5, 0.5) + item.transform = new_transform + item.pixel_x = 8 + item.pixel_y = -8 + else + item.pixel_x = initial(pixel_x) + item.pixel_y = initial(pixel_y) + item.transform = new + +// I kind of wanted the item to be held in the gripper when stored as well, but i realized "store" is just drop as well, so i'll do this for now. +// This will handle cases where the borg runs out of power or is damaged enough so the module is forcefully stored. +/obj/item/gripper/cyborg_unequip(mob/user) + . = ..() + if(wrapped) + drop_held() + +// Clear references on being destroyed +/obj/item/Destroy() + for(var/obj/item/gripper/gripper in vis_locs) + if(gripper.wrapped == src) + gripper.wrapped = null + gripper.update_appearance() + . = ..() + +/obj/item/gripper/mining name = "shelter capsule deployer" - desc = "A simple grasping tool for carrying and deploying shelter capsules. Alt click to drop instead of use." + desc = "A simple grasping tool for carrying and deploying shelter capsules." icon_state = "gripper_mining" can_hold = list( /obj/item/survivalcapsule ) -/obj/item/weapon/gripper/medical +/obj/item/gripper/medical name = "medical gripper" - desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more. Alt click to drop instead of use." + desc = "A simple grasping tool for interacting with medical equipment, such as beakers, blood bags, chem bags and more." icon_state = "gripper_medical" can_hold = list( /obj/item/storage/bag/bio, diff --git a/code/game/objects/items/stacks/rods.dm b/code/game/objects/items/stacks/rods.dm index c6763092ca..08f0a6fb7f 100644 --- a/code/game/objects/items/stacks/rods.dm +++ b/code/game/objects/items/stacks/rods.dm @@ -3,6 +3,7 @@ GLOBAL_LIST_INIT(rod_recipes, list ( \ new/datum/stack_recipe("table frame", /obj/structure/table_frame, 2, time = 10, one_per_turf = 1, on_floor = 1), \ new/datum/stack_recipe("scooter frame", /obj/item/scooter_frame, 10, time = 25, one_per_turf = 0), \ new/datum/stack_recipe("railing", /obj/structure/railing, 3, time = 18, window_checks = TRUE), \ + new/datum/stack_recipe("catwalk tile", /obj/item/stack/tile/catwalk, 1, 4, 20), \ )) /obj/item/stack/rods diff --git a/code/game/objects/items/stacks/sheets/sheet_types.dm b/code/game/objects/items/stacks/sheets/sheet_types.dm index 15153521ed..504bff9e29 100644 --- a/code/game/objects/items/stacks/sheets/sheet_types.dm +++ b/code/game/objects/items/stacks/sheets/sheet_types.dm @@ -502,6 +502,8 @@ GLOBAL_LIST_INIT(cardboard_recipes, list ( \ new /datum/stack_recipe("sterile masks box", /obj/item/storage/box/masks), \ new /datum/stack_recipe("body bag box", /obj/item/storage/box/bodybags), \ new /datum/stack_recipe("prescription glasses box", /obj/item/storage/box/rxglasses), \ + new /datum/stack_recipe("oxygen tank box", /obj/item/storage/box/emergencytank), \ + new /datum/stack_recipe("extended oxygen tank box", /obj/item/storage/box/engitank), \ null, \ new /datum/stack_recipe("disk box", /obj/item/storage/box/disks), \ new /datum/stack_recipe("light tubes box", /obj/item/storage/box/lights/tubes), \ diff --git a/code/game/objects/items/stacks/tiles/tile_types.dm b/code/game/objects/items/stacks/tiles/tile_types.dm index 8b272f98b4..6eac04b976 100644 --- a/code/game/objects/items/stacks/tiles/tile_types.dm +++ b/code/game/objects/items/stacks/tiles/tile_types.dm @@ -555,3 +555,10 @@ color = "#92661A" turf_type = /turf/open/floor/bronze custom_materials = list(/datum/material/bronze = 250) + +/obj/item/stack/tile/catwalk + name = "catwalk tile" + singular_name = "catwalk floor tile" + desc = "Flooring that shows its contents underneath. Engineers love it!" + icon_state = "catwalk_tile" + turf_type = /turf/open/floor/plating/catwalk_floor diff --git a/code/game/objects/items/storage/boxes.dm b/code/game/objects/items/storage/boxes.dm index ea40b49c5f..8b897cb6e5 100644 --- a/code/game/objects/items/storage/boxes.dm +++ b/code/game/objects/items/storage/boxes.dm @@ -102,12 +102,22 @@ new /obj/item/disk/nanite_program(src) // Ordinary survival box +/obj/item/storage/box/survival + name = "survival box" + desc = "A box with the bare essentials of ensuring the survival of you and others." + icon_state = "internals" + illustration = "emergencytank" + var/mask_type = /obj/item/clothing/mask/breath + var/internal_type = /obj/item/tank/internals/emergency_oxygen + var/medipen_type = /obj/item/reagent_containers/hypospray/medipen + /obj/item/storage/box/survival/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/reagent_containers/hypospray/medipen(src) + new mask_type(src) + if(!isnull(medipen_type)) + new medipen_type(src) if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) + new internal_type(src) else new /obj/item/tank/internals/plasmaman/belt(src) @@ -115,10 +125,13 @@ ..() // we want the survival stuff too. new /obj/item/radio/off(src) -/obj/item/storage/box/survival_mining/PopulateContents() - new /obj/item/clothing/mask/gas/explorer(src) +// Mining survival box +/obj/item/storage/box/survival/mining + mask_type = /obj/item/clothing/mask/gas/explorer + +/obj/item/storage/box/survival/mining/PopulateContents() + ..() new /obj/item/crowbar/red(src) - new /obj/item/reagent_containers/hypospray/medipen(src) if(!isplasmaman(loc)) new /obj/item/tank/internals/emergency_oxygen(src) @@ -126,39 +139,30 @@ new /obj/item/tank/internals/plasmaman/belt(src) // Engineer survival box -/obj/item/storage/box/engineer/PopulateContents() - new /obj/item/clothing/mask/breath(src) - new /obj/item/reagent_containers/hypospray/medipen(src) +/obj/item/storage/box/survival/engineer + name = "extended-capacity survival box" + desc = "A box with the bare essentials of ensuring the survival of you and others. This one is labelled to contain an extended-capacity tank." + illustration = "extendedtank" + internal_type = /obj/item/tank/internals/emergency_oxygen/engi - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/engineer/radio/PopulateContents() +/obj/item/storage/box/survival/engineer/radio/PopulateContents() ..() // we want the regular items too. new /obj/item/radio/off(src) // Syndie survival box -/obj/item/storage/box/syndie/PopulateContents() - new /obj/item/clothing/mask/gas/syndicate(src) - - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen/engi(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) +/obj/item/storage/box/survival/syndie //why is this its own thing if it's just the engi box with a syndie mask and medipen? + name = "extended-capacity survival box" + desc = "A box with the bare essentials of ensuring the survival of you and others. This one is labelled to contain an extended-capacity tank." + illustration = "extendedtank" + mask_type = /obj/item/clothing/mask/gas/syndicate + internal_type = /obj/item/tank/internals/emergency_oxygen/engi + medipen_type = null // Security survival box -/obj/item/storage/box/security/PopulateContents() - new /obj/item/clothing/mask/gas/sechailer(src) - new /obj/item/reagent_containers/hypospray/medipen(src) +/obj/item/storage/box/survival/security + mask_type = /obj/item/clothing/mask/gas/sechailer - if(!isplasmaman(loc)) - new /obj/item/tank/internals/emergency_oxygen(src) - else - new /obj/item/tank/internals/plasmaman/belt(src) - -/obj/item/storage/box/security/radio/PopulateContents() +/obj/item/storage/box/survival/security/radio/PopulateContents() ..() // we want the regular stuff too new /obj/item/radio/off(src) @@ -1244,6 +1248,26 @@ icon_state = "box_pink" illustration = null +/obj/item/storage/box/emergencytank + name = "emergency oxygen tank box" + desc = "A box of emergency oxygen tanks." + illustration = "emergencytank" + +/obj/item/storage/box/emergencytank/PopulateContents() + ..() + for(var/i in 1 to 7) + new /obj/item/tank/internals/emergency_oxygen(src) //in case anyone ever wants to do anything with spawning them, apart from crafting the box + +/obj/item/storage/box/engitank + name = "extended-capacity emergency oxygen tank box" + desc = "A box of extended-capacity emergency oxygen tanks." + illustration = "extendedtank" + +/obj/item/storage/box/engitank/PopulateContents() + ..() + for(var/i in 1 to 7) + new /obj/item/tank/internals/emergency_oxygen/engi(src) //in case anyone ever wants to do anything with spawning them, apart from crafting the box + /obj/item/storage/box/mre //base MRE type. name = "Nanotrasen MRE Ration Kit Menu 0" desc = "A package containing food suspended in an outdated bluespace pocket which lasts for centuries. If you're lucky you may even be able to enjoy the meal without getting food poisoning." diff --git a/code/game/objects/structures/crates_lockers/closets/secure/security.dm b/code/game/objects/structures/crates_lockers/closets/secure/security.dm index 4fa5312581..02ee466376 100644 --- a/code/game/objects/structures/crates_lockers/closets/secure/security.dm +++ b/code/game/objects/structures/crates_lockers/closets/secure/security.dm @@ -7,7 +7,7 @@ new /obj/item/clothing/neck/petcollar(src) //I considered removing the pet stuff too but eh, who knows. We might get Renault back. Plus I guess you could use that collar for... other means. Aren't you supposed to be guarding the disk? new /obj/item/pet_carrier(src) new /obj/item/clothing/suit/armor/vest/capcarapace(src) - new /obj/item/clothing/suit/armor/vest/capcarapace/alt(src) + new /obj/item/clothing/suit/toggle/captains_parade(src) new /obj/item/clothing/head/crown/fancy(src) new /obj/item/cartridge/captain(src) new /obj/item/storage/box/silver_ids(src) @@ -56,6 +56,7 @@ /obj/structure/closet/secure_closet/hos/PopulateContents() ..() new /obj/item/clothing/neck/cloak/hos(src) + new /obj/item/clothing/suit/toggle/armor/hos/hos_formal(src) new /obj/item/cartridge/hos(src) new /obj/item/radio/headset/heads/hos(src) new /obj/item/clothing/under/rank/security/head_of_security/parade/female(src) diff --git a/code/game/turfs/open/floor/catwalk_plating.dm b/code/game/turfs/open/floor/catwalk_plating.dm new file mode 100644 index 0000000000..c6bfdc0448 --- /dev/null +++ b/code/game/turfs/open/floor/catwalk_plating.dm @@ -0,0 +1,44 @@ +/** + * ## catwalk flooring + * + * They show what's underneath their catwalk flooring (pipes and the like) + * you can crowbar it to interact with the underneath stuff without destroying the tile... + * unless you want to! + */ +/turf/open/floor/plating/catwalk_floor + icon = 'icons/turf/floors/catwalk_plating.dmi' + icon_state = "catwalk_below" + floor_tile = /obj/item/stack/tile/catwalk + name = "catwalk floor" + desc = "Flooring that shows its contents underneath. Engineers love it!" + baseturfs = /turf/open/floor/plating + footstep = FOOTSTEP_CATWALK + barefootstep = FOOTSTEP_CATWALK + clawfootstep = FOOTSTEP_CATWALK + heavyfootstep = FOOTSTEP_CATWALK + var/covered = TRUE + +/turf/open/floor/plating/catwalk_floor/Initialize() + . = ..() + layer = CATWALK_LAYER + update_icon(UPDATE_OVERLAYS) + +/turf/open/floor/plating/catwalk_floor/update_overlays() + . = ..() + var/static/catwalk_overlay + if(isnull(catwalk_overlay)) + catwalk_overlay = iconstate2appearance(icon, "catwalk_above") + if(covered) + . += catwalk_overlay + +/turf/open/floor/plating/catwalk_floor/screwdriver_act(mob/living/user, obj/item/tool) + . = ..() + covered = !covered + to_chat(user, span_notice("[!covered ? "You removed the cover!" : "You added the cover!"]")) + update_icon(UPDATE_OVERLAYS) + +/turf/open/floor/plating/catwalk_floor/pry_tile(obj/item/crowbar, mob/user, silent) + if(covered) + to_chat(user, span_notice("You need to remove the cover first!")) + return FALSE + . = ..() diff --git a/code/game/world.dm b/code/game/world.dm index f466913346..14a85bb619 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -16,7 +16,7 @@ GLOBAL_LIST(topic_status_cache) call(debug_server, "auxtools_init")() enable_debugging() AUXTOOLS_CHECK(AUXMOS) -#ifdef REFERENCE_TRACKING +#ifdef EXTOOLS_REFERENCE_TRACKING enable_reference_tracking() #endif world.Profile(PROFILE_START) diff --git a/code/modules/VR/vr_sleeper.dm b/code/modules/VR/vr_sleeper.dm index cc59fe10ab..a5e31e595c 100644 --- a/code/modules/VR/vr_sleeper.dm +++ b/code/modules/VR/vr_sleeper.dm @@ -75,6 +75,9 @@ /obj/machinery/vr_sleeper/MouseDrop_T(mob/target, mob/user) if(user.lying || !iscarbon(target) || !Adjacent(target) || !user.canUseTopic(src, BE_CLOSE, TRUE, NO_TK)) return + if(occupant) + to_chat(user, "The VR Sleeper is already occupied!") + return close_machine(target) ui_interact(user) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 22bbf03014..437c74991c 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -195,7 +195,7 @@ GLOBAL_PROTECT(admin_verbs_debug) // #endif /datum/admins/proc/create_or_modify_area, /datum/admins/proc/fixcorruption, -#ifdef REFERENCE_TRACKING +#ifdef EXTOOLS_REFERENCE_TRACKING /datum/admins/proc/view_refs, /datum/admins/proc/view_del_failures, #endif diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 953835bff0..4224b406b3 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -2862,7 +2862,7 @@ return if(!CONFIG_GET(string/centcom_ban_db)) - to_chat(usr, "Centcom Galactic Ban DB is disabled!") + to_chat(usr, span_warning("Centcom Galactic Ban DB is disabled!")) return var/ckey = href_list["centcomlookup"] @@ -2889,8 +2889,19 @@ dat += "
0 bans detected for [ckey]
" else bans = json_decode(response["body"]) - dat += "
[bans.len] ban\s detected for [ckey]
" + + //Ignore bans from non-whitelisted sources, if a whitelist exists + var/list/valid_sources + if(CONFIG_GET(string/centcom_source_whitelist)) + valid_sources = splittext(CONFIG_GET(string/centcom_source_whitelist), ",") + dat += "
Bans detected for [ckey]
" + else + //Ban count is potentially inaccurate if they're using a whitelist + dat += "
[bans.len] ban\s detected for [ckey]
" + for(var/list/ban in bans) + if(valid_sources && !(ban["sourceName"] in valid_sources)) + continue dat += "Server: [sanitize(ban["sourceName"])]
" dat += "RP Level: [sanitize(ban["sourceRoleplayLevel"])]
" dat += "Type: [sanitize(ban["type"])]
" diff --git a/code/modules/admin/view_variables/reference_tracking.dm b/code/modules/admin/view_variables/reference_tracking.dm index 279654eec1..714b54cd45 100644 --- a/code/modules/admin/view_variables/reference_tracking.dm +++ b/code/modules/admin/view_variables/reference_tracking.dm @@ -1,4 +1,4 @@ -#ifdef REFERENCE_TRACKING +#ifdef EXTOOLS_REFERENCE_TRACKING GLOBAL_LIST_EMPTY(deletion_failures) @@ -102,29 +102,21 @@ GLOBAL_LIST_EMPTY(deletion_failures) #endif -#ifdef LEGACY_REFERENCE_TRACKING +#ifdef REFERENCE_TRACKING -/datum/verb/legacy_find_refs() - set category = "Debug" - set name = "Find References" - set src in world - - find_references_legacy(FALSE) - - -/datum/proc/find_references_legacy(skip_alert) +/datum/proc/find_references(skip_alert) running_find_references = type if(usr?.client) if(usr.client.running_find_references) - testing("CANCELLED search for references to a [usr.client.running_find_references].") + log_reftracker("CANCELLED search for references to a [usr.client.running_find_references].") usr.client.running_find_references = null running_find_references = null //restart the garbage collector SSgarbage.can_fire = TRUE - SSgarbage.next_fire = world.time + world.tick_lag + SSgarbage.update_nextfire(reset_time = TRUE) return - if(!skip_alert && alert("Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", "Yes", "No") != "Yes") + if(!skip_alert && tgui_alert(usr,"Running this will lock everything up for about 5 minutes. Would you like to begin the search?", "Find References", list("Yes", "No")) != "Yes") running_find_references = null return @@ -134,92 +126,122 @@ GLOBAL_LIST_EMPTY(deletion_failures) if(usr?.client) usr.client.running_find_references = type - testing("Beginning search for references to a [type].") - last_find_references = world.time + log_reftracker("Beginning search for references to a [type].") + + var/starting_time = world.time + + //Time to search the whole game for our ref + DoSearchVar(GLOB, "GLOB") //globals + log_reftracker("Finished searching globals") - DoSearchVar(GLOB) //globals for(var/datum/thing in world) //atoms (don't beleive its lies) - DoSearchVar(thing, "World -> [thing]") + DoSearchVar(thing, "World -> [thing.type]", search_time = starting_time) + log_reftracker("Finished searching atoms") for(var/datum/thing) //datums - DoSearchVar(thing, "World -> [thing]") + DoSearchVar(thing, "Datums -> [thing.type]", search_time = starting_time) + log_reftracker("Finished searching datums") + //Warning, attempting to search clients like this will cause crashes if done on live. Watch yourself for(var/client/thing) //clients - DoSearchVar(thing, "World -> [thing]") + DoSearchVar(thing, "Clients -> [thing.type]", search_time = starting_time) + log_reftracker("Finished searching clients") + + log_reftracker("Completed search for references to a [type].") - testing("Completed search for references to a [type].") if(usr?.client) usr.client.running_find_references = null running_find_references = null //restart the garbage collector SSgarbage.can_fire = TRUE - SSgarbage.next_fire = world.time + world.tick_lag + SSgarbage.update_nextfire(reset_time = TRUE) +/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64, search_time = world.time) + #ifdef REFERENCE_TRACKING_DEBUG + if(!found_refs && SSgarbage.should_save_refs) + found_refs = list() + #endif -/datum/verb/qdel_then_find_references() - set category = "Debug" - set name = "qdel() then Find References" - set src in world - - qdel(src, TRUE) //force a qdel - if(!running_find_references) - find_references_legacy(TRUE) - - -/datum/verb/qdel_then_if_fail_find_references() - set category = "Debug" - set name = "qdel() then Find References if GC failure" - set src in world - - qdel_and_find_ref_if_fail(src, TRUE) - - -/datum/proc/DoSearchVar(potential_container, container_name, recursive_limit = 64) if(usr?.client && !usr.client.running_find_references) return if(!recursive_limit) + log_reftracker("Recursion limit reached. [container_name]") return - if(istype(potential_container, /datum)) - var/datum/datum_container = potential_container - if(datum_container.last_find_references == last_find_references) - return - - datum_container.last_find_references = last_find_references - var/list/vars_list = datum_container.vars - - for(var/varname in vars_list) - if (varname == "vars") - continue - var/variable = vars_list[varname] - - if(variable == src) - testing("Found [type] \ref[src] in [datum_container.type]'s [varname] var. [container_name]") - - else if(islist(variable)) - DoSearchVar(variable, "[container_name] -> list", recursive_limit - 1) - - else if(islist(potential_container)) - var/normal = IS_NORMAL_LIST(potential_container) - for(var/element_in_list in potential_container) - if(element_in_list == src) - testing("Found [type] \ref[src] in list [container_name].") - - else if(element_in_list && !isnum(element_in_list) && normal && potential_container[element_in_list] == src) - testing("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]") - - else if(islist(element_in_list)) - DoSearchVar(element_in_list, "[container_name] -> list", recursive_limit - 1) - + //Check each time you go down a layer. This makes it a bit slow, but it won't effect the rest of the game at all #ifndef FIND_REF_NO_CHECK_TICK CHECK_TICK #endif + if(istype(potential_container, /datum)) + var/datum/datum_container = potential_container + if(datum_container.last_find_references == search_time) + return + + datum_container.last_find_references = search_time + var/list/vars_list = datum_container.vars + + for(var/varname in vars_list) + #ifndef FIND_REF_NO_CHECK_TICK + CHECK_TICK + #endif + if (varname == "vars" || varname == "vis_locs") //Fun fact, vis_locs don't count for references + continue + var/variable = vars_list[varname] + + if(variable == src) + #ifdef REFERENCE_TRACKING_DEBUG + if(SSgarbage.should_save_refs) + found_refs[varname] = TRUE + #endif + log_reftracker("Found [type] \ref[src] in [datum_container.type]'s \ref[datum_container] [varname] var. [container_name]") + continue + + if(islist(variable)) + DoSearchVar(variable, "[container_name] \ref[datum_container] -> [varname] (list)", recursive_limit - 1, search_time) + + else if(islist(potential_container)) + var/normal = IS_NORMAL_LIST(potential_container) + var/list/potential_cache = potential_container + for(var/element_in_list in potential_cache) + #ifndef FIND_REF_NO_CHECK_TICK + CHECK_TICK + #endif + //Check normal entrys + if(element_in_list == src) + #ifdef REFERENCE_TRACKING_DEBUG + if(SSgarbage.should_save_refs) + found_refs[potential_cache] = TRUE + #endif + log_reftracker("Found [type] \ref[src] in list [container_name].") + continue + + var/assoc_val = null + if(!isnum(element_in_list) && normal) + assoc_val = potential_cache[element_in_list] + //Check assoc entrys + if(assoc_val == src) + #ifdef REFERENCE_TRACKING_DEBUG + if(SSgarbage.should_save_refs) + found_refs[potential_cache] = TRUE + #endif + log_reftracker("Found [type] \ref[src] in list [container_name]\[[element_in_list]\]") + continue + //We need to run both of these checks, since our object could be hiding in either of them + //Check normal sublists + if(islist(element_in_list)) + DoSearchVar(element_in_list, "[container_name] -> [element_in_list] (list)", recursive_limit - 1, search_time) + //Check assoc sublists + if(islist(assoc_val)) + DoSearchVar(potential_container[element_in_list], "[container_name]\[[element_in_list]\] -> [assoc_val] (list)", recursive_limit - 1, search_time) /proc/qdel_and_find_ref_if_fail(datum/thing_to_del, force = FALSE) - SSgarbage.reference_find_on_fail[REF(thing_to_del)] = TRUE - qdel(thing_to_del, force) + thing_to_del.qdel_and_find_ref_if_fail(force) + +/datum/proc/qdel_and_find_ref_if_fail(force = FALSE) + SSgarbage.reference_find_on_fail["\ref[src]"] = TRUE + qdel(src, force) #endif diff --git a/code/modules/admin/view_variables/topic_basic.dm b/code/modules/admin/view_variables/topic_basic.dm index 01b5536948..043c50173d 100644 --- a/code/modules/admin/view_variables/topic_basic.dm +++ b/code/modules/admin/view_variables/topic_basic.dm @@ -47,7 +47,7 @@ usr.client.debug_variables(src) return - #ifdef REFERENCE_TRACKING + #ifdef EXTOOLS_REFERENCE_TRACKING if(href_list[VV_HK_VIEW_REFERENCES]) var/datum/D = locate(href_list[VV_HK_TARGET]) if(!D) diff --git a/code/modules/antagonists/eldritch_cult/eldritch_items.dm b/code/modules/antagonists/eldritch_cult/eldritch_items.dm index 87a4ddf7eb..28228aedd6 100644 --- a/code/modules/antagonists/eldritch_cult/eldritch_items.dm +++ b/code/modules/antagonists/eldritch_cult/eldritch_items.dm @@ -260,7 +260,7 @@ if((IS_HERETIC(local_user) || IS_HERETIC_MONSTER(local_user)) && HAS_TRAIT(src,TRAIT_NODROP)) REMOVE_TRAIT(src, TRAIT_NODROP, CLOTHING_TRAIT) - for(var/mob/living/carbon/human/human_in_range in spiral_range(9,local_user)) + for(var/mob/living/carbon/human/human_in_range in viewers(9,local_user)) if(IS_HERETIC(human_in_range) || IS_HERETIC_MONSTER(human_in_range)) continue diff --git a/code/modules/antagonists/slaughter/slaughter.dm b/code/modules/antagonists/slaughter/slaughter.dm index f616f2c848..55d60006cf 100644 --- a/code/modules/antagonists/slaughter/slaughter.dm +++ b/code/modules/antagonists/slaughter/slaughter.dm @@ -129,13 +129,12 @@ /mob/living/simple_animal/slaughter/proc/release_victims() if(!consumed_mobs) return - + var/turf/T = get_turf(src) + if(!T) + T = find_safe_turf() for(var/mob/living/M in consumed_mobs) if(!M) continue - var/turf/T = find_safe_turf() - if(!T) - T = get_turf(src) M.forceMove(T) /mob/living/simple_animal/slaughter/proc/refresh_consumed_buff() @@ -263,12 +262,12 @@ if(!consumed_mobs) return + var/turf/T = get_turf(src) + if(!T) + T = find_safe_turf() for(var/mob/living/M in consumed_mobs) if(!M) - continue - var/turf/T = find_safe_turf() - if(!T) - T = get_turf(src) + continue M.forceMove(T) if(M.revive(full_heal = TRUE, admin_revive = TRUE)) M.grab_ghost(force = TRUE) diff --git a/code/modules/antagonists/traitor/classes/traitor_class.dm b/code/modules/antagonists/traitor/classes/traitor_class.dm index 6aaa0b41c8..bcc6fc18dd 100644 --- a/code/modules/antagonists/traitor/classes/traitor_class.dm +++ b/code/modules/antagonists/traitor/classes/traitor_class.dm @@ -45,5 +45,5 @@ GLOBAL_LIST_EMPTY(traitor_classes) /datum/traitor_class/proc/clean_up_traitor(datum/antagonist/traitor/T) // Any effects that need to be cleaned up if traitor class is being swapped. -/datum/traitor_class/proc/on_process(/datum/antagonist/traitor/T) +/datum/traitor_class/proc/on_process(datum/antagonist/traitor/T) // only for processing traitor classes; runs once an SSprocessing tick diff --git a/code/modules/asset_cache/asset_list_items.dm b/code/modules/asset_cache/asset_list_items.dm index 6a49addc13..0af024985e 100644 --- a/code/modules/asset_cache/asset_list_items.dm +++ b/code/modules/asset_cache/asset_list_items.dm @@ -3,21 +3,21 @@ /datum/asset/simple/tgui_common keep_local_name = TRUE assets = list( - "tgui-common.bundle.js" = 'tgui/public/tgui-common.bundle.js', + "tgui-common.bundle.js" = file("tgui/public/tgui-common.bundle.js"), ) /datum/asset/simple/tgui keep_local_name = TRUE assets = list( - "tgui.bundle.js" = 'tgui/public/tgui.bundle.js', - "tgui.bundle.css" = 'tgui/public/tgui.bundle.css', + "tgui.bundle.js" = file("tgui/public/tgui.bundle.js"), + "tgui.bundle.css" = file("tgui/public/tgui.bundle.css"), ) /datum/asset/simple/tgui_panel keep_local_name = TRUE assets = list( - "tgui-panel.bundle.js" = 'tgui/public/tgui-panel.bundle.js', - "tgui-panel.bundle.css" = 'tgui/public/tgui-panel.bundle.css', + "tgui-panel.bundle.js" = file("tgui/public/tgui-panel.bundle.js"), + "tgui-panel.bundle.css" = file("tgui/public/tgui-panel.bundle.css"), ) /datum/asset/simple/headers @@ -168,10 +168,12 @@ /datum/asset/simple/namespaced/tgfont assets = list( - "tgfont.eot" = 'tgui/packages/tgfont/dist/tgfont.eot', - "tgfont.woff2" = 'tgui/packages/tgfont/dist/tgfont.woff2', + "tgfont.eot" = file("tgui/packages/tgfont/dist/tgfont.eot"), + "tgfont.woff2" = file("tgui/packages/tgfont/dist/tgfont.woff2"), + ) + parents = list( + "tgfont.css" = file("tgui/packages/tgfont/dist/tgfont.css"), ) - parents = list("tgfont.css" = 'tgui/packages/tgfont/dist/tgfont.css') /datum/asset/spritesheet/chat name = "chat" diff --git a/code/modules/atmospherics/auxgm/gas_types.dm b/code/modules/atmospherics/auxgm/gas_types.dm index 6d30d7ed92..6faa3d55d6 100644 --- a/code/modules/atmospherics/auxgm/gas_types.dm +++ b/code/modules/atmospherics/auxgm/gas_types.dm @@ -157,6 +157,8 @@ id = GAS_METHANE specific_heat = 30 name = "Methane" + powerloss_inhibition = 1 + heat_resistance = 3 breath_results = GAS_METHYL_BROMIDE fire_products = list(GAS_CO2 = 1, GAS_H2O = 2) fire_burn_rate = 0.5 @@ -177,6 +179,8 @@ id = GAS_METHYL_BROMIDE specific_heat = 42 name = "Methyl Bromide" + powermix = 1 + heat_penalty = -1 flags = GAS_FLAG_DANGEROUS breath_alert_info = list( not_enough_alert = list( diff --git a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm index 3a3ba555de..a0665707ff 100644 --- a/code/modules/atmospherics/environmental/LINDA_turf_tile.dm +++ b/code/modules/atmospherics/environmental/LINDA_turf_tile.dm @@ -32,6 +32,11 @@ if(!blocks_air) air = new(2500,src) air.copy_from_turf(src) + if(planetary_atmos && !(initial_gas_mix in SSair.planetary)) + var/datum/gas_mixture/mix = new + mix.parse_gas_string(initial_gas_mix) + mix.mark_immutable() + SSair.planetary[initial_gas_mix] = mix update_air_ref(planetary_atmos ? 1 : 2) . = ..() diff --git a/code/modules/atmospherics/gasmixtures/gas_mixture.dm b/code/modules/atmospherics/gasmixtures/gas_mixture.dm index ed4b1f63c2..b670baaae7 100644 --- a/code/modules/atmospherics/gasmixtures/gas_mixture.dm +++ b/code/modules/atmospherics/gasmixtures/gas_mixture.dm @@ -261,6 +261,8 @@ we use a hook instead return 1 /datum/gas_mixture/parse_gas_string(gas_string) + gas_string = SSair.preprocess_gas_string(gas_string) + var/list/gas = params2list(gas_string) if(gas["TEMP"]) var/temp = text2num(gas["TEMP"]) diff --git a/code/modules/atmospherics/gasmixtures/reactions.dm b/code/modules/atmospherics/gasmixtures/reactions.dm index b523d333df..19cd865bd0 100644 --- a/code/modules/atmospherics/gasmixtures/reactions.dm +++ b/code/modules/atmospherics/gasmixtures/reactions.dm @@ -648,7 +648,7 @@ //Replace miasma with oxygen var/cleaned_air = min(air.get_moles(GAS_MIASMA), 20 + (air.return_temperature() - FIRE_MINIMUM_TEMPERATURE_TO_EXIST - 70) / 20) air.adjust_moles(GAS_MIASMA, -cleaned_air) - air.adjust_moles(GAS_O2, cleaned_air) + air.adjust_moles(GAS_METHANE, cleaned_air) //Possibly burning a bit of organic matter through maillard reaction, so a *tiny* bit more heat would be understandable air.set_temperature(air.return_temperature() + cleaned_air * 0.002) diff --git a/code/modules/atmospherics/machinery/pipes/pipes.dm b/code/modules/atmospherics/machinery/pipes/pipes.dm index e05502dbad..e286cbbe7f 100644 --- a/code/modules/atmospherics/machinery/pipes/pipes.dm +++ b/code/modules/atmospherics/machinery/pipes/pipes.dm @@ -75,6 +75,9 @@ /obj/machinery/atmospherics/pipe/setPipenet(datum/pipeline/P) parent = P +/obj/machinery/atmospherics/pipe/zap_act(power, zap_flags) + return 0 // they're not really machines in the normal sense, probably shouldn't explode + /obj/machinery/atmospherics/pipe/Destroy() QDEL_NULL(parent) diff --git a/code/modules/cargo/packs/goodies.dm b/code/modules/cargo/packs/goodies.dm index 5d4598fd58..5151845221 100644 --- a/code/modules/cargo/packs/goodies.dm +++ b/code/modules/cargo/packs/goodies.dm @@ -70,6 +70,12 @@ cost = 1500 contains = list(/obj/item/toy/plush/beeplushie) +/datum/supply_pack/goody/dyespray + name = "Hair Dye Spray" + desc = "A cool spray to dye your hair with awesome colors!" + cost = PAYCHECK_EASY * 2 + contains = list(/obj/item/dyespray) + /datum/supply_pack/goody/beach_ball name = "Beach Ball" desc = "The simple beach ball is one of Nanotrasen's most popular products. 'Why do we make beach balls? Because we can! (TM)' - Nanotrasen" diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index 5bc0440b77..cb262e57f3 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -60,6 +60,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) //autocorrected this round, not that you'd need to check that. var/UI_style = null + var/outline_enabled = TRUE + var/outline_color = COLOR_BLUE_GRAY var/buttons_locked = FALSE var/hotkeys = FALSE @@ -117,6 +119,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) var/hair_color = "000000" //Hair color var/facial_hair_style = "Shaved" //Face hair type var/facial_hair_color = "000000" //Facial hair color + var/grad_style //Hair gradient style + var/grad_color = "FFFFFF" //Hair gradient color var/skin_tone = "caucasian1" //Skin color var/use_custom_skin_tone = FALSE var/left_eye_color = "000000" //Eye color @@ -503,6 +507,12 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "< >
" dat += "    Change
" + dat += "

Hair Gradient

" + + dat += "[grad_style]" + dat += "< >
" + dat += "    Change
" + dat += "" //Mutant stuff var/mutant_category = 0 @@ -774,6 +784,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) dat += "
" dat += "

General Settings

" dat += "UI Style: [UI_style]
" + dat += "Outline: [outline_enabled ? "Enabled" : "Disabled"]
" + dat += "Outline Color:     Change
" dat += "tgui Monitors: [(tgui_lock) ? "Primary" : "All"]
" dat += "tgui Style: [(tgui_fancy) ? "Fancy" : "No Frills"]
" dat += "Show Runechat Chat Bubbles: [chat_on_map ? "Enabled" : "Disabled"]
" @@ -1707,6 +1719,23 @@ GLOBAL_LIST_EMPTY(preferences_datums) if("previous_facehair_style") facial_hair_style = previous_list_item(facial_hair_style, GLOB.facial_hair_styles_list) + if("grad_color") + var/new_grad_color = input(user, "Choose your character's gradient colour:", "Character Preference","#"+grad_color) as color|null + if(new_grad_color) + grad_color = sanitize_hexcolor(new_grad_color, 6) + + if("grad_style") + var/new_grad_style + new_grad_style = input(user, "Choose your character's hair gradient style:", "Character Preference") as null|anything in GLOB.hair_gradients_list + if(new_grad_style) + grad_style = new_grad_style + + if("next_grad_style") + grad_style = next_list_item(grad_style, GLOB.hair_gradients_list) + + if("previous_grad_style") + grad_style = previous_list_item(grad_style, GLOB.hair_gradients_list) + if("cycle_bg") bgstate = next_list_item(bgstate, bgstate_options) @@ -2706,6 +2735,12 @@ GLOBAL_LIST_EMPTY(preferences_datums) buttons_locked = !buttons_locked if("tgui_fancy") tgui_fancy = !tgui_fancy + if("outline_enabled") + outline_enabled = !outline_enabled + if("outline_color") + var/pickedOutlineColor = input(user, "Choose your outline color.", "General Preference", outline_color) as color|null + if(pickedOutlineColor) + outline_color = pickedOutlineColor if("tgui_lock") tgui_lock = !tgui_lock if("winflash") @@ -3006,6 +3041,8 @@ GLOBAL_LIST_EMPTY(preferences_datums) character.dna.skin_tone_override = use_custom_skin_tone ? skin_tone : null character.hair_style = hair_style character.facial_hair_style = facial_hair_style + character.grad_style = grad_style + character.grad_color = grad_color character.underwear = underwear character.saved_underwear = underwear @@ -3070,6 +3107,7 @@ GLOBAL_LIST_EMPTY(preferences_datums) if(additional_language && additional_language != "None") var/language_entry = GLOB.roundstart_languages[additional_language] if(language_entry) + character.additional_language = language_entry character.grant_language(language_entry, TRUE, TRUE) //limb stuff, only done when initially spawning in diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm index d4d13dc40f..96ffd09af8 100644 --- a/code/modules/client/preferences_savefile.dm +++ b/code/modules/client/preferences_savefile.dm @@ -45,6 +45,9 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car if(current_version < 46) //If you remove this, remove force_reset_keybindings() too. force_reset_keybindings_direct(TRUE) addtimer(CALLBACK(src, .proc/force_reset_keybindings), 30) //No mob available when this is run, timer allows user choice. + if(current_version < 30) + outline_enabled = TRUE + outline_color = COLOR_BLUE_GRAY /datum/preferences/proc/update_character(current_version, savefile/S) if(current_version < 19) @@ -377,6 +380,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["ooccolor"] >> ooccolor S["lastchangelog"] >> lastchangelog S["UI_style"] >> UI_style + S["outline_color"] >> outline_color + S["outline_enabled"] >> outline_enabled S["hotkeys"] >> hotkeys S["chat_on_map"] >> chat_on_map S["max_chat_length"] >> max_chat_length @@ -555,6 +560,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["ooccolor"], ooccolor) WRITE_FILE(S["lastchangelog"], lastchangelog) WRITE_FILE(S["UI_style"], UI_style) + WRITE_FILE(S["outline_enabled"], outline_enabled) + WRITE_FILE(S["outline_color"], outline_color) WRITE_FILE(S["hotkeys"], hotkeys) WRITE_FILE(S["chat_on_map"], chat_on_map) WRITE_FILE(S["max_chat_length"], max_chat_length) @@ -676,6 +683,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car S["skin_tone"] >> skin_tone S["hair_style_name"] >> hair_style S["facial_style_name"] >> facial_hair_style + S["grad_style"] >> grad_style + S["grad_color"] >> grad_color S["underwear"] >> underwear S["undie_color"] >> undie_color S["undershirt"] >> undershirt @@ -868,6 +877,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car age = sanitize_integer(age, AGE_MIN, AGE_MAX, initial(age)) hair_color = sanitize_hexcolor(hair_color, 6, FALSE) facial_hair_color = sanitize_hexcolor(facial_hair_color, 6, FALSE) + grad_style = sanitize_inlist(grad_style, GLOB.hair_gradients_list, "None") + grad_color = sanitize_hexcolor(grad_color, 6, FALSE) eye_type = sanitize_inlist(eye_type, GLOB.eye_types, DEFAULT_EYES_TYPE) left_eye_color = sanitize_hexcolor(left_eye_color, 6, FALSE) right_eye_color = sanitize_hexcolor(right_eye_color, 6, FALSE) @@ -1037,6 +1048,8 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car WRITE_FILE(S["skin_tone"] , skin_tone) WRITE_FILE(S["hair_style_name"] , hair_style) WRITE_FILE(S["facial_style_name"] , facial_hair_style) + WRITE_FILE(S["grad_style"] , grad_style) + WRITE_FILE(S["grad_color"] , grad_color) WRITE_FILE(S["underwear"] , underwear) WRITE_FILE(S["undie_color"] , undie_color) WRITE_FILE(S["undershirt"] , undershirt) diff --git a/code/modules/clothing/masks/gasmask.dm b/code/modules/clothing/masks/gasmask.dm index 525db577e0..2e3c544d08 100644 --- a/code/modules/clothing/masks/gasmask.dm +++ b/code/modules/clothing/masks/gasmask.dm @@ -83,6 +83,7 @@ icon_state = "clown" item_state = "clown_hat" dye_color = "clown" + w_class = WEIGHT_CLASS_SMALL flags_cover = MASKCOVERSEYES resistance_flags = FLAMMABLE actions_types = list(/datum/action/item_action/adjust) @@ -131,6 +132,7 @@ clothing_flags = ALLOWINTERNALS icon_state = "mime" item_state = "mime" + w_class = WEIGHT_CLASS_SMALL flags_cover = MASKCOVERSEYES resistance_flags = FLAMMABLE actions_types = list(/datum/action/item_action/adjust) diff --git a/code/modules/clothing/outfits/ert.dm b/code/modules/clothing/outfits/ert.dm index 7cd2e9b394..86aa0748df 100644 --- a/code/modules/clothing/outfits/ert.dm +++ b/code/modules/clothing/outfits/ert.dm @@ -30,7 +30,7 @@ glasses = /obj/item/clothing/glasses/hud/security/sunglasses back = /obj/item/storage/backpack/captain belt = /obj/item/storage/belt/security/full - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer=1,\ /obj/item/gun/energy/e_gun=1) @@ -50,7 +50,7 @@ suit = /obj/item/clothing/suit/space/hardsuit/ert/alert glasses = /obj/item/clothing/glasses/thermal/eyepatch - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/gun/energy/e_gun=1) @@ -58,7 +58,7 @@ /datum/outfit/ert/commander/alert/red name = "ERT Commander - Red Alert" - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/gun/energy/pulse/pistol/loyalpin=1) @@ -71,7 +71,7 @@ glasses = /obj/item/clothing/glasses/hud/security/sunglasses belt = /obj/item/storage/belt/security/full back = /obj/item/storage/backpack/security - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/storage/box/handcuffs=1,\ /obj/item/clothing/mask/gas/sechailer=1,\ /obj/item/gun/energy/e_gun/stun=1,\ @@ -91,7 +91,7 @@ name = "ERT Security - Amber Alert" suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/sec - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/storage/box/handcuffs=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/melee/baton/loaded=1,\ @@ -99,7 +99,7 @@ /datum/outfit/ert/security/alert/red name = "ERT Security - Red Alert" - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/storage/box/handcuffs=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/melee/baton/loaded=1,\ @@ -114,7 +114,7 @@ back = /obj/item/storage/backpack/satchel/med belt = /obj/item/storage/belt/medical r_hand = /obj/item/storage/firstaid/regular - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer=1,\ /obj/item/gun/energy/e_gun=1,\ @@ -135,7 +135,7 @@ name = "ERT Medic - Amber Alert" suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/med - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/gun/energy/e_gun=1,\ @@ -144,7 +144,7 @@ /datum/outfit/ert/medic/alert/red name = "ERT Medic - Red Alert" - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/gun/energy/pulse/pistol/loyalpin=1,\ @@ -161,7 +161,7 @@ belt = /obj/item/storage/belt/utility/full l_pocket = /obj/item/rcd_ammo/large r_hand = /obj/item/storage/firstaid/regular - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer=1,\ /obj/item/gun/energy/e_gun=1,\ @@ -181,7 +181,7 @@ name = "ERT Engineer - Amber Alert" suit = /obj/item/clothing/suit/space/hardsuit/ert/alert/engi - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/gun/energy/e_gun=1,\ @@ -189,7 +189,7 @@ /datum/outfit/ert/engineer/alert/red name = "ERT Engineer - Red Alert" - backpack_contents = list(/obj/item/storage/box/engineer=1,\ + backpack_contents = list(/obj/item/storage/box/survival/engineer=1,\ /obj/item/melee/baton/loaded=1,\ /obj/item/clothing/mask/gas/sechailer/swat=1,\ /obj/item/gun/energy/pulse/pistol/loyalpin=1,\ @@ -260,7 +260,7 @@ name = "Inquisition Commander" r_hand = /obj/item/nullrod/scythe/talking/chainsword suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal - backpack_contents = list(/obj/item/storage/box/engineer=1, + backpack_contents = list(/obj/item/storage/box/survival/engineer=1, /obj/item/clothing/mask/gas/sechailer=1, /obj/item/gun/energy/e_gun=1) @@ -269,7 +269,7 @@ suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor - backpack_contents = list(/obj/item/storage/box/engineer=1, + backpack_contents = list(/obj/item/storage/box/survival/engineer=1, /obj/item/storage/box/handcuffs=1, /obj/item/clothing/mask/gas/sechailer=1, /obj/item/gun/energy/e_gun/stun=1, @@ -281,7 +281,7 @@ suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor - backpack_contents = list(/obj/item/storage/box/engineer=1, + backpack_contents = list(/obj/item/storage/box/survival/engineer=1, /obj/item/melee/baton/loaded=1, /obj/item/clothing/mask/gas/sechailer=1, /obj/item/gun/energy/e_gun=1, @@ -307,7 +307,7 @@ glasses = /obj/item/clothing/glasses/hud/health back = /obj/item/storage/backpack/cultpack belt = /obj/item/storage/belt/soulstone - backpack_contents = list(/obj/item/storage/box/engineer=1, + backpack_contents = list(/obj/item/storage/box/survival/engineer=1, /obj/item/nullrod=1, /obj/item/clothing/mask/gas/sechailer=1, /obj/item/gun/energy/e_gun=1, @@ -319,7 +319,7 @@ suit = /obj/item/clothing/suit/space/hardsuit/ert/paranormal/inquisitor belt = /obj/item/storage/belt/soulstone/full/chappy - backpack_contents = list(/obj/item/storage/box/engineer=1, + backpack_contents = list(/obj/item/storage/box/survival/engineer=1, /obj/item/grenade/chem_grenade/holy=1, /obj/item/nullrod=1, /obj/item/clothing/mask/gas/sechailer=1, diff --git a/code/modules/clothing/outfits/vr.dm b/code/modules/clothing/outfits/vr.dm index acf015c845..ac852a35a8 100644 --- a/code/modules/clothing/outfits/vr.dm +++ b/code/modules/clothing/outfits/vr.dm @@ -28,7 +28,7 @@ id = /obj/item/card/id/syndicate/locked_banking belt = /obj/item/gun/ballistic/automatic/pistol l_pocket = /obj/item/paper/fluff/vr/fluke_ops - backpack_contents = list(/obj/item/storage/box/syndie=1,\ + backpack_contents = list(/obj/item/storage/box/survival/syndie=1,\ /obj/item/kitchen/knife/combat/survival) starting_funds = 0 //Should be operating, not shopping. diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm index 4af9e7387d..cd48a81350 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -237,12 +237,28 @@ /obj/item/clothing/head/helmet/space/hardsuit/mining/Initialize() . = ..() AddComponent(/datum/component/armor_plate) + RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon) + +/obj/item/clothing/head/helmet/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount) + SIGNAL_HANDLER + + if(amount) + name = "reinforced [initial(name)]" + hardsuit_type = "mining_goliath" + if(amount == maxamount) + hardsuit_type = "mining_goliath_full" + icon_state = "hardsuit[on]-[hardsuit_type]" + if(ishuman(loc)) + var/mob/living/carbon/human/wearer = loc + if(wearer.head == src) + wearer.update_inv_head() /obj/item/clothing/suit/space/hardsuit/mining icon_state = "hardsuit-mining" name = "mining hardsuit" desc = "A special suit that protects against hazardous, low pressure environments. Has reinforced plating for wildlife encounters." item_state = "mining_hardsuit" + hardsuit_type = "mining" max_heat_protection_temperature = FIRE_SUIT_MAX_TEMP_PROTECT resistance_flags = FIRE_PROOF armor = list("melee" = 30, "bullet" = 5, "laser" = 10, "energy" = 5, "bomb" = 50, "bio" = 100, "rad" = 50, "fire" = 50, "acid" = 75, "wound" = 15) @@ -254,6 +270,21 @@ /obj/item/clothing/suit/space/hardsuit/mining/Initialize() . = ..() AddComponent(/datum/component/armor_plate) + RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon) + +/obj/item/clothing/suit/space/hardsuit/mining/proc/upgrade_icon(datum/source, amount, maxamount) + SIGNAL_HANDLER + + if(amount) + name = "reinforced [initial(name)]" + hardsuit_type = "mining_goliath" + if(amount == maxamount) + hardsuit_type = "mining_goliath_full" + icon_state = "hardsuit-[hardsuit_type]" + if(ishuman(loc)) + var/mob/living/carbon/human/wearer = loc + if(wearer.wear_suit == src) + wearer.update_inv_wear_suit() //Syndicate hardsuit /obj/item/clothing/head/helmet/space/hardsuit/syndi diff --git a/code/modules/clothing/suits/armor.dm b/code/modules/clothing/suits/armor.dm index 254ed60c03..90fd0b2812 100644 --- a/code/modules/clothing/suits/armor.dm +++ b/code/modules/clothing/suits/armor.dm @@ -136,17 +136,24 @@ icon_state = "syndievest" mutantrace_variation = STYLE_DIGITIGRADE -/obj/item/clothing/suit/armor/vest/capcarapace/alt +/obj/item/clothing/suit/toggle/captains_parade name = "captain's parade jacket" desc = "For when an armoured vest isn't fashionable enough." icon_state = "capformal" item_state = "capspacesuit" + body_parts_covered = CHEST|GROIN|ARMS + armor = list("melee" = 50, "bullet" = 40, "laser" = 50, "energy" = 50, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 100, "acid" = 90, "wound" = 10) + togglename = "buttons" + +/obj/item/clothing/suit/toggle/captains_parade/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed /obj/item/clothing/suit/armor/riot name = "riot suit" desc = "A suit of semi-flexible polycarbonate body armor with heavy padding to protect against melee attacks. Helps the wearer resist shoving in close quarters." - icon_state = "swat" - item_state = "swat_suit" + icon_state = "riot" + item_state = "riot" body_parts_covered = CHEST|GROIN|LEGS|FEET|ARMS|HANDS cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS heat_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS @@ -319,3 +326,29 @@ cold_protection = CHEST|GROIN|LEGS|FEET|ARMS|HANDS min_cold_protection_temperature = SPACE_SUIT_MIN_TEMP_PROTECT armor = list("melee" = 25, "bullet" = 20, "laser" = 20, "energy" = 10, "bomb" = 20, "bio" = 50, "rad" = 20, "fire" = -10, "acid" = 50, "wound" = 10) + +/obj/item/clothing/suit/toggle/armor/vest/centcom_formal + name = "\improper CentCom formal coat" + desc = "A stylish coat given to CentCom Commanders. Perfect for sending ERTs to suicide missions with style!" + icon_state = "centcom_formal" + item_state = "centcom" + body_parts_covered = CHEST|GROIN|ARMS + armor = list("melee" = 35, "bullet" = 40, "laser" = 40, "energy" = 50, "bomb" = 35, "bio" = 10, "rad" = 10, "fire" = 10, "acid" = 60) + togglename = "buttons" + +/obj/item/clothing/suit/toggle/armor/vest/centcom_formal/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed + +/obj/item/clothing/suit/toggle/armor/hos/hos_formal + name = "\improper Head of Security's parade jacket" + desc = "For when an armoured vest isn't fashionable enough." + icon_state = "hosformal" + item_state = "hostrench" + body_parts_covered = CHEST|GROIN|ARMS + armor = list("melee" = 30, "bullet" = 30, "laser" = 30, "energy" = 40, "bomb" = 25, "bio" = 0, "rad" = 0, "fire" = 70, "acid" = 90, "wound" = 10) + togglename = "buttons" + +/obj/item/clothing/suit/toggle/armor/hos/hos_formal/Initialize() + . = ..() + allowed = GLOB.security_wintercoat_allowed diff --git a/code/modules/clothing/suits/toggles.dm b/code/modules/clothing/suits/toggles.dm index 4e29483846..78708f54bb 100644 --- a/code/modules/clothing/suits/toggles.dm +++ b/code/modules/clothing/suits/toggles.dm @@ -6,6 +6,7 @@ var/hoodtype = /obj/item/clothing/head/hooded/winterhood //so the chaplain hoodie or other hoodies can override this ///Alternative mode for hiding the hood, instead of storing the hood in the suit it qdels it, useful for when you deal with hooded suit with storage. var/alternative_mode = FALSE + var/no_t //do not update sprites when pulling up hood so we can avoid oddities with certain mechanics /obj/item/clothing/suit/hooded/Initialize() . = ..() @@ -51,6 +52,8 @@ update_icon() /obj/item/clothing/suit/hooded/update_icon_state() + if(no_t) + return icon_state = "[initial(icon_state)]" if(ishuman(hood?.loc)) var/mob/living/carbon/human/H = hood.loc diff --git a/code/modules/events/supermatter_surge.dm b/code/modules/events/supermatter_surge.dm index 6b0a093440..78b5c9e70c 100644 --- a/code/modules/events/supermatter_surge.dm +++ b/code/modules/events/supermatter_surge.dm @@ -27,13 +27,27 @@ if(prob(low_threat_perc)) severity = "low; the supermatter should return to normal operation shortly." else - severity = "medium; the supermatter should return to normal operation, but check NT CIMS to ensure this." + severity = "medium; the supermatter should return to normal operation, but regardless, check if the emitters may need to be turned off temporarily." else - severity = "high; if the supermatter's cooling is not fortified, coolant may need to be added." + severity = "high; the emitters likely need to be turned off, and if the supermatter's cooling loop is not fortified, pre-cooled gas may need to be added." if(100000 to INFINITY) - severity = "extreme; emergency action is likely to be required even if coolant loop is fine." + severity = "extreme; emergency action is likely to be required even if coolant loop is fine. Turn off the emitters and make sure the loop is properly cooling gases." if(power > 20000 || prob(round(power/200))) priority_announce("Supermatter surge detected. Estimated severity is [severity]", "Anomaly Alert") /datum/round_event/supermatter_surge/start() - GLOB.main_supermatter_engine.matter_power += power + var/obj/machinery/power/supermatter_crystal/supermatter = GLOB.main_supermatter_engine + var/power_proportion = supermatter.powerloss_inhibitor/2 // what % of the power goes into matter power, at most 50% + // we reduce the proportion that goes into actual matter power based on powerloss inhibitor + // primarily so the supermatter doesn't tesla the instant these happen + supermatter.matter_power += power * power_proportion + var/datum/gas_mixture/methane_puff = new + var/selected_gas = pick(4;GAS_CO2, 10;GAS_METHANE, 4;GAS_H2O, 1;GAS_BZ, 1;GAS_METHYL_BROMIDE) + methane_puff.set_moles(selected_gas, 500) + methane_puff.set_temperature(500) + var/energy_ratio = (power * 500 * (1-power_proportion)) / methane_puff.thermal_energy() + if(energy_ratio < 1) // energy output we want is lower than current energy, reduce the amount of gas we puff out + methane_puff.set_moles(GAS_METHANE, energy_ratio * 500) + else // energy output we want is higher than current energy, increase its actual heat + methane_puff.set_temperature(energy_ratio * 500) + supermatter.assume_air(methane_puff) diff --git a/code/modules/holiday/holidays.dm b/code/modules/holiday/holidays.dm index e3f5264568..239246cd59 100644 --- a/code/modules/holiday/holidays.dm +++ b/code/modules/holiday/holidays.dm @@ -641,6 +641,18 @@ Since Ramadan is an entire month that lasts 29.5 days on average, the start and /datum/holiday/easter/getStationPrefix() return pick("Fluffy","Bunny","Easter","Egg") +/datum/holiday/ianbirthday + name = "Ian's Birthday" //github.com/tgstation/tgstation/commit/de7e4f0de0d568cd6e1f0d7bcc3fd34700598acb + begin_month = SEPTEMBER + begin_day = 9 + end_day = 10 + +/datum/holiday/ianbirthday/greet() + return "Happy birthday, Ian!" + +/datum/holiday/ianbirthday/getStationPrefix() + return pick("Ian", "Corgi", "Erro") + //Random citadel thing for halloween species /proc/force_enable_halloween_species() var/list/oldlist = SSevents.holidays diff --git a/code/modules/jobs/job_types/atmospheric_technician.dm b/code/modules/jobs/job_types/atmospheric_technician.dm index bff56d1d16..de66c32138 100644 --- a/code/modules/jobs/job_types/atmospheric_technician.dm +++ b/code/modules/jobs/job_types/atmospheric_technician.dm @@ -39,7 +39,7 @@ backpack = /obj/item/storage/backpack/industrial satchel = /obj/item/storage/backpack/satchel/eng duffelbag = /obj/item/storage/backpack/duffelbag/engineering - box = /obj/item/storage/box/engineer + box = /obj/item/storage/box/survival/engineer pda_slot = SLOT_L_STORE backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1) diff --git a/code/modules/jobs/job_types/chief_engineer.dm b/code/modules/jobs/job_types/chief_engineer.dm index 902be0bdc8..804952dbb2 100644 --- a/code/modules/jobs/job_types/chief_engineer.dm +++ b/code/modules/jobs/job_types/chief_engineer.dm @@ -54,7 +54,7 @@ backpack = /obj/item/storage/backpack/industrial satchel = /obj/item/storage/backpack/satchel/eng duffelbag = /obj/item/storage/backpack/duffelbag/engineering - box = /obj/item/storage/box/engineer + box = /obj/item/storage/box/survival/engineer pda_slot = SLOT_L_STORE chameleon_extras = /obj/item/stamp/ce diff --git a/code/modules/jobs/job_types/head_of_security.dm b/code/modules/jobs/job_types/head_of_security.dm index c772a8acae..d612b6a56d 100644 --- a/code/modules/jobs/job_types/head_of_security.dm +++ b/code/modules/jobs/job_types/head_of_security.dm @@ -58,7 +58,7 @@ backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel/sec duffelbag = /obj/item/storage/backpack/duffelbag/sec - box = /obj/item/storage/box/security + box = /obj/item/storage/box/survival/security implants = list(/obj/item/implant/mindshield) diff --git a/code/modules/jobs/job_types/security_officer.dm b/code/modules/jobs/job_types/security_officer.dm index bdae7fe028..3462fb96c9 100644 --- a/code/modules/jobs/job_types/security_officer.dm +++ b/code/modules/jobs/job_types/security_officer.dm @@ -133,7 +133,7 @@ GLOBAL_LIST_INIT(available_depts, list(SEC_DEPT_ENGINEERING, SEC_DEPT_MEDICAL, S backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel/sec duffelbag = /obj/item/storage/backpack/duffelbag/sec - box = /obj/item/storage/box/security + box = /obj/item/storage/box/survival/security implants = list(/obj/item/implant/mindshield) diff --git a/code/modules/jobs/job_types/shaft_miner.dm b/code/modules/jobs/job_types/shaft_miner.dm index 04d3fb53b8..0c04380afe 100644 --- a/code/modules/jobs/job_types/shaft_miner.dm +++ b/code/modules/jobs/job_types/shaft_miner.dm @@ -45,7 +45,7 @@ backpack = /obj/item/storage/backpack/explorer satchel = /obj/item/storage/backpack/satchel/explorer duffelbag = /obj/item/storage/backpack/duffelbag - box = /obj/item/storage/box/survival_mining + box = /obj/item/storage/box/survival/mining chameleon_extras = /obj/item/gun/energy/kinetic_accelerator diff --git a/code/modules/jobs/job_types/station_engineer.dm b/code/modules/jobs/job_types/station_engineer.dm index 2396728ad8..25bd2196a0 100644 --- a/code/modules/jobs/job_types/station_engineer.dm +++ b/code/modules/jobs/job_types/station_engineer.dm @@ -42,7 +42,7 @@ backpack = /obj/item/storage/backpack/industrial satchel = /obj/item/storage/backpack/satchel/eng duffelbag = /obj/item/storage/backpack/duffelbag/engineering - box = /obj/item/storage/box/engineer + box = /obj/item/storage/box/survival/engineer pda_slot = SLOT_L_STORE backpack_contents = list(/obj/item/modular_computer/tablet/preset/advanced=1) diff --git a/code/modules/jobs/job_types/warden.dm b/code/modules/jobs/job_types/warden.dm index dae3094ebe..074ccac09f 100644 --- a/code/modules/jobs/job_types/warden.dm +++ b/code/modules/jobs/job_types/warden.dm @@ -54,7 +54,7 @@ backpack = /obj/item/storage/backpack/security satchel = /obj/item/storage/backpack/satchel/sec duffelbag = /obj/item/storage/backpack/duffelbag/sec - box = /obj/item/storage/box/security + box = /obj/item/storage/box/survival/security implants = list(/obj/item/implant/mindshield) diff --git a/code/modules/mapping/mapping_helpers/_mapping_helpers.dm b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm index 4de16ba350..a96b81528f 100644 --- a/code/modules/mapping/mapping_helpers/_mapping_helpers.dm +++ b/code/modules/mapping/mapping_helpers/_mapping_helpers.dm @@ -8,61 +8,73 @@ ..() return late ? INITIALIZE_HINT_LATELOAD : INITIALIZE_HINT_QDEL + //airlock helpers /obj/effect/mapping_helpers/airlock layer = DOOR_HELPER_LAYER +/obj/effect/mapping_helpers/airlock/Initialize(mapload) + . = ..() + if(!mapload) + log_mapping("[src] spawned outside of mapload!") + return + var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc + if(!airlock) + log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") + else + payload(airlock) + +/obj/effect/mapping_helpers/airlock/proc/payload(obj/machinery/door/airlock/payload) + return + /obj/effect/mapping_helpers/airlock/cyclelink_helper name = "airlock cyclelink helper" icon_state = "airlock_cyclelink_helper" -/obj/effect/mapping_helpers/airlock/cyclelink_helper/Initialize(mapload) - . = ..() - if(!mapload) - log_mapping("[src] spawned outside of mapload!") - return - var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc - if(airlock) - if(airlock.cyclelinkeddir) - log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!") - else - airlock.cyclelinkeddir = dir +/obj/effect/mapping_helpers/airlock/cyclelink_helper/payload(obj/machinery/door/airlock/airlock) + if(airlock.cyclelinkeddir) + log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] cyclelinkeddir, but it's already set!") else - log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") + airlock.cyclelinkeddir = dir +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi + name = "airlock multi-cyclelink helper" + icon_state = "airlock_multicyclelink_helper" + var/cycle_id + +/obj/effect/mapping_helpers/airlock/cyclelink_helper_multi/payload(obj/machinery/door/airlock/airlock) + if(airlock.closeOtherId) + log_mapping("[src] at [AREACOORD(src)] tried to set [airlock] closeOtherId, but it's already set!") + else + airlock.closeOtherId = cycle_id /obj/effect/mapping_helpers/airlock/locked name = "airlock lock helper" icon_state = "airlock_locked_helper" -/obj/effect/mapping_helpers/airlock/locked/Initialize(mapload) - . = ..() - if(!mapload) - log_mapping("[src] spawned outside of mapload!") - return - var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc - if(airlock) - if(airlock.locked) - log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!") - else - airlock.locked = TRUE +/obj/effect/mapping_helpers/airlock/locked/payload(obj/machinery/door/airlock/airlock) + if(airlock.locked) + log_mapping("[src] at [AREACOORD(src)] tried to bolt [airlock] but it's already locked!") else - log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") + airlock.locked = TRUE + /obj/effect/mapping_helpers/airlock/unres name = "airlock unresctricted side helper" icon_state = "airlock_unres_helper" -/obj/effect/mapping_helpers/airlock/unres/Initialize(mapload) - . = ..() - if(!mapload) - log_mapping("[src] spawned outside of mapload!") - return - var/obj/machinery/door/airlock/airlock = locate(/obj/machinery/door/airlock) in loc - if(airlock) - airlock.unres_sides ^= dir +/obj/effect/mapping_helpers/airlock/unres/payload(obj/machinery/door/airlock/airlock) + airlock.unres_sides ^= dir + +/obj/effect/mapping_helpers/airlock/abandoned + name = "airlock abandoned helper" + icon_state = "airlock_abandoned" + +/obj/effect/mapping_helpers/airlock/abandoned/payload(obj/machinery/door/airlock/airlock) + if(airlock.abandoned) + log_mapping("[src] at [AREACOORD(src)] tried to make [airlock] abandoned but it's already abandoned!") else - log_mapping("[src] failed to find an airlock at [AREACOORD(src)]") + airlock.abandoned = TRUE //needs to do its thing before spawn_rivers() is called @@ -79,9 +91,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) //This helper applies components to things on the map directly. /obj/effect/mapping_helpers/component_injector name = "Component Injector" + icon_state = "component" late = TRUE - var/target_type - var/target_name + var/all = FALSE //Will inject into all fitting the criteria if true, otherwise first found + var/target_type //Will inject into atoms of this type + var/target_name //Will inject into atoms with this name var/component_type //Late init so everything is likely ready and loaded (no warranty) @@ -98,8 +112,11 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) continue var/cargs = build_args() A._AddComponent(cargs) + if(!all) + qdel(src) + return + if(all) qdel(src) - return /obj/effect/mapping_helpers/component_injector/proc/build_args() return list(component_type) @@ -115,3 +132,276 @@ INITIALIZE_IMMEDIATE(/obj/effect/mapping_helpers/no_lava) CRASH("Wrong disease type passed in.") var/datum/disease/D = new disease_type() return list(component_type,D) + +// /obj/effect/mapping_helpers/component_injector/areabound +// name = "Areabound Injector" +// icon_state = "component_areabound" +// component_type = /datum/component/areabound +// target_type = /atom/movable + +/obj/effect/mapping_helpers/dead_body_placer + name = "Dead Body placer" + late = TRUE + icon_state = "deadbodyplacer" + var/bodycount = 2 //number of bodies to spawn + +/obj/effect/mapping_helpers/dead_body_placer/LateInitialize() + var/area/a = get_area(src) + var/list/trays = list() + for (var/i in a.contents) + if (istype(i, /obj/structure/bodycontainer/morgue)) + trays += i + if(!trays.len) + log_mapping("[src] at [x],[y] could not find any morgues.") + return + for (var/i = 1 to bodycount) + var/obj/structure/bodycontainer/morgue/j = pick(trays) + var/mob/living/carbon/human/h = new /mob/living/carbon/human(j, 1) + h.death() + for (var/part in h.internal_organs) //randomly remove organs from each body, set those we keep to be in stasis + if (prob(40)) + qdel(part) + else + var/obj/item/organ/O = part + O.organ_flags |= ORGAN_FROZEN + j.update_appearance() + qdel(src) + + +//On Ian's birthday, the hop's office is decorated. +/obj/effect/mapping_helpers/ianbirthday + name = "Ian's Bday Helper" + late = TRUE + icon_state = "iansbdayhelper" + var/balloon_clusters = 2 + +/obj/effect/mapping_helpers/ianbirthday/LateInitialize() + if(locate(/datum/holiday/ianbirthday) in SSevents.holidays) + birthday() + qdel(src) + +/obj/effect/mapping_helpers/ianbirthday/proc/birthday() + var/area/a = get_area(src) + var/list/table = list()//should only be one aka the front desk, but just in case... + var/list/openturfs = list() + + //confetti and a corgi balloon! (and some list stuff for more decorations) + for(var/thing in a.contents) + if(istype(thing, /obj/structure/table/reinforced)) + table += thing + if(isopenturf(thing)) + // new /obj/effect/decal/cleanable/confetti(thing) + // if(locate(/obj/structure/bed/dogbed/ian) in thing) + // new /obj/item/toy/balloon/corgi(thing) + // else + openturfs += thing + + //cake + knife to cut it! + if(length(table)) + var/turf/food_turf = get_turf(pick(table)) + new /obj/item/kitchen/knife(food_turf) + var/obj/item/reagent_containers/food/snacks/store/cake/birthday/iancake = new(food_turf) + iancake.desc = "Happy birthday, Ian!" + // remind me to give ian proper baloons! + //some balloons! this picks an open turf and pops a few balloons in and around that turf, yay. + // for(var/i in 1 to balloon_clusters) + // var/turf/clusterspot = pick_n_take(openturfs) + // new /obj/item/toy/balloon(clusterspot) + // var/balloons_left_to_give = 3 //the amount of balloons around the cluster + // var/list/dirs_to_balloon = GLOB.cardinals.Copy() + // while(balloons_left_to_give > 0) + // balloons_left_to_give-- + // var/chosen_dir = pick_n_take(dirs_to_balloon) + // var/turf/balloonstep = get_step(clusterspot, chosen_dir) + // var/placed = FALSE + // if(isopenturf(balloonstep)) + // var/obj/item/toy/balloon/B = new(balloonstep)//this clumps the cluster together + // placed = TRUE + // if(chosen_dir == NORTH) + // B.pixel_y -= 10 + // if(chosen_dir == SOUTH) + // B.pixel_y += 10 + // if(chosen_dir == EAST) + // B.pixel_x -= 10 + // if(chosen_dir == WEST) + // B.pixel_x += 10 + // if(!placed) + // new /obj/item/toy/balloon(clusterspot) + //remind me to add wall decor! + +/obj/effect/mapping_helpers/ianbirthday/admin//so admins may birthday any room + name = "generic birthday setup" + icon_state = "bdayhelper" + +/obj/effect/mapping_helpers/ianbirthday/admin/LateInitialize() + birthday() + qdel(src) + +//Ian, like most dogs, loves a good new years eve party. +/obj/effect/mapping_helpers/iannewyear + name = "Ian's New Years Helper" + late = TRUE + icon_state = "iansnewyrshelper" + +/obj/effect/mapping_helpers/iannewyear/LateInitialize() + if(SSevents.holidays && SSevents.holidays[NEW_YEAR]) + fireworks() + qdel(src) + +/obj/effect/mapping_helpers/iannewyear/proc/fireworks() + var/area/a = get_area(src) + var/list/table = list()//should only be one aka the front desk, but just in case... + var/list/openturfs = list() + + for(var/thing in a.contents) + if(istype(thing, /obj/structure/table/reinforced)) + table += thing + else if(isopenturf(thing)) + if(locate(/obj/structure/bed/dogbed/ian) in thing) + new /obj/item/clothing/head/festive(thing) + var/obj/item/reagent_containers/food/drinks/bottle/champagne/iandrink = new(thing) + iandrink.name = "dog champagne" + iandrink.pixel_y += 8 + iandrink.pixel_x += 8 + else + openturfs += thing + + var/turf/fireworks_turf = get_turf(pick(table)) + var/obj/item/storage/box/matches/matchbox = new(fireworks_turf) + matchbox.pixel_y += 8 + matchbox.pixel_x -= 3 + // new /obj/item/storage/box/fireworks/dangerous(fireworks_turf) //dangerous version for extra holiday memes. + +//lets mappers place notes on airlocks with custom info or a pre-made note from a path +/obj/effect/mapping_helpers/airlock_note_placer + name = "Airlock Note Placer" + late = TRUE + icon_state = "airlocknoteplacer" + var/note_info //for writing out custom notes without creating an extra paper subtype + var/note_name //custom note name + var/note_path //if you already have something wrote up in a paper subtype, put the path here + +/obj/effect/mapping_helpers/airlock_note_placer/LateInitialize() + var/turf/turf = get_turf(src) + if(note_path && !istype(note_path, /obj/item/paper)) //don't put non-paper in the paper slot thank you + log_mapping("[src] at [x],[y] had an improper note_path path, could not place paper note.") + qdel(src) + if(locate(/obj/machinery/door/airlock) in turf) + var/obj/machinery/door/airlock/found_airlock = locate(/obj/machinery/door/airlock) in turf + if(note_path) + found_airlock.note = note_path + found_airlock.update_appearance() + qdel(src) + if(note_info) + var/obj/item/paper/paper = new /obj/item/paper(src) + if(note_name) + paper.name = note_name + paper.info = "[note_info]" + found_airlock.note = paper + paper.forceMove(found_airlock) + found_airlock.update_appearance() + qdel(src) + log_mapping("[src] at [x],[y] had no note_path or note_info, cannot place paper note.") + qdel(src) + log_mapping("[src] at [x],[y] could not find an airlock on current turf, cannot place paper note.") + qdel(src) + +//This helper applies traits to things on the map directly. +/obj/effect/mapping_helpers/trait_injector + name = "Trait Injector" + icon_state = "trait" + late = TRUE + ///Will inject into all fitting the criteria if false, otherwise first found. + var/first_match_only = TRUE + ///Will inject into atoms of this type. + var/target_type + ///Will inject into atoms with this name. + var/target_name + ///Name of the trait, in the lower-case text (NOT the upper-case define) form. + var/trait_name + +//Late init so everything is likely ready and loaded (no warranty) +/obj/effect/mapping_helpers/trait_injector/LateInitialize() + if(!GLOB.trait_name_map) + GLOB.trait_name_map = generate_trait_name_map() + if(!GLOB.trait_name_map.Find(trait_name)) + CRASH("Wrong trait in [type] - [trait_name] is not a trait") + var/turf/target_turf = get_turf(src) + var/matches_found = 0 + for(var/a in target_turf.GetAllContents()) + var/atom/atom_on_turf = a + if(atom_on_turf == src) + continue + if(target_name && atom_on_turf.name != target_name) + continue + if(target_type && !istype(atom_on_turf,target_type)) + continue + ADD_TRAIT(atom_on_turf, trait_name, MAPPING_HELPER_TRAIT) + matches_found++ + if(first_match_only) + qdel(src) + return + if(!matches_found) + stack_trace("Trait mapper found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name] | trait name: [trait_name]") + qdel(src) + +/// Fetches an external dmi and applies to the target object +/obj/effect/mapping_helpers/custom_icon + name = "Custom Icon Helper" + icon_state = "trait" + late = TRUE + ///Will inject into all fitting the criteria if false, otherwise first found. + var/first_match_only = TRUE + ///Will inject into atoms of this type. + var/target_type + ///Will inject into atoms with this name. + var/target_name + /// This is the var tha will be set with the fetched icon. In case you want to set some secondary icon sheets like inhands and such. + var/target_variable = "icon" + /// This should return raw dmi in response to http get request. For example: "https://github.com/tgstation/SS13-sprites/raw/master/mob/medu.dmi?raw=true" + var/icon_url + +/obj/effect/mapping_helpers/custom_icon/LateInitialize() + ///TODO put this injector stuff under common root + var/I = fetch_icon(icon_url) + var/turf/target_turf = get_turf(src) + var/matches_found = 0 + for(var/a in target_turf.GetAllContents()) + var/atom/atom_on_turf = a + if(atom_on_turf == src) + continue + if(target_name && atom_on_turf.name != target_name) + continue + if(target_type && !istype(atom_on_turf,target_type)) + continue + atom_on_turf.vars[target_variable] = I + matches_found++ + if(first_match_only) + qdel(src) + return + if(!matches_found) + stack_trace("[src] found no targets at ([x], [y], [z]). First Match Only: [first_match_only ? "true" : "false"] target type: [target_type] | target name: [target_name]") + qdel(src) + +/obj/effect/mapping_helpers/custom_icon/proc/fetch_icon(url) + var/static/icon_cache = list() + var/static/query_in_progress = FALSE //We're using a single tmp file so keep it linear. + if(query_in_progress) + UNTIL(!query_in_progress) + if(icon_cache[url]) + return icon_cache[url] + log_asset("Custom Icon Helper fetching dmi from: [url]") + var/datum/http_request/request = new() + var/file_name = "tmp/custom_map_icon.dmi" + request.prepare(RUSTG_HTTP_METHOD_GET, url , "", "", file_name) + query_in_progress = TRUE + request.begin_async() + UNTIL(request.is_complete()) + var/datum/http_response/response = request.into_response() + if(response.errored || response.status_code != 200) + query_in_progress = FALSE + CRASH("Failed to fetch mapped custom icon from url [url], code: [response.status_code], error: [response.error]") + var/icon/I = new(file_name) + icon_cache[url] = I + query_in_progress = FALSE + return I diff --git a/code/modules/mapping/mapping_helpers/baseturf.dm b/code/modules/mapping/mapping_helpers/baseturf.dm index f4bd0d7c7f..4d79d3dba5 100644 --- a/code/modules/mapping/mapping_helpers/baseturf.dm +++ b/code/modules/mapping/mapping_helpers/baseturf.dm @@ -30,8 +30,8 @@ qdel(src) /obj/effect/baseturf_helper/proc/replace_baseturf(turf/thing) - var/list/baseturf_cache = thing.baseturfs - if(length(baseturf_cache)) + if(length(thing.baseturfs)) + var/list/baseturf_cache = thing.baseturfs.Copy() for(var/i in baseturf_cache) if(baseturf_to_replace[i]) baseturf_cache -= i @@ -44,6 +44,8 @@ else thing.PlaceOnBottom(null, baseturf) + + /obj/effect/baseturf_helper/space name = "space baseturf editor" baseturf = /turf/open/space @@ -79,4 +81,3 @@ /obj/effect/baseturf_helper/lava_land/surface name = "lavaland baseturf editor" baseturf = /turf/open/lava/smooth/lava_land_surface - diff --git a/code/modules/mining/equipment/explorer_gear.dm b/code/modules/mining/equipment/explorer_gear.dm index cda38033c2..dc52fb7f20 100644 --- a/code/modules/mining/equipment/explorer_gear.dm +++ b/code/modules/mining/equipment/explorer_gear.dm @@ -2,8 +2,9 @@ /obj/item/clothing/suit/hooded/explorer name = "explorer suit" desc = "An armoured suit for exploring harsh environments." - icon_state = "explorer" - item_state = "explorer" + icon_state = "explorer-normal" + item_state = "explorer-normal" + var/suit_type = "normal" body_parts_covered = CHEST|GROIN|LEGS|ARMS cold_protection = CHEST|GROIN|LEGS|ARMS min_cold_protection_temperature = FIRE_SUIT_MIN_TEMP_PROTECT @@ -13,11 +14,15 @@ allowed = list(/obj/item/flashlight, /obj/item/tank/internals, /obj/item/resonator, /obj/item/mining_scanner, /obj/item/t_scanner/adv_mining_scanner, /obj/item/gun/energy/kinetic_accelerator, /obj/item/pickaxe) resistance_flags = FIRE_PROOF mutantrace_variation = STYLE_DIGITIGRADE|STYLE_SNEK_TAURIC|STYLE_PAW_TAURIC + no_t = TRUE /obj/item/clothing/head/hooded/explorer name = "explorer hood" desc = "An armoured hood for exploring harsh environments." - icon_state = "explorer" + icon_state = "explorer-normal" + item_state = "explorer-normal" + var/suit_type = "normal" + var/basestate = "normal" body_parts_covered = HEAD flags_inv = HIDEHAIR|HIDEFACE|HIDEEARS min_cold_protection_temperature = FIRE_HELM_MIN_TEMP_PROTECT @@ -33,10 +38,40 @@ /obj/item/clothing/suit/hooded/explorer/standard/Initialize() . = ..() AddComponent(/datum/component/armor_plate) + RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon) + +/obj/item/clothing/suit/hooded/explorer/standard/proc/upgrade_icon(datum/source, amount, maxamount) + SIGNAL_HANDLER + + if(amount) + name = "reinforced [initial(name)]" + suit_type = "normal_goliath" + if(amount == maxamount) + suit_type = "normal_goliath_full" + icon_state = "explorer-[suit_type]" + if(ishuman(loc)) + var/mob/living/carbon/human/wearer = loc + if(wearer.wear_suit == src) + wearer.update_inv_wear_suit() /obj/item/clothing/head/hooded/explorer/standard/Initialize() . = ..() AddComponent(/datum/component/armor_plate) + RegisterSignal(src, COMSIG_ARMOR_PLATED, .proc/upgrade_icon) + +/obj/item/clothing/head/hooded/explorer/standard/proc/upgrade_icon(datum/source, amount, maxamount) + SIGNAL_HANDLER + + if(amount) + name = "reinforced [initial(name)]" + suit_type = "normal_goliath" + if(amount == maxamount) + suit_type = "normal_goliath_full" + icon_state = "explorer-[suit_type]" + if(ishuman(loc)) + var/mob/living/carbon/human/wearer = loc + if(wearer.head == src) + wearer.update_inv_head() /obj/item/clothing/mask/gas/explorer name = "explorer gas mask" diff --git a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm index 88dda923a0..e002fa369d 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/hair_head.dm @@ -1058,3 +1058,58 @@ /datum/sprite_accessory/hair/zone name = "Zone" icon_state = "hair_zone" + +/datum/sprite_accessory/hair_gradient + icon = 'icons/mob/hair_gradients.dmi' + +/datum/sprite_accessory/hair_gradient/none + name = "None" + icon_state = "none" + +/datum/sprite_accessory/hair_gradient/fadeup + name = "Fade Up" + icon_state = "fadeup" + +/datum/sprite_accessory/hair_gradient/fadedown + name = "Fade Down" + icon_state = "fadedown" + +/datum/sprite_accessory/hair_gradient/vertical_split + name = "Vertical Split" + icon_state = "vsplit" + +/datum/sprite_accessory/hair_gradient/_split + name = "Horizontal Split" + icon_state = "bottomflat" + +/datum/sprite_accessory/hair_gradient/reflected + name = "Reflected" + icon_state = "reflected_high" + +/datum/sprite_accessory/hair_gradient/reflected_inverse + name = "Reflected Inverse" + icon_state = "reflected_inverse_high" + +/datum/sprite_accessory/hair_gradient/wavy + name = "Wavy" + icon_state = "wavy" + +/datum/sprite_accessory/hair_gradient/long_fade_up + name = "Long Fade Up" + icon_state = "long_fade_up" + +/datum/sprite_accessory/hair_gradient/long_fade_down + name = "Long Fade Down" + icon_state = "long_fade_down" + +/datum/sprite_accessory/hair_gradient/short_fade_up + name = "Short Fade Up" + icon_state = "short_fade_up" + +/datum/sprite_accessory/hair_gradient/short_fade_down + name = "Short Fade Down" + icon_state = "short_fade_down" + +/datum/sprite_accessory/hair_gradient/wavy_spike + name = "Spiked Wavy" + icon_state = "wavy_spiked" diff --git a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm index c7cd699ec9..08897ade34 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/synthliz.dm @@ -38,14 +38,14 @@ icon = 'modular_citadel/icons/mob/synthliz_body_markings.dmi' name = "Synthetic Lizard - Pecs Light" icon_state = "synthlizpecslight" - covered_limbs = list("Chest" = MATRIX_GREEN_BLUE) + covered_limbs = list("Chest" = MATRIX_GREEN_BLUE, "Left Arm" = MATRIX_BLUE, "Right Arm" = MATRIX_BLUE, "Left Leg" = MATRIX_GREEN, "Right Leg" = MATRIX_GREEN) /datum/sprite_accessory/mam_body_markings/synthliz recommended_species = list("synthliz") icon = 'modular_citadel/icons/mob/synthliz_body_markings.dmi' name = "Synthetic Lizard - Plates" icon_state = "synthlizscutes" - covered_limbs = list("Chest" = MATRIX_GREEN) + covered_limbs = list("Chest" = MATRIX_GREEN, "Left Leg" = MATRIX_GREEN, "Right Leg" = MATRIX_GREEN) //Synth tails /datum/sprite_accessory/tails/mam_tails/synthliz diff --git a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm index b5ba7f0732..64a3c1a24c 100644 --- a/code/modules/mob/dead/new_player/sprite_accessories/tails.dm +++ b/code/modules/mob/dead/new_player/sprite_accessories/tails.dm @@ -542,6 +542,20 @@ icon = 'modular_citadel/icons/mob/mam_tails.dmi' matrixed_sections = MATRIX_RED_GREEN +/datum/sprite_accessory/tails/human/takahiro_kitsune + name = "Takahiro Kitsune Tails" //takahiro had five tails i just wanted to follow the 'T' naming convention vs. tamamo and triple + icon_state = "7sune" + color_src = MATRIXED + icon = 'modular_citadel/icons/mob/mam_tails.dmi' + matrixed_sections = MATRIX_RED_GREEN + +/datum/sprite_accessory/tails_animated/human/takahiro_kitsune + name = "Takahiro Kitsune Tails" + icon_state = "7sune" + color_src = MATRIXED + icon = 'modular_citadel/icons/mob/mam_tails.dmi' + matrixed_sections = MATRIX_RED_GREEN + /datum/sprite_accessory/tails/human/tentacle name = "Tentacle" icon_state = "tentacle" diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 429f00161b..1aa2863eda 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -176,7 +176,7 @@ GLOBAL_VAR_INIT(observer_default_invisibility, INVISIBILITY_OBSERVER) * Hair will always update its dir, so if your sprite has no dirs the haircut will go all over the place. * |- Ricotez */ -/mob/dead/observer/update_icon(new_form) +/mob/dead/observer/update_icon(updates=ALL, new_form=null) . = ..() if(client) //We update our preferences in case they changed right before update_icon was called. ghost_accs = client.prefs.ghost_accs diff --git a/code/modules/mob/living/carbon/human/human_defines.dm b/code/modules/mob/living/carbon/human/human_defines.dm index de934318b3..6723d2d4a6 100644 --- a/code/modules/mob/living/carbon/human/human_defines.dm +++ b/code/modules/mob/living/carbon/human/human_defines.dm @@ -20,6 +20,11 @@ var/hair_color = "000" var/hair_style = "Bald" + ///Colour used for the hair gradient. + var/grad_color = "000" + ///Style used for the hair gradient. + var/grad_style + //Facial hair colour and style var/facial_hair_color = "000" var/facial_hair_style = "Shaved" @@ -81,6 +86,8 @@ tooltips = TRUE + var/additional_language //the additional language this human can speak from their preference selection + /// Unarmed parry data for human /datum/block_parry_data/unarmed/human parry_respect_clickdelay = TRUE diff --git a/code/modules/mob/living/carbon/human/species.dm b/code/modules/mob/living/carbon/human/species.dm index 6f94be2d41..80edd142b7 100644 --- a/code/modules/mob/living/carbon/human/species.dm +++ b/code/modules/mob/living/carbon/human/species.dm @@ -52,6 +52,10 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) var/hair_color ///The alpha used by the hair. 255 is completely solid, 0 is invisible. var/hair_alpha = 255 + ///The gradient style used for the mob's hair. + var/grad_style + ///The gradient color used to color the gradient. + var/grad_color ///Does the species use skintones or not? As of now only used by humans. var/use_skintones = FALSE @@ -678,6 +682,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(!hair_hidden || dynamic_hair_suffix) var/mutable_appearance/hair_overlay = mutable_appearance(layer = -HAIR_LAYER) + var/mutable_appearance/gradient_overlay = mutable_appearance(layer = -HAIR_LAYER) if(!hair_hidden && !H.getorgan(/obj/item/organ/brain)) //Applies the debrained overlay if there is no brain if(!(NOBLOOD in species_traits)) hair_overlay.icon = 'icons/mob/human_parts.dmi' @@ -713,8 +718,21 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) hair_overlay.color = "#" + hair_color else hair_overlay.color = "#" + H.hair_color + + //Gradients + grad_style = H.grad_style + grad_color = H.grad_color + if(grad_style) + var/datum/sprite_accessory/gradient = GLOB.hair_gradients_list[grad_style] + var/icon/temp = icon(gradient.icon, gradient.icon_state) + var/icon/temp_hair = icon(hair_file, hair_state) + temp.Blend(temp_hair, ICON_ADD) + gradient_overlay.icon = temp + gradient_overlay.color = "#" + grad_color + else hair_overlay.color = forced_colour + hair_overlay.alpha = hair_alpha if(OFFSET_HAIR in H.dna.species.offset_features) @@ -723,6 +741,7 @@ GLOBAL_LIST_EMPTY(roundstart_race_names) if(hair_overlay.icon) standing += hair_overlay + standing += gradient_overlay if(standing.len) H.overlays_standing[HAIR_LAYER] = standing diff --git a/code/modules/mob/living/living_active_parry.dm b/code/modules/mob/living/living_active_parry.dm index e34c5ce053..13240b2734 100644 --- a/code/modules/mob/living/living_active_parry.dm +++ b/code/modules/mob/living/living_active_parry.dm @@ -103,13 +103,16 @@ var/_method = override[thing] if(_method == ITEM_PARRY) using_item = thing + tool = using_item method = ITEM_PARRY data = using_item.block_parry_data else if(_method == UNARMED_PARRY) method = UNARMED_PARRY + tool = src data = thing if(!using_item && !method && length(other_items)) using_item = other_items[1] + tool = using_item method = ITEM_PARRY data = using_item.block_parry_data if(!method) diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index b11737659c..559ffbbc08 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -3,12 +3,15 @@ /** * Returns the thing in our active hand (whatever is in our active module-slot, in this case) + * + * Arguments + * * get_gripper - If the active module is a gripper, should we return the gripper or the contained item? (if the gripper contains nothing, returns the gripper anyways) */ -/mob/living/silicon/robot/get_active_held_item() +/mob/living/silicon/robot/get_active_held_item(get_gripper = FALSE) var/item = module_active // snowflake handler for the gripper - if(istype(item, /obj/item/weapon/gripper)) - var/obj/item/weapon/gripper/G = item + if(istype(item, /obj/item/gripper) && !get_gripper) + var/obj/item/gripper/G = item if(G.wrapped) if(G.wrapped.loc != G) G.wrapped = null @@ -284,9 +287,14 @@ /** * Unequips the active held item, if there is one. + * + * Will always consider dropping gripper contents first. */ /mob/living/silicon/robot/proc/uneq_active() if(module_active) + var/obj/item/gripper/gripper = get_active_held_item(TRUE) + if(istype(gripper) && gripper.drop_held()) + return unequip_module_from_slot(module_active, get_selected_module()) /** @@ -302,11 +310,12 @@ * Checks if the item is currently in a slot. * * If the item is found in a slot, this returns TRUE. Otherwise, it returns FALSE + * Modified to accept items inside of grippers, used for `code\modules\tgui\states\hands.dm:27` * Arguments * * item_module - the item being checked */ /mob/living/silicon/robot/proc/activated(obj/item/item_module) - if(item_module in held_items) + if(get_active_held_item() == item_module || (item_module in held_items)) return TRUE return FALSE diff --git a/code/modules/mob/living/silicon/robot/robot_modules.dm b/code/modules/mob/living/silicon/robot/robot_modules.dm index d0c53508fa..7ecc714b56 100644 --- a/code/modules/mob/living/silicon/robot/robot_modules.dm +++ b/code/modules/mob/living/silicon/robot/robot_modules.dm @@ -328,7 +328,7 @@ /obj/item/crowbar/cyborg, /obj/item/healthanalyzer, /obj/item/reagent_containers/borghypo, - /obj/item/weapon/gripper/medical, + /obj/item/gripper/medical, /obj/item/reagent_containers/dropper, /obj/item/reagent_containers/syringe, /obj/item/surgical_drapes, @@ -448,7 +448,7 @@ /obj/item/analyzer, /obj/item/storage/part_replacer/cyborg, /obj/item/holosign_creator/combifan, - /obj/item/weapon/gripper, + /obj/item/gripper, /obj/item/lightreplacer/cyborg, /obj/item/geiger_counter/cyborg, /obj/item/assembly/signaler/cyborg, @@ -923,7 +923,7 @@ /obj/item/gun/energy/kinetic_accelerator/cyborg, /obj/item/gun/energy/plasmacutter/cyborg, /obj/item/gps/cyborg, - /obj/item/weapon/gripper/mining, + /obj/item/gripper/mining, /obj/item/cyborg_clamp, /obj/item/stack/marker_beacon, /obj/item/destTagger, @@ -1075,7 +1075,7 @@ /obj/item/multitool/cyborg, /obj/item/storage/part_replacer/cyborg, /obj/item/holosign_creator/atmos, - /obj/item/weapon/gripper, + /obj/item/gripper, /obj/item/lightreplacer/cyborg, /obj/item/stack/sheet/metal/cyborg, /obj/item/stack/sheet/glass/cyborg, diff --git a/code/modules/mob/living/simple_animal/friendly/mouse.dm b/code/modules/mob/living/simple_animal/friendly/mouse.dm index 8348af43fa..340d2e5598 100644 --- a/code/modules/mob/living/simple_animal/friendly/mouse.dm +++ b/code/modules/mob/living/simple_animal/friendly/mouse.dm @@ -98,6 +98,16 @@ qdel(bigcheese) evolve() return + for(var/obj/item/trash/garbage in range(1, src)) + if(prob(2)) + qdel(garbage) + evolve_plague() + return + for(var/obj/effect/decal/cleanable/blood/gibs/leftovers in range(1, src)) + if(prob(2)) + qdel(leftovers) + evolve_plague() + return /** *Checks the mouse cap, if it's above the cap, doesn't spawn a mouse. If below, spawns a mouse and adds it to cheeserats. @@ -123,6 +133,13 @@ mind.transfer_to(regalrat) qdel(src) +/mob/living/simple_animal/mouse/proc/evolve_plague() + var/mob/living/simple_animal/hostile/plaguerat = new /mob/living/simple_animal/hostile/plaguerat(loc) + visible_message("[src] devours the food! He rots into something worse!") + if(mind) + mind.transfer_to(plaguerat) + qdel(src) + /* * Mouse types */ @@ -169,3 +186,16 @@ GLOBAL_VAR(tom_existed) /obj/item/reagent_containers/food/snacks/deadmouse/on_grind() reagents.clear_reagents() +/mob/living/simple_animal/mouse/proc/miasma(datum/gas_mixture/environment, check_temp = FALSE) + if(isturf(src.loc) && isopenturf(src.loc)) + var/turf/open/ST = src.loc + var/miasma_moles = ST.air.get_moles(GAS_MIASMA) + if(prob(5) && miasma_moles >= 5) + evolve_plague() + else if(miasma_moles >= 20) + evolve_plague() + return + +/mob/living/simple_animal/mouse/handle_environment(datum/gas_mixture/environment) + . = ..() + miasma() diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin.dm new file mode 100644 index 0000000000..61436cea1c --- /dev/null +++ b/code/modules/mob/living/simple_animal/gremlin/gremlin.dm @@ -0,0 +1,254 @@ +#define GREMLIN_VENT_CHANCE 1.75 + +//Gremlins +//Small monsters that don't attack humans or other animals. Instead they mess with electronics, computers and machinery + +//List of objects that gremlins can't tamper with (because nobody coded an interaction for it) +//List starts out empty. Whenever a gremlin finds a machine that it couldn't tamper with, the machine's type is added here, and all machines of such type are ignored from then on (NOT SUBTYPES) +GLOBAL_LIST(bad_gremlin_items) + +/mob/living/simple_animal/hostile/gremlin + name = "gremlin" + desc = "This tiny creature finds great joy in discovering and using technology. Nothing excites it more than pushing random buttons on a computer to see what it might do." + icon = 'icons/mob/mob.dmi' + icon_state = "gremlin" + icon_living = "gremlin" + icon_dead = "gremlin_dead" + + var/in_vent = FALSE + + health = 20 + maxHealth = 20 + search_objects = 3 //Completely ignore mobs + + //Tampering is handled by the 'npc_tamper()' obj proc + wanted_objects = list( + /obj/machinery, + /obj/item/reagent_containers/food, + /obj/structure/sink + ) + + var/obj/machinery/atmospherics/components/unary/vent_pump/entry_vent + var/obj/machinery/atmospherics/components/unary/vent_pump/exit_vent + + dextrous = TRUE + possible_a_intents = list(INTENT_HELP, INTENT_GRAB, INTENT_DISARM, INTENT_HARM) + faction = list("meme", "gremlin") + speed = 0.5 + gold_core_spawnable = 2 + unique_name = TRUE + + //Ensure gremlins don't attack other mobs + melee_damage_upper = 0 + melee_damage_lower = 0 + attack_sound = null + obj_damage = 0 + environment_smash = ENVIRONMENT_SMASH_NONE + + //List of objects that we don't even want to try to tamper with + //Subtypes of these are calculated too + var/list/unwanted_objects = list(/obj/machinery/atmospherics/pipe, /turf, /obj/structure) //ensure gremlins dont try to fuck with walls / normal pipes / glass / etc + + var/min_next_vent = 0 + + //Amount of ticks spent pathing to the target. If it gets above a certain amount, assume that the target is unreachable and stop + var/time_chasing_target = 0 + + //If you're going to make gremlins slower, increase this value - otherwise gremlins will abandon their targets too early + var/max_time_chasing_target = 2 + + var/next_eat = 0 + + //Last 20 heard messages are remembered by gremlins, and will be used to generate messages for comms console tampering, etc... + var/list/hear_memory = list() + var/const/max_hear_memory = 20 + +/mob/living/simple_animal/hostile/gremlin/Initialize() + . = ..() + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + access_card = new /obj/item/card/id(src) + var/datum/job/captain/C = new /datum/job/captain + access_card.access = C.get_access() + +/mob/living/simple_animal/hostile/gremlin/AttackingTarget() + var/is_hungry = world.time >= next_eat || prob(25) + if(istype(target, /obj/item/reagent_containers/food) && is_hungry) //eat food if we're hungry or bored + visible_message("[src] hungrily devours [target]!") + playsound(src, 'sound/items/eatfood.ogg', 50, 1) + qdel(target) + LoseTarget() + next_eat = world.time + rand(700, 3000) //anywhere from 70 seconds to 5 minutes until the gremlin is hungry again + return + if(istype(target, /obj)) + var/obj/M = target + tamper(M) + if(prob(50)) //50% chance to move to the next machine + LoseTarget() + +/mob/living/simple_animal/hostile/gremlin/Hear(message, atom/movable/speaker, message_langs, raw_message, radio_freq, list/spans, message_mode) + . = ..() + if(message) + hear_memory.Insert(1, raw_message) + if(hear_memory.len > max_hear_memory) + hear_memory.Cut(hear_memory.len) + +/mob/living/simple_animal/hostile/gremlin/proc/generate_markov_input() + var/result = "" + + for(var/memory in hear_memory) + result += memory + " " + + return result + +/mob/living/simple_animal/hostile/gremlin/proc/generate_markov_chain() + return markov_chain(generate_markov_input(), rand(2,5), rand(100,700)) //The numbers are chosen arbitarily + +/mob/living/simple_animal/hostile/gremlin/proc/tamper(obj/M) + switch(M.npc_tamper_act(src)) + if(NPC_TAMPER_ACT_FORGET) + visible_message(pick( + "\The [src] plays around with \the [M], but finds it rather boring.", + "\The [src] tries to think of some more ways to screw \the [M] up, but fails miserably.", + "\The [src] decides to ignore \the [M], and starts looking for something more fun.")) + + LAZYADD(GLOB.bad_gremlin_items,M.type) + return FALSE + if(NPC_TAMPER_ACT_NOMSG) + //Don't create a visible message + return TRUE + + else + visible_message(pick( + "\The [src]'s eyes light up as \he tampers with \the [M].", + "\The [src] twists some knobs around on \the [M] and bursts into laughter!", + "\The [src] presses a few buttons on \the [M] and giggles mischievously.", + "\The [src] rubs its hands devilishly and starts messing with \the [M].", + "\The [src] turns a small valve on \the [M].")) + + //Add a clue for detectives to find. The clue is only added if no such clue already existed on that machine + return TRUE + +/mob/living/simple_animal/hostile/gremlin/CanAttack(atom/new_target) + if(LAZYFIND(GLOB.bad_gremlin_items,new_target.type)) + return FALSE + if(is_type_in_list(new_target, unwanted_objects)) + return FALSE + if(istype(new_target, /obj/machinery)) + var/obj/machinery/M = new_target + if(M.stat) //Unpowered or broken + return FALSE + else if(istype(new_target, /obj/machinery/door/firedoor)) + var/obj/machinery/door/firedoor/F = new_target + //Only tamper with firelocks that are closed, opening them! + if(!F.density) + return FALSE + + return ..() + +/mob/living/simple_animal/hostile/gremlin/death(gibbed) + walk(src,0) + QDEL_NULL(access_card) + return ..() + +/mob/living/simple_animal/hostile/gremlin/Life() + . = ..() + if(!health || stat == DEAD) + return + //Don't try to path to one target for too long. If it takes longer than a certain amount of time, assume it can't be reached and find a new one + if(!client) //don't do this shit if there's a client, they're capable of ventcrawling manually + if(in_vent) + target = null + if(entry_vent && get_dist(src, entry_vent) <= 1) + var/list/vents = list() + var/datum/pipeline/entry_vent_parent = entry_vent.parents[1] + for(var/obj/machinery/atmospherics/components/unary/vent_pump/temp_vent in entry_vent_parent.other_atmosmch) + vents += temp_vent + if(!vents.len) + entry_vent = null + in_vent = FALSE + return + exit_vent = pick(vents) + visible_message("[src] crawls into the ventilation ducts!") + + loc = exit_vent + var/travel_time = round(get_dist(loc, exit_vent.loc) / 2) + addtimer(CALLBACK(src, .proc/exit_vents), travel_time) //come out at exit vent in 2 to 20 seconds + + + if(world.time > min_next_vent && !entry_vent && !in_vent && prob(GREMLIN_VENT_CHANCE)) //small chance to go into a vent + for(var/obj/machinery/atmospherics/components/unary/vent_pump/v in view(7,src)) + if(!v.welded) + entry_vent = v + in_vent = TRUE + walk_to(src, entry_vent) + break + if(!target) + time_chasing_target = 0 + else + if(++time_chasing_target > max_time_chasing_target) + LoseTarget() + time_chasing_target = 0 + . = ..() + +/mob/living/simple_animal/hostile/gremlin/EscapeConfinement() + if(istype(loc, /obj) && CanAttack(loc)) //If we're inside a machine, screw with it + var/obj/M = loc + tamper(M) + + return ..() + +/mob/living/simple_animal/hostile/gremlin/proc/exit_vents() + if(!exit_vent || exit_vent.welded) + loc = entry_vent + entry_vent = null + return + loc = exit_vent.loc + entry_vent = null + exit_vent = null + in_vent = FALSE + var/area/new_area = get_area(loc) + message_admins("[src] came out at [new_area][ADMIN_JMP(loc)]!") + if(new_area) + new_area.Entered(src) + visible_message("[src] climbs out of the ventilation ducts!") + min_next_vent = world.time + 900 //90 seconds between ventcrawls + +//This allows player-controlled gremlins to tamper with machinery +/mob/living/simple_animal/hostile/gremlin/UnarmedAttack(var/atom/A) + if(istype(A, /obj/machinery) || istype(A, /obj/structure)) + tamper(A) + if(istype(target, /obj/item/reagent_containers/food)) //eat food + visible_message("[src] hungrily devours [target]!", "You hungrily devour [target]!") + playsound(src, 'sound/items/eatfood.ogg', 50, 1) + qdel(target) + LoseTarget() + next_eat = world.time + rand(700, 3000) //anywhere from 70 seconds to 5 minutes until the gremlin is hungry again + + return ..() + +/mob/living/simple_animal/hostile/gremlin/IsAdvancedToolUser() + return 1 + +/mob/living/simple_animal/hostile/gremlin/proc/divide() + //Health is halved and then reduced by 2. A new gremlin is spawned with the same health as the parent + //Need to have at least 6 health for this, otherwise resulting health would be less than 1 + if(health < 7.5) + return + + visible_message("\The [src] splits into two!") + var/mob/living/simple_animal/hostile/gremlin/G = new /mob/living/simple_animal/hostile/gremlin(get_turf(src)) + + if(mind) + mind.transfer_to(G) + + health = round(health * 0.5) - 2 + maxHealth = health + resize *= 0.9 + + G.health = health + G.maxHealth = maxHealth + +/mob/living/simple_animal/hostile/gremlin/traitor + health = 85 + maxHealth = 85 + gold_core_spawnable = 0 diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm new file mode 100644 index 0000000000..ff0eb13dc7 --- /dev/null +++ b/code/modules/mob/living/simple_animal/gremlin/gremlin_act.dm @@ -0,0 +1,214 @@ +/obj/proc/npc_tamper_act(mob/living/L) + return NPC_TAMPER_ACT_FORGET + +/obj/machinery/atmospherics/components/binary/passive_gate/npc_tamper_act(mob/living/L) + if(prob(50)) //Turn on/off + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS) + else //Change pressure + target_pressure = rand(0, MAX_OUTPUT_PRESSURE) + investigate_log("was set to [target_pressure] kPa by [key_name(L)]", INVESTIGATE_ATMOS) + update_icon() + +/obj/machinery/atmospherics/components/binary/pump/npc_tamper_act(mob/living/L) + if(prob(50)) //Turn on/off + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS) + else //Change pressure + target_pressure = rand(0, MAX_OUTPUT_PRESSURE) + investigate_log("was set to [target_pressure] kPa by [key_name(L)]", INVESTIGATE_ATMOS) + update_icon() + +/obj/machinery/atmospherics/components/binary/volume_pump/npc_tamper_act(mob/living/L) + if(prob(50)) //Turn on/off + on = !on + investigate_log("was turned [on ? "on" : "off"] by [key_name(L)]", INVESTIGATE_ATMOS) + else //Change pressure + transfer_rate = rand(0, MAX_TRANSFER_RATE) + investigate_log("was set to [transfer_rate] L/s by [key_name(L)]", INVESTIGATE_ATMOS) + update_icon() + +/obj/machinery/atmospherics/components/binary/valve/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/space_heater/npc_tamper_act(mob/living/L) + var/list/choose_modes = list("standby", "heat", "cool") + if(prob(50)) + choose_modes -= mode + mode = pick(choose_modes) + else + on = !on + update_icon() + +/obj/machinery/shield_gen/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/firealarm/npc_tamper_act(mob/living/L) + alarm() + +/obj/machinery/airalarm/npc_tamper_act(mob/living/L) + if(panel_open) + wires.npc_tamper(L) + else + panel_open = !panel_open + +/obj/machinery/ignition_switch/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/flasher_button/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/crema_switch/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/camera/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/atmospherics/components/unary/cryo_cell/npc_tamper_act(mob/living/L) + if(prob(50)) + if(beaker) + beaker.forceMove(loc) + beaker = null + else + if(occupant) + if(state_open) + if (close_machine() == usr) + on = TRUE + else + open_machine() + +/obj/machinery/door_control/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/door/airlock/npc_tamper_act(mob/living/L) + //Open the firelocks as well, otherwise they block the way for our gremlin which isn't fun + for(var/obj/machinery/door/firedoor/F in get_turf(src)) + if(F.density) + F.npc_tamper_act(L) + + if(prob(40)) //40% - mess with wires + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + else //60% - just open it + open() + +/obj/machinery/gibber/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/light_switch/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/turretid/npc_tamper_act(mob/living/L) + enabled = rand(0, 1) + lethal = rand(0, 1) + updateTurrets() + +/obj/machinery/vending/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/shower/npc_tamper_act(mob/living/L) + attack_hand(L) + + +/obj/machinery/deepfryer/npc_tamper_act(mob/living/L) + //Deepfry a random nearby item + var/list/pickable_items = list() + + for(var/obj/item/I in range(1, L)) + pickable_items.Add(I) + + if(!pickable_items.len) + return + + var/obj/item/I = pick(pickable_items) + + attackby(I, L) //shove the item in, even if it can't be deepfried normally + +/obj/machinery/power/apc/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/power/rad_collector/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/power/emitter/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/particle_accelerator/control_box/npc_tamper_act(mob/living/L) + if(!panel_open) + panel_open = !panel_open + if(wires) + wires.npc_tamper(L) + +/obj/machinery/computer/communications/npc_tamper_act(mob/living/user) + if(!authenticated) + if(prob(20)) //20% chance to log in + authenticated = TRUE + + else //Already logged in + if(prob(50)) //50% chance to log off + authenticated = FALSE + else if(istype(user, /mob/living/simple_animal/hostile/gremlin)) //make a hilarious public message + var/mob/living/simple_animal/hostile/gremlin/G = user + var/result = G.generate_markov_chain() + + if(result) + if(prob(85)) + SScommunications.make_announcement(G, FALSE, result) + var/turf/T = get_turf(G) + log_say("[key_name(usr)] ([ADMIN_JMP(T)]) has made a captain announcement: [result]") + message_admins("[key_name_admin(G)] has made a captain announcement.", 1) + else + if(SSshuttle.emergency.mode == SHUTTLE_IDLE) + SSshuttle.requestEvac(G, result) + else if(SSshuttle.emergency.mode == SHUTTLE_ESCAPE) + SSshuttle.cancelEvac(G) + +/obj/machinery/button/door/npc_tamper_act(mob/living/L) + attack_hand(L) + +/obj/machinery/sleeper/npc_tamper_act(mob/living/L) + if(prob(75)) + inject_chem(pick(available_chems)) + else + if(state_open) + close_machine() + else + open_machine() + +/obj/machinery/power/smes/npc_tamper_act(mob/living/L) + if(prob(50)) //mess with input + input_level = rand(0, input_level_max) + else //mess with output + output_level = rand(0, output_level_max) + +/obj/machinery/syndicatebomb/npc_tamper_act(mob/living/L) //suicide bomber gremlins + if(!open_panel) + open_panel = !open_panel + if(wires) + wires.npc_tamper(L) + +/obj/machinery/computer/bank_machine/npc_tamper_act(mob/living/L) + siphoning = !siphoning + +/obj/machinery/computer/slot_machine/npc_tamper_act(mob/living/L) + spin(L) + +/obj/structure/sink/npc_tamper_act(mob/living/L) + if(istype(L, /mob/living/simple_animal/hostile/gremlin)) + visible_message("\The [L] climbs into \the [src] and turns the faucet on!") + + var/mob/living/simple_animal/hostile/gremlin/G = L + G.divide() + + return NPC_TAMPER_ACT_NOMSG diff --git a/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm b/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm new file mode 100644 index 0000000000..6f5f0e3dba --- /dev/null +++ b/code/modules/mob/living/simple_animal/gremlin/gremlin_event.dm @@ -0,0 +1,44 @@ +/datum/round_event_control/gremlin + name = "Spawn Gremlins" + typepath = /datum/round_event/gremlin + weight = 15 + max_occurrences = 2 + earliest_start = 20 MINUTES + min_players = 5 + + + +/datum/round_event/gremlin + var/static/list/acceptable_spawns = list("xeno_spawn", "generic event spawn", "blobstart", "Assistant") + +/datum/round_event/gremlin/announce() + priority_announce("Bioscans indicate that some gremlins entered through the vents. Deal with them!", "Gremlin Alert", 'sound/announcer/classic/attention.ogg') + +/datum/round_event/gremlin/start() + + var/list/spawn_locs = list() + + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(isturf(L.loc) && !isspaceturf(L.loc)) + if(L.name in acceptable_spawns) + spawn_locs += L.loc + if(!spawn_locs.len) //If we can't find any gremlin spawns, try the xeno spawns + for(var/obj/effect/landmark/L in GLOB.landmarks_list) + if(isturf(L.loc)) + switch(L.name) + if("Assistant") + spawn_locs += L.loc + if(!spawn_locs.len) //If we can't find THAT, then just give up and cry + return MAP_ERROR + + var/gremlins_to_spawn = rand(2,5) + var/list/gremlin_areas = list() + for(var/i = 0, i <= gremlins_to_spawn, i++) + var/spawnat = pick(spawn_locs) + spawn_locs -= spawnat + gremlin_areas += get_area(spawnat) + new /mob/living/simple_animal/hostile/gremlin(spawnat) + var/grems = gremlin_areas.Join(", ") + message_admins("Gremlins have been spawned at the areas: [grems]") + log_game("Gremlins have been spawned at the areas: [grems]") + return SUCCESSFUL_SPAWN diff --git a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm index 5751512b19..7ca2f6c561 100644 --- a/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm +++ b/code/modules/mob/living/simple_animal/hostile/megafauna/colossus.dm @@ -528,21 +528,7 @@ Difficulty: Very Hard /obj/machinery/anomalous_crystal/emitter/ActivationReaction(mob/user, method) if(..()) var/obj/item/projectile/P = new generated_projectile(get_turf(src)) - P.setDir(dir) - switch(dir) - if(NORTH) - P.yo = 20 - P.xo = 0 - if(EAST) - P.yo = 0 - P.xo = 20 - if(WEST) - P.yo = 0 - P.xo = -20 - else - P.yo = -20 - P.xo = 0 - P.fire() + P.fire(angle2dir(dir)) /obj/machinery/anomalous_crystal/dark_reprise //Revives anyone nearby, but turns them into shadowpeople and renders them uncloneable, so the crystal is your only hope of getting up again if you go down. observer_desc = "When activated, this crystal revives anyone nearby, but turns them into Shadowpeople and makes them unclonable, making the crystal their only hope of getting up again." diff --git a/code/modules/mob/living/simple_animal/hostile/plaguerat.dm b/code/modules/mob/living/simple_animal/hostile/plaguerat.dm new file mode 100644 index 0000000000..331dc1eb75 --- /dev/null +++ b/code/modules/mob/living/simple_animal/hostile/plaguerat.dm @@ -0,0 +1,131 @@ +/mob/living/simple_animal/hostile/plaguerat + name = "plague rat" + desc = "A large decaying rat. It spreads its filth and emits a putrid odor to create more of its kind." + icon_state = "plaguerat" + icon_living = "plaguerat" + icon_dead = "plaguerat_dead" + speak = list("Skree!","SKREEE!","Squeak?") + speak_emote = list("squeaks") + emote_hear = list("Hisses.") + emote_see = list("runs in a circle.", "stands on its hind legs.") + gender = NEUTER + speak_chance = 1 + turns_per_move = 5 + maxHealth = 15 + health = 15 + see_in_dark = 6 + obj_damage = 10 + butcher_results = list(/obj/item/reagent_containers/food/snacks/meat/slab = 1) + response_help_continuous = "glares at" + response_help_simple = "glare at" + response_disarm_continuous = "skoffs at" + response_disarm_simple = "skoff at" + response_harm_continuous = "slashes" + response_harm_simple = "slash" + melee_damage_lower = 5 + melee_damage_upper = 7 + attack_verb_continuous = "slashes" + attack_verb_simple = "slash" + attack_sound = 'sound/weapons/punch1.ogg' + faction = list("rat") + density = FALSE + pass_flags = PASSTABLE | PASSGRILLE | PASSMOB + mob_size = MOB_SIZE_TINY + mob_biotypes = MOB_ORGANIC|MOB_BEAST + var/datum/action/cooldown/scavenge + var/last_spawn_time = 0 + ///Number assigned to rats and mice, checked when determining infighting. + +/mob/living/simple_animal/hostile/plaguerat/Initialize() + . = ..() + SSmobs.cheeserats += src + AddComponent(/datum/component/swarming) + AddElement(/datum/element/ventcrawling, given_tier = VENTCRAWLER_ALWAYS) + scavenge = new /datum/action/cooldown/scavenge + scavenge.Grant(src) + +/mob/living/simple_animal/hostile/plaguerat/Destroy() + SSmobs.cheeserats -= src + return ..() + +/mob/living/simple_animal/hostile/plaguerat/BiologicalLife(seconds, times_fired) + if(!(. = ..())) + return + if(isopenturf(loc)) + var/turf/open/T = src.loc + if(T.air) + T.atmos_spawn_air("miasma=5;TEMP=293.15") + if(prob(40)) + scavenge.Trigger() + if(prob(50)) + var/turf/open/floor/F = get_turf(src) + if(istype(F) && !F.intact) + var/obj/structure/cable/C = locate() in F + if(C && C.avail()) + visible_message("[src] chews through the [C]. It looks unharmed!") + playsound(src, 'sound/effects/sparks2.ogg', 100, TRUE) + C.deconstruct() + for(var/obj/O in range(1,src)) + if(istype(O, /obj/item/trash) || istype(O, /obj/effect/decal/cleanable/blood/gibs)) + qdel(O) + be_fruitful() + +/mob/living/simple_animal/hostile/plaguerat/CanAttack(atom/the_target) + if(istype(the_target,/mob/living/simple_animal)) + var/mob/living/A = the_target + if(istype(the_target, /mob/living/simple_animal/hostile/plaguerat) && A.stat == CONSCIOUS) + var/mob/living/simple_animal/hostile/plaguerat/R = the_target + if(R.faction_check_mob(src, TRUE)) + return FALSE + else + return TRUE + return ..() + +/** + *Checks the mouse cap, if it's above the cap, doesn't spawn a mouse. If below, spawns a mouse and adds it to cheeserats. + */ + +/mob/living/simple_animal/hostile/plaguerat/proc/be_fruitful() + var/cap = CONFIG_GET(number/ratcap) + if(LAZYLEN(SSmobs.cheeserats) >= cap) + visible_message("[src] gnaws into its food, [cap] rats are now on the station!") + return + var/mob/living/newmouse = new /mob/living/simple_animal/hostile/plaguerat(loc) + SSmobs.cheeserats += newmouse + visible_message("[src] gnaws into its food, attracting another rat!") + +/** + *Creates a chance to spawn more trash or gibs to repopulate. Otherwise, spawns a corpse or dirt. + */ + +/datum/action/cooldown/scavenge + name = "Scavenge" + desc = "Spread the plague, scavenge for trash and fresh meat to reproduce." + icon_icon = 'icons/mob/actions/actions_animal.dmi' + background_icon_state = "bg_clock" + button_icon_state = "coffer" + cooldown_time = 50 + +/datum/action/cooldown/scavenge/Trigger() + . = ..() + if(!.) + return + var/turf/T = get_turf(owner) + var/loot = rand(1,100) + switch(loot) + if(1 to 3) + var/pickedtrash = pick(GLOB.ratking_trash) + to_chat(owner, "Excellent, you find more trash to spread your filth!") + new /obj/effect/decal/cleanable/dirt(T) + new pickedtrash(T) + if(4 to 6) + to_chat(owner, "You find blood and gibs to feed your young!") + new /obj/effect/decal/cleanable/blood/gibs(T) + new /obj/effect/decal/cleanable/blood/(T) + if(7 to 18) + to_chat(owner, "A corpse rises from the ground. Best to leave it alone.") + new /obj/effect/mob_spawn/human/corpse/assistant(T) + if(19 to 100) + to_chat(owner, "Drat. Nothing.") + new /obj/effect/decal/cleanable/dirt(T) + StartCooldown() diff --git a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm index 5722cfda07..39e9e84ed2 100644 --- a/code/modules/mob/living/simple_animal/hostile/space_dragon.dm +++ b/code/modules/mob/living/simple_animal/hostile/space_dragon.dm @@ -109,7 +109,7 @@ continue playsound(src, 'sound/effects/splat.ogg', 50, TRUE) visible_message("[src] vomits up [consumed_mob]!") - consumed_mob.forceMove(loc) + consumed_mob.forceMove(get_turf(src)) consumed_mob.Paralyze(50) if((rifts_charged == 3 || (SSshuttle.emergency.mode == SHUTTLE_DOCKED && rifts_charged > 0)) && !objective_complete) victory() @@ -123,6 +123,7 @@ to_chat(src, "You've failed to summon the rift in a timely manner! You're being pulled back from whence you came!") destroy_rifts() playsound(src, 'sound/magic/demon_dies.ogg', 100, TRUE) + empty_contents() QDEL_NULL(src) /mob/living/simple_animal/hostile/space_dragon/AttackingTarget() @@ -351,7 +352,7 @@ */ /mob/living/simple_animal/hostile/space_dragon/proc/empty_contents() for(var/atom/movable/AM in src) - AM.forceMove(loc) + AM.forceMove(get_turf(src)) if(prob(90)) step(AM, pick(GLOB.alldirs)) @@ -529,7 +530,7 @@ /obj/structure/carp_rift name = "carp rift" desc = "A rift akin to the ones space carp use to travel long distances." - armor = list(MELEE = 0, BULLET = 0, LASER = 0, ENERGY = 100, BOMB = 50, BIO = 100, RAD = 100, FIRE = 100, ACID = 100) + armor = list("melee" = 0, "bullet" = 0, "laser" = 0, "energy" = 100, "bomb" = 50, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) max_integrity = 300 icon = 'icons/obj/carp_rift.dmi' icon_state = "carp_rift_carpspawn" @@ -636,7 +637,7 @@ icon_state = "carp_rift_charged" light_color = LIGHT_COLOR_YELLOW update_light() - armor = list(MELEE = 100, BULLET = 100, LASER = 100, ENERGY = 100, BOMB = 100, BIO = 100, RAD = 100, FIRE = 100, ACID = 100) + armor = list("melee" = 100, "bullet" = 100, "laser" = 100, "energy" = 100, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) resistance_flags = INDESTRUCTIBLE dragon.rifts_charged += 1 if(dragon.rifts_charged != 3 && !dragon.objective_complete) diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index efb4512bca..5ba4769f99 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -238,6 +238,7 @@ if(istype(W)) if(equip_to_slot_if_possible(W, slot, FALSE, FALSE, FALSE, FALSE, TRUE)) + W.apply_outline() return TRUE if(!W) diff --git a/code/modules/movespeed/_movespeed_modifier.dm b/code/modules/movespeed/_movespeed_modifier.dm index 5c4bf2ed64..74eedc16c9 100644 --- a/code/modules/movespeed/_movespeed_modifier.dm +++ b/code/modules/movespeed/_movespeed_modifier.dm @@ -32,7 +32,7 @@ Key procs /// Unique ID. You can never have different modifications with the same ID. By default, this SHOULD NOT be set. Only set it for cases where you're dynamically making modifiers/need to have two types overwrite each other. If unset, uses path (converted to text) as ID. var/id - /// Higher ones override lower priorities. This is NOT used for ID, ID must be unique, if it isn't unique the newer one overwrites automatically if overriding. + /// Determines order. Lower priorities are applied first. var/priority = 0 var/flags = NONE @@ -66,8 +66,10 @@ Key procs if(!complex_calculation || (multiplicative_slowdown > 0)) // we aren't limiting how much things can slowdown.. yet. return existing + multiplicative_slowdown var/current_tiles = 10 / max(existing, world.tick_lag) - var/minimum_speed = 10 / min(current_tiles + max_tiles_per_second_boost, max(current_tiles, absolute_max_tiles_per_second)) - return max(minimum_speed, existing + multiplicative_slowdown) + // multiplicative_slowdown is negative due to our first check + var/max_buff_to = max(existing + multiplicative_slowdown, 10 / absolute_max_tiles_per_second, 10 / (current_tiles + max_tiles_per_second_boost)) + // never slow the user + return min(existing, max_buff_to) GLOBAL_LIST_EMPTY(movespeed_modification_cache) diff --git a/code/modules/movespeed/modifiers/reagents.dm b/code/modules/movespeed/modifiers/reagents.dm index ca0a74d749..96b7be3a69 100644 --- a/code/modules/movespeed/modifiers/reagents.dm +++ b/code/modules/movespeed/modifiers/reagents.dm @@ -7,7 +7,7 @@ /datum/movespeed_modifier/reagent/ephedrine // strong painkiller effect that caps out at slightly above runspeed multiplicative_slowdown = -1.5 - priority = -100 + priority = 500 complex_calculation = TRUE absolute_max_tiles_per_second = 7 @@ -21,14 +21,14 @@ // extremely strong painkiller effect: allows user to run at old sprint speeds but not over by cancelling out slowdowns. // however, will not make user go faster than that multiplicative_slowdown = -4 - priority = -100 + priority = 500 complex_calculation = TRUE absolute_max_tiles_per_second = 8 /datum/movespeed_modifier/reagent/methamphetamine // very strong painkiller effect that caps out at slightly above runspeed multiplicative_slowdown = -2.5 - priority = -100 + priority = 500 complex_calculation = TRUE absolute_max_tiles_per_second = 7.5 diff --git a/code/modules/movespeed/modifiers/status_effects.dm b/code/modules/movespeed/modifiers/status_effects.dm index 260ee17c21..ab1b12e5dc 100644 --- a/code/modules/movespeed/modifiers/status_effects.dm +++ b/code/modules/movespeed/modifiers/status_effects.dm @@ -19,14 +19,14 @@ /datum/movespeed_modifier/status_effect/tased multiplicative_slowdown = 1.5 - priority = 50 + priority = 1500 /datum/movespeed_modifier/status_effect/domain multiplicative_slowdown = 3 /datum/movespeed_modifier/status_effect/tased/no_combat_mode multiplicative_slowdown = 8 - priority = 100 + priority = 1500 /datum/movespeed_modifier/status_effect/electrostaff multiplicative_slowdown = 1 @@ -55,7 +55,7 @@ /datum/movespeed_modifier/status_effect/slime/light_pink // decently good painkiller + speedup effect blacklisted_movetypes = FLYING | FLOATING - priority = -150 // someday we really need to make these defines lmao + priority = 500 // someday we really need to make these defines lmao multiplicative_slowdown = -2 complex_calculation = TRUE absolute_max_tiles_per_second = 7 diff --git a/code/modules/plumbing/plumbers/autohydro.dm b/code/modules/plumbing/plumbers/autohydro.dm index 9c358d8d6e..4e1dd88406 100644 --- a/code/modules/plumbing/plumbers/autohydro.dm +++ b/code/modules/plumbing/plumbers/autohydro.dm @@ -5,12 +5,6 @@ obj_flags = CAN_BE_HIT | UNIQUE_RENAME circuit = /obj/item/circuitboard/machine/hydroponics/automagic - -/obj/machinery/hydroponics/constructable/automagic/attackby(obj/item/O, mob/user, params) - if(istype(O, /obj/item/reagent_containers)) - return FALSE //avoid fucky wuckies - ..() - /obj/machinery/hydroponics/constructable/automagic/default_unfasten_wrench(mob/user, obj/item/I, time = 20) . = ..() if(. == SUCCESSFUL_UNFASTEN) diff --git a/code/modules/power/supermatter/supermatter.dm b/code/modules/power/supermatter/supermatter.dm index b460cfea51..148535af76 100644 --- a/code/modules/power/supermatter/supermatter.dm +++ b/code/modules/power/supermatter/supermatter.dm @@ -39,7 +39,8 @@ #define DAMAGE_INCREASE_MULTIPLIER 0.25 -#define THERMAL_RELEASE_MODIFIER 5 //Higher == less heat released during reaction, not to be confused with the above values +#define THERMAL_RELEASE_MODIFIER 350 //Higher == more heat released during reaction, not to be confused with the above values +#define THERMAL_RELEASE_CAP_MODIFIER 250 //Higher == lower cap on how much heat can be released per tick--currently 1.3x old value #define PLASMA_RELEASE_MODIFIER 750 //Higher == less plasma released by reaction #define OXYGEN_RELEASE_MODIFIER 325 //Higher == less oxygen released at high temperature/power @@ -500,7 +501,8 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) powerloss_dynamic_scaling = clamp(powerloss_dynamic_scaling + clamp(powerloss_inhibition_gas - powerloss_dynamic_scaling, -0.02, 0.02), 0, 1) else powerloss_dynamic_scaling = clamp(powerloss_dynamic_scaling - 0.05, 0, 1) - //Ranges from 0 to 1(1-(value between 0 and 1 * ranges from 1 to 1.5(mol / 500))) + //Ranges from 0 to 1 (1-(value between 0 and 1 * ranges from 1 to 1.5(mol / 500))) + //0 means full inhibition, 1 means no inhibition //We take the mol count, and scale it to be our inhibitor powerloss_inhibitor = clamp(1-(powerloss_dynamic_scaling * clamp(combined_gas/POWERLOSS_INHIBITION_MOLE_BOOST_THRESHOLD, 1, 1.5)), 0, 1) @@ -536,15 +538,19 @@ GLOBAL_DATUM(main_supermatter_engine, /obj/machinery/power/supermatter_crystal) //Power * 0.55 * a value between 1 and 0.8 var/device_energy = power * REACTION_POWER_MODIFIER - removed.set_temperature(removed.return_temperature() + ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_MODIFIER)) - //We don't want our output to be too hot - removed.set_temperature(max(0, min(removed.return_temperature(), 2500 * dynamic_heat_modifier))) + var/effective_temperature = min(removed.return_temperature(), 2500 * dynamic_heat_modifier) + var/max_temp_increase = effective_temperature + ((device_energy * dynamic_heat_modifier) / THERMAL_RELEASE_CAP_MODIFIER) //Calculate how much gas to release //Varies based on power and gas content removed.adjust_moles(GAS_PLASMA, max((device_energy * dynamic_heat_modifier) / PLASMA_RELEASE_MODIFIER, 0)) //Varies based on power, gas content, and heat - removed.adjust_moles(GAS_O2, max(((device_energy + removed.return_temperature() * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0)) + removed.adjust_moles(GAS_O2, max(((device_energy + effective_temperature * dynamic_heat_modifier) - T0C) / OXYGEN_RELEASE_MODIFIER, 0)) + + if(removed.return_temperature() < max_temp_increase) + removed.adjust_heat(device_energy * dynamic_heat_modifier * THERMAL_RELEASE_MODIFIER) + removed.set_temperature(min(removed.return_temperature(), max_temp_increase)) + if(produces_gas) env.merge(removed) diff --git a/code/modules/projectiles/guns/energy.dm b/code/modules/projectiles/guns/energy.dm index 0f5934e8fd..34e9247184 100644 --- a/code/modules/projectiles/guns/energy.dm +++ b/code/modules/projectiles/guns/energy.dm @@ -44,7 +44,7 @@ cell.use(round(cell.charge * severity/100)) chambered = null //we empty the chamber recharge_newshot() //and try to charge a new shot - update_icon() + update_appearance() /obj/item/gun/energy/get_cell() return cell @@ -61,7 +61,7 @@ recharge_newshot(TRUE) if(selfcharge) START_PROCESSING(SSobj, src) - update_icon() + update_appearance() /obj/item/gun/energy/ComponentInitialize() . = ..() @@ -74,7 +74,7 @@ /obj/item/gun/energy/handle_atom_del(atom/A) if(A == cell) cell = null - update_icon() + update_appearance() return ..() /obj/item/gun/energy/examine(mob/user) @@ -100,7 +100,7 @@ cell.give(100) if(!chambered) //if empty chamber we try to charge a new shot recharge_newshot(TRUE) - update_icon() + update_appearance() // ATTACK SELF IGNORING PARENT RETURN VALUE /obj/item/gun/energy/attack_self(mob/living/user) @@ -174,7 +174,7 @@ if(user_for_feedback) to_chat(user_for_feedback, "[src] is now set to [C.select_name || C].") post_set_firemode() - update_icon(TRUE) + update_appearance() /obj/item/gun/energy/proc/post_set_firemode(recharge_newshot = TRUE) if(recharge_newshot) diff --git a/code/modules/reagents/reagent_containers/hypospray.dm b/code/modules/reagents/reagent_containers/hypospray.dm index 143025aed1..9d2e4a8e31 100644 --- a/code/modules/reagents/reagent_containers/hypospray.dm +++ b/code/modules/reagents/reagent_containers/hypospray.dm @@ -58,6 +58,7 @@ name = "combat stimulant injector" desc = "A modified air-needle autoinjector, used by support operatives to quickly heal injuries in combat and get people back in the fight." amount_per_transfer_from_this = 10 + item_state = "combat_hypo" icon_state = "combat_hypo" volume = 100 ignore_flags = 1 // So they can heal their comrades. @@ -69,17 +70,27 @@ list_reagents = list(/datum/reagent/medicine/epinephrine = 30, /datum/reagent/medicine/omnizine = 30, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/atropine = 15) /obj/item/reagent_containers/hypospray/combat/nanites - desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with experimental medical compounds for rapid healing." + name = "experimental combat stimulant injector" + desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with experimental medical nanites and a stimulant for rapid healing and a combat boost." + item_state = "nanite_hypo" + icon_state = "nanite_hypo" volume = 100 list_reagents = list(/datum/reagent/medicine/adminordrazine/quantum_heal = 80, /datum/reagent/medicine/synaptizine = 20) -/obj/item/reagent_containers/hypospray/magillitis - name = "experimental autoinjector" - desc = "A modified air-needle autoinjector with a small single-use reservoir. It contains an experimental serum." - icon_state = "combat_hypo" - volume = 5 - reagent_flags = NONE - list_reagents = list(/datum/reagent/magillitis = 5) +/obj/item/reagent_containers/hypospray/combat/nanites/update_icon() + if(reagents.total_volume > 0) + icon_state = initial(icon_state) + else + icon_state = "[initial(icon_state)]0" + +/obj/item/reagent_containers/hypospray/combat/heresypurge + name = "holy water piercing injector" + desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with 5 doses of a holy water and pacifier mixture. Not for use on your teammates." + item_state = "holy_hypo" + icon_state = "holy_hypo" + volume = 250 + list_reagents = list(/datum/reagent/water/holywater = 150, /datum/reagent/peaceborg_tire = 50, /datum/reagent/peaceborg_confuse = 50) + amount_per_transfer_from_this = 50 //MediPens @@ -136,6 +147,8 @@ /obj/item/reagent_containers/hypospray/medipen/ekit name = "emergency first-aid autoinjector" desc = "An epinephrine medipen with extra coagulant and antibiotics to help stabilize bad cuts and burns." + icon_state = "firstaid" + item_state = "firstaid" volume = 15 amount_per_transfer_from_this = 15 list_reagents = list(/datum/reagent/medicine/epinephrine = 12, /datum/reagent/medicine/coagulant = 2.5, /datum/reagent/medicine/spaceacillin = 0.5) @@ -143,15 +156,19 @@ /obj/item/reagent_containers/hypospray/medipen/blood_loss name = "hypovolemic-response autoinjector" desc = "A medipen designed to stabilize and rapidly reverse severe bloodloss." + icon_state = "hypovolemic" + item_state = "hypovolemic" volume = 15 amount_per_transfer_from_this = 15 list_reagents = list(/datum/reagent/medicine/epinephrine = 5, /datum/reagent/medicine/coagulant = 2.5, /datum/reagent/iron = 3.5, /datum/reagent/medicine/salglu_solution = 4) /obj/item/reagent_containers/hypospray/medipen/stimulants - name = "illegal stimpack medipen" - desc = "A highly illegal medipen due to its load and small injections, allow for five uses before being drained" + name = "stimpack medipen" + desc = "Contains stimulants." + icon_state = "syndipen" + item_state = "syndipen" volume = 50 - amount_per_transfer_from_this = 10 + amount_per_transfer_from_this = 50 list_reagents = list(/datum/reagent/medicine/stimulants = 50) /obj/item/reagent_containers/hypospray/medipen/stimulants/baseball @@ -166,6 +183,7 @@ name = "stimpack medipen" desc = "A rapid way to stimulate your body's adrenaline, allowing for freer movement in restrictive armor." icon_state = "stimpen" + item_state = "stimpen" volume = 20 amount_per_transfer_from_this = 20 list_reagents = list(/datum/reagent/medicine/ephedrine = 10, /datum/reagent/consumable/coffee = 10) @@ -177,20 +195,79 @@ /obj/item/reagent_containers/hypospray/medipen/morphine name = "morphine medipen" desc = "A rapid way to get you out of a tight situation and fast! You'll feel rather drowsy, though." + icon_state = "morphen" + item_state = "morphen" + volume = 10 + amount_per_transfer_from_this = 10 list_reagents = list(/datum/reagent/medicine/morphine = 10) +/obj/item/reagent_containers/hypospray/medipen/penacid + name = "pentetic acid medipen" + desc = "A autoinjector containing pentetic acid, used to reduce high levels of radiations and moderate toxins." + icon_state = "penacid" + item_state = "penacid" + volume = 10 + amount_per_transfer_from_this = 10 + list_reagents = list(/datum/reagent/medicine/pen_acid = 10) + +/obj/item/reagent_containers/hypospray/medipen/atropine + name = "atropine autoinjector" + desc = "A rapid way to save a person from a critical injury state!" + icon_state = "atropen" + item_state = "atropen" + volume = 10 + amount_per_transfer_from_this = 10 + list_reagents = list(/datum/reagent/medicine/atropine = 10) + +/obj/item/reagent_containers/hypospray/medipen/salacid + name = "salicyclic acid medipen" + desc = "A autoinjector containing salicyclic acid, used to treat severe brute damage." + icon_state = "salacid" + item_state = "salacid" + volume = 10 + amount_per_transfer_from_this = 10 + list_reagents = list(/datum/reagent/medicine/sal_acid = 10) + +/obj/item/reagent_containers/hypospray/medipen/oxandrolone + name = "oxandrolone medipen" + desc = "A autoinjector containing oxandrolone, used to treat severe burns." + icon_state = "oxapen" + item_state = "oxapen" + volume = 10 + amount_per_transfer_from_this = 10 + list_reagents = list(/datum/reagent/medicine/oxandrolone = 10) + +/obj/item/reagent_containers/hypospray/medipen/salbutamol + name = "salbutamol medipen" + desc = "A autoinjector containing salbutamol, used to heal oxygen damage quickly." + icon_state = "salpen" + item_state = "salpen" + volume = 10 + amount_per_transfer_from_this = 10 + list_reagents = list(/datum/reagent/medicine/salbutamol = 10) + /obj/item/reagent_containers/hypospray/medipen/tuberculosiscure name = "BVAK autoinjector" desc = "Bio Virus Antidote Kit autoinjector. Has a two use system for yourself, and someone else. Inject when infected." - icon_state = "stimpen" + icon_state = "tbpen" + item_state = "tbpen" volume = 60 amount_per_transfer_from_this = 30 list_reagents = list(/datum/reagent/medicine/atropine = 10, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/salbutamol = 20, /datum/reagent/medicine/spaceacillin = 20) +/obj/item/reagent_containers/hypospray/medipen/tuberculosiscure/update_icon() + if(reagents.total_volume > 30) + icon_state = initial(icon_state) + else if (reagents.total_volume > 0) + icon_state = "[initial(icon_state)]1" + else + icon_state = "[initial(icon_state)]0" + /obj/item/reagent_containers/hypospray/medipen/survival name = "survival medipen" desc = "A medipen for surviving in the harshest of environments, heals and protects from environmental hazards. WARNING: Do not inject more than one pen in quick succession." - icon_state = "stimpen" + icon_state = "minepen" + item_state = "minepen" volume = 52 amount_per_transfer_from_this = 52 list_reagents = list(/datum/reagent/medicine/salbutamol = 10, /datum/reagent/medicine/leporazine = 15, /datum/reagent/medicine/neo_jelly = 15, /datum/reagent/medicine/epinephrine = 10, /datum/reagent/medicine/lavaland_extract = 2) @@ -198,16 +275,21 @@ /obj/item/reagent_containers/hypospray/medipen/firelocker name = "fire treatment medipen" desc = "A medipen that has been fulled with burn healing chemicals for personnel without advanced medical knowledge." + icon_state = "firepen" + item_state = "firepen" volume = 15 amount_per_transfer_from_this = 15 list_reagents = list(/datum/reagent/medicine/oxandrolone = 5, /datum/reagent/medicine/kelotane = 10) -/obj/item/reagent_containers/hypospray/combat/heresypurge - name = "holy water autoinjector" - desc = "A modified air-needle autoinjector for use in combat situations. Prefilled with 5 doses of a holy water mixture." - volume = 250 - list_reagents = list(/datum/reagent/water/holywater = 150, /datum/reagent/peaceborg_tire = 50, /datum/reagent/peaceborg_confuse = 50) - amount_per_transfer_from_this = 50 +/obj/item/reagent_containers/hypospray/medipen/magillitis + name = "experimental autoinjector" + desc = "A custom-frame needle injector with a small single-use reservoir, containing an experimental serum. Unlike the more common medipen frame, it cannot pierce through protective armor or hardsuits, nor can the chemical inside be extracted." + icon_state = "gorillapen" + item_state = "gorillapen" + volume = 5 + ignore_flags = 0 + reagent_flags = NONE + list_reagents = list(/datum/reagent/magillitis = 5) #define HYPO_SPRAY 0 #define HYPO_INJECT 1 diff --git a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm index 0c34a251ae..c408ad096e 100644 --- a/code/modules/research/xenobiology/crossbreeding/_status_effects.dm +++ b/code/modules/research/xenobiology/crossbreeding/_status_effects.dm @@ -573,6 +573,10 @@ if(batteries.len) var/obj/item/stock_parts/cell/ToCharge = pick(batteries) ToCharge.charge += min(ToCharge.maxcharge - ToCharge.charge, ToCharge.maxcharge/10) //10% of the cell, or to maximum. + ToCharge.update_appearance() //make sure the cell gets their appearance updated. + var/atom/l = ToCharge.loc + if(isgun(l)) //updates the gun appearance as well if the cell is inside one. + l.update_appearance() to_chat(owner, "[linked_extract] discharges some energy into a device you have.") return ..() diff --git a/code/modules/station_goals/dna_vault.dm b/code/modules/station_goals/dna_vault.dm index b68a289e85..69f2d5454e 100644 --- a/code/modules/station_goals/dna_vault.dm +++ b/code/modules/station_goals/dna_vault.dm @@ -63,10 +63,10 @@ name = "DNA Sampler" desc = "Can be used to take chemical and genetic samples of pretty much anything." icon = 'icons/obj/syringe.dmi' - item_state = "hypo" + item_state = "sampler" lefthand_file = 'icons/mob/inhands/equipment/medical_lefthand.dmi' righthand_file = 'icons/mob/inhands/equipment/medical_righthand.dmi' - icon_state = "hypo" + icon_state = "sampler" item_flags = NOBLUDGEON var/list/animals = list() var/list/plants = list() diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index 93f88192e5..e10b05822b 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -54,7 +54,8 @@ var/SA_para_min = 1 //nitrous values var/SA_sleep_min = 5 - var/BZ_trip_balls_min = 1 //BZ gas + var/BZ_trip_balls_min = 0.1 //BZ gas + var/BZ_brain_damage_min = 1 var/gas_stimulation_min = 0.002 //Nitryl and Stimulum var/cold_message = "your face freezing and an icicle forming" @@ -269,13 +270,13 @@ // BZ var/bz_pp = PP(breath, GAS_BZ) - if(bz_pp > BZ_trip_balls_min) + if(bz_pp > BZ_brain_damage_min) H.hallucination += 10 H.reagents.add_reagent(/datum/reagent/bz_metabolites,5) if(prob(33)) H.adjustOrganLoss(ORGAN_SLOT_BRAIN, 3, 150) - else if(bz_pp > 0.01) + else if(bz_pp > BZ_trip_balls_min) H.hallucination += 5 H.reagents.add_reagent(/datum/reagent/bz_metabolites,1) @@ -476,7 +477,7 @@ ) SA_para_min = 30 SA_sleep_min = 50 - BZ_trip_balls_min = 30 + BZ_brain_damage_min = 30 emp_vulnerability = 3 cold_level_1_threshold = 200 @@ -509,8 +510,29 @@ heat_level_2_threshold = 600 // up 200 from level 1, 1000 is silly but w/e for level 3 /obj/item/organ/lungs/ashwalker/populate_gas_info() + // humans usually breathe 21 but require 16/17, so 80% - 1, which is more lenient but it's fine + #define SAFE_THRESHOLD_RATIO 0.8 + var/datum/gas_mixture/breath = SSair.planetary[LAVALAND_DEFAULT_ATMOS] // y'all know + var/pressure = breath.return_pressure() + var/total_moles = breath.total_moles() + for(var/id in breath.get_gases()) + var/this_pressure = PP(breath, id) + var/req_pressure = (this_pressure * SAFE_THRESHOLD_RATIO) - 1 + if(req_pressure > 0) + gas_min[id] = req_pressure + if(id in gas_max) + gas_max[id] += this_pressure + var/bz = breath.get_moles(GAS_BZ) + if(bz) + BZ_trip_balls_min += bz + BZ_brain_damage_min += bz + + gas_max[GAS_N2] = PP(breath, GAS_N2) + 5 + var/o2_pp = PP(breath, GAS_O2) + safe_breath_min = 0.3 * o2_pp + safe_breath_max = 1.3 * o2_pp ..() - gas_max[GAS_N2] = 28 + #undef SAFE_THRESHOLD_RATIO /obj/item/organ/lungs/slime name = "vacuole" diff --git a/code/modules/tooltip/tooltip.dm b/code/modules/tooltip/tooltip.dm index ca1b163968..65e44cfbc6 100644 --- a/code/modules/tooltip/tooltip.dm +++ b/code/modules/tooltip/tooltip.dm @@ -137,7 +137,7 @@ Notes: /atom/movable/MouseEntered(location, control, params) . = ..() if(tooltips) - if(!QDELETED(src)) + if(!QDELETED(src) && usr.client.prefs.enable_tips) var/list/tooltip_data = get_tooltip_data() if(length(tooltip_data)) var/examine_data = tooltip_data.Join("
") diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index 7cc72ed1c1..706a7a3910 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -32,47 +32,79 @@ /// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)` #define TEST_FOCUS(test_path) ##test_path { focus = TRUE; } +/// Constants indicating unit test completion status +#define UNIT_TEST_PASSED 0 +#define UNIT_TEST_FAILED 1 +#define UNIT_TEST_SKIPPED 2 + +#define TEST_DEFAULT 1 +#define TEST_DEL_WORLD INFINITY + +/// A trait source when adding traits through unit tests +#define TRAIT_SOURCE_UNIT_TESTS "unit_tests" + #include "anchored_mobs.dm" #include "bespoke_id.dm" #include "binary_insert.dm" +// #include "bloody_footprints.dm" +// #include "breath.dm" // #include "card_mismatch.dm" #include "chain_pull_through_space.dm" // #include "combat.dm" #include "component_tests.dm" +// #include "connect_loc.dm" // #include "confusion.dm" +// #include "crayons.dm" +// #include "create_and_destroy.dm" +// #include "designs.dm" +// #include "dynamic_ruleset_sanity.dm" +// #include "egg_glands.dm" // #include "emoting.dm" +// #include "food_edibility_check.dm" +// #include "greyscale_config.dm" // #include "heretic_knowledge.dm" // #include "holidays.dm" -#include "initialize_sanity.dm" +// #include "hydroponics_harvest.dm" // #include "keybinding_init.dm" #include "machine_disassembly.dm" #include "medical_wounds.dm" #include "merge_type.dm" // #include "metabolizing.dm" +// #include "ntnetwork_tests.dm" // #include "outfit_sanity.dm" // #include "pills.dm" // #include "plantgrowth_tests.dm" // #include "projectiles.dm" +// #include "rcd.dm" #include "reagent_id_typos.dm" // #include "reagent_mod_expose.dm" // #include "reagent_mod_procs.dm" #include "reagent_recipe_collisions.dm" #include "resist.dm" // #include "say.dm" +// #include "security_officer_distribution.dm" // #include "serving_tray.dm" // #include "siunit.dm" #include "spawn_humans.dm" +#include "spawn_mobs.dm" // #include "species_whitelists.dm" // #include "stomach.dm" +// #include "strippable.dm" #include "subsystem_init.dm" #include "surgeries.dm" #include "teleporters.dm" +#include "tgui_create_message.dm" #include "timer_sanity.dm" #include "unit_test.dm" +// #include "wizard.dm" /// CIT TESTS #include "character_saving.dm" +#ifdef REFERENCE_TRACKING //Don't try and parse this file if ref tracking isn't turned on. IE: don't parse ref tracking please mr linter +#include "find_reference_sanity.dm" +#endif + #undef TEST_ASSERT #undef TEST_ASSERT_EQUAL #undef TEST_ASSERT_NOTEQUAL diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm index ffdd1bf7c9..10363d5aad 100644 --- a/code/modules/unit_tests/chain_pull_through_space.dm +++ b/code/modules/unit_tests/chain_pull_through_space.dm @@ -1,6 +1,6 @@ /datum/unit_test/chain_pull_through_space var/turf/open/space/space_tile - var/turf/claimed_tile + var/claimed_tile var/mob/living/carbon/human/alice var/mob/living/carbon/human/bob var/mob/living/carbon/human/charlie @@ -9,25 +9,25 @@ ..() // Create a space tile that goes to another z-level - claimed_tile = run_loc_bottom_left + claimed_tile = run_loc_floor_bottom_left.type - space_tile = new(locate(run_loc_bottom_left.x, run_loc_bottom_left.y, run_loc_bottom_left.z)) + space_tile = new(locate(run_loc_floor_bottom_left.x, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) space_tile.destination_x = 100 space_tile.destination_y = 100 space_tile.destination_z = 5 // Create our list of humans, all adjacent to one another - alice = new(locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z)) + alice = new(locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) alice.name = "Alice" - bob = new(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_bottom_left.z)) + bob = new(locate(run_loc_floor_bottom_left.x + 3, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) bob.name = "Bob" - charlie = new(locate(run_loc_bottom_left.x + 4, run_loc_bottom_left.y, run_loc_bottom_left.z)) + charlie = new(locate(run_loc_floor_bottom_left.x + 4, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) charlie.name = "Charlie" /datum/unit_test/chain_pull_through_space/Destroy() - space_tile.copyTurf(claimed_tile) + space_tile.ChangeTurf(claimed_tile) qdel(alice) qdel(bob) qdel(charlie) @@ -40,14 +40,14 @@ bob.start_pulling(charlie) // Walk normally to the left, make sure we're still a chain - alice.Move(locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z)) - if (bob.x != run_loc_bottom_left.x + 2) + alice.Move(locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) + if (bob.x != run_loc_floor_bottom_left.x + 2) return Fail("During normal move, Bob was not at the correct x ([bob.x])") - if (charlie.x != run_loc_bottom_left.x + 3) + if (charlie.x != run_loc_floor_bottom_left.x + 3) return Fail("During normal move, Charlie was not at the correct x ([charlie.x])") // We're going through the space turf now that should teleport us - alice.Move(run_loc_bottom_left) + alice.Move(run_loc_floor_bottom_left) if (alice.z != space_tile.destination_z) return Fail("Alice did not teleport to the destination z-level. Current location: ([alice.x], [alice.y], [alice.z])") diff --git a/code/modules/unit_tests/find_reference_sanity.dm b/code/modules/unit_tests/find_reference_sanity.dm new file mode 100644 index 0000000000..f41714f065 --- /dev/null +++ b/code/modules/unit_tests/find_reference_sanity.dm @@ -0,0 +1,111 @@ +///Used to test the completeness of the reference finder proc. +/datum/unit_test/find_reference_sanity + +/atom/movable/ref_holder + var/atom/movable/ref_test/test + var/list/test_list = list() + var/list/test_assoc_list = list() + +/atom/movable/ref_holder/Destroy() + test = null + test_list.Cut() + test_assoc_list.Cut() + return ..() + +/atom/movable/ref_test + var/atom/movable/ref_test/self_ref + +/atom/movable/ref_test/Destroy(force) + self_ref = null + return ..() + +/datum/unit_test/find_reference_sanity/Run() + var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) + var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder) + SSgarbage.should_save_refs = TRUE + + //Sanity check + victim.DoSearchVar(testbed, "Sanity Check", search_time = 1) //We increment search time to get around an optimization + TEST_ASSERT(!victim.found_refs.len, "The ref-tracking tool found a ref where none existed") + SSgarbage.should_save_refs = FALSE + +/datum/unit_test/find_reference_baseline/Run() + var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) + var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder) + SSgarbage.should_save_refs = TRUE + + //Set up for the first round of tests + testbed.test = victim + testbed.test_list += victim + testbed.test_assoc_list["baseline"] = victim + + victim.DoSearchVar(testbed, "First Run", search_time = 2) + + TEST_ASSERT(victim.found_refs["test"], "The ref-tracking tool failed to find a regular value") + TEST_ASSERT(victim.found_refs[testbed.test_list], "The ref-tracking tool failed to find a list entry") + TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list value") + SSgarbage.should_save_refs = FALSE + +/datum/unit_test/find_reference_exotic/Run() + var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) + var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder) + SSgarbage.should_save_refs = TRUE + + //Second round, bit harder this time + testbed.overlays += victim + testbed.vis_contents += victim + testbed.test_assoc_list[victim] = TRUE + + victim.DoSearchVar(testbed, "Second Run", search_time = 3) + + //This is another sanity check + TEST_ASSERT(!victim.found_refs[testbed.overlays], "The ref-tracking tool found an overlays entry? That shouldn't be possible") + TEST_ASSERT(victim.found_refs[testbed.vis_contents], "The ref-tracking tool failed to find a vis_contents entry") + TEST_ASSERT(victim.found_refs[testbed.test_assoc_list], "The ref-tracking tool failed to find an assoc list key") + SSgarbage.should_save_refs = FALSE + +/datum/unit_test/find_reference_esoteric/Run() + var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) + var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder) + SSgarbage.should_save_refs = TRUE + + //Let's get a bit esoteric + victim.self_ref = victim + var/list/to_find = list(victim) + testbed.test_list += list(to_find) + var/list/to_find_assoc = list(victim) + testbed.test_assoc_list["Nesting"] = to_find_assoc + + victim.DoSearchVar(victim, "Third Run Self", search_time = 4) + victim.DoSearchVar(testbed, "Third Run Testbed", search_time = 4) + TEST_ASSERT(victim.found_refs["self_ref"], "The ref-tracking tool failed to find a self reference") + TEST_ASSERT(victim.found_refs[to_find], "The ref-tracking tool failed to find a nested list entry") + TEST_ASSERT(victim.found_refs[to_find_assoc], "The ref-tracking tool failed to find a nested assoc list entry") + SSgarbage.should_save_refs = FALSE + +/datum/unit_test/find_reference_null_key_entry/Run() + var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) + var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder) + SSgarbage.should_save_refs = TRUE + + //Calm before the storm + testbed.test_assoc_list = list(null = victim) + + victim.DoSearchVar(testbed, "Fourth Run", search_time = 5) + TEST_ASSERT(testbed.test_assoc_list, "The ref-tracking tool failed to find a null key'd assoc list entry") + +/datum/unit_test/find_reference_assoc_investigation/Run() + var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) + var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder) + SSgarbage.should_save_refs = TRUE + + //Let's do some more complex assoc list investigation + var/list/to_find_in_key = list(victim) + testbed.test_assoc_list[to_find_in_key] = list("memes") + var/list/to_find_null_assoc_nested = list(victim) + testbed.test_assoc_list[null] = to_find_null_assoc_nested + + victim.DoSearchVar(testbed, "Fifth Run", search_time = 6) + TEST_ASSERT(victim.found_refs[to_find_in_key], "The ref-tracking tool failed to find a nested assoc list key") + TEST_ASSERT(victim.found_refs[to_find_null_assoc_nested], "The ref-tracking tool failed to find a null key'd nested assoc list entry") + SSgarbage.should_save_refs = FALSE diff --git a/code/modules/unit_tests/initialize_sanity.dm b/code/modules/unit_tests/initialize_sanity.dm deleted file mode 100644 index d183f530c8..0000000000 --- a/code/modules/unit_tests/initialize_sanity.dm +++ /dev/null @@ -1,11 +0,0 @@ -/datum/unit_test/initialize_sanity/Run() - if(length(SSatoms.BadInitializeCalls)) - Fail("Bad Initialize() calls detected. Please read logs.") - var/list/init_failures_to_text = list( - "[BAD_INIT_QDEL_BEFORE]" = "Qdeleted Before Initialized", - "[BAD_INIT_DIDNT_INIT]" = "Did Not Initialize", - "[BAD_INIT_SLEPT]" = "Initialize() Slept", - "[BAD_INIT_NO_HINT]" = "No Initialize() Hint Returned", - ) - for(var/failure in SSatoms.BadInitializeCalls) - log_world("[failure]: [init_failures_to_text["[SSatoms.BadInitializeCalls[failure]]"]]") // You like stacked brackets? diff --git a/code/modules/unit_tests/say.dm b/code/modules/unit_tests/say.dm deleted file mode 100644 index a7df5ad624..0000000000 --- a/code/modules/unit_tests/say.dm +++ /dev/null @@ -1,23 +0,0 @@ -/// Test to verify message mods are parsed correctly -/datum/unit_test/get_message_mods - var/mob/host_mob - -/datum/unit_test/get_message_mods/Run() - host_mob = allocate(/mob/living/carbon/human) - - test("Hello", "Hello", list()) - test(";HELP", "HELP", list(MODE_HEADSET = TRUE)) - test(";%Never gonna give you up", "Never gonna give you up", list(MODE_HEADSET = TRUE, MODE_SING = TRUE)) - test(".s Gun plz", "Gun plz", list(RADIO_KEY = RADIO_KEY_SECURITY, RADIO_EXTENSION = RADIO_CHANNEL_SECURITY)) - test("...What", "...What", list()) - -/datum/unit_test/get_message_mods/proc/test(message, expected_message, list/expected_mods) - var/list/mods = list() - TEST_ASSERT_EQUAL(host_mob.get_message_mods(message, mods), expected_message, "Chopped message was not what we expected. Message: [message]") - - for (var/mod_key in mods) - TEST_ASSERT_EQUAL(mods[mod_key], expected_mods[mod_key], "The value for [mod_key] was not what we expected. Message: [message]") - expected_mods -= mod_key - - if (expected_mods.len) - Fail("Some message mods were expected, but were not returned by get_message_mods: [json_encode(expected_mods)]. Message: [message]") diff --git a/code/modules/unit_tests/spawn_humans.dm b/code/modules/unit_tests/spawn_humans.dm index 0500deae0a..1bc7ca3d8a 100644 --- a/code/modules/unit_tests/spawn_humans.dm +++ b/code/modules/unit_tests/spawn_humans.dm @@ -1,5 +1,5 @@ /datum/unit_test/spawn_humans/Run() - var/locs = block(run_loc_bottom_left, run_loc_top_right) + var/locs = block(run_loc_floor_bottom_left, run_loc_floor_top_right) for(var/I in 1 to 5) new /mob/living/carbon/human(pick(locs)) diff --git a/code/modules/unit_tests/spawn_mobs.dm b/code/modules/unit_tests/spawn_mobs.dm new file mode 100644 index 0000000000..f14d14eb43 --- /dev/null +++ b/code/modules/unit_tests/spawn_mobs.dm @@ -0,0 +1,8 @@ +///Unit test that spawns all mobs that can be spawned by golden slimes +/datum/unit_test/spawn_mobs + +/datum/unit_test/spawn_mobs/Run() + for(var/_animal in typesof(/mob/living/simple_animal)) + var/mob/living/simple_animal/animal = _animal + if (initial(animal.gold_core_spawnable) == HOSTILE_SPAWN || initial(animal.gold_core_spawnable) == FRIENDLY_SPAWN) + allocate(_animal) diff --git a/code/modules/unit_tests/teleporters.dm b/code/modules/unit_tests/teleporters.dm index 0fc9bdb082..e1b4b71e9e 100644 --- a/code/modules/unit_tests/teleporters.dm +++ b/code/modules/unit_tests/teleporters.dm @@ -1,8 +1,8 @@ /datum/unit_test/auto_teleporter_linking/Run() // Put down the teleporter machinery var/obj/machinery/teleport/hub/hub = allocate(/obj/machinery/teleport/hub) - var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_bottom_left.x + 2, run_loc_bottom_left.y, run_loc_bottom_left.z)) - var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_bottom_left.x + 1, run_loc_bottom_left.y, run_loc_bottom_left.z)) + var/obj/machinery/computer/teleporter/computer = allocate(/obj/machinery/computer/teleporter, locate(run_loc_floor_bottom_left.x + 2, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) + var/obj/machinery/teleport/station/station = allocate(/obj/machinery/teleport/station, locate(run_loc_floor_bottom_left.x + 1, run_loc_floor_bottom_left.y, run_loc_floor_bottom_left.z)) TEST_ASSERT_EQUAL(hub.power_station, station, "Hub didn't link to the station") TEST_ASSERT_EQUAL(station.teleporter_console, computer, "Station didn't link to the teleporter console") diff --git a/code/modules/unit_tests/tgui_create_message.dm b/code/modules/unit_tests/tgui_create_message.dm new file mode 100644 index 0000000000..4d5a4bc0a0 --- /dev/null +++ b/code/modules/unit_tests/tgui_create_message.dm @@ -0,0 +1,28 @@ +/// Test that `TGUI_CREATE_MESSAGE` is correctly implemented +/datum/unit_test/tgui_create_message + +/datum/unit_test/tgui_create_message/Run() + var/type = "something/here" + var/list/payload = list( + "name" = "Terry McTider", + "heads_caved" = 100, + "accomplishments" = list( + "nothing", + "literally nothing", + list( + "something" = "just kidding", + ), + ), + ) + + var/message = TGUI_CREATE_MESSAGE(type, payload) + + // Ensure consistent output to compare by performing a round-trip. + var/output = json_encode(json_decode(url_decode(message))) + + var/expected = json_encode(list( + "type" = type, + "payload" = payload, + )) + + TEST_ASSERT_EQUAL(expected, output, "TGUI_CREATE_MESSAGE didn't round trip properly") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 15fe6b466c..aee62b7a52 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -7,7 +7,7 @@ Call Fail() to fail the test (You should specify a reason) You may use /New() and /Destroy() for setup/teardown respectively -You can use the run_loc_bottom_left and run_loc_top_right to get turfs for testing +You can use the run_loc_floor_bottom_left and run_loc_floor_top_right to get turfs for testing */ @@ -19,39 +19,46 @@ GLOBAL_VAR(test_log) //Bit of metadata for the future maybe var/list/procs_tested - /// The bottom left turf of the testing zone - var/turf/run_loc_bottom_left - - /// The top right turf of the testing zone - var/turf/run_loc_top_right - - /// The type of turf to allocate for the testing zone - var/test_turf_type = /turf/open/floor/plasteel + /// The bottom left floor turf of the testing zone + var/turf/run_loc_floor_bottom_left + /// The top right floor turf of the testing zone + var/turf/run_loc_floor_top_right + ///The priority of the test, the larger it is the later it fires + var/priority = TEST_DEFAULT //internal shit var/focus = FALSE var/succeeded = TRUE var/list/allocated var/list/fail_reasons - var/static/datum/turf_reservation/turf_reservation + var/static/datum/turf_reservation/reservation + +/proc/cmp_unit_test_priority(datum/unit_test/a, datum/unit_test/b) + return initial(a.priority) - initial(b.priority) /datum/unit_test/New() - if (isnull(turf_reservation)) - turf_reservation = SSmapping.RequestBlockReservation(5, 5) + if (isnull(reservation)) + reservation = SSmapping.RequestBlockReservation(5, 5) - for (var/turf/reserved_turf in turf_reservation.reserved_turfs) - reserved_turf.ChangeTurf(test_turf_type) + for (var/turf/reserved_turf in reservation.reserved_turfs) + reserved_turf.ChangeTurf(/turf/open/floor/plasteel) allocated = new - run_loc_bottom_left = locate(turf_reservation.bottom_left_coords[1], turf_reservation.bottom_left_coords[2], turf_reservation.bottom_left_coords[3]) - run_loc_top_right = locate(turf_reservation.top_right_coords[1], turf_reservation.top_right_coords[2], turf_reservation.top_right_coords[3]) + run_loc_floor_bottom_left = locate(reservation.bottom_left_coords[1], reservation.bottom_left_coords[2], reservation.bottom_left_coords[3]) + run_loc_floor_top_right = locate(reservation.top_right_coords[1], reservation.top_right_coords[2], reservation.top_right_coords[3]) + + TEST_ASSERT(isfloorturf(run_loc_floor_bottom_left), "run_loc_floor_bottom_left was not a floor ([run_loc_floor_bottom_left])") + TEST_ASSERT(isfloorturf(run_loc_floor_top_right), "run_loc_floor_top_right was not a floor ([run_loc_floor_top_right])") /datum/unit_test/Destroy() - //clear the test area - for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right)) - qdel(AM) QDEL_LIST(allocated) + // clear the test area + for (var/turf/turf in block(locate(1, 1, run_loc_floor_bottom_left.z), locate(world.maxx, world.maxy, run_loc_floor_bottom_left.z))) + for (var/content in turf.contents) + if (istype(content, /obj/effect/landmark)) + continue + qdel(content) return ..() /datum/unit_test/proc/Run() @@ -70,44 +77,64 @@ GLOBAL_VAR(test_log) /datum/unit_test/proc/allocate(type, ...) var/list/arguments = args.Copy(2) if (!arguments.len) - arguments = list(run_loc_bottom_left) + arguments = list(run_loc_floor_bottom_left) else if (arguments[1] == null) - arguments[1] = run_loc_bottom_left + arguments[1] = run_loc_floor_bottom_left var/instance = new type(arglist(arguments)) allocated += instance return instance +/proc/RunUnitTest(test_path, list/test_results) + var/datum/unit_test/test = new test_path + + GLOB.current_test = test + var/duration = REALTIMEOFDAY + + test.Run() + + duration = REALTIMEOFDAY - duration + GLOB.current_test = null + GLOB.failed_any_test |= !test.succeeded + + var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [test_path] [duration / 10]s") + var/list/fail_reasons = test.fail_reasons + + for(var/J in 1 to LAZYLEN(fail_reasons)) + log_entry += "\tREASON #[J]: [fail_reasons[J]]" + var/message = log_entry.Join("\n") + log_test(message) + + test_results[test_path] = list("status" = test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED, "message" = message, "name" = test_path) + + qdel(test) + /proc/RunUnitTests() CHECK_TICK - var/tests_to_run = subtypesof(/datum/unit_test) + var/list/tests_to_run = subtypesof(/datum/unit_test) for (var/_test_to_run in tests_to_run) var/datum/unit_test/test_to_run = _test_to_run if (initial(test_to_run.focus)) tests_to_run = list(test_to_run) break - for(var/I in tests_to_run) - var/datum/unit_test/test = new I + tests_to_run = sortTim(tests_to_run, /proc/cmp_unit_test_priority) - GLOB.current_test = test - var/duration = REALTIMEOFDAY + var/list/test_results = list() - test.Run() + for(var/unit_path in tests_to_run) + CHECK_TICK //We check tick first because the unit test we run last may be so expensive that checking tick will lock up this loop forever + RunUnitTest(unit_path, test_results) - duration = REALTIMEOFDAY - duration - GLOB.current_test = null - GLOB.failed_any_test |= !test.succeeded - - var/list/log_entry = list("[test.succeeded ? "PASS" : "FAIL"]: [I] [duration / 10]s") - var/list/fail_reasons = test.fail_reasons - - qdel(test) - - for(var/J in 1 to LAZYLEN(fail_reasons)) - log_entry += "\tREASON #[J]: [fail_reasons[J]]" - log_test(log_entry.Join("\n")) - - CHECK_TICK + var/file_name = "data/unit_tests.json" + fdel(file_name) + file(file_name) << json_encode(test_results) SSticker.force_ending = TRUE + //We have to call this manually because del_text can preceed us, and SSticker doesn't fire in the post game + SSticker.ready_for_reboot = TRUE + SSticker.standard_reboot() + +// /datum/map_template/unit_tests +// name = "Unit Tests Zone" +// mappath = "_maps/templates/unit_tests.dmm" diff --git a/code/modules/uplink/uplink_items/uplink_devices.dm b/code/modules/uplink/uplink_items/uplink_devices.dm index ad1cc31ba7..21889219cf 100644 --- a/code/modules/uplink/uplink_items/uplink_devices.dm +++ b/code/modules/uplink/uplink_items/uplink_devices.dm @@ -213,7 +213,7 @@ name = "Stimpack" desc = "Stimpacks, the tool of many great heroes. Makes you nearly immune to non-lethal weaponry for about \ 5 minutes after injection." - item = /obj/item/reagent_containers/syringe/stimulants + item = /obj/item/reagent_containers/hypospray/medipen/stimulants cost = 5 surplus = 90 diff --git a/code/modules/uplink/uplink_items/uplink_roles.dm b/code/modules/uplink/uplink_items/uplink_roles.dm index 774c2d9794..72e0111c41 100644 --- a/code/modules/uplink/uplink_items/uplink_roles.dm +++ b/code/modules/uplink/uplink_items/uplink_roles.dm @@ -195,7 +195,7 @@ name = "Magillitis Serum Autoinjector" desc = "A single-use autoinjector which contains an experimental serum that causes rapid muscular growth in Hominidae. \ Side-affects may include hypertrichosis, violent outbursts, and an unending affinity for bananas." - item = /obj/item/reagent_containers/hypospray/magillitis + item = /obj/item/reagent_containers/hypospray/medipen/magillitis cost = 8 restricted_roles = list("Geneticist", "Chief Medical Officer") diff --git a/code/modules/vending/games.dm b/code/modules/vending/games.dm index cea9c5ae70..a1d9bc1691 100644 --- a/code/modules/vending/games.dm +++ b/code/modules/vending/games.dm @@ -9,6 +9,7 @@ /obj/item/toy/cards/deck/cas/black = 3, /obj/item/toy/cards/deck/unum = 3, /obj/item/cardpack/series_one = 10, + /obj/item/dyespray=3, /obj/item/tcgcard_binder = 5) contraband = list(/obj/item/dice/fudge = 9) premium = list(/obj/item/melee/skateboard/pro = 3, diff --git a/config/entries/admin.txt b/config/entries/admin.txt index 883bda9422..e583790b82 100644 --- a/config/entries/admin.txt +++ b/config/entries/admin.txt @@ -52,8 +52,12 @@ ANNOUNCE_ADMIN_LOGOUT ## Uncomment to have an admin message sent anytime an admin connects to a round in play, you can edit the messages in admin.dm #ANNOUNCE_ADMIN_LOGIN +## Uncomment to enable global ban DB using the provided URL. The API should expect to receive a ckey at the end of the URL. ## More API details can be found here: https://centcom.melonmesa.com -CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search +#CENTCOM_BAN_DB https://centcom.melonmesa.com/ban/search +## Uncomment to enable source whitelisting, a comma-separated list (no spaces) of CentCom sources (sourceName). +## If enabled, only bans from these servers will be shown to admins using CentCom. The default sources list is an example. +#CENTCOM_SOURCE_WHITELIST Beestation MRP,TGMC,FTL13 ## AUTOADMIN ## The default admin rank diff --git a/config/entries/general.txt b/config/entries/general.txt index 3584af63c7..b3130565e4 100644 --- a/config/entries/general.txt +++ b/config/entries/general.txt @@ -491,3 +491,9 @@ ALLOW_CUSTOM_SKINTONES ## Allows pAI custom holoforms PAI_CUSTOM_HOLOFORMS + +## How long in seconds after which a hard delete is treated as causing lag. This can be a float and supports a precision as low as nanoseconds. +#HARD_DELETES_OVERRUN_THRESHOLD 0.5 + +## Once a typepath causes overrun from hard deletes this many times, stop hard deleting it on garbage collection failures. (set to 0 to disable) +#HARD_DELETES_OVERRUN_LIMIT 0 diff --git a/dependencies.sh b/dependencies.sh index cdbdcd98df..aca82a3a05 100644 --- a/dependencies.sh +++ b/dependencies.sh @@ -8,20 +8,20 @@ export BYOND_MAJOR=514 export BYOND_MINOR=1556 #rust_g git tag -export RUST_G_VERSION=0.4.8 - -#auxmos git tag -export AUXMOS_VERSION=v0.2.3 +export RUST_G_VERSION=0.4.10 #node version export NODE_VERSION=12 -export NODE_VERSION_PRECISE=12.20.0 +export NODE_VERSION_PRECISE=12.22.4 # SpacemanDMM git tag -export SPACEMAN_DMM_VERSION=suite-1.6 - -# Extools git tag -export EXTOOLS_VERSION=v0.0.7 +export SPACEMAN_DMM_VERSION=suite-1.7 # Python version for mapmerge and other tools export PYTHON_VERSION=3.6.8 + +# Auxmos git tag +export AUXMOS_VERSION=v0.2.3 + +# Extools git tag +export EXTOOLS_VERSION=v0.0.7 diff --git a/html/changelog.html b/html/changelog.html index 00188ead51..71799f0524 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -50,6 +50,142 @@ -->
+

10 September 2021

+

BlueWildrose updated:

+
    +
  • CTRL + (combat mode) Right Click - positional dropping and item rotation.
  • +
+

keronshb updated:

+
    +
  • Readds Reebe
  • +
  • Added the ability to dye your hair with gradients by using a hair dye spray.
  • +
  • The new Colorist quirk, allowing you to spawn with a hair dye spray.
  • +
  • Adds Hair gradients to preferences
  • +
  • Three new hair gradients, a pair of shorter fades and a spiky wave.
  • +
  • Adds viewers for mask of madness so it doesn't wallhack
  • +
+

zeroisthebiggay updated:

+
    +
  • Replaced the DNA probe's old sprite (Hypospray) with a unique sprite
  • +
  • added some icons and images for hyposprays and medipens so they stand out
  • +
  • added inhands for the cautery, retractor, drapes and hemostat.
  • +
  • New icon and sprites for the DNA probe
  • +
  • Emergency survival boxes now have an unique blue sprite and description to tell them apart from regular boxes.
  • +
  • Added craftable normal/extended emergency oxygen tank boxes to put your emergency oxygen tank collection inside.
  • +
  • Emergency first aid kits now look visually consistent with full first aid kits.
  • +
  • Ports new Hypospray, Combat Autoinjector, Pestle, Mortar and Dropper sprites from Shiptest!
  • +
  • Adds a new sprite for pill bottles!
  • +
  • new surgical tool sprites
  • +
  • The cyborg toolset is now all new and improved, with a new coat of paint!
  • +
  • Updates pride hammer sprites!
  • +
  • Replaced old Mjolnir sprites with new Mjolnir sprites.
  • +
+ +

08 September 2021

+

keronshb updated:

+
    +
  • Adds the parade outfit for the HoS and Centcomm
  • +
  • Recolors the parade outfit for the Captain
  • +
  • Adds a toggle option for the parade outfits
  • +
  • Observers can no longer highlight your items
  • +
+ +

07 September 2021

+

bunny232 updated:

+
    +
  • fixed some jank in pubby's xenobiological secure pen
  • +
+

keronshb updated:

+
    +
  • Gremlins no longer have AA when dead
  • +
+

zeroisthebiggay updated:

+
    +
  • you can just about
  • +
+ +

05 September 2021

+

DeltaFire15 updated:

+
    +
  • Unreadied player gamemode votes now actually get ignored (if the config for this is enabled).
  • +
+ +

04 September 2021

+

Putnam3145 updated:

+
    +
  • Might've fixed some ghost sprite oddities nobody even knew about
  • +
+

WanderingFox95 updated:

+
    +
  • Lavaland architects don't screw up so badly anymore and do in fact not somehow leave holes through the planet surface all the way into space under their bookshelves.
  • +
  • Snow was removed from Lavaland Ruins.
  • +
+

keronshb updated:

+
    +
  • Fixes entering an occupied VR sleeper bug
  • +
+

timothyteakettle updated:

+
    +
  • makes the arm/leg markings for synthetic lizards appear as an option again
  • +
+ +

03 September 2021

+

timothyteakettle updated:

+
    +
  • fixes losing your additional language upon changing species
  • +
+ +

01 September 2021

+

BlueWildrose updated:

+
    +
  • The waddle component now takes size into account when running rotating animations, thus not reverting character sizes to default size.
  • +
+

ma44 updated:

+
    +
  • Weapon rechargers now have their recharger indicators again.
  • +
  • Energy guns now update when switching modes again.
  • +
  • Stabilized yellow slime extracts will now update cells and guns it recharges.
  • +
  • Weapon rechargers will now be more noticeable when it has finished recharging something.
  • +
+

zeroisthebiggay updated:

+
    +
  • the fucking chainsaw sprite
  • +
+ +

28 August 2021

+

DeltaFire15 updated:

+
    +
  • Demons now drop bodies on their own tile instead of scattering them across the station. tweak: Space dragon content ejection is now slightly safer.
  • +
  • Space dragons no longer delete their contents if they die due to timeout, ejecting them instead.
  • +
  • Carp rifts created by space dragons now have their armor work correctly.
  • +
+

Putnam3145 updated:

+
    +
  • Planetary monstermos can now be disabled with varedit
  • +
  • Lavaland/ice planet atmos is no longer a preset gas mixture and varies per round
  • +
+

keronshb updated:

+
    +
  • Ports Inventory Outlines
  • +
  • Re-adds the old glue sprite
  • +
  • Adds Plague Rats
  • +
  • Gives Plague Rat spawn conditions for regular mice
  • +
  • Plague Rat sprite
  • +
  • Gremlin
  • +
  • Gremlin sprites
  • +
+

zeroisthebiggay updated:

+
    +
  • grilles as maintenance loot
  • +
  • sevensune tail from hyperstation
  • +
+ +

26 August 2021

+

keronshb updated:

+
    +
  • Adds catwalk floors
  • +
+

24 August 2021

BlueWildrose updated:

    @@ -346,118 +482,6 @@
  • Returns the wheelchair sprites to having, you know, wheels?
  • A motorized wheelchair addsound: Chairwhoosh.
- -

09 July 2021

-

MrJWhit updated:

-
    -
  • Made a light not exist on the same tile as a door on pubby.
  • -
  • Makes the RD APC automatically connect to the powernet with the correct wire on pubby.
  • -
  • Added another wall for the AM engine so it doesn't space the airlock roundstart.
  • -
- -

07 July 2021

-

DeltaFire15 updated:

-
    -
  • Golem / Plasmaman species color should work again.
  • -
- -

05 July 2021

-

Putnam3145 updated:

-
    -
  • Watchers no longer search 9 tiles away for stuff then throw the result away if it's more than 1 tile away
  • -
  • Auxmos pull now uses a tag instead of pulling straight from the main branch
  • -
-

WanderingFox95 updated:

-
    -
  • Moved a sprite one pixel to the left.
  • -
-

zeroisthebiggay updated:

-
    -
  • tg based tool resprites
  • -
  • wirecutters have proper overlays
  • -
- -

04 July 2021

-

cadyn updated:

-
    -
  • Updated server scripts for proper linux support
  • -
- -

03 July 2021

-

DeltaFire15 updated:

-
    -
  • Turrets on nonlethal mode now once again shoot till the target is stamcrit as opposed to unable to use items, resolving some issues.
  • -
-

Putnam3145 updated:

-
    -
  • A bunch of sleeping process() calls now either don't sleep or make sure to call a proc with waitfor set to FALSE
  • -
-

WanderingFox95 updated:

-
    -
  • The axolotl ears in the .dmi file actually exist to the game now.
  • -
- -

02 July 2021

-

silicons updated:

-
    -
  • spray bottles work again.
  • -
- -

30 June 2021

-

WanderingFox95 updated:

-
    -
  • More plushies in the code.
  • -
  • Nabbed some plushie sprites from Cit RP and TG and made some myself. Enjoy!
  • -
  • The Daily Whiplash (Newspaper Baton) is now available! (Using sticky tape to stick a newspaper onto a baton) Bap!
  • -
  • A rolled up newspaper sprite was provided.
  • -
  • Switched Gateway and Vault locations on Boxstation, bringing it more in line with other stations.
  • -
-

bunny232 updated:

-
    -
  • The pubby xenobiology air/scrubber network is now isolated from the rest of the station
  • -
-

qweq12yt updated:

-
    -
  • HoP's cargo access was removed...
  • -
-

shellspeed1 updated:

-
    -
  • NT has lost experimental KA tech to miners who were lost in the field. Make sure to try and recover it.
  • -
  • Adds an extremely expensive survival pod to mining for people to work towards. Get to work cracking rocks today.
  • -
- -

28 June 2021

-

Putnam3145 updated:

-
    -
  • APCs aren't infinite power anymore
  • -
  • FDA, LINDA without the bookkeeping.
  • -
  • Putnamos, a simpler replacement for Monstermos. If I can get Monstermos to work, this will be Monstermos instead.
  • -
  • LINDA, the old active-turfs-based atmos subsystem.
  • -
  • Monstermos? I would rather not get rid of this, but I can't get it to work correctly.
  • -
  • Extools has been removed, and loading extools alongside auxtools will cause massive problems. If this is tested or merged, remove extools from the static files.
  • -
-

WanderingFox95 updated:

-
    -
  • Carrots are good for your eyes - but eyes are also good for your carrots. Adds the googly eyes trait to walkingshrooms (Oculary Mimicry)
  • -
  • Googly Eyes - they make everything better.
  • -
- -

25 June 2021

-

MrJWhit updated:

-
    -
  • Adds two missing decals to the 5x5 SM.
  • -
-

brokenOculus updated:

-
    -
  • pillbottles and syringesare now printable from the medbay protolathe, shiftstart. Hyposprays are now printable in medbay lathe under advanced biotech.
  • -
- -

24 June 2021

-

WanderingFox95 updated:

-
    -
  • An announcement to let players know the Cat Surgeon has come to visit.
  • -
  • Upped the Volume of his spawn noise and lowered the spawn weight slightly.
  • -
GoonStation 13 Development Team diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 9790591c1c..5dfa933371 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -29848,3 +29848,102 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. BlueWildrose: - balance: MK ultra explosions (failures at making MKultra) are gone, and replaced with a gas that just causes a bunch of status effects to you. +2021-08-26: + keronshb: + - rscadd: Adds catwalk floors +2021-08-28: + DeltaFire15: + - bugfix: 'Demons now drop bodies on their own tile instead of scattering them across + the station. tweak: Space dragon content ejection is now slightly safer.' + - bugfix: Space dragons no longer delete their contents if they die due to timeout, + ejecting them instead. + - bugfix: Carp rifts created by space dragons now have their armor work correctly. + Putnam3145: + - config: Planetary monstermos can now be disabled with varedit + - rscadd: Lavaland/ice planet atmos is no longer a preset gas mixture and varies + per round + keronshb: + - rscadd: Ports Inventory Outlines + - imageadd: Re-adds the old glue sprite + - rscadd: Adds Plague Rats + - rscadd: Gives Plague Rat spawn conditions for regular mice + - imageadd: Plague Rat sprite + - rscadd: Gremlin + - imageadd: Gremlin sprites + zeroisthebiggay: + - rscdel: grilles as maintenance loot + - rscadd: sevensune tail from hyperstation +2021-09-01: + BlueWildrose: + - bugfix: The waddle component now takes size into account when running rotating + animations, thus not reverting character sizes to default size. + ma44: + - bugfix: Weapon rechargers now have their recharger indicators again. + - bugfix: Energy guns now update when switching modes again. + - bugfix: Stabilized yellow slime extracts will now update cells and guns it recharges. + - rscadd: Weapon rechargers will now be more noticeable when it has finished recharging + something. + zeroisthebiggay: + - bugfix: the fucking chainsaw sprite +2021-09-03: + timothyteakettle: + - bugfix: fixes losing your additional language upon changing species +2021-09-04: + Putnam3145: + - bugfix: Might've fixed some ghost sprite oddities nobody even knew about + WanderingFox95: + - bugfix: Lavaland architects don't screw up so badly anymore and do in fact not + somehow leave holes through the planet surface all the way into space under + their bookshelves. + - bugfix: Snow was removed from Lavaland Ruins. + keronshb: + - bugfix: Fixes entering an occupied VR sleeper bug + timothyteakettle: + - bugfix: makes the arm/leg markings for synthetic lizards appear as an option again +2021-09-05: + DeltaFire15: + - bugfix: Unreadied player gamemode votes now actually get ignored (if the config + for this is enabled). +2021-09-07: + bunny232: + - bugfix: fixed some jank in pubby's xenobiological secure pen + keronshb: + - bugfix: Gremlins no longer have AA when dead + zeroisthebiggay: + - spellcheck: you can just about +2021-09-08: + keronshb: + - imageadd: Adds the parade outfit for the HoS and Centcomm + - imageadd: Recolors the parade outfit for the Captain + - imageadd: Adds a toggle option for the parade outfits + - bugfix: Observers can no longer highlight your items +2021-09-10: + BlueWildrose: + - rscadd: CTRL + (combat mode) Right Click - positional dropping and item rotation. + keronshb: + - rscadd: Readds Reebe + - rscadd: Added the ability to dye your hair with gradients by using a hair dye + spray. + - rscadd: The new Colorist quirk, allowing you to spawn with a hair dye spray. + - rscadd: Adds Hair gradients to preferences + - imageadd: Three new hair gradients, a pair of shorter fades and a spiky wave. + - bugfix: Adds viewers for mask of madness so it doesn't wallhack + zeroisthebiggay: + - bugfix: Replaced the DNA probe's old sprite (Hypospray) with a unique sprite + - imageadd: added some icons and images for hyposprays and medipens so they stand + out + - imageadd: added inhands for the cautery, retractor, drapes and hemostat. + - imageadd: New icon and sprites for the DNA probe + - imageadd: Emergency survival boxes now have an unique blue sprite and description + to tell them apart from regular boxes. + - imageadd: Added craftable normal/extended emergency oxygen tank boxes to put your + emergency oxygen tank collection inside. + - imageadd: Emergency first aid kits now look visually consistent with full first + aid kits. + - imageadd: Ports new Hypospray, Combat Autoinjector, Pestle, Mortar and Dropper + sprites from Shiptest! + - imageadd: Adds a new sprite for pill bottles! + - imageadd: new surgical tool sprites + - imageadd: The cyborg toolset is now all new and improved, with a new coat of paint! + - imageadd: Updates pride hammer sprites! + - imageadd: Replaced old Mjolnir sprites with new Mjolnir sprites. diff --git a/html/changelogs/AutoChangeLog-pr-15063.yml b/html/changelogs/AutoChangeLog-pr-15063.yml new file mode 100644 index 0000000000..3f4a8e41f7 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15063.yml @@ -0,0 +1,6 @@ +author: "zeroisthebiggay" +delete-after: True +changes: + - imageadd: "reinforcing the mining hardsuit with goliath hide now makes the sprite cooler" + - imageadd: "The SWAT helmet is now consistent between its front, side and back sprites for coloration." + - imageadd: "new riot armor sprites" diff --git a/html/changelogs/AutoChangeLog-pr-15097.yml b/html/changelogs/AutoChangeLog-pr-15097.yml new file mode 100644 index 0000000000..3bef77537a --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15097.yml @@ -0,0 +1,7 @@ +author: "LetterN" +delete-after: True +changes: + - code_imp: "Tickers, GC, MC, FS updates" + - code_imp: "rust_g update. It is default that we use their urlencode/unencode now." + - code_imp: "updates CBT to juke" + - bugfix: "CI Cache works properly now" diff --git a/html/changelogs/AutoChangeLog-pr-15103.yml b/html/changelogs/AutoChangeLog-pr-15103.yml new file mode 100644 index 0000000000..b187336c91 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15103.yml @@ -0,0 +1,8 @@ +author: "SandPoot" +delete-after: True +changes: + - rscadd: "Cyborg grippers now have a preview of the item you are holding. +tweak: Cyborg grippers no longer drop using alt-click, instead, you try to drop the gripper to drop the item, and then you can drop the gripper. +tweak: You can now use the \"Pick up\" verb on the right click menu to take items with a cyborg gripper." + - bugfix: "You should be able to access the interface of airlock electronics once again as a borg." + - refactor: "Reworks a lot of stuff that was being used on the grippers." diff --git a/html/changelogs/AutoChangeLog-pr-15110.yml b/html/changelogs/AutoChangeLog-pr-15110.yml new file mode 100644 index 0000000000..c519dc4248 --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15110.yml @@ -0,0 +1,4 @@ +author: "timothyteakettle" +delete-after: True +changes: + - bugfix: "arachnid legs now show up properly" diff --git a/html/changelogs/AutoChangeLog-pr-15111.yml b/html/changelogs/AutoChangeLog-pr-15111.yml new file mode 100644 index 0000000000..117e152e5c --- /dev/null +++ b/html/changelogs/AutoChangeLog-pr-15111.yml @@ -0,0 +1,4 @@ +author: "Putnam3145" +delete-after: True +changes: + - bugfix: "Removed some crashes" diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi index 68e6170969..34bd587146 100644 Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ diff --git a/icons/mob/arachnid_parts.dmi b/icons/mob/arachnid_parts.dmi index 1b70935450..196e2037b5 100644 Binary files a/icons/mob/arachnid_parts.dmi and b/icons/mob/arachnid_parts.dmi differ diff --git a/icons/mob/clothing/head.dmi b/icons/mob/clothing/head.dmi index a688f3d9dd..1596672ac3 100644 Binary files a/icons/mob/clothing/head.dmi and b/icons/mob/clothing/head.dmi differ diff --git a/icons/mob/clothing/head_muzzled.dmi b/icons/mob/clothing/head_muzzled.dmi index e99f1ff6d4..2fce92ee81 100644 Binary files a/icons/mob/clothing/head_muzzled.dmi and b/icons/mob/clothing/head_muzzled.dmi differ diff --git a/icons/mob/clothing/suit.dmi b/icons/mob/clothing/suit.dmi index 39ad5337a7..6d7637f030 100644 Binary files a/icons/mob/clothing/suit.dmi and b/icons/mob/clothing/suit.dmi differ diff --git a/icons/mob/clothing/suit_digi.dmi b/icons/mob/clothing/suit_digi.dmi index f80cb4a426..80259e2832 100644 Binary files a/icons/mob/clothing/suit_digi.dmi and b/icons/mob/clothing/suit_digi.dmi differ diff --git a/icons/mob/clothing/taur_canine.dmi b/icons/mob/clothing/taur_canine.dmi index 6155a274f2..db88043fb2 100644 Binary files a/icons/mob/clothing/taur_canine.dmi and b/icons/mob/clothing/taur_canine.dmi differ diff --git a/icons/mob/clothing/taur_hooved.dmi b/icons/mob/clothing/taur_hooved.dmi index 03fd8c8a30..a0af305d4c 100644 Binary files a/icons/mob/clothing/taur_hooved.dmi and b/icons/mob/clothing/taur_hooved.dmi differ diff --git a/icons/mob/clothing/taur_naga.dmi b/icons/mob/clothing/taur_naga.dmi index 3525333fb3..3df8625bec 100644 Binary files a/icons/mob/clothing/taur_naga.dmi and b/icons/mob/clothing/taur_naga.dmi differ diff --git a/icons/mob/hair_gradients.dmi b/icons/mob/hair_gradients.dmi new file mode 100644 index 0000000000..ceb3b52eab Binary files /dev/null and b/icons/mob/hair_gradients.dmi differ diff --git a/icons/mob/inhands/equipment/medical_lefthand.dmi b/icons/mob/inhands/equipment/medical_lefthand.dmi index 232f3f9e65..7cbb240b93 100644 Binary files a/icons/mob/inhands/equipment/medical_lefthand.dmi and b/icons/mob/inhands/equipment/medical_lefthand.dmi differ diff --git a/icons/mob/inhands/equipment/medical_righthand.dmi b/icons/mob/inhands/equipment/medical_righthand.dmi index 8133cca9bd..3b2b906404 100644 Binary files a/icons/mob/inhands/equipment/medical_righthand.dmi and b/icons/mob/inhands/equipment/medical_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/chainsaw_lefthand.dmi b/icons/mob/inhands/weapons/chainsaw_lefthand.dmi index 3e0aea0e6b..3053fea723 100644 Binary files a/icons/mob/inhands/weapons/chainsaw_lefthand.dmi and b/icons/mob/inhands/weapons/chainsaw_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/chainsaw_righthand.dmi b/icons/mob/inhands/weapons/chainsaw_righthand.dmi index 0800a52731..4e76c19b7a 100644 Binary files a/icons/mob/inhands/weapons/chainsaw_righthand.dmi and b/icons/mob/inhands/weapons/chainsaw_righthand.dmi differ diff --git a/icons/mob/inhands/weapons/hammers_lefthand.dmi b/icons/mob/inhands/weapons/hammers_lefthand.dmi index 612066728a..023dfeed89 100644 Binary files a/icons/mob/inhands/weapons/hammers_lefthand.dmi and b/icons/mob/inhands/weapons/hammers_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/hammers_righthand.dmi b/icons/mob/inhands/weapons/hammers_righthand.dmi index 9341cc8e4c..f87d8c9b46 100644 Binary files a/icons/mob/inhands/weapons/hammers_righthand.dmi and b/icons/mob/inhands/weapons/hammers_righthand.dmi differ diff --git a/icons/mob/mob.dmi b/icons/mob/mob.dmi index 3137e8ac26..65efde8697 100644 Binary files a/icons/mob/mob.dmi and b/icons/mob/mob.dmi differ diff --git a/icons/obj/chemical.dmi b/icons/obj/chemical.dmi index 3916fcb694..36db68b7bb 100644 Binary files a/icons/obj/chemical.dmi and b/icons/obj/chemical.dmi differ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index 27f9f5e0ac..7d4ae79957 100644 Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi index f689ee5068..e38eec35eb 100644 Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ diff --git a/icons/obj/dyespray.dmi b/icons/obj/dyespray.dmi new file mode 100644 index 0000000000..eb05603679 Binary files /dev/null and b/icons/obj/dyespray.dmi differ diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi index b468d0d4c9..39f1845f43 100644 Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ diff --git a/icons/obj/items_cyborg.dmi b/icons/obj/items_cyborg.dmi index 769d5492c6..5d87bee320 100644 Binary files a/icons/obj/items_cyborg.dmi and b/icons/obj/items_cyborg.dmi differ diff --git a/icons/obj/stationobjs.dmi b/icons/obj/stationobjs.dmi index a4fbe53dba..7e04fa0d2a 100644 Binary files a/icons/obj/stationobjs.dmi and b/icons/obj/stationobjs.dmi differ diff --git a/icons/obj/storage.dmi b/icons/obj/storage.dmi index b4f223e57d..8a17a8f448 100644 Binary files a/icons/obj/storage.dmi and b/icons/obj/storage.dmi differ diff --git a/icons/obj/surgery.dmi b/icons/obj/surgery.dmi index 52172bbe29..58ba5fadd8 100755 Binary files a/icons/obj/surgery.dmi and b/icons/obj/surgery.dmi differ diff --git a/icons/obj/syringe.dmi b/icons/obj/syringe.dmi index b474dc68ba..6d0c779e92 100644 Binary files a/icons/obj/syringe.dmi and b/icons/obj/syringe.dmi differ diff --git a/icons/obj/tiles.dmi b/icons/obj/tiles.dmi index 088b6c2c7b..757208cd9b 100644 Binary files a/icons/obj/tiles.dmi and b/icons/obj/tiles.dmi differ diff --git a/icons/obj/tools.dmi b/icons/obj/tools.dmi index a720aff62c..95a887a733 100644 Binary files a/icons/obj/tools.dmi and b/icons/obj/tools.dmi differ diff --git a/icons/turf/floors/catwalk_plating.dmi b/icons/turf/floors/catwalk_plating.dmi new file mode 100644 index 0000000000..24954e4a17 Binary files /dev/null and b/icons/turf/floors/catwalk_plating.dmi differ diff --git a/modular_citadel/icons/mob/mam_tails.dmi b/modular_citadel/icons/mob/mam_tails.dmi index c1ffced3d5..f30f7480f6 100644 Binary files a/modular_citadel/icons/mob/mam_tails.dmi and b/modular_citadel/icons/mob/mam_tails.dmi differ diff --git a/rust_g.dll b/rust_g.dll index 8ef1c59a10..26f6942861 100644 Binary files a/rust_g.dll and b/rust_g.dll differ diff --git a/strings/tips.txt b/strings/tips.txt index d4373b3606..5c6228769e 100644 --- a/strings/tips.txt +++ b/strings/tips.txt @@ -136,7 +136,7 @@ As a Security Officer, your security HUDglasses can not only see crewmates' job As a Security Officer, mindshield implants can only prevent someone from being turned into a cultist: unlike revolutionaries, it will not de-cult them if they have already been converted. As a Security Officer, examining someone while wearing your security HUDglasses can allow you to swiftly edit their records and criminal status. Be sure to set someone to WANTED if you can't catch up to them, as it'll alert other officers of who's the bad guy, and cause the little security droids to chase after them for you. As a Security Officer, you can take out the power cell on your baton to replace it with a better or fully charged one. Just use a screwdriver on your baton to remove the old cell. -As a Security Officer, you can just about any firearm on your vest, this even works with other non-standard armor-substitutes like security winter coats! +As a Security Officer, you can place just about any firearm on your vest slot, and this even works with other non-standard armor-substitutes like security winter coats! As the Detective, people leave fingerprints everywhere and on everything. With the exception of white latex, gloves will hide them. All is not lost, however, as gloves leave fibers specific to their kind such as black or nitrile, pointing to a general department. As the Detective, you can use your forensics scanner from a distance. Use this to scan boxes or other storage containers. As the Detective, your revolver can be loaded with .357 ammunition. Use a screwdriver to permanently modify your revolver into using this type of ammunition, be warned however, firing it has a decent chance to cause the revolver to misfire and shoot you in the foot. diff --git a/tgstation.dme b/tgstation.dme index 7b0806a921..c4de2a1799 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -103,6 +103,7 @@ #include "code\__DEFINES\rockpaperscissors.dm" #include "code\__DEFINES\role_preferences.dm" #include "code\__DEFINES\rust_g.dm" +#include "code\__DEFINES\rust_g_overrides.dm" #include "code\__DEFINES\say.dm" #include "code\__DEFINES\security_levels.dm" #include "code\__DEFINES\shuttles.dm" @@ -179,6 +180,7 @@ #include "code\__HELPERS\icons.dm" #include "code\__HELPERS\level_traits.dm" #include "code\__HELPERS\lighting.dm" +#include "code\__HELPERS\markov.dm" #include "code\__HELPERS\matrices.dm" #include "code\__HELPERS\mobs.dm" #include "code\__HELPERS\mouse_control.dm" @@ -462,6 +464,8 @@ #include "code\datums\achievements\skill_achievements.dm" #include "code\datums\actions\beam_rifle.dm" #include "code\datums\actions\ninja.dm" +#include "code\datums\atmosphere\_atmosphere.dm" +#include "code\datums\atmosphere\planetary.dm" #include "code\datums\brain_damage\brain_trauma.dm" #include "code\datums\brain_damage\hypnosis.dm" #include "code\datums\brain_damage\imaginary_friend.dm" @@ -662,6 +666,7 @@ #include "code\datums\helper_datums\events.dm" #include "code\datums\helper_datums\getrev.dm" #include "code\datums\helper_datums\icon_snapshot.dm" +#include "code\datums\helper_datums\stack_end_detector.dm" #include "code\datums\helper_datums\teleport.dm" #include "code\datums\looping_sounds\_looping_sound.dm" #include "code\datums\looping_sounds\item_sounds.dm" @@ -1099,6 +1104,7 @@ #include "code\game\objects\items\dna_injector.dm" #include "code\game\objects\items\documents.dm" #include "code\game\objects\items\dualsaber.dm" +#include "code\game\objects\items\dyekit.dm" #include "code\game\objects\items\eightball.dm" #include "code\game\objects\items\electrostaff.dm" #include "code\game\objects\items\extinguisher.dm" @@ -1391,6 +1397,7 @@ #include "code\game\turfs\closed.dm" #include "code\game\turfs\open.dm" #include "code\game\turfs\turf.dm" +#include "code\game\turfs\open\floor\catwalk_plating.dm" #include "code\game\turfs\simulated\chasm.dm" #include "code\game\turfs\simulated\dirtystation.dm" #include "code\game\turfs\simulated\floor.dm" @@ -2791,6 +2798,9 @@ #include "code\modules\mob\living\simple_animal\friendly\drone\say.dm" #include "code\modules\mob\living\simple_animal\friendly\drone\verbs.dm" #include "code\modules\mob\living\simple_animal\friendly\drone\visuals_icons.dm" +#include "code\modules\mob\living\simple_animal\gremlin\gremlin.dm" +#include "code\modules\mob\living\simple_animal\gremlin\gremlin_act.dm" +#include "code\modules\mob\living\simple_animal\gremlin\gremlin_event.dm" #include "code\modules\mob\living\simple_animal\guardian\guardian.dm" #include "code\modules\mob\living\simple_animal\guardian\types\assassin.dm" #include "code\modules\mob\living\simple_animal\guardian\types\charger.dm" @@ -2827,6 +2837,7 @@ #include "code\modules\mob\living\simple_animal\hostile\nanotrasen.dm" #include "code\modules\mob\living\simple_animal\hostile\netherworld.dm" #include "code\modules\mob\living\simple_animal\hostile\pirate.dm" +#include "code\modules\mob\living\simple_animal\hostile\plaguerat.dm" #include "code\modules\mob\living\simple_animal\hostile\regalrat.dm" #include "code\modules\mob\living\simple_animal\hostile\russian.dm" #include "code\modules\mob\living\simple_animal\hostile\sharks.dm" diff --git a/tools/build/README.md b/tools/build/README.md index f367857158..51121dbde6 100644 --- a/tools/build/README.md +++ b/tools/build/README.md @@ -13,6 +13,14 @@ This build script is the recommended way to compile the game, including not only The script will skip build steps whose inputs have not changed since the last run. +## Getting list of available targets + +You can get a list of all targets that you can build by running the following command: + +``` +tools/build/build --help +``` + ## Dependencies - On Windows, `BUILD.bat` will automatically install a private (vendored) copy of Node. @@ -22,3 +30,5 @@ The script will skip build steps whose inputs have not changed since the last ru ## Why? We used to include compiled versions of the tgui JavaScript code in the Git repository so that the project could be compiled using BYOND only. These pre-compiled files tended to have merge conflicts for no good reason. Using a build script lets us avoid this problem, while keeping builds convenient for people who are not modifying tgui. + +This build script is based on [Juke Build](https://github.com/stylemistake/juke-build) - please follow the link and read the documentation for the project to understand how it works and how to contribute to this build script. diff --git a/tools/build/binaries/README.md b/tools/build/binaries/README.md deleted file mode 100644 index 625f337d98..0000000000 --- a/tools/build/binaries/README.md +++ /dev/null @@ -1 +0,0 @@ -This directory is used to store temporary files to create binaries on linux \ No newline at end of file diff --git a/tools/build/build b/tools/build/build index cd4d804e8f..0e202e1bba 100755 --- a/tools/build/build +++ b/tools/build/build @@ -1,6 +1,4 @@ #!/bin/sh - -#Build TGUI set -e cd "$(dirname "$0")" exec ../bootstrap/node build.js "$@" diff --git a/tools/build/build.bat b/tools/build/build.bat index f5d9bd48f3..56e3bf1171 100644 --- a/tools/build/build.bat +++ b/tools/build/build.bat @@ -1 +1,2 @@ -@"%~dp0\..\bootstrap\node" "%~dp0\build.js" +@echo off +"%~dp0\..\bootstrap\node.bat" --experimental-modules "%~dp0\build.js" %* diff --git a/tools/build/build.js b/tools/build/build.js index 0f04441334..f8d3a9a7dd 100755 --- a/tools/build/build.js +++ b/tools/build/build.js @@ -1,225 +1,293 @@ #!/usr/bin/env node /** + * Build script for /tg/station 13 codebase. + * + * This script uses Juke Build, read the docs here: + * https://github.com/stylemistake/juke-build + * * @file - * @copyright 2020 Aleksej Komarov + * @copyright 2021 Aleksej Komarov * @license MIT */ -// Change working directory to project root -process.chdir(require('path').resolve(__dirname, '../../')); +import fs from 'fs'; +import { DreamDaemon, DreamMaker } from './lib/byond.js'; +import { yarn } from './lib/yarn.js'; +import Juke from './juke/index.js'; -// Validate NodeJS version -const NODE_VERSION = parseInt(process.versions.node.match(/(\d+)/)[1]); -const NODE_VERSION_TARGET = parseInt(require('fs') - .readFileSync('dependencies.sh', 'utf-8') - .match(/NODE_VERSION=(\d+)/)[1]); -if (NODE_VERSION < NODE_VERSION_TARGET) { - console.error('Your current Node.js version is out of date.'); - console.error('You have two options:'); - console.error(' a) Go to https://nodejs.org/ and install the latest LTS release of Node.js'); - console.error(' b) Uninstall Node.js (our build system automatically downloads one)'); - process.exit(1); -} +Juke.chdir('../..', import.meta.url); +Juke.setup({ file: import.meta.url }).then((code) => process.exit(code)); -const STANDARD_BUILD = "Standard Build" -const TGS_BUILD = "TGS Build" -const ALL_MAPS_BUILD = "CI All Maps Build" -const TEST_RUN_BUILD = "CI Integration Tests Build" -const NO_DM_BUILD = "Except DM Build" +const DME_NAME = 'tgstation'; -let BUILD_MODE = STANDARD_BUILD; -if (process.env.CBT_BUILD_MODE) { - switch (process.env.CBT_BUILD_MODE) { - case "ALL_MAPS": - BUILD_MODE = ALL_MAPS_BUILD - break; - case "TEST_RUN": - BUILD_MODE = TEST_RUN_BUILD - break; - case "TGS": - BUILD_MODE = TGS_BUILD - break; - case "NO_DM": - BUILD_MODE = NO_DM_BUILD - break; - default: - BUILD_MODE = process.env.CBT_BUILD_MODE - break; - } -} -console.log(`Starting CBT in ${BUILD_MODE} mode.`) +export const DefineParameter = new Juke.Parameter({ + type: 'string[]', + alias: 'D', +}); -const DME_NAME = 'tgstation' +export const PortParameter = new Juke.Parameter({ + type: 'string', + alias: 'p', +}); -// Main -// -------------------------------------------------------- +export const CiParameter = new Juke.Parameter({ + type: 'boolean', +}); -const { resolveGlob, stat } = require('./cbt/fs'); -const { exec } = require('./cbt/process'); -const { Task, runTasks } = require('./cbt/task'); -const { regQuery } = require('./cbt/winreg'); -const fs = require('fs'); +export const DmMapsIncludeTarget = new Juke.Target({ + executes: async () => { + const folders = [ + ...Juke.glob('_maps/RandomRuins/**/*.dmm'), + ...Juke.glob('_maps/RandomZLevels/**/*.dmm'), + ...Juke.glob('_maps/shuttles/**/*.dmm'), + ...Juke.glob('_maps/templates/**/*.dmm'), + ]; + const content = folders + .map((file) => file.replace('_maps/', '')) + .map((file) => `#include "${file}"`) + .join('\n') + '\n'; + fs.writeFileSync('_maps/templates.dm', content); + }, +}); -const yarn = args => { - const yarnPath = resolveGlob('./tgui/.yarn/releases/yarn-*.cjs')[0] - .replace('/tgui/', '/'); - return exec('node', [yarnPath, ...args], { - cwd: './tgui', - }); -}; +export const DmTarget = new Juke.Target({ + dependsOn: ({ get }) => [ + get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget, + ], + inputs: [ + '_maps/map_files/generic/**', + 'code/**', + 'goon/**', + 'html/**', + 'icons/**', + 'interface/**', + `${DME_NAME}.dme`, + ], + outputs: [ + `${DME_NAME}.dmb`, + `${DME_NAME}.rsc`, + ], + parameters: [DefineParameter], + executes: async ({ get }) => { + const defines = get(DefineParameter); + if (defines.length > 0) { + Juke.logger.info('Using defines:', defines.join(', ')); + } + await DreamMaker(`${DME_NAME}.dme`, { + defines: ['CBT', ...defines], + }); + }, +}); -/** Installs all tgui dependencies */ -const taskYarn = new Task('yarn') - // The following dependencies skip what could be considered an important - // step in Yarn: it verifies the integrity of cache. With this setup, if - // cache ever becomes corrupted, your only option is to clean build. - .depends('tgui/.yarn/+(cache|releases|plugins|sdks)/**/*') - .depends('tgui/**/package.json') - .depends('tgui/yarn.lock') - // Phony target (automatically created at the end of the task) - .provides('tgui/.yarn/install-target') - .build(() => yarn(['install'])); +export const DmTestTarget = new Juke.Target({ + dependsOn: ({ get }) => [ + get(DefineParameter).includes('ALL_MAPS') && DmMapsIncludeTarget, + ], + executes: async ({ get }) => { + const defines = get(DefineParameter); + if (defines.length > 0) { + Juke.logger.info('Using defines:', defines.join(', ')); + } + fs.copyFileSync(`${DME_NAME}.dme`, `${DME_NAME}.test.dme`); + await DreamMaker(`${DME_NAME}.test.dme`, { + defines: ['CBT', 'CIBUILDING', ...defines], + }); + Juke.rm('data/logs/ci', { recursive: true }); + await DreamDaemon( + `${DME_NAME}.test.dmb`, + '-close', '-trusted', '-verbose', + '-params', 'log-directory=ci' + ); + Juke.rm('*.test.*'); + try { + const cleanRun = fs.readFileSync('data/logs/ci/clean_run.lk', 'utf-8'); + console.log(cleanRun); + } + catch (err) { + Juke.logger.error('Test run was not clean, exiting'); + throw new Juke.ExitCode(1); + } + }, +}); -/** Builds svg fonts */ -const taskTgfont = new Task('tgfont') - .depends('tgui/.yarn/install-target') - .depends('tgui/packages/tgfont/**/*.+(js|cjs|svg)') - .depends('tgui/packages/tgfont/package.json') - .provides('tgui/packages/tgfont/dist/tgfont.css') - .provides('tgui/packages/tgfont/dist/tgfont.eot') - .provides('tgui/packages/tgfont/dist/tgfont.woff2') - .build(() => yarn(['workspace', 'tgfont', 'build'])); +export const YarnTarget = new Juke.Target({ + inputs: [ + 'tgui/.yarn/+(cache|releases|plugins|sdks)/**/*', + 'tgui/**/package.json', + 'tgui/yarn.lock', + ], + outputs: [ + 'tgui/.yarn/install-target', + ], + executes: async () => { + await yarn('install'); + }, +}); -/** Builds tgui */ -const taskTgui = new Task('tgui') - .depends('tgui/.yarn/install-target') - .depends('tgui/webpack.config.js') - .depends('tgui/**/package.json') - .depends('tgui/packages/**/*.+(js|cjs|ts|tsx|scss)') - .provides('tgui/public/tgui.bundle.css') - .provides('tgui/public/tgui.bundle.js') - .provides('tgui/public/tgui-common.bundle.js') - .provides('tgui/public/tgui-panel.bundle.css') - .provides('tgui/public/tgui-panel.bundle.js') - .build(async () => { - await yarn(['run', 'webpack-cli', '--mode=production']); - }); +export const TgFontTarget = new Juke.Target({ + dependsOn: [YarnTarget], + inputs: [ + 'tgui/.yarn/install-target', + 'tgui/packages/tgfont/**/*.+(js|cjs|svg)', + 'tgui/packages/tgfont/package.json', + ], + outputs: [ + 'tgui/packages/tgfont/dist/tgfont.css', + 'tgui/packages/tgfont/dist/tgfont.eot', + 'tgui/packages/tgfont/dist/tgfont.woff2', + ], + executes: async () => { + await yarn('workspace', 'tgfont', 'build'); + }, +}); + +export const TguiTarget = new Juke.Target({ + dependsOn: [YarnTarget], + inputs: [ + 'tgui/.yarn/install-target', + 'tgui/webpack.config.js', + 'tgui/**/package.json', + 'tgui/packages/**/*.+(js|cjs|ts|tsx|scss)', + ], + outputs: [ + 'tgui/public/tgui.bundle.css', + 'tgui/public/tgui.bundle.js', + 'tgui/public/tgui-panel.bundle.css', + 'tgui/public/tgui-panel.bundle.js', + ], + executes: async () => { + await yarn('webpack-cli', '--mode=production'); + }, +}); + +export const TguiEslintTarget = new Juke.Target({ + dependsOn: [YarnTarget], + executes: async ({ args }) => { + await yarn( + 'eslint', 'packages', + '--fix', '--ext', '.js,.cjs,.ts,.tsx', + ...args + ); + }, +}); + +export const TguiTscTarget = new Juke.Target({ + dependsOn: [YarnTarget], + executes: async () => { + await yarn('tsc'); + }, +}); + +export const TguiTestTarget = new Juke.Target({ + dependsOn: [YarnTarget], + executes: async ({ args }) => { + await yarn('jest', ...args); + }, +}); + +export const TguiLintTarget = new Juke.Target({ + dependsOn: [YarnTarget, TguiEslintTarget, TguiTscTarget, TguiTestTarget], +}); + +export const TguiDevTarget = new Juke.Target({ + dependsOn: [YarnTarget], + executes: async ({ args }) => { + await yarn('node', 'packages/tgui-dev-server/index.js', ...args); + }, +}); + +export const TguiAnalyzeTarget = new Juke.Target({ + dependsOn: [YarnTarget], + executes: async () => { + await yarn('webpack-cli', '--mode=production', '--analyze'); + }, +}); + +export const TestTarget = new Juke.Target({ + dependsOn: [DmTestTarget, TguiTestTarget], +}); + +export const LintTarget = new Juke.Target({ + dependsOn: [TguiLintTarget], +}); + +export const BuildTarget = new Juke.Target({ + dependsOn: [TguiTarget, TgFontTarget, DmTarget], +}); + +export const ServerTarget = new Juke.Target({ + dependsOn: [BuildTarget], + executes: async ({ get }) => { + const port = get(PortParameter) || '1337'; + await DreamDaemon(`${DME_NAME}.dmb`, port, '-trusted'); + }, +}); + +export const AllTarget = new Juke.Target({ + dependsOn: [TestTarget, LintTarget, BuildTarget], +}); + +/** + * Removes the immediate build junk to produce clean builds. + */ +export const CleanTarget = new Juke.Target({ + executes: async () => { + Juke.rm('*.dmb'); + Juke.rm('*.rsc'); + Juke.rm('*.mdme'); + Juke.rm('*.mdme*'); + Juke.rm('*.m.*'); + Juke.rm('_maps/templates.dm'); + Juke.rm('tgui/public/.tmp', { recursive: true }); + Juke.rm('tgui/public/*.map'); + Juke.rm('tgui/public/*.chunk.*'); + Juke.rm('tgui/public/*.bundle.*'); + Juke.rm('tgui/public/*.hot-update.*'); + Juke.rm('tgui/packages/tgfont/dist', { recursive: true }); + Juke.rm('tgui/.yarn/cache', { recursive: true }); + Juke.rm('tgui/.yarn/unplugged', { recursive: true }); + Juke.rm('tgui/.yarn/webpack', { recursive: true }); + Juke.rm('tgui/.yarn/build-state.yml'); + Juke.rm('tgui/.yarn/install-state.gz'); + Juke.rm('tgui/.yarn/install-target'); + Juke.rm('tgui/.pnp.*'); + }, +}); + +/** + * Removes more junk at expense of much slower initial builds. + */ +export const DistCleanTarget = new Juke.Target({ + dependsOn: [CleanTarget], + executes: async () => { + Juke.logger.info('Cleaning up data/logs'); + Juke.rm('data/logs', { recursive: true }); + Juke.logger.info('Cleaning up bootstrap cache'); + Juke.rm('tools/bootstrap/.cache', { recursive: true }); + Juke.logger.info('Cleaning up global yarn cache'); + await yarn('cache', 'clean', '--all'); + }, +}); /** * Prepends the defines to the .dme. * Does not clean them up, as this is intended for TGS which * clones new copies anyway. */ -const taskPrependDefines = (...defines) => new Task('prepend-defines') - .build(async () => { - const dmeContents = fs.readFileSync(`${DME_NAME}.dme`); - const textToWrite = defines.map(define => `#define ${define}\n`); - fs.writeFileSync(`${DME_NAME}.dme`, `${textToWrite}\n${dmeContents}`); - }); +const prependDefines = (...defines) => { + const dmeContents = fs.readFileSync(`${DME_NAME}.dme`); + const textToWrite = defines.map(define => `#define ${define}\n`); + fs.writeFileSync(`${DME_NAME}.dme`, `${textToWrite}\n${dmeContents}`); +}; -const taskDm = (...injectedDefines) => new Task('dm') - .depends('_maps/map_files/generic/**') - .depends('code/**') - .depends('goon/**') - .depends('html/**') - .depends('icons/**') - .depends('interface/**') - .depends(process.platform === 'win32' ? 'auxmos.*' : 'libauxmos.*') - .depends('tgui/public/tgui.html') - .depends('tgui/public/*.bundle.*') - .depends(`${DME_NAME}.dme`) - .provides(`${DME_NAME}.dmb`) - .provides(`${DME_NAME}.rsc`) - .build(async () => { - const dmPath = await (async () => { - // Search in array of paths - const paths = [ - ...((process.env.DM_EXE && process.env.DM_EXE.split(',')) || []), - 'C:\\Program Files\\BYOND\\bin\\dm.exe', - 'C:\\Program Files (x86)\\BYOND\\bin\\dm.exe', - ['reg', 'HKLM\\Software\\Dantom\\BYOND', 'installpath'], - ['reg', 'HKLM\\SOFTWARE\\WOW6432Node\\Dantom\\BYOND', 'installpath'], - ]; - const isFile = path => { - try { - const fstat = stat(path); - return fstat && fstat.isFile(); - } - catch (err) {} - return false; - }; - for (let path of paths) { - // Resolve a registry key - if (Array.isArray(path)) { - const [type, ...args] = path; - path = await regQuery(...args); - } - if (!path) { - continue; - } - // Check if path exists - if (isFile(path)) { - return path; - } - if (isFile(path + '/dm.exe')) { - return path + '/dm.exe'; - } - if (isFile(path + '/bin/dm.exe')) { - return path + '/bin/dm.exe'; - } - } - // Default paths - return ( - process.platform === 'win32' && 'dm.exe' - || 'DreamMaker' - ); - })(); - if (injectedDefines.length) { - const injectedContent = injectedDefines - .map(x => `#define ${x}\n`) - .join('') - // Create mdme file - fs.writeFileSync(`${DME_NAME}.mdme`, injectedContent) - // Add the actual dme content - const dme_content = fs.readFileSync(`${DME_NAME}.dme`) - fs.appendFileSync(`${DME_NAME}.mdme`, dme_content) - await exec(dmPath, [`${DME_NAME}.mdme`]); - // Rename dmb - fs.renameSync(`${DME_NAME}.mdme.dmb`, `${DME_NAME}.dmb`) - // Rename rsc - fs.renameSync(`${DME_NAME}.mdme.rsc`, `${DME_NAME}.rsc`) - // Remove mdme - fs.unlinkSync(`${DME_NAME}.mdme`) - } - else { - await exec(dmPath, [`${DME_NAME}.dme`]); - } - }); +export const TgsTarget = new Juke.Target({ + dependsOn: [TguiTarget, TgFontTarget], + executes: async () => { + Juke.logger.info('Prepending TGS define'); + prependDefines('TGS'); + }, +}); -// Frontend -let tasksToRun = [ - taskYarn, - taskTgfont, - taskTgui, -]; -switch (BUILD_MODE) { - case STANDARD_BUILD: - tasksToRun.push(taskDm('CBT')); - break; - case TGS_BUILD: - tasksToRun.push(taskPrependDefines('TGS')); - break; - case ALL_MAPS_BUILD: - tasksToRun.push(taskDm('CBT','CIBUILDING','CITESTING','ALL_MAPS')); - break; - case TEST_RUN_BUILD: - tasksToRun.push(taskDm('CBT','CIBUILDING')); - break; - case NO_DM_BUILD: - break; - default: - console.error(`Unknown build mode : ${BUILD_MODE}`) - break; -} +const TGS_MODE = process.env.CBT_BUILD_MODE === 'TGS'; -runTasks(tasksToRun); +export default TGS_MODE ? TgsTarget : BuildTarget; diff --git a/tools/build/build.sh b/tools/build/build.sh deleted file mode 100755 index 62372eff2d..0000000000 --- a/tools/build/build.sh +++ /dev/null @@ -1,75 +0,0 @@ -#!/bin/sh - -#Detect OS and use corresponding package manager for dependencies. Currently only works for arch, debian/ubuntu, and RHEL/fedora/CentOS -if [[ -f '/etc/arch-release' ]]; then - echo -ne '\n y' | sudo pacman --needed -Sy base-devel git curl nodejs unzip -fi -if [[ -f '/etc/debian version' ]]; then - sudo dpkg --add-architecture i386 - sudo apt-get update - sudo apt-get install -y build-essential git curl lib32z1 pkg-config libssl-dev:i386 libssl-dev nodejs unzip g++-multilib libc6-i386 libstdc++6:i386 -fi -if [[ -f '/etc/centos-release' ]] || [[ -f '/etc/fedora-release' ]]; then #DNF should work for both of these - sudo dnf --refresh install make automake gcc gcc-c++ kernel-devel git curl unzip glibc-devel.i686 openssl-devel.i686 libgcc.i686 libstdc++-devel.i686 -fi - -cd binaries - -#Install rust if not present -if ! [ -x "$has_cargo" ]; then - echo "Installing rust..." - curl https://sh.rustup.rs -sSf | sh -s -- -y - . ~/.profile -fi - -#Download/update rust-g repo -if [ ! -d "rust-g" ]; then - echo "Cloning rust-g..." - git clone https://github.com/tgstation/rust-g - cd rust-g - ~/.cargo/bin/rustup target add i686-unknown-linux-gnu -else - echo "Fetching rust-g..." - cd rust-g - git fetch - ~/.cargo/bin/rustup target add i686-unknown-linux-gnu -fi - -#Compile and move rust-g binary to repo root -echo "Deploying rust-g..." -git checkout "$RUST_G_VERSION" -env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo build --release --target=i686-unknown-linux-gnu -mv target/i686-unknown-linux-gnu/release/librust_g.so ../../../../librust_g.so -cd .. - -#Download/update auxmos repo -if [ ! -d "auxmos" ]; then - echo "Cloning auxmos..." - git clone https://github.com/Putnam3145/auxmos - cd auxmos - ~/.cargo/bin/rustup target add i686-unknown-linux-gnu -else - echo "Fetching auxmos..." - cd auxmos - git fetch - ~/.cargo/bin/rustup target add i686-unknown-linux-gnu -fi - -#Compile and move auxmos binary to repo root -echo "Deploying auxmos..." -git checkout "$AUXMOS_VERSION" -env PKG_CONFIG_ALLOW_CROSS=1 ~/.cargo/bin/cargo rustc --release --target=i686-unknown-linux-gnu --features all_reaction_hooks,explosive_decompression -- -C target-cpu=native -mv target/i686-unknown-linux-gnu/release/libauxmos.so ../../../../libauxmos.so -cd ../.. - -#Install BYOND -cd ../.. -./tools/ci/install_byond.sh -source $HOME/BYOND/byond/bin/byondsetup - -cd tools/build - -#Build TGUI -set -e -cd "$(dirname "$0")" -exec ../bootstrap/node build.js "$@" diff --git a/tools/build/cbt/fs.js b/tools/build/cbt/fs.js deleted file mode 100644 index 68a5a8bfbf..0000000000 --- a/tools/build/cbt/fs.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -const fs = require('fs'); -const glob = require('./glob'); - -class File { - constructor(path) { - this.path = path; - } - - get stat() { - if (this._stat === undefined) { - this._stat = stat(this.path); - } - return this._stat; - } - - exists() { - return this.stat !== null; - } - - get mtime() { - return this.stat && this.stat.mtime; - } - - touch() { - const time = new Date(); - try { - fs.utimesSync(this.path, time, time); - } - catch (err) { - fs.closeSync(fs.openSync(this.path, 'w')); - } - } -} - -class Glob { - constructor(path) { - this.path = path; - } - - toFiles() { - const paths = glob.sync(this.path, { - strict: false, - silent: true, - }); - return paths - .map(path => new File(path)) - .filter(file => file.exists()); - } -} - -/** - * If true, source is newer than target. - * @param {File[]} sources - * @param {File[]} targets - */ -const compareFiles = (sources, targets) => { - let bestSource = null; - let bestTarget = null; - for (const file of sources) { - if (!bestSource || file.mtime > bestSource.mtime) { - bestSource = file; - } - } - for (const file of targets) { - if (!file.exists()) { - return `target '${file.path}' is missing`; - } - if (!bestTarget || file.mtime < bestTarget.mtime) { - bestTarget = file; - } - } - // Doesn't need rebuild if there is no source, but target exists. - if (!bestSource) { - if (bestTarget) { - return false; - } - return 'no known sources or targets'; - } - // Always needs a rebuild if no targets were specified (e.g. due to GLOB). - if (!bestTarget) { - return 'no targets were specified'; - } - // Needs rebuild if source is newer than target - if (bestSource.mtime > bestTarget.mtime) { - return `source '${bestSource.path}' is newer than target '${bestTarget.path}'`; - } - return false; -}; - -/** - * Returns file stats for the provided path, or null if file is - * not accessible. - */ -const stat = path => { - try { - return fs.statSync(path); - } - catch { - return null; - } -}; - -/** - * Resolves a glob pattern and returns files that are safe - * to call `stat` on. - */ -const resolveGlob = globPath => { - const unsafePaths = glob.sync(globPath, { - strict: false, - silent: true, - }); - const safePaths = []; - for (let path of unsafePaths) { - try { - fs.statSync(path); - safePaths.push(path); - } - catch {} - } - return safePaths; -}; - -module.exports = { - File, - Glob, - compareFiles, - stat, - resolveGlob, -}; diff --git a/tools/build/cbt/glob.js b/tools/build/cbt/glob.js deleted file mode 100644 index df6e153b38..0000000000 --- a/tools/build/cbt/glob.js +++ /dev/null @@ -1,3281 +0,0 @@ -'use strict'; - -var fs = require('fs'); -var path$1 = require('path'); -var util = require('util'); -var events = require('events'); -var assert = require('assert'); - -function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } - -var fs__default = /*#__PURE__*/_interopDefaultLegacy(fs); -var path__default = /*#__PURE__*/_interopDefaultLegacy(path$1); -var util__default = /*#__PURE__*/_interopDefaultLegacy(util); -var events__default = /*#__PURE__*/_interopDefaultLegacy(events); -var assert__default = /*#__PURE__*/_interopDefaultLegacy(assert); - -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - - -var isWindows = process.platform === 'win32'; - - -// JavaScript implementation of realpath, ported from node pre-v6 - -var DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG); - -function rethrow() { - // Only enable in debug mode. A backtrace uses ~1000 bytes of heap space and - // is fairly slow to generate. - var callback; - if (DEBUG) { - var backtrace = new Error; - callback = debugCallback; - } else - callback = missingCallback; - - return callback; - - function debugCallback(err) { - if (err) { - backtrace.message = err.message; - err = backtrace; - missingCallback(err); - } - } - - function missingCallback(err) { - if (err) { - if (process.throwDeprecation) - throw err; // Forgot a callback but don't know where? Use NODE_DEBUG=fs - else if (!process.noDeprecation) { - var msg = 'fs: missing callback ' + (err.stack || err.message); - if (process.traceDeprecation) - console.trace(msg); - else - console.error(msg); - } - } - } -} - -function maybeCallback(cb) { - return typeof cb === 'function' ? cb : rethrow(); -} - -var normalize = path__default['default'].normalize; - -// Regexp that finds the next partion of a (partial) path -// result is [base_with_slash, base], e.g. ['somedir/', 'somedir'] -if (isWindows) { - var nextPartRe = /(.*?)(?:[\/\\]+|$)/g; -} else { - var nextPartRe = /(.*?)(?:[\/]+|$)/g; -} - -// Regex to find the device root, including trailing slash. E.g. 'c:\\'. -if (isWindows) { - var splitRootRe = /^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/; -} else { - var splitRootRe = /^[\/]*/; -} - -var realpathSync = function realpathSync(p, cache) { - // make p is absolute - p = path__default['default'].resolve(p); - - if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { - return cache[p]; - } - - var original = p, - seenLinks = {}, - knownHard = {}; - - // current character position in p - var pos; - // the partial path so far, including a trailing slash if any - var current; - // the partial path without a trailing slash (except when pointing at a root) - var base; - // the partial path scanned in the previous round, with slash - var previous; - - start(); - - function start() { - // Skip over roots - var m = splitRootRe.exec(p); - pos = m[0].length; - current = m[0]; - base = m[0]; - previous = ''; - - // On windows, check that the root exists. On unix there is no need. - if (isWindows && !knownHard[base]) { - fs__default['default'].lstatSync(base); - knownHard[base] = true; - } - } - - // walk down the path, swapping out linked pathparts for their real - // values - // NB: p.length changes. - while (pos < p.length) { - // find the next part - nextPartRe.lastIndex = pos; - var result = nextPartRe.exec(p); - previous = current; - current += result[0]; - base = previous + result[1]; - pos = nextPartRe.lastIndex; - - // continue if not a symlink - if (knownHard[base] || (cache && cache[base] === base)) { - continue; - } - - var resolvedLink; - if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { - // some known symbolic link. no need to stat again. - resolvedLink = cache[base]; - } else { - var stat = fs__default['default'].lstatSync(base); - if (!stat.isSymbolicLink()) { - knownHard[base] = true; - if (cache) cache[base] = base; - continue; - } - - // read the link if it wasn't read before - // dev/ino always return 0 on windows, so skip the check. - var linkTarget = null; - if (!isWindows) { - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); - if (seenLinks.hasOwnProperty(id)) { - linkTarget = seenLinks[id]; - } - } - if (linkTarget === null) { - fs__default['default'].statSync(base); - linkTarget = fs__default['default'].readlinkSync(base); - } - resolvedLink = path__default['default'].resolve(previous, linkTarget); - // track this, if given a cache. - if (cache) cache[base] = resolvedLink; - if (!isWindows) seenLinks[id] = linkTarget; - } - - // resolve the link, then start over - p = path__default['default'].resolve(resolvedLink, p.slice(pos)); - start(); - } - - if (cache) cache[original] = p; - - return p; -}; - - -var realpath = function realpath(p, cache, cb) { - if (typeof cb !== 'function') { - cb = maybeCallback(cache); - cache = null; - } - - // make p is absolute - p = path__default['default'].resolve(p); - - if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { - return process.nextTick(cb.bind(null, null, cache[p])); - } - - var original = p, - seenLinks = {}, - knownHard = {}; - - // current character position in p - var pos; - // the partial path so far, including a trailing slash if any - var current; - // the partial path without a trailing slash (except when pointing at a root) - var base; - // the partial path scanned in the previous round, with slash - var previous; - - start(); - - function start() { - // Skip over roots - var m = splitRootRe.exec(p); - pos = m[0].length; - current = m[0]; - base = m[0]; - previous = ''; - - // On windows, check that the root exists. On unix there is no need. - if (isWindows && !knownHard[base]) { - fs__default['default'].lstat(base, function(err) { - if (err) return cb(err); - knownHard[base] = true; - LOOP(); - }); - } else { - process.nextTick(LOOP); - } - } - - // walk down the path, swapping out linked pathparts for their real - // values - function LOOP() { - // stop if scanned past end of path - if (pos >= p.length) { - if (cache) cache[original] = p; - return cb(null, p); - } - - // find the next part - nextPartRe.lastIndex = pos; - var result = nextPartRe.exec(p); - previous = current; - current += result[0]; - base = previous + result[1]; - pos = nextPartRe.lastIndex; - - // continue if not a symlink - if (knownHard[base] || (cache && cache[base] === base)) { - return process.nextTick(LOOP); - } - - if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { - // known symbolic link. no need to stat again. - return gotResolvedLink(cache[base]); - } - - return fs__default['default'].lstat(base, gotStat); - } - - function gotStat(err, stat) { - if (err) return cb(err); - - // if not a symlink, skip to the next path part - if (!stat.isSymbolicLink()) { - knownHard[base] = true; - if (cache) cache[base] = base; - return process.nextTick(LOOP); - } - - // stat & read the link if not read before - // call gotTarget as soon as the link target is known - // dev/ino always return 0 on windows, so skip the check. - if (!isWindows) { - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); - if (seenLinks.hasOwnProperty(id)) { - return gotTarget(null, seenLinks[id], base); - } - } - fs__default['default'].stat(base, function(err) { - if (err) return cb(err); - - fs__default['default'].readlink(base, function(err, target) { - if (!isWindows) seenLinks[id] = target; - gotTarget(err, target); - }); - }); - } - - function gotTarget(err, target, base) { - if (err) return cb(err); - - var resolvedLink = path__default['default'].resolve(previous, target); - if (cache) cache[base] = resolvedLink; - gotResolvedLink(resolvedLink); - } - - function gotResolvedLink(resolvedLink) { - // resolve the link, then start over - p = path__default['default'].resolve(resolvedLink, p.slice(pos)); - start(); - } -}; - -var old = { - realpathSync: realpathSync, - realpath: realpath -}; - -var fs_realpath = realpath$1; -realpath$1.realpath = realpath$1; -realpath$1.sync = realpathSync$1; -realpath$1.realpathSync = realpathSync$1; -realpath$1.monkeypatch = monkeypatch; -realpath$1.unmonkeypatch = unmonkeypatch; - - -var origRealpath = fs__default['default'].realpath; -var origRealpathSync = fs__default['default'].realpathSync; - -var version = process.version; -var ok = /^v[0-5]\./.test(version); - - -function newError (er) { - return er && er.syscall === 'realpath' && ( - er.code === 'ELOOP' || - er.code === 'ENOMEM' || - er.code === 'ENAMETOOLONG' - ) -} - -function realpath$1 (p, cache, cb) { - if (ok) { - return origRealpath(p, cache, cb) - } - - if (typeof cache === 'function') { - cb = cache; - cache = null; - } - origRealpath(p, cache, function (er, result) { - if (newError(er)) { - old.realpath(p, cache, cb); - } else { - cb(er, result); - } - }); -} - -function realpathSync$1 (p, cache) { - if (ok) { - return origRealpathSync(p, cache) - } - - try { - return origRealpathSync(p, cache) - } catch (er) { - if (newError(er)) { - return old.realpathSync(p, cache) - } else { - throw er - } - } -} - -function monkeypatch () { - fs__default['default'].realpath = realpath$1; - fs__default['default'].realpathSync = realpathSync$1; -} - -function unmonkeypatch () { - fs__default['default'].realpath = origRealpath; - fs__default['default'].realpathSync = origRealpathSync; -} - -var concatMap = function (xs, fn) { - var res = []; - for (var i = 0; i < xs.length; i++) { - var x = fn(xs[i], i); - if (isArray(x)) res.push.apply(res, x); - else res.push(x); - } - return res; -}; - -var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; -}; - -var balancedMatch = balanced; -function balanced(a, b, str) { - if (a instanceof RegExp) a = maybeMatch(a, str); - if (b instanceof RegExp) b = maybeMatch(b, str); - - var r = range(a, b, str); - - return r && { - start: r[0], - end: r[1], - pre: str.slice(0, r[0]), - body: str.slice(r[0] + a.length, r[1]), - post: str.slice(r[1] + b.length) - }; -} - -function maybeMatch(reg, str) { - var m = str.match(reg); - return m ? m[0] : null; -} - -balanced.range = range; -function range(a, b, str) { - var begs, beg, left, right, result; - var ai = str.indexOf(a); - var bi = str.indexOf(b, ai + 1); - var i = ai; - - if (ai >= 0 && bi > 0) { - begs = []; - left = str.length; - - while (i >= 0 && !result) { - if (i == ai) { - begs.push(i); - ai = str.indexOf(a, i + 1); - } else if (begs.length == 1) { - result = [ begs.pop(), bi ]; - } else { - beg = begs.pop(); - if (beg < left) { - left = beg; - right = bi; - } - - bi = str.indexOf(b, i + 1); - } - - i = ai < bi && ai >= 0 ? ai : bi; - } - - if (begs.length) { - result = [ left, right ]; - } - } - - return result; -} - -var braceExpansion = expandTop; - -var escSlash = '\0SLASH'+Math.random()+'\0'; -var escOpen = '\0OPEN'+Math.random()+'\0'; -var escClose = '\0CLOSE'+Math.random()+'\0'; -var escComma = '\0COMMA'+Math.random()+'\0'; -var escPeriod = '\0PERIOD'+Math.random()+'\0'; - -function numeric(str) { - return parseInt(str, 10) == str - ? parseInt(str, 10) - : str.charCodeAt(0); -} - -function escapeBraces(str) { - return str.split('\\\\').join(escSlash) - .split('\\{').join(escOpen) - .split('\\}').join(escClose) - .split('\\,').join(escComma) - .split('\\.').join(escPeriod); -} - -function unescapeBraces(str) { - return str.split(escSlash).join('\\') - .split(escOpen).join('{') - .split(escClose).join('}') - .split(escComma).join(',') - .split(escPeriod).join('.'); -} - - -// Basically just str.split(","), but handling cases -// where we have nested braced sections, which should be -// treated as individual members, like {a,{b,c},d} -function parseCommaParts(str) { - if (!str) - return ['']; - - var parts = []; - var m = balancedMatch('{', '}', str); - - if (!m) - return str.split(','); - - var pre = m.pre; - var body = m.body; - var post = m.post; - var p = pre.split(','); - - p[p.length-1] += '{' + body + '}'; - var postParts = parseCommaParts(post); - if (post.length) { - p[p.length-1] += postParts.shift(); - p.push.apply(p, postParts); - } - - parts.push.apply(parts, p); - - return parts; -} - -function expandTop(str) { - if (!str) - return []; - - // I don't know why Bash 4.3 does this, but it does. - // Anything starting with {} will have the first two bytes preserved - // but *only* at the top level, so {},a}b will not expand to anything, - // but a{},b}c will be expanded to [a}c,abc]. - // One could argue that this is a bug in Bash, but since the goal of - // this module is to match Bash's rules, we escape a leading {} - if (str.substr(0, 2) === '{}') { - str = '\\{\\}' + str.substr(2); - } - - return expand(escapeBraces(str), true).map(unescapeBraces); -} - -function embrace(str) { - return '{' + str + '}'; -} -function isPadded(el) { - return /^-?0\d/.test(el); -} - -function lte(i, y) { - return i <= y; -} -function gte(i, y) { - return i >= y; -} - -function expand(str, isTop) { - var expansions = []; - - var m = balancedMatch('{', '}', str); - if (!m || /\$$/.test(m.pre)) return [str]; - - var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); - var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); - var isSequence = isNumericSequence || isAlphaSequence; - var isOptions = m.body.indexOf(',') >= 0; - if (!isSequence && !isOptions) { - // {a},b} - if (m.post.match(/,.*\}/)) { - str = m.pre + '{' + m.body + escClose + m.post; - return expand(str); - } - return [str]; - } - - var n; - if (isSequence) { - n = m.body.split(/\.\./); - } else { - n = parseCommaParts(m.body); - if (n.length === 1) { - // x{{a,b}}y ==> x{a}y x{b}y - n = expand(n[0], false).map(embrace); - if (n.length === 1) { - var post = m.post.length - ? expand(m.post, false) - : ['']; - return post.map(function(p) { - return m.pre + n[0] + p; - }); - } - } - } - - // at this point, n is the parts, and we know it's not a comma set - // with a single entry. - - // no need to expand pre, since it is guaranteed to be free of brace-sets - var pre = m.pre; - var post = m.post.length - ? expand(m.post, false) - : ['']; - - var N; - - if (isSequence) { - var x = numeric(n[0]); - var y = numeric(n[1]); - var width = Math.max(n[0].length, n[1].length); - var incr = n.length == 3 - ? Math.abs(numeric(n[2])) - : 1; - var test = lte; - var reverse = y < x; - if (reverse) { - incr *= -1; - test = gte; - } - var pad = n.some(isPadded); - - N = []; - - for (var i = x; test(i, y); i += incr) { - var c; - if (isAlphaSequence) { - c = String.fromCharCode(i); - if (c === '\\') - c = ''; - } else { - c = String(i); - if (pad) { - var need = width - c.length; - if (need > 0) { - var z = new Array(need + 1).join('0'); - if (i < 0) - c = '-' + z + c.slice(1); - else - c = z + c; - } - } - } - N.push(c); - } - } else { - N = concatMap(n, function(el) { return expand(el, false) }); - } - - for (var j = 0; j < N.length; j++) { - for (var k = 0; k < post.length; k++) { - var expansion = pre + N[j] + post[k]; - if (!isTop || isSequence || expansion) - expansions.push(expansion); - } - } - - return expansions; -} - -var minimatch_1 = minimatch; -minimatch.Minimatch = Minimatch; - -var path = { sep: '/' }; -try { - path = path__default['default']; -} catch (er) {} - -var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {}; - - -var plTypes = { - '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, - '?': { open: '(?:', close: ')?' }, - '+': { open: '(?:', close: ')+' }, - '*': { open: '(?:', close: ')*' }, - '@': { open: '(?:', close: ')' } -}; - -// any single thing other than / -// don't need to escape / when using new RegExp() -var qmark = '[^/]'; - -// * => any number of characters -var star = qmark + '*?'; - -// ** when dots are allowed. Anything goes, except .. and . -// not (^ or / followed by one or two dots followed by $ or /), -// followed by anything, any number of times. -var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?'; - -// not a ^ or / followed by a dot, -// followed by anything, any number of times. -var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?'; - -// characters that need to be escaped in RegExp. -var reSpecials = charSet('().*{}+?[]^$\\!'); - -// "abc" -> { a:true, b:true, c:true } -function charSet (s) { - return s.split('').reduce(function (set, c) { - set[c] = true; - return set - }, {}) -} - -// normalizes slashes. -var slashSplit = /\/+/; - -minimatch.filter = filter; -function filter (pattern, options) { - options = options || {}; - return function (p, i, list) { - return minimatch(p, pattern, options) - } -} - -function ext (a, b) { - a = a || {}; - b = b || {}; - var t = {}; - Object.keys(b).forEach(function (k) { - t[k] = b[k]; - }); - Object.keys(a).forEach(function (k) { - t[k] = a[k]; - }); - return t -} - -minimatch.defaults = function (def) { - if (!def || !Object.keys(def).length) return minimatch - - var orig = minimatch; - - var m = function minimatch (p, pattern, options) { - return orig.minimatch(p, pattern, ext(def, options)) - }; - - m.Minimatch = function Minimatch (pattern, options) { - return new orig.Minimatch(pattern, ext(def, options)) - }; - - return m -}; - -Minimatch.defaults = function (def) { - if (!def || !Object.keys(def).length) return Minimatch - return minimatch.defaults(def).Minimatch -}; - -function minimatch (p, pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('glob pattern string required') - } - - if (!options) options = {}; - - // shortcut: comments match nothing. - if (!options.nocomment && pattern.charAt(0) === '#') { - return false - } - - // "" only matches "" - if (pattern.trim() === '') return p === '' - - return new Minimatch(pattern, options).match(p) -} - -function Minimatch (pattern, options) { - if (!(this instanceof Minimatch)) { - return new Minimatch(pattern, options) - } - - if (typeof pattern !== 'string') { - throw new TypeError('glob pattern string required') - } - - if (!options) options = {}; - pattern = pattern.trim(); - - // windows support: need to use /, not \ - if (path.sep !== '/') { - pattern = pattern.split(path.sep).join('/'); - } - - this.options = options; - this.set = []; - this.pattern = pattern; - this.regexp = null; - this.negate = false; - this.comment = false; - this.empty = false; - - // make the set of regexps etc. - this.make(); -} - -Minimatch.prototype.debug = function () {}; - -Minimatch.prototype.make = make; -function make () { - // don't do it more than once. - if (this._made) return - - var pattern = this.pattern; - var options = this.options; - - // empty patterns and comments match nothing. - if (!options.nocomment && pattern.charAt(0) === '#') { - this.comment = true; - return - } - if (!pattern) { - this.empty = true; - return - } - - // step 1: figure out negation, etc. - this.parseNegate(); - - // step 2: expand braces - var set = this.globSet = this.braceExpand(); - - if (options.debug) this.debug = console.error; - - this.debug(this.pattern, set); - - // step 3: now we have a set, so turn each one into a series of path-portion - // matching patterns. - // These will be regexps, except in the case of "**", which is - // set to the GLOBSTAR object for globstar behavior, - // and will not contain any / characters - set = this.globParts = set.map(function (s) { - return s.split(slashSplit) - }); - - this.debug(this.pattern, set); - - // glob --> regexps - set = set.map(function (s, si, set) { - return s.map(this.parse, this) - }, this); - - this.debug(this.pattern, set); - - // filter out everything that didn't compile properly. - set = set.filter(function (s) { - return s.indexOf(false) === -1 - }); - - this.debug(this.pattern, set); - - this.set = set; -} - -Minimatch.prototype.parseNegate = parseNegate; -function parseNegate () { - var pattern = this.pattern; - var negate = false; - var options = this.options; - var negateOffset = 0; - - if (options.nonegate) return - - for (var i = 0, l = pattern.length - ; i < l && pattern.charAt(i) === '!' - ; i++) { - negate = !negate; - negateOffset++; - } - - if (negateOffset) this.pattern = pattern.substr(negateOffset); - this.negate = negate; -} - -// Brace expansion: -// a{b,c}d -> abd acd -// a{b,}c -> abc ac -// a{0..3}d -> a0d a1d a2d a3d -// a{b,c{d,e}f}g -> abg acdfg acefg -// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg -// -// Invalid sets are not expanded. -// a{2..}b -> a{2..}b -// a{b}c -> a{b}c -minimatch.braceExpand = function (pattern, options) { - return braceExpand(pattern, options) -}; - -Minimatch.prototype.braceExpand = braceExpand; - -function braceExpand (pattern, options) { - if (!options) { - if (this instanceof Minimatch) { - options = this.options; - } else { - options = {}; - } - } - - pattern = typeof pattern === 'undefined' - ? this.pattern : pattern; - - if (typeof pattern === 'undefined') { - throw new TypeError('undefined pattern') - } - - if (options.nobrace || - !pattern.match(/\{.*\}/)) { - // shortcut. no need to expand. - return [pattern] - } - - return braceExpansion(pattern) -} - -// parse a component of the expanded set. -// At this point, no pattern may contain "/" in it -// so we're going to return a 2d array, where each entry is the full -// pattern, split on '/', and then turned into a regular expression. -// A regexp is made at the end which joins each array with an -// escaped /, and another full one which joins each regexp with |. -// -// Following the lead of Bash 4.1, note that "**" only has special meaning -// when it is the *only* thing in a path portion. Otherwise, any series -// of * is equivalent to a single *. Globstar behavior is enabled by -// default, and can be disabled by setting options.noglobstar. -Minimatch.prototype.parse = parse; -var SUBPARSE = {}; -function parse (pattern, isSub) { - if (pattern.length > 1024 * 64) { - throw new TypeError('pattern is too long') - } - - var options = this.options; - - // shortcuts - if (!options.noglobstar && pattern === '**') return GLOBSTAR - if (pattern === '') return '' - - var re = ''; - var hasMagic = !!options.nocase; - var escaping = false; - // ? => one single character - var patternListStack = []; - var negativeLists = []; - var stateChar; - var inClass = false; - var reClassStart = -1; - var classStart = -1; - // . and .. never match anything that doesn't start with ., - // even when options.dot is set. - var patternStart = pattern.charAt(0) === '.' ? '' // anything - // not (start or / followed by . or .. followed by / or end) - : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' - : '(?!\\.)'; - var self = this; - - function clearStateChar () { - if (stateChar) { - // we had some state-tracking character - // that wasn't consumed by this pass. - switch (stateChar) { - case '*': - re += star; - hasMagic = true; - break - case '?': - re += qmark; - hasMagic = true; - break - default: - re += '\\' + stateChar; - break - } - self.debug('clearStateChar %j %j', stateChar, re); - stateChar = false; - } - } - - for (var i = 0, len = pattern.length, c - ; (i < len) && (c = pattern.charAt(i)) - ; i++) { - this.debug('%s\t%s %s %j', pattern, i, re, c); - - // skip over any that are escaped. - if (escaping && reSpecials[c]) { - re += '\\' + c; - escaping = false; - continue - } - - switch (c) { - case '/': - // completely not allowed, even escaped. - // Should already be path-split by now. - return false - - case '\\': - clearStateChar(); - escaping = true; - continue - - // the various stateChar values - // for the "extglob" stuff. - case '?': - case '*': - case '+': - case '@': - case '!': - this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c); - - // all of those are literals inside a class, except that - // the glob [!a] means [^a] in regexp - if (inClass) { - this.debug(' in class'); - if (c === '!' && i === classStart + 1) c = '^'; - re += c; - continue - } - - // if we already have a stateChar, then it means - // that there was something like ** or +? in there. - // Handle the stateChar, then proceed with this one. - self.debug('call clearStateChar %j', stateChar); - clearStateChar(); - stateChar = c; - // if extglob is disabled, then +(asdf|foo) isn't a thing. - // just clear the statechar *now*, rather than even diving into - // the patternList stuff. - if (options.noext) clearStateChar(); - continue - - case '(': - if (inClass) { - re += '('; - continue - } - - if (!stateChar) { - re += '\\('; - continue - } - - patternListStack.push({ - type: stateChar, - start: i - 1, - reStart: re.length, - open: plTypes[stateChar].open, - close: plTypes[stateChar].close - }); - // negation is (?:(?!js)[^/]*) - re += stateChar === '!' ? '(?:(?!(?:' : '(?:'; - this.debug('plType %j %j', stateChar, re); - stateChar = false; - continue - - case ')': - if (inClass || !patternListStack.length) { - re += '\\)'; - continue - } - - clearStateChar(); - hasMagic = true; - var pl = patternListStack.pop(); - // negation is (?:(?!js)[^/]*) - // The others are (?:) - re += pl.close; - if (pl.type === '!') { - negativeLists.push(pl); - } - pl.reEnd = re.length; - continue - - case '|': - if (inClass || !patternListStack.length || escaping) { - re += '\\|'; - escaping = false; - continue - } - - clearStateChar(); - re += '|'; - continue - - // these are mostly the same in regexp and glob - case '[': - // swallow any state-tracking char before the [ - clearStateChar(); - - if (inClass) { - re += '\\' + c; - continue - } - - inClass = true; - classStart = i; - reClassStart = re.length; - re += c; - continue - - case ']': - // a right bracket shall lose its special - // meaning and represent itself in - // a bracket expression if it occurs - // first in the list. -- POSIX.2 2.8.3.2 - if (i === classStart + 1 || !inClass) { - re += '\\' + c; - escaping = false; - continue - } - - // handle the case where we left a class open. - // "[z-a]" is valid, equivalent to "\[z-a\]" - if (inClass) { - // split where the last [ was, make sure we don't have - // an invalid re. if so, re-walk the contents of the - // would-be class to re-translate any characters that - // were passed through as-is - // TODO: It would probably be faster to determine this - // without a try/catch and a new RegExp, but it's tricky - // to do safely. For now, this is safe and works. - var cs = pattern.substring(classStart + 1, i); - try { - RegExp('[' + cs + ']'); - } catch (er) { - // not a valid class! - var sp = this.parse(cs, SUBPARSE); - re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]'; - hasMagic = hasMagic || sp[1]; - inClass = false; - continue - } - } - - // finish up the class. - hasMagic = true; - inClass = false; - re += c; - continue - - default: - // swallow any state char that wasn't consumed - clearStateChar(); - - if (escaping) { - // no need - escaping = false; - } else if (reSpecials[c] - && !(c === '^' && inClass)) { - re += '\\'; - } - - re += c; - - } // switch - } // for - - // handle the case where we left a class open. - // "[abc" is valid, equivalent to "\[abc" - if (inClass) { - // split where the last [ was, and escape it - // this is a huge pita. We now have to re-walk - // the contents of the would-be class to re-translate - // any characters that were passed through as-is - cs = pattern.substr(classStart + 1); - sp = this.parse(cs, SUBPARSE); - re = re.substr(0, reClassStart) + '\\[' + sp[0]; - hasMagic = hasMagic || sp[1]; - } - - // handle the case where we had a +( thing at the *end* - // of the pattern. - // each pattern list stack adds 3 chars, and we need to go through - // and escape any | chars that were passed through as-is for the regexp. - // Go through and escape them, taking care not to double-escape any - // | chars that were already escaped. - for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { - var tail = re.slice(pl.reStart + pl.open.length); - this.debug('setting tail', re, pl); - // maybe some even number of \, then maybe 1 \, followed by a | - tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { - if (!$2) { - // the | isn't already escaped, so escape it. - $2 = '\\'; - } - - // need to escape all those slashes *again*, without escaping the - // one that we need for escaping the | character. As it works out, - // escaping an even number of slashes can be done by simply repeating - // it exactly after itself. That's why this trick works. - // - // I am sorry that you have to see this. - return $1 + $1 + $2 + '|' - }); - - this.debug('tail=%j\n %s', tail, tail, pl, re); - var t = pl.type === '*' ? star - : pl.type === '?' ? qmark - : '\\' + pl.type; - - hasMagic = true; - re = re.slice(0, pl.reStart) + t + '\\(' + tail; - } - - // handle trailing things that only matter at the very end. - clearStateChar(); - if (escaping) { - // trailing \\ - re += '\\\\'; - } - - // only need to apply the nodot start if the re starts with - // something that could conceivably capture a dot - var addPatternStart = false; - switch (re.charAt(0)) { - case '.': - case '[': - case '(': addPatternStart = true; - } - - // Hack to work around lack of negative lookbehind in JS - // A pattern like: *.!(x).!(y|z) needs to ensure that a name - // like 'a.xyz.yz' doesn't match. So, the first negative - // lookahead, has to look ALL the way ahead, to the end of - // the pattern. - for (var n = negativeLists.length - 1; n > -1; n--) { - var nl = negativeLists[n]; - - var nlBefore = re.slice(0, nl.reStart); - var nlFirst = re.slice(nl.reStart, nl.reEnd - 8); - var nlLast = re.slice(nl.reEnd - 8, nl.reEnd); - var nlAfter = re.slice(nl.reEnd); - - nlLast += nlAfter; - - // Handle nested stuff like *(*.js|!(*.json)), where open parens - // mean that we should *not* include the ) in the bit that is considered - // "after" the negated section. - var openParensBefore = nlBefore.split('(').length - 1; - var cleanAfter = nlAfter; - for (i = 0; i < openParensBefore; i++) { - cleanAfter = cleanAfter.replace(/\)[+*?]?/, ''); - } - nlAfter = cleanAfter; - - var dollar = ''; - if (nlAfter === '' && isSub !== SUBPARSE) { - dollar = '$'; - } - var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast; - re = newRe; - } - - // if the re is not "" at this point, then we need to make sure - // it doesn't match against an empty path part. - // Otherwise a/* will match a/, which it should not. - if (re !== '' && hasMagic) { - re = '(?=.)' + re; - } - - if (addPatternStart) { - re = patternStart + re; - } - - // parsing just a piece of a larger pattern. - if (isSub === SUBPARSE) { - return [re, hasMagic] - } - - // skip the regexp for non-magical patterns - // unescape anything in it, though, so that it'll be - // an exact match against a file etc. - if (!hasMagic) { - return globUnescape(pattern) - } - - var flags = options.nocase ? 'i' : ''; - try { - var regExp = new RegExp('^' + re + '$', flags); - } catch (er) { - // If it was an invalid regular expression, then it can't match - // anything. This trick looks for a character after the end of - // the string, which is of course impossible, except in multi-line - // mode, but it's not a /m regex. - return new RegExp('$.') - } - - regExp._glob = pattern; - regExp._src = re; - - return regExp -} - -minimatch.makeRe = function (pattern, options) { - return new Minimatch(pattern, options || {}).makeRe() -}; - -Minimatch.prototype.makeRe = makeRe; -function makeRe () { - if (this.regexp || this.regexp === false) return this.regexp - - // at this point, this.set is a 2d array of partial - // pattern strings, or "**". - // - // It's better to use .match(). This function shouldn't - // be used, really, but it's pretty convenient sometimes, - // when you just want to work with a regex. - var set = this.set; - - if (!set.length) { - this.regexp = false; - return this.regexp - } - var options = this.options; - - var twoStar = options.noglobstar ? star - : options.dot ? twoStarDot - : twoStarNoDot; - var flags = options.nocase ? 'i' : ''; - - var re = set.map(function (pattern) { - return pattern.map(function (p) { - return (p === GLOBSTAR) ? twoStar - : (typeof p === 'string') ? regExpEscape(p) - : p._src - }).join('\\\/') - }).join('|'); - - // must match entire pattern - // ending in a * or ** will make it less strict. - re = '^(?:' + re + ')$'; - - // can match anything, as long as it's not this. - if (this.negate) re = '^(?!' + re + ').*$'; - - try { - this.regexp = new RegExp(re, flags); - } catch (ex) { - this.regexp = false; - } - return this.regexp -} - -minimatch.match = function (list, pattern, options) { - options = options || {}; - var mm = new Minimatch(pattern, options); - list = list.filter(function (f) { - return mm.match(f) - }); - if (mm.options.nonull && !list.length) { - list.push(pattern); - } - return list -}; - -Minimatch.prototype.match = match; -function match (f, partial) { - this.debug('match', f, this.pattern); - // short-circuit in the case of busted things. - // comments, etc. - if (this.comment) return false - if (this.empty) return f === '' - - if (f === '/' && partial) return true - - var options = this.options; - - // windows: need to use /, not \ - if (path.sep !== '/') { - f = f.split(path.sep).join('/'); - } - - // treat the test path as a set of pathparts. - f = f.split(slashSplit); - this.debug(this.pattern, 'split', f); - - // just ONE of the pattern sets in this.set needs to match - // in order for it to be valid. If negating, then just one - // match means that we have failed. - // Either way, return on the first hit. - - var set = this.set; - this.debug(this.pattern, 'set', set); - - // Find the basename of the path by looking for the last non-empty segment - var filename; - var i; - for (i = f.length - 1; i >= 0; i--) { - filename = f[i]; - if (filename) break - } - - for (i = 0; i < set.length; i++) { - var pattern = set[i]; - var file = f; - if (options.matchBase && pattern.length === 1) { - file = [filename]; - } - var hit = this.matchOne(file, pattern, partial); - if (hit) { - if (options.flipNegate) return true - return !this.negate - } - } - - // didn't get any hits. this is success if it's a negative - // pattern, failure otherwise. - if (options.flipNegate) return false - return this.negate -} - -// set partial to true to test if, for example, -// "/a/b" matches the start of "/*/b/*/d" -// Partial means, if you run out of file before you run -// out of pattern, then that's fine, as long as all -// the parts match. -Minimatch.prototype.matchOne = function (file, pattern, partial) { - var options = this.options; - - this.debug('matchOne', - { 'this': this, file: file, pattern: pattern }); - - this.debug('matchOne', file.length, pattern.length); - - for (var fi = 0, - pi = 0, - fl = file.length, - pl = pattern.length - ; (fi < fl) && (pi < pl) - ; fi++, pi++) { - this.debug('matchOne loop'); - var p = pattern[pi]; - var f = file[fi]; - - this.debug(pattern, p, f); - - // should be impossible. - // some invalid regexp stuff in the set. - if (p === false) return false - - if (p === GLOBSTAR) { - this.debug('GLOBSTAR', [pattern, p, f]); - - // "**" - // a/**/b/**/c would match the following: - // a/b/x/y/z/c - // a/x/y/z/b/c - // a/b/x/b/x/c - // a/b/c - // To do this, take the rest of the pattern after - // the **, and see if it would match the file remainder. - // If so, return success. - // If not, the ** "swallows" a segment, and try again. - // This is recursively awful. - // - // a/**/b/**/c matching a/b/x/y/z/c - // - a matches a - // - doublestar - // - matchOne(b/x/y/z/c, b/**/c) - // - b matches b - // - doublestar - // - matchOne(x/y/z/c, c) -> no - // - matchOne(y/z/c, c) -> no - // - matchOne(z/c, c) -> no - // - matchOne(c, c) yes, hit - var fr = fi; - var pr = pi + 1; - if (pr === pl) { - this.debug('** at the end'); - // a ** at the end will just swallow the rest. - // We have found a match. - // however, it will not swallow /.x, unless - // options.dot is set. - // . and .. are *never* matched by **, for explosively - // exponential reasons. - for (; fi < fl; fi++) { - if (file[fi] === '.' || file[fi] === '..' || - (!options.dot && file[fi].charAt(0) === '.')) return false - } - return true - } - - // ok, let's see if we can swallow whatever we can. - while (fr < fl) { - var swallowee = file[fr]; - - this.debug('\nglobstar while', file, fr, pattern, pr, swallowee); - - // XXX remove this slice. Just pass the start index. - if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { - this.debug('globstar found match!', fr, fl, swallowee); - // found a match. - return true - } else { - // can't swallow "." or ".." ever. - // can only swallow ".foo" when explicitly asked. - if (swallowee === '.' || swallowee === '..' || - (!options.dot && swallowee.charAt(0) === '.')) { - this.debug('dot detected!', file, fr, pattern, pr); - break - } - - // ** swallows a segment, and continue. - this.debug('globstar swallow a segment, and continue'); - fr++; - } - } - - // no match was found. - // However, in partial mode, we can't say this is necessarily over. - // If there's more *pattern* left, then - if (partial) { - // ran out of file - this.debug('\n>>> no match, partial?', file, fr, pattern, pr); - if (fr === fl) return true - } - return false - } - - // something other than ** - // non-magic patterns just have to match exactly - // patterns with magic have been turned into regexps. - var hit; - if (typeof p === 'string') { - if (options.nocase) { - hit = f.toLowerCase() === p.toLowerCase(); - } else { - hit = f === p; - } - this.debug('string match', p, f, hit); - } else { - hit = f.match(p); - this.debug('pattern match', p, f, hit); - } - - if (!hit) return false - } - - // Note: ending in / means that we'll get a final "" - // at the end of the pattern. This can only match a - // corresponding "" at the end of the file. - // If the file ends in /, then it can only match a - // a pattern that ends in /, unless the pattern just - // doesn't have any more for it. But, a/b/ should *not* - // match "a/b/*", even though "" matches against the - // [^/]*? pattern, except in partial mode, where it might - // simply not be reached yet. - // However, a/b/ should still satisfy a/* - - // now either we fell off the end of the pattern, or we're done. - if (fi === fl && pi === pl) { - // ran out of pattern and filename at the same time. - // an exact hit! - return true - } else if (fi === fl) { - // ran out of file, but still had pattern left. - // this is ok if we're doing the match as part of - // a glob fs traversal. - return partial - } else if (pi === pl) { - // ran out of pattern, still have file left. - // this is only acceptable if we're on the very last - // empty segment of a file with a trailing slash. - // a/* should match a/b/ - var emptyFileEnd = (fi === fl - 1) && (file[fi] === ''); - return emptyFileEnd - } - - // should be unreachable. - throw new Error('wtf?') -}; - -// replace stuff like \* with * -function globUnescape (s) { - return s.replace(/\\(.)/g, '$1') -} - -function regExpEscape (s) { - return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') -} - -function createCommonjsModule(fn, module) { - return module = { exports: {} }, fn(module, module.exports), module.exports; -} - -var inherits_browser = createCommonjsModule(function (module) { -if (typeof Object.create === 'function') { - // implementation from standard node.js 'util' module - module.exports = function inherits(ctor, superCtor) { - if (superCtor) { - ctor.super_ = superCtor; - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - } - }; -} else { - // old school shim for old browsers - module.exports = function inherits(ctor, superCtor) { - if (superCtor) { - ctor.super_ = superCtor; - var TempCtor = function () {}; - TempCtor.prototype = superCtor.prototype; - ctor.prototype = new TempCtor(); - ctor.prototype.constructor = ctor; - } - }; -} -}); - -var inherits = createCommonjsModule(function (module) { -try { - var util = util__default['default']; - /* istanbul ignore next */ - if (typeof util.inherits !== 'function') throw ''; - module.exports = util.inherits; -} catch (e) { - /* istanbul ignore next */ - module.exports = inherits_browser; -} -}); - -function posix(path) { - return path.charAt(0) === '/'; -} - -function win32(path) { - // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 - var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; - var result = splitDeviceRe.exec(path); - var device = result[1] || ''; - var isUnc = Boolean(device && device.charAt(1) !== ':'); - - // UNC paths are always absolute - return Boolean(result[2] || isUnc); -} - -var pathIsAbsolute = process.platform === 'win32' ? win32 : posix; -var posix_1 = posix; -var win32_1 = win32; -pathIsAbsolute.posix = posix_1; -pathIsAbsolute.win32 = win32_1; - -var alphasort_1 = alphasort; -var alphasorti_1 = alphasorti; -var setopts_1 = setopts; -var ownProp_1 = ownProp; -var makeAbs_1 = makeAbs; -var finish_1 = finish; -var mark_1 = mark; -var isIgnored_1 = isIgnored; -var childrenIgnored_1 = childrenIgnored; - -function ownProp (obj, field) { - return Object.prototype.hasOwnProperty.call(obj, field) -} - - - - -var Minimatch$1 = minimatch_1.Minimatch; - -function alphasorti (a, b) { - return a.toLowerCase().localeCompare(b.toLowerCase()) -} - -function alphasort (a, b) { - return a.localeCompare(b) -} - -function setupIgnores (self, options) { - self.ignore = options.ignore || []; - - if (!Array.isArray(self.ignore)) - self.ignore = [self.ignore]; - - if (self.ignore.length) { - self.ignore = self.ignore.map(ignoreMap); - } -} - -// ignore patterns are always in dot:true mode. -function ignoreMap (pattern) { - var gmatcher = null; - if (pattern.slice(-3) === '/**') { - var gpattern = pattern.replace(/(\/\*\*)+$/, ''); - gmatcher = new Minimatch$1(gpattern, { dot: true }); - } - - return { - matcher: new Minimatch$1(pattern, { dot: true }), - gmatcher: gmatcher - } -} - -function setopts (self, pattern, options) { - if (!options) - options = {}; - - // base-matching: just use globstar for that. - if (options.matchBase && -1 === pattern.indexOf("/")) { - if (options.noglobstar) { - throw new Error("base matching requires globstar") - } - pattern = "**/" + pattern; - } - - self.silent = !!options.silent; - self.pattern = pattern; - self.strict = options.strict !== false; - self.realpath = !!options.realpath; - self.realpathCache = options.realpathCache || Object.create(null); - self.follow = !!options.follow; - self.dot = !!options.dot; - self.mark = !!options.mark; - self.nodir = !!options.nodir; - if (self.nodir) - self.mark = true; - self.sync = !!options.sync; - self.nounique = !!options.nounique; - self.nonull = !!options.nonull; - self.nosort = !!options.nosort; - self.nocase = !!options.nocase; - self.stat = !!options.stat; - self.noprocess = !!options.noprocess; - self.absolute = !!options.absolute; - - self.maxLength = options.maxLength || Infinity; - self.cache = options.cache || Object.create(null); - self.statCache = options.statCache || Object.create(null); - self.symlinks = options.symlinks || Object.create(null); - - setupIgnores(self, options); - - self.changedCwd = false; - var cwd = process.cwd(); - if (!ownProp(options, "cwd")) - self.cwd = cwd; - else { - self.cwd = path__default['default'].resolve(options.cwd); - self.changedCwd = self.cwd !== cwd; - } - - self.root = options.root || path__default['default'].resolve(self.cwd, "/"); - self.root = path__default['default'].resolve(self.root); - if (process.platform === "win32") - self.root = self.root.replace(/\\/g, "/"); - - // TODO: is an absolute `cwd` supposed to be resolved against `root`? - // e.g. { cwd: '/test', root: __dirname } === path.join(__dirname, '/test') - self.cwdAbs = pathIsAbsolute(self.cwd) ? self.cwd : makeAbs(self, self.cwd); - if (process.platform === "win32") - self.cwdAbs = self.cwdAbs.replace(/\\/g, "/"); - self.nomount = !!options.nomount; - - // disable comments and negation in Minimatch. - // Note that they are not supported in Glob itself anyway. - options.nonegate = true; - options.nocomment = true; - - self.minimatch = new Minimatch$1(pattern, options); - self.options = self.minimatch.options; -} - -function finish (self) { - var nou = self.nounique; - var all = nou ? [] : Object.create(null); - - for (var i = 0, l = self.matches.length; i < l; i ++) { - var matches = self.matches[i]; - if (!matches || Object.keys(matches).length === 0) { - if (self.nonull) { - // do like the shell, and spit out the literal glob - var literal = self.minimatch.globSet[i]; - if (nou) - all.push(literal); - else - all[literal] = true; - } - } else { - // had matches - var m = Object.keys(matches); - if (nou) - all.push.apply(all, m); - else - m.forEach(function (m) { - all[m] = true; - }); - } - } - - if (!nou) - all = Object.keys(all); - - if (!self.nosort) - all = all.sort(self.nocase ? alphasorti : alphasort); - - // at *some* point we statted all of these - if (self.mark) { - for (var i = 0; i < all.length; i++) { - all[i] = self._mark(all[i]); - } - if (self.nodir) { - all = all.filter(function (e) { - var notDir = !(/\/$/.test(e)); - var c = self.cache[e] || self.cache[makeAbs(self, e)]; - if (notDir && c) - notDir = c !== 'DIR' && !Array.isArray(c); - return notDir - }); - } - } - - if (self.ignore.length) - all = all.filter(function(m) { - return !isIgnored(self, m) - }); - - self.found = all; -} - -function mark (self, p) { - var abs = makeAbs(self, p); - var c = self.cache[abs]; - var m = p; - if (c) { - var isDir = c === 'DIR' || Array.isArray(c); - var slash = p.slice(-1) === '/'; - - if (isDir && !slash) - m += '/'; - else if (!isDir && slash) - m = m.slice(0, -1); - - if (m !== p) { - var mabs = makeAbs(self, m); - self.statCache[mabs] = self.statCache[abs]; - self.cache[mabs] = self.cache[abs]; - } - } - - return m -} - -// lotta situps... -function makeAbs (self, f) { - var abs = f; - if (f.charAt(0) === '/') { - abs = path__default['default'].join(self.root, f); - } else if (pathIsAbsolute(f) || f === '') { - abs = f; - } else if (self.changedCwd) { - abs = path__default['default'].resolve(self.cwd, f); - } else { - abs = path__default['default'].resolve(f); - } - - if (process.platform === 'win32') - abs = abs.replace(/\\/g, '/'); - - return abs -} - - -// Return true, if pattern ends with globstar '**', for the accompanying parent directory. -// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents -function isIgnored (self, path) { - if (!self.ignore.length) - return false - - return self.ignore.some(function(item) { - return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) - }) -} - -function childrenIgnored (self, path) { - if (!self.ignore.length) - return false - - return self.ignore.some(function(item) { - return !!(item.gmatcher && item.gmatcher.match(path)) - }) -} - -var common = { - alphasort: alphasort_1, - alphasorti: alphasorti_1, - setopts: setopts_1, - ownProp: ownProp_1, - makeAbs: makeAbs_1, - finish: finish_1, - mark: mark_1, - isIgnored: isIgnored_1, - childrenIgnored: childrenIgnored_1 -}; - -var sync = globSync; -globSync.GlobSync = GlobSync; -var setopts$1 = common.setopts; -var ownProp$1 = common.ownProp; -var childrenIgnored$1 = common.childrenIgnored; -var isIgnored$1 = common.isIgnored; - -function globSync (pattern, options) { - if (typeof options === 'function' || arguments.length === 3) - throw new TypeError('callback provided to sync glob\n'+ - 'See: https://github.com/isaacs/node-glob/issues/167') - - return new GlobSync(pattern, options).found -} - -function GlobSync (pattern, options) { - if (!pattern) - throw new Error('must provide pattern') - - if (typeof options === 'function' || arguments.length === 3) - throw new TypeError('callback provided to sync glob\n'+ - 'See: https://github.com/isaacs/node-glob/issues/167') - - if (!(this instanceof GlobSync)) - return new GlobSync(pattern, options) - - setopts$1(this, pattern, options); - - if (this.noprocess) - return this - - var n = this.minimatch.set.length; - this.matches = new Array(n); - for (var i = 0; i < n; i ++) { - this._process(this.minimatch.set[i], i, false); - } - this._finish(); -} - -GlobSync.prototype._finish = function () { - assert__default['default'](this instanceof GlobSync); - if (this.realpath) { - var self = this; - this.matches.forEach(function (matchset, index) { - var set = self.matches[index] = Object.create(null); - for (var p in matchset) { - try { - p = self._makeAbs(p); - var real = fs_realpath.realpathSync(p, self.realpathCache); - set[real] = true; - } catch (er) { - if (er.syscall === 'stat') - set[self._makeAbs(p)] = true; - else - throw er - } - } - }); - } - common.finish(this); -}; - - -GlobSync.prototype._process = function (pattern, index, inGlobStar) { - assert__default['default'](this instanceof GlobSync); - - // Get the first [n] parts of pattern that are all strings. - var n = 0; - while (typeof pattern[n] === 'string') { - n ++; - } - // now n is the index of the first one that is *not* a string. - - // See if there's anything else - var prefix; - switch (n) { - // if not, then this is rather simple - case pattern.length: - this._processSimple(pattern.join('/'), index); - return - - case 0: - // pattern *starts* with some non-trivial item. - // going to readdir(cwd), but not include the prefix in matches. - prefix = null; - break - - default: - // pattern has some string bits in the front. - // whatever it starts with, whether that's 'absolute' like /foo/bar, - // or 'relative' like '../baz' - prefix = pattern.slice(0, n).join('/'); - break - } - - var remain = pattern.slice(n); - - // get the list of entries. - var read; - if (prefix === null) - read = '.'; - else if (pathIsAbsolute(prefix) || pathIsAbsolute(pattern.join('/'))) { - if (!prefix || !pathIsAbsolute(prefix)) - prefix = '/' + prefix; - read = prefix; - } else - read = prefix; - - var abs = this._makeAbs(read); - - //if ignored, skip processing - if (childrenIgnored$1(this, read)) - return - - var isGlobStar = remain[0] === minimatch_1.GLOBSTAR; - if (isGlobStar) - this._processGlobStar(prefix, read, abs, remain, index, inGlobStar); - else - this._processReaddir(prefix, read, abs, remain, index, inGlobStar); -}; - - -GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { - var entries = this._readdir(abs, inGlobStar); - - // if the abs isn't a dir, then nothing can match! - if (!entries) - return - - // It will only match dot entries if it starts with a dot, or if - // dot is set. Stuff like @(.foo|.bar) isn't allowed. - var pn = remain[0]; - var negate = !!this.minimatch.negate; - var rawGlob = pn._glob; - var dotOk = this.dot || rawGlob.charAt(0) === '.'; - - var matchedEntries = []; - for (var i = 0; i < entries.length; i++) { - var e = entries[i]; - if (e.charAt(0) !== '.' || dotOk) { - var m; - if (negate && !prefix) { - m = !e.match(pn); - } else { - m = e.match(pn); - } - if (m) - matchedEntries.push(e); - } - } - - var len = matchedEntries.length; - // If there are no matched entries, then nothing matches. - if (len === 0) - return - - // if this is the last remaining pattern bit, then no need for - // an additional stat *unless* the user has specified mark or - // stat explicitly. We know they exist, since readdir returned - // them. - - if (remain.length === 1 && !this.mark && !this.stat) { - if (!this.matches[index]) - this.matches[index] = Object.create(null); - - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i]; - if (prefix) { - if (prefix.slice(-1) !== '/') - e = prefix + '/' + e; - else - e = prefix + e; - } - - if (e.charAt(0) === '/' && !this.nomount) { - e = path__default['default'].join(this.root, e); - } - this._emitMatch(index, e); - } - // This was the last one, and no stats were needed - return - } - - // now test all matched entries as stand-ins for that part - // of the pattern. - remain.shift(); - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i]; - var newPattern; - if (prefix) - newPattern = [prefix, e]; - else - newPattern = [e]; - this._process(newPattern.concat(remain), index, inGlobStar); - } -}; - - -GlobSync.prototype._emitMatch = function (index, e) { - if (isIgnored$1(this, e)) - return - - var abs = this._makeAbs(e); - - if (this.mark) - e = this._mark(e); - - if (this.absolute) { - e = abs; - } - - if (this.matches[index][e]) - return - - if (this.nodir) { - var c = this.cache[abs]; - if (c === 'DIR' || Array.isArray(c)) - return - } - - this.matches[index][e] = true; - - if (this.stat) - this._stat(e); -}; - - -GlobSync.prototype._readdirInGlobStar = function (abs) { - // follow all symlinked directories forever - // just proceed as if this is a non-globstar situation - if (this.follow) - return this._readdir(abs, false) - - var entries; - var lstat; - try { - lstat = fs__default['default'].lstatSync(abs); - } catch (er) { - if (er.code === 'ENOENT') { - // lstat failed, doesn't exist - return null - } - } - - var isSym = lstat && lstat.isSymbolicLink(); - this.symlinks[abs] = isSym; - - // If it's not a symlink or a dir, then it's definitely a regular file. - // don't bother doing a readdir in that case. - if (!isSym && lstat && !lstat.isDirectory()) - this.cache[abs] = 'FILE'; - else - entries = this._readdir(abs, false); - - return entries -}; - -GlobSync.prototype._readdir = function (abs, inGlobStar) { - - if (inGlobStar && !ownProp$1(this.symlinks, abs)) - return this._readdirInGlobStar(abs) - - if (ownProp$1(this.cache, abs)) { - var c = this.cache[abs]; - if (!c || c === 'FILE') - return null - - if (Array.isArray(c)) - return c - } - - try { - return this._readdirEntries(abs, fs__default['default'].readdirSync(abs)) - } catch (er) { - this._readdirError(abs, er); - return null - } -}; - -GlobSync.prototype._readdirEntries = function (abs, entries) { - // if we haven't asked to stat everything, then just - // assume that everything in there exists, so we can avoid - // having to stat it a second time. - if (!this.mark && !this.stat) { - for (var i = 0; i < entries.length; i ++) { - var e = entries[i]; - if (abs === '/') - e = abs + e; - else - e = abs + '/' + e; - this.cache[e] = true; - } - } - - this.cache[abs] = entries; - - // mark and cache dir-ness - return entries -}; - -GlobSync.prototype._readdirError = function (f, er) { - // handle errors, and cache the information - switch (er.code) { - case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 - case 'ENOTDIR': // totally normal. means it *does* exist. - var abs = this._makeAbs(f); - this.cache[abs] = 'FILE'; - if (abs === this.cwdAbs) { - var error = new Error(er.code + ' invalid cwd ' + this.cwd); - error.path = this.cwd; - error.code = er.code; - throw error - } - break - - case 'ENOENT': // not terribly unusual - case 'ELOOP': - case 'ENAMETOOLONG': - case 'UNKNOWN': - this.cache[this._makeAbs(f)] = false; - break - - default: // some unusual error. Treat as failure. - this.cache[this._makeAbs(f)] = false; - if (this.strict) - throw er - if (!this.silent) - console.error('glob error', er); - break - } -}; - -GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { - - var entries = this._readdir(abs, inGlobStar); - - // no entries means not a dir, so it can never have matches - // foo.txt/** doesn't match foo.txt - if (!entries) - return - - // test without the globstar, and with every child both below - // and replacing the globstar. - var remainWithoutGlobStar = remain.slice(1); - var gspref = prefix ? [ prefix ] : []; - var noGlobStar = gspref.concat(remainWithoutGlobStar); - - // the noGlobStar pattern exits the inGlobStar state - this._process(noGlobStar, index, false); - - var len = entries.length; - var isSym = this.symlinks[abs]; - - // If it's a symlink, and we're in a globstar, then stop - if (isSym && inGlobStar) - return - - for (var i = 0; i < len; i++) { - var e = entries[i]; - if (e.charAt(0) === '.' && !this.dot) - continue - - // these two cases enter the inGlobStar state - var instead = gspref.concat(entries[i], remainWithoutGlobStar); - this._process(instead, index, true); - - var below = gspref.concat(entries[i], remain); - this._process(below, index, true); - } -}; - -GlobSync.prototype._processSimple = function (prefix, index) { - // XXX review this. Shouldn't it be doing the mounting etc - // before doing stat? kinda weird? - var exists = this._stat(prefix); - - if (!this.matches[index]) - this.matches[index] = Object.create(null); - - // If it doesn't exist, then just mark the lack of results - if (!exists) - return - - if (prefix && pathIsAbsolute(prefix) && !this.nomount) { - var trail = /[\/\\]$/.test(prefix); - if (prefix.charAt(0) === '/') { - prefix = path__default['default'].join(this.root, prefix); - } else { - prefix = path__default['default'].resolve(this.root, prefix); - if (trail) - prefix += '/'; - } - } - - if (process.platform === 'win32') - prefix = prefix.replace(/\\/g, '/'); - - // Mark this as a match - this._emitMatch(index, prefix); -}; - -// Returns either 'DIR', 'FILE', or false -GlobSync.prototype._stat = function (f) { - var abs = this._makeAbs(f); - var needDir = f.slice(-1) === '/'; - - if (f.length > this.maxLength) - return false - - if (!this.stat && ownProp$1(this.cache, abs)) { - var c = this.cache[abs]; - - if (Array.isArray(c)) - c = 'DIR'; - - // It exists, but maybe not how we need it - if (!needDir || c === 'DIR') - return c - - if (needDir && c === 'FILE') - return false - - // otherwise we have to stat, because maybe c=true - // if we know it exists, but not what it is. - } - var stat = this.statCache[abs]; - if (!stat) { - var lstat; - try { - lstat = fs__default['default'].lstatSync(abs); - } catch (er) { - if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { - this.statCache[abs] = false; - return false - } - } - - if (lstat && lstat.isSymbolicLink()) { - try { - stat = fs__default['default'].statSync(abs); - } catch (er) { - stat = lstat; - } - } else { - stat = lstat; - } - } - - this.statCache[abs] = stat; - - var c = true; - if (stat) - c = stat.isDirectory() ? 'DIR' : 'FILE'; - - this.cache[abs] = this.cache[abs] || c; - - if (needDir && c === 'FILE') - return false - - return c -}; - -GlobSync.prototype._mark = function (p) { - return common.mark(this, p) -}; - -GlobSync.prototype._makeAbs = function (f) { - return common.makeAbs(this, f) -}; - -// Returns a wrapper function that returns a wrapped callback -// The wrapper function should do some stuff, and return a -// presumably different callback function. -// This makes sure that own properties are retained, so that -// decorations and such are not lost along the way. -var wrappy_1 = wrappy; -function wrappy (fn, cb) { - if (fn && cb) return wrappy(fn)(cb) - - if (typeof fn !== 'function') - throw new TypeError('need wrapper function') - - Object.keys(fn).forEach(function (k) { - wrapper[k] = fn[k]; - }); - - return wrapper - - function wrapper() { - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - var ret = fn.apply(this, args); - var cb = args[args.length-1]; - if (typeof ret === 'function' && ret !== cb) { - Object.keys(cb).forEach(function (k) { - ret[k] = cb[k]; - }); - } - return ret - } -} - -var once_1 = wrappy_1(once); -var strict = wrappy_1(onceStrict); - -once.proto = once(function () { - Object.defineProperty(Function.prototype, 'once', { - value: function () { - return once(this) - }, - configurable: true - }); - - Object.defineProperty(Function.prototype, 'onceStrict', { - value: function () { - return onceStrict(this) - }, - configurable: true - }); -}); - -function once (fn) { - var f = function () { - if (f.called) return f.value - f.called = true; - return f.value = fn.apply(this, arguments) - }; - f.called = false; - return f -} - -function onceStrict (fn) { - var f = function () { - if (f.called) - throw new Error(f.onceError) - f.called = true; - return f.value = fn.apply(this, arguments) - }; - var name = fn.name || 'Function wrapped with `once`'; - f.onceError = name + " shouldn't be called more than once"; - f.called = false; - return f -} -once_1.strict = strict; - -var reqs = Object.create(null); - - -var inflight_1 = wrappy_1(inflight); - -function inflight (key, cb) { - if (reqs[key]) { - reqs[key].push(cb); - return null - } else { - reqs[key] = [cb]; - return makeres(key) - } -} - -function makeres (key) { - return once_1(function RES () { - var cbs = reqs[key]; - var len = cbs.length; - var args = slice(arguments); - - // XXX It's somewhat ambiguous whether a new callback added in this - // pass should be queued for later execution if something in the - // list of callbacks throws, or if it should just be discarded. - // However, it's such an edge case that it hardly matters, and either - // choice is likely as surprising as the other. - // As it happens, we do go ahead and schedule it for later execution. - try { - for (var i = 0; i < len; i++) { - cbs[i].apply(null, args); - } - } finally { - if (cbs.length > len) { - // added more in the interim. - // de-zalgo, just in case, but don't call again. - cbs.splice(0, len); - process.nextTick(function () { - RES.apply(null, args); - }); - } else { - delete reqs[key]; - } - } - }) -} - -function slice (args) { - var length = args.length; - var array = []; - - for (var i = 0; i < length; i++) array[i] = args[i]; - return array -} - -// Approach: -// -// 1. Get the minimatch set -// 2. For each pattern in the set, PROCESS(pattern, false) -// 3. Store matches per-set, then uniq them -// -// PROCESS(pattern, inGlobStar) -// Get the first [n] items from pattern that are all strings -// Join these together. This is PREFIX. -// If there is no more remaining, then stat(PREFIX) and -// add to matches if it succeeds. END. -// -// If inGlobStar and PREFIX is symlink and points to dir -// set ENTRIES = [] -// else readdir(PREFIX) as ENTRIES -// If fail, END -// -// with ENTRIES -// If pattern[n] is GLOBSTAR -// // handle the case where the globstar match is empty -// // by pruning it out, and testing the resulting pattern -// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) -// // handle other cases. -// for ENTRY in ENTRIES (not dotfiles) -// // attach globstar + tail onto the entry -// // Mark that this entry is a globstar match -// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) -// -// else // not globstar -// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) -// Test ENTRY against pattern[n] -// If fails, continue -// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) -// -// Caveat: -// Cache all stats and readdirs results to minimize syscall. Since all -// we ever care about is existence and directory-ness, we can just keep -// `true` for files, and [children,...] for directories, or `false` for -// things that don't exist. - -var glob_1 = glob; - -var EE = events__default['default'].EventEmitter; -var setopts$2 = common.setopts; -var ownProp$2 = common.ownProp; - - -var childrenIgnored$2 = common.childrenIgnored; -var isIgnored$2 = common.isIgnored; - - - -function glob (pattern, options, cb) { - if (typeof options === 'function') cb = options, options = {}; - if (!options) options = {}; - - if (options.sync) { - if (cb) - throw new TypeError('callback provided to sync glob') - return sync(pattern, options) - } - - return new Glob(pattern, options, cb) -} - -glob.sync = sync; -var GlobSync$1 = glob.GlobSync = sync.GlobSync; - -// old api surface -glob.glob = glob; - -function extend (origin, add) { - if (add === null || typeof add !== 'object') { - return origin - } - - var keys = Object.keys(add); - var i = keys.length; - while (i--) { - origin[keys[i]] = add[keys[i]]; - } - return origin -} - -glob.hasMagic = function (pattern, options_) { - var options = extend({}, options_); - options.noprocess = true; - - var g = new Glob(pattern, options); - var set = g.minimatch.set; - - if (!pattern) - return false - - if (set.length > 1) - return true - - for (var j = 0; j < set[0].length; j++) { - if (typeof set[0][j] !== 'string') - return true - } - - return false -}; - -glob.Glob = Glob; -inherits(Glob, EE); -function Glob (pattern, options, cb) { - if (typeof options === 'function') { - cb = options; - options = null; - } - - if (options && options.sync) { - if (cb) - throw new TypeError('callback provided to sync glob') - return new GlobSync$1(pattern, options) - } - - if (!(this instanceof Glob)) - return new Glob(pattern, options, cb) - - setopts$2(this, pattern, options); - this._didRealPath = false; - - // process each pattern in the minimatch set - var n = this.minimatch.set.length; - - // The matches are stored as {: true,...} so that - // duplicates are automagically pruned. - // Later, we do an Object.keys() on these. - // Keep them as a list so we can fill in when nonull is set. - this.matches = new Array(n); - - if (typeof cb === 'function') { - cb = once_1(cb); - this.on('error', cb); - this.on('end', function (matches) { - cb(null, matches); - }); - } - - var self = this; - this._processing = 0; - - this._emitQueue = []; - this._processQueue = []; - this.paused = false; - - if (this.noprocess) - return this - - if (n === 0) - return done() - - var sync = true; - for (var i = 0; i < n; i ++) { - this._process(this.minimatch.set[i], i, false, done); - } - sync = false; - - function done () { - --self._processing; - if (self._processing <= 0) { - if (sync) { - process.nextTick(function () { - self._finish(); - }); - } else { - self._finish(); - } - } - } -} - -Glob.prototype._finish = function () { - assert__default['default'](this instanceof Glob); - if (this.aborted) - return - - if (this.realpath && !this._didRealpath) - return this._realpath() - - common.finish(this); - this.emit('end', this.found); -}; - -Glob.prototype._realpath = function () { - if (this._didRealpath) - return - - this._didRealpath = true; - - var n = this.matches.length; - if (n === 0) - return this._finish() - - var self = this; - for (var i = 0; i < this.matches.length; i++) - this._realpathSet(i, next); - - function next () { - if (--n === 0) - self._finish(); - } -}; - -Glob.prototype._realpathSet = function (index, cb) { - var matchset = this.matches[index]; - if (!matchset) - return cb() - - var found = Object.keys(matchset); - var self = this; - var n = found.length; - - if (n === 0) - return cb() - - var set = this.matches[index] = Object.create(null); - found.forEach(function (p, i) { - // If there's a problem with the stat, then it means that - // one or more of the links in the realpath couldn't be - // resolved. just return the abs value in that case. - p = self._makeAbs(p); - fs_realpath.realpath(p, self.realpathCache, function (er, real) { - if (!er) - set[real] = true; - else if (er.syscall === 'stat') - set[p] = true; - else - self.emit('error', er); // srsly wtf right here - - if (--n === 0) { - self.matches[index] = set; - cb(); - } - }); - }); -}; - -Glob.prototype._mark = function (p) { - return common.mark(this, p) -}; - -Glob.prototype._makeAbs = function (f) { - return common.makeAbs(this, f) -}; - -Glob.prototype.abort = function () { - this.aborted = true; - this.emit('abort'); -}; - -Glob.prototype.pause = function () { - if (!this.paused) { - this.paused = true; - this.emit('pause'); - } -}; - -Glob.prototype.resume = function () { - if (this.paused) { - this.emit('resume'); - this.paused = false; - if (this._emitQueue.length) { - var eq = this._emitQueue.slice(0); - this._emitQueue.length = 0; - for (var i = 0; i < eq.length; i ++) { - var e = eq[i]; - this._emitMatch(e[0], e[1]); - } - } - if (this._processQueue.length) { - var pq = this._processQueue.slice(0); - this._processQueue.length = 0; - for (var i = 0; i < pq.length; i ++) { - var p = pq[i]; - this._processing--; - this._process(p[0], p[1], p[2], p[3]); - } - } - } -}; - -Glob.prototype._process = function (pattern, index, inGlobStar, cb) { - assert__default['default'](this instanceof Glob); - assert__default['default'](typeof cb === 'function'); - - if (this.aborted) - return - - this._processing++; - if (this.paused) { - this._processQueue.push([pattern, index, inGlobStar, cb]); - return - } - - //console.error('PROCESS %d', this._processing, pattern) - - // Get the first [n] parts of pattern that are all strings. - var n = 0; - while (typeof pattern[n] === 'string') { - n ++; - } - // now n is the index of the first one that is *not* a string. - - // see if there's anything else - var prefix; - switch (n) { - // if not, then this is rather simple - case pattern.length: - this._processSimple(pattern.join('/'), index, cb); - return - - case 0: - // pattern *starts* with some non-trivial item. - // going to readdir(cwd), but not include the prefix in matches. - prefix = null; - break - - default: - // pattern has some string bits in the front. - // whatever it starts with, whether that's 'absolute' like /foo/bar, - // or 'relative' like '../baz' - prefix = pattern.slice(0, n).join('/'); - break - } - - var remain = pattern.slice(n); - - // get the list of entries. - var read; - if (prefix === null) - read = '.'; - else if (pathIsAbsolute(prefix) || pathIsAbsolute(pattern.join('/'))) { - if (!prefix || !pathIsAbsolute(prefix)) - prefix = '/' + prefix; - read = prefix; - } else - read = prefix; - - var abs = this._makeAbs(read); - - //if ignored, skip _processing - if (childrenIgnored$2(this, read)) - return cb() - - var isGlobStar = remain[0] === minimatch_1.GLOBSTAR; - if (isGlobStar) - this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb); - else - this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb); -}; - -Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { - var self = this; - this._readdir(abs, inGlobStar, function (er, entries) { - return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) - }); -}; - -Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { - - // if the abs isn't a dir, then nothing can match! - if (!entries) - return cb() - - // It will only match dot entries if it starts with a dot, or if - // dot is set. Stuff like @(.foo|.bar) isn't allowed. - var pn = remain[0]; - var negate = !!this.minimatch.negate; - var rawGlob = pn._glob; - var dotOk = this.dot || rawGlob.charAt(0) === '.'; - - var matchedEntries = []; - for (var i = 0; i < entries.length; i++) { - var e = entries[i]; - if (e.charAt(0) !== '.' || dotOk) { - var m; - if (negate && !prefix) { - m = !e.match(pn); - } else { - m = e.match(pn); - } - if (m) - matchedEntries.push(e); - } - } - - //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) - - var len = matchedEntries.length; - // If there are no matched entries, then nothing matches. - if (len === 0) - return cb() - - // if this is the last remaining pattern bit, then no need for - // an additional stat *unless* the user has specified mark or - // stat explicitly. We know they exist, since readdir returned - // them. - - if (remain.length === 1 && !this.mark && !this.stat) { - if (!this.matches[index]) - this.matches[index] = Object.create(null); - - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i]; - if (prefix) { - if (prefix !== '/') - e = prefix + '/' + e; - else - e = prefix + e; - } - - if (e.charAt(0) === '/' && !this.nomount) { - e = path__default['default'].join(this.root, e); - } - this._emitMatch(index, e); - } - // This was the last one, and no stats were needed - return cb() - } - - // now test all matched entries as stand-ins for that part - // of the pattern. - remain.shift(); - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i]; - if (prefix) { - if (prefix !== '/') - e = prefix + '/' + e; - else - e = prefix + e; - } - this._process([e].concat(remain), index, inGlobStar, cb); - } - cb(); -}; - -Glob.prototype._emitMatch = function (index, e) { - if (this.aborted) - return - - if (isIgnored$2(this, e)) - return - - if (this.paused) { - this._emitQueue.push([index, e]); - return - } - - var abs = pathIsAbsolute(e) ? e : this._makeAbs(e); - - if (this.mark) - e = this._mark(e); - - if (this.absolute) - e = abs; - - if (this.matches[index][e]) - return - - if (this.nodir) { - var c = this.cache[abs]; - if (c === 'DIR' || Array.isArray(c)) - return - } - - this.matches[index][e] = true; - - var st = this.statCache[abs]; - if (st) - this.emit('stat', e, st); - - this.emit('match', e); -}; - -Glob.prototype._readdirInGlobStar = function (abs, cb) { - if (this.aborted) - return - - // follow all symlinked directories forever - // just proceed as if this is a non-globstar situation - if (this.follow) - return this._readdir(abs, false, cb) - - var lstatkey = 'lstat\0' + abs; - var self = this; - var lstatcb = inflight_1(lstatkey, lstatcb_); - - if (lstatcb) - fs__default['default'].lstat(abs, lstatcb); - - function lstatcb_ (er, lstat) { - if (er && er.code === 'ENOENT') - return cb() - - var isSym = lstat && lstat.isSymbolicLink(); - self.symlinks[abs] = isSym; - - // If it's not a symlink or a dir, then it's definitely a regular file. - // don't bother doing a readdir in that case. - if (!isSym && lstat && !lstat.isDirectory()) { - self.cache[abs] = 'FILE'; - cb(); - } else - self._readdir(abs, false, cb); - } -}; - -Glob.prototype._readdir = function (abs, inGlobStar, cb) { - if (this.aborted) - return - - cb = inflight_1('readdir\0'+abs+'\0'+inGlobStar, cb); - if (!cb) - return - - //console.error('RD %j %j', +inGlobStar, abs) - if (inGlobStar && !ownProp$2(this.symlinks, abs)) - return this._readdirInGlobStar(abs, cb) - - if (ownProp$2(this.cache, abs)) { - var c = this.cache[abs]; - if (!c || c === 'FILE') - return cb() - - if (Array.isArray(c)) - return cb(null, c) - } - fs__default['default'].readdir(abs, readdirCb(this, abs, cb)); -}; - -function readdirCb (self, abs, cb) { - return function (er, entries) { - if (er) - self._readdirError(abs, er, cb); - else - self._readdirEntries(abs, entries, cb); - } -} - -Glob.prototype._readdirEntries = function (abs, entries, cb) { - if (this.aborted) - return - - // if we haven't asked to stat everything, then just - // assume that everything in there exists, so we can avoid - // having to stat it a second time. - if (!this.mark && !this.stat) { - for (var i = 0; i < entries.length; i ++) { - var e = entries[i]; - if (abs === '/') - e = abs + e; - else - e = abs + '/' + e; - this.cache[e] = true; - } - } - - this.cache[abs] = entries; - return cb(null, entries) -}; - -Glob.prototype._readdirError = function (f, er, cb) { - if (this.aborted) - return - - // handle errors, and cache the information - switch (er.code) { - case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 - case 'ENOTDIR': // totally normal. means it *does* exist. - var abs = this._makeAbs(f); - this.cache[abs] = 'FILE'; - if (abs === this.cwdAbs) { - var error = new Error(er.code + ' invalid cwd ' + this.cwd); - error.path = this.cwd; - error.code = er.code; - this.emit('error', error); - this.abort(); - } - break - - case 'ENOENT': // not terribly unusual - case 'ELOOP': - case 'ENAMETOOLONG': - case 'UNKNOWN': - this.cache[this._makeAbs(f)] = false; - break - - default: // some unusual error. Treat as failure. - this.cache[this._makeAbs(f)] = false; - if (this.strict) { - this.emit('error', er); - // If the error is handled, then we abort - // if not, we threw out of here - this.abort(); - } - if (!this.silent) - console.error('glob error', er); - break - } - - return cb() -}; - -Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { - var self = this; - this._readdir(abs, inGlobStar, function (er, entries) { - self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb); - }); -}; - - -Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { - //console.error('pgs2', prefix, remain[0], entries) - - // no entries means not a dir, so it can never have matches - // foo.txt/** doesn't match foo.txt - if (!entries) - return cb() - - // test without the globstar, and with every child both below - // and replacing the globstar. - var remainWithoutGlobStar = remain.slice(1); - var gspref = prefix ? [ prefix ] : []; - var noGlobStar = gspref.concat(remainWithoutGlobStar); - - // the noGlobStar pattern exits the inGlobStar state - this._process(noGlobStar, index, false, cb); - - var isSym = this.symlinks[abs]; - var len = entries.length; - - // If it's a symlink, and we're in a globstar, then stop - if (isSym && inGlobStar) - return cb() - - for (var i = 0; i < len; i++) { - var e = entries[i]; - if (e.charAt(0) === '.' && !this.dot) - continue - - // these two cases enter the inGlobStar state - var instead = gspref.concat(entries[i], remainWithoutGlobStar); - this._process(instead, index, true, cb); - - var below = gspref.concat(entries[i], remain); - this._process(below, index, true, cb); - } - - cb(); -}; - -Glob.prototype._processSimple = function (prefix, index, cb) { - // XXX review this. Shouldn't it be doing the mounting etc - // before doing stat? kinda weird? - var self = this; - this._stat(prefix, function (er, exists) { - self._processSimple2(prefix, index, er, exists, cb); - }); -}; -Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { - - //console.error('ps2', prefix, exists) - - if (!this.matches[index]) - this.matches[index] = Object.create(null); - - // If it doesn't exist, then just mark the lack of results - if (!exists) - return cb() - - if (prefix && pathIsAbsolute(prefix) && !this.nomount) { - var trail = /[\/\\]$/.test(prefix); - if (prefix.charAt(0) === '/') { - prefix = path__default['default'].join(this.root, prefix); - } else { - prefix = path__default['default'].resolve(this.root, prefix); - if (trail) - prefix += '/'; - } - } - - if (process.platform === 'win32') - prefix = prefix.replace(/\\/g, '/'); - - // Mark this as a match - this._emitMatch(index, prefix); - cb(); -}; - -// Returns either 'DIR', 'FILE', or false -Glob.prototype._stat = function (f, cb) { - var abs = this._makeAbs(f); - var needDir = f.slice(-1) === '/'; - - if (f.length > this.maxLength) - return cb() - - if (!this.stat && ownProp$2(this.cache, abs)) { - var c = this.cache[abs]; - - if (Array.isArray(c)) - c = 'DIR'; - - // It exists, but maybe not how we need it - if (!needDir || c === 'DIR') - return cb(null, c) - - if (needDir && c === 'FILE') - return cb() - - // otherwise we have to stat, because maybe c=true - // if we know it exists, but not what it is. - } - var stat = this.statCache[abs]; - if (stat !== undefined) { - if (stat === false) - return cb(null, stat) - else { - var type = stat.isDirectory() ? 'DIR' : 'FILE'; - if (needDir && type === 'FILE') - return cb() - else - return cb(null, type, stat) - } - } - - var self = this; - var statcb = inflight_1('stat\0' + abs, lstatcb_); - if (statcb) - fs__default['default'].lstat(abs, statcb); - - function lstatcb_ (er, lstat) { - if (lstat && lstat.isSymbolicLink()) { - // If it's a symlink, then treat it as the target, unless - // the target does not exist, then treat it as a file. - return fs__default['default'].stat(abs, function (er, stat) { - if (er) - self._stat2(f, abs, null, lstat, cb); - else - self._stat2(f, abs, er, stat, cb); - }) - } else { - self._stat2(f, abs, er, lstat, cb); - } - } -}; - -Glob.prototype._stat2 = function (f, abs, er, stat, cb) { - if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { - this.statCache[abs] = false; - return cb() - } - - var needDir = f.slice(-1) === '/'; - this.statCache[abs] = stat; - - if (abs.slice(-1) === '/' && stat && !stat.isDirectory()) - return cb(null, false, stat) - - var c = true; - if (stat) - c = stat.isDirectory() ? 'DIR' : 'FILE'; - this.cache[abs] = this.cache[abs] || c; - - if (needDir && c === 'FILE') - return cb() - - return cb(null, c, stat) -}; - -var glob$1 = glob_1; - -module.exports = glob$1; diff --git a/tools/build/cbt/process.js b/tools/build/cbt/process.js deleted file mode 100644 index ecd150b8d4..0000000000 --- a/tools/build/cbt/process.js +++ /dev/null @@ -1,102 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -const { spawn } = require('child_process'); -const { resolve: resolvePath } = require('path'); -const { stat } = require('./fs'); - -/** - * @type {import('child_process').ChildProcessWithoutNullStreams} - */ -const children = new Set(); - -const killChildren = () => { - for (const child of children) { - child.kill('SIGTERM'); - children.delete(child); - console.log('killed child process'); - } -}; - -const trap = (signals, handler) => { - let readline; - if (process.platform === 'win32') { - readline = require('readline').createInterface({ - input: process.stdin, - output: process.stdout, - }); - } - for (const signal of signals) { - const handleSignal = () => handler(signal); - if (signal === 'EXIT') { - process.on('exit', handleSignal); - continue; - } - if (readline) { - readline.on('SIG' + signal, handleSignal); - } - process.on('SIG' + signal, handleSignal); - } -}; - -trap(['EXIT', 'BREAK', 'HUP', 'INT', 'TERM'], signal => { - if (signal !== 'EXIT') { - console.log('Received', signal); - } - killChildren(); - if (signal !== 'EXIT') { - process.exit(1); - } -}); - -const exceptionHandler = err => { - console.log(err); - killChildren(); - process.exit(1); -}; - -process.on('unhandledRejection', exceptionHandler); -process.on('uncaughtException', exceptionHandler); - -class ExitError extends Error {} - -/** - * @param {string} executable - * @param {string[]} args - * @param {import('child_process').SpawnOptionsWithoutStdio} options - */ -const exec = (executable, args, options) => { - return new Promise((resolve, reject) => { - // If executable exists relative to the current directory, - // use that executable, otherwise spawn should fall back to - // running it from PATH. - if (stat(executable)) { - executable = resolvePath(executable); - } - const child = spawn(executable, args, options); - children.add(child); - child.stdout.pipe(process.stdout, { end: false }); - child.stderr.pipe(process.stderr, { end: false }); - child.stdin.end(); - child.on('error', err => reject(err)); - child.on('exit', code => { - children.delete(child); - if (code !== 0) { - const error = new ExitError('Process exited with code: ' + code); - error.code = code; - reject(error); - } - else { - resolve(code); - } - }); - }); -}; - -module.exports = { - exec, - ExitError, -}; diff --git a/tools/build/cbt/task.js b/tools/build/cbt/task.js deleted file mode 100644 index f0782bfe1e..0000000000 --- a/tools/build/cbt/task.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * @file - * @copyright 2020 Aleksej Komarov - * @license MIT - */ - -const { compareFiles, Glob, File } = require('./fs'); - -class Task { - constructor(name) { - this.name = name; - this.sources = []; - this.targets = []; - this.script = null; - } - - depends(path) { - if (path.includes('*')) { - this.sources.push(new Glob(path)); - } - else { - this.sources.push(new File(path)); - } - return this; - } - - provides(path) { - if (path.includes('*')) { - this.targets.push(new Glob(path)); - } - else { - this.targets.push(new File(path)); - } - return this; - } - - build(script) { - this.script = script; - return this; - } - - async run() { - /** - * @returns {File[]} - */ - const getFiles = files => files - .flatMap(file => { - if (file instanceof Glob) { - return file.toFiles(); - } - if (file instanceof File) { - return file; - } - }) - .filter(Boolean); - // Normalize all our dependencies by converting globs to files - const fileSources = getFiles(this.sources); - const fileTargets = getFiles(this.targets); - // Consider dependencies first, and skip the task if it - // doesn't need a rebuild. - let needsRebuild = 'no targets'; - if (fileTargets.length > 0) { - needsRebuild = compareFiles(fileSources, fileTargets); - if (!needsRebuild) { - console.warn(` => Skipping '${this.name}' (up to date)`); - return; - } - } - if (!this.script) { - return; - } - if (process.env.DEBUG && needsRebuild) { - console.debug(` Reason: ${needsRebuild}`); - } - console.warn(` => Starting '${this.name}'`); - const startedAt = Date.now(); - // Run the script - await this.script(); - // Touch all targets so that they don't rebuild again - if (fileTargets.length > 0) { - for (const file of fileTargets) { - file.touch(); - } - } - const time = ((Date.now() - startedAt) / 1000) + 's'; - console.warn(` => Finished '${this.name}' in ${time}`); - } -} - -const runTasks = async tasks => { - const startedAt = Date.now(); - // Run all if none of the tasks were specified in command line - const runAll = !tasks.some(task => process.argv.includes(task.name)); - for (const task of tasks) { - if (runAll || process.argv.includes(task.name)) { - await task.run(); - } - } - const time = ((Date.now() - startedAt) / 1000) + 's'; - console.log(` => Done in ${time}`); - process.exit(); -}; - -module.exports = { - Task, - runTasks, -}; diff --git a/tools/build/juke/index.d.ts b/tools/build/juke/index.d.ts new file mode 100644 index 0000000000..cdc30ea577 --- /dev/null +++ b/tools/build/juke/index.d.ts @@ -0,0 +1,248 @@ +// Generated by dts-bundle-generator v5.9.0 + +/// + +import _chalk from 'chalk'; +import { SpawnOptionsWithoutStdio } from 'child_process'; +import EventEmitter from 'events'; + +/** + * Change the current working directory of the Node.js process. + * + * Second argument is a file (or directory), relative to which chdir will be + * performed. This is usually `import.meta.url`. + */ +export declare const chdir: (directory: string, relativeTo?: string | undefined) => void; +export declare const logger: { + log: (...args: unknown[]) => void; + error: (...args: unknown[]) => void; + action: (...args: unknown[]) => void; + warn: (...args: unknown[]) => void; + info: (...args: unknown[]) => void; + debug: (...args: unknown[]) => void; +}; +export declare type ParameterType = (string | string[] | number | number[] | boolean | boolean[]); +export declare type StringType = ("string" | "string[]" | "number" | "number[]" | "boolean" | "boolean[]"); +export declare type TypeByString = (T extends "string" ? string : T extends "string[]" ? string[] : T extends "number" ? number : T extends "number[]" ? number[] : T extends "boolean" ? boolean : T extends "boolean[]" ? boolean[] : never); +export declare type ParameterConfig = { + /** + * Parameter name, as it would be used in CLI. + */ + name?: string; + /** + * Parameter type, one of: + * - `string` + * - `string[]` + * - `number` + * - `number[]` + * - `boolean` + * - `boolean[]` + */ + type: T; + /** + * Short flag for use in CLI, can only be a single character. + */ + alias?: string; +}; +export interface Parameter { + type: StringType; + name?: string; + alias?: string; + __internalType?: T; + isString(): this is Parameter; + isNumber(): this is Parameter; + isBoolean(): this is Parameter; + isArray(): this is Parameter; + toKebabCase(): string | undefined; + toConstCase(): string | undefined; + toCamelCase(): string | undefined; +} +export declare type ParameterCtor = { + new (config: ParameterConfig): Parameter>; +}; +export declare const Parameter: ParameterCtor; +export declare type ParameterCreator = (config: ParameterConfig) => Parameter>; +export declare const createParameter: ParameterCreator; +export declare type ExecutionContext = { + /** Get parameter value. */ + get: (parameter: Parameter) => (T extends Array ? T : T | null); + args: string[]; +}; +export declare type BooleanLike = boolean | null | undefined; +export declare type WithExecutionContext = (context: ExecutionContext) => R | Promise; +export declare type WithOptionalExecutionContext = R | WithExecutionContext; +export declare type DependsOn = WithOptionalExecutionContext<(Target | BooleanLike)[]>; +export declare type ExecutesFn = WithExecutionContext; +export declare type OnlyWhenFn = WithExecutionContext; +export declare type FileIo = WithOptionalExecutionContext<(string | BooleanLike)[]>; +export declare type TargetConfig = { + /** + * Target name. This parameter is required. + */ + name?: string; + /** + * Dependencies for this target. They will be ran before executing this + * target, and may run in parallel. + */ + dependsOn?: DependsOn; + /** + * Function that is delegated to the execution engine for building this + * target. It is normally an async function, which accepts a single + * argument - execution context (contains `get` for interacting with + * parameters). + * + * @example + * executes: async ({ get }) => { + * console.log(get(Parameter)); + * }, + */ + executes?: ExecutesFn; + /** + * Files that are consumed by this target. + */ + inputs?: FileIo; + /** + * Files that are produced by this target. Additionally, they are also + * touched every time target finishes executing in order to stop + * this target from re-running. + */ + outputs?: FileIo; + /** + * Parameters that are local to this task. Can be retrieved via `get` + * in the executor function. + */ + parameters?: Parameter[]; + /** + * Target will run only when this function returns true. It accepts a + * single argument - execution context. + */ + onlyWhen?: OnlyWhenFn; +}; +export declare class Target { + name?: string; + dependsOn: DependsOn; + executes?: ExecutesFn; + inputs: FileIo; + outputs: FileIo; + parameters: Parameter[]; + onlyWhen?: OnlyWhenFn; + constructor(target: TargetConfig); +} +export declare type TargetCreator = (target: TargetConfig) => Target; +export declare const createTarget: TargetCreator; +export declare type RunnerConfig = { + targets?: Target[]; + default?: Target; + parameters?: Parameter[]; + singleTarget?: boolean; +}; +export declare const runner: { + config: RunnerConfig; + targets: Target[]; + parameters: Parameter[]; + workers: Worker[]; + configure(config: RunnerConfig): void; + start(): Promise; +}; +declare class Worker { + readonly target: Target; + readonly context: ExecutionContext; + readonly dependsOn: Target[]; + dependencies: Set; + generator?: AsyncGenerator; + emitter: EventEmitter; + hasFailed: boolean; + constructor(target: Target, context: ExecutionContext, dependsOn: Target[]); + resolveDependency(target: Target): void; + rejectDependency(target: Target): void; + start(): void; + onFinish(fn: () => void): void; + onFail(fn: () => void): void; + private debugLog; + private process; +} +export declare class ExitCode extends Error { + code: number | null; + signal: string | null; + constructor(code: number | null, signal?: string | null); +} +export declare type ExecOptions = SpawnOptionsWithoutStdio & { + /** + * If `true`, this exec call will not pipe its output to stdio. + * @default false + */ + silent?: boolean; + /** + * Throw an exception on non-zero exit code. + * @default true + */ + throw?: boolean; +}; +export declare type ExecReturn = { + /** Exit code of the program. */ + code: number | null; + /** Signal received by the program which caused it to exit. */ + signal: NodeJS.Signals | null; + /** Output collected from `stdout` */ + stdout: string; + /** Output collected from `stderr` */ + stderr: string; + /** A combined output collected from `stdout` and `stderr`. */ + combined: string; +}; +export declare const exec: (executable: string, args?: string[], options?: ExecOptions) => Promise; +/** + * Unix style pathname pattern expansion. + * + * Perform a search matching a specified pattern according to the rules of + * the `glob` npm package. Path can be either absolute or relative, and can + * contain shell-style wildcards. Broken symlinks are included in the results + * (as in the shell). Whether or not the results are sorted depends on the + * file system. + * + * @returns A possibly empty list of file paths. + */ +export declare const glob: (globPath: string) => string[]; +export declare type RmOptions = { + /** + * If true, perform a recursive directory removal. + */ + recursive?: boolean; + /** + * If true, exceptions will be ignored if file or directory does not exist. + */ + force?: boolean; +}; +/** + * Removes files and directories (synchronously). Supports globs. + */ +export declare const rm: (path: string, options?: RmOptions) => void; +export declare const chalk: _chalk.Chalk & _chalk.ChalkFunction & { + supportsColor: false | _chalk.ColorSupport; + Level: _chalk.Level; + Color: ("black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "grey" | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright") | ("bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgGrey" | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright"); + ForegroundColor: "black" | "red" | "green" | "yellow" | "blue" | "magenta" | "cyan" | "white" | "gray" | "grey" | "blackBright" | "redBright" | "greenBright" | "yellowBright" | "blueBright" | "magentaBright" | "cyanBright" | "whiteBright"; + BackgroundColor: "bgBlack" | "bgRed" | "bgGreen" | "bgYellow" | "bgBlue" | "bgMagenta" | "bgCyan" | "bgWhite" | "bgGray" | "bgGrey" | "bgBlackBright" | "bgRedBright" | "bgGreenBright" | "bgYellowBright" | "bgBlueBright" | "bgMagentaBright" | "bgCyanBright" | "bgWhiteBright"; + Modifiers: "bold" | "reset" | "dim" | "italic" | "underline" | "inverse" | "hidden" | "strikethrough" | "visible"; + stderr: _chalk.Chalk & { + supportsColor: false | _chalk.ColorSupport; + }; +}; +export declare type SetupConfig = { + file: string; + /** + * If true, CLI will only accept a single target to run and will receive all + * passed arguments as is (not only flags). + */ + singleTarget?: boolean; +}; +/** + * Configures Juke Build and starts executing targets. + * + * @param config Juke Build configuration. + * @returns Exit code of the whole runner process. + */ +export declare const setup: (config: SetupConfig) => Promise; +export declare const sleep: (time: number) => Promise; + +export {}; diff --git a/tools/build/juke/index.js b/tools/build/juke/index.js new file mode 100644 index 0000000000..4ee8fbf0bf --- /dev/null +++ b/tools/build/juke/index.js @@ -0,0 +1,5128 @@ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __defProps = Object.defineProperties; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropDescs = Object.getOwnPropertyDescriptors; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getOwnPropSymbols = Object.getOwnPropertySymbols; +var __getProtoOf = Object.getPrototypeOf; +var __hasOwnProp = Object.prototype.hasOwnProperty; +var __propIsEnum = Object.prototype.propertyIsEnumerable; +var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; +var __spreadValues = (a, b) => { + for (var prop in b || (b = {})) + if (__hasOwnProp.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + if (__getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(b)) { + if (__propIsEnum.call(b, prop)) + __defNormalProp(a, prop, b[prop]); + } + return a; +}; +var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); +var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); +var __objRest = (source, exclude) => { + var target = {}; + for (var prop in source) + if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0) + target[prop] = source[prop]; + if (source != null && __getOwnPropSymbols) + for (var prop of __getOwnPropSymbols(source)) { + if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop)) + target[prop] = source[prop]; + } + return target; +}; +var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; +}; +var __export = (target, all) => { + __markAsModule(target); + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); +}; +var __reExport = (target, module2, desc) => { + if (module2 && typeof module2 === "object" || typeof module2 === "function") { + for (let key of __getOwnPropNames(module2)) + if (!__hasOwnProp.call(target, key) && key !== "default") + __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); + } + return target; +}; +var __toModule = (module2) => { + return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); +}; + +// pnp:color-name-npm-1.1.4-025792b0ea-b044585952.zip/node_modules/color-name/index.js +var require_color_name = __commonJS({ + "pnp:color-name-npm-1.1.4-025792b0ea-b044585952.zip/node_modules/color-name/index.js"(exports, module2) { + "use strict"; + module2.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] + }; + } +}); + +// pnp:color-convert-npm-2.0.1-79730e935b-79e6bdb9fd.zip/node_modules/color-convert/conversions.js +var require_conversions = __commonJS({ + "pnp:color-convert-npm-2.0.1-79730e935b-79e6bdb9fd.zip/node_modules/color-convert/conversions.js"(exports, module2) { + var cssKeywords = require_color_name(); + var reverseKeywords = {}; + for (const key of Object.keys(cssKeywords)) { + reverseKeywords[cssKeywords[key]] = key; + } + var convert = { + rgb: { channels: 3, labels: "rgb" }, + hsl: { channels: 3, labels: "hsl" }, + hsv: { channels: 3, labels: "hsv" }, + hwb: { channels: 3, labels: "hwb" }, + cmyk: { channels: 4, labels: "cmyk" }, + xyz: { channels: 3, labels: "xyz" }, + lab: { channels: 3, labels: "lab" }, + lch: { channels: 3, labels: "lch" }, + hex: { channels: 1, labels: ["hex"] }, + keyword: { channels: 1, labels: ["keyword"] }, + ansi16: { channels: 1, labels: ["ansi16"] }, + ansi256: { channels: 1, labels: ["ansi256"] }, + hcg: { channels: 3, labels: ["h", "c", "g"] }, + apple: { channels: 3, labels: ["r16", "g16", "b16"] }, + gray: { channels: 1, labels: ["gray"] } + }; + module2.exports = convert; + for (const model of Object.keys(convert)) { + if (!("channels" in convert[model])) { + throw new Error("missing channels property: " + model); + } + if (!("labels" in convert[model])) { + throw new Error("missing channel labels property: " + model); + } + if (convert[model].labels.length !== convert[model].channels) { + throw new Error("channel and label counts mismatch: " + model); + } + const { channels, labels } = convert[model]; + delete convert[model].channels; + delete convert[model].labels; + Object.defineProperty(convert[model], "channels", { value: channels }); + Object.defineProperty(convert[model], "labels", { value: labels }); + } + convert.rgb.hsl = function(rgb) { + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const min = Math.min(r, g, b); + const max = Math.max(r, g, b); + const delta = max - min; + let h; + let s; + if (max === min) { + h = 0; + } else if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else if (b === max) { + h = 4 + (r - g) / delta; + } + h = Math.min(h * 60, 360); + if (h < 0) { + h += 360; + } + const l = (min + max) / 2; + if (max === min) { + s = 0; + } else if (l <= 0.5) { + s = delta / (max + min); + } else { + s = delta / (2 - max - min); + } + return [h, s * 100, l * 100]; + }; + convert.rgb.hsv = function(rgb) { + let rdif; + let gdif; + let bdif; + let h; + let s; + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const v = Math.max(r, g, b); + const diff = v - Math.min(r, g, b); + const diffc = function(c) { + return (v - c) / 6 / diff + 1 / 2; + }; + if (diff === 0) { + h = 0; + s = 0; + } else { + s = diff / v; + rdif = diffc(r); + gdif = diffc(g); + bdif = diffc(b); + if (r === v) { + h = bdif - gdif; + } else if (g === v) { + h = 1 / 3 + rdif - bdif; + } else if (b === v) { + h = 2 / 3 + gdif - rdif; + } + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + } + return [ + h * 360, + s * 100, + v * 100 + ]; + }; + convert.rgb.hwb = function(rgb) { + const r = rgb[0]; + const g = rgb[1]; + let b = rgb[2]; + const h = convert.rgb.hsl(rgb)[0]; + const w = 1 / 255 * Math.min(r, Math.min(g, b)); + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + return [h, w * 100, b * 100]; + }; + convert.rgb.cmyk = function(rgb) { + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const k = Math.min(1 - r, 1 - g, 1 - b); + const c = (1 - r - k) / (1 - k) || 0; + const m = (1 - g - k) / (1 - k) || 0; + const y = (1 - b - k) / (1 - k) || 0; + return [c * 100, m * 100, y * 100, k * 100]; + }; + function comparativeDistance(x, y) { + return (x[0] - y[0]) ** 2 + (x[1] - y[1]) ** 2 + (x[2] - y[2]) ** 2; + } + convert.rgb.keyword = function(rgb) { + const reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } + let currentClosestDistance = Infinity; + let currentClosestKeyword; + for (const keyword of Object.keys(cssKeywords)) { + const value = cssKeywords[keyword]; + const distance = comparativeDistance(rgb, value); + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + return currentClosestKeyword; + }; + convert.keyword.rgb = function(keyword) { + return cssKeywords[keyword]; + }; + convert.rgb.xyz = function(rgb) { + let r = rgb[0] / 255; + let g = rgb[1] / 255; + let b = rgb[2] / 255; + r = r > 0.04045 ? ((r + 0.055) / 1.055) ** 2.4 : r / 12.92; + g = g > 0.04045 ? ((g + 0.055) / 1.055) ** 2.4 : g / 12.92; + b = b > 0.04045 ? ((b + 0.055) / 1.055) ** 2.4 : b / 12.92; + const x = r * 0.4124 + g * 0.3576 + b * 0.1805; + const y = r * 0.2126 + g * 0.7152 + b * 0.0722; + const z = r * 0.0193 + g * 0.1192 + b * 0.9505; + return [x * 100, y * 100, z * 100]; + }; + convert.rgb.lab = function(rgb) { + const xyz = convert.rgb.xyz(rgb); + let x = xyz[0]; + let y = xyz[1]; + let z = xyz[2]; + x /= 95.047; + y /= 100; + z /= 108.883; + x = x > 8856e-6 ? x ** (1 / 3) : 7.787 * x + 16 / 116; + y = y > 8856e-6 ? y ** (1 / 3) : 7.787 * y + 16 / 116; + z = z > 8856e-6 ? z ** (1 / 3) : 7.787 * z + 16 / 116; + const l = 116 * y - 16; + const a = 500 * (x - y); + const b = 200 * (y - z); + return [l, a, b]; + }; + convert.hsl.rgb = function(hsl) { + const h = hsl[0] / 360; + const s = hsl[1] / 100; + const l = hsl[2] / 100; + let t2; + let t3; + let val; + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + const t1 = 2 * l - t2; + const rgb = [0, 0, 0]; + for (let i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + rgb[i] = val * 255; + } + return rgb; + }; + convert.hsl.hsv = function(hsl) { + const h = hsl[0]; + let s = hsl[1] / 100; + let l = hsl[2] / 100; + let smin = s; + const lmin = Math.max(l, 0.01); + l *= 2; + s *= l <= 1 ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + const v = (l + s) / 2; + const sv = l === 0 ? 2 * smin / (lmin + smin) : 2 * s / (l + s); + return [h, sv * 100, v * 100]; + }; + convert.hsv.rgb = function(hsv) { + const h = hsv[0] / 60; + const s = hsv[1] / 100; + let v = hsv[2] / 100; + const hi = Math.floor(h) % 6; + const f = h - Math.floor(h); + const p = 255 * v * (1 - s); + const q = 255 * v * (1 - s * f); + const t = 255 * v * (1 - s * (1 - f)); + v *= 255; + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } + }; + convert.hsv.hsl = function(hsv) { + const h = hsv[0]; + const s = hsv[1] / 100; + const v = hsv[2] / 100; + const vmin = Math.max(v, 0.01); + let sl; + let l; + l = (2 - s) * v; + const lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= lmin <= 1 ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + return [h, sl * 100, l * 100]; + }; + convert.hwb.rgb = function(hwb) { + const h = hwb[0] / 360; + let wh = hwb[1] / 100; + let bl = hwb[2] / 100; + const ratio = wh + bl; + let f; + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + const i = Math.floor(6 * h); + const v = 1 - bl; + f = 6 * h - i; + if ((i & 1) !== 0) { + f = 1 - f; + } + const n = wh + f * (v - wh); + let r; + let g; + let b; + switch (i) { + default: + case 6: + case 0: + r = v; + g = n; + b = wh; + break; + case 1: + r = n; + g = v; + b = wh; + break; + case 2: + r = wh; + g = v; + b = n; + break; + case 3: + r = wh; + g = n; + b = v; + break; + case 4: + r = n; + g = wh; + b = v; + break; + case 5: + r = v; + g = wh; + b = n; + break; + } + return [r * 255, g * 255, b * 255]; + }; + convert.cmyk.rgb = function(cmyk) { + const c = cmyk[0] / 100; + const m = cmyk[1] / 100; + const y = cmyk[2] / 100; + const k = cmyk[3] / 100; + const r = 1 - Math.min(1, c * (1 - k) + k); + const g = 1 - Math.min(1, m * (1 - k) + k); + const b = 1 - Math.min(1, y * (1 - k) + k); + return [r * 255, g * 255, b * 255]; + }; + convert.xyz.rgb = function(xyz) { + const x = xyz[0] / 100; + const y = xyz[1] / 100; + const z = xyz[2] / 100; + let r; + let g; + let b; + r = x * 3.2406 + y * -1.5372 + z * -0.4986; + g = x * -0.9689 + y * 1.8758 + z * 0.0415; + b = x * 0.0557 + y * -0.204 + z * 1.057; + r = r > 31308e-7 ? 1.055 * r ** (1 / 2.4) - 0.055 : r * 12.92; + g = g > 31308e-7 ? 1.055 * g ** (1 / 2.4) - 0.055 : g * 12.92; + b = b > 31308e-7 ? 1.055 * b ** (1 / 2.4) - 0.055 : b * 12.92; + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + return [r * 255, g * 255, b * 255]; + }; + convert.xyz.lab = function(xyz) { + let x = xyz[0]; + let y = xyz[1]; + let z = xyz[2]; + x /= 95.047; + y /= 100; + z /= 108.883; + x = x > 8856e-6 ? x ** (1 / 3) : 7.787 * x + 16 / 116; + y = y > 8856e-6 ? y ** (1 / 3) : 7.787 * y + 16 / 116; + z = z > 8856e-6 ? z ** (1 / 3) : 7.787 * z + 16 / 116; + const l = 116 * y - 16; + const a = 500 * (x - y); + const b = 200 * (y - z); + return [l, a, b]; + }; + convert.lab.xyz = function(lab) { + const l = lab[0]; + const a = lab[1]; + const b = lab[2]; + let x; + let y; + let z; + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + const y2 = y ** 3; + const x2 = x ** 3; + const z2 = z ** 3; + y = y2 > 8856e-6 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 8856e-6 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 8856e-6 ? z2 : (z - 16 / 116) / 7.787; + x *= 95.047; + y *= 100; + z *= 108.883; + return [x, y, z]; + }; + convert.lab.lch = function(lab) { + const l = lab[0]; + const a = lab[1]; + const b = lab[2]; + let h; + const hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + if (h < 0) { + h += 360; + } + const c = Math.sqrt(a * a + b * b); + return [l, c, h]; + }; + convert.lch.lab = function(lch) { + const l = lch[0]; + const c = lch[1]; + const h = lch[2]; + const hr = h / 360 * 2 * Math.PI; + const a = c * Math.cos(hr); + const b = c * Math.sin(hr); + return [l, a, b]; + }; + convert.rgb.ansi16 = function(args, saturation = null) { + const [r, g, b] = args; + let value = saturation === null ? convert.rgb.hsv(args)[2] : saturation; + value = Math.round(value / 50); + if (value === 0) { + return 30; + } + let ansi = 30 + (Math.round(b / 255) << 2 | Math.round(g / 255) << 1 | Math.round(r / 255)); + if (value === 2) { + ansi += 60; + } + return ansi; + }; + convert.hsv.ansi16 = function(args) { + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); + }; + convert.rgb.ansi256 = function(args) { + const r = args[0]; + const g = args[1]; + const b = args[2]; + if (r === g && g === b) { + if (r < 8) { + return 16; + } + if (r > 248) { + return 231; + } + return Math.round((r - 8) / 247 * 24) + 232; + } + const ansi = 16 + 36 * Math.round(r / 255 * 5) + 6 * Math.round(g / 255 * 5) + Math.round(b / 255 * 5); + return ansi; + }; + convert.ansi16.rgb = function(args) { + let color = args % 10; + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } + color = color / 10.5 * 255; + return [color, color, color]; + } + const mult = (~~(args > 50) + 1) * 0.5; + const r = (color & 1) * mult * 255; + const g = (color >> 1 & 1) * mult * 255; + const b = (color >> 2 & 1) * mult * 255; + return [r, g, b]; + }; + convert.ansi256.rgb = function(args) { + if (args >= 232) { + const c = (args - 232) * 10 + 8; + return [c, c, c]; + } + args -= 16; + let rem; + const r = Math.floor(args / 36) / 5 * 255; + const g = Math.floor((rem = args % 36) / 6) / 5 * 255; + const b = rem % 6 / 5 * 255; + return [r, g, b]; + }; + convert.rgb.hex = function(args) { + const integer = ((Math.round(args[0]) & 255) << 16) + ((Math.round(args[1]) & 255) << 8) + (Math.round(args[2]) & 255); + const string = integer.toString(16).toUpperCase(); + return "000000".substring(string.length) + string; + }; + convert.hex.rgb = function(args) { + const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } + let colorString = match[0]; + if (match[0].length === 3) { + colorString = colorString.split("").map((char) => { + return char + char; + }).join(""); + } + const integer = parseInt(colorString, 16); + const r = integer >> 16 & 255; + const g = integer >> 8 & 255; + const b = integer & 255; + return [r, g, b]; + }; + convert.rgb.hcg = function(rgb) { + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const max = Math.max(Math.max(r, g), b); + const min = Math.min(Math.min(r, g), b); + const chroma = max - min; + let grayscale; + let hue; + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } + if (chroma <= 0) { + hue = 0; + } else if (max === r) { + hue = (g - b) / chroma % 6; + } else if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma; + } + hue /= 6; + hue %= 1; + return [hue * 360, chroma * 100, grayscale * 100]; + }; + convert.hsl.hcg = function(hsl) { + const s = hsl[1] / 100; + const l = hsl[2] / 100; + const c = l < 0.5 ? 2 * s * l : 2 * s * (1 - l); + let f = 0; + if (c < 1) { + f = (l - 0.5 * c) / (1 - c); + } + return [hsl[0], c * 100, f * 100]; + }; + convert.hsv.hcg = function(hsv) { + const s = hsv[1] / 100; + const v = hsv[2] / 100; + const c = s * v; + let f = 0; + if (c < 1) { + f = (v - c) / (1 - c); + } + return [hsv[0], c * 100, f * 100]; + }; + convert.hcg.rgb = function(hcg) { + const h = hcg[0] / 360; + const c = hcg[1] / 100; + const g = hcg[2] / 100; + if (c === 0) { + return [g * 255, g * 255, g * 255]; + } + const pure = [0, 0, 0]; + const hi = h % 1 * 6; + const v = hi % 1; + const w = 1 - v; + let mg = 0; + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; + pure[1] = v; + pure[2] = 0; + break; + case 1: + pure[0] = w; + pure[1] = 1; + pure[2] = 0; + break; + case 2: + pure[0] = 0; + pure[1] = 1; + pure[2] = v; + break; + case 3: + pure[0] = 0; + pure[1] = w; + pure[2] = 1; + break; + case 4: + pure[0] = v; + pure[1] = 0; + pure[2] = 1; + break; + default: + pure[0] = 1; + pure[1] = 0; + pure[2] = w; + } + mg = (1 - c) * g; + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; + }; + convert.hcg.hsv = function(hcg) { + const c = hcg[1] / 100; + const g = hcg[2] / 100; + const v = c + g * (1 - c); + let f = 0; + if (v > 0) { + f = c / v; + } + return [hcg[0], f * 100, v * 100]; + }; + convert.hcg.hsl = function(hcg) { + const c = hcg[1] / 100; + const g = hcg[2] / 100; + const l = g * (1 - c) + 0.5 * c; + let s = 0; + if (l > 0 && l < 0.5) { + s = c / (2 * l); + } else if (l >= 0.5 && l < 1) { + s = c / (2 * (1 - l)); + } + return [hcg[0], s * 100, l * 100]; + }; + convert.hcg.hwb = function(hcg) { + const c = hcg[1] / 100; + const g = hcg[2] / 100; + const v = c + g * (1 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; + }; + convert.hwb.hcg = function(hwb) { + const w = hwb[1] / 100; + const b = hwb[2] / 100; + const v = 1 - b; + const c = v - w; + let g = 0; + if (c < 1) { + g = (v - c) / (1 - c); + } + return [hwb[0], c * 100, g * 100]; + }; + convert.apple.rgb = function(apple) { + return [apple[0] / 65535 * 255, apple[1] / 65535 * 255, apple[2] / 65535 * 255]; + }; + convert.rgb.apple = function(rgb) { + return [rgb[0] / 255 * 65535, rgb[1] / 255 * 65535, rgb[2] / 255 * 65535]; + }; + convert.gray.rgb = function(args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; + }; + convert.gray.hsl = function(args) { + return [0, 0, args[0]]; + }; + convert.gray.hsv = convert.gray.hsl; + convert.gray.hwb = function(gray) { + return [0, 100, gray[0]]; + }; + convert.gray.cmyk = function(gray) { + return [0, 0, 0, gray[0]]; + }; + convert.gray.lab = function(gray) { + return [gray[0], 0, 0]; + }; + convert.gray.hex = function(gray) { + const val = Math.round(gray[0] / 100 * 255) & 255; + const integer = (val << 16) + (val << 8) + val; + const string = integer.toString(16).toUpperCase(); + return "000000".substring(string.length) + string; + }; + convert.rgb.gray = function(rgb) { + const val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; + }; + } +}); + +// pnp:color-convert-npm-2.0.1-79730e935b-79e6bdb9fd.zip/node_modules/color-convert/route.js +var require_route = __commonJS({ + "pnp:color-convert-npm-2.0.1-79730e935b-79e6bdb9fd.zip/node_modules/color-convert/route.js"(exports, module2) { + var conversions = require_conversions(); + function buildGraph() { + const graph = {}; + const models = Object.keys(conversions); + for (let len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + distance: -1, + parent: null + }; + } + return graph; + } + function deriveBFS(fromModel) { + const graph = buildGraph(); + const queue = [fromModel]; + graph[fromModel].distance = 0; + while (queue.length) { + const current = queue.pop(); + const adjacents = Object.keys(conversions[current]); + for (let len = adjacents.length, i = 0; i < len; i++) { + const adjacent = adjacents[i]; + const node = graph[adjacent]; + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + return graph; + } + function link(from, to) { + return function(args) { + return to(from(args)); + }; + } + function wrapConversion(toModel, graph) { + const path2 = [graph[toModel].parent, toModel]; + let fn = conversions[graph[toModel].parent][toModel]; + let cur = graph[toModel].parent; + while (graph[cur].parent) { + path2.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } + fn.conversion = path2; + return fn; + } + module2.exports = function(fromModel) { + const graph = deriveBFS(fromModel); + const conversion = {}; + const models = Object.keys(graph); + for (let len = models.length, i = 0; i < len; i++) { + const toModel = models[i]; + const node = graph[toModel]; + if (node.parent === null) { + continue; + } + conversion[toModel] = wrapConversion(toModel, graph); + } + return conversion; + }; + } +}); + +// pnp:color-convert-npm-2.0.1-79730e935b-79e6bdb9fd.zip/node_modules/color-convert/index.js +var require_color_convert = __commonJS({ + "pnp:color-convert-npm-2.0.1-79730e935b-79e6bdb9fd.zip/node_modules/color-convert/index.js"(exports, module2) { + var conversions = require_conversions(); + var route = require_route(); + var convert = {}; + var models = Object.keys(conversions); + function wrapRaw(fn) { + const wrappedFn = function(...args) { + const arg0 = args[0]; + if (arg0 === void 0 || arg0 === null) { + return arg0; + } + if (arg0.length > 1) { + args = arg0; + } + return fn(args); + }; + if ("conversion" in fn) { + wrappedFn.conversion = fn.conversion; + } + return wrappedFn; + } + function wrapRounded(fn) { + const wrappedFn = function(...args) { + const arg0 = args[0]; + if (arg0 === void 0 || arg0 === null) { + return arg0; + } + if (arg0.length > 1) { + args = arg0; + } + const result = fn(args); + if (typeof result === "object") { + for (let len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } + return result; + }; + if ("conversion" in fn) { + wrappedFn.conversion = fn.conversion; + } + return wrappedFn; + } + models.forEach((fromModel) => { + convert[fromModel] = {}; + Object.defineProperty(convert[fromModel], "channels", { value: conversions[fromModel].channels }); + Object.defineProperty(convert[fromModel], "labels", { value: conversions[fromModel].labels }); + const routes = route(fromModel); + const routeModels = Object.keys(routes); + routeModels.forEach((toModel) => { + const fn = routes[toModel]; + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); + }); + module2.exports = convert; + } +}); + +// pnp:ansi-styles-npm-4.3.0-245c7d42c7-513b44c3b2.zip/node_modules/ansi-styles/index.js +var require_ansi_styles = __commonJS({ + "pnp:ansi-styles-npm-4.3.0-245c7d42c7-513b44c3b2.zip/node_modules/ansi-styles/index.js"(exports, module2) { + "use strict"; + var wrapAnsi16 = (fn, offset) => (...args) => { + const code = fn(...args); + return `[${code + offset}m`; + }; + var wrapAnsi256 = (fn, offset) => (...args) => { + const code = fn(...args); + return `[${38 + offset};5;${code}m`; + }; + var wrapAnsi16m = (fn, offset) => (...args) => { + const rgb = fn(...args); + return `[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; + }; + var ansi2ansi = (n) => n; + var rgb2rgb = (r, g, b) => [r, g, b]; + var setLazyProperty = (object, property, get) => { + Object.defineProperty(object, property, { + get: () => { + const value = get(); + Object.defineProperty(object, property, { + value, + enumerable: true, + configurable: true + }); + return value; + }, + enumerable: true, + configurable: true + }); + }; + var colorConvert; + var makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { + if (colorConvert === void 0) { + colorConvert = require_color_convert(); + } + const offset = isBackground ? 10 : 0; + const styles = {}; + for (const [sourceSpace, suite] of Object.entries(colorConvert)) { + const name = sourceSpace === "ansi16" ? "ansi" : sourceSpace; + if (sourceSpace === targetSpace) { + styles[name] = wrap(identity, offset); + } else if (typeof suite === "object") { + styles[name] = wrap(suite[targetSpace], offset); + } + } + return styles; + }; + function assembleStyles() { + const codes = new Map(); + const styles = { + modifier: { + reset: [0, 0], + bold: [1, 22], + dim: [2, 22], + italic: [3, 23], + underline: [4, 24], + inverse: [7, 27], + hidden: [8, 28], + strikethrough: [9, 29] + }, + color: { + black: [30, 39], + red: [31, 39], + green: [32, 39], + yellow: [33, 39], + blue: [34, 39], + magenta: [35, 39], + cyan: [36, 39], + white: [37, 39], + blackBright: [90, 39], + redBright: [91, 39], + greenBright: [92, 39], + yellowBright: [93, 39], + blueBright: [94, 39], + magentaBright: [95, 39], + cyanBright: [96, 39], + whiteBright: [97, 39] + }, + bgColor: { + bgBlack: [40, 49], + bgRed: [41, 49], + bgGreen: [42, 49], + bgYellow: [43, 49], + bgBlue: [44, 49], + bgMagenta: [45, 49], + bgCyan: [46, 49], + bgWhite: [47, 49], + bgBlackBright: [100, 49], + bgRedBright: [101, 49], + bgGreenBright: [102, 49], + bgYellowBright: [103, 49], + bgBlueBright: [104, 49], + bgMagentaBright: [105, 49], + bgCyanBright: [106, 49], + bgWhiteBright: [107, 49] + } + }; + styles.color.gray = styles.color.blackBright; + styles.bgColor.bgGray = styles.bgColor.bgBlackBright; + styles.color.grey = styles.color.blackBright; + styles.bgColor.bgGrey = styles.bgColor.bgBlackBright; + for (const [groupName, group] of Object.entries(styles)) { + for (const [styleName, style] of Object.entries(group)) { + styles[styleName] = { + open: `[${style[0]}m`, + close: `[${style[1]}m` + }; + group[styleName] = styles[styleName]; + codes.set(style[0], style[1]); + } + Object.defineProperty(styles, groupName, { + value: group, + enumerable: false + }); + } + Object.defineProperty(styles, "codes", { + value: codes, + enumerable: false + }); + styles.color.close = ""; + styles.bgColor.close = ""; + setLazyProperty(styles.color, "ansi", () => makeDynamicStyles(wrapAnsi16, "ansi16", ansi2ansi, false)); + setLazyProperty(styles.color, "ansi256", () => makeDynamicStyles(wrapAnsi256, "ansi256", ansi2ansi, false)); + setLazyProperty(styles.color, "ansi16m", () => makeDynamicStyles(wrapAnsi16m, "rgb", rgb2rgb, false)); + setLazyProperty(styles.bgColor, "ansi", () => makeDynamicStyles(wrapAnsi16, "ansi16", ansi2ansi, true)); + setLazyProperty(styles.bgColor, "ansi256", () => makeDynamicStyles(wrapAnsi256, "ansi256", ansi2ansi, true)); + setLazyProperty(styles.bgColor, "ansi16m", () => makeDynamicStyles(wrapAnsi16m, "rgb", rgb2rgb, true)); + return styles; + } + Object.defineProperty(module2, "exports", { + enumerable: true, + get: assembleStyles + }); + } +}); + +// pnp:has-flag-npm-4.0.0-32af9f0536-261a135703.zip/node_modules/has-flag/index.js +var require_has_flag = __commonJS({ + "pnp:has-flag-npm-4.0.0-32af9f0536-261a135703.zip/node_modules/has-flag/index.js"(exports, module2) { + "use strict"; + module2.exports = (flag, argv = process.argv) => { + const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--"; + const position = argv.indexOf(prefix + flag); + const terminatorPosition = argv.indexOf("--"); + return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition); + }; + } +}); + +// pnp:supports-color-npm-7.2.0-606bfcf7da-3dda818de0.zip/node_modules/supports-color/index.js +var require_supports_color = __commonJS({ + "pnp:supports-color-npm-7.2.0-606bfcf7da-3dda818de0.zip/node_modules/supports-color/index.js"(exports, module2) { + "use strict"; + var os = require("os"); + var tty = require("tty"); + var hasFlag = require_has_flag(); + var { env } = process; + var forceColor; + if (hasFlag("no-color") || hasFlag("no-colors") || hasFlag("color=false") || hasFlag("color=never")) { + forceColor = 0; + } else if (hasFlag("color") || hasFlag("colors") || hasFlag("color=true") || hasFlag("color=always")) { + forceColor = 1; + } + if ("FORCE_COLOR" in env) { + if (env.FORCE_COLOR === "true") { + forceColor = 1; + } else if (env.FORCE_COLOR === "false") { + forceColor = 0; + } else { + forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3); + } + } + function translateLevel(level) { + if (level === 0) { + return false; + } + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; + } + function supportsColor(haveStream, streamIsTTY) { + if (forceColor === 0) { + return 0; + } + if (hasFlag("color=16m") || hasFlag("color=full") || hasFlag("color=truecolor")) { + return 3; + } + if (hasFlag("color=256")) { + return 2; + } + if (haveStream && !streamIsTTY && forceColor === void 0) { + return 0; + } + const min = forceColor || 0; + if (env.TERM === "dumb") { + return min; + } + if (process.platform === "win32") { + const osRelease = os.release().split("."); + if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } + return 1; + } + if ("CI" in env) { + if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env) || env.CI_NAME === "codeship") { + return 1; + } + return min; + } + if ("TEAMCITY_VERSION" in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } + if (env.COLORTERM === "truecolor") { + return 3; + } + if ("TERM_PROGRAM" in env) { + const version2 = parseInt((env.TERM_PROGRAM_VERSION || "").split(".")[0], 10); + switch (env.TERM_PROGRAM) { + case "iTerm.app": + return version2 >= 3 ? 3 : 2; + case "Apple_Terminal": + return 2; + } + } + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + if ("COLORTERM" in env) { + return 1; + } + return min; + } + function getSupportLevel(stream) { + const level = supportsColor(stream, stream && stream.isTTY); + return translateLevel(level); + } + module2.exports = { + supportsColor: getSupportLevel, + stdout: translateLevel(supportsColor(true, tty.isatty(1))), + stderr: translateLevel(supportsColor(true, tty.isatty(2))) + }; + } +}); + +// pnp:chalk-npm-4.1.2-ba8b67ab80-fe75c9d5c7.zip/node_modules/chalk/source/util.js +var require_util = __commonJS({ + "pnp:chalk-npm-4.1.2-ba8b67ab80-fe75c9d5c7.zip/node_modules/chalk/source/util.js"(exports, module2) { + "use strict"; + var stringReplaceAll = (string, substring, replacer) => { + let index = string.indexOf(substring); + if (index === -1) { + return string; + } + const substringLength = substring.length; + let endIndex = 0; + let returnValue = ""; + do { + returnValue += string.substr(endIndex, index - endIndex) + substring + replacer; + endIndex = index + substringLength; + index = string.indexOf(substring, endIndex); + } while (index !== -1); + returnValue += string.substr(endIndex); + return returnValue; + }; + var stringEncaseCRLFWithFirstIndex = (string, prefix, postfix, index) => { + let endIndex = 0; + let returnValue = ""; + do { + const gotCR = string[index - 1] === "\r"; + returnValue += string.substr(endIndex, (gotCR ? index - 1 : index) - endIndex) + prefix + (gotCR ? "\r\n" : "\n") + postfix; + endIndex = index + 1; + index = string.indexOf("\n", endIndex); + } while (index !== -1); + returnValue += string.substr(endIndex); + return returnValue; + }; + module2.exports = { + stringReplaceAll, + stringEncaseCRLFWithFirstIndex + }; + } +}); + +// pnp:chalk-npm-4.1.2-ba8b67ab80-fe75c9d5c7.zip/node_modules/chalk/source/templates.js +var require_templates = __commonJS({ + "pnp:chalk-npm-4.1.2-ba8b67ab80-fe75c9d5c7.zip/node_modules/chalk/source/templates.js"(exports, module2) { + "use strict"; + var TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; + var STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; + var STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; + var ESCAPE_REGEX = /\\(u(?:[a-f\d]{4}|{[a-f\d]{1,6}})|x[a-f\d]{2}|.)|([^\\])/gi; + var ESCAPES = new Map([ + ["n", "\n"], + ["r", "\r"], + ["t", " "], + ["b", "\b"], + ["f", "\f"], + ["v", "\v"], + ["0", "\0"], + ["\\", "\\"], + ["e", ""], + ["a", "\x07"] + ]); + function unescape(c) { + const u = c[0] === "u"; + const bracket = c[1] === "{"; + if (u && !bracket && c.length === 5 || c[0] === "x" && c.length === 3) { + return String.fromCharCode(parseInt(c.slice(1), 16)); + } + if (u && bracket) { + return String.fromCodePoint(parseInt(c.slice(2, -1), 16)); + } + return ESCAPES.get(c) || c; + } + function parseArguments(name, arguments_) { + const results = []; + const chunks = arguments_.trim().split(/\s*,\s*/g); + let matches; + for (const chunk of chunks) { + const number = Number(chunk); + if (!Number.isNaN(number)) { + results.push(number); + } else if (matches = chunk.match(STRING_REGEX)) { + results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, character) => escape ? unescape(escape) : character)); + } else { + throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); + } + } + return results; + } + function parseStyle(style) { + STYLE_REGEX.lastIndex = 0; + const results = []; + let matches; + while ((matches = STYLE_REGEX.exec(style)) !== null) { + const name = matches[1]; + if (matches[2]) { + const args = parseArguments(name, matches[2]); + results.push([name].concat(args)); + } else { + results.push([name]); + } + } + return results; + } + function buildStyle(chalk5, styles) { + const enabled = {}; + for (const layer of styles) { + for (const style of layer.styles) { + enabled[style[0]] = layer.inverse ? null : style.slice(1); + } + } + let current = chalk5; + for (const [styleName, styles2] of Object.entries(enabled)) { + if (!Array.isArray(styles2)) { + continue; + } + if (!(styleName in current)) { + throw new Error(`Unknown Chalk style: ${styleName}`); + } + current = styles2.length > 0 ? current[styleName](...styles2) : current[styleName]; + } + return current; + } + module2.exports = (chalk5, temporary) => { + const styles = []; + const chunks = []; + let chunk = []; + temporary.replace(TEMPLATE_REGEX, (m, escapeCharacter, inverse, style, close, character) => { + if (escapeCharacter) { + chunk.push(unescape(escapeCharacter)); + } else if (style) { + const string = chunk.join(""); + chunk = []; + chunks.push(styles.length === 0 ? string : buildStyle(chalk5, styles)(string)); + styles.push({ inverse, styles: parseStyle(style) }); + } else if (close) { + if (styles.length === 0) { + throw new Error("Found extraneous } in Chalk template literal"); + } + chunks.push(buildStyle(chalk5, styles)(chunk.join(""))); + chunk = []; + styles.pop(); + } else { + chunk.push(character); + } + }); + chunks.push(chunk.join("")); + if (styles.length > 0) { + const errMessage = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? "" : "s"} (\`}\`)`; + throw new Error(errMessage); + } + return chunks.join(""); + }; + } +}); + +// pnp:chalk-npm-4.1.2-ba8b67ab80-fe75c9d5c7.zip/node_modules/chalk/source/index.js +var require_source = __commonJS({ + "pnp:chalk-npm-4.1.2-ba8b67ab80-fe75c9d5c7.zip/node_modules/chalk/source/index.js"(exports, module2) { + "use strict"; + var ansiStyles = require_ansi_styles(); + var { stdout: stdoutColor, stderr: stderrColor } = require_supports_color(); + var { + stringReplaceAll, + stringEncaseCRLFWithFirstIndex + } = require_util(); + var { isArray } = Array; + var levelMapping = [ + "ansi", + "ansi", + "ansi256", + "ansi16m" + ]; + var styles = Object.create(null); + var applyOptions = (object, options = {}) => { + if (options.level && !(Number.isInteger(options.level) && options.level >= 0 && options.level <= 3)) { + throw new Error("The `level` option should be an integer from 0 to 3"); + } + const colorLevel = stdoutColor ? stdoutColor.level : 0; + object.level = options.level === void 0 ? colorLevel : options.level; + }; + var ChalkClass = class { + constructor(options) { + return chalkFactory(options); + } + }; + var chalkFactory = (options) => { + const chalk6 = {}; + applyOptions(chalk6, options); + chalk6.template = (...arguments_) => chalkTag(chalk6.template, ...arguments_); + Object.setPrototypeOf(chalk6, Chalk.prototype); + Object.setPrototypeOf(chalk6.template, chalk6); + chalk6.template.constructor = () => { + throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead."); + }; + chalk6.template.Instance = ChalkClass; + return chalk6.template; + }; + function Chalk(options) { + return chalkFactory(options); + } + for (const [styleName, style] of Object.entries(ansiStyles)) { + styles[styleName] = { + get() { + const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); + Object.defineProperty(this, styleName, { value: builder }); + return builder; + } + }; + } + styles.visible = { + get() { + const builder = createBuilder(this, this._styler, true); + Object.defineProperty(this, "visible", { value: builder }); + return builder; + } + }; + var usedModels = ["rgb", "hex", "keyword", "hsl", "hsv", "hwb", "ansi", "ansi256"]; + for (const model of usedModels) { + styles[model] = { + get() { + const { level } = this; + return function(...arguments_) { + const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); + return createBuilder(this, styler, this._isEmpty); + }; + } + }; + } + for (const model of usedModels) { + const bgModel = "bg" + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const { level } = this; + return function(...arguments_) { + const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler); + return createBuilder(this, styler, this._isEmpty); + }; + } + }; + } + var proto = Object.defineProperties(() => { + }, __spreadProps(__spreadValues({}, styles), { + level: { + enumerable: true, + get() { + return this._generator.level; + }, + set(level) { + this._generator.level = level; + } + } + })); + var createStyler = (open, close, parent) => { + let openAll; + let closeAll; + if (parent === void 0) { + openAll = open; + closeAll = close; + } else { + openAll = parent.openAll + open; + closeAll = close + parent.closeAll; + } + return { + open, + close, + openAll, + closeAll, + parent + }; + }; + var createBuilder = (self, _styler, _isEmpty) => { + const builder = (...arguments_) => { + if (isArray(arguments_[0]) && isArray(arguments_[0].raw)) { + return applyStyle(builder, chalkTag(builder, ...arguments_)); + } + return applyStyle(builder, arguments_.length === 1 ? "" + arguments_[0] : arguments_.join(" ")); + }; + Object.setPrototypeOf(builder, proto); + builder._generator = self; + builder._styler = _styler; + builder._isEmpty = _isEmpty; + return builder; + }; + var applyStyle = (self, string) => { + if (self.level <= 0 || !string) { + return self._isEmpty ? "" : string; + } + let styler = self._styler; + if (styler === void 0) { + return string; + } + const { openAll, closeAll } = styler; + if (string.indexOf("") !== -1) { + while (styler !== void 0) { + string = stringReplaceAll(string, styler.close, styler.open); + styler = styler.parent; + } + } + const lfIndex = string.indexOf("\n"); + if (lfIndex !== -1) { + string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); + } + return openAll + string + closeAll; + }; + var template; + var chalkTag = (chalk6, ...strings) => { + const [firstString] = strings; + if (!isArray(firstString) || !isArray(firstString.raw)) { + return strings.join(" "); + } + const arguments_ = strings.slice(1); + const parts = [firstString.raw[0]]; + for (let i = 1; i < firstString.length; i++) { + parts.push(String(arguments_[i - 1]).replace(/[{}\\]/g, "\\$&"), String(firstString.raw[i])); + } + if (template === void 0) { + template = require_templates(); + } + return template(chalk6, parts.join("")); + }; + Object.defineProperties(Chalk.prototype, styles); + var chalk5 = Chalk(); + chalk5.supportsColor = stdoutColor; + chalk5.stderr = Chalk({ level: stderrColor ? stderrColor.level : 0 }); + chalk5.stderr.supportsColor = stderrColor; + module2.exports = chalk5; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/lowercase.js +var require_lowercase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/lowercase.js"(exports, module2) { + "use strict"; + function lowercase(str) { + str = String(str); + if (!str) { + return str; + } + return str.toLowerCase(); + } + lowercase.isLowercase = function(str) { + return str && !/[A-Z]+/.test(str); + }; + module2.exports = lowercase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/uppercase.js +var require_uppercase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/uppercase.js"(exports, module2) { + "use strict"; + function uppercase(str) { + str = String(str); + if (!str) { + return str; + } + return str.toUpperCase(); + } + uppercase.isUppercase = function(str) { + return str && !/[a-z]+/.test(str); + }; + module2.exports = uppercase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/camelcase.js +var require_camelcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/camelcase.js"(exports, module2) { + "use strict"; + var lowercase = require_lowercase(); + var uppercase = require_uppercase(); + var replacing = { + from: /[\-_:\.\s]([a-zA-Z])([a-zA-Z]*)/g, + to: function(match, $1, $2, offset, src) { + const len = $1.length; + return uppercase($1) + $2; + } + }; + function camelcase2(str) { + if (camelcase2.isCamelcase(str)) { + return str; + } + str = String(str).replace(/^[\-_:\.\s]/, ""); + if (!str) { + return str; + } + if (uppercase.isUppercase(str)) { + str = lowercase(str); + } + return lowercase(str[0]) + str.replace(replacing.from, replacing.to).slice(1).replace(/^([A-Z]+)([A-Z])/, (match, $1, $2) => lowercase($1) + $2); + } + camelcase2.isCamelcase = function(str) { + return str && /^[a-zA-Z]+$/.test(str) && lowercase(str[0]) === str[0]; + }; + module2.exports = camelcase2; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/capitalcase.js +var require_capitalcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/capitalcase.js"(exports, module2) { + "use strict"; + var uppercase = require_uppercase(); + function capitalcase(str) { + str = String(str); + if (!str) { + return str; + } + return uppercase(str[0]) + str.slice(1); + } + module2.exports = capitalcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/snakecase.js +var require_snakecase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/snakecase.js"(exports, module2) { + "use strict"; + var lowercase = require_lowercase(); + var uppercase = require_uppercase(); + var JOINER = "_"; + var replacing = { + from: /([A-Z]+)/g, + to(match, $1, offset, src) { + const prefix = offset === 0 ? "" : JOINER; + const len = $1.length; + if (len === 1) { + return prefix + lowercase($1); + } + const next = src.slice(offset + $1.length); + const isOneWord = uppercase.isUppercase($1) && next[0] === JOINER; + if (isOneWord) { + return prefix + lowercase($1); + } + const replaced = lowercase($1.substr(0, len - 1)) + JOINER + lowercase($1[len - 1]); + return prefix + replaced; + } + }; + function snakecase(str) { + if (snakecase.isSnakecase(str)) { + return str; + } + str = String(str).replace(/[\-.:\s]/g, JOINER); + if (!str) { + return str; + } + if (uppercase.isUppercase(str)) { + str = lowercase(str); + } + return str.replace(replacing.from, replacing.to).replace(/_+/g, "_"); + } + snakecase.isSnakecase = function(str) { + return str && /^[a-z_]+$/.test(str); + }; + module2.exports = snakecase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/constcase.js +var require_constcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/constcase.js"(exports, module2) { + "use strict"; + var uppercase = require_uppercase(); + var snakecase = require_snakecase(); + function constcase2(str) { + if (constcase2.isConstcase(str)) { + return str; + } + return uppercase(snakecase(str)); + } + constcase2.isConstcase = function(str) { + return str && /^[A-Z_]+$/.test(str); + }; + module2.exports = constcase2; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/cramcase.js +var require_cramcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/cramcase.js"(exports, module2) { + "use strict"; + var snakecase = require_snakecase(); + function cramcase(str) { + return snakecase(str).replace(/_/g, ""); + } + module2.exports = cramcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/decapitalcase.js +var require_decapitalcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/decapitalcase.js"(exports, module2) { + "use strict"; + var lowercase = require_lowercase(); + function capitalcase(str) { + str = String(str); + if (!str) { + return str; + } + return lowercase(str[0]) + str.slice(1); + } + module2.exports = capitalcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/dotcase.js +var require_dotcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/dotcase.js"(exports, module2) { + "use strict"; + var snakecase = require_snakecase(); + function dotcase(str) { + return snakecase(str).replace(/_/g, "."); + } + module2.exports = dotcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/enumcase.js +var require_enumcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/enumcase.js"(exports, module2) { + "use strict"; + var snakecase = require_snakecase(); + function enumcase(str) { + return snakecase(str).replace(/_/g, ":"); + } + module2.exports = enumcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/pascalcase.js +var require_pascalcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/pascalcase.js"(exports, module2) { + "use strict"; + var camelcase2 = require_camelcase(); + var capitalcase = require_capitalcase(); + function pascalcase(str) { + return capitalcase(camelcase2(str)); + } + module2.exports = pascalcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/pathcase.js +var require_pathcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/pathcase.js"(exports, module2) { + "use strict"; + var snakecase = require_snakecase(); + function pathcase(str) { + return snakecase(str).replace(/_/g, "/"); + } + module2.exports = pathcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/trimcase.js +var require_trimcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/trimcase.js"(exports, module2) { + "use strict"; + function trimcase(str) { + return String(str).trim(); + } + module2.exports = trimcase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/sentencecase.js +var require_sentencecase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/sentencecase.js"(exports, module2) { + "use strict"; + var lowercase = require_lowercase(); + var trimcase = require_trimcase(); + var snakecase = require_snakecase(); + var capitalcase = require_capitalcase(); + var JOINER = " "; + function sentencecase(str) { + str = String(str).replace(/^[\-_\.\s]/g, JOINER); + if (!str) { + return str; + } + return capitalcase(snakecase(trimcase(str)).replace(/_/g, JOINER)); + } + module2.exports = sentencecase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/spacecase.js +var require_spacecase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/spacecase.js"(exports, module2) { + "use strict"; + var snakecase = require_snakecase(); + function spacecase(str) { + return snakecase(str).replace(/_/g, " "); + } + module2.exports = spacecase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/spinalcase.js +var require_spinalcase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/spinalcase.js"(exports, module2) { + "use strict"; + var snakecase = require_snakecase(); + function spinalcase2(str) { + return snakecase(str).replace(/_/g, "-"); + } + module2.exports = spinalcase2; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/titlecase.js +var require_titlecase = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/titlecase.js"(exports, module2) { + "use strict"; + var snakecase = require_snakecase(); + var lowercase = require_lowercase(); + var trimcase = require_trimcase(); + var capitalcase = require_capitalcase(); + var LOWERCASE_WORDS = "a,the,and,or,not,but,for,of".split(","); + function titlecase(str) { + return snakecase(str).split(/_/g).map(trimcase).map(function(word) { + var lower = !!~LOWERCASE_WORDS.indexOf(word); + if (lower) { + return lowercase(word); + } else { + return capitalcase(word); + } + }).join(" "); + } + module2.exports = titlecase; + } +}); + +// pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/index.js +var require_lib = __commonJS({ + "pnp:stringcase-npm-4.3.1-2f1c329337-741a448632.zip/node_modules/stringcase/lib/index.js"(exports, module2) { + "use strict"; + var camelcase2 = require_camelcase(); + var capitalcase = require_capitalcase(); + var constcase2 = require_constcase(); + var cramcase = require_cramcase(); + var decapitalcase = require_decapitalcase(); + var dotcase = require_dotcase(); + var enumcase = require_enumcase(); + var lowercase = require_lowercase(); + var pascalcase = require_pascalcase(); + var pathcase = require_pathcase(); + var sentencecase = require_sentencecase(); + var snakecase = require_snakecase(); + var spacecase = require_spacecase(); + var spinalcase2 = require_spinalcase(); + var titlecase = require_titlecase(); + var trimcase = require_trimcase(); + var uppercase = require_uppercase(); + exports.camelcase = camelcase2; + exports.capitalcase = capitalcase; + exports.constcase = constcase2; + exports.cramcase = cramcase; + exports.decapitalcase = decapitalcase; + exports.dotcase = dotcase; + exports.enumcase = enumcase; + exports.lowercase = lowercase; + exports.pascalcase = pascalcase; + exports.pathcase = pathcase; + exports.sentencecase = sentencecase; + exports.snakecase = snakecase; + exports.spacecase = spacecase; + exports.spinalcase = spinalcase2; + exports.titlecase = titlecase; + exports.trimcase = trimcase; + exports.uppercase = uppercase; + module2.exports = { + camelcase: camelcase2, + capitalcase, + constcase: constcase2, + cramcase, + decapitalcase, + dotcase, + enumcase, + lowercase, + pascalcase, + pathcase, + sentencecase, + snakecase, + spacecase, + spinalcase: spinalcase2, + titlecase, + trimcase, + uppercase + }; + } +}); + +// pnp:fs.realpath-npm-1.0.0-c8f05d8126-99ddea01a7.zip/node_modules/fs.realpath/old.js +var require_old = __commonJS({ + "pnp:fs.realpath-npm-1.0.0-c8f05d8126-99ddea01a7.zip/node_modules/fs.realpath/old.js"(exports) { + var pathModule = require("path"); + var isWindows = process.platform === "win32"; + var fs4 = require("fs"); + var DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG); + function rethrow() { + var callback; + if (DEBUG) { + var backtrace = new Error(); + callback = debugCallback; + } else + callback = missingCallback; + return callback; + function debugCallback(err) { + if (err) { + backtrace.message = err.message; + err = backtrace; + missingCallback(err); + } + } + function missingCallback(err) { + if (err) { + if (process.throwDeprecation) + throw err; + else if (!process.noDeprecation) { + var msg = "fs: missing callback " + (err.stack || err.message); + if (process.traceDeprecation) + console.trace(msg); + else + console.error(msg); + } + } + } + } + function maybeCallback(cb) { + return typeof cb === "function" ? cb : rethrow(); + } + var normalize = pathModule.normalize; + if (isWindows) { + nextPartRe = /(.*?)(?:[\/\\]+|$)/g; + } else { + nextPartRe = /(.*?)(?:[\/]+|$)/g; + } + var nextPartRe; + if (isWindows) { + splitRootRe = /^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/; + } else { + splitRootRe = /^[\/]*/; + } + var splitRootRe; + exports.realpathSync = function realpathSync(p, cache) { + p = pathModule.resolve(p); + if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { + return cache[p]; + } + var original = p, seenLinks = {}, knownHard = {}; + var pos; + var current; + var base; + var previous; + start(); + function start() { + var m = splitRootRe.exec(p); + pos = m[0].length; + current = m[0]; + base = m[0]; + previous = ""; + if (isWindows && !knownHard[base]) { + fs4.lstatSync(base); + knownHard[base] = true; + } + } + while (pos < p.length) { + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; + if (knownHard[base] || cache && cache[base] === base) { + continue; + } + var resolvedLink; + if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { + resolvedLink = cache[base]; + } else { + var stat = fs4.lstatSync(base); + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + if (cache) + cache[base] = base; + continue; + } + var linkTarget = null; + if (!isWindows) { + var id = stat.dev.toString(32) + ":" + stat.ino.toString(32); + if (seenLinks.hasOwnProperty(id)) { + linkTarget = seenLinks[id]; + } + } + if (linkTarget === null) { + fs4.statSync(base); + linkTarget = fs4.readlinkSync(base); + } + resolvedLink = pathModule.resolve(previous, linkTarget); + if (cache) + cache[base] = resolvedLink; + if (!isWindows) + seenLinks[id] = linkTarget; + } + p = pathModule.resolve(resolvedLink, p.slice(pos)); + start(); + } + if (cache) + cache[original] = p; + return p; + }; + exports.realpath = function realpath(p, cache, cb) { + if (typeof cb !== "function") { + cb = maybeCallback(cache); + cache = null; + } + p = pathModule.resolve(p); + if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { + return process.nextTick(cb.bind(null, null, cache[p])); + } + var original = p, seenLinks = {}, knownHard = {}; + var pos; + var current; + var base; + var previous; + start(); + function start() { + var m = splitRootRe.exec(p); + pos = m[0].length; + current = m[0]; + base = m[0]; + previous = ""; + if (isWindows && !knownHard[base]) { + fs4.lstat(base, function(err) { + if (err) + return cb(err); + knownHard[base] = true; + LOOP(); + }); + } else { + process.nextTick(LOOP); + } + } + function LOOP() { + if (pos >= p.length) { + if (cache) + cache[original] = p; + return cb(null, p); + } + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; + if (knownHard[base] || cache && cache[base] === base) { + return process.nextTick(LOOP); + } + if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { + return gotResolvedLink(cache[base]); + } + return fs4.lstat(base, gotStat); + } + function gotStat(err, stat) { + if (err) + return cb(err); + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + if (cache) + cache[base] = base; + return process.nextTick(LOOP); + } + if (!isWindows) { + var id = stat.dev.toString(32) + ":" + stat.ino.toString(32); + if (seenLinks.hasOwnProperty(id)) { + return gotTarget(null, seenLinks[id], base); + } + } + fs4.stat(base, function(err2) { + if (err2) + return cb(err2); + fs4.readlink(base, function(err3, target) { + if (!isWindows) + seenLinks[id] = target; + gotTarget(err3, target); + }); + }); + } + function gotTarget(err, target, base2) { + if (err) + return cb(err); + var resolvedLink = pathModule.resolve(previous, target); + if (cache) + cache[base2] = resolvedLink; + gotResolvedLink(resolvedLink); + } + function gotResolvedLink(resolvedLink) { + p = pathModule.resolve(resolvedLink, p.slice(pos)); + start(); + } + }; + } +}); + +// pnp:fs.realpath-npm-1.0.0-c8f05d8126-99ddea01a7.zip/node_modules/fs.realpath/index.js +var require_fs = __commonJS({ + "pnp:fs.realpath-npm-1.0.0-c8f05d8126-99ddea01a7.zip/node_modules/fs.realpath/index.js"(exports, module2) { + module2.exports = realpath; + realpath.realpath = realpath; + realpath.sync = realpathSync; + realpath.realpathSync = realpathSync; + realpath.monkeypatch = monkeypatch; + realpath.unmonkeypatch = unmonkeypatch; + var fs4 = require("fs"); + var origRealpath = fs4.realpath; + var origRealpathSync = fs4.realpathSync; + var version2 = process.version; + var ok = /^v[0-5]\./.test(version2); + var old = require_old(); + function newError(er) { + return er && er.syscall === "realpath" && (er.code === "ELOOP" || er.code === "ENOMEM" || er.code === "ENAMETOOLONG"); + } + function realpath(p, cache, cb) { + if (ok) { + return origRealpath(p, cache, cb); + } + if (typeof cache === "function") { + cb = cache; + cache = null; + } + origRealpath(p, cache, function(er, result) { + if (newError(er)) { + old.realpath(p, cache, cb); + } else { + cb(er, result); + } + }); + } + function realpathSync(p, cache) { + if (ok) { + return origRealpathSync(p, cache); + } + try { + return origRealpathSync(p, cache); + } catch (er) { + if (newError(er)) { + return old.realpathSync(p, cache); + } else { + throw er; + } + } + } + function monkeypatch() { + fs4.realpath = realpath; + fs4.realpathSync = realpathSync; + } + function unmonkeypatch() { + fs4.realpath = origRealpath; + fs4.realpathSync = origRealpathSync; + } + } +}); + +// pnp:concat-map-npm-0.0.1-85a921b7ee-902a9f5d89.zip/node_modules/concat-map/index.js +var require_concat_map = __commonJS({ + "pnp:concat-map-npm-0.0.1-85a921b7ee-902a9f5d89.zip/node_modules/concat-map/index.js"(exports, module2) { + module2.exports = function(xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) + res.push.apply(res, x); + else + res.push(x); + } + return res; + }; + var isArray = Array.isArray || function(xs) { + return Object.prototype.toString.call(xs) === "[object Array]"; + }; + } +}); + +// pnp:balanced-match-npm-1.0.0-951a2ad706-9b67bfe558.zip/node_modules/balanced-match/index.js +var require_balanced_match = __commonJS({ + "pnp:balanced-match-npm-1.0.0-951a2ad706-9b67bfe558.zip/node_modules/balanced-match/index.js"(exports, module2) { + "use strict"; + module2.exports = balanced; + function balanced(a, b, str) { + if (a instanceof RegExp) + a = maybeMatch(a, str); + if (b instanceof RegExp) + b = maybeMatch(b, str); + var r = range(a, b, str); + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; + } + function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; + } + balanced.range = range; + function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [begs.pop(), bi]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + bi = str.indexOf(b, i + 1); + } + i = ai < bi && ai >= 0 ? ai : bi; + } + if (begs.length) { + result = [left, right]; + } + } + return result; + } + } +}); + +// pnp:brace-expansion-npm-1.1.11-fb95eb05ad-faf34a7bb0.zip/node_modules/brace-expansion/index.js +var require_brace_expansion = __commonJS({ + "pnp:brace-expansion-npm-1.1.11-fb95eb05ad-faf34a7bb0.zip/node_modules/brace-expansion/index.js"(exports, module2) { + var concatMap = require_concat_map(); + var balanced = require_balanced_match(); + module2.exports = expandTop; + var escSlash = "\0SLASH" + Math.random() + "\0"; + var escOpen = "\0OPEN" + Math.random() + "\0"; + var escClose = "\0CLOSE" + Math.random() + "\0"; + var escComma = "\0COMMA" + Math.random() + "\0"; + var escPeriod = "\0PERIOD" + Math.random() + "\0"; + function numeric(str) { + return parseInt(str, 10) == str ? parseInt(str, 10) : str.charCodeAt(0); + } + function escapeBraces(str) { + return str.split("\\\\").join(escSlash).split("\\{").join(escOpen).split("\\}").join(escClose).split("\\,").join(escComma).split("\\.").join(escPeriod); + } + function unescapeBraces(str) { + return str.split(escSlash).join("\\").split(escOpen).join("{").split(escClose).join("}").split(escComma).join(",").split(escPeriod).join("."); + } + function parseCommaParts(str) { + if (!str) + return [""]; + var parts = []; + var m = balanced("{", "}", str); + if (!m) + return str.split(","); + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(","); + p[p.length - 1] += "{" + body + "}"; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length - 1] += postParts.shift(); + p.push.apply(p, postParts); + } + parts.push.apply(parts, p); + return parts; + } + function expandTop(str) { + if (!str) + return []; + if (str.substr(0, 2) === "{}") { + str = "\\{\\}" + str.substr(2); + } + return expand(escapeBraces(str), true).map(unescapeBraces); + } + function embrace(str) { + return "{" + str + "}"; + } + function isPadded(el) { + return /^-?0\d/.test(el); + } + function lte(i, y) { + return i <= y; + } + function gte(i, y) { + return i >= y; + } + function expand(str, isTop) { + var expansions = []; + var m = balanced("{", "}", str); + if (!m || /\$$/.test(m.pre)) + return [str]; + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(",") >= 0; + if (!isSequence && !isOptions) { + if (m.post.match(/,.*\}/)) { + str = m.pre + "{" + m.body + escClose + m.post; + return expand(str); + } + return [str]; + } + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length ? expand(m.post, false) : [""]; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + var pre = m.pre; + var post = m.post.length ? expand(m.post, false) : [""]; + var N; + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length); + var incr = n.length == 3 ? Math.abs(numeric(n[2])) : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + N = []; + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === "\\") + c = ""; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join("0"); + if (i < 0) + c = "-" + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { + return expand(el, false); + }); + } + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } + } + return expansions; + } + } +}); + +// pnp:minimatch-npm-3.0.4-6e76f51c23-66ac295f8a.zip/node_modules/minimatch/minimatch.js +var require_minimatch = __commonJS({ + "pnp:minimatch-npm-3.0.4-6e76f51c23-66ac295f8a.zip/node_modules/minimatch/minimatch.js"(exports, module2) { + module2.exports = minimatch; + minimatch.Minimatch = Minimatch; + var path2 = { sep: "/" }; + try { + path2 = require("path"); + } catch (er) { + } + var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {}; + var expand = require_brace_expansion(); + var plTypes = { + "!": { open: "(?:(?!(?:", close: "))[^/]*?)" }, + "?": { open: "(?:", close: ")?" }, + "+": { open: "(?:", close: ")+" }, + "*": { open: "(?:", close: ")*" }, + "@": { open: "(?:", close: ")" } + }; + var qmark = "[^/]"; + var star = qmark + "*?"; + var twoStarDot = "(?:(?!(?:\\/|^)(?:\\.{1,2})($|\\/)).)*?"; + var twoStarNoDot = "(?:(?!(?:\\/|^)\\.).)*?"; + var reSpecials = charSet("().*{}+?[]^$\\!"); + function charSet(s) { + return s.split("").reduce(function(set, c) { + set[c] = true; + return set; + }, {}); + } + var slashSplit = /\/+/; + minimatch.filter = filter; + function filter(pattern, options) { + options = options || {}; + return function(p, i, list) { + return minimatch(p, pattern, options); + }; + } + function ext(a, b) { + a = a || {}; + b = b || {}; + var t = {}; + Object.keys(b).forEach(function(k) { + t[k] = b[k]; + }); + Object.keys(a).forEach(function(k) { + t[k] = a[k]; + }); + return t; + } + minimatch.defaults = function(def) { + if (!def || !Object.keys(def).length) + return minimatch; + var orig = minimatch; + var m = function minimatch2(p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)); + }; + m.Minimatch = function Minimatch2(pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)); + }; + return m; + }; + Minimatch.defaults = function(def) { + if (!def || !Object.keys(def).length) + return Minimatch; + return minimatch.defaults(def).Minimatch; + }; + function minimatch(p, pattern, options) { + if (typeof pattern !== "string") { + throw new TypeError("glob pattern string required"); + } + if (!options) + options = {}; + if (!options.nocomment && pattern.charAt(0) === "#") { + return false; + } + if (pattern.trim() === "") + return p === ""; + return new Minimatch(pattern, options).match(p); + } + function Minimatch(pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options); + } + if (typeof pattern !== "string") { + throw new TypeError("glob pattern string required"); + } + if (!options) + options = {}; + pattern = pattern.trim(); + if (path2.sep !== "/") { + pattern = pattern.split(path2.sep).join("/"); + } + this.options = options; + this.set = []; + this.pattern = pattern; + this.regexp = null; + this.negate = false; + this.comment = false; + this.empty = false; + this.make(); + } + Minimatch.prototype.debug = function() { + }; + Minimatch.prototype.make = make; + function make() { + if (this._made) + return; + var pattern = this.pattern; + var options = this.options; + if (!options.nocomment && pattern.charAt(0) === "#") { + this.comment = true; + return; + } + if (!pattern) { + this.empty = true; + return; + } + this.parseNegate(); + var set = this.globSet = this.braceExpand(); + if (options.debug) + this.debug = console.error; + this.debug(this.pattern, set); + set = this.globParts = set.map(function(s) { + return s.split(slashSplit); + }); + this.debug(this.pattern, set); + set = set.map(function(s, si, set2) { + return s.map(this.parse, this); + }, this); + this.debug(this.pattern, set); + set = set.filter(function(s) { + return s.indexOf(false) === -1; + }); + this.debug(this.pattern, set); + this.set = set; + } + Minimatch.prototype.parseNegate = parseNegate; + function parseNegate() { + var pattern = this.pattern; + var negate = false; + var options = this.options; + var negateOffset = 0; + if (options.nonegate) + return; + for (var i = 0, l = pattern.length; i < l && pattern.charAt(i) === "!"; i++) { + negate = !negate; + negateOffset++; + } + if (negateOffset) + this.pattern = pattern.substr(negateOffset); + this.negate = negate; + } + minimatch.braceExpand = function(pattern, options) { + return braceExpand(pattern, options); + }; + Minimatch.prototype.braceExpand = braceExpand; + function braceExpand(pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options; + } else { + options = {}; + } + } + pattern = typeof pattern === "undefined" ? this.pattern : pattern; + if (typeof pattern === "undefined") { + throw new TypeError("undefined pattern"); + } + if (options.nobrace || !pattern.match(/\{.*\}/)) { + return [pattern]; + } + return expand(pattern); + } + Minimatch.prototype.parse = parse; + var SUBPARSE = {}; + function parse(pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError("pattern is too long"); + } + var options = this.options; + if (!options.noglobstar && pattern === "**") + return GLOBSTAR; + if (pattern === "") + return ""; + var re = ""; + var hasMagic = !!options.nocase; + var escaping = false; + var patternListStack = []; + var negativeLists = []; + var stateChar; + var inClass = false; + var reClassStart = -1; + var classStart = -1; + var patternStart = pattern.charAt(0) === "." ? "" : options.dot ? "(?!(?:^|\\/)\\.{1,2}(?:$|\\/))" : "(?!\\.)"; + var self = this; + function clearStateChar() { + if (stateChar) { + switch (stateChar) { + case "*": + re += star; + hasMagic = true; + break; + case "?": + re += qmark; + hasMagic = true; + break; + default: + re += "\\" + stateChar; + break; + } + self.debug("clearStateChar %j %j", stateChar, re); + stateChar = false; + } + } + for (var i = 0, len = pattern.length, c; i < len && (c = pattern.charAt(i)); i++) { + this.debug("%s %s %s %j", pattern, i, re, c); + if (escaping && reSpecials[c]) { + re += "\\" + c; + escaping = false; + continue; + } + switch (c) { + case "/": + return false; + case "\\": + clearStateChar(); + escaping = true; + continue; + case "?": + case "*": + case "+": + case "@": + case "!": + this.debug("%s %s %s %j <-- stateChar", pattern, i, re, c); + if (inClass) { + this.debug(" in class"); + if (c === "!" && i === classStart + 1) + c = "^"; + re += c; + continue; + } + self.debug("call clearStateChar %j", stateChar); + clearStateChar(); + stateChar = c; + if (options.noext) + clearStateChar(); + continue; + case "(": + if (inClass) { + re += "("; + continue; + } + if (!stateChar) { + re += "\\("; + continue; + } + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }); + re += stateChar === "!" ? "(?:(?!(?:" : "(?:"; + this.debug("plType %j %j", stateChar, re); + stateChar = false; + continue; + case ")": + if (inClass || !patternListStack.length) { + re += "\\)"; + continue; + } + clearStateChar(); + hasMagic = true; + var pl = patternListStack.pop(); + re += pl.close; + if (pl.type === "!") { + negativeLists.push(pl); + } + pl.reEnd = re.length; + continue; + case "|": + if (inClass || !patternListStack.length || escaping) { + re += "\\|"; + escaping = false; + continue; + } + clearStateChar(); + re += "|"; + continue; + case "[": + clearStateChar(); + if (inClass) { + re += "\\" + c; + continue; + } + inClass = true; + classStart = i; + reClassStart = re.length; + re += c; + continue; + case "]": + if (i === classStart + 1 || !inClass) { + re += "\\" + c; + escaping = false; + continue; + } + if (inClass) { + var cs = pattern.substring(classStart + 1, i); + try { + RegExp("[" + cs + "]"); + } catch (er) { + var sp = this.parse(cs, SUBPARSE); + re = re.substr(0, reClassStart) + "\\[" + sp[0] + "\\]"; + hasMagic = hasMagic || sp[1]; + inClass = false; + continue; + } + } + hasMagic = true; + inClass = false; + re += c; + continue; + default: + clearStateChar(); + if (escaping) { + escaping = false; + } else if (reSpecials[c] && !(c === "^" && inClass)) { + re += "\\"; + } + re += c; + } + } + if (inClass) { + cs = pattern.substr(classStart + 1); + sp = this.parse(cs, SUBPARSE); + re = re.substr(0, reClassStart) + "\\[" + sp[0]; + hasMagic = hasMagic || sp[1]; + } + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length); + this.debug("setting tail", re, pl); + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function(_, $1, $2) { + if (!$2) { + $2 = "\\"; + } + return $1 + $1 + $2 + "|"; + }); + this.debug("tail=%j\n %s", tail, tail, pl, re); + var t = pl.type === "*" ? star : pl.type === "?" ? qmark : "\\" + pl.type; + hasMagic = true; + re = re.slice(0, pl.reStart) + t + "\\(" + tail; + } + clearStateChar(); + if (escaping) { + re += "\\\\"; + } + var addPatternStart = false; + switch (re.charAt(0)) { + case ".": + case "[": + case "(": + addPatternStart = true; + } + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n]; + var nlBefore = re.slice(0, nl.reStart); + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8); + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd); + var nlAfter = re.slice(nl.reEnd); + nlLast += nlAfter; + var openParensBefore = nlBefore.split("(").length - 1; + var cleanAfter = nlAfter; + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, ""); + } + nlAfter = cleanAfter; + var dollar = ""; + if (nlAfter === "" && isSub !== SUBPARSE) { + dollar = "$"; + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast; + re = newRe; + } + if (re !== "" && hasMagic) { + re = "(?=.)" + re; + } + if (addPatternStart) { + re = patternStart + re; + } + if (isSub === SUBPARSE) { + return [re, hasMagic]; + } + if (!hasMagic) { + return globUnescape(pattern); + } + var flags = options.nocase ? "i" : ""; + try { + var regExp = new RegExp("^" + re + "$", flags); + } catch (er) { + return new RegExp("$."); + } + regExp._glob = pattern; + regExp._src = re; + return regExp; + } + minimatch.makeRe = function(pattern, options) { + return new Minimatch(pattern, options || {}).makeRe(); + }; + Minimatch.prototype.makeRe = makeRe; + function makeRe() { + if (this.regexp || this.regexp === false) + return this.regexp; + var set = this.set; + if (!set.length) { + this.regexp = false; + return this.regexp; + } + var options = this.options; + var twoStar = options.noglobstar ? star : options.dot ? twoStarDot : twoStarNoDot; + var flags = options.nocase ? "i" : ""; + var re = set.map(function(pattern) { + return pattern.map(function(p) { + return p === GLOBSTAR ? twoStar : typeof p === "string" ? regExpEscape(p) : p._src; + }).join("\\/"); + }).join("|"); + re = "^(?:" + re + ")$"; + if (this.negate) + re = "^(?!" + re + ").*$"; + try { + this.regexp = new RegExp(re, flags); + } catch (ex) { + this.regexp = false; + } + return this.regexp; + } + minimatch.match = function(list, pattern, options) { + options = options || {}; + var mm = new Minimatch(pattern, options); + list = list.filter(function(f) { + return mm.match(f); + }); + if (mm.options.nonull && !list.length) { + list.push(pattern); + } + return list; + }; + Minimatch.prototype.match = match; + function match(f, partial) { + this.debug("match", f, this.pattern); + if (this.comment) + return false; + if (this.empty) + return f === ""; + if (f === "/" && partial) + return true; + var options = this.options; + if (path2.sep !== "/") { + f = f.split(path2.sep).join("/"); + } + f = f.split(slashSplit); + this.debug(this.pattern, "split", f); + var set = this.set; + this.debug(this.pattern, "set", set); + var filename; + var i; + for (i = f.length - 1; i >= 0; i--) { + filename = f[i]; + if (filename) + break; + } + for (i = 0; i < set.length; i++) { + var pattern = set[i]; + var file = f; + if (options.matchBase && pattern.length === 1) { + file = [filename]; + } + var hit = this.matchOne(file, pattern, partial); + if (hit) { + if (options.flipNegate) + return true; + return !this.negate; + } + } + if (options.flipNegate) + return false; + return this.negate; + } + Minimatch.prototype.matchOne = function(file, pattern, partial) { + var options = this.options; + this.debug("matchOne", { "this": this, file, pattern }); + this.debug("matchOne", file.length, pattern.length); + for (var fi = 0, pi = 0, fl = file.length, pl = pattern.length; fi < fl && pi < pl; fi++, pi++) { + this.debug("matchOne loop"); + var p = pattern[pi]; + var f = file[fi]; + this.debug(pattern, p, f); + if (p === false) + return false; + if (p === GLOBSTAR) { + this.debug("GLOBSTAR", [pattern, p, f]); + var fr = fi; + var pr = pi + 1; + if (pr === pl) { + this.debug("** at the end"); + for (; fi < fl; fi++) { + if (file[fi] === "." || file[fi] === ".." || !options.dot && file[fi].charAt(0) === ".") + return false; + } + return true; + } + while (fr < fl) { + var swallowee = file[fr]; + this.debug("\nglobstar while", file, fr, pattern, pr, swallowee); + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug("globstar found match!", fr, fl, swallowee); + return true; + } else { + if (swallowee === "." || swallowee === ".." || !options.dot && swallowee.charAt(0) === ".") { + this.debug("dot detected!", file, fr, pattern, pr); + break; + } + this.debug("globstar swallow a segment, and continue"); + fr++; + } + } + if (partial) { + this.debug("\n>>> no match, partial?", file, fr, pattern, pr); + if (fr === fl) + return true; + } + return false; + } + var hit; + if (typeof p === "string") { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase(); + } else { + hit = f === p; + } + this.debug("string match", p, f, hit); + } else { + hit = f.match(p); + this.debug("pattern match", p, f, hit); + } + if (!hit) + return false; + } + if (fi === fl && pi === pl) { + return true; + } else if (fi === fl) { + return partial; + } else if (pi === pl) { + var emptyFileEnd = fi === fl - 1 && file[fi] === ""; + return emptyFileEnd; + } + throw new Error("wtf?"); + }; + function globUnescape(s) { + return s.replace(/\\(.)/g, "$1"); + } + function regExpEscape(s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); + } + } +}); + +// pnp:inherits-npm-2.0.4-c66b3957a0-4a48a73384.zip/node_modules/inherits/inherits_browser.js +var require_inherits_browser = __commonJS({ + "pnp:inherits-npm-2.0.4-c66b3957a0-4a48a73384.zip/node_modules/inherits/inherits_browser.js"(exports, module2) { + if (typeof Object.create === "function") { + module2.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + } + }; + } else { + module2.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor; + var TempCtor = function() { + }; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + } + }; + } + } +}); + +// pnp:inherits-npm-2.0.4-c66b3957a0-4a48a73384.zip/node_modules/inherits/inherits.js +var require_inherits = __commonJS({ + "pnp:inherits-npm-2.0.4-c66b3957a0-4a48a73384.zip/node_modules/inherits/inherits.js"(exports, module2) { + try { + util = require("util"); + if (typeof util.inherits !== "function") + throw ""; + module2.exports = util.inherits; + } catch (e) { + module2.exports = require_inherits_browser(); + } + var util; + } +}); + +// pnp:path-is-absolute-npm-1.0.1-31bc695ffd-060840f92c.zip/node_modules/path-is-absolute/index.js +var require_path_is_absolute = __commonJS({ + "pnp:path-is-absolute-npm-1.0.1-31bc695ffd-060840f92c.zip/node_modules/path-is-absolute/index.js"(exports, module2) { + "use strict"; + function posix(path2) { + return path2.charAt(0) === "/"; + } + function win32(path2) { + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path2); + var device = result[1] || ""; + var isUnc = Boolean(device && device.charAt(1) !== ":"); + return Boolean(result[2] || isUnc); + } + module2.exports = process.platform === "win32" ? win32 : posix; + module2.exports.posix = posix; + module2.exports.win32 = win32; + } +}); + +// pnp:glob-npm-7.1.7-5698ad9c48-b61f48973b.zip/node_modules/glob/common.js +var require_common = __commonJS({ + "pnp:glob-npm-7.1.7-5698ad9c48-b61f48973b.zip/node_modules/glob/common.js"(exports) { + exports.setopts = setopts; + exports.ownProp = ownProp; + exports.makeAbs = makeAbs; + exports.finish = finish; + exports.mark = mark; + exports.isIgnored = isIgnored; + exports.childrenIgnored = childrenIgnored; + function ownProp(obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field); + } + var path2 = require("path"); + var minimatch = require_minimatch(); + var isAbsolute = require_path_is_absolute(); + var Minimatch = minimatch.Minimatch; + function alphasort(a, b) { + return a.localeCompare(b, "en"); + } + function setupIgnores(self, options) { + self.ignore = options.ignore || []; + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore]; + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap); + } + } + function ignoreMap(pattern) { + var gmatcher = null; + if (pattern.slice(-3) === "/**") { + var gpattern = pattern.replace(/(\/\*\*)+$/, ""); + gmatcher = new Minimatch(gpattern, { dot: true }); + } + return { + matcher: new Minimatch(pattern, { dot: true }), + gmatcher + }; + } + function setopts(self, pattern, options) { + if (!options) + options = {}; + if (options.matchBase && pattern.indexOf("/") === -1) { + if (options.noglobstar) { + throw new Error("base matching requires globstar"); + } + pattern = "**/" + pattern; + } + self.silent = !!options.silent; + self.pattern = pattern; + self.strict = options.strict !== false; + self.realpath = !!options.realpath; + self.realpathCache = options.realpathCache || Object.create(null); + self.follow = !!options.follow; + self.dot = !!options.dot; + self.mark = !!options.mark; + self.nodir = !!options.nodir; + if (self.nodir) + self.mark = true; + self.sync = !!options.sync; + self.nounique = !!options.nounique; + self.nonull = !!options.nonull; + self.nosort = !!options.nosort; + self.nocase = !!options.nocase; + self.stat = !!options.stat; + self.noprocess = !!options.noprocess; + self.absolute = !!options.absolute; + self.maxLength = options.maxLength || Infinity; + self.cache = options.cache || Object.create(null); + self.statCache = options.statCache || Object.create(null); + self.symlinks = options.symlinks || Object.create(null); + setupIgnores(self, options); + self.changedCwd = false; + var cwd = process.cwd(); + if (!ownProp(options, "cwd")) + self.cwd = cwd; + else { + self.cwd = path2.resolve(options.cwd); + self.changedCwd = self.cwd !== cwd; + } + self.root = options.root || path2.resolve(self.cwd, "/"); + self.root = path2.resolve(self.root); + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/"); + self.cwdAbs = isAbsolute(self.cwd) ? self.cwd : makeAbs(self, self.cwd); + if (process.platform === "win32") + self.cwdAbs = self.cwdAbs.replace(/\\/g, "/"); + self.nomount = !!options.nomount; + options.nonegate = true; + options.nocomment = true; + self.minimatch = new Minimatch(pattern, options); + self.options = self.minimatch.options; + } + function finish(self) { + var nou = self.nounique; + var all = nou ? [] : Object.create(null); + for (var i = 0, l = self.matches.length; i < l; i++) { + var matches = self.matches[i]; + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + var literal = self.minimatch.globSet[i]; + if (nou) + all.push(literal); + else + all[literal] = true; + } + } else { + var m = Object.keys(matches); + if (nou) + all.push.apply(all, m); + else + m.forEach(function(m2) { + all[m2] = true; + }); + } + } + if (!nou) + all = Object.keys(all); + if (!self.nosort) + all = all.sort(alphasort); + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]); + } + if (self.nodir) { + all = all.filter(function(e) { + var notDir = !/\/$/.test(e); + var c = self.cache[e] || self.cache[makeAbs(self, e)]; + if (notDir && c) + notDir = c !== "DIR" && !Array.isArray(c); + return notDir; + }); + } + } + if (self.ignore.length) + all = all.filter(function(m2) { + return !isIgnored(self, m2); + }); + self.found = all; + } + function mark(self, p) { + var abs = makeAbs(self, p); + var c = self.cache[abs]; + var m = p; + if (c) { + var isDir = c === "DIR" || Array.isArray(c); + var slash = p.slice(-1) === "/"; + if (isDir && !slash) + m += "/"; + else if (!isDir && slash) + m = m.slice(0, -1); + if (m !== p) { + var mabs = makeAbs(self, m); + self.statCache[mabs] = self.statCache[abs]; + self.cache[mabs] = self.cache[abs]; + } + } + return m; + } + function makeAbs(self, f) { + var abs = f; + if (f.charAt(0) === "/") { + abs = path2.join(self.root, f); + } else if (isAbsolute(f) || f === "") { + abs = f; + } else if (self.changedCwd) { + abs = path2.resolve(self.cwd, f); + } else { + abs = path2.resolve(f); + } + if (process.platform === "win32") + abs = abs.replace(/\\/g, "/"); + return abs; + } + function isIgnored(self, path3) { + if (!self.ignore.length) + return false; + return self.ignore.some(function(item) { + return item.matcher.match(path3) || !!(item.gmatcher && item.gmatcher.match(path3)); + }); + } + function childrenIgnored(self, path3) { + if (!self.ignore.length) + return false; + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path3)); + }); + } + } +}); + +// pnp:glob-npm-7.1.7-5698ad9c48-b61f48973b.zip/node_modules/glob/sync.js +var require_sync = __commonJS({ + "pnp:glob-npm-7.1.7-5698ad9c48-b61f48973b.zip/node_modules/glob/sync.js"(exports, module2) { + module2.exports = globSync; + globSync.GlobSync = GlobSync; + var fs4 = require("fs"); + var rp = require_fs(); + var minimatch = require_minimatch(); + var Minimatch = minimatch.Minimatch; + var Glob2 = require_glob().Glob; + var util = require("util"); + var path2 = require("path"); + var assert = require("assert"); + var isAbsolute = require_path_is_absolute(); + var common = require_common(); + var setopts = common.setopts; + var ownProp = common.ownProp; + var childrenIgnored = common.childrenIgnored; + var isIgnored = common.isIgnored; + function globSync(pattern, options) { + if (typeof options === "function" || arguments.length === 3) + throw new TypeError("callback provided to sync glob\nSee: https://github.com/isaacs/node-glob/issues/167"); + return new GlobSync(pattern, options).found; + } + function GlobSync(pattern, options) { + if (!pattern) + throw new Error("must provide pattern"); + if (typeof options === "function" || arguments.length === 3) + throw new TypeError("callback provided to sync glob\nSee: https://github.com/isaacs/node-glob/issues/167"); + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options); + setopts(this, pattern, options); + if (this.noprocess) + return this; + var n = this.minimatch.set.length; + this.matches = new Array(n); + for (var i = 0; i < n; i++) { + this._process(this.minimatch.set[i], i, false); + } + this._finish(); + } + GlobSync.prototype._finish = function() { + assert(this instanceof GlobSync); + if (this.realpath) { + var self = this; + this.matches.forEach(function(matchset, index) { + var set = self.matches[index] = Object.create(null); + for (var p in matchset) { + try { + p = self._makeAbs(p); + var real = rp.realpathSync(p, self.realpathCache); + set[real] = true; + } catch (er) { + if (er.syscall === "stat") + set[self._makeAbs(p)] = true; + else + throw er; + } + } + }); + } + common.finish(this); + }; + GlobSync.prototype._process = function(pattern, index, inGlobStar) { + assert(this instanceof GlobSync); + var n = 0; + while (typeof pattern[n] === "string") { + n++; + } + var prefix; + switch (n) { + case pattern.length: + this._processSimple(pattern.join("/"), index); + return; + case 0: + prefix = null; + break; + default: + prefix = pattern.slice(0, n).join("/"); + break; + } + var remain = pattern.slice(n); + var read; + if (prefix === null) + read = "."; + else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) { + if (!prefix || !isAbsolute(prefix)) + prefix = "/" + prefix; + read = prefix; + } else + read = prefix; + var abs = this._makeAbs(read); + if (childrenIgnored(this, read)) + return; + var isGlobStar = remain[0] === minimatch.GLOBSTAR; + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar); + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar); + }; + GlobSync.prototype._processReaddir = function(prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar); + if (!entries) + return; + var pn = remain[0]; + var negate = !!this.minimatch.negate; + var rawGlob = pn._glob; + var dotOk = this.dot || rawGlob.charAt(0) === "."; + var matchedEntries = []; + for (var i = 0; i < entries.length; i++) { + var e = entries[i]; + if (e.charAt(0) !== "." || dotOk) { + var m; + if (negate && !prefix) { + m = !e.match(pn); + } else { + m = e.match(pn); + } + if (m) + matchedEntries.push(e); + } + } + var len = matchedEntries.length; + if (len === 0) + return; + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null); + for (var i = 0; i < len; i++) { + var e = matchedEntries[i]; + if (prefix) { + if (prefix.slice(-1) !== "/") + e = prefix + "/" + e; + else + e = prefix + e; + } + if (e.charAt(0) === "/" && !this.nomount) { + e = path2.join(this.root, e); + } + this._emitMatch(index, e); + } + return; + } + remain.shift(); + for (var i = 0; i < len; i++) { + var e = matchedEntries[i]; + var newPattern; + if (prefix) + newPattern = [prefix, e]; + else + newPattern = [e]; + this._process(newPattern.concat(remain), index, inGlobStar); + } + }; + GlobSync.prototype._emitMatch = function(index, e) { + if (isIgnored(this, e)) + return; + var abs = this._makeAbs(e); + if (this.mark) + e = this._mark(e); + if (this.absolute) { + e = abs; + } + if (this.matches[index][e]) + return; + if (this.nodir) { + var c = this.cache[abs]; + if (c === "DIR" || Array.isArray(c)) + return; + } + this.matches[index][e] = true; + if (this.stat) + this._stat(e); + }; + GlobSync.prototype._readdirInGlobStar = function(abs) { + if (this.follow) + return this._readdir(abs, false); + var entries; + var lstat; + var stat; + try { + lstat = fs4.lstatSync(abs); + } catch (er) { + if (er.code === "ENOENT") { + return null; + } + } + var isSym = lstat && lstat.isSymbolicLink(); + this.symlinks[abs] = isSym; + if (!isSym && lstat && !lstat.isDirectory()) + this.cache[abs] = "FILE"; + else + entries = this._readdir(abs, false); + return entries; + }; + GlobSync.prototype._readdir = function(abs, inGlobStar) { + var entries; + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs); + if (ownProp(this.cache, abs)) { + var c = this.cache[abs]; + if (!c || c === "FILE") + return null; + if (Array.isArray(c)) + return c; + } + try { + return this._readdirEntries(abs, fs4.readdirSync(abs)); + } catch (er) { + this._readdirError(abs, er); + return null; + } + }; + GlobSync.prototype._readdirEntries = function(abs, entries) { + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i++) { + var e = entries[i]; + if (abs === "/") + e = abs + e; + else + e = abs + "/" + e; + this.cache[e] = true; + } + } + this.cache[abs] = entries; + return entries; + }; + GlobSync.prototype._readdirError = function(f, er) { + switch (er.code) { + case "ENOTSUP": + case "ENOTDIR": + var abs = this._makeAbs(f); + this.cache[abs] = "FILE"; + if (abs === this.cwdAbs) { + var error = new Error(er.code + " invalid cwd " + this.cwd); + error.path = this.cwd; + error.code = er.code; + throw error; + } + break; + case "ENOENT": + case "ELOOP": + case "ENAMETOOLONG": + case "UNKNOWN": + this.cache[this._makeAbs(f)] = false; + break; + default: + this.cache[this._makeAbs(f)] = false; + if (this.strict) + throw er; + if (!this.silent) + console.error("glob error", er); + break; + } + }; + GlobSync.prototype._processGlobStar = function(prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar); + if (!entries) + return; + var remainWithoutGlobStar = remain.slice(1); + var gspref = prefix ? [prefix] : []; + var noGlobStar = gspref.concat(remainWithoutGlobStar); + this._process(noGlobStar, index, false); + var len = entries.length; + var isSym = this.symlinks[abs]; + if (isSym && inGlobStar) + return; + for (var i = 0; i < len; i++) { + var e = entries[i]; + if (e.charAt(0) === "." && !this.dot) + continue; + var instead = gspref.concat(entries[i], remainWithoutGlobStar); + this._process(instead, index, true); + var below = gspref.concat(entries[i], remain); + this._process(below, index, true); + } + }; + GlobSync.prototype._processSimple = function(prefix, index) { + var exists = this._stat(prefix); + if (!this.matches[index]) + this.matches[index] = Object.create(null); + if (!exists) + return; + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix); + if (prefix.charAt(0) === "/") { + prefix = path2.join(this.root, prefix); + } else { + prefix = path2.resolve(this.root, prefix); + if (trail) + prefix += "/"; + } + } + if (process.platform === "win32") + prefix = prefix.replace(/\\/g, "/"); + this._emitMatch(index, prefix); + }; + GlobSync.prototype._stat = function(f) { + var abs = this._makeAbs(f); + var needDir = f.slice(-1) === "/"; + if (f.length > this.maxLength) + return false; + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs]; + if (Array.isArray(c)) + c = "DIR"; + if (!needDir || c === "DIR") + return c; + if (needDir && c === "FILE") + return false; + } + var exists; + var stat = this.statCache[abs]; + if (!stat) { + var lstat; + try { + lstat = fs4.lstatSync(abs); + } catch (er) { + if (er && (er.code === "ENOENT" || er.code === "ENOTDIR")) { + this.statCache[abs] = false; + return false; + } + } + if (lstat && lstat.isSymbolicLink()) { + try { + stat = fs4.statSync(abs); + } catch (er) { + stat = lstat; + } + } else { + stat = lstat; + } + } + this.statCache[abs] = stat; + var c = true; + if (stat) + c = stat.isDirectory() ? "DIR" : "FILE"; + this.cache[abs] = this.cache[abs] || c; + if (needDir && c === "FILE") + return false; + return c; + }; + GlobSync.prototype._mark = function(p) { + return common.mark(this, p); + }; + GlobSync.prototype._makeAbs = function(f) { + return common.makeAbs(this, f); + }; + } +}); + +// pnp:wrappy-npm-1.0.2-916de4d4b3-159da4805f.zip/node_modules/wrappy/wrappy.js +var require_wrappy = __commonJS({ + "pnp:wrappy-npm-1.0.2-916de4d4b3-159da4805f.zip/node_modules/wrappy/wrappy.js"(exports, module2) { + module2.exports = wrappy; + function wrappy(fn, cb) { + if (fn && cb) + return wrappy(fn)(cb); + if (typeof fn !== "function") + throw new TypeError("need wrapper function"); + Object.keys(fn).forEach(function(k) { + wrapper[k] = fn[k]; + }); + return wrapper; + function wrapper() { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + var ret = fn.apply(this, args); + var cb2 = args[args.length - 1]; + if (typeof ret === "function" && ret !== cb2) { + Object.keys(cb2).forEach(function(k) { + ret[k] = cb2[k]; + }); + } + return ret; + } + } + } +}); + +// pnp:once-npm-1.4.0-ccf03ef07a-cd0a885013.zip/node_modules/once/once.js +var require_once = __commonJS({ + "pnp:once-npm-1.4.0-ccf03ef07a-cd0a885013.zip/node_modules/once/once.js"(exports, module2) { + var wrappy = require_wrappy(); + module2.exports = wrappy(once); + module2.exports.strict = wrappy(onceStrict); + once.proto = once(function() { + Object.defineProperty(Function.prototype, "once", { + value: function() { + return once(this); + }, + configurable: true + }); + Object.defineProperty(Function.prototype, "onceStrict", { + value: function() { + return onceStrict(this); + }, + configurable: true + }); + }); + function once(fn) { + var f = function() { + if (f.called) + return f.value; + f.called = true; + return f.value = fn.apply(this, arguments); + }; + f.called = false; + return f; + } + function onceStrict(fn) { + var f = function() { + if (f.called) + throw new Error(f.onceError); + f.called = true; + return f.value = fn.apply(this, arguments); + }; + var name = fn.name || "Function wrapped with `once`"; + f.onceError = name + " shouldn't be called more than once"; + f.called = false; + return f; + } + } +}); + +// pnp:inflight-npm-1.0.6-ccedb4b908-f4f76aa072.zip/node_modules/inflight/inflight.js +var require_inflight = __commonJS({ + "pnp:inflight-npm-1.0.6-ccedb4b908-f4f76aa072.zip/node_modules/inflight/inflight.js"(exports, module2) { + var wrappy = require_wrappy(); + var reqs = Object.create(null); + var once = require_once(); + module2.exports = wrappy(inflight); + function inflight(key, cb) { + if (reqs[key]) { + reqs[key].push(cb); + return null; + } else { + reqs[key] = [cb]; + return makeres(key); + } + } + function makeres(key) { + return once(function RES() { + var cbs = reqs[key]; + var len = cbs.length; + var args = slice(arguments); + try { + for (var i = 0; i < len; i++) { + cbs[i].apply(null, args); + } + } finally { + if (cbs.length > len) { + cbs.splice(0, len); + process.nextTick(function() { + RES.apply(null, args); + }); + } else { + delete reqs[key]; + } + } + }); + } + function slice(args) { + var length = args.length; + var array = []; + for (var i = 0; i < length; i++) + array[i] = args[i]; + return array; + } + } +}); + +// pnp:glob-npm-7.1.7-5698ad9c48-b61f48973b.zip/node_modules/glob/glob.js +var require_glob = __commonJS({ + "pnp:glob-npm-7.1.7-5698ad9c48-b61f48973b.zip/node_modules/glob/glob.js"(exports, module2) { + module2.exports = glob2; + var fs4 = require("fs"); + var rp = require_fs(); + var minimatch = require_minimatch(); + var Minimatch = minimatch.Minimatch; + var inherits = require_inherits(); + var EE = require("events").EventEmitter; + var path2 = require("path"); + var assert = require("assert"); + var isAbsolute = require_path_is_absolute(); + var globSync = require_sync(); + var common = require_common(); + var setopts = common.setopts; + var ownProp = common.ownProp; + var inflight = require_inflight(); + var util = require("util"); + var childrenIgnored = common.childrenIgnored; + var isIgnored = common.isIgnored; + var once = require_once(); + function glob2(pattern, options, cb) { + if (typeof options === "function") + cb = options, options = {}; + if (!options) + options = {}; + if (options.sync) { + if (cb) + throw new TypeError("callback provided to sync glob"); + return globSync(pattern, options); + } + return new Glob2(pattern, options, cb); + } + glob2.sync = globSync; + var GlobSync = glob2.GlobSync = globSync.GlobSync; + glob2.glob = glob2; + function extend(origin, add) { + if (add === null || typeof add !== "object") { + return origin; + } + var keys = Object.keys(add); + var i = keys.length; + while (i--) { + origin[keys[i]] = add[keys[i]]; + } + return origin; + } + glob2.hasMagic = function(pattern, options_) { + var options = extend({}, options_); + options.noprocess = true; + var g = new Glob2(pattern, options); + var set = g.minimatch.set; + if (!pattern) + return false; + if (set.length > 1) + return true; + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== "string") + return true; + } + return false; + }; + glob2.Glob = Glob2; + inherits(Glob2, EE); + function Glob2(pattern, options, cb) { + if (typeof options === "function") { + cb = options; + options = null; + } + if (options && options.sync) { + if (cb) + throw new TypeError("callback provided to sync glob"); + return new GlobSync(pattern, options); + } + if (!(this instanceof Glob2)) + return new Glob2(pattern, options, cb); + setopts(this, pattern, options); + this._didRealPath = false; + var n = this.minimatch.set.length; + this.matches = new Array(n); + if (typeof cb === "function") { + cb = once(cb); + this.on("error", cb); + this.on("end", function(matches) { + cb(null, matches); + }); + } + var self = this; + this._processing = 0; + this._emitQueue = []; + this._processQueue = []; + this.paused = false; + if (this.noprocess) + return this; + if (n === 0) + return done(); + var sync = true; + for (var i = 0; i < n; i++) { + this._process(this.minimatch.set[i], i, false, done); + } + sync = false; + function done() { + --self._processing; + if (self._processing <= 0) { + if (sync) { + process.nextTick(function() { + self._finish(); + }); + } else { + self._finish(); + } + } + } + } + Glob2.prototype._finish = function() { + assert(this instanceof Glob2); + if (this.aborted) + return; + if (this.realpath && !this._didRealpath) + return this._realpath(); + common.finish(this); + this.emit("end", this.found); + }; + Glob2.prototype._realpath = function() { + if (this._didRealpath) + return; + this._didRealpath = true; + var n = this.matches.length; + if (n === 0) + return this._finish(); + var self = this; + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next); + function next() { + if (--n === 0) + self._finish(); + } + }; + Glob2.prototype._realpathSet = function(index, cb) { + var matchset = this.matches[index]; + if (!matchset) + return cb(); + var found = Object.keys(matchset); + var self = this; + var n = found.length; + if (n === 0) + return cb(); + var set = this.matches[index] = Object.create(null); + found.forEach(function(p, i) { + p = self._makeAbs(p); + rp.realpath(p, self.realpathCache, function(er, real) { + if (!er) + set[real] = true; + else if (er.syscall === "stat") + set[p] = true; + else + self.emit("error", er); + if (--n === 0) { + self.matches[index] = set; + cb(); + } + }); + }); + }; + Glob2.prototype._mark = function(p) { + return common.mark(this, p); + }; + Glob2.prototype._makeAbs = function(f) { + return common.makeAbs(this, f); + }; + Glob2.prototype.abort = function() { + this.aborted = true; + this.emit("abort"); + }; + Glob2.prototype.pause = function() { + if (!this.paused) { + this.paused = true; + this.emit("pause"); + } + }; + Glob2.prototype.resume = function() { + if (this.paused) { + this.emit("resume"); + this.paused = false; + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0); + this._emitQueue.length = 0; + for (var i = 0; i < eq.length; i++) { + var e = eq[i]; + this._emitMatch(e[0], e[1]); + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0); + this._processQueue.length = 0; + for (var i = 0; i < pq.length; i++) { + var p = pq[i]; + this._processing--; + this._process(p[0], p[1], p[2], p[3]); + } + } + } + }; + Glob2.prototype._process = function(pattern, index, inGlobStar, cb) { + assert(this instanceof Glob2); + assert(typeof cb === "function"); + if (this.aborted) + return; + this._processing++; + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]); + return; + } + var n = 0; + while (typeof pattern[n] === "string") { + n++; + } + var prefix; + switch (n) { + case pattern.length: + this._processSimple(pattern.join("/"), index, cb); + return; + case 0: + prefix = null; + break; + default: + prefix = pattern.slice(0, n).join("/"); + break; + } + var remain = pattern.slice(n); + var read; + if (prefix === null) + read = "."; + else if (isAbsolute(prefix) || isAbsolute(pattern.join("/"))) { + if (!prefix || !isAbsolute(prefix)) + prefix = "/" + prefix; + read = prefix; + } else + read = prefix; + var abs = this._makeAbs(read); + if (childrenIgnored(this, read)) + return cb(); + var isGlobStar = remain[0] === minimatch.GLOBSTAR; + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb); + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb); + }; + Glob2.prototype._processReaddir = function(prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this; + this._readdir(abs, inGlobStar, function(er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb); + }); + }; + Glob2.prototype._processReaddir2 = function(prefix, read, abs, remain, index, inGlobStar, entries, cb) { + if (!entries) + return cb(); + var pn = remain[0]; + var negate = !!this.minimatch.negate; + var rawGlob = pn._glob; + var dotOk = this.dot || rawGlob.charAt(0) === "."; + var matchedEntries = []; + for (var i = 0; i < entries.length; i++) { + var e = entries[i]; + if (e.charAt(0) !== "." || dotOk) { + var m; + if (negate && !prefix) { + m = !e.match(pn); + } else { + m = e.match(pn); + } + if (m) + matchedEntries.push(e); + } + } + var len = matchedEntries.length; + if (len === 0) + return cb(); + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null); + for (var i = 0; i < len; i++) { + var e = matchedEntries[i]; + if (prefix) { + if (prefix !== "/") + e = prefix + "/" + e; + else + e = prefix + e; + } + if (e.charAt(0) === "/" && !this.nomount) { + e = path2.join(this.root, e); + } + this._emitMatch(index, e); + } + return cb(); + } + remain.shift(); + for (var i = 0; i < len; i++) { + var e = matchedEntries[i]; + var newPattern; + if (prefix) { + if (prefix !== "/") + e = prefix + "/" + e; + else + e = prefix + e; + } + this._process([e].concat(remain), index, inGlobStar, cb); + } + cb(); + }; + Glob2.prototype._emitMatch = function(index, e) { + if (this.aborted) + return; + if (isIgnored(this, e)) + return; + if (this.paused) { + this._emitQueue.push([index, e]); + return; + } + var abs = isAbsolute(e) ? e : this._makeAbs(e); + if (this.mark) + e = this._mark(e); + if (this.absolute) + e = abs; + if (this.matches[index][e]) + return; + if (this.nodir) { + var c = this.cache[abs]; + if (c === "DIR" || Array.isArray(c)) + return; + } + this.matches[index][e] = true; + var st = this.statCache[abs]; + if (st) + this.emit("stat", e, st); + this.emit("match", e); + }; + Glob2.prototype._readdirInGlobStar = function(abs, cb) { + if (this.aborted) + return; + if (this.follow) + return this._readdir(abs, false, cb); + var lstatkey = "lstat\0" + abs; + var self = this; + var lstatcb = inflight(lstatkey, lstatcb_); + if (lstatcb) + fs4.lstat(abs, lstatcb); + function lstatcb_(er, lstat) { + if (er && er.code === "ENOENT") + return cb(); + var isSym = lstat && lstat.isSymbolicLink(); + self.symlinks[abs] = isSym; + if (!isSym && lstat && !lstat.isDirectory()) { + self.cache[abs] = "FILE"; + cb(); + } else + self._readdir(abs, false, cb); + } + }; + Glob2.prototype._readdir = function(abs, inGlobStar, cb) { + if (this.aborted) + return; + cb = inflight("readdir\0" + abs + "\0" + inGlobStar, cb); + if (!cb) + return; + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb); + if (ownProp(this.cache, abs)) { + var c = this.cache[abs]; + if (!c || c === "FILE") + return cb(); + if (Array.isArray(c)) + return cb(null, c); + } + var self = this; + fs4.readdir(abs, readdirCb(this, abs, cb)); + }; + function readdirCb(self, abs, cb) { + return function(er, entries) { + if (er) + self._readdirError(abs, er, cb); + else + self._readdirEntries(abs, entries, cb); + }; + } + Glob2.prototype._readdirEntries = function(abs, entries, cb) { + if (this.aborted) + return; + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i++) { + var e = entries[i]; + if (abs === "/") + e = abs + e; + else + e = abs + "/" + e; + this.cache[e] = true; + } + } + this.cache[abs] = entries; + return cb(null, entries); + }; + Glob2.prototype._readdirError = function(f, er, cb) { + if (this.aborted) + return; + switch (er.code) { + case "ENOTSUP": + case "ENOTDIR": + var abs = this._makeAbs(f); + this.cache[abs] = "FILE"; + if (abs === this.cwdAbs) { + var error = new Error(er.code + " invalid cwd " + this.cwd); + error.path = this.cwd; + error.code = er.code; + this.emit("error", error); + this.abort(); + } + break; + case "ENOENT": + case "ELOOP": + case "ENAMETOOLONG": + case "UNKNOWN": + this.cache[this._makeAbs(f)] = false; + break; + default: + this.cache[this._makeAbs(f)] = false; + if (this.strict) { + this.emit("error", er); + this.abort(); + } + if (!this.silent) + console.error("glob error", er); + break; + } + return cb(); + }; + Glob2.prototype._processGlobStar = function(prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this; + this._readdir(abs, inGlobStar, function(er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb); + }); + }; + Glob2.prototype._processGlobStar2 = function(prefix, read, abs, remain, index, inGlobStar, entries, cb) { + if (!entries) + return cb(); + var remainWithoutGlobStar = remain.slice(1); + var gspref = prefix ? [prefix] : []; + var noGlobStar = gspref.concat(remainWithoutGlobStar); + this._process(noGlobStar, index, false, cb); + var isSym = this.symlinks[abs]; + var len = entries.length; + if (isSym && inGlobStar) + return cb(); + for (var i = 0; i < len; i++) { + var e = entries[i]; + if (e.charAt(0) === "." && !this.dot) + continue; + var instead = gspref.concat(entries[i], remainWithoutGlobStar); + this._process(instead, index, true, cb); + var below = gspref.concat(entries[i], remain); + this._process(below, index, true, cb); + } + cb(); + }; + Glob2.prototype._processSimple = function(prefix, index, cb) { + var self = this; + this._stat(prefix, function(er, exists) { + self._processSimple2(prefix, index, er, exists, cb); + }); + }; + Glob2.prototype._processSimple2 = function(prefix, index, er, exists, cb) { + if (!this.matches[index]) + this.matches[index] = Object.create(null); + if (!exists) + return cb(); + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix); + if (prefix.charAt(0) === "/") { + prefix = path2.join(this.root, prefix); + } else { + prefix = path2.resolve(this.root, prefix); + if (trail) + prefix += "/"; + } + } + if (process.platform === "win32") + prefix = prefix.replace(/\\/g, "/"); + this._emitMatch(index, prefix); + cb(); + }; + Glob2.prototype._stat = function(f, cb) { + var abs = this._makeAbs(f); + var needDir = f.slice(-1) === "/"; + if (f.length > this.maxLength) + return cb(); + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs]; + if (Array.isArray(c)) + c = "DIR"; + if (!needDir || c === "DIR") + return cb(null, c); + if (needDir && c === "FILE") + return cb(); + } + var exists; + var stat = this.statCache[abs]; + if (stat !== void 0) { + if (stat === false) + return cb(null, stat); + else { + var type = stat.isDirectory() ? "DIR" : "FILE"; + if (needDir && type === "FILE") + return cb(); + else + return cb(null, type, stat); + } + } + var self = this; + var statcb = inflight("stat\0" + abs, lstatcb_); + if (statcb) + fs4.lstat(abs, statcb); + function lstatcb_(er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + return fs4.stat(abs, function(er2, stat2) { + if (er2) + self._stat2(f, abs, null, lstat, cb); + else + self._stat2(f, abs, er2, stat2, cb); + }); + } else { + self._stat2(f, abs, er, lstat, cb); + } + } + }; + Glob2.prototype._stat2 = function(f, abs, er, stat, cb) { + if (er && (er.code === "ENOENT" || er.code === "ENOTDIR")) { + this.statCache[abs] = false; + return cb(); + } + var needDir = f.slice(-1) === "/"; + this.statCache[abs] = stat; + if (abs.slice(-1) === "/" && stat && !stat.isDirectory()) + return cb(null, false, stat); + var c = true; + if (stat) + c = stat.isDirectory() ? "DIR" : "FILE"; + this.cache[abs] = this.cache[abs] || c; + if (needDir && c === "FILE") + return cb(); + return cb(null, c, stat); + }; + } +}); + +// pnp:/Users/style/Documents/Projects/juke-build/src/index.ts +__export(exports, { + ExitCode: () => ExitCode, + Parameter: () => Parameter, + Target: () => Target, + chalk: () => chalk4, + chdir: () => chdir, + createParameter: () => createParameter, + createTarget: () => createTarget, + exec: () => exec, + glob: () => glob, + logger: () => logger, + rm: () => rm, + runner: () => runner, + setup: () => setup, + sleep: () => sleep +}); +var import_chalk4 = __toModule(require_source()); +var import_module = __toModule(require("module")); + +// pnp:/Users/style/Documents/Projects/juke-build/package.json +var version = "0.8.1"; + +// pnp:/Users/style/Documents/Projects/juke-build/src/chdir.ts +var import_fs = __toModule(require("fs")); +var import_path = __toModule(require("path")); +var import_url = __toModule(require("url")); +var chdir = (directory, relativeTo) => { + if (relativeTo) { + if (relativeTo.startsWith("file://")) { + relativeTo = import_url.default.fileURLToPath(relativeTo); + } + try { + const stat = import_fs.default.statSync(relativeTo); + if (!stat.isDirectory()) { + relativeTo = import_path.default.dirname(relativeTo); + } + } catch { + relativeTo = import_path.default.dirname(relativeTo); + } + directory = import_path.default.resolve(relativeTo, directory); + } + process.chdir(directory); +}; + +// pnp:/Users/style/Documents/Projects/juke-build/src/exec.ts +var import_chalk = __toModule(require_source()); +var import_child_process = __toModule(require("child_process")); +var import_fs2 = __toModule(require("fs")); +var import_path2 = __toModule(require("path")); +var children = new Set(); +var killChildren = () => { + for (const child of children) { + child.kill("SIGTERM"); + children.delete(child); + console.log("killed child process"); + } +}; +var trap = (signals, handler) => { + let readline; + if (process.platform === "win32") { + readline = require("readline").createInterface({ + input: process.stdin, + output: process.stdout + }); + } + for (const signal of signals) { + const handleSignal = () => handler(signal); + if (signal === "EXIT") { + process.on("exit", handleSignal); + continue; + } + if (readline) { + readline.on("SIG" + signal, handleSignal); + } + process.on("SIG" + signal, handleSignal); + } +}; +var ExitCode = class extends Error { + constructor(code, signal) { + super("Process exited with code: " + code); + this.code = null; + this.signal = null; + this.code = code; + this.signal = signal || null; + } +}; +var exec = (executable, args = [], options = {}) => { + const _a = options, { + silent = false, + throw: canThrow = true + } = _a, spawnOptions = __objRest(_a, [ + "silent", + "throw" + ]); + return new Promise((resolve, reject) => { + if (import_fs2.default.existsSync(executable)) { + executable = (0, import_path2.resolve)(executable); + } + if (process.env.JUKE_DEBUG) { + console.log(import_chalk.default.grey("$", executable, ...args)); + } + const child = (0, import_child_process.spawn)(executable, args, spawnOptions); + children.add(child); + let stdout = ""; + let stderr = ""; + let combined = ""; + child.stdout.on("data", (data) => { + if (!silent) { + process.stdout.write(data); + } + stdout += data; + combined += data; + }); + child.stderr.on("data", (data) => { + if (!silent) { + process.stderr.write(data); + } + stderr += data; + combined += data; + }); + child.on("error", (err) => reject(err)); + child.on("exit", (code, signal) => { + children.delete(child); + if (code !== 0 && canThrow) { + const error = new ExitCode(code); + error.code = code; + error.signal = signal; + reject(error); + return; + } + resolve({ + code, + signal, + stdout, + stderr, + combined + }); + }); + }); +}; + +// pnp:/Users/style/Documents/Projects/juke-build/src/logger.ts +var import_chalk2 = __toModule(require_source()); +var logger = { + log: (...args) => { + console.log(...args); + }, + error: (...args) => { + console.log(import_chalk2.default.bold(import_chalk2.default.redBright("=>"), import_chalk2.default.whiteBright(...args))); + }, + action: (...args) => { + console.log(import_chalk2.default.bold(import_chalk2.default.greenBright("=>"), import_chalk2.default.whiteBright(...args))); + }, + warn: (...args) => { + console.log(import_chalk2.default.bold(import_chalk2.default.yellowBright("=>"), import_chalk2.default.whiteBright(...args))); + }, + info: (...args) => { + console.log(import_chalk2.default.bold(import_chalk2.default.blueBright("::"), import_chalk2.default.whiteBright(...args))); + }, + debug: (...args) => { + if (process.env.JUKE_DEBUG) { + console.log(import_chalk2.default.gray(...args)); + } + } +}; + +// pnp:/Users/style/Documents/Projects/juke-build/src/string.ts +var import_stringcase = __toModule(require_lib()); +var toKebabCase = (str) => (0, import_stringcase.spinalcase)(str); +var toCamelCase = (str) => (0, import_stringcase.camelcase)(str); +var toConstCase = (str) => (0, import_stringcase.constcase)(str); + +// pnp:/Users/style/Documents/Projects/juke-build/src/parameter.ts +var Parameter = class { + constructor(config) { + this.type = config.type; + this.name = config.name; + this.alias = config.alias; + } + isString() { + return this.type === "string" || this.type === "string[]"; + } + isNumber() { + return this.type === "number" || this.type === "number[]"; + } + isBoolean() { + return this.type === "boolean" || this.type === "boolean[]"; + } + isArray() { + return this.type.endsWith("[]"); + } + toKebabCase() { + if (!this.name) + return; + return toKebabCase(this.name); + } + toConstCase() { + if (!this.name) + return; + return toConstCase(this.name); + } + toCamelCase() { + if (!this.name) + return; + return toCamelCase(this.name); + } +}; +var createParameter = (config) => new Parameter(config); + +// pnp:/Users/style/Documents/Projects/juke-build/src/runner.ts +var import_chalk3 = __toModule(require_source()); +var import_events = __toModule(require("events")); + +// pnp:/Users/style/Documents/Projects/juke-build/src/argparse.ts +var stringToBoolean = (str) => str !== void 0 && str !== null && str !== "false" && str !== "0" && str !== "null"; +var prepareArgs = (args, singleTarget = false) => { + let inGlobalContext = true; + const globalFlags = []; + const taskArgs = []; + let currentTaskArgs; + while (args.length !== 0) { + const arg = args.shift(); + if (!arg) { + continue; + } + if (arg === "--") { + inGlobalContext = false; + continue; + } + if (arg.startsWith("-")) { + if (inGlobalContext) { + globalFlags.push(arg); + } else if (currentTaskArgs) { + currentTaskArgs.push(arg); + } + continue; + } + inGlobalContext = false; + if (singleTarget) { + if (!currentTaskArgs) { + currentTaskArgs = [arg]; + } else { + currentTaskArgs.push(arg); + } + continue; + } + if (currentTaskArgs) { + taskArgs.push(currentTaskArgs); + } + currentTaskArgs = [arg]; + } + if (currentTaskArgs) { + taskArgs.push(currentTaskArgs); + } + return { globalFlags, taskArgs }; +}; +var parseArgs = (args, parameters) => { + args = [...args]; + const parameterMap = new Map(); + const pushValue = (key, value) => { + const values = parameterMap.get(key); + if (!values) { + parameterMap.set(key, [value]); + return; + } + values.push(value); + }; + let currentSet = []; + let currentSetType; + while (true) { + if (currentSet.length === 0) { + const arg2 = args.shift(); + if (!arg2) { + break; + } + if (arg2.startsWith("--")) { + currentSet = [arg2.substr(2)]; + currentSetType = "long"; + } else if (arg2.startsWith("-")) { + currentSet = Array.from(arg2); + currentSetType = "short"; + } else { + currentSet = []; + currentSetType = void 0; + } + } + const arg = currentSet.shift(); + if (currentSetType === "short") { + const parameter = parameters.find((p) => p.alias === arg); + if (!parameter) { + continue; + } + if (parameter.isBoolean()) { + pushValue(parameter, true); + continue; + } + if (currentSet.length === 0) { + continue; + } + const string = currentSet.join(""); + currentSet = []; + if (parameter.isNumber()) { + pushValue(parameter, parseFloat(string)); + continue; + } + pushValue(parameter, string); + continue; + } + if (currentSetType === "long") { + const equalsIndex = arg.indexOf("="); + let name = arg; + let value = null; + if (equalsIndex >= 0) { + name = arg.substr(0, equalsIndex); + value = arg.substr(equalsIndex + 1); + if (value === "") { + value = null; + } + } + const parameter = parameters.find((p) => p.name === name || p.toKebabCase() === name || p.toCamelCase() === name); + if (!parameter) { + continue; + } + if (parameter.isBoolean()) { + const noEqualsSign = equalsIndex < 0; + pushValue(parameter, noEqualsSign || stringToBoolean(value)); + continue; + } + if (value === null) { + continue; + } + if (parameter.isNumber()) { + pushValue(parameter, parseFloat(value)); + continue; + } + pushValue(parameter, value); + continue; + } + } + for (const [key, value] of Object.entries(process.env)) { + const parameter = parameters.find((p) => p.name === key || p.toConstCase() === key); + if (!parameter || parameterMap.has(parameter)) { + continue; + } + let values = []; + if (value !== void 0) { + if (parameter.isArray()) { + values = value.split(","); + } else { + values = [value]; + } + } + for (const value2 of values) { + if (parameter.isBoolean()) { + pushValue(parameter, stringToBoolean(value2)); + continue; + } + if (value2 === "") { + continue; + } + if (parameter.isNumber()) { + pushValue(parameter, parseFloat(value2)); + continue; + } + pushValue(parameter, value2); + continue; + } + } + return parameterMap; +}; + +// pnp:/Users/style/Documents/Projects/juke-build/src/fs.ts +var import_fs3 = __toModule(require("fs")); +var import_glob = __toModule(require_glob()); +var File = class { + constructor(path2) { + this.path = path2; + } + get stat() { + if (this._stat === void 0) { + try { + this._stat = import_fs3.default.statSync(this.path); + } catch { + this._stat = null; + } + } + return this._stat; + } + exists() { + return this.stat !== null; + } + get mtime() { + return this.stat && this.stat.mtime; + } + touch() { + const time = new Date(); + try { + import_fs3.default.utimesSync(this.path, time, time); + } catch (err) { + import_fs3.default.closeSync(import_fs3.default.openSync(this.path, "w")); + } + } +}; +var Glob = class { + constructor(path2) { + this.path = path2; + this.path = path2; + } + toFiles() { + const paths = import_glob.glob.sync(this.path, { + strict: false, + silent: true + }); + return paths.map((path2) => new File(path2)).filter((file) => file.exists()); + } +}; +var compareFiles = (sources, targets) => { + let bestSource = null; + let bestTarget = null; + for (const file of sources) { + if (!bestSource || file.mtime > bestSource.mtime) { + bestSource = file; + } + } + for (const file of targets) { + if (!file.exists()) { + return `target '${file.path}' is missing`; + } + if (!bestTarget || file.mtime < bestTarget.mtime) { + bestTarget = file; + } + } + if (!bestSource) { + if (bestTarget) { + return false; + } + return "no known sources or targets"; + } + if (!bestTarget) { + return "no targets were specified"; + } + if (bestSource.mtime > bestTarget.mtime) { + return `source '${bestSource.path}' is newer than target '${bestTarget.path}'`; + } + return false; +}; +var glob = (globPath) => { + const unsafePaths = import_glob.glob.sync(globPath, { + strict: false, + silent: true + }); + const safePaths = []; + for (let path2 of unsafePaths) { + try { + import_fs3.default.lstatSync(path2); + safePaths.push(path2); + } catch { + } + } + return safePaths; +}; +var rm = (path2, options = {}) => { + for (const p of glob(path2)) { + try { + if (options.recursive && import_fs3.default.rmSync) { + import_fs3.default.rmSync(p, options); + } else if (options.recursive) { + import_fs3.default.rmdirSync(p, { recursive: true }); + } else { + import_fs3.default.unlinkSync(p); + } + } catch (err) { + if (!options.force) { + throw err; + } + } + } +}; + +// pnp:/Users/style/Documents/Projects/juke-build/src/runner.ts +var runner = new class Runner { + constructor() { + this.config = {}; + this.targets = []; + this.parameters = []; + this.workers = []; + } + configure(config) { + this.config = config; + this.targets = config.targets || []; + this.parameters = config.parameters || []; + } + async start() { + const startedAt = Date.now(); + const { globalFlags, taskArgs } = prepareArgs(process.argv.slice(2), this.config.singleTarget); + const globalParameterMap = parseArgs(globalFlags, this.parameters); + const targetsToRun = new Map(); + const showListOfTargets = () => { + logger.info("Available targets:"); + for (const target of this.targets) { + logger.log(" - " + import_chalk3.default.cyan(target.name)); + } + logger.info("Available parameters:"); + for (const parameter of this.parameters) { + logger.log(" --" + parameter.name + (parameter.alias ? `, -${parameter.alias}` : "") + ` (type: ${parameter.type})`); + } + }; + if (globalFlags.includes("-h") || globalFlags.includes("--help")) { + showListOfTargets(); + process.exit(1); + } + for (const [taskName, ...args] of taskArgs) { + const target = this.targets.find((t) => t.name === taskName); + if (!target) { + const nameStr = import_chalk3.default.cyan(taskName); + logger.error(`Task '${nameStr}' was not found.`); + showListOfTargets(); + process.exit(1); + } + targetsToRun.set(target, { args }); + } + if (targetsToRun.size === 0) { + if (!this.config.default) { + logger.error(`No task was provided in arguments.`); + showListOfTargets(); + process.exit(1); + } + targetsToRun.set(this.config.default, { + args: [] + }); + } + let toVisit = Array.from(targetsToRun.entries()); + while (true) { + const node = toVisit.shift(); + if (!node) { + break; + } + const [target, meta] = node; + if (!meta.context) { + const localParameterMap = parseArgs(meta.args, target.parameters); + meta.context = { + get: (parameter) => { + let value = localParameterMap.get(parameter); + if (value === void 0) { + value = globalParameterMap.get(parameter); + } + if (parameter.isArray()) { + return value || []; + } else { + const returnValue = value && value[0]; + return returnValue !== void 0 ? returnValue : null; + } + }, + args: meta.args + }; + } + if (!meta.dependsOn) { + const optionalDependsOn = (typeof target.dependsOn === "function" ? await target.dependsOn(meta.context) : target.dependsOn) || []; + meta.dependsOn = optionalDependsOn.filter((dep) => typeof dep === "object" && dep !== null); + } + for (const dependency of meta.dependsOn) { + if (!targetsToRun.has(dependency)) { + const depMeta = { args: meta.args }; + targetsToRun.set(dependency, depMeta); + toVisit.push([dependency, depMeta]); + } else { + logger.debug("Dropped a possible circular dependency", dependency); + } + } + } + for (const [target, meta] of targetsToRun.entries()) { + const context = meta.context; + const dependsOn = meta.dependsOn; + const spawnedWorker = new Worker(target, context, dependsOn); + this.workers.push(spawnedWorker); + spawnedWorker.onFinish(() => { + for (const worker of this.workers) { + if (worker === spawnedWorker) { + continue; + } + worker.resolveDependency(target); + } + }); + spawnedWorker.onFail(() => { + for (const worker of this.workers) { + if (worker === spawnedWorker) { + continue; + } + worker.rejectDependency(target); + } + }); + } + const resolutions = await Promise.all(this.workers.map((worker) => new Promise((resolve) => { + worker.onFinish(() => resolve(true)); + worker.onFail(() => resolve(false)); + worker.start(); + }))); + const hasFailedWorkers = resolutions.includes(false); + if (!hasFailedWorkers) { + const time = (Date.now() - startedAt) / 1e3 + "s"; + const timeStr = import_chalk3.default.magenta(time); + logger.action(`Done in ${timeStr}`); + } + return Number(hasFailedWorkers); + } +}(); +var Worker = class { + constructor(target, context, dependsOn) { + this.target = target; + this.context = context; + this.dependsOn = dependsOn; + this.emitter = new import_events.default(); + this.hasFailed = false; + this.dependencies = new Set(dependsOn); + this.debugLog("ready"); + } + resolveDependency(target) { + var _a; + this.dependencies.delete(target); + (_a = this.generator) == null ? void 0 : _a.next(); + } + rejectDependency(target) { + var _a; + this.dependencies.delete(target); + this.hasFailed = true; + (_a = this.generator) == null ? void 0 : _a.next(); + } + start() { + this.generator = this.process(); + this.generator.next(); + } + onFinish(fn) { + this.emitter.once("finish", fn); + } + onFail(fn) { + this.emitter.once("fail", fn); + } + debugLog(...args) { + logger.debug(`${this.target.name}:`, ...args); + } + async *process() { + const nameStr = import_chalk3.default.cyan(this.target.name); + this.debugLog("Waiting for dependencies"); + while (true) { + if (this.dependencies.size === 0) { + break; + } + yield; + } + if (this.hasFailed) { + const nameStr2 = import_chalk3.default.cyan(this.target.name); + logger.error(`Target '${nameStr2}' failed`); + this.emitter.emit("fail"); + return; + } + if (this.target.onlyWhen) { + const result = await this.target.onlyWhen(this.context); + if (!result) { + logger.info(`Skipping '${nameStr}' (condition unmet)`); + this.emitter.emit("finish"); + return; + } + this.debugLog("Needs rebuild based on onlyWhen condition"); + } + this.debugLog("Comparing inputs and outputs"); + const fileMapper = async (fileIo) => { + const optionalPaths = (typeof fileIo === "function" ? await fileIo(this.context) : fileIo) || []; + const paths = optionalPaths.filter((path2) => typeof path2 === "string"); + return paths.flatMap((path2) => path2.includes("*") ? new Glob(path2).toFiles() : new File(path2)); + }; + const inputs = await fileMapper(this.target.inputs); + const outputs = await fileMapper(this.target.outputs); + if (inputs.length > 0) { + const needsRebuild = compareFiles(inputs, outputs); + if (!needsRebuild) { + logger.info(`Skipping '${nameStr}' (up to date)`); + this.emitter.emit("finish"); + return; + } else { + this.debugLog("Needs rebuild, reason:", needsRebuild); + } + } else { + this.debugLog("Nothing to compare"); + } + if (this.hasFailed) { + const nameStr2 = import_chalk3.default.cyan(this.target.name); + logger.error(`Target '${nameStr2}' failed (at file comparison stage)`); + this.emitter.emit("fail"); + return; + } + if (this.target.executes) { + logger.action(`Starting '${nameStr}'`); + const startedAt = Date.now(); + try { + await this.target.executes(this.context); + } catch (err) { + const time2 = (Date.now() - startedAt) / 1e3 + "s"; + const timeStr2 = import_chalk3.default.magenta(time2); + if (err instanceof ExitCode) { + const codeStr = import_chalk3.default.red(err.code); + logger.error(`Target '${nameStr}' failed in ${timeStr2}, exit code: ${codeStr}`); + } else { + logger.error(`Target '${nameStr}' failed in ${timeStr2}, unhandled exception:`); + console.error(err); + } + this.emitter.emit("fail"); + return; + } + const time = (Date.now() - startedAt) / 1e3 + "s"; + const timeStr = import_chalk3.default.magenta(time); + logger.action(`Finished '${nameStr}' in ${timeStr}`); + } + if (outputs.length > 0) { + for (const file of outputs) { + file.touch(); + } + } + this.emitter.emit("finish"); + } +}; + +// pnp:/Users/style/Documents/Projects/juke-build/src/target.ts +var Target = class { + constructor(target) { + this.name = target.name; + this.dependsOn = target.dependsOn || []; + this.executes = target.executes; + this.inputs = target.inputs || []; + this.outputs = target.outputs || []; + this.parameters = target.parameters || []; + this.onlyWhen = target.onlyWhen; + } +}; +var createTarget = (config) => new Target(config); + +// pnp:/Users/style/Documents/Projects/juke-build/src/index.ts +var chalk4 = import_chalk4.default; +var lastExitCode = null; +var setup = async (config) => { + logger.info(`Juke Build version ${version}`); + if (!config.file) { + logger.error(`Field 'file' is required in Juke.setup()`); + process.exit(1); + } + let buildModule = await import(config.file); + const isCommonJs = Boolean((0, import_module.createRequire)(config.file).cache[config.file]); + if (isCommonJs) { + buildModule = buildModule.default; + } + const targets = []; + const parameters = []; + for (const name of Object.keys(buildModule)) { + if (name === "default") { + continue; + } + const obj = buildModule[name]; + if (obj instanceof Target) { + if (!obj.name) { + obj.name = name !== "Target" ? toKebabCase(name.replace(/Target$/, "")) : "target"; + } + targets.push(obj); + continue; + } + if (obj instanceof Parameter) { + if (!obj.name) { + obj.name = name !== "Parameter" ? toKebabCase(name.replace(/Parameter$/, "")) : "parameter"; + } + parameters.push(obj); + continue; + } + } + const DefaultTarget = buildModule.default || buildModule.DefaultTarget || buildModule.Default; + if (DefaultTarget && !(DefaultTarget instanceof Target)) { + logger.error(`Default export is not a valid 'Target' object.`); + process.exit(1); + } + runner.configure({ + parameters, + targets, + default: DefaultTarget, + singleTarget: config.singleTarget + }); + return runner.start().then((code) => { + lastExitCode = code; + return code; + }); +}; +var sleep = (time) => new Promise((resolve) => setTimeout(resolve, time)); +trap(["EXIT", "BREAK", "HUP", "INT", "TERM"], (signal) => { + if (signal !== "EXIT") { + console.log("Received", signal); + } + killChildren(); + if (signal !== "EXIT") { + process.exit(1); + } else if (lastExitCode !== null) { + process.exit(lastExitCode); + } +}); +var exceptionHandler = (err) => { + console.log(err); + killChildren(); + process.exit(1); +}; +process.on("unhandledRejection", exceptionHandler); +process.on("uncaughtException", exceptionHandler); +// Annotate the CommonJS export names for ESM import in node: +0 && (module.exports = { + ExitCode, + Parameter, + Target, + chalk, + chdir, + createParameter, + createTarget, + exec, + glob, + logger, + rm, + runner, + setup, + sleep +}); diff --git a/tools/build/juke/package.json b/tools/build/juke/package.json new file mode 100644 index 0000000000..0fef86e339 --- /dev/null +++ b/tools/build/juke/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "type": "commonjs" +} diff --git a/tools/build/lib/byond.js b/tools/build/lib/byond.js new file mode 100644 index 0000000000..952b60c4e9 --- /dev/null +++ b/tools/build/lib/byond.js @@ -0,0 +1,115 @@ +import fs from 'fs'; +import path from 'path'; +import Juke from '../juke/index.js'; +import { regQuery } from './winreg.js'; + +/** + * Cached path to DM compiler + */ +let dmPath; + +const getDmPath = async () => { + if (dmPath) { + return dmPath; + } + dmPath = await (async () => { + // Search in array of paths + const paths = [ + ...((process.env.DM_EXE && process.env.DM_EXE.split(',')) || []), + 'C:\\Program Files\\BYOND\\bin\\dm.exe', + 'C:\\Program Files (x86)\\BYOND\\bin\\dm.exe', + ['reg', 'HKLM\\Software\\Dantom\\BYOND', 'installpath'], + ['reg', 'HKLM\\SOFTWARE\\WOW6432Node\\Dantom\\BYOND', 'installpath'], + ]; + const isFile = path => { + try { + return fs.statSync(path).isFile(); + } + catch (err) { + return false; + } + }; + for (let path of paths) { + // Resolve a registry key + if (Array.isArray(path)) { + const [type, ...args] = path; + path = await regQuery(...args); + } + if (!path) { + continue; + } + // Check if path exists + if (isFile(path)) { + return path; + } + if (isFile(path + '/dm.exe')) { + return path + '/dm.exe'; + } + if (isFile(path + '/bin/dm.exe')) { + return path + '/bin/dm.exe'; + } + } + // Default paths + return ( + process.platform === 'win32' && 'dm.exe' + || 'DreamMaker' + ); + })(); + return dmPath; +}; + +/** + * @param {string} dmeFile + * @param {{ defines?: string[] }} options + */ +export const DreamMaker = async (dmeFile, options = {}) => { + const dmPath = await getDmPath(); + // Get project basename + const dmeBaseName = dmeFile.replace(/\.dme$/, ''); + // Make sure output files are writable + const testOutputFile = (name) => { + try { + fs.closeSync(fs.openSync(name, 'r+')); + } + catch (err) { + if (err && err.code === 'ENOENT') { + return; + } + if (err && err.code === 'EBUSY') { + Juke.logger.error(`File '${name}' is locked by the DreamDaemon process.`); + Juke.logger.error(`Stop the currently running server and try again.`); + throw new Juke.ExitCode(1); + } + throw err; + } + }; + testOutputFile(`${dmeBaseName}.dmb`); + testOutputFile(`${dmeBaseName}.rsc`); + // Compile + const { defines } = options; + if (defines && defines.length > 0) { + const injectedContent = defines + .map(x => `#define ${x}\n`) + .join(''); + fs.writeFileSync(`${dmeBaseName}.m.dme`, injectedContent); + const dmeContent = fs.readFileSync(`${dmeBaseName}.dme`); + fs.appendFileSync(`${dmeBaseName}.m.dme`, dmeContent); + await Juke.exec(dmPath, [`${dmeBaseName}.m.dme`]); + fs.writeFileSync(`${dmeBaseName}.dmb`, fs.readFileSync(`${dmeBaseName}.m.dmb`)); + fs.writeFileSync(`${dmeBaseName}.rsc`, fs.readFileSync(`${dmeBaseName}.m.rsc`)); + fs.unlinkSync(`${dmeBaseName}.m.dmb`); + fs.unlinkSync(`${dmeBaseName}.m.rsc`); + fs.unlinkSync(`${dmeBaseName}.m.dme`); + } + else { + await Juke.exec(dmPath, [dmeFile]); + } +}; + +export const DreamDaemon = async (dmbFile, ...args) => { + const dmPath = await getDmPath(); + const baseDir = path.dirname(dmPath); + const ddExeName = process.platform === 'win32' ? 'dd.exe' : 'DreamDaemon'; + const ddExePath = baseDir === '.' ? ddExeName : path.join(baseDir, ddExeName); + return Juke.exec(ddExePath, [dmbFile, ...args]); +}; diff --git a/tools/build/cbt/winreg.js b/tools/build/lib/winreg.js similarity index 81% rename from tools/build/cbt/winreg.js rename to tools/build/lib/winreg.js index a19aa66f48..0916ea3a31 100644 --- a/tools/build/cbt/winreg.js +++ b/tools/build/lib/winreg.js @@ -4,14 +4,14 @@ * Adapted from `tgui/packages/tgui-dev-server/winreg.js`. * * @file - * @copyright 2020 Aleksej Komarov + * @copyright 2021 Aleksej Komarov * @license MIT */ -const { exec } = require('child_process'); -const { promisify } = require('util'); +import { exec } from 'child_process'; +import { promisify } from 'util'; -const regQuery = async (path, key) => { +export const regQuery = async (path, key) => { if (process.platform !== 'win32') { return null; } @@ -40,7 +40,3 @@ const regQuery = async (path, key) => { return null; } }; - -module.exports = { - regQuery, -}; diff --git a/tools/build/lib/yarn.js b/tools/build/lib/yarn.js new file mode 100644 index 0000000000..8b93f4cfc9 --- /dev/null +++ b/tools/build/lib/yarn.js @@ -0,0 +1,13 @@ +import Juke from '../juke/index.js'; + +let yarnPath; + +export const yarn = (...args) => { + if (!yarnPath) { + yarnPath = Juke.glob('./tgui/.yarn/releases/*.cjs')[0] + .replace('/tgui/', '/'); + } + return Juke.exec('node', [yarnPath, ...args], { + cwd: './tgui', + }); +}; diff --git a/tools/build/package.json b/tools/build/package.json new file mode 100644 index 0000000000..e986b24bba --- /dev/null +++ b/tools/build/package.json @@ -0,0 +1,4 @@ +{ + "private": true, + "type": "module" +} diff --git a/tools/requirements.txt b/tools/requirements.txt index 81b49fedfd..a9abe5e826 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,6 +1,6 @@ pygit2==1.0.1 bidict==0.13.1 -Pillow==8.2.0 +Pillow==8.3.2 # changelogs PyYaml==5.4