diff --git a/.editorconfig b/.editorconfig index 13dcd5e029..c44572fbf9 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,11 +1,14 @@ [*] -insert_final_newline = true indent_style = tab indent_size = 4 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +#end_of_line = lf [*.yml] indent_style = space indent_size = 2 -[*.txt] -insert_final_newline = false +[*.py] +indent_style = space diff --git a/.gitattributes b/.gitattributes index a0a624af04..c447869d3e 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,42 @@ -# merger hooks, run tools/hooks/install.bat or install.sh to set up -*.dmm merge=dmm -*.dmi merge=dmi - -# force changelog merging to use union -html/changelog.html merge=union - -# force all files to be LF * text=auto + +## Enforce text mode and LF line breaks +## porter note: not yet LFing dm. +# *.bat text eol=lf +*.css text eol=lf +# *.dm text eol=lf +# *.dme text eol=lf +# *.dmf text eol=lf +*.htm text eol=lf +*.html text eol=lf +*.js text eol=lf +*.json text eol=lf +*.jsx text eol=lf +# *.md text eol=lf +*.py text eol=lf +*.scss text eol=lf +# *.sh text eol=lf +# *.sql text eol=lf +*.svg text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +# *.txt text eol=lf +*.yaml text eol=lf +*.yml text eol=lf + +## Enforce binary mode +*.bmp binary +*.dll binary +*.dmb binary +*.exe binary +*.gif binary +*.jpg binary +*.png binary +*.so binary + +## Merger hooks, run tools/hooks/install.bat or install.sh to set up +*.dmm text eol=lf merge=dmm +*.dmi binary merge=dmi + +## Force changelog merging to use union +html/changelog.html text eol=lf merge=union diff --git a/.github/workflows/autobuild_tgui.yml b/.github/workflows/autobuild_tgui.yml deleted file mode 100644 index 226ea2b7ce..0000000000 --- a/.github/workflows/autobuild_tgui.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Autobuild TGUI - -on: - push: - branches: - - 'master' - paths: - - 'tgui/**.js' - - 'tgui/**.scss' - -jobs: - build: - name: Rebuild TGUI - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v1 - with: - fetch-depth: 25 - - name: Setup Node - uses: actions/setup-node@v1 - with: - node-version: '>=12.13' - - name: Build TGUI - run: bin/tgui --ci - working-directory: ./tgui - - name: Commit Artifacts - run: | - git config --local user.email "action@github.com" - git config --local user.name "TGUI" - git pull origin master - git commit -m "Automatic TGUI Rebuild [ci skip]" -a || true - - name: Push Artifacts - uses: ad-m/github-push-action@master - with: - github_token: ${{ secrets.GITHUB_MASTER_KEY }} diff --git a/.github/workflows/round_id_linker.yml b/.github/workflows/round_id_linker.yml new file mode 100644 index 0000000000..37998a93eb --- /dev/null +++ b/.github/workflows/round_id_linker.yml @@ -0,0 +1,12 @@ +name: "Round ID Linker" +on: + issues: + types: [opened] + +jobs: + link_rounds: + runs-on: ubuntu-latest + steps: + - uses: Cidatel-Station-13/round_linker@master #notice: fork the round linkies from tg!! + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index f65245c021..e885761104 100644 --- a/.gitignore +++ b/.gitignore @@ -198,13 +198,36 @@ Temporary Items !/.vscode/extensions.json tools/MapAtmosFixer/MapAtmosFixer/obj/* tools/MapAtmosFixer/MapAtmosFixer/bin/* +tools/CreditsTool/bin/* +tools/CreditsTool/obj/* #GitHub Atom .atom-build.json -#Jukebox audio files -/config/jukebox_music/**/* +#KDevelop and Kate +*.kdev4* +*.kate-swp #extra map stuff /_maps/**/backup/ /_maps/templates.dm + +#dmdoc default folder +/dmdoc + +# Ignore custom music and title screens (amend as appropriate) +/config/jukebox_music/sounds/* +!/config/jukebox_music/sounds/exclude +/config/title_music/sounds/* +!/config/title_music/sounds/exclude +/config/title_screens/images/* +!/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 diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..11d27b55d3 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,11 @@ +build: + stage: build + rules: + - if: $CI_MERGE_REQUEST_IID || $CI_COMMIT_REF_NAME == "master" + changes: + - tgui/**/*.js + - tgui/**/*.scss + when: always + image: node:lts + script: + - tgui/bin/tgui --ci diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/.travis.yml b/.travis.yml index 2214df3aee..83ef6fa8ff 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,13 +1,13 @@ language: generic +os: linux dist: xenial -sudo: false branches: except: - ___TGS3TempBranch - ___TGSTempBranch -matrix: +jobs: include: - name: "Run Linters" addons: @@ -16,9 +16,15 @@ matrix: - python3 - python3-pip - python3-setuptools + - pcregrep + - rustc + - cargo + cache: + directories: + - $HOME/SpacemanDMM install: - tools/travis/install_build_tools.sh - - tools/travis/install_dreamchecker.sh + - tools/travis/install_spaceman_dmm.sh dreamchecker script: - tools/travis/check_filedirs.sh tgstation.dme - tools/travis/check_changelogs.sh @@ -76,3 +82,21 @@ matrix: script: - tools/travis/dm.sh -DTRAVISBUILDING tgstation.dme || travis_terminate 1 - tools/travis/run_server.sh + + # - name: "Generate Documentation" + # # Only run for non-PR commits to the real master branch. + # if: branch = master AND head_branch IS blank + # install: + # - tools/travis/install_spaceman_dmm.sh dmdoc + # before_script: + # # Travis checks out a hash, try to get back on a branch. + # - git checkout $TRAVIS_BRANCH || true + # script: + # - ~/dmdoc + # - touch dmdoc/.nojekyll + # deploy: + # provider: pages + # skip_cleanup: true + # local_dir: dmdoc + # token: $DMDOC_GITHUB_TOKEN + # fqdn: codedocs.tgstation13.org diff --git a/_maps/map_files/Deltastation/DeltaStation2.dmm b/_maps/map_files/Deltastation/DeltaStation2.dmm index 17253e2665..943aa85efd 100644 --- a/_maps/map_files/Deltastation/DeltaStation2.dmm +++ b/_maps/map_files/Deltastation/DeltaStation2.dmm @@ -12556,15 +12556,11 @@ /area/engine/atmospherics_engine) "aAb" = ( /turf/closed/wall, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aAc" = ( /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden, /turf/closed/wall, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aAd" = ( /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/tile/neutral{ @@ -13138,9 +13134,7 @@ /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBh" = ( /obj/item/kirbyplants/random, /obj/machinery/light/small{ @@ -13149,25 +13143,19 @@ /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBi" = ( /obj/structure/table, /obj/item/clothing/suit/apron/overalls, /obj/item/cultivator, /obj/effect/turf_decal/bot, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBj" = ( /obj/machinery/biogenerator, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBk" = ( /obj/structure/table, /obj/item/reagent_containers/food/snacks/grown/wheat, @@ -13182,18 +13170,14 @@ pixel_y = 4 }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBl" = ( /obj/machinery/hydroponics/soil, /obj/item/seeds/tea, /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBm" = ( /obj/item/kirbyplants/random, /obj/machinery/status_display/evac{ @@ -13201,9 +13185,7 @@ }, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBn" = ( /obj/structure/table, /obj/item/stack/packageWrap, @@ -13213,9 +13195,7 @@ /obj/item/reagent_containers/food/snacks/grown/cherries, /obj/effect/turf_decal/bot, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBo" = ( /obj/item/kirbyplants/random, /obj/machinery/light/small{ @@ -13225,18 +13205,14 @@ /obj/effect/turf_decal/delivery, /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBp" = ( /obj/machinery/hydroponics/soil, /obj/item/seeds/poppy, /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aBq" = ( /obj/structure/cable/white{ icon_state = "2-4" @@ -13817,33 +13793,25 @@ /area/engine/atmospherics_engine) "aCv" = ( /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aCw" = ( /obj/effect/turf_decal/delivery, /obj/machinery/hydroponics/soil, /obj/item/seeds/watermelon, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aCx" = ( /obj/machinery/atmospherics/components/unary/vent_scrubber/on{ dir = 4 }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aCy" = ( /obj/machinery/atmospherics/pipe/manifold/scrubbers/hidden{ dir = 4 }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aCz" = ( /obj/structure/cable/white{ icon_state = "1-2" @@ -14459,21 +14427,15 @@ }, /obj/effect/turf_decal/bot, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aDy" = ( /obj/machinery/atmospherics/components/unary/vent_pump/on, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aDz" = ( /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aDA" = ( /obj/structure/sink{ dir = 4; @@ -14484,9 +14446,7 @@ }, /obj/effect/turf_decal/bot, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aDB" = ( /obj/effect/decal/cleanable/dirt, /obj/effect/decal/cleanable/dirt, @@ -14968,34 +14928,26 @@ icon_state = "2-4" }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aEC" = ( /obj/structure/cable/white{ icon_state = "4-8" }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aED" = ( /obj/structure/cable/white{ icon_state = "4-8" }, /obj/machinery/atmospherics/pipe/simple/supply/hidden, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aEE" = ( /obj/structure/cable/white{ icon_state = "2-8" }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aEG" = ( /obj/effect/decal/cleanable/dirt, /obj/structure/cable/white{ @@ -15628,18 +15580,14 @@ /obj/effect/decal/cleanable/dirt, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFK" = ( /obj/machinery/hydroponics/soil, /obj/item/seeds/harebell, /obj/machinery/light/small, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFL" = ( /obj/structure/table, /obj/item/shovel/spade, @@ -15658,9 +15606,7 @@ }, /obj/effect/turf_decal/bot, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFM" = ( /obj/machinery/status_display/evac{ pixel_y = -32 @@ -15669,34 +15615,26 @@ /obj/machinery/hydroponics/soil, /obj/item/seeds/poppy/geranium, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFN" = ( /obj/structure/table, /obj/item/plant_analyzer, /obj/item/hatchet, /obj/effect/turf_decal/bot, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFO" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFP" = ( /obj/structure/cable/white{ icon_state = "1-2" }, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFQ" = ( /obj/structure/table, /obj/item/crowbar, @@ -15704,9 +15642,7 @@ /obj/item/reagent_containers/glass/bucket, /obj/effect/turf_decal/bot, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFR" = ( /obj/machinery/hydroponics/soil, /obj/item/seeds/tower, @@ -15715,9 +15651,7 @@ /obj/effect/turf_decal/delivery, /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aFS" = ( /obj/structure/cable/white{ icon_state = "1-2" @@ -16707,9 +16641,7 @@ "aHl" = ( /obj/effect/spawner/structure/window/reinforced, /turf/open/floor/plating, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aHm" = ( /obj/machinery/door/airlock/maintenance_hatch{ name = "Maintenance Hatch"; @@ -16721,9 +16653,7 @@ dir = 1 }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aHn" = ( /obj/machinery/door/airlock/maintenance_hatch{ name = "Maintenance Hatch"; @@ -16737,16 +16667,12 @@ dir = 1 }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aHo" = ( /obj/effect/spawner/structure/window/reinforced, /obj/machinery/atmospherics/pipe/simple/scrubbers/hidden, /turf/open/floor/plating, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "aHp" = ( /obj/effect/decal/cleanable/dirt, /obj/structure/cable/white{ @@ -125347,9 +125273,7 @@ /obj/item/reagent_containers/glass/bucket, /obj/effect/turf_decal/delivery, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "etO" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden{ dir = 4 @@ -125474,9 +125398,7 @@ /obj/machinery/hydroponics/soil, /obj/item/seeds/grape, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "faI" = ( /obj/structure/cable/white{ icon_state = "1-2" @@ -126395,9 +126317,7 @@ pixel_x = 6 }, /turf/open/floor/plasteel, -/area/hydroponics/garden/abandoned{ - name = "Maintenance Garden" - }) +/area/hydroponics/garden/abandoned) "loH" = ( /obj/effect/turf_decal/tile/neutral{ dir = 1 diff --git a/_maps/map_files/KiloStation/KiloStation.dmm b/_maps/map_files/KiloStation/KiloStation.dmm index f426ad8e97..51bfe5038e 100644 --- a/_maps/map_files/KiloStation/KiloStation.dmm +++ b/_maps/map_files/KiloStation/KiloStation.dmm @@ -75352,9 +75352,7 @@ /obj/machinery/atmospherics/pipe/simple/orange/visible{ dir = 4 }, -/obj/machinery/airalarm{ - pixel_z = 24 - }, +/obj/machinery/airalarm, /obj/machinery/light/small, /obj/effect/decal/cleanable/dirt, /turf/open/floor/plating{ diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index 0a6c2b9eca..4715c3bed3 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -36,6 +36,7 @@ var/grab_state = 0 var/throwforce = 0 var/datum/component/orbiter/orbiting + /// Used for space ztransit stuff var/can_be_z_moved = TRUE ///If we were without gravity and another animation happened, the bouncing will stop, and we need to restart it in next life(). var/floating_need_update = FALSE diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index 3df64550b1..b150d4930e 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -133,13 +133,10 @@ else to_chat(user, "The plating is going to need some support! Place metal rods first.") -/turf/open/space/Entered(atom/movable/A, atom/OldLoc) - . = ..() - - var/turf/old = get_turf(OldLoc) - if(!isspaceturf(old) && ismob(A)) - var/mob/M = A - M.update_gravity(M.mob_has_gravity()) +/turf/open/space/Entered(atom/movable/A) + ..() + if ((!(A) || src != A.loc)) + return if(destination_z && destination_x && destination_y && !(A.pulledby || !A.can_be_z_moved)) var/tx = destination_x @@ -160,18 +157,27 @@ ty-- DT = locate(tx, ty, destination_z) - var/atom/movable/AM = A.pulling + var/atom/movable/pulling = A.pulling + var/atom/movable/puller = A A.forceMove(DT) - if(AM) - var/turf/T = get_step(A.loc,turn(A.dir, 180)) - AM.can_be_z_moved = FALSE - AM.forceMove(T) - A.start_pulling(AM) - AM.can_be_z_moved = TRUE + + while (pulling != null) + var/next_pulling = pulling.pulling + + var/turf/T = get_step(puller.loc, turn(puller.dir, 180)) + pulling.can_be_z_moved = FALSE + pulling.forceMove(T) + puller.start_pulling(pulling) + pulling.can_be_z_moved = TRUE + + puller = pulling + pulling = next_pulling //now we're on the new z_level, proceed the space drifting stoplag()//Let a diagonal move finish, if necessary A.newtonian_move(A.inertia_dir) + A.inertia_moving = TRUE + /turf/open/space/Exited(atom/movable/AM, atom/OldLoc) . = ..() diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm index e758a43589..3229304c13 100644 --- a/code/modules/unit_tests/_unit_tests.dm +++ b/code/modules/unit_tests/_unit_tests.dm @@ -2,13 +2,41 @@ //Keep this sorted alphabetically #ifdef UNIT_TESTS +/// Asserts that a condition is true +/// If the condition is not true, fails the test +#define TEST_ASSERT(assertion, reason) if (!(assertion)) { return Fail("Assertion failed: [reason || "No reason"]") } + +/// Asserts that the two parameters passed are equal, fails otherwise +/// Optionally allows an additional message in the case of a failure +#define TEST_ASSERT_EQUAL(a, b, message) if ((a) != (b)) { return Fail("Expected [isnull(a) ? "null" : a] to be equal to [isnull(b) ? "null" : b].[message ? " [message]" : ""]") } + #include "anchored_mobs.dm" +#include "bespoke_id.dm" +// #include "binary_insert.dm" +// #include "card_mismatch.dm" shame we don't have this! +#include "chain_pull_through_space.dm" #include "character_saving.dm" #include "component_tests.dm" +// #include "confusion.dm" +// #include "keybinding_init.dm" +#include "machine_disassembly.dm" +#include "medical_wounds.dm" +// #include "metabolizing.dm" +// #include "outfit_sanity.dm" +// #include "plantgrowth_tests.dm" +// #include "quick_swap_sanity.dm" - we don't have quick swap yet #include "reagent_id_typos.dm" #include "reagent_recipe_collisions.dm" +#include "resist.dm" +// #include "say.dm" //no saymods, someone update saycode please. +// #include "siunit.dm" #include "spawn_humans.dm" +// #include "species_whitelists.dm" #include "subsystem_init.dm" +#include "surgeries.dm" #include "timer_sanity.dm" #include "unit_test.dm" + +#undef TEST_ASSERT +#undef TEST_ASSERT_EQUAL #endif diff --git a/code/modules/unit_tests/anchored_mobs.dm b/code/modules/unit_tests/anchored_mobs.dm index 5324179bb7..103b97e7a9 100644 --- a/code/modules/unit_tests/anchored_mobs.dm +++ b/code/modules/unit_tests/anchored_mobs.dm @@ -6,4 +6,4 @@ L += "[i]" if(!L.len) return //passed! - Fail("The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]") \ No newline at end of file + Fail("The following mobs are defined as anchored. This is incompatible with the new move force/resist system and needs to be revised.: [L.Join(" ")]") diff --git a/code/modules/unit_tests/bespoke_id.dm b/code/modules/unit_tests/bespoke_id.dm new file mode 100644 index 0000000000..06676c626c --- /dev/null +++ b/code/modules/unit_tests/bespoke_id.dm @@ -0,0 +1,8 @@ +/datum/unit_test/bespoke_id/Run() + var/datum/element/base = /datum/element + var/base_index = initial(base.id_arg_index) + + for(var/i in subtypesof(/datum/element)) + var/datum/element/faketype = i + if((initial(faketype.element_flags) & ELEMENT_BESPOKE) && initial(faketype.id_arg_index) == base_index) + Fail("A bespoke element was not configured with a proper id_arg_index: [faketype]") diff --git a/code/modules/unit_tests/binary_insert.dm b/code/modules/unit_tests/binary_insert.dm new file mode 100644 index 0000000000..ac7f58208e --- /dev/null +++ b/code/modules/unit_tests/binary_insert.dm @@ -0,0 +1,26 @@ +/// A test to ensure the sanity of BINARY_INSERT +/datum/unit_test/binary_insert/Run() + var/list/datum/binary_insert_node/nodes = list() + + var/datum/binary_insert_node/node_a = new /datum/binary_insert_node(10) + BINARY_INSERT(node_a, nodes, /datum/binary_insert_node, node_a, x, COMPARE_KEY) + TEST_ASSERT_EQUAL(nodes.len, 1, "List should have one node") + + var/datum/binary_insert_node/node_b = new /datum/binary_insert_node(5) + BINARY_INSERT(node_b, nodes, /datum/binary_insert_node, node_b, x, COMPARE_KEY) + TEST_ASSERT_EQUAL(nodes.len, 2, "List should have two nodes") + TEST_ASSERT_EQUAL(nodes[1].x, 5, "The first node should be the one with 5") + TEST_ASSERT_EQUAL(nodes[2].x, 10, "The second node should be the one with 10") + + var/datum/binary_insert_node/node_c = new /datum/binary_insert_node(15) + BINARY_INSERT(node_c, nodes, /datum/binary_insert_node, node_c, x, COMPARE_KEY) + TEST_ASSERT_EQUAL(nodes.len, 3, "List should have three nodes") + TEST_ASSERT_EQUAL(nodes[1].x, 5, "The first node should be the one with 5") + TEST_ASSERT_EQUAL(nodes[2].x, 10, "The second node should be the one with 10") + TEST_ASSERT_EQUAL(nodes[3].x, 15, "The third node should be the one with 15") + +/datum/binary_insert_node + var/x + +/datum/binary_insert_node/New(_x) + x = _x diff --git a/code/modules/unit_tests/chain_pull_through_space.dm b/code/modules/unit_tests/chain_pull_through_space.dm new file mode 100644 index 0000000000..ffdd1bf7c9 --- /dev/null +++ b/code/modules/unit_tests/chain_pull_through_space.dm @@ -0,0 +1,62 @@ +/datum/unit_test/chain_pull_through_space + var/turf/open/space/space_tile + var/turf/claimed_tile + var/mob/living/carbon/human/alice + var/mob/living/carbon/human/bob + var/mob/living/carbon/human/charlie + +/datum/unit_test/chain_pull_through_space/New() + ..() + + // Create a space tile that goes to another z-level + claimed_tile = run_loc_bottom_left + + space_tile = new(locate(run_loc_bottom_left.x, run_loc_bottom_left.y, run_loc_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.name = "Alice" + + bob = new(locate(run_loc_bottom_left.x + 3, run_loc_bottom_left.y, run_loc_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.name = "Charlie" + +/datum/unit_test/chain_pull_through_space/Destroy() + space_tile.copyTurf(claimed_tile) + qdel(alice) + qdel(bob) + qdel(charlie) + return ..() + +/datum/unit_test/chain_pull_through_space/Run() + // Alice pulls Bob, who pulls Charlie + // Normally, when Alice moves forward, the rest follow + alice.start_pulling(bob) + 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) + return Fail("During normal move, Bob was not at the correct x ([bob.x])") + if (charlie.x != run_loc_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) + 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])") + + if (bob.z != space_tile.destination_z) + return Fail("Bob did not teleport to the destination z-level. Current location: ([bob.x], [bob.y], [bob.z])") + if (!bob.Adjacent(alice)) + return Fail("Bob is not adjacent to Alice. Bob is at [bob.x], Alice is at [alice.x]") + + if (charlie.z != space_tile.destination_z) + return Fail("Charlie did not teleport to the destination z-level. Current location: ([charlie.x], [charlie.y], [charlie.z])") + if (!charlie.Adjacent(bob)) + return Fail("Charlie is not adjacent to Bob. Charlie is at [charlie.x], Bob is at [bob.x]") diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm index 409d7f4322..0099d7508c 100644 --- a/code/modules/unit_tests/component_tests.dm +++ b/code/modules/unit_tests/component_tests.dm @@ -9,4 +9,4 @@ if(dupe_type && !ispath(dupe_type)) bad_dts += t if(length(bad_dms) || length(bad_dts)) - Fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])") \ No newline at end of file + Fail("Components with invalid dupe modes: ([bad_dms.Join(",")]) ||| Components with invalid dupe types: ([bad_dts.Join(",")])") diff --git a/code/modules/unit_tests/machine_disassembly.dm b/code/modules/unit_tests/machine_disassembly.dm new file mode 100644 index 0000000000..bcc769bcf2 --- /dev/null +++ b/code/modules/unit_tests/machine_disassembly.dm @@ -0,0 +1,13 @@ +/// Ensures that when disassembling a machine, all the parts are given back +/datum/unit_test/machine_disassembly/Run() + var/obj/machinery/freezer = allocate(/obj/machinery/atmospherics/components/unary/thermomachine/freezer) + + var/turf/freezer_location = freezer.loc + freezer_location.ChangeTurf(/turf/open/floor/plasteel) + freezer.deconstruct() + + // Check that the components are created + TEST_ASSERT(locate(/obj/item/stock_parts/micro_laser) in freezer_location, "Couldn't find micro-laser when disassembling freezer") + + // Check that the circuit board itself is created + TEST_ASSERT(locate(/obj/item/circuitboard/machine/thermomachine/freezer) in freezer_location, "Couldn't find the circuit board when disassembling freezer") diff --git a/code/modules/unit_tests/medical_wounds.dm b/code/modules/unit_tests/medical_wounds.dm new file mode 100644 index 0000000000..75c08931f1 --- /dev/null +++ b/code/modules/unit_tests/medical_wounds.dm @@ -0,0 +1,87 @@ +/// This test is used to make sure a flesh-and-bone base human can suffer all the types of wounds, and that suffering more severe wounds removes and replaces the lesser wound. Also tests that [/mob/living/carbon/proc/fully_heal] removes all wounds +/datum/unit_test/test_human_base/Run() + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human) + + /// the limbs have no wound resistance like the chest and head do, so let's go with the r_arm + var/obj/item/bodypart/tested_part = victim.get_bodypart(BODY_ZONE_R_ARM) + /// In order of the wound types we're trying to inflict, what sharpness do we need to deal them? + var/list/sharps = list(SHARP_NONE, SHARP_EDGED, SHARP_POINTY, SHARP_NONE) + /// Since burn wounds need burn damage, duh + var/list/dam_types = list(BRUTE, BRUTE, BRUTE, BURN) + + var/i = 1 + var/list/iter_test_wound_list + + for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\ + list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\ + list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\ + list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical))) + + TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test") + var/datum/wound/iter_test_wound + var/threshold_penalty = 0 + + for(iter_test_wound in iter_test_wound_list) + var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty + if(dam_types[i] == BRUTE) + tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i]) + else if(dam_types[i] == BURN) + tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i]) + + TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]") + TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]") + var/datum/wound/actual_wound = victim.all_wounds[1] + TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]") + threshold_penalty = actual_wound.threshold_penalty + i++ + victim.fully_heal(TRUE) // should clear all wounds between types + + +/// This test is used for making sure species with bones but no flesh (skeletons, plasmamen) can only suffer BONE_WOUNDS, and nothing tagged with FLESH_WOUND (it's possible to require both) +/datum/unit_test/test_human_bone/Run() + var/mob/living/carbon/human/victim = allocate(/mob/living/carbon/human) + + /// the limbs have no wound resistance like the chest and head do, so let's go with the r_arm + var/obj/item/bodypart/tested_part = victim.get_bodypart(BODY_ZONE_R_ARM) + /// In order of the wound types we're trying to inflict, what sharpness do we need to deal them? + var/list/sharps = list(SHARP_NONE, SHARP_EDGED, SHARP_POINTY, SHARP_NONE) + /// Since burn wounds need burn damage, duh + var/list/dam_types = list(BRUTE, BRUTE, BRUTE, BURN) + + var/i = 1 + var/list/iter_test_wound_list + victim.dna.species.species_traits &= HAS_FLESH // take away the base human's flesh (ouchie!) ((not actually ouchie, this just affects their wounds and dismemberment handling)) + + for(iter_test_wound_list in list(list(/datum/wound/blunt/moderate, /datum/wound/blunt/severe, /datum/wound/blunt/critical),\ + list(/datum/wound/slash/moderate, /datum/wound/slash/severe, /datum/wound/slash/critical),\ + list(/datum/wound/pierce/moderate, /datum/wound/pierce/severe, /datum/wound/pierce/critical),\ + list(/datum/wound/burn/moderate, /datum/wound/burn/severe, /datum/wound/burn/critical))) + + TEST_ASSERT_EQUAL(length(victim.all_wounds), 0, "Patient is somehow wounded before test") + var/datum/wound/iter_test_wound + var/threshold_penalty = 0 + + for(iter_test_wound in iter_test_wound_list) + var/threshold = initial(iter_test_wound.threshold_minimum) - threshold_penalty // just enough to guarantee the next tier of wound, given the existing wound threshold penalty + if(dam_types[i] == BRUTE) + tested_part.receive_damage(WOUND_MINIMUM_DAMAGE, 0, wound_bonus = threshold, sharpness=sharps[i]) + else if(dam_types[i] == BURN) + tested_part.receive_damage(0, WOUND_MINIMUM_DAMAGE, wound_bonus = threshold, sharpness=sharps[i]) + + // so if we just tried to deal a flesh wound, make sure we didn't actually suffer it. We may have suffered a bone wound instead, but we just want to make sure we don't have a flesh wound + if(initial(iter_test_wound.wound_flags) & FLESH_WOUND) + if(!length(victim.all_wounds)) // not having a wound is good news + continue + else // we have to check that it's actually a bone wound and not the intended wound type + TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]") + var/datum/wound/actual_wound = victim.all_wounds[1] + TEST_ASSERT((actual_wound.wound_flags & ~FLESH_WOUND), "Patient has flesh wound despite no HAS_FLESH flag, expected either no wound or bone wound. Offending wound: [actual_wound]") + threshold_penalty = actual_wound.threshold_penalty + else // otherwise if it's a bone wound, check that we have it per usual + TEST_ASSERT(length(victim.all_wounds), "Patient has no wounds when one wound is expected. Severity: [initial(iter_test_wound.severity)]") + TEST_ASSERT_EQUAL(length(victim.all_wounds), 1, "Patient has more than one wound when only one is expected. Severity: [initial(iter_test_wound.severity)]") + var/datum/wound/actual_wound = victim.all_wounds[1] + TEST_ASSERT_EQUAL(actual_wound.type, iter_test_wound, "Patient has wound of incorrect severity. Expected: [initial(iter_test_wound.name)] Got: [actual_wound]") + threshold_penalty = actual_wound.threshold_penalty + i++ + victim.fully_heal(TRUE) // should clear all wounds between types diff --git a/code/modules/unit_tests/metabolizing.dm b/code/modules/unit_tests/metabolizing.dm new file mode 100644 index 0000000000..895762c0ec --- /dev/null +++ b/code/modules/unit_tests/metabolizing.dm @@ -0,0 +1,19 @@ +/datum/unit_test/metabolization/Run() + // Pause natural mob life so it can be handled entirely by the test + SSmobs.pause() + + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + var/mob/living/carbon/monkey/monkey = allocate(/mob/living/carbon/monkey) + + for (var/reagent_type in subtypesof(/datum/reagent)) + test_reagent(human, reagent_type) + test_reagent(monkey, reagent_type) + +/datum/unit_test/metabolization/proc/test_reagent(mob/living/carbon/C, reagent_type) + C.reagents.add_reagent(reagent_type, 10) + C.reagents.metabolize(C, can_overdose = TRUE) + C.reagents.clear_reagents() + +/datum/unit_test/metabolization/Destroy() + SSmobs.ignite() + return ..() diff --git a/code/modules/unit_tests/outfit_sanity.dm b/code/modules/unit_tests/outfit_sanity.dm new file mode 100644 index 0000000000..235820f9e9 --- /dev/null +++ b/code/modules/unit_tests/outfit_sanity.dm @@ -0,0 +1,50 @@ +#define CHECK_OUTFIT_SLOT(outfit_key, slot_name) if (outfit.##outfit_key) { \ + H.equip_to_slot_or_del(new outfit.##outfit_key(H), ##slot_name, TRUE); \ + /* We don't check the result of equip_to_slot_or_del because it returns false for random jumpsuits, as they delete themselves on init */ \ + if (!H.get_item_by_slot(##slot_name)) { \ + Fail("[outfit.name]'s [#outfit_key] is invalid!"); \ + } \ +} + +/datum/unit_test/outfit_sanity/Run() + var/mob/living/carbon/human/H = allocate(/mob/living/carbon/human) + + for (var/outfit_type in subtypesof(/datum/outfit)) + // Only make one human and keep undressing it because it's much faster + for (var/obj/item/I in H.get_equipped_items(include_pockets = TRUE)) + qdel(I) + + var/datum/outfit/outfit = new outfit_type + outfit.pre_equip(H, TRUE) + + CHECK_OUTFIT_SLOT(uniform, ITEM_SLOT_ICLOTHING) + CHECK_OUTFIT_SLOT(suit, ITEM_SLOT_OCLOTHING) + CHECK_OUTFIT_SLOT(back, ITEM_SLOT_BACK) + CHECK_OUTFIT_SLOT(belt, ITEM_SLOT_BELT) + CHECK_OUTFIT_SLOT(gloves, ITEM_SLOT_GLOVES) + CHECK_OUTFIT_SLOT(shoes, ITEM_SLOT_FEET) + CHECK_OUTFIT_SLOT(head, ITEM_SLOT_HEAD) + CHECK_OUTFIT_SLOT(mask, ITEM_SLOT_MASK) + CHECK_OUTFIT_SLOT(neck, ITEM_SLOT_NECK) + CHECK_OUTFIT_SLOT(ears, ITEM_SLOT_EARS) + CHECK_OUTFIT_SLOT(glasses, ITEM_SLOT_EYES) + CHECK_OUTFIT_SLOT(id, ITEM_SLOT_ID) + CHECK_OUTFIT_SLOT(suit_store, ITEM_SLOT_SUITSTORE) + CHECK_OUTFIT_SLOT(l_pocket, ITEM_SLOT_POCKET) + CHECK_OUTFIT_SLOT(r_pocket, ITEM_SLOT_POCKET) + + if (outfit.backpack_contents || outfit.box) + var/list/backpack_contents = outfit.backpack_contents?.Copy() + if (outfit.box) + if (!backpack_contents) + backpack_contents = list() + backpack_contents.Insert(1, outfit.box) + backpack_contents[outfit.box] = 1 + + for (var/path in backpack_contents) + var/number = backpack_contents[path] || 1 + for (var/_ in 1 to number) + if (!H.equip_to_slot_or_del(new path(H), ITEM_SLOT_BACKPACK, TRUE)) + Fail("[outfit.name]'s backpack_contents are invalid! Couldn't add [path] to backpack.") + +#undef CHECK_OUTFIT_SLOT diff --git a/code/modules/unit_tests/plantgrowth_tests.dm b/code/modules/unit_tests/plantgrowth_tests.dm new file mode 100644 index 0000000000..6b40236860 --- /dev/null +++ b/code/modules/unit_tests/plantgrowth_tests.dm @@ -0,0 +1,27 @@ + +// Checks plants for broken tray icons. Use Advanced Proc Call to activate. +// Maybe some day it would be used as unit test. +// -------- IT IS NOW! +/datum/unit_test/plantgrowth/Run() + var/list/states = icon_states('icons/obj/hydroponics/growing.dmi') + states |= icon_states('icons/obj/hydroponics/growing_fruits.dmi') + states |= icon_states('icons/obj/hydroponics/growing_flowers.dmi') + states |= icon_states('icons/obj/hydroponics/growing_mushrooms.dmi') + states |= icon_states('icons/obj/hydroponics/growing_vegetables.dmi') + states |= icon_states('goon/icons/obj/hydroponics.dmi') + var/list/paths = subtypesof(/obj/item/seeds) - /obj/item/seeds - typesof(/obj/item/seeds/sample) - /obj/item/seeds/lavaland + + for(var/seedpath in paths) + var/obj/item/seeds/seed = new seedpath + + for(var/i in 1 to seed.growthstages) + if("[seed.icon_grow][i]" in states) + continue + Fail("[seed.name] ([seed.type]) lacks the [seed.icon_grow][i] icon!") + + if(!(seed.icon_dead in states)) + Fail("[seed.name] ([seed.type]) lacks the [seed.icon_dead] icon!") + + if(seed.icon_harvest) // mushrooms have no grown sprites, same for items with no product + if(!(seed.icon_harvest in states)) + Fail("[seed.name] ([seed.type]) lacks the [seed.icon_harvest] icon!") diff --git a/code/modules/unit_tests/quick_swap_sanity.dm b/code/modules/unit_tests/quick_swap_sanity.dm new file mode 100644 index 0000000000..85e73f9b6a --- /dev/null +++ b/code/modules/unit_tests/quick_swap_sanity.dm @@ -0,0 +1,31 @@ +/// Test that quick swap correctly swaps items and invalidates suit storage +/datum/unit_test/quick_swap_sanity/Run() + // Create a human with a medical winter coat and a health analyzer in suit storage + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + + var/obj/item/coat = allocate(/obj/item/clothing/suit/hooded/wintercoat/medical) + TEST_ASSERT(human.equip_to_slot_if_possible(coat, ITEM_SLOT_OCLOTHING), "Couldn't equip winter coat") + + var/obj/item/analyzer = allocate(/obj/item/healthanalyzer) + TEST_ASSERT(human.equip_to_slot_if_possible(analyzer, ITEM_SLOT_SUITSTORE), "Couldn't equip health analyzer") + + // Then, have them quick swap between the coat and a space suit + var/obj/item/hardsuit = allocate(/obj/item/clothing/suit/space/hardsuit) + TEST_ASSERT(human.equip_to_appropriate_slot(hardsuit, swap = TRUE), "Couldn't quick swap to hardsuit") + + // Check if the human has the hardsuit on + TEST_ASSERT_EQUAL(human.wear_suit, hardsuit, "Human didn't equip the hardsuit") + + // Make sure the health analyzer was dropped as part of the swap + // Since health analyzers are an invalid suit storage item + TEST_ASSERT_EQUAL(human.s_store, null, "Human didn't drop the health analyzer") + + // Give the human an emergency oxygen tank + // This is valid suit storage for both the winter coat AND the hardsuit + var/obj/item/tank = allocate(/obj/item/tank/internals/emergency_oxygen) + TEST_ASSERT(human.equip_to_slot_if_possible(tank, ITEM_SLOT_SUITSTORE), "Couldn't equip emergency oxygen tank") + + // Now, quick swap back to the coat + // Since the tank is a valid suit storage item, it should not be dropped + TEST_ASSERT(human.equip_to_appropriate_slot(coat, swap = TRUE), "Couldn't quick swap to coat") + TEST_ASSERT_EQUAL(human.s_store, tank, "Human dropped the oxygen tank, when it was a valid item to keep in suit storage") diff --git a/code/modules/unit_tests/resist.dm b/code/modules/unit_tests/resist.dm new file mode 100644 index 0000000000..9fe5cd1114 --- /dev/null +++ b/code/modules/unit_tests/resist.dm @@ -0,0 +1,29 @@ +/// Test that stop, drop, and roll lowers fire stacks +/datum/unit_test/stop_drop_and_roll/Run() + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + + TEST_ASSERT_EQUAL(human.fire_stacks, 0, "Human does not have 0 fire stacks pre-ignition") + + human.adjust_fire_stacks(5) + human.IgniteMob() + + TEST_ASSERT_EQUAL(human.fire_stacks, 5, "Human does not have 5 fire stacks pre-resist") + + // Stop, drop, and roll has a sleep call. This would delay the test, and is not necessary. + CallAsync(human, /mob/living/verb/resist) + + TEST_ASSERT(human.fire_stacks < 5, "Human did not lower fire stacks after resisting") + +/// Test that you can resist out of a container +/datum/unit_test/container_resist/Run() + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human) + var/obj/structure/closet/closet = allocate(/obj/structure/closet, get_turf(human)) + + closet.open(human) + TEST_ASSERT(!(human in closet.contents), "Human was in the contents of an open closet") + + closet.close(human) + TEST_ASSERT(human in closet.contents, "Human was not in the contents of the closed closet") + + human.resist() + TEST_ASSERT(!(human in closet.contents), "Human resisted out of a standard closet, but was still in it") diff --git a/code/modules/unit_tests/say.dm b/code/modules/unit_tests/say.dm new file mode 100644 index 0000000000..3fe6675ab4 --- /dev/null +++ b/code/modules/unit_tests/say.dm @@ -0,0 +1,24 @@ +/// 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()) + //note to lettern: add the ++, ||, __, and the verb*text checks + +/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..7189e87277 100644 --- a/code/modules/unit_tests/spawn_humans.dm +++ b/code/modules/unit_tests/spawn_humans.dm @@ -1,7 +1,7 @@ /datum/unit_test/spawn_humans/Run() - var/locs = block(run_loc_bottom_left, run_loc_top_right) + var/locs = block(run_loc_bottom_left, run_loc_top_right) - for(var/I in 1 to 5) - new /mob/living/carbon/human(pick(locs)) + for(var/I in 1 to 5) + new /mob/living/carbon/human(pick(locs)) - sleep(50) + sleep(50) diff --git a/code/modules/unit_tests/subsystem_init.dm b/code/modules/unit_tests/subsystem_init.dm index f768f03f78..7d5473bc1b 100644 --- a/code/modules/unit_tests/subsystem_init.dm +++ b/code/modules/unit_tests/subsystem_init.dm @@ -4,4 +4,4 @@ if(ss.flags & SS_NO_INIT) continue if(!ss.initialized) - Fail("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.") \ No newline at end of file + Fail("[ss]([ss.type]) is a subsystem meant to initialize but doesn't get set as initialized.") diff --git a/code/modules/unit_tests/surgeries.dm b/code/modules/unit_tests/surgeries.dm new file mode 100644 index 0000000000..491b0c5645 --- /dev/null +++ b/code/modules/unit_tests/surgeries.dm @@ -0,0 +1,79 @@ +/datum/unit_test/amputation/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) + + TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 0, "Patient is somehow missing limbs before surgery") + + var/datum/surgery/amputation/surgery = new(patient, BODY_ZONE_R_ARM, patient.get_bodypart(BODY_ZONE_R_ARM)) + + var/datum/surgery_step/sever_limb/sever_limb = new + sever_limb.success(user, patient, BODY_ZONE_R_ARM, null, surgery) + + TEST_ASSERT_EQUAL(patient.get_missing_limbs().len, 1, "Patient did not lose any limbs") + TEST_ASSERT_EQUAL(patient.get_missing_limbs()[1], BODY_ZONE_R_ARM, "Patient is missing a limb that isn't the one we operated on") + +/datum/unit_test/brain_surgery/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) + patient.gain_trauma_type(BRAIN_TRAUMA_MILD, TRAUMA_RESILIENCE_SURGERY) + patient.setOrganLoss(ORGAN_SLOT_BRAIN, 20) + + TEST_ASSERT(patient.has_trauma_type(), "Patient does not have any traumas, despite being given one") + + var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) + + var/datum/surgery_step/fix_brain/fix_brain = new + fix_brain.success(user, patient) + + TEST_ASSERT(!patient.has_trauma_type(), "Patient kept their brain trauma after brain surgery") + TEST_ASSERT(patient.getOrganLoss(ORGAN_SLOT_BRAIN) < 20, "Patient did not heal their brain damage after brain surgery") + +/datum/unit_test/multiple_surgeries/Run() + var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/patient_zero = allocate(/mob/living/carbon/human) + var/mob/living/carbon/human/patient_one = allocate(/mob/living/carbon/human) + + var/obj/item/scalpel/scalpel = allocate(/obj/item/scalpel) + + var/datum/surgery_step/incise/surgery_step = new + var/datum/surgery/organ_manipulation/surgery_for_zero = new + + INVOKE_ASYNC(surgery_step, /datum/surgery_step/proc/initiate, user, patient_zero, BODY_ZONE_CHEST, scalpel, surgery_for_zero) + TEST_ASSERT(surgery_for_zero.step_in_progress, "Surgery on patient zero was not initiated") + + var/datum/surgery/organ_manipulation/surgery_for_one = new + + // Without waiting for the incision to complete, try to start a new surgery + TEST_ASSERT(!surgery_step.initiate(user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one), "Was allowed to start a second surgery without the rod of asclepius") + TEST_ASSERT(!surgery_for_one.step_in_progress, "Surgery for patient one is somehow in progress, despite not initiating") + + user.apply_status_effect(STATUS_EFFECT_HIPPOCRATIC_OATH) + INVOKE_ASYNC(surgery_step, /datum/surgery_step/proc/initiate, user, patient_one, BODY_ZONE_CHEST, scalpel, surgery_for_one) + TEST_ASSERT(surgery_for_one.step_in_progress, "Surgery on patient one was not initiated, despite having rod of asclepius") + +/datum/unit_test/tend_wounds/Run() + var/mob/living/carbon/human/patient = allocate(/mob/living/carbon/human) + patient.take_overall_damage(100, 100) + + var/mob/living/carbon/human/user = allocate(/mob/living/carbon/human) + + // Test that tending wounds actually lowers damage + var/datum/surgery_step/heal/brute/basic/basic_brute_heal = new + basic_brute_heal.success(user, patient, BODY_ZONE_CHEST) + TEST_ASSERT(patient.getBruteLoss() < 100, "Tending brute wounds didn't lower brute damage ([patient.getBruteLoss()])") + + var/datum/surgery_step/heal/burn/basic/basic_burn_heal = new + basic_burn_heal.success(user, patient, BODY_ZONE_CHEST) + TEST_ASSERT(patient.getFireLoss() < 100, "Tending burn wounds didn't lower burn damage ([patient.getFireLoss()])") + + // Test that wearing clothing lowers heal amount + var/mob/living/carbon/human/naked_patient = allocate(/mob/living/carbon/human) + naked_patient.take_overall_damage(100) + + var/mob/living/carbon/human/clothed_patient = allocate(/mob/living/carbon/human) + clothed_patient.equipOutfit(/datum/outfit/job/doctor, TRUE) + clothed_patient.take_overall_damage(100) + + basic_brute_heal.success(user, naked_patient, BODY_ZONE_CHEST) + basic_brute_heal.success(user, clothed_patient, BODY_ZONE_CHEST) + + TEST_ASSERT(naked_patient.getBruteLoss() < clothed_patient.getBruteLoss(), "Naked patient did not heal more from wounds tending than a clothed patient") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm index 49974f2cb0..36b406e75e 100644 --- a/code/modules/unit_tests/unit_test.dm +++ b/code/modules/unit_tests/unit_test.dm @@ -1,14 +1,9 @@ /* - Usage: Override /Run() to run your test code - 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 - */ GLOBAL_DATUM(current_test, /datum/unit_test) @@ -18,16 +13,18 @@ GLOBAL_VAR(test_log) /datum/unit_test //Bit of metadata for the future maybe var/list/procs_tested - + //usable vars var/turf/run_loc_bottom_left var/turf/run_loc_top_right //internal shit var/succeeded = TRUE + var/list/allocated var/list/fail_reasons /datum/unit_test/New() + allocated = new run_loc_bottom_left = locate(1, 1, 1) run_loc_top_right = locate(5, 5, 1) @@ -35,6 +32,7 @@ GLOBAL_VAR(test_log) //clear the test area for(var/atom/movable/AM in block(run_loc_bottom_left, run_loc_top_right)) qdel(AM) + QDEL_LIST(allocated) return ..() /datum/unit_test/proc/Run() @@ -48,6 +46,18 @@ GLOBAL_VAR(test_log) LAZYADD(fail_reasons, reason) +/// Allocates an instance of the provided type, and places it somewhere in an available loc +/// Instances allocated through this proc will be destroyed when the test is over +/datum/unit_test/proc/allocate(type, ...) + var/list/arguments = args.Copy(2) + if (!arguments.len) + arguments = list(run_loc_bottom_left) + else if (arguments[1] == null) + arguments[1] = run_loc_bottom_left + var/instance = new type(arglist(arguments)) + allocated += instance + return instance + /proc/RunUnitTests() CHECK_TICK diff --git a/tools/travis/check_filedirs.sh b/tools/travis/check_filedirs.sh index 30df015763..b7bac4b7a2 100755 --- a/tools/travis/check_filedirs.sh +++ b/tools/travis/check_filedirs.sh @@ -1,15 +1,15 @@ + #!/bin/bash if [ -n "$1" ] then dme=$1 else - echo "Specify a DME to check" + echo "ERROR: Specify a DME to check" exit 1 fi if [[ $(awk '/BEGIN_FILE_DIR/{flag=1;next}/END_FILE_DIR/{flag=0}flag' $dme | wc -l) -ne 1 ]] then - echo "File DIR was ticked, please untick it, see: https://tgstation13.org/phpBB/viewtopic.php?f=5&t=321 for more" + echo "ERROR: File DIR was ticked, please untick it, see: https://tgstation13.org/phpBB/viewtopic.php?f=5&t=321 for more" exit 1 fi - diff --git a/tools/travis/check_grep.sh b/tools/travis/check_grep.sh index 2117b570eb..b85f775eb9 100755 --- a/tools/travis/check_grep.sh +++ b/tools/travis/check_grep.sh @@ -7,43 +7,70 @@ shopt -s globstar st=0 if grep -El '^\".+\" = \(.+\)' _maps/**/*.dmm; then - echo "Non-TGM formatted map detected. Please convert it using Map Merger!" + echo "ERROR: Non-TGM formatted map detected. Please convert it using Map Merger!" st=1 fi; if grep -P '^\ttag = \"icon' _maps/**/*.dmm; then - echo "tag vars from icon state generation detected in maps, please remove them." + echo "ERROR: tag vars from icon state generation detected in maps, please remove them." st=1 fi; -if grep 'step_[xy]' _maps/**/*.dmm; then - echo "step_x/step_y variables detected in maps, please remove them." +if grep -P 'step_[xy]' _maps/**/*.dmm; then + echo "ERROR: step_x/step_y variables detected in maps, please remove them." st=1 fi; -if grep 'pixel_[xy] = 0' _maps/**/*.dmm; then - echo "pixel_x/pixel_y = 0 variables detected in maps, please review to ensure they are not dirty varedits." +if grep -P 'pixel_[^xy]' _maps/**/*.dmm; then + echo "ERROR: incorrect pixel offset variables detected in maps, please remove them." + st=1 fi; if grep -P '\td[1-2] =' _maps/**/*.dmm; then - echo "d1/d2 cable variables detected in maps, please remove them." + echo "ERROR: d1/d2 cable variables detected in maps, please remove them." st=1 fi; -if grep '^/area/.+[\{]' _maps/**/*.dmm; then - echo "Vareditted /area path use detected in maps, please replace with proper paths." +if grep -P '^/area/.+[\{]' _maps/**/*.dmm; then + echo "WARNING: Vareditted /area path use detected in maps, please replace with proper paths." + #st=1 +fi; +if grep -P '\W\/turf\s*[,\){]' _maps/**/*.dmm; then + echo "ERROR: base /turf path use detected in maps, please replace with proper paths." st=1 fi; -if grep '\W\/turf\s*[,\){]' _maps/**/*.dmm; then - echo "base /turf path use detected in maps, please replace with proper paths." - st=1 -fi; -if grep '^/*var/' code/**/*.dm; then - echo "Unmanaged global var use detected in code, please use the helpers." +if grep -P '^/*var/' code/**/*.dm; then + echo "ERROR: Unmanaged global var use detected in code, please use the helpers." st=1 fi; +nl=' +' +nl=$'\n' +while read f; do + t=$(tail -c2 "$f"; printf x); r1="${nl}$"; r2="${nl}${r1}" + if [[ ! ${t%x} =~ $r1 ]]; then + echo "file $f is missing a trailing newline" + #st=1 + fi; +done < <(find . -type f -name '*.dm') if grep -i 'centcomm' code/**/*.dm; then - echo "Misspelling(s) of CENTCOM detected in code, please remove the extra M(s)." + echo "ERROR: Misspelling(s) of CENTCOM detected in code, please remove the extra M(s)." st=1 fi; if grep -i 'centcomm' _maps/**/*.dmm; then - echo "Misspelling(s) of CENTCOM detected in maps, please remove the extra M(s)." + echo "ERROR: Misspelling(s) of CENTCOM detected in maps, please remove the extra M(s)." st=1 fi; +if ls _maps/*.json | grep -P "[A-Z]"; then + echo "Uppercase in a map json detected, these must be all lowercase." + st=1 +fi; +for json in _maps/*.json +do + map_path=$(jq -r '.map_path' $json) + while read map_file; do + filename="_maps/$map_path/$map_file" + if [ ! -f $filename ] + then + echo "found invalid file reference to $filename in _maps/$json" + st=1 + fi + done < <(jq -r '[.map_file] | flatten | .[]' $json) +done exit $st diff --git a/tools/travis/install_dreamchecker.sh b/tools/travis/install_dreamchecker.sh deleted file mode 100755 index 4423e5aafb..0000000000 --- a/tools/travis/install_dreamchecker.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash -set -euo pipefail - -source dependencies.sh - -wget -O ~/dreamchecker "https://github.com/SpaceManiac/SpacemanDMM/releases/download/$SPACEMAN_DMM_VERSION/dreamchecker" -chmod +x ~/dreamchecker -~/dreamchecker --version diff --git a/tools/travis/install_spaceman_dmm.sh b/tools/travis/install_spaceman_dmm.sh new file mode 100755 index 0000000000..39464193f8 --- /dev/null +++ b/tools/travis/install_spaceman_dmm.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -euo pipefail + +source dependencies.sh + +wget -O ~/$1 "https://github.com/SpaceManiac/SpacemanDMM/releases/download/$SPACEMAN_DMM_VERSION/$1" +chmod +x ~/$1 +~/$1 --version diff --git a/tools/travis/run_server.sh b/tools/travis/run_server.sh index 975e8f6884..04c491cd5c 100755 --- a/tools/travis/run_server.sh +++ b/tools/travis/run_server.sh @@ -2,6 +2,7 @@ set -euo pipefail tools/deploy.sh travis_test +rm travis_test/*.dll mkdir travis_test/config #test config