diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 058a32d65a..d21efd8c7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,178 +1,57 @@ -name: CI +name: CI Suite + on: pull_request: branches: - master + - "project/**" + merge_group: + branches: + - master -env: - BASENAME: "vorestation" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: + start_gate: + if: ( !contains(github.event.head_commit.message, '[ci skip]') ) + name: Start Gate + runs-on: ubuntu-24.04 + steps: + - name: Mandatory Empty Step + run: exit 0 + run_linters: - if: ( !contains(github.event.head_commit.message, '[ci skip]') ) name: Run Linters - runs-on: ubuntu-24.04 - concurrency: - group: run_linters-${{ github.head_ref || github.run_id }} - cancel-in-progress: true - steps: - - uses: actions/checkout@v4 - with: - fetch-depth: 0 - # Caches - - name: Setup Bun - uses: ./.github/actions/setup_bun - - name: Restore Bootstrap cache - uses: actions/cache@v4 - with: - path: tools/bootstrap/.cache - key: ${{ runner.os }}-bootstrap-${{ hashFiles('tools/requirements.txt') }} - restore-keys: | - ${{ runner.os }}-bootstrap- - - name: Restore Rust cache - uses: actions/cache@v4 - with: - path: ~/.cargo - key: ${{ runner.os }}-rust-${{ hashFiles('tools/ci/ci_dependencies.sh')}} - restore-keys: | - ${{ runner.os }}-rust- - - name: Restore Cutter cache - uses: actions/cache@v4 - with: - path: tools/icon_cutter/cache - key: ${{ runner.os }}-cutter-${{ hashFiles('dependencies.sh') }} - # End Caches - - name: Install Tools - run: | - pip3 install setuptools - bash tools/ci/install_ripgrep.sh - bash tools/ci/install_spaceman_dmm.sh dmm-tools - tools/bootstrap/python -c '' - - name: Run Validation Tests - run: bash tools/ci/validate_files.sh - - name: Run Define Sanity Checks - run: tools/bootstrap/python -m define_sanity.check - - name: Run DMI Tests - run: tools/bootstrap/python -m dmi.test - - name: Run Map Checks - run: | - tools/bootstrap/python -m tools.maplint.source - tools/bootstrap/python -m mapmerge2.dmm_test - - name: Run TGUI Checks - run: tools/build/build.sh --ci lint tgui-test - - name: Run Nanomap Checks - run: tools/github-actions/nanomap-renderer-invoker.sh --testing + needs: start_gate + uses: ./.github/workflows/run_linters.yml - dreamchecker: - if: ( !contains(github.event.head_commit.message, '[ci skip]') ) - name: DreamChecker - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - - name: Restore SpacemanDMM Cache - uses: actions/cache@v4 - with: - path: ~/SpacemanDMM - key: ${{ runner.os }}-dreamchecker-${{ hashFiles('dependencies.sh')}} - restore-keys: ${{ runner.os }}-dreamchecker - - - name: Install Dependencies - run: | - tools/ci/install_spaceman_dmm.sh dreamchecker - - - name: Run Linter - id: linter - run: | - ~/dreamchecker > ${GITHUB_WORKSPACE}/output-annotations.txt 2>&1 - - - name: Annotate Linter - uses: yogstation13/DreamAnnotate@v2 - if: always() - with: - outputFile: output-annotations.txt - - odlint: - name: Lint with OpenDream - runs-on: ubuntu-22.04 - steps: - - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 9 - - uses: actions/checkout@v4 - - uses: robinraju/release-downloader@v1.12 - with: - repository: "OpenDreamProject/OpenDream" - tag: "latest" - fileName: "DMCompiler_linux-x64.tar.gz" - extract: true - - run: ./DMCompiler_linux-x64/DMCompiler --suppress-unimplemented vorestation.dme - - unit_tests: - timeout-minutes: 30 - if: ( !contains(github.event.head_commit.message, '[ci skip]') ) - strategy: - matrix: - map: ["USE_MAP_TETHER", "USE_MAP_STELLARDELIGHT", "USE_MAP_GROUNDBASE"] - # name: Integration Tests (${{ matrix.map }}) + run_all_tests: name: Integration Tests - # needs: ['run_linters', 'dreamchecker'] - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - name: Ensure +x on CI directory - run: | - chmod -R +x ./tools/ci - - name: Restore BYOND from Cache - uses: ./.github/actions/restore_or_install_byond - with: - major: ${{ inputs.major }} - minor: ${{ inputs.minor }} - - name: Install RUST_G Dependencies - run: | - sudo dpkg --add-architecture i386 - sudo apt update || true - sudo apt install zlib1g-dev:i386 - ldd librust_g.so - ldd libbapi_dmm_reader.so - ldd libverdigris.so - - name: Unit Tests - run: tools/ci/compile_and_run.sh - env: - EXTRA_ARGS: "-DUNIT_TEST -D${{ matrix.map }}" - RUN: "1" + needs: start_gate + uses: ./.github/workflows/perform_regular_version_tests.yml + with: + maps: '{"paths":["USE_MAP_TETHER","USE_MAP_STELLARDELIGHT","USE_MAP_GROUNDBASE"]}' + max_required_byond_client: 516 - extra_map_tests: - if: ( !contains(github.event.head_commit.message, '[ci skip]') ) - name: Map Tests - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - - name: Ensure +x on CI directory - run: | - chmod -R +x ./tools/ci - - name: Restore BYOND from Cache - uses: ./.github/actions/restore_or_install_byond - with: - major: ${{ inputs.major }} - minor: ${{ inputs.minor }} - - name: Compile POIs - run: tools/ci/compile_and_run.sh - env: - EXTRA_ARGS: "-DMAP_TEST" - RUN: "0" - - name: Compile away missions - run: tools/ci/compile_and_run.sh - env: - EXTRA_ARGS: "-DAWAY_MISSION_TEST -DUSE_MAP_TETHER" # Only Tether has support for all away missions - RUN: "0" + run_extra_map_tests: + name: Extra Map Tests + needs: start_gate + uses: ./.github/workflows/run_extra_map_tests.yml - tests_successful: - if: ( !contains(github.event.head_commit.message, '[ci skip]') ) - name: Integration Tests - needs: ["run_linters", "dreamchecker", "unit_tests", "extra_map_tests"] + completion_gate: # Serves as a non-moving target for branch rulesets + if: always() && !cancelled() + name: Completion Gate + needs: + [ + run_all_tests, + run_extra_map_tests, + run_linters, + ] runs-on: ubuntu-24.04 steps: - - name: Report Success - run: | - echo "Jobs Successful!" + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} diff --git a/.github/workflows/perform_regular_version_tests.yml b/.github/workflows/perform_regular_version_tests.yml new file mode 100644 index 0000000000..724db11f7d --- /dev/null +++ b/.github/workflows/perform_regular_version_tests.yml @@ -0,0 +1,24 @@ +name: Run Regular BYOND Version Tests + +on: + workflow_call: + inputs: + maps: + required: true + type: string + max_required_byond_client: + required: true + type: string + +jobs: + run: + uses: ./.github/workflows/run_integration_tests.yml + + strategy: + fail-fast: false + matrix: + map: ${{ fromJSON(inputs.maps).paths }} + + with: + map: ${{ matrix.map }} + max_required_byond_client: ${{ inputs.max_required_byond_client }} diff --git a/.github/workflows/run_extra_map_tests.yml b/.github/workflows/run_extra_map_tests.yml new file mode 100644 index 0000000000..95696bad02 --- /dev/null +++ b/.github/workflows/run_extra_map_tests.yml @@ -0,0 +1,51 @@ +name: Run Extra Map Tests + +on: + workflow_call: + inputs: + major: + required: false + type: string + minor: + required: false + type: string + +jobs: + run_extra_map_tests: + # If `inputs.major` is specified, this will output `Run Tests (major.minor; map; max)`. + # For example, `Run Tests (515.1627; runtimestation; 515)`. + # + # Otherwise, it will output `Run Tests (map; max)`. + # For example, `Run Tests (runtimestation; 515)`. + name: Run Extra Map Tests (${{ inputs.major && format('{0}.{1}; ', inputs.major, inputs.minor) || '' }}) + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + - name: Ensure +x on CI directory + run: | + chmod -R +x ./tools/ci + - name: Restore BYOND from Cache + uses: ./.github/actions/restore_or_install_byond + with: + major: ${{ inputs.major }} + minor: ${{ inputs.minor }} + - name: Install RUST_G Dependencies + run: | + sudo dpkg --add-architecture i386 + sudo apt update || true + sudo apt install zlib1g-dev:i386 + ldd librust_g.so + ldd libbapi_dmm_reader.so + ldd libverdigris.so + - name: Compile POIs + run: tools/ci/compile_and_run.sh + env: + EXTRA_ARGS: "-DMAP_TEST" + RUN: "0" + - name: Compile away missions + run: tools/ci/compile_and_run.sh + env: + EXTRA_ARGS: "-DAWAY_MISSION_TEST -DUSE_MAP_TETHER" # Only Tether has support for all away missions + RUN: "0" diff --git a/.github/workflows/run_integration_tests.yml b/.github/workflows/run_integration_tests.yml new file mode 100644 index 0000000000..ee5f8b7add --- /dev/null +++ b/.github/workflows/run_integration_tests.yml @@ -0,0 +1,82 @@ +name: Run Integration Tests + +on: + workflow_call: + inputs: + map: + required: true + type: string + major: + required: false + type: string + minor: + required: false + type: string + max_required_byond_client: + required: true + type: string + +jobs: + run_integration_tests: + # If `inputs.major` is specified, this will output `Run Tests (major.minor; map; max)`. + # For example, `Run Tests (515.1627; runtimestation; 515)`. + # + # Otherwise, it will output `Run Tests (map; max)`. + # For example, `Run Tests (runtimestation; 515)`. + name: Run Tests (${{ inputs.major && format('{0}.{1}; ', inputs.major, inputs.minor) || '' }}${{ inputs.map }}; ${{ inputs.max_required_byond_client }}) + runs-on: ubuntu-24.04 + timeout-minutes: 15 + + steps: + - uses: actions/checkout@v4 + - name: Ensure +x on CI directory + run: | + chmod -R +x ./tools/ci + - name: Restore BYOND from Cache + uses: ./.github/actions/restore_or_install_byond + with: + major: ${{ inputs.major }} + minor: ${{ inputs.minor }} + - name: Install RUST_G Dependencies + run: | + sudo dpkg --add-architecture i386 + sudo apt update || true + sudo apt install zlib1g-dev:i386 + ldd librust_g.so + ldd libbapi_dmm_reader.so + ldd libverdigris.so + - name: Unit Tests + id: run_tests + run: tools/ci/compile_and_run.sh + env: + EXTRA_ARGS: "-DUNIT_TESTS -D${{ inputs.map }}" + RUN: "1" + - name: On test fail, write a step summary + if: always() && steps.run_tests.outcome == 'failure' + run: | + # Get a JSON array of failed unit tests + FAILED_UNIT_TESTS=$(jq 'to_entries | map(.value | select(.status == 1))' data/unit_tests.json) + + FAIL_COUNT=$(echo $FAILED_UNIT_TESTS | jq 'length') + + echo "# Test failures" >> $GITHUB_STEP_SUMMARY + echo "$FAIL_COUNT tests failed." >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + for i in $( seq $FAIL_COUNT ); do + CURRENT_FAIL=$(echo $FAILED_UNIT_TESTS | jq --arg i $i '.[($i | tonumber) - 1]') + + TEST=$(echo $CURRENT_FAIL | jq --raw-output '.name') + + echo "### $TEST" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo $CURRENT_FAIL | jq --raw-output '.message' >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + done + - name: Check client Compatibility + if: always() && steps.compile_tests.outcome == 'success' + uses: tgstation/byond-client-compatibility-check@v3 + with: + dmb-location: vorestation.dmb + max-required-client-version: ${{inputs.max_required_byond_client}} diff --git a/.github/workflows/run_linters.yml b/.github/workflows/run_linters.yml new file mode 100644 index 0000000000..bf9f82b03a --- /dev/null +++ b/.github/workflows/run_linters.yml @@ -0,0 +1,111 @@ +name: Run Linters + +on: + workflow_call: + +jobs: + linters: + runs-on: ubuntu-24.04 + timeout-minutes: 5 + + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + # Caches + - name: Restore SpacemanDMM cache + uses: actions/cache@v4 + with: + path: ~/SpacemanDMM + key: ${{ runner.os }}-spacemandmm-${{ hashFiles('dependencies.sh') }} + restore-keys: | + ${{ runner.os }}-spacemandmm- + - name: Setup Bun + uses: ./.github/actions/setup_bun + - name: Restore Bootstrap cache + uses: actions/cache@v4 + with: + path: tools/bootstrap/.cache + key: ${{ runner.os }}-bootstrap-${{ hashFiles('tools/requirements.txt') }} + restore-keys: | + ${{ runner.os }}-bootstrap- + - name: Restore Rust cache + uses: actions/cache@v4 + with: + path: ~/.cargo + key: ${{ runner.os }}-rust-${{ hashFiles('tools/ci/ci_dependencies.sh')}} + restore-keys: | + ${{ runner.os }}-rust- + - name: Restore Cutter cache + uses: actions/cache@v4 + with: + path: tools/icon_cutter/cache + key: ${{ runner.os }}-cutter-${{ hashFiles('dependencies.sh') }} + # End Caches + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4.2.0 + with: + dotnet-version: 9.x + - name: Install OpenDream + uses: robinraju/release-downloader@v1.12 + with: + repository: "OpenDreamProject/OpenDream" + tag: "latest" + fileName: "DMCompiler_linux-x64.tar.gz" + extract: true + - name: Install Tools + run: | + pip3 install setuptools + bash tools/ci/install_spaceman_dmm.sh dreamchecker + bash tools/ci/install_ripgrep.sh + tools/bootstrap/python -c '' + - name: Give Linters A Go + id: linter-setup + run: ":" + - name: Ensure genesis_call.dme is unchanged + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: bash tools/ci/check_genesis.sh + - name: Run Grep Checks + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: bash tools/ci/check_grep.sh + #- name: Ticked File Enforcement + # if: steps.linter-setup.conclusion == 'success' && !cancelled() + # run: | + # tools/bootstrap/python tools/ticked_file_enforcement/ticked_file_enforcement.py < tools/ticked_file_enforcement/schemas/tgstation_dme.json + # tools/bootstrap/python tools/ticked_file_enforcement/ticked_file_enforcement.py < tools/ticked_file_enforcement/schemas/unit_tests.json + - name: Run Define Sanity Checks + run: tools/bootstrap/python -m define_sanity.check + - name: Run DreamChecker + if: steps.linter-setup.conclusion == 'success' && !cancelled() + shell: bash + run: ~/dreamchecker 2>&1 | bash tools/ci/annotate_dm.sh + - name: Run OpenDream + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: ./DMCompiler_linux-x64/DMCompiler vorestation.dme --suppress-unimplemented --define=CIBUILDING | bash tools/ci/annotate_od.sh + - name: Run Map Checks + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: | + tools/bootstrap/python -m mapmerge2.dmm_test + tools/bootstrap/python -m tools.maplint.source + - name: Check Cutter + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: tools/bootstrap/python -m tools.icon_cutter.check + - name: Run DMI Tests + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: tools/bootstrap/python -m dmi.test + #- name: Check File Directories + # if: steps.linter-setup.conclusion == 'success' && !cancelled() + # run: bash tools/ci/check_filedirs.sh tgstation.dme + - name: Check Changelogs + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: bash tools/ci/check_changelogs.sh + - name: Check Miscellaneous Files + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: bash tools/ci/check_misc.sh + - name: Run TGUI Checks + if: steps.linter-setup.conclusion == 'success' && !cancelled() + run: tools/build/build.sh --ci lint tgui-test + + + - name: Run Nanomap Checks + run: tools/github-actions/nanomap-renderer-invoker.sh --testing diff --git a/.gitignore b/.gitignore index dae7acc003..e2b98d86c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,19 @@ +###Files and folders specified here will never be tracked. + +#Ignore compiled files and other files generated during compilation. +*.dmb +*.rsc +*.m.dme +*.test.dme +*.lk +*.int +*.backup + #ignore misc BYOND files Thumbs.db vchat.db vchat.db* *.log -*.int -*.rsc -*.dmb -*.lk -*.backup *.before *.pyc *.pid diff --git a/archive/maps/gateway_archive_vr/wildwest.dm b/archive/maps/gateway_archive_vr/wildwest.dm index 41fed0335a..e7ceb49e0e 100644 --- a/archive/maps/gateway_archive_vr/wildwest.dm +++ b/archive/maps/gateway_archive_vr/wildwest.dm @@ -75,7 +75,7 @@ if("To Kill") to_chat(user, span_boldwarning("Your wish is granted, but at a terrible cost...")) to_chat(user, span_warning("The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart.")) - ticker.mode.traitors += user.mind + SSticker.mode.traitors += user.mind user.mind.special_role = "traitor" var/datum/objective/hijack/hijack = new hijack.owner = user.mind diff --git a/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm b/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm index e2ff8c0666..a2b6f4e3fd 100644 --- a/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm +++ b/code/ATMOSPHERICS/components/binary_devices/dp_vent_pump.dm @@ -169,10 +169,10 @@ //Radio remote control /obj/machinery/atmospherics/binary/dp_vent_pump/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) /obj/machinery/atmospherics/binary/dp_vent_pump/proc/broadcast_status() if(!radio_connection) diff --git a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm index d39fc1e3be..dcb0cd7739 100644 --- a/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm +++ b/code/ATMOSPHERICS/components/binary_devices/passive_gate.dm @@ -144,10 +144,10 @@ //Radio remote control /obj/machinery/atmospherics/binary/passive_gate/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) /obj/machinery/atmospherics/binary/passive_gate/proc/broadcast_status() if(!radio_connection) diff --git a/code/ATMOSPHERICS/components/binary_devices/pump.dm b/code/ATMOSPHERICS/components/binary_devices/pump.dm index 6dd6699e65..2888102ebb 100644 --- a/code/ATMOSPHERICS/components/binary_devices/pump.dm +++ b/code/ATMOSPHERICS/components/binary_devices/pump.dm @@ -121,10 +121,10 @@ Thus, the two variables affect pump operation are set in New(): //Radio remote control /obj/machinery/atmospherics/binary/pump/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) /obj/machinery/atmospherics/binary/pump/proc/broadcast_status() if(!radio_connection) diff --git a/code/ATMOSPHERICS/components/trinary_devices/filter.dm b/code/ATMOSPHERICS/components/trinary_devices/filter.dm index b055da6e85..f055d9d856 100755 --- a/code/ATMOSPHERICS/components/trinary_devices/filter.dm +++ b/code/ATMOSPHERICS/components/trinary_devices/filter.dm @@ -34,10 +34,10 @@ var/datum/radio_frequency/radio_connection /obj/machinery/atmospherics/trinary/atmos_filter/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) /obj/machinery/atmospherics/trinary/atmos_filter/Initialize(mapload) . = ..() diff --git a/code/ATMOSPHERICS/components/tvalve.dm b/code/ATMOSPHERICS/components/tvalve.dm index df44e35574..0dd7388966 100644 --- a/code/ATMOSPHERICS/components/tvalve.dm +++ b/code/ATMOSPHERICS/components/tvalve.dm @@ -295,10 +295,10 @@ //Radio remote control /obj/machinery/atmospherics/tvalve/digital/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) diff --git a/code/ATMOSPHERICS/components/unary/outlet_injector.dm b/code/ATMOSPHERICS/components/unary/outlet_injector.dm index fc422b88f6..d5c951e88d 100644 --- a/code/ATMOSPHERICS/components/unary/outlet_injector.dm +++ b/code/ATMOSPHERICS/components/unary/outlet_injector.dm @@ -100,10 +100,10 @@ flick("inject", src) /obj/machinery/atmospherics/unary/outlet_injector/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency) + radio_connection = SSradio.add_object(src, frequency) /obj/machinery/atmospherics/unary/outlet_injector/proc/broadcast_status() if(!radio_connection) diff --git a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm index 3a5de0d5ed..cda1bc1de9 100644 --- a/code/ATMOSPHERICS/components/unary/vent_scrubber.dm +++ b/code/ATMOSPHERICS/components/unary/vent_scrubber.dm @@ -90,9 +90,9 @@ add_underlay(T,, dir) /obj/machinery/atmospherics/unary/vent_scrubber/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, radio_filter_in) + radio_connection = SSradio.add_object(src, frequency, radio_filter_in) /obj/machinery/atmospherics/unary/vent_scrubber/proc/broadcast_status() if(!radio_connection) diff --git a/code/ATMOSPHERICS/components/valve.dm b/code/ATMOSPHERICS/components/valve.dm index 479ec37ecc..167dfbfe26 100644 --- a/code/ATMOSPHERICS/components/valve.dm +++ b/code/ATMOSPHERICS/components/valve.dm @@ -258,10 +258,10 @@ icon_state = "valve[open]nopower" /obj/machinery/atmospherics/valve/digital/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) /obj/machinery/atmospherics/valve/digital/Initialize(mapload) . = ..() diff --git a/code/ZAS/_docs.dm b/code/ZAS/README.md similarity index 94% rename from code/ZAS/_docs.dm rename to code/ZAS/README.md index 822c8ac7b2..a3452cc66d 100644 --- a/code/ZAS/_docs.dm +++ b/code/ZAS/README.md @@ -1,19 +1,17 @@ -/* - -Zone Air System: +# Zone Air System This air system divides the station into impermeable areas called zones. When something happens, i.e. a door opening or a wall being taken down, zones equalize and eventually merge. Making an airtight area closes the connection again. -Control Flow: +## Control Flow: Every air tick: Marked turfs are updated with update_air_properties(), followed by post_update_air_properties(). Edges, including those generated by connections in the previous step, are processed. This is where gas is exchanged. Fire is processed. Marked zones have their air archived. -Important Functions: +## Important Functions: SSair.mark_for_update(turf) When stuff happens, call this. It works on everything. You basically don't need to worry about any other @@ -24,5 +22,3 @@ Notes for people who used ZAS before: To get the zones that are connected to a zone, use this loop: for(var/connection_edge/zone/edge in zone.edges) var/zone/connected_zone = edge.get_connected_zone(zone) - -*/ diff --git a/code/__defines/MC.dm b/code/__defines/MC.dm index 542986c3e4..1767320780 100644 --- a/code/__defines/MC.dm +++ b/code/__defines/MC.dm @@ -1,5 +1,4 @@ #define MC_TICK_CHECK ( ( TICK_USAGE > Master.current_ticklimit || src.state != SS_RUNNING ) ? pause() : 0 ) -#define CURRENT_RUNLEVEL (2 ** (Master.current_runlevel - 1)) #define MC_SPLIT_TICK_INIT(phase_count) var/original_tick_limit = Master.current_ticklimit; var/split_tick_phases = ##phase_count #define MC_SPLIT_TICK \ @@ -30,7 +29,14 @@ #define NEW_SS_GLOBAL(varname) if(varname != src){if(istype(varname)){Recover();qdel(varname);}varname = src;} #define START_PROCESSING(Processor, Datum) if (!(Datum.datum_flags & DF_ISPROCESSING)) {Datum.datum_flags |= DF_ISPROCESSING;Processor.processing += Datum} -#define STOP_PROCESSING(Processor, Datum) Datum.datum_flags &= ~DF_ISPROCESSING;Processor.processing -= Datum +#define STOP_PROCESSING(Processor, Datum) Datum.datum_flags &= ~DF_ISPROCESSING;Processor.processing -= Datum;Processor.currentrun -= Datum + +/// Returns true if the MC is initialized and running. +/// Optional argument init_stage controls what stage the mc must have initialized to count as initialized. Defaults to INITSTAGE_MAX if not specified. +#define MC_RUNNING(INIT_STAGE...) (Master && Master.processing > 0 && Master.current_runlevel && Master.init_stage_completed >= (max(min(INITSTAGE_MAX, ##INIT_STAGE), 1))) + +#define MC_LOOP_RTN_NEWSTAGES 1 +#define MC_LOOP_RTN_GRACEFUL_EXIT 2 //! SubSystem flags (Please design any new flags so that the default is off, to make adding flags to subsystems easier) @@ -65,17 +71,26 @@ /// This flag overrides SS_KEEP_TIMING #define SS_POST_FIRE_TIMING 64 +/// If this subsystem doesn't initialize, it should not report as a hard error in CI. +/// This should be used for subsystems that are flaky for complicated reasons, such as +/// the Lua subsystem, which relies on auxtools, which is unstable. +/// It should not be used simply to silence CI. +#define SS_OK_TO_FAIL_INIT 128 + //! SUBSYSTEM STATES -#define SS_IDLE 0 /// aint doing shit. -#define SS_QUEUED 1 /// queued to run -#define SS_RUNNING 2 /// actively running -#define SS_PAUSED 3 /// paused by mc_tick_check -#define SS_SLEEPING 4 /// fire() slept. -#define SS_PAUSING 5 /// in the middle of pausing +#define SS_IDLE 0 /// ain't doing shit. +#define SS_QUEUED 1 /// queued to run +#define SS_RUNNING 2 /// actively running +#define SS_PAUSED 3 /// paused by mc_tick_check +#define SS_SLEEPING 4 /// fire() slept. +#define SS_PAUSING 5 /// in the middle of pausing + // Subsystem init stages -#define INITSTAGE_EARLY 1 //! Early init stuff that doesn't need to wait for mapload -#define INITSTAGE_MAIN 2 //! Main init stage -#define INITSTAGE_MAX 2 //! Highest initstage. +#define INITSTAGE_FIRST 1 +#define INITSTAGE_EARLY 2 //! Early init stuff that doesn't need to wait for mapload +#define INITSTAGE_MAIN 3 //! Main init stage +#define INITSTAGE_LAST 4 +#define INITSTAGE_MAX 4 //! Highest initstage. #define SUBSYSTEM_DEF(X) GLOBAL_REAL(SS##X, /datum/controller/subsystem/##X);\ /datum/controller/subsystem/##X/New(){\ @@ -108,6 +123,10 @@ /datum/controller/subsystem/verb_manager/##X/fire() {..() /*just so it shows up on the profiler*/} \ /datum/controller/subsystem/verb_manager/##X + + +/////// OLD DEFINES + // Boilerplate code for multi-step processors. See machines.dm for example use. #define INTERNAL_PROCESS_STEP(this_step, initial_step, proc_to_call, cost_var, next_step)\ if(current_step == this_step || (initial_step && !resumed)) /* So we start at step 1 if not resumed.*/ {\ @@ -120,3 +139,5 @@ if(current_step == this_step || (initial_step && !resumed)) /* So we start at st resumed = 0;\ current_step = next_step;\ } + +#define CURRENT_RUNLEVEL (2 ** (Master.current_runlevel - 1)) diff --git a/code/__defines/_compile_options.dm b/code/__defines/_compile_options.dm index 5cf3db3ed3..787cc35ecf 100644 --- a/code/__defines/_compile_options.dm +++ b/code/__defines/_compile_options.dm @@ -18,7 +18,11 @@ // CI will override these as appropriate // #define MAP_TEST 0 // #define AWAY_MISSION_TEST 0 -// #define UNIT_TEST 0 +// #define UNIT_TESTS 1 + +#if defined(CIBUILDING) && !defined(OPENDREAM) +#define UNIT_TESTS +#endif // Comment/Uncomment this to turn off/on shuttle code debugging logs #define DEBUG_SHUTTLES diff --git a/code/__defines/_planes+layers.dm b/code/__defines/_planes+layers.dm index 62487951d3..a201d8785b 100644 --- a/code/__defines/_planes+layers.dm +++ b/code/__defines/_planes+layers.dm @@ -206,6 +206,12 @@ What is the naming convention for planes or layers? #define PLANE_ADMIN3 99 //Purely for shenanigans (above HUD) +///Plane of the "splash" icon used that shows on the lobby screen +#define SPLASHSCREEN_PLANE 100 + +///cinematics are "below" the splash screen +#define CINEMATIC_LAYER -1 + ////////////////////////// /atom/proc/hud_layerise() diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm index ab036d07f1..c96cbb4767 100644 --- a/code/__defines/dcs/signals.dm +++ b/code/__defines/dcs/signals.dm @@ -929,6 +929,6 @@ #define COMSIG_HOSE_FORCEPUMP "hose_force_pump" //Unittest data update -#ifdef UNIT_TEST +#ifdef UNIT_TESTS #define COMSIG_UNITTEST_DATA "unittest_send_data" #endif diff --git a/code/__defines/dcs/signals/signals_subsystem.dm b/code/__defines/dcs/signals/signals_subsystem.dm index 26daf58e1a..749ba9f638 100644 --- a/code/__defines/dcs/signals/signals_subsystem.dm +++ b/code/__defines/dcs/signals/signals_subsystem.dm @@ -3,5 +3,26 @@ // All signals send the source datum of the signal as the first argument ///Subsystem signals -///From base of datum/controller/subsystem/Initialize +///From base of /datum/controller/master/init_subsystem() #define COMSIG_SUBSYSTEM_POST_INITIALIZE "subsystem_post_initialize" + +///Called when the ticker enters the pre-game phase +#define COMSIG_TICKER_ENTER_PREGAME "comsig_ticker_enter_pregame" + +///Called when the ticker sets up the game for start +#define COMSIG_TICKER_ENTER_SETTING_UP "comsig_ticker_enter_setting_up" + +///Called when the ticker fails to set up the game for start +#define COMSIG_TICKER_ERROR_SETTING_UP "comsig_ticker_error_setting_up" + +/// Called when the round has started, but before GAME_STATE_PLAYING. +#define COMSIG_TICKER_ROUND_STARTING "comsig_ticker_round_starting" + +// Point of interest signals +/// Sent from base of /datum/controller/subsystem/points_of_interest/proc/on_poi_element_added : (atom/new_poi) +#define COMSIG_ADDED_POINT_OF_INTEREST "added_point_of_interest" +/// Sent from base of /datum/controller/subsystem/points_of_interest/proc/on_poi_element_removed : (atom/old_poi) +#define COMSIG_REMOVED_POINT_OF_INTEREST "removed_point_of_interest" + +///Sent after awards are saved in the database (/datum/controller/subsystem/achievements/save_achievements_to_db) +#define COMSIG_ACHIEVEMENTS_SAVED_TO_DB "achievements_saved_to_db" diff --git a/code/__defines/font_awesome_icons.dm b/code/__defines/font_awesome_icons.dm new file mode 100644 index 0000000000..5982d23c45 --- /dev/null +++ b/code/__defines/font_awesome_icons.dm @@ -0,0 +1,1955 @@ +/* This file is automatically generated by the unit test. Do not edit it manually. + * Generating this file is done by running the unit test locally, see the fail message for more details. + * All valid font awesome icons should be here. + */ + +#define FA_ICON_0 "fa-0" +#define FA_ICON_1 "fa-1" +#define FA_ICON_2 "fa-2" +#define FA_ICON_3 "fa-3" +#define FA_ICON_4 "fa-4" +#define FA_ICON_5 "fa-5" +#define FA_ICON_6 "fa-6" +#define FA_ICON_7 "fa-7" +#define FA_ICON_8 "fa-8" +#define FA_ICON_9 "fa-9" +#define FA_ICON_A "fa-a" +#define FA_ICON_AD "fa-ad" +#define FA_ICON_ADD "fa-add" +#define FA_ICON_ADDRESS_BOOK "fa-address-book" +#define FA_ICON_ADDRESS_CARD "fa-address-card" +#define FA_ICON_ADJUST "fa-adjust" +#define FA_ICON_AIR_FRESHENER "fa-air-freshener" +#define FA_ICON_ALIGN_CENTER "fa-align-center" +#define FA_ICON_ALIGN_JUSTIFY "fa-align-justify" +#define FA_ICON_ALIGN_LEFT "fa-align-left" +#define FA_ICON_ALIGN_RIGHT "fa-align-right" +#define FA_ICON_ALLERGIES "fa-allergies" +#define FA_ICON_AMBULANCE "fa-ambulance" +#define FA_ICON_AMERICAN_SIGN_LANGUAGE_INTERPRETING "fa-american-sign-language-interpreting" +#define FA_ICON_ANCHOR "fa-anchor" +#define FA_ICON_ANCHOR_CIRCLE_CHECK "fa-anchor-circle-check" +#define FA_ICON_ANCHOR_CIRCLE_EXCLAMATION "fa-anchor-circle-exclamation" +#define FA_ICON_ANCHOR_CIRCLE_XMARK "fa-anchor-circle-xmark" +#define FA_ICON_ANCHOR_LOCK "fa-anchor-lock" +#define FA_ICON_ANGLE_DOUBLE_DOWN "fa-angle-double-down" +#define FA_ICON_ANGLE_DOUBLE_LEFT "fa-angle-double-left" +#define FA_ICON_ANGLE_DOUBLE_RIGHT "fa-angle-double-right" +#define FA_ICON_ANGLE_DOUBLE_UP "fa-angle-double-up" +#define FA_ICON_ANGLE_DOWN "fa-angle-down" +#define FA_ICON_ANGLE_LEFT "fa-angle-left" +#define FA_ICON_ANGLE_RIGHT "fa-angle-right" +#define FA_ICON_ANGLE_UP "fa-angle-up" +#define FA_ICON_ANGLES_DOWN "fa-angles-down" +#define FA_ICON_ANGLES_LEFT "fa-angles-left" +#define FA_ICON_ANGLES_RIGHT "fa-angles-right" +#define FA_ICON_ANGLES_UP "fa-angles-up" +#define FA_ICON_ANGRY "fa-angry" +#define FA_ICON_ANKH "fa-ankh" +#define FA_ICON_APPLE_ALT "fa-apple-alt" +#define FA_ICON_APPLE_WHOLE "fa-apple-whole" +#define FA_ICON_ARCHIVE "fa-archive" +#define FA_ICON_ARCHWAY "fa-archway" +#define FA_ICON_AREA_CHART "fa-area-chart" +#define FA_ICON_ARROW_ALT_CIRCLE_DOWN "fa-arrow-alt-circle-down" +#define FA_ICON_ARROW_ALT_CIRCLE_LEFT "fa-arrow-alt-circle-left" +#define FA_ICON_ARROW_ALT_CIRCLE_RIGHT "fa-arrow-alt-circle-right" +#define FA_ICON_ARROW_ALT_CIRCLE_UP "fa-arrow-alt-circle-up" +#define FA_ICON_ARROW_CIRCLE_DOWN "fa-arrow-circle-down" +#define FA_ICON_ARROW_CIRCLE_LEFT "fa-arrow-circle-left" +#define FA_ICON_ARROW_CIRCLE_RIGHT "fa-arrow-circle-right" +#define FA_ICON_ARROW_CIRCLE_UP "fa-arrow-circle-up" +#define FA_ICON_ARROW_DOWN "fa-arrow-down" +#define FA_ICON_ARROW_DOWN_1_9 "fa-arrow-down-1-9" +#define FA_ICON_ARROW_DOWN_9_1 "fa-arrow-down-9-1" +#define FA_ICON_ARROW_DOWN_A_Z "fa-arrow-down-a-z" +#define FA_ICON_ARROW_DOWN_LONG "fa-arrow-down-long" +#define FA_ICON_ARROW_DOWN_SHORT_WIDE "fa-arrow-down-short-wide" +#define FA_ICON_ARROW_DOWN_UP_ACROSS_LINE "fa-arrow-down-up-across-line" +#define FA_ICON_ARROW_DOWN_UP_LOCK "fa-arrow-down-up-lock" +#define FA_ICON_ARROW_DOWN_WIDE_SHORT "fa-arrow-down-wide-short" +#define FA_ICON_ARROW_DOWN_Z_A "fa-arrow-down-z-a" +#define FA_ICON_ARROW_LEFT "fa-arrow-left" +#define FA_ICON_ARROW_LEFT_LONG "fa-arrow-left-long" +#define FA_ICON_ARROW_LEFT_ROTATE "fa-arrow-left-rotate" +#define FA_ICON_ARROW_POINTER "fa-arrow-pointer" +#define FA_ICON_ARROW_RIGHT "fa-arrow-right" +#define FA_ICON_ARROW_RIGHT_ARROW_LEFT "fa-arrow-right-arrow-left" +#define FA_ICON_ARROW_RIGHT_FROM_BRACKET "fa-arrow-right-from-bracket" +#define FA_ICON_ARROW_RIGHT_FROM_FILE "fa-arrow-right-from-file" +#define FA_ICON_ARROW_RIGHT_LONG "fa-arrow-right-long" +#define FA_ICON_ARROW_RIGHT_ROTATE "fa-arrow-right-rotate" +#define FA_ICON_ARROW_RIGHT_TO_BRACKET "fa-arrow-right-to-bracket" +#define FA_ICON_ARROW_RIGHT_TO_CITY "fa-arrow-right-to-city" +#define FA_ICON_ARROW_RIGHT_TO_FILE "fa-arrow-right-to-file" +#define FA_ICON_ARROW_ROTATE_BACK "fa-arrow-rotate-back" +#define FA_ICON_ARROW_ROTATE_BACKWARD "fa-arrow-rotate-backward" +#define FA_ICON_ARROW_ROTATE_FORWARD "fa-arrow-rotate-forward" +#define FA_ICON_ARROW_ROTATE_LEFT "fa-arrow-rotate-left" +#define FA_ICON_ARROW_ROTATE_RIGHT "fa-arrow-rotate-right" +#define FA_ICON_ARROW_TREND_DOWN "fa-arrow-trend-down" +#define FA_ICON_ARROW_TREND_UP "fa-arrow-trend-up" +#define FA_ICON_ARROW_TURN_DOWN "fa-arrow-turn-down" +#define FA_ICON_ARROW_TURN_RIGHT "fa-arrow-turn-right" +#define FA_ICON_ARROW_TURN_UP "fa-arrow-turn-up" +#define FA_ICON_ARROW_UP "fa-arrow-up" +#define FA_ICON_ARROW_UP_1_9 "fa-arrow-up-1-9" +#define FA_ICON_ARROW_UP_9_1 "fa-arrow-up-9-1" +#define FA_ICON_ARROW_UP_A_Z "fa-arrow-up-a-z" +#define FA_ICON_ARROW_UP_FROM_BRACKET "fa-arrow-up-from-bracket" +#define FA_ICON_ARROW_UP_FROM_GROUND_WATER "fa-arrow-up-from-ground-water" +#define FA_ICON_ARROW_UP_FROM_WATER_PUMP "fa-arrow-up-from-water-pump" +#define FA_ICON_ARROW_UP_LONG "fa-arrow-up-long" +#define FA_ICON_ARROW_UP_RIGHT_DOTS "fa-arrow-up-right-dots" +#define FA_ICON_ARROW_UP_RIGHT_FROM_SQUARE "fa-arrow-up-right-from-square" +#define FA_ICON_ARROW_UP_SHORT_WIDE "fa-arrow-up-short-wide" +#define FA_ICON_ARROW_UP_WIDE_SHORT "fa-arrow-up-wide-short" +#define FA_ICON_ARROW_UP_Z_A "fa-arrow-up-z-a" +#define FA_ICON_ARROWS "fa-arrows" +#define FA_ICON_ARROWS_ALT "fa-arrows-alt" +#define FA_ICON_ARROWS_ALT_H "fa-arrows-alt-h" +#define FA_ICON_ARROWS_ALT_V "fa-arrows-alt-v" +#define FA_ICON_ARROWS_DOWN_TO_LINE "fa-arrows-down-to-line" +#define FA_ICON_ARROWS_DOWN_TO_PEOPLE "fa-arrows-down-to-people" +#define FA_ICON_ARROWS_H "fa-arrows-h" +#define FA_ICON_ARROWS_LEFT_RIGHT "fa-arrows-left-right" +#define FA_ICON_ARROWS_LEFT_RIGHT_TO_LINE "fa-arrows-left-right-to-line" +#define FA_ICON_ARROWS_ROTATE "fa-arrows-rotate" +#define FA_ICON_ARROWS_SPIN "fa-arrows-spin" +#define FA_ICON_ARROWS_SPLIT_UP_AND_LEFT "fa-arrows-split-up-and-left" +#define FA_ICON_ARROWS_TO_CIRCLE "fa-arrows-to-circle" +#define FA_ICON_ARROWS_TO_DOT "fa-arrows-to-dot" +#define FA_ICON_ARROWS_TO_EYE "fa-arrows-to-eye" +#define FA_ICON_ARROWS_TURN_RIGHT "fa-arrows-turn-right" +#define FA_ICON_ARROWS_TURN_TO_DOTS "fa-arrows-turn-to-dots" +#define FA_ICON_ARROWS_UP_DOWN "fa-arrows-up-down" +#define FA_ICON_ARROWS_UP_DOWN_LEFT_RIGHT "fa-arrows-up-down-left-right" +#define FA_ICON_ARROWS_UP_TO_LINE "fa-arrows-up-to-line" +#define FA_ICON_ARROWS_V "fa-arrows-v" +#define FA_ICON_ASL_INTERPRETING "fa-asl-interpreting" +#define FA_ICON_ASSISTIVE_LISTENING_SYSTEMS "fa-assistive-listening-systems" +#define FA_ICON_ASTERISK "fa-asterisk" +#define FA_ICON_AT "fa-at" +#define FA_ICON_ATLAS "fa-atlas" +#define FA_ICON_ATOM "fa-atom" +#define FA_ICON_AUDIO_DESCRIPTION "fa-audio-description" +#define FA_ICON_AUSTRAL_SIGN "fa-austral-sign" +#define FA_ICON_AUTOMOBILE "fa-automobile" +#define FA_ICON_AWARD "fa-award" +#define FA_ICON_B "fa-b" +#define FA_ICON_BABY "fa-baby" +#define FA_ICON_BABY_CARRIAGE "fa-baby-carriage" +#define FA_ICON_BACKSPACE "fa-backspace" +#define FA_ICON_BACKWARD "fa-backward" +#define FA_ICON_BACKWARD_FAST "fa-backward-fast" +#define FA_ICON_BACKWARD_STEP "fa-backward-step" +#define FA_ICON_BACON "fa-bacon" +#define FA_ICON_BACTERIA "fa-bacteria" +#define FA_ICON_BACTERIUM "fa-bacterium" +#define FA_ICON_BAG_SHOPPING "fa-bag-shopping" +#define FA_ICON_BAHAI "fa-bahai" +#define FA_ICON_BAHT_SIGN "fa-baht-sign" +#define FA_ICON_BALANCE_SCALE "fa-balance-scale" +#define FA_ICON_BALANCE_SCALE_LEFT "fa-balance-scale-left" +#define FA_ICON_BALANCE_SCALE_RIGHT "fa-balance-scale-right" +#define FA_ICON_BAN "fa-ban" +#define FA_ICON_BAN_SMOKING "fa-ban-smoking" +#define FA_ICON_BAND_AID "fa-band-aid" +#define FA_ICON_BANDAGE "fa-bandage" +#define FA_ICON_BANK "fa-bank" +#define FA_ICON_BAR_CHART "fa-bar-chart" +#define FA_ICON_BARCODE "fa-barcode" +#define FA_ICON_BARS "fa-bars" +#define FA_ICON_BARS_PROGRESS "fa-bars-progress" +#define FA_ICON_BARS_STAGGERED "fa-bars-staggered" +#define FA_ICON_BASEBALL "fa-baseball" +#define FA_ICON_BASEBALL_BALL "fa-baseball-ball" +#define FA_ICON_BASEBALL_BAT_BALL "fa-baseball-bat-ball" +#define FA_ICON_BASKET_SHOPPING "fa-basket-shopping" +#define FA_ICON_BASKETBALL "fa-basketball" +#define FA_ICON_BASKETBALL_BALL "fa-basketball-ball" +#define FA_ICON_BATH "fa-bath" +#define FA_ICON_BATHTUB "fa-bathtub" +#define FA_ICON_BATTERY "fa-battery" +#define FA_ICON_BATTERY_0 "fa-battery-0" +#define FA_ICON_BATTERY_2 "fa-battery-2" +#define FA_ICON_BATTERY_3 "fa-battery-3" +#define FA_ICON_BATTERY_4 "fa-battery-4" +#define FA_ICON_BATTERY_5 "fa-battery-5" +#define FA_ICON_BATTERY_CAR "fa-battery-car" +#define FA_ICON_BATTERY_EMPTY "fa-battery-empty" +#define FA_ICON_BATTERY_FULL "fa-battery-full" +#define FA_ICON_BATTERY_HALF "fa-battery-half" +#define FA_ICON_BATTERY_QUARTER "fa-battery-quarter" +#define FA_ICON_BATTERY_THREE_QUARTERS "fa-battery-three-quarters" +#define FA_ICON_BED "fa-bed" +#define FA_ICON_BED_PULSE "fa-bed-pulse" +#define FA_ICON_BEER "fa-beer" +#define FA_ICON_BEER_MUG_EMPTY "fa-beer-mug-empty" +#define FA_ICON_BELL "fa-bell" +#define FA_ICON_BELL_CONCIERGE "fa-bell-concierge" +#define FA_ICON_BELL_SLASH "fa-bell-slash" +#define FA_ICON_BEZIER_CURVE "fa-bezier-curve" +#define FA_ICON_BIBLE "fa-bible" +#define FA_ICON_BICYCLE "fa-bicycle" +#define FA_ICON_BIKING "fa-biking" +#define FA_ICON_BINOCULARS "fa-binoculars" +#define FA_ICON_BIOHAZARD "fa-biohazard" +#define FA_ICON_BIRTHDAY_CAKE "fa-birthday-cake" +#define FA_ICON_BITCOIN_SIGN "fa-bitcoin-sign" +#define FA_ICON_BLACKBOARD "fa-blackboard" +#define FA_ICON_BLENDER "fa-blender" +#define FA_ICON_BLENDER_PHONE "fa-blender-phone" +#define FA_ICON_BLIND "fa-blind" +#define FA_ICON_BLOG "fa-blog" +#define FA_ICON_BOLD "fa-bold" +#define FA_ICON_BOLT "fa-bolt" +#define FA_ICON_BOLT_LIGHTNING "fa-bolt-lightning" +#define FA_ICON_BOMB "fa-bomb" +#define FA_ICON_BONE "fa-bone" +#define FA_ICON_BONG "fa-bong" +#define FA_ICON_BOOK "fa-book" +#define FA_ICON_BOOK_ATLAS "fa-book-atlas" +#define FA_ICON_BOOK_BIBLE "fa-book-bible" +#define FA_ICON_BOOK_BOOKMARK "fa-book-bookmark" +#define FA_ICON_BOOK_DEAD "fa-book-dead" +#define FA_ICON_BOOK_JOURNAL_WHILLS "fa-book-journal-whills" +#define FA_ICON_BOOK_MEDICAL "fa-book-medical" +#define FA_ICON_BOOK_OPEN "fa-book-open" +#define FA_ICON_BOOK_OPEN_READER "fa-book-open-reader" +#define FA_ICON_BOOK_QURAN "fa-book-quran" +#define FA_ICON_BOOK_READER "fa-book-reader" +#define FA_ICON_BOOK_SKULL "fa-book-skull" +#define FA_ICON_BOOK_TANAKH "fa-book-tanakh" +#define FA_ICON_BOOKMARK "fa-bookmark" +#define FA_ICON_BORDER_ALL "fa-border-all" +#define FA_ICON_BORDER_NONE "fa-border-none" +#define FA_ICON_BORDER_STYLE "fa-border-style" +#define FA_ICON_BORDER_TOP_LEFT "fa-border-top-left" +#define FA_ICON_BORE_HOLE "fa-bore-hole" +#define FA_ICON_BOTTLE_DROPLET "fa-bottle-droplet" +#define FA_ICON_BOTTLE_WATER "fa-bottle-water" +#define FA_ICON_BOWL_FOOD "fa-bowl-food" +#define FA_ICON_BOWL_RICE "fa-bowl-rice" +#define FA_ICON_BOWLING_BALL "fa-bowling-ball" +#define FA_ICON_BOX "fa-box" +#define FA_ICON_BOX_ARCHIVE "fa-box-archive" +#define FA_ICON_BOX_OPEN "fa-box-open" +#define FA_ICON_BOX_TISSUE "fa-box-tissue" +#define FA_ICON_BOXES "fa-boxes" +#define FA_ICON_BOXES_ALT "fa-boxes-alt" +#define FA_ICON_BOXES_PACKING "fa-boxes-packing" +#define FA_ICON_BOXES_STACKED "fa-boxes-stacked" +#define FA_ICON_BRAILLE "fa-braille" +#define FA_ICON_BRAIN "fa-brain" +#define FA_ICON_BRAZILIAN_REAL_SIGN "fa-brazilian-real-sign" +#define FA_ICON_BREAD_SLICE "fa-bread-slice" +#define FA_ICON_BRIDGE "fa-bridge" +#define FA_ICON_BRIDGE_CIRCLE_CHECK "fa-bridge-circle-check" +#define FA_ICON_BRIDGE_CIRCLE_EXCLAMATION "fa-bridge-circle-exclamation" +#define FA_ICON_BRIDGE_CIRCLE_XMARK "fa-bridge-circle-xmark" +#define FA_ICON_BRIDGE_LOCK "fa-bridge-lock" +#define FA_ICON_BRIDGE_WATER "fa-bridge-water" +#define FA_ICON_BRIEFCASE "fa-briefcase" +#define FA_ICON_BRIEFCASE_CLOCK "fa-briefcase-clock" +#define FA_ICON_BRIEFCASE_MEDICAL "fa-briefcase-medical" +#define FA_ICON_BROADCAST_TOWER "fa-broadcast-tower" +#define FA_ICON_BROOM "fa-broom" +#define FA_ICON_BROOM_BALL "fa-broom-ball" +#define FA_ICON_BRUSH "fa-brush" +#define FA_ICON_BUCKET "fa-bucket" +#define FA_ICON_BUG "fa-bug" +#define FA_ICON_BUG_SLASH "fa-bug-slash" +#define FA_ICON_BUGS "fa-bugs" +#define FA_ICON_BUILDING "fa-building" +#define FA_ICON_BUILDING_CIRCLE_ARROW_RIGHT "fa-building-circle-arrow-right" +#define FA_ICON_BUILDING_CIRCLE_CHECK "fa-building-circle-check" +#define FA_ICON_BUILDING_CIRCLE_EXCLAMATION "fa-building-circle-exclamation" +#define FA_ICON_BUILDING_CIRCLE_XMARK "fa-building-circle-xmark" +#define FA_ICON_BUILDING_COLUMNS "fa-building-columns" +#define FA_ICON_BUILDING_FLAG "fa-building-flag" +#define FA_ICON_BUILDING_LOCK "fa-building-lock" +#define FA_ICON_BUILDING_NGO "fa-building-ngo" +#define FA_ICON_BUILDING_SHIELD "fa-building-shield" +#define FA_ICON_BUILDING_UN "fa-building-un" +#define FA_ICON_BUILDING_USER "fa-building-user" +#define FA_ICON_BUILDING_WHEAT "fa-building-wheat" +#define FA_ICON_BULLHORN "fa-bullhorn" +#define FA_ICON_BULLSEYE "fa-bullseye" +#define FA_ICON_BURGER "fa-burger" +#define FA_ICON_BURN "fa-burn" +#define FA_ICON_BURST "fa-burst" +#define FA_ICON_BUS "fa-bus" +#define FA_ICON_BUS_ALT "fa-bus-alt" +#define FA_ICON_BUS_SIMPLE "fa-bus-simple" +#define FA_ICON_BUSINESS_TIME "fa-business-time" +#define FA_ICON_C "fa-c" +#define FA_ICON_CAB "fa-cab" +#define FA_ICON_CABLE_CAR "fa-cable-car" +#define FA_ICON_CAKE "fa-cake" +#define FA_ICON_CAKE_CANDLES "fa-cake-candles" +#define FA_ICON_CALCULATOR "fa-calculator" +#define FA_ICON_CALENDAR "fa-calendar" +#define FA_ICON_CALENDAR_ALT "fa-calendar-alt" +#define FA_ICON_CALENDAR_CHECK "fa-calendar-check" +#define FA_ICON_CALENDAR_DAY "fa-calendar-day" +#define FA_ICON_CALENDAR_DAYS "fa-calendar-days" +#define FA_ICON_CALENDAR_MINUS "fa-calendar-minus" +#define FA_ICON_CALENDAR_PLUS "fa-calendar-plus" +#define FA_ICON_CALENDAR_TIMES "fa-calendar-times" +#define FA_ICON_CALENDAR_WEEK "fa-calendar-week" +#define FA_ICON_CALENDAR_XMARK "fa-calendar-xmark" +#define FA_ICON_CAMERA "fa-camera" +#define FA_ICON_CAMERA_ALT "fa-camera-alt" +#define FA_ICON_CAMERA_RETRO "fa-camera-retro" +#define FA_ICON_CAMERA_ROTATE "fa-camera-rotate" +#define FA_ICON_CAMPGROUND "fa-campground" +#define FA_ICON_CANCEL "fa-cancel" +#define FA_ICON_CANDY_CANE "fa-candy-cane" +#define FA_ICON_CANNABIS "fa-cannabis" +#define FA_ICON_CAPSULES "fa-capsules" +#define FA_ICON_CAR "fa-car" +#define FA_ICON_CAR_ALT "fa-car-alt" +#define FA_ICON_CAR_BATTERY "fa-car-battery" +#define FA_ICON_CAR_BURST "fa-car-burst" +#define FA_ICON_CAR_CRASH "fa-car-crash" +#define FA_ICON_CAR_ON "fa-car-on" +#define FA_ICON_CAR_REAR "fa-car-rear" +#define FA_ICON_CAR_SIDE "fa-car-side" +#define FA_ICON_CAR_TUNNEL "fa-car-tunnel" +#define FA_ICON_CARAVAN "fa-caravan" +#define FA_ICON_CARET_DOWN "fa-caret-down" +#define FA_ICON_CARET_LEFT "fa-caret-left" +#define FA_ICON_CARET_RIGHT "fa-caret-right" +#define FA_ICON_CARET_SQUARE_DOWN "fa-caret-square-down" +#define FA_ICON_CARET_SQUARE_LEFT "fa-caret-square-left" +#define FA_ICON_CARET_SQUARE_RIGHT "fa-caret-square-right" +#define FA_ICON_CARET_SQUARE_UP "fa-caret-square-up" +#define FA_ICON_CARET_UP "fa-caret-up" +#define FA_ICON_CARRIAGE_BABY "fa-carriage-baby" +#define FA_ICON_CARROT "fa-carrot" +#define FA_ICON_CART_ARROW_DOWN "fa-cart-arrow-down" +#define FA_ICON_CART_FLATBED "fa-cart-flatbed" +#define FA_ICON_CART_FLATBED_SUITCASE "fa-cart-flatbed-suitcase" +#define FA_ICON_CART_PLUS "fa-cart-plus" +#define FA_ICON_CART_SHOPPING "fa-cart-shopping" +#define FA_ICON_CASH_REGISTER "fa-cash-register" +#define FA_ICON_CAT "fa-cat" +#define FA_ICON_CEDI_SIGN "fa-cedi-sign" +#define FA_ICON_CENT_SIGN "fa-cent-sign" +#define FA_ICON_CERTIFICATE "fa-certificate" +#define FA_ICON_CHAIN "fa-chain" +#define FA_ICON_CHAIN_BROKEN "fa-chain-broken" +#define FA_ICON_CHAIN_SLASH "fa-chain-slash" +#define FA_ICON_CHAIR "fa-chair" +#define FA_ICON_CHALKBOARD "fa-chalkboard" +#define FA_ICON_CHALKBOARD_TEACHER "fa-chalkboard-teacher" +#define FA_ICON_CHALKBOARD_USER "fa-chalkboard-user" +#define FA_ICON_CHAMPAGNE_GLASSES "fa-champagne-glasses" +#define FA_ICON_CHARGING_STATION "fa-charging-station" +#define FA_ICON_CHART_AREA "fa-chart-area" +#define FA_ICON_CHART_BAR "fa-chart-bar" +#define FA_ICON_CHART_COLUMN "fa-chart-column" +#define FA_ICON_CHART_GANTT "fa-chart-gantt" +#define FA_ICON_CHART_LINE "fa-chart-line" +#define FA_ICON_CHART_PIE "fa-chart-pie" +#define FA_ICON_CHART_SIMPLE "fa-chart-simple" +#define FA_ICON_CHECK "fa-check" +#define FA_ICON_CHECK_CIRCLE "fa-check-circle" +#define FA_ICON_CHECK_DOUBLE "fa-check-double" +#define FA_ICON_CHECK_SQUARE "fa-check-square" +#define FA_ICON_CHECK_TO_SLOT "fa-check-to-slot" +#define FA_ICON_CHEESE "fa-cheese" +#define FA_ICON_CHESS "fa-chess" +#define FA_ICON_CHESS_BISHOP "fa-chess-bishop" +#define FA_ICON_CHESS_BOARD "fa-chess-board" +#define FA_ICON_CHESS_KING "fa-chess-king" +#define FA_ICON_CHESS_KNIGHT "fa-chess-knight" +#define FA_ICON_CHESS_PAWN "fa-chess-pawn" +#define FA_ICON_CHESS_QUEEN "fa-chess-queen" +#define FA_ICON_CHESS_ROOK "fa-chess-rook" +#define FA_ICON_CHEVRON_CIRCLE_DOWN "fa-chevron-circle-down" +#define FA_ICON_CHEVRON_CIRCLE_LEFT "fa-chevron-circle-left" +#define FA_ICON_CHEVRON_CIRCLE_RIGHT "fa-chevron-circle-right" +#define FA_ICON_CHEVRON_CIRCLE_UP "fa-chevron-circle-up" +#define FA_ICON_CHEVRON_DOWN "fa-chevron-down" +#define FA_ICON_CHEVRON_LEFT "fa-chevron-left" +#define FA_ICON_CHEVRON_RIGHT "fa-chevron-right" +#define FA_ICON_CHEVRON_UP "fa-chevron-up" +#define FA_ICON_CHILD "fa-child" +#define FA_ICON_CHILD_DRESS "fa-child-dress" +#define FA_ICON_CHILD_REACHING "fa-child-reaching" +#define FA_ICON_CHILD_RIFLE "fa-child-rifle" +#define FA_ICON_CHILDREN "fa-children" +#define FA_ICON_CHURCH "fa-church" +#define FA_ICON_CIRCLE "fa-circle" +#define FA_ICON_CIRCLE_ARROW_DOWN "fa-circle-arrow-down" +#define FA_ICON_CIRCLE_ARROW_LEFT "fa-circle-arrow-left" +#define FA_ICON_CIRCLE_ARROW_RIGHT "fa-circle-arrow-right" +#define FA_ICON_CIRCLE_ARROW_UP "fa-circle-arrow-up" +#define FA_ICON_CIRCLE_CHECK "fa-circle-check" +#define FA_ICON_CIRCLE_CHEVRON_DOWN "fa-circle-chevron-down" +#define FA_ICON_CIRCLE_CHEVRON_LEFT "fa-circle-chevron-left" +#define FA_ICON_CIRCLE_CHEVRON_RIGHT "fa-circle-chevron-right" +#define FA_ICON_CIRCLE_CHEVRON_UP "fa-circle-chevron-up" +#define FA_ICON_CIRCLE_DOLLAR_TO_SLOT "fa-circle-dollar-to-slot" +#define FA_ICON_CIRCLE_DOT "fa-circle-dot" +#define FA_ICON_CIRCLE_DOWN "fa-circle-down" +#define FA_ICON_CIRCLE_EXCLAMATION "fa-circle-exclamation" +#define FA_ICON_CIRCLE_H "fa-circle-h" +#define FA_ICON_CIRCLE_HALF_STROKE "fa-circle-half-stroke" +#define FA_ICON_CIRCLE_INFO "fa-circle-info" +#define FA_ICON_CIRCLE_LEFT "fa-circle-left" +#define FA_ICON_CIRCLE_MINUS "fa-circle-minus" +#define FA_ICON_CIRCLE_NODES "fa-circle-nodes" +#define FA_ICON_CIRCLE_NOTCH "fa-circle-notch" +#define FA_ICON_CIRCLE_PAUSE "fa-circle-pause" +#define FA_ICON_CIRCLE_PLAY "fa-circle-play" +#define FA_ICON_CIRCLE_PLUS "fa-circle-plus" +#define FA_ICON_CIRCLE_QUESTION "fa-circle-question" +#define FA_ICON_CIRCLE_RADIATION "fa-circle-radiation" +#define FA_ICON_CIRCLE_RIGHT "fa-circle-right" +#define FA_ICON_CIRCLE_STOP "fa-circle-stop" +#define FA_ICON_CIRCLE_UP "fa-circle-up" +#define FA_ICON_CIRCLE_USER "fa-circle-user" +#define FA_ICON_CIRCLE_XMARK "fa-circle-xmark" +#define FA_ICON_CITY "fa-city" +#define FA_ICON_CLAPPERBOARD "fa-clapperboard" +#define FA_ICON_CLINIC_MEDICAL "fa-clinic-medical" +#define FA_ICON_CLIPBOARD "fa-clipboard" +#define FA_ICON_CLIPBOARD_CHECK "fa-clipboard-check" +#define FA_ICON_CLIPBOARD_LIST "fa-clipboard-list" +#define FA_ICON_CLIPBOARD_QUESTION "fa-clipboard-question" +#define FA_ICON_CLIPBOARD_USER "fa-clipboard-user" +#define FA_ICON_CLOCK "fa-clock" +#define FA_ICON_CLOCK_FOUR "fa-clock-four" +#define FA_ICON_CLOCK_ROTATE_LEFT "fa-clock-rotate-left" +#define FA_ICON_CLONE "fa-clone" +#define FA_ICON_CLOSE "fa-close" +#define FA_ICON_CLOSED_CAPTIONING "fa-closed-captioning" +#define FA_ICON_CLOUD "fa-cloud" +#define FA_ICON_CLOUD_ARROW_DOWN "fa-cloud-arrow-down" +#define FA_ICON_CLOUD_ARROW_UP "fa-cloud-arrow-up" +#define FA_ICON_CLOUD_BOLT "fa-cloud-bolt" +#define FA_ICON_CLOUD_DOWNLOAD "fa-cloud-download" +#define FA_ICON_CLOUD_DOWNLOAD_ALT "fa-cloud-download-alt" +#define FA_ICON_CLOUD_MEATBALL "fa-cloud-meatball" +#define FA_ICON_CLOUD_MOON "fa-cloud-moon" +#define FA_ICON_CLOUD_MOON_RAIN "fa-cloud-moon-rain" +#define FA_ICON_CLOUD_RAIN "fa-cloud-rain" +#define FA_ICON_CLOUD_SHOWERS_HEAVY "fa-cloud-showers-heavy" +#define FA_ICON_CLOUD_SHOWERS_WATER "fa-cloud-showers-water" +#define FA_ICON_CLOUD_SUN "fa-cloud-sun" +#define FA_ICON_CLOUD_SUN_RAIN "fa-cloud-sun-rain" +#define FA_ICON_CLOUD_UPLOAD "fa-cloud-upload" +#define FA_ICON_CLOUD_UPLOAD_ALT "fa-cloud-upload-alt" +#define FA_ICON_CLOVER "fa-clover" +#define FA_ICON_CNY "fa-cny" +#define FA_ICON_COCKTAIL "fa-cocktail" +#define FA_ICON_CODE "fa-code" +#define FA_ICON_CODE_BRANCH "fa-code-branch" +#define FA_ICON_CODE_COMMIT "fa-code-commit" +#define FA_ICON_CODE_COMPARE "fa-code-compare" +#define FA_ICON_CODE_FORK "fa-code-fork" +#define FA_ICON_CODE_MERGE "fa-code-merge" +#define FA_ICON_CODE_PULL_REQUEST "fa-code-pull-request" +#define FA_ICON_COFFEE "fa-coffee" +#define FA_ICON_COG "fa-cog" +#define FA_ICON_COGS "fa-cogs" +#define FA_ICON_COINS "fa-coins" +#define FA_ICON_COLON_SIGN "fa-colon-sign" +#define FA_ICON_COLUMNS "fa-columns" +#define FA_ICON_COMMENT "fa-comment" +#define FA_ICON_COMMENT_ALT "fa-comment-alt" +#define FA_ICON_COMMENT_DOLLAR "fa-comment-dollar" +#define FA_ICON_COMMENT_DOTS "fa-comment-dots" +#define FA_ICON_COMMENT_MEDICAL "fa-comment-medical" +#define FA_ICON_COMMENT_SLASH "fa-comment-slash" +#define FA_ICON_COMMENT_SMS "fa-comment-sms" +#define FA_ICON_COMMENTING "fa-commenting" +#define FA_ICON_COMMENTS "fa-comments" +#define FA_ICON_COMMENTS_DOLLAR "fa-comments-dollar" +#define FA_ICON_COMPACT_DISC "fa-compact-disc" +#define FA_ICON_COMPASS "fa-compass" +#define FA_ICON_COMPASS_DRAFTING "fa-compass-drafting" +#define FA_ICON_COMPRESS "fa-compress" +#define FA_ICON_COMPRESS_ALT "fa-compress-alt" +#define FA_ICON_COMPRESS_ARROWS_ALT "fa-compress-arrows-alt" +#define FA_ICON_COMPUTER "fa-computer" +#define FA_ICON_COMPUTER_MOUSE "fa-computer-mouse" +#define FA_ICON_CONCIERGE_BELL "fa-concierge-bell" +#define FA_ICON_CONTACT_BOOK "fa-contact-book" +#define FA_ICON_CONTACT_CARD "fa-contact-card" +#define FA_ICON_COOKIE "fa-cookie" +#define FA_ICON_COOKIE_BITE "fa-cookie-bite" +#define FA_ICON_COPY "fa-copy" +#define FA_ICON_COPYRIGHT "fa-copyright" +#define FA_ICON_COUCH "fa-couch" +#define FA_ICON_COW "fa-cow" +#define FA_ICON_CREDIT_CARD "fa-credit-card" +#define FA_ICON_CREDIT_CARD_ALT "fa-credit-card-alt" +#define FA_ICON_CROP "fa-crop" +#define FA_ICON_CROP_ALT "fa-crop-alt" +#define FA_ICON_CROP_SIMPLE "fa-crop-simple" +#define FA_ICON_CROSS "fa-cross" +#define FA_ICON_CROSSHAIRS "fa-crosshairs" +#define FA_ICON_CROW "fa-crow" +#define FA_ICON_CROWN "fa-crown" +#define FA_ICON_CRUTCH "fa-crutch" +#define FA_ICON_CRUZEIRO_SIGN "fa-cruzeiro-sign" +#define FA_ICON_CUBE "fa-cube" +#define FA_ICON_CUBES "fa-cubes" +#define FA_ICON_CUBES_STACKED "fa-cubes-stacked" +#define FA_ICON_CUT "fa-cut" +#define FA_ICON_CUTLERY "fa-cutlery" +#define FA_ICON_D "fa-d" +#define FA_ICON_DASHBOARD "fa-dashboard" +#define FA_ICON_DATABASE "fa-database" +#define FA_ICON_DEAF "fa-deaf" +#define FA_ICON_DEAFNESS "fa-deafness" +#define FA_ICON_DEDENT "fa-dedent" +#define FA_ICON_DELETE_LEFT "fa-delete-left" +#define FA_ICON_DEMOCRAT "fa-democrat" +#define FA_ICON_DESKTOP "fa-desktop" +#define FA_ICON_DESKTOP_ALT "fa-desktop-alt" +#define FA_ICON_DHARMACHAKRA "fa-dharmachakra" +#define FA_ICON_DIAGNOSES "fa-diagnoses" +#define FA_ICON_DIAGRAM_NEXT "fa-diagram-next" +#define FA_ICON_DIAGRAM_PREDECESSOR "fa-diagram-predecessor" +#define FA_ICON_DIAGRAM_PROJECT "fa-diagram-project" +#define FA_ICON_DIAGRAM_SUCCESSOR "fa-diagram-successor" +#define FA_ICON_DIAMOND "fa-diamond" +#define FA_ICON_DIAMOND_TURN_RIGHT "fa-diamond-turn-right" +#define FA_ICON_DICE "fa-dice" +#define FA_ICON_DICE_D20 "fa-dice-d20" +#define FA_ICON_DICE_D6 "fa-dice-d6" +#define FA_ICON_DICE_FIVE "fa-dice-five" +#define FA_ICON_DICE_FOUR "fa-dice-four" +#define FA_ICON_DICE_ONE "fa-dice-one" +#define FA_ICON_DICE_SIX "fa-dice-six" +#define FA_ICON_DICE_THREE "fa-dice-three" +#define FA_ICON_DICE_TWO "fa-dice-two" +#define FA_ICON_DIGGING "fa-digging" +#define FA_ICON_DIGITAL_TACHOGRAPH "fa-digital-tachograph" +#define FA_ICON_DIRECTIONS "fa-directions" +#define FA_ICON_DISEASE "fa-disease" +#define FA_ICON_DISPLAY "fa-display" +#define FA_ICON_DIVIDE "fa-divide" +#define FA_ICON_DIZZY "fa-dizzy" +#define FA_ICON_DNA "fa-dna" +#define FA_ICON_DOG "fa-dog" +#define FA_ICON_DOLLAR "fa-dollar" +#define FA_ICON_DOLLAR_SIGN "fa-dollar-sign" +#define FA_ICON_DOLLY "fa-dolly" +#define FA_ICON_DOLLY_BOX "fa-dolly-box" +#define FA_ICON_DOLLY_FLATBED "fa-dolly-flatbed" +#define FA_ICON_DONATE "fa-donate" +#define FA_ICON_DONG_SIGN "fa-dong-sign" +#define FA_ICON_DOOR_CLOSED "fa-door-closed" +#define FA_ICON_DOOR_OPEN "fa-door-open" +#define FA_ICON_DOT_CIRCLE "fa-dot-circle" +#define FA_ICON_DOVE "fa-dove" +#define FA_ICON_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER "fa-down-left-and-up-right-to-center" +#define FA_ICON_DOWN_LONG "fa-down-long" +#define FA_ICON_DOWNLOAD "fa-download" +#define FA_ICON_DRAFTING_COMPASS "fa-drafting-compass" +#define FA_ICON_DRAGON "fa-dragon" +#define FA_ICON_DRAW_POLYGON "fa-draw-polygon" +#define FA_ICON_DRIVERS_LICENSE "fa-drivers-license" +#define FA_ICON_DROPLET "fa-droplet" +#define FA_ICON_DROPLET_SLASH "fa-droplet-slash" +#define FA_ICON_DRUM "fa-drum" +#define FA_ICON_DRUM_STEELPAN "fa-drum-steelpan" +#define FA_ICON_DRUMSTICK_BITE "fa-drumstick-bite" +#define FA_ICON_DUMBBELL "fa-dumbbell" +#define FA_ICON_DUMPSTER "fa-dumpster" +#define FA_ICON_DUMPSTER_FIRE "fa-dumpster-fire" +#define FA_ICON_DUNGEON "fa-dungeon" +#define FA_ICON_E "fa-e" +#define FA_ICON_EAR_DEAF "fa-ear-deaf" +#define FA_ICON_EAR_LISTEN "fa-ear-listen" +#define FA_ICON_EARTH "fa-earth" +#define FA_ICON_EARTH_AFRICA "fa-earth-africa" +#define FA_ICON_EARTH_AMERICA "fa-earth-america" +#define FA_ICON_EARTH_AMERICAS "fa-earth-americas" +#define FA_ICON_EARTH_ASIA "fa-earth-asia" +#define FA_ICON_EARTH_EUROPE "fa-earth-europe" +#define FA_ICON_EARTH_OCEANIA "fa-earth-oceania" +#define FA_ICON_EDIT "fa-edit" +#define FA_ICON_EGG "fa-egg" +#define FA_ICON_EJECT "fa-eject" +#define FA_ICON_ELEVATOR "fa-elevator" +#define FA_ICON_ELLIPSIS "fa-ellipsis" +#define FA_ICON_ELLIPSIS_H "fa-ellipsis-h" +#define FA_ICON_ELLIPSIS_V "fa-ellipsis-v" +#define FA_ICON_ELLIPSIS_VERTICAL "fa-ellipsis-vertical" +#define FA_ICON_ENVELOPE "fa-envelope" +#define FA_ICON_ENVELOPE_CIRCLE_CHECK "fa-envelope-circle-check" +#define FA_ICON_ENVELOPE_OPEN "fa-envelope-open" +#define FA_ICON_ENVELOPE_OPEN_TEXT "fa-envelope-open-text" +#define FA_ICON_ENVELOPE_SQUARE "fa-envelope-square" +#define FA_ICON_ENVELOPES_BULK "fa-envelopes-bulk" +#define FA_ICON_EQUALS "fa-equals" +#define FA_ICON_ERASER "fa-eraser" +#define FA_ICON_ETHERNET "fa-ethernet" +#define FA_ICON_EUR "fa-eur" +#define FA_ICON_EURO "fa-euro" +#define FA_ICON_EURO_SIGN "fa-euro-sign" +#define FA_ICON_EXCHANGE "fa-exchange" +#define FA_ICON_EXCHANGE_ALT "fa-exchange-alt" +#define FA_ICON_EXCLAMATION "fa-exclamation" +#define FA_ICON_EXCLAMATION_CIRCLE "fa-exclamation-circle" +#define FA_ICON_EXCLAMATION_TRIANGLE "fa-exclamation-triangle" +#define FA_ICON_EXPAND "fa-expand" +#define FA_ICON_EXPAND_ALT "fa-expand-alt" +#define FA_ICON_EXPAND_ARROWS_ALT "fa-expand-arrows-alt" +#define FA_ICON_EXPLOSION "fa-explosion" +#define FA_ICON_EXTERNAL_LINK "fa-external-link" +#define FA_ICON_EXTERNAL_LINK_ALT "fa-external-link-alt" +#define FA_ICON_EXTERNAL_LINK_SQUARE "fa-external-link-square" +#define FA_ICON_EXTERNAL_LINK_SQUARE_ALT "fa-external-link-square-alt" +#define FA_ICON_EYE "fa-eye" +#define FA_ICON_EYE_DROPPER "fa-eye-dropper" +#define FA_ICON_EYE_DROPPER_EMPTY "fa-eye-dropper-empty" +#define FA_ICON_EYE_LOW_VISION "fa-eye-low-vision" +#define FA_ICON_EYE_SLASH "fa-eye-slash" +#define FA_ICON_EYEDROPPER "fa-eyedropper" +#define FA_ICON_F "fa-f" +#define FA_ICON_FACE_ANGRY "fa-face-angry" +#define FA_ICON_FACE_DIZZY "fa-face-dizzy" +#define FA_ICON_FACE_FLUSHED "fa-face-flushed" +#define FA_ICON_FACE_FROWN "fa-face-frown" +#define FA_ICON_FACE_FROWN_OPEN "fa-face-frown-open" +#define FA_ICON_FACE_GRIMACE "fa-face-grimace" +#define FA_ICON_FACE_GRIN "fa-face-grin" +#define FA_ICON_FACE_GRIN_BEAM "fa-face-grin-beam" +#define FA_ICON_FACE_GRIN_BEAM_SWEAT "fa-face-grin-beam-sweat" +#define FA_ICON_FACE_GRIN_HEARTS "fa-face-grin-hearts" +#define FA_ICON_FACE_GRIN_SQUINT "fa-face-grin-squint" +#define FA_ICON_FACE_GRIN_SQUINT_TEARS "fa-face-grin-squint-tears" +#define FA_ICON_FACE_GRIN_STARS "fa-face-grin-stars" +#define FA_ICON_FACE_GRIN_TEARS "fa-face-grin-tears" +#define FA_ICON_FACE_GRIN_TONGUE "fa-face-grin-tongue" +#define FA_ICON_FACE_GRIN_TONGUE_SQUINT "fa-face-grin-tongue-squint" +#define FA_ICON_FACE_GRIN_TONGUE_WINK "fa-face-grin-tongue-wink" +#define FA_ICON_FACE_GRIN_WIDE "fa-face-grin-wide" +#define FA_ICON_FACE_GRIN_WINK "fa-face-grin-wink" +#define FA_ICON_FACE_KISS "fa-face-kiss" +#define FA_ICON_FACE_KISS_BEAM "fa-face-kiss-beam" +#define FA_ICON_FACE_KISS_WINK_HEART "fa-face-kiss-wink-heart" +#define FA_ICON_FACE_LAUGH "fa-face-laugh" +#define FA_ICON_FACE_LAUGH_BEAM "fa-face-laugh-beam" +#define FA_ICON_FACE_LAUGH_SQUINT "fa-face-laugh-squint" +#define FA_ICON_FACE_LAUGH_WINK "fa-face-laugh-wink" +#define FA_ICON_FACE_MEH "fa-face-meh" +#define FA_ICON_FACE_MEH_BLANK "fa-face-meh-blank" +#define FA_ICON_FACE_ROLLING_EYES "fa-face-rolling-eyes" +#define FA_ICON_FACE_SAD_CRY "fa-face-sad-cry" +#define FA_ICON_FACE_SAD_TEAR "fa-face-sad-tear" +#define FA_ICON_FACE_SMILE "fa-face-smile" +#define FA_ICON_FACE_SMILE_BEAM "fa-face-smile-beam" +#define FA_ICON_FACE_SMILE_WINK "fa-face-smile-wink" +#define FA_ICON_FACE_SURPRISE "fa-face-surprise" +#define FA_ICON_FACE_TIRED "fa-face-tired" +#define FA_ICON_FAN "fa-fan" +#define FA_ICON_FAST_BACKWARD "fa-fast-backward" +#define FA_ICON_FAST_FORWARD "fa-fast-forward" +#define FA_ICON_FAUCET "fa-faucet" +#define FA_ICON_FAUCET_DRIP "fa-faucet-drip" +#define FA_ICON_FAX "fa-fax" +#define FA_ICON_FEATHER "fa-feather" +#define FA_ICON_FEATHER_ALT "fa-feather-alt" +#define FA_ICON_FEATHER_POINTED "fa-feather-pointed" +#define FA_ICON_FEED "fa-feed" +#define FA_ICON_FEMALE "fa-female" +#define FA_ICON_FERRY "fa-ferry" +#define FA_ICON_FIGHTER_JET "fa-fighter-jet" +#define FA_ICON_FILE "fa-file" +#define FA_ICON_FILE_ALT "fa-file-alt" +#define FA_ICON_FILE_ARCHIVE "fa-file-archive" +#define FA_ICON_FILE_ARROW_DOWN "fa-file-arrow-down" +#define FA_ICON_FILE_ARROW_UP "fa-file-arrow-up" +#define FA_ICON_FILE_AUDIO "fa-file-audio" +#define FA_ICON_FILE_CIRCLE_CHECK "fa-file-circle-check" +#define FA_ICON_FILE_CIRCLE_EXCLAMATION "fa-file-circle-exclamation" +#define FA_ICON_FILE_CIRCLE_MINUS "fa-file-circle-minus" +#define FA_ICON_FILE_CIRCLE_PLUS "fa-file-circle-plus" +#define FA_ICON_FILE_CIRCLE_QUESTION "fa-file-circle-question" +#define FA_ICON_FILE_CIRCLE_XMARK "fa-file-circle-xmark" +#define FA_ICON_FILE_CLIPBOARD "fa-file-clipboard" +#define FA_ICON_FILE_CODE "fa-file-code" +#define FA_ICON_FILE_CONTRACT "fa-file-contract" +#define FA_ICON_FILE_CSV "fa-file-csv" +#define FA_ICON_FILE_DOWNLOAD "fa-file-download" +#define FA_ICON_FILE_EDIT "fa-file-edit" +#define FA_ICON_FILE_EXCEL "fa-file-excel" +#define FA_ICON_FILE_EXPORT "fa-file-export" +#define FA_ICON_FILE_IMAGE "fa-file-image" +#define FA_ICON_FILE_IMPORT "fa-file-import" +#define FA_ICON_FILE_INVOICE "fa-file-invoice" +#define FA_ICON_FILE_INVOICE_DOLLAR "fa-file-invoice-dollar" +#define FA_ICON_FILE_LINES "fa-file-lines" +#define FA_ICON_FILE_MEDICAL "fa-file-medical" +#define FA_ICON_FILE_MEDICAL_ALT "fa-file-medical-alt" +#define FA_ICON_FILE_PDF "fa-file-pdf" +#define FA_ICON_FILE_PEN "fa-file-pen" +#define FA_ICON_FILE_POWERPOINT "fa-file-powerpoint" +#define FA_ICON_FILE_PRESCRIPTION "fa-file-prescription" +#define FA_ICON_FILE_SHIELD "fa-file-shield" +#define FA_ICON_FILE_SIGNATURE "fa-file-signature" +#define FA_ICON_FILE_TEXT "fa-file-text" +#define FA_ICON_FILE_UPLOAD "fa-file-upload" +#define FA_ICON_FILE_VIDEO "fa-file-video" +#define FA_ICON_FILE_WAVEFORM "fa-file-waveform" +#define FA_ICON_FILE_WORD "fa-file-word" +#define FA_ICON_FILE_ZIPPER "fa-file-zipper" +#define FA_ICON_FILL "fa-fill" +#define FA_ICON_FILL_DRIP "fa-fill-drip" +#define FA_ICON_FILM "fa-film" +#define FA_ICON_FILTER "fa-filter" +#define FA_ICON_FILTER_CIRCLE_DOLLAR "fa-filter-circle-dollar" +#define FA_ICON_FILTER_CIRCLE_XMARK "fa-filter-circle-xmark" +#define FA_ICON_FINGERPRINT "fa-fingerprint" +#define FA_ICON_FIRE "fa-fire" +#define FA_ICON_FIRE_ALT "fa-fire-alt" +#define FA_ICON_FIRE_BURNER "fa-fire-burner" +#define FA_ICON_FIRE_EXTINGUISHER "fa-fire-extinguisher" +#define FA_ICON_FIRE_FLAME_CURVED "fa-fire-flame-curved" +#define FA_ICON_FIRE_FLAME_SIMPLE "fa-fire-flame-simple" +#define FA_ICON_FIRST_AID "fa-first-aid" +#define FA_ICON_FISH "fa-fish" +#define FA_ICON_FISH_FINS "fa-fish-fins" +#define FA_ICON_FIST_RAISED "fa-fist-raised" +#define FA_ICON_FLAG "fa-flag" +#define FA_ICON_FLAG_CHECKERED "fa-flag-checkered" +#define FA_ICON_FLAG_USA "fa-flag-usa" +#define FA_ICON_FLASK "fa-flask" +#define FA_ICON_FLASK_VIAL "fa-flask-vial" +#define FA_ICON_FLOPPY_DISK "fa-floppy-disk" +#define FA_ICON_FLORIN_SIGN "fa-florin-sign" +#define FA_ICON_FLUSHED "fa-flushed" +#define FA_ICON_FOLDER "fa-folder" +#define FA_ICON_FOLDER_BLANK "fa-folder-blank" +#define FA_ICON_FOLDER_CLOSED "fa-folder-closed" +#define FA_ICON_FOLDER_MINUS "fa-folder-minus" +#define FA_ICON_FOLDER_OPEN "fa-folder-open" +#define FA_ICON_FOLDER_PLUS "fa-folder-plus" +#define FA_ICON_FOLDER_TREE "fa-folder-tree" +#define FA_ICON_FONT "fa-font" +#define FA_ICON_FOOTBALL "fa-football" +#define FA_ICON_FOOTBALL_BALL "fa-football-ball" +#define FA_ICON_FORWARD "fa-forward" +#define FA_ICON_FORWARD_FAST "fa-forward-fast" +#define FA_ICON_FORWARD_STEP "fa-forward-step" +#define FA_ICON_FRANC_SIGN "fa-franc-sign" +#define FA_ICON_FROG "fa-frog" +#define FA_ICON_FROWN "fa-frown" +#define FA_ICON_FROWN_OPEN "fa-frown-open" +#define FA_ICON_FUNNEL_DOLLAR "fa-funnel-dollar" +#define FA_ICON_FUTBOL "fa-futbol" +#define FA_ICON_FUTBOL_BALL "fa-futbol-ball" +#define FA_ICON_G "fa-g" +#define FA_ICON_GAMEPAD "fa-gamepad" +#define FA_ICON_GAS_PUMP "fa-gas-pump" +#define FA_ICON_GAUGE "fa-gauge" +#define FA_ICON_GAUGE_HIGH "fa-gauge-high" +#define FA_ICON_GAUGE_MED "fa-gauge-med" +#define FA_ICON_GAUGE_SIMPLE "fa-gauge-simple" +#define FA_ICON_GAUGE_SIMPLE_HIGH "fa-gauge-simple-high" +#define FA_ICON_GAUGE_SIMPLE_MED "fa-gauge-simple-med" +#define FA_ICON_GAVEL "fa-gavel" +#define FA_ICON_GBP "fa-gbp" +#define FA_ICON_GEAR "fa-gear" +#define FA_ICON_GEARS "fa-gears" +#define FA_ICON_GEM "fa-gem" +#define FA_ICON_GENDERLESS "fa-genderless" +#define FA_ICON_GHOST "fa-ghost" +#define FA_ICON_GIFT "fa-gift" +#define FA_ICON_GIFTS "fa-gifts" +#define FA_ICON_GLASS_CHEERS "fa-glass-cheers" +#define FA_ICON_GLASS_MARTINI "fa-glass-martini" +#define FA_ICON_GLASS_MARTINI_ALT "fa-glass-martini-alt" +#define FA_ICON_GLASS_WATER "fa-glass-water" +#define FA_ICON_GLASS_WATER_DROPLET "fa-glass-water-droplet" +#define FA_ICON_GLASS_WHISKEY "fa-glass-whiskey" +#define FA_ICON_GLASSES "fa-glasses" +#define FA_ICON_GLOBE "fa-globe" +#define FA_ICON_GLOBE_AFRICA "fa-globe-africa" +#define FA_ICON_GLOBE_AMERICAS "fa-globe-americas" +#define FA_ICON_GLOBE_ASIA "fa-globe-asia" +#define FA_ICON_GLOBE_EUROPE "fa-globe-europe" +#define FA_ICON_GLOBE_OCEANIA "fa-globe-oceania" +#define FA_ICON_GOLF_BALL "fa-golf-ball" +#define FA_ICON_GOLF_BALL_TEE "fa-golf-ball-tee" +#define FA_ICON_GOPURAM "fa-gopuram" +#define FA_ICON_GRADUATION_CAP "fa-graduation-cap" +#define FA_ICON_GREATER_THAN "fa-greater-than" +#define FA_ICON_GREATER_THAN_EQUAL "fa-greater-than-equal" +#define FA_ICON_GRIMACE "fa-grimace" +#define FA_ICON_GRIN "fa-grin" +#define FA_ICON_GRIN_ALT "fa-grin-alt" +#define FA_ICON_GRIN_BEAM "fa-grin-beam" +#define FA_ICON_GRIN_BEAM_SWEAT "fa-grin-beam-sweat" +#define FA_ICON_GRIN_HEARTS "fa-grin-hearts" +#define FA_ICON_GRIN_SQUINT "fa-grin-squint" +#define FA_ICON_GRIN_SQUINT_TEARS "fa-grin-squint-tears" +#define FA_ICON_GRIN_STARS "fa-grin-stars" +#define FA_ICON_GRIN_TEARS "fa-grin-tears" +#define FA_ICON_GRIN_TONGUE "fa-grin-tongue" +#define FA_ICON_GRIN_TONGUE_SQUINT "fa-grin-tongue-squint" +#define FA_ICON_GRIN_TONGUE_WINK "fa-grin-tongue-wink" +#define FA_ICON_GRIN_WINK "fa-grin-wink" +#define FA_ICON_GRIP "fa-grip" +#define FA_ICON_GRIP_HORIZONTAL "fa-grip-horizontal" +#define FA_ICON_GRIP_LINES "fa-grip-lines" +#define FA_ICON_GRIP_LINES_VERTICAL "fa-grip-lines-vertical" +#define FA_ICON_GRIP_VERTICAL "fa-grip-vertical" +#define FA_ICON_GROUP_ARROWS_ROTATE "fa-group-arrows-rotate" +#define FA_ICON_GUARANI_SIGN "fa-guarani-sign" +#define FA_ICON_GUITAR "fa-guitar" +#define FA_ICON_GUN "fa-gun" +#define FA_ICON_H "fa-h" +#define FA_ICON_H_SQUARE "fa-h-square" +#define FA_ICON_HAMBURGER "fa-hamburger" +#define FA_ICON_HAMMER "fa-hammer" +#define FA_ICON_HAMSA "fa-hamsa" +#define FA_ICON_HAND "fa-hand" +#define FA_ICON_HAND_BACK_FIST "fa-hand-back-fist" +#define FA_ICON_HAND_DOTS "fa-hand-dots" +#define FA_ICON_HAND_FIST "fa-hand-fist" +#define FA_ICON_HAND_HOLDING "fa-hand-holding" +#define FA_ICON_HAND_HOLDING_DOLLAR "fa-hand-holding-dollar" +#define FA_ICON_HAND_HOLDING_DROPLET "fa-hand-holding-droplet" +#define FA_ICON_HAND_HOLDING_HAND "fa-hand-holding-hand" +#define FA_ICON_HAND_HOLDING_HEART "fa-hand-holding-heart" +#define FA_ICON_HAND_HOLDING_MEDICAL "fa-hand-holding-medical" +#define FA_ICON_HAND_HOLDING_USD "fa-hand-holding-usd" +#define FA_ICON_HAND_HOLDING_WATER "fa-hand-holding-water" +#define FA_ICON_HAND_LIZARD "fa-hand-lizard" +#define FA_ICON_HAND_MIDDLE_FINGER "fa-hand-middle-finger" +#define FA_ICON_HAND_PAPER "fa-hand-paper" +#define FA_ICON_HAND_PEACE "fa-hand-peace" +#define FA_ICON_HAND_POINT_DOWN "fa-hand-point-down" +#define FA_ICON_HAND_POINT_LEFT "fa-hand-point-left" +#define FA_ICON_HAND_POINT_RIGHT "fa-hand-point-right" +#define FA_ICON_HAND_POINT_UP "fa-hand-point-up" +#define FA_ICON_HAND_POINTER "fa-hand-pointer" +#define FA_ICON_HAND_ROCK "fa-hand-rock" +#define FA_ICON_HAND_SCISSORS "fa-hand-scissors" +#define FA_ICON_HAND_SPARKLES "fa-hand-sparkles" +#define FA_ICON_HAND_SPOCK "fa-hand-spock" +#define FA_ICON_HANDCUFFS "fa-handcuffs" +#define FA_ICON_HANDS "fa-hands" +#define FA_ICON_HANDS_AMERICAN_SIGN_LANGUAGE_INTERPRETING "fa-hands-american-sign-language-interpreting" +#define FA_ICON_HANDS_ASL_INTERPRETING "fa-hands-asl-interpreting" +#define FA_ICON_HANDS_BOUND "fa-hands-bound" +#define FA_ICON_HANDS_BUBBLES "fa-hands-bubbles" +#define FA_ICON_HANDS_CLAPPING "fa-hands-clapping" +#define FA_ICON_HANDS_HELPING "fa-hands-helping" +#define FA_ICON_HANDS_HOLDING "fa-hands-holding" +#define FA_ICON_HANDS_HOLDING_CHILD "fa-hands-holding-child" +#define FA_ICON_HANDS_HOLDING_CIRCLE "fa-hands-holding-circle" +#define FA_ICON_HANDS_PRAYING "fa-hands-praying" +#define FA_ICON_HANDS_WASH "fa-hands-wash" +#define FA_ICON_HANDSHAKE "fa-handshake" +#define FA_ICON_HANDSHAKE_ALT "fa-handshake-alt" +#define FA_ICON_HANDSHAKE_ALT_SLASH "fa-handshake-alt-slash" +#define FA_ICON_HANDSHAKE_ANGLE "fa-handshake-angle" +#define FA_ICON_HANDSHAKE_SIMPLE "fa-handshake-simple" +#define FA_ICON_HANDSHAKE_SIMPLE_SLASH "fa-handshake-simple-slash" +#define FA_ICON_HANDSHAKE_SLASH "fa-handshake-slash" +#define FA_ICON_HANUKIAH "fa-hanukiah" +#define FA_ICON_HARD_DRIVE "fa-hard-drive" +#define FA_ICON_HARD_HAT "fa-hard-hat" +#define FA_ICON_HARD_OF_HEARING "fa-hard-of-hearing" +#define FA_ICON_HASHTAG "fa-hashtag" +#define FA_ICON_HAT_COWBOY "fa-hat-cowboy" +#define FA_ICON_HAT_COWBOY_SIDE "fa-hat-cowboy-side" +#define FA_ICON_HAT_HARD "fa-hat-hard" +#define FA_ICON_HAT_WIZARD "fa-hat-wizard" +#define FA_ICON_HAYKAL "fa-haykal" +#define FA_ICON_HDD "fa-hdd" +#define FA_ICON_HEAD_SIDE_COUGH "fa-head-side-cough" +#define FA_ICON_HEAD_SIDE_COUGH_SLASH "fa-head-side-cough-slash" +#define FA_ICON_HEAD_SIDE_MASK "fa-head-side-mask" +#define FA_ICON_HEAD_SIDE_VIRUS "fa-head-side-virus" +#define FA_ICON_HEADER "fa-header" +#define FA_ICON_HEADING "fa-heading" +#define FA_ICON_HEADPHONES "fa-headphones" +#define FA_ICON_HEADPHONES_ALT "fa-headphones-alt" +#define FA_ICON_HEADPHONES_SIMPLE "fa-headphones-simple" +#define FA_ICON_HEADSET "fa-headset" +#define FA_ICON_HEART "fa-heart" +#define FA_ICON_HEART_BROKEN "fa-heart-broken" +#define FA_ICON_HEART_CIRCLE_BOLT "fa-heart-circle-bolt" +#define FA_ICON_HEART_CIRCLE_CHECK "fa-heart-circle-check" +#define FA_ICON_HEART_CIRCLE_EXCLAMATION "fa-heart-circle-exclamation" +#define FA_ICON_HEART_CIRCLE_MINUS "fa-heart-circle-minus" +#define FA_ICON_HEART_CIRCLE_PLUS "fa-heart-circle-plus" +#define FA_ICON_HEART_CIRCLE_XMARK "fa-heart-circle-xmark" +#define FA_ICON_HEART_CRACK "fa-heart-crack" +#define FA_ICON_HEART_MUSIC_CAMERA_BOLT "fa-heart-music-camera-bolt" +#define FA_ICON_HEART_PULSE "fa-heart-pulse" +#define FA_ICON_HEARTBEAT "fa-heartbeat" +#define FA_ICON_HELICOPTER "fa-helicopter" +#define FA_ICON_HELICOPTER_SYMBOL "fa-helicopter-symbol" +#define FA_ICON_HELMET_SAFETY "fa-helmet-safety" +#define FA_ICON_HELMET_UN "fa-helmet-un" +#define FA_ICON_HIGHLIGHTER "fa-highlighter" +#define FA_ICON_HIKING "fa-hiking" +#define FA_ICON_HILL_AVALANCHE "fa-hill-avalanche" +#define FA_ICON_HILL_ROCKSLIDE "fa-hill-rockslide" +#define FA_ICON_HIPPO "fa-hippo" +#define FA_ICON_HISTORY "fa-history" +#define FA_ICON_HOCKEY_PUCK "fa-hockey-puck" +#define FA_ICON_HOLLY_BERRY "fa-holly-berry" +#define FA_ICON_HOME "fa-home" +#define FA_ICON_HOME_ALT "fa-home-alt" +#define FA_ICON_HOME_LG "fa-home-lg" +#define FA_ICON_HOME_LG_ALT "fa-home-lg-alt" +#define FA_ICON_HOME_USER "fa-home-user" +#define FA_ICON_HORSE "fa-horse" +#define FA_ICON_HORSE_HEAD "fa-horse-head" +#define FA_ICON_HOSPITAL "fa-hospital" +#define FA_ICON_HOSPITAL_ALT "fa-hospital-alt" +#define FA_ICON_HOSPITAL_SYMBOL "fa-hospital-symbol" +#define FA_ICON_HOSPITAL_USER "fa-hospital-user" +#define FA_ICON_HOSPITAL_WIDE "fa-hospital-wide" +#define FA_ICON_HOT_TUB "fa-hot-tub" +#define FA_ICON_HOT_TUB_PERSON "fa-hot-tub-person" +#define FA_ICON_HOTDOG "fa-hotdog" +#define FA_ICON_HOTEL "fa-hotel" +#define FA_ICON_HOURGLASS "fa-hourglass" +#define FA_ICON_HOURGLASS_1 "fa-hourglass-1" +#define FA_ICON_HOURGLASS_2 "fa-hourglass-2" +#define FA_ICON_HOURGLASS_3 "fa-hourglass-3" +#define FA_ICON_HOURGLASS_EMPTY "fa-hourglass-empty" +#define FA_ICON_HOURGLASS_END "fa-hourglass-end" +#define FA_ICON_HOURGLASS_HALF "fa-hourglass-half" +#define FA_ICON_HOURGLASS_START "fa-hourglass-start" +#define FA_ICON_HOUSE "fa-house" +#define FA_ICON_HOUSE_CHIMNEY "fa-house-chimney" +#define FA_ICON_HOUSE_CHIMNEY_CRACK "fa-house-chimney-crack" +#define FA_ICON_HOUSE_CHIMNEY_MEDICAL "fa-house-chimney-medical" +#define FA_ICON_HOUSE_CHIMNEY_USER "fa-house-chimney-user" +#define FA_ICON_HOUSE_CHIMNEY_WINDOW "fa-house-chimney-window" +#define FA_ICON_HOUSE_CIRCLE_CHECK "fa-house-circle-check" +#define FA_ICON_HOUSE_CIRCLE_EXCLAMATION "fa-house-circle-exclamation" +#define FA_ICON_HOUSE_CIRCLE_XMARK "fa-house-circle-xmark" +#define FA_ICON_HOUSE_CRACK "fa-house-crack" +#define FA_ICON_HOUSE_DAMAGE "fa-house-damage" +#define FA_ICON_HOUSE_FIRE "fa-house-fire" +#define FA_ICON_HOUSE_FLAG "fa-house-flag" +#define FA_ICON_HOUSE_FLOOD_WATER "fa-house-flood-water" +#define FA_ICON_HOUSE_FLOOD_WATER_CIRCLE_ARROW_RIGHT "fa-house-flood-water-circle-arrow-right" +#define FA_ICON_HOUSE_LAPTOP "fa-house-laptop" +#define FA_ICON_HOUSE_LOCK "fa-house-lock" +#define FA_ICON_HOUSE_MEDICAL "fa-house-medical" +#define FA_ICON_HOUSE_MEDICAL_CIRCLE_CHECK "fa-house-medical-circle-check" +#define FA_ICON_HOUSE_MEDICAL_CIRCLE_EXCLAMATION "fa-house-medical-circle-exclamation" +#define FA_ICON_HOUSE_MEDICAL_CIRCLE_XMARK "fa-house-medical-circle-xmark" +#define FA_ICON_HOUSE_MEDICAL_FLAG "fa-house-medical-flag" +#define FA_ICON_HOUSE_SIGNAL "fa-house-signal" +#define FA_ICON_HOUSE_TSUNAMI "fa-house-tsunami" +#define FA_ICON_HOUSE_USER "fa-house-user" +#define FA_ICON_HRYVNIA "fa-hryvnia" +#define FA_ICON_HRYVNIA_SIGN "fa-hryvnia-sign" +#define FA_ICON_HURRICANE "fa-hurricane" +#define FA_ICON_I "fa-i" +#define FA_ICON_I_CURSOR "fa-i-cursor" +#define FA_ICON_ICE_CREAM "fa-ice-cream" +#define FA_ICON_ICICLES "fa-icicles" +#define FA_ICON_ICONS "fa-icons" +#define FA_ICON_ID_BADGE "fa-id-badge" +#define FA_ICON_ID_CARD "fa-id-card" +#define FA_ICON_ID_CARD_ALT "fa-id-card-alt" +#define FA_ICON_ID_CARD_CLIP "fa-id-card-clip" +#define FA_ICON_IGLOO "fa-igloo" +#define FA_ICON_ILS "fa-ils" +#define FA_ICON_IMAGE "fa-image" +#define FA_ICON_IMAGE_PORTRAIT "fa-image-portrait" +#define FA_ICON_IMAGES "fa-images" +#define FA_ICON_INBOX "fa-inbox" +#define FA_ICON_INDENT "fa-indent" +#define FA_ICON_INDIAN_RUPEE "fa-indian-rupee" +#define FA_ICON_INDIAN_RUPEE_SIGN "fa-indian-rupee-sign" +#define FA_ICON_INDUSTRY "fa-industry" +#define FA_ICON_INFINITY "fa-infinity" +#define FA_ICON_INFO "fa-info" +#define FA_ICON_INFO_CIRCLE "fa-info-circle" +#define FA_ICON_INR "fa-inr" +#define FA_ICON_INSTITUTION "fa-institution" +#define FA_ICON_ITALIC "fa-italic" +#define FA_ICON_J "fa-j" +#define FA_ICON_JAR "fa-jar" +#define FA_ICON_JAR_WHEAT "fa-jar-wheat" +#define FA_ICON_JEDI "fa-jedi" +#define FA_ICON_JET_FIGHTER "fa-jet-fighter" +#define FA_ICON_JET_FIGHTER_UP "fa-jet-fighter-up" +#define FA_ICON_JOINT "fa-joint" +#define FA_ICON_JOURNAL_WHILLS "fa-journal-whills" +#define FA_ICON_JPY "fa-jpy" +#define FA_ICON_JUG_DETERGENT "fa-jug-detergent" +#define FA_ICON_K "fa-k" +#define FA_ICON_KAABA "fa-kaaba" +#define FA_ICON_KEY "fa-key" +#define FA_ICON_KEYBOARD "fa-keyboard" +#define FA_ICON_KHANDA "fa-khanda" +#define FA_ICON_KIP_SIGN "fa-kip-sign" +#define FA_ICON_KISS "fa-kiss" +#define FA_ICON_KISS_BEAM "fa-kiss-beam" +#define FA_ICON_KISS_WINK_HEART "fa-kiss-wink-heart" +#define FA_ICON_KIT_MEDICAL "fa-kit-medical" +#define FA_ICON_KITCHEN_SET "fa-kitchen-set" +#define FA_ICON_KIWI_BIRD "fa-kiwi-bird" +#define FA_ICON_KRW "fa-krw" +#define FA_ICON_L "fa-l" +#define FA_ICON_LADDER_WATER "fa-ladder-water" +#define FA_ICON_LAND_MINE_ON "fa-land-mine-on" +#define FA_ICON_LANDMARK "fa-landmark" +#define FA_ICON_LANDMARK_ALT "fa-landmark-alt" +#define FA_ICON_LANDMARK_DOME "fa-landmark-dome" +#define FA_ICON_LANDMARK_FLAG "fa-landmark-flag" +#define FA_ICON_LANGUAGE "fa-language" +#define FA_ICON_LAPTOP "fa-laptop" +#define FA_ICON_LAPTOP_CODE "fa-laptop-code" +#define FA_ICON_LAPTOP_FILE "fa-laptop-file" +#define FA_ICON_LAPTOP_HOUSE "fa-laptop-house" +#define FA_ICON_LAPTOP_MEDICAL "fa-laptop-medical" +#define FA_ICON_LARI_SIGN "fa-lari-sign" +#define FA_ICON_LAUGH "fa-laugh" +#define FA_ICON_LAUGH_BEAM "fa-laugh-beam" +#define FA_ICON_LAUGH_SQUINT "fa-laugh-squint" +#define FA_ICON_LAUGH_WINK "fa-laugh-wink" +#define FA_ICON_LAYER_GROUP "fa-layer-group" +#define FA_ICON_LEAF "fa-leaf" +#define FA_ICON_LEFT_LONG "fa-left-long" +#define FA_ICON_LEFT_RIGHT "fa-left-right" +#define FA_ICON_LEGAL "fa-legal" +#define FA_ICON_LEMON "fa-lemon" +#define FA_ICON_LESS_THAN "fa-less-than" +#define FA_ICON_LESS_THAN_EQUAL "fa-less-than-equal" +#define FA_ICON_LEVEL_DOWN "fa-level-down" +#define FA_ICON_LEVEL_DOWN_ALT "fa-level-down-alt" +#define FA_ICON_LEVEL_UP "fa-level-up" +#define FA_ICON_LEVEL_UP_ALT "fa-level-up-alt" +#define FA_ICON_LIFE_RING "fa-life-ring" +#define FA_ICON_LIGHTBULB "fa-lightbulb" +#define FA_ICON_LINE_CHART "fa-line-chart" +#define FA_ICON_LINES_LEANING "fa-lines-leaning" +#define FA_ICON_LINK "fa-link" +#define FA_ICON_LINK_SLASH "fa-link-slash" +#define FA_ICON_LIRA_SIGN "fa-lira-sign" +#define FA_ICON_LIST "fa-list" +#define FA_ICON_LIST_1_2 "fa-list-1-2" +#define FA_ICON_LIST_ALT "fa-list-alt" +#define FA_ICON_LIST_CHECK "fa-list-check" +#define FA_ICON_LIST_DOTS "fa-list-dots" +#define FA_ICON_LIST_NUMERIC "fa-list-numeric" +#define FA_ICON_LIST_OL "fa-list-ol" +#define FA_ICON_LIST_SQUARES "fa-list-squares" +#define FA_ICON_LIST_UL "fa-list-ul" +#define FA_ICON_LITECOIN_SIGN "fa-litecoin-sign" +#define FA_ICON_LOCATION "fa-location" +#define FA_ICON_LOCATION_ARROW "fa-location-arrow" +#define FA_ICON_LOCATION_CROSSHAIRS "fa-location-crosshairs" +#define FA_ICON_LOCATION_DOT "fa-location-dot" +#define FA_ICON_LOCATION_PIN "fa-location-pin" +#define FA_ICON_LOCATION_PIN_LOCK "fa-location-pin-lock" +#define FA_ICON_LOCK "fa-lock" +#define FA_ICON_LOCK_OPEN "fa-lock-open" +#define FA_ICON_LOCUST "fa-locust" +#define FA_ICON_LONG_ARROW_ALT_DOWN "fa-long-arrow-alt-down" +#define FA_ICON_LONG_ARROW_ALT_LEFT "fa-long-arrow-alt-left" +#define FA_ICON_LONG_ARROW_ALT_RIGHT "fa-long-arrow-alt-right" +#define FA_ICON_LONG_ARROW_ALT_UP "fa-long-arrow-alt-up" +#define FA_ICON_LONG_ARROW_DOWN "fa-long-arrow-down" +#define FA_ICON_LONG_ARROW_LEFT "fa-long-arrow-left" +#define FA_ICON_LONG_ARROW_RIGHT "fa-long-arrow-right" +#define FA_ICON_LONG_ARROW_UP "fa-long-arrow-up" +#define FA_ICON_LOW_VISION "fa-low-vision" +#define FA_ICON_LUGGAGE_CART "fa-luggage-cart" +#define FA_ICON_LUNGS "fa-lungs" +#define FA_ICON_LUNGS_VIRUS "fa-lungs-virus" +#define FA_ICON_M "fa-m" +#define FA_ICON_MAGIC "fa-magic" +#define FA_ICON_MAGIC_WAND_SPARKLES "fa-magic-wand-sparkles" +#define FA_ICON_MAGNET "fa-magnet" +#define FA_ICON_MAGNIFYING_GLASS "fa-magnifying-glass" +#define FA_ICON_MAGNIFYING_GLASS_ARROW_RIGHT "fa-magnifying-glass-arrow-right" +#define FA_ICON_MAGNIFYING_GLASS_CHART "fa-magnifying-glass-chart" +#define FA_ICON_MAGNIFYING_GLASS_DOLLAR "fa-magnifying-glass-dollar" +#define FA_ICON_MAGNIFYING_GLASS_LOCATION "fa-magnifying-glass-location" +#define FA_ICON_MAGNIFYING_GLASS_MINUS "fa-magnifying-glass-minus" +#define FA_ICON_MAGNIFYING_GLASS_PLUS "fa-magnifying-glass-plus" +#define FA_ICON_MAIL_BULK "fa-mail-bulk" +#define FA_ICON_MAIL_FORWARD "fa-mail-forward" +#define FA_ICON_MAIL_REPLY "fa-mail-reply" +#define FA_ICON_MAIL_REPLY_ALL "fa-mail-reply-all" +#define FA_ICON_MALE "fa-male" +#define FA_ICON_MANAT_SIGN "fa-manat-sign" +#define FA_ICON_MAP "fa-map" +#define FA_ICON_MAP_LOCATION "fa-map-location" +#define FA_ICON_MAP_LOCATION_DOT "fa-map-location-dot" +#define FA_ICON_MAP_MARKED "fa-map-marked" +#define FA_ICON_MAP_MARKED_ALT "fa-map-marked-alt" +#define FA_ICON_MAP_MARKER "fa-map-marker" +#define FA_ICON_MAP_MARKER_ALT "fa-map-marker-alt" +#define FA_ICON_MAP_PIN "fa-map-pin" +#define FA_ICON_MAP_SIGNS "fa-map-signs" +#define FA_ICON_MARKER "fa-marker" +#define FA_ICON_MARS "fa-mars" +#define FA_ICON_MARS_AND_VENUS "fa-mars-and-venus" +#define FA_ICON_MARS_AND_VENUS_BURST "fa-mars-and-venus-burst" +#define FA_ICON_MARS_DOUBLE "fa-mars-double" +#define FA_ICON_MARS_STROKE "fa-mars-stroke" +#define FA_ICON_MARS_STROKE_H "fa-mars-stroke-h" +#define FA_ICON_MARS_STROKE_RIGHT "fa-mars-stroke-right" +#define FA_ICON_MARS_STROKE_UP "fa-mars-stroke-up" +#define FA_ICON_MARS_STROKE_V "fa-mars-stroke-v" +#define FA_ICON_MARTINI_GLASS "fa-martini-glass" +#define FA_ICON_MARTINI_GLASS_CITRUS "fa-martini-glass-citrus" +#define FA_ICON_MARTINI_GLASS_EMPTY "fa-martini-glass-empty" +#define FA_ICON_MASK "fa-mask" +#define FA_ICON_MASK_FACE "fa-mask-face" +#define FA_ICON_MASK_VENTILATOR "fa-mask-ventilator" +#define FA_ICON_MASKS_THEATER "fa-masks-theater" +#define FA_ICON_MATTRESS_PILLOW "fa-mattress-pillow" +#define FA_ICON_MAXIMIZE "fa-maximize" +#define FA_ICON_MEDAL "fa-medal" +#define FA_ICON_MEDKIT "fa-medkit" +#define FA_ICON_MEH "fa-meh" +#define FA_ICON_MEH_BLANK "fa-meh-blank" +#define FA_ICON_MEH_ROLLING_EYES "fa-meh-rolling-eyes" +#define FA_ICON_MEMORY "fa-memory" +#define FA_ICON_MENORAH "fa-menorah" +#define FA_ICON_MERCURY "fa-mercury" +#define FA_ICON_MESSAGE "fa-message" +#define FA_ICON_METEOR "fa-meteor" +#define FA_ICON_MICROCHIP "fa-microchip" +#define FA_ICON_MICROPHONE "fa-microphone" +#define FA_ICON_MICROPHONE_ALT "fa-microphone-alt" +#define FA_ICON_MICROPHONE_ALT_SLASH "fa-microphone-alt-slash" +#define FA_ICON_MICROPHONE_LINES "fa-microphone-lines" +#define FA_ICON_MICROPHONE_LINES_SLASH "fa-microphone-lines-slash" +#define FA_ICON_MICROPHONE_SLASH "fa-microphone-slash" +#define FA_ICON_MICROSCOPE "fa-microscope" +#define FA_ICON_MILL_SIGN "fa-mill-sign" +#define FA_ICON_MINIMIZE "fa-minimize" +#define FA_ICON_MINUS "fa-minus" +#define FA_ICON_MINUS_CIRCLE "fa-minus-circle" +#define FA_ICON_MINUS_SQUARE "fa-minus-square" +#define FA_ICON_MITTEN "fa-mitten" +#define FA_ICON_MOBILE "fa-mobile" +#define FA_ICON_MOBILE_ALT "fa-mobile-alt" +#define FA_ICON_MOBILE_ANDROID "fa-mobile-android" +#define FA_ICON_MOBILE_ANDROID_ALT "fa-mobile-android-alt" +#define FA_ICON_MOBILE_BUTTON "fa-mobile-button" +#define FA_ICON_MOBILE_PHONE "fa-mobile-phone" +#define FA_ICON_MOBILE_RETRO "fa-mobile-retro" +#define FA_ICON_MOBILE_SCREEN "fa-mobile-screen" +#define FA_ICON_MOBILE_SCREEN_BUTTON "fa-mobile-screen-button" +#define FA_ICON_MONEY_BILL "fa-money-bill" +#define FA_ICON_MONEY_BILL_1 "fa-money-bill-1" +#define FA_ICON_MONEY_BILL_1_WAVE "fa-money-bill-1-wave" +#define FA_ICON_MONEY_BILL_ALT "fa-money-bill-alt" +#define FA_ICON_MONEY_BILL_TRANSFER "fa-money-bill-transfer" +#define FA_ICON_MONEY_BILL_TREND_UP "fa-money-bill-trend-up" +#define FA_ICON_MONEY_BILL_WAVE "fa-money-bill-wave" +#define FA_ICON_MONEY_BILL_WAVE_ALT "fa-money-bill-wave-alt" +#define FA_ICON_MONEY_BILL_WHEAT "fa-money-bill-wheat" +#define FA_ICON_MONEY_BILLS "fa-money-bills" +#define FA_ICON_MONEY_CHECK "fa-money-check" +#define FA_ICON_MONEY_CHECK_ALT "fa-money-check-alt" +#define FA_ICON_MONEY_CHECK_DOLLAR "fa-money-check-dollar" +#define FA_ICON_MONUMENT "fa-monument" +#define FA_ICON_MOON "fa-moon" +#define FA_ICON_MORTAR_BOARD "fa-mortar-board" +#define FA_ICON_MORTAR_PESTLE "fa-mortar-pestle" +#define FA_ICON_MOSQUE "fa-mosque" +#define FA_ICON_MOSQUITO "fa-mosquito" +#define FA_ICON_MOSQUITO_NET "fa-mosquito-net" +#define FA_ICON_MOTORCYCLE "fa-motorcycle" +#define FA_ICON_MOUND "fa-mound" +#define FA_ICON_MOUNTAIN "fa-mountain" +#define FA_ICON_MOUNTAIN_CITY "fa-mountain-city" +#define FA_ICON_MOUNTAIN_SUN "fa-mountain-sun" +#define FA_ICON_MOUSE "fa-mouse" +#define FA_ICON_MOUSE_POINTER "fa-mouse-pointer" +#define FA_ICON_MUG_HOT "fa-mug-hot" +#define FA_ICON_MUG_SAUCER "fa-mug-saucer" +#define FA_ICON_MULTIPLY "fa-multiply" +#define FA_ICON_MUSEUM "fa-museum" +#define FA_ICON_MUSIC "fa-music" +#define FA_ICON_N "fa-n" +#define FA_ICON_NAIRA_SIGN "fa-naira-sign" +#define FA_ICON_NAVICON "fa-navicon" +#define FA_ICON_NETWORK_WIRED "fa-network-wired" +#define FA_ICON_NEUTER "fa-neuter" +#define FA_ICON_NEWSPAPER "fa-newspaper" +#define FA_ICON_NOT_EQUAL "fa-not-equal" +#define FA_ICON_NOTDEF "fa-notdef" +#define FA_ICON_NOTE_STICKY "fa-note-sticky" +#define FA_ICON_NOTES_MEDICAL "fa-notes-medical" +#define FA_ICON_O "fa-o" +#define FA_ICON_OBJECT_GROUP "fa-object-group" +#define FA_ICON_OBJECT_UNGROUP "fa-object-ungroup" +#define FA_ICON_OIL_CAN "fa-oil-can" +#define FA_ICON_OIL_WELL "fa-oil-well" +#define FA_ICON_OM "fa-om" +#define FA_ICON_OTTER "fa-otter" +#define FA_ICON_OUTDENT "fa-outdent" +#define FA_ICON_P "fa-p" +#define FA_ICON_PAGER "fa-pager" +#define FA_ICON_PAINT_BRUSH "fa-paint-brush" +#define FA_ICON_PAINT_ROLLER "fa-paint-roller" +#define FA_ICON_PAINTBRUSH "fa-paintbrush" +#define FA_ICON_PALETTE "fa-palette" +#define FA_ICON_PALLET "fa-pallet" +#define FA_ICON_PANORAMA "fa-panorama" +#define FA_ICON_PAPER_PLANE "fa-paper-plane" +#define FA_ICON_PAPERCLIP "fa-paperclip" +#define FA_ICON_PARACHUTE_BOX "fa-parachute-box" +#define FA_ICON_PARAGRAPH "fa-paragraph" +#define FA_ICON_PARKING "fa-parking" +#define FA_ICON_PASSPORT "fa-passport" +#define FA_ICON_PASTAFARIANISM "fa-pastafarianism" +#define FA_ICON_PASTE "fa-paste" +#define FA_ICON_PAUSE "fa-pause" +#define FA_ICON_PAUSE_CIRCLE "fa-pause-circle" +#define FA_ICON_PAW "fa-paw" +#define FA_ICON_PEACE "fa-peace" +#define FA_ICON_PEN "fa-pen" +#define FA_ICON_PEN_ALT "fa-pen-alt" +#define FA_ICON_PEN_CLIP "fa-pen-clip" +#define FA_ICON_PEN_FANCY "fa-pen-fancy" +#define FA_ICON_PEN_NIB "fa-pen-nib" +#define FA_ICON_PEN_RULER "fa-pen-ruler" +#define FA_ICON_PEN_SQUARE "fa-pen-square" +#define FA_ICON_PEN_TO_SQUARE "fa-pen-to-square" +#define FA_ICON_PENCIL "fa-pencil" +#define FA_ICON_PENCIL_ALT "fa-pencil-alt" +#define FA_ICON_PENCIL_RULER "fa-pencil-ruler" +#define FA_ICON_PENCIL_SQUARE "fa-pencil-square" +#define FA_ICON_PEOPLE_ARROWS "fa-people-arrows" +#define FA_ICON_PEOPLE_ARROWS_LEFT_RIGHT "fa-people-arrows-left-right" +#define FA_ICON_PEOPLE_CARRY "fa-people-carry" +#define FA_ICON_PEOPLE_CARRY_BOX "fa-people-carry-box" +#define FA_ICON_PEOPLE_GROUP "fa-people-group" +#define FA_ICON_PEOPLE_LINE "fa-people-line" +#define FA_ICON_PEOPLE_PULLING "fa-people-pulling" +#define FA_ICON_PEOPLE_ROBBERY "fa-people-robbery" +#define FA_ICON_PEOPLE_ROOF "fa-people-roof" +#define FA_ICON_PEPPER_HOT "fa-pepper-hot" +#define FA_ICON_PERCENT "fa-percent" +#define FA_ICON_PERCENTAGE "fa-percentage" +#define FA_ICON_PERSON "fa-person" +#define FA_ICON_PERSON_ARROW_DOWN_TO_LINE "fa-person-arrow-down-to-line" +#define FA_ICON_PERSON_ARROW_UP_FROM_LINE "fa-person-arrow-up-from-line" +#define FA_ICON_PERSON_BIKING "fa-person-biking" +#define FA_ICON_PERSON_BOOTH "fa-person-booth" +#define FA_ICON_PERSON_BREASTFEEDING "fa-person-breastfeeding" +#define FA_ICON_PERSON_BURST "fa-person-burst" +#define FA_ICON_PERSON_CANE "fa-person-cane" +#define FA_ICON_PERSON_CHALKBOARD "fa-person-chalkboard" +#define FA_ICON_PERSON_CIRCLE_CHECK "fa-person-circle-check" +#define FA_ICON_PERSON_CIRCLE_EXCLAMATION "fa-person-circle-exclamation" +#define FA_ICON_PERSON_CIRCLE_MINUS "fa-person-circle-minus" +#define FA_ICON_PERSON_CIRCLE_PLUS "fa-person-circle-plus" +#define FA_ICON_PERSON_CIRCLE_QUESTION "fa-person-circle-question" +#define FA_ICON_PERSON_CIRCLE_XMARK "fa-person-circle-xmark" +#define FA_ICON_PERSON_DIGGING "fa-person-digging" +#define FA_ICON_PERSON_DOTS_FROM_LINE "fa-person-dots-from-line" +#define FA_ICON_PERSON_DRESS "fa-person-dress" +#define FA_ICON_PERSON_DRESS_BURST "fa-person-dress-burst" +#define FA_ICON_PERSON_DROWNING "fa-person-drowning" +#define FA_ICON_PERSON_FALLING "fa-person-falling" +#define FA_ICON_PERSON_FALLING_BURST "fa-person-falling-burst" +#define FA_ICON_PERSON_HALF_DRESS "fa-person-half-dress" +#define FA_ICON_PERSON_HARASSING "fa-person-harassing" +#define FA_ICON_PERSON_HIKING "fa-person-hiking" +#define FA_ICON_PERSON_MILITARY_POINTING "fa-person-military-pointing" +#define FA_ICON_PERSON_MILITARY_RIFLE "fa-person-military-rifle" +#define FA_ICON_PERSON_MILITARY_TO_PERSON "fa-person-military-to-person" +#define FA_ICON_PERSON_PRAYING "fa-person-praying" +#define FA_ICON_PERSON_PREGNANT "fa-person-pregnant" +#define FA_ICON_PERSON_RAYS "fa-person-rays" +#define FA_ICON_PERSON_RIFLE "fa-person-rifle" +#define FA_ICON_PERSON_RUNNING "fa-person-running" +#define FA_ICON_PERSON_SHELTER "fa-person-shelter" +#define FA_ICON_PERSON_SKATING "fa-person-skating" +#define FA_ICON_PERSON_SKIING "fa-person-skiing" +#define FA_ICON_PERSON_SKIING_NORDIC "fa-person-skiing-nordic" +#define FA_ICON_PERSON_SNOWBOARDING "fa-person-snowboarding" +#define FA_ICON_PERSON_SWIMMING "fa-person-swimming" +#define FA_ICON_PERSON_THROUGH_WINDOW "fa-person-through-window" +#define FA_ICON_PERSON_WALKING "fa-person-walking" +#define FA_ICON_PERSON_WALKING_ARROW_LOOP_LEFT "fa-person-walking-arrow-loop-left" +#define FA_ICON_PERSON_WALKING_ARROW_RIGHT "fa-person-walking-arrow-right" +#define FA_ICON_PERSON_WALKING_DASHED_LINE_ARROW_RIGHT "fa-person-walking-dashed-line-arrow-right" +#define FA_ICON_PERSON_WALKING_LUGGAGE "fa-person-walking-luggage" +#define FA_ICON_PERSON_WALKING_WITH_CANE "fa-person-walking-with-cane" +#define FA_ICON_PESETA_SIGN "fa-peseta-sign" +#define FA_ICON_PESO_SIGN "fa-peso-sign" +#define FA_ICON_PHONE "fa-phone" +#define FA_ICON_PHONE_ALT "fa-phone-alt" +#define FA_ICON_PHONE_FLIP "fa-phone-flip" +#define FA_ICON_PHONE_SLASH "fa-phone-slash" +#define FA_ICON_PHONE_SQUARE "fa-phone-square" +#define FA_ICON_PHONE_SQUARE_ALT "fa-phone-square-alt" +#define FA_ICON_PHONE_VOLUME "fa-phone-volume" +#define FA_ICON_PHOTO_FILM "fa-photo-film" +#define FA_ICON_PHOTO_VIDEO "fa-photo-video" +#define FA_ICON_PIE_CHART "fa-pie-chart" +#define FA_ICON_PIGGY_BANK "fa-piggy-bank" +#define FA_ICON_PILLS "fa-pills" +#define FA_ICON_PING_PONG_PADDLE_BALL "fa-ping-pong-paddle-ball" +#define FA_ICON_PIZZA_SLICE "fa-pizza-slice" +#define FA_ICON_PLACE_OF_WORSHIP "fa-place-of-worship" +#define FA_ICON_PLANE "fa-plane" +#define FA_ICON_PLANE_ARRIVAL "fa-plane-arrival" +#define FA_ICON_PLANE_CIRCLE_CHECK "fa-plane-circle-check" +#define FA_ICON_PLANE_CIRCLE_EXCLAMATION "fa-plane-circle-exclamation" +#define FA_ICON_PLANE_CIRCLE_XMARK "fa-plane-circle-xmark" +#define FA_ICON_PLANE_DEPARTURE "fa-plane-departure" +#define FA_ICON_PLANE_LOCK "fa-plane-lock" +#define FA_ICON_PLANE_SLASH "fa-plane-slash" +#define FA_ICON_PLANE_UP "fa-plane-up" +#define FA_ICON_PLANT_WILT "fa-plant-wilt" +#define FA_ICON_PLATE_WHEAT "fa-plate-wheat" +#define FA_ICON_PLAY "fa-play" +#define FA_ICON_PLAY_CIRCLE "fa-play-circle" +#define FA_ICON_PLUG "fa-plug" +#define FA_ICON_PLUG_CIRCLE_BOLT "fa-plug-circle-bolt" +#define FA_ICON_PLUG_CIRCLE_CHECK "fa-plug-circle-check" +#define FA_ICON_PLUG_CIRCLE_EXCLAMATION "fa-plug-circle-exclamation" +#define FA_ICON_PLUG_CIRCLE_MINUS "fa-plug-circle-minus" +#define FA_ICON_PLUG_CIRCLE_PLUS "fa-plug-circle-plus" +#define FA_ICON_PLUG_CIRCLE_XMARK "fa-plug-circle-xmark" +#define FA_ICON_PLUS "fa-plus" +#define FA_ICON_PLUS_CIRCLE "fa-plus-circle" +#define FA_ICON_PLUS_MINUS "fa-plus-minus" +#define FA_ICON_PLUS_SQUARE "fa-plus-square" +#define FA_ICON_PODCAST "fa-podcast" +#define FA_ICON_POLL "fa-poll" +#define FA_ICON_POLL_H "fa-poll-h" +#define FA_ICON_POO "fa-poo" +#define FA_ICON_POO_BOLT "fa-poo-bolt" +#define FA_ICON_POO_STORM "fa-poo-storm" +#define FA_ICON_POOP "fa-poop" +#define FA_ICON_PORTRAIT "fa-portrait" +#define FA_ICON_POUND_SIGN "fa-pound-sign" +#define FA_ICON_POWER_OFF "fa-power-off" +#define FA_ICON_PRAY "fa-pray" +#define FA_ICON_PRAYING_HANDS "fa-praying-hands" +#define FA_ICON_PRESCRIPTION "fa-prescription" +#define FA_ICON_PRESCRIPTION_BOTTLE "fa-prescription-bottle" +#define FA_ICON_PRESCRIPTION_BOTTLE_ALT "fa-prescription-bottle-alt" +#define FA_ICON_PRESCRIPTION_BOTTLE_MEDICAL "fa-prescription-bottle-medical" +#define FA_ICON_PRINT "fa-print" +#define FA_ICON_PROCEDURES "fa-procedures" +#define FA_ICON_PROJECT_DIAGRAM "fa-project-diagram" +#define FA_ICON_PUMP_MEDICAL "fa-pump-medical" +#define FA_ICON_PUMP_SOAP "fa-pump-soap" +#define FA_ICON_PUZZLE_PIECE "fa-puzzle-piece" +#define FA_ICON_Q "fa-q" +#define FA_ICON_QRCODE "fa-qrcode" +#define FA_ICON_QUESTION "fa-question" +#define FA_ICON_QUESTION_CIRCLE "fa-question-circle" +#define FA_ICON_QUIDDITCH "fa-quidditch" +#define FA_ICON_QUIDDITCH_BROOM_BALL "fa-quidditch-broom-ball" +#define FA_ICON_QUOTE_LEFT "fa-quote-left" +#define FA_ICON_QUOTE_LEFT_ALT "fa-quote-left-alt" +#define FA_ICON_QUOTE_RIGHT "fa-quote-right" +#define FA_ICON_QUOTE_RIGHT_ALT "fa-quote-right-alt" +#define FA_ICON_QURAN "fa-quran" +#define FA_ICON_R "fa-r" +#define FA_ICON_RADIATION "fa-radiation" +#define FA_ICON_RADIATION_ALT "fa-radiation-alt" +#define FA_ICON_RADIO "fa-radio" +#define FA_ICON_RAINBOW "fa-rainbow" +#define FA_ICON_RANDOM "fa-random" +#define FA_ICON_RANKING_STAR "fa-ranking-star" +#define FA_ICON_RECEIPT "fa-receipt" +#define FA_ICON_RECORD_VINYL "fa-record-vinyl" +#define FA_ICON_RECTANGLE_AD "fa-rectangle-ad" +#define FA_ICON_RECTANGLE_LIST "fa-rectangle-list" +#define FA_ICON_RECTANGLE_TIMES "fa-rectangle-times" +#define FA_ICON_RECTANGLE_XMARK "fa-rectangle-xmark" +#define FA_ICON_RECYCLE "fa-recycle" +#define FA_ICON_REDO "fa-redo" +#define FA_ICON_REDO_ALT "fa-redo-alt" +#define FA_ICON_REFRESH "fa-refresh" +#define FA_ICON_REGISTERED "fa-registered" +#define FA_ICON_REMOVE "fa-remove" +#define FA_ICON_REMOVE_FORMAT "fa-remove-format" +#define FA_ICON_REORDER "fa-reorder" +#define FA_ICON_REPEAT "fa-repeat" +#define FA_ICON_REPLY "fa-reply" +#define FA_ICON_REPLY_ALL "fa-reply-all" +#define FA_ICON_REPUBLICAN "fa-republican" +#define FA_ICON_RESTROOM "fa-restroom" +#define FA_ICON_RETWEET "fa-retweet" +#define FA_ICON_RIBBON "fa-ribbon" +#define FA_ICON_RIGHT_FROM_BRACKET "fa-right-from-bracket" +#define FA_ICON_RIGHT_LEFT "fa-right-left" +#define FA_ICON_RIGHT_LONG "fa-right-long" +#define FA_ICON_RIGHT_TO_BRACKET "fa-right-to-bracket" +#define FA_ICON_RING "fa-ring" +#define FA_ICON_RMB "fa-rmb" +#define FA_ICON_ROAD "fa-road" +#define FA_ICON_ROAD_BARRIER "fa-road-barrier" +#define FA_ICON_ROAD_BRIDGE "fa-road-bridge" +#define FA_ICON_ROAD_CIRCLE_CHECK "fa-road-circle-check" +#define FA_ICON_ROAD_CIRCLE_EXCLAMATION "fa-road-circle-exclamation" +#define FA_ICON_ROAD_CIRCLE_XMARK "fa-road-circle-xmark" +#define FA_ICON_ROAD_LOCK "fa-road-lock" +#define FA_ICON_ROAD_SPIKES "fa-road-spikes" +#define FA_ICON_ROBOT "fa-robot" +#define FA_ICON_ROCKET "fa-rocket" +#define FA_ICON_ROD_ASCLEPIUS "fa-rod-asclepius" +#define FA_ICON_ROD_SNAKE "fa-rod-snake" +#define FA_ICON_ROTATE "fa-rotate" +#define FA_ICON_ROTATE_BACK "fa-rotate-back" +#define FA_ICON_ROTATE_BACKWARD "fa-rotate-backward" +#define FA_ICON_ROTATE_FORWARD "fa-rotate-forward" +#define FA_ICON_ROTATE_LEFT "fa-rotate-left" +#define FA_ICON_ROTATE_RIGHT "fa-rotate-right" +#define FA_ICON_ROUBLE "fa-rouble" +#define FA_ICON_ROUTE "fa-route" +#define FA_ICON_RSS "fa-rss" +#define FA_ICON_RSS_SQUARE "fa-rss-square" +#define FA_ICON_RUB "fa-rub" +#define FA_ICON_RUBLE "fa-ruble" +#define FA_ICON_RUBLE_SIGN "fa-ruble-sign" +#define FA_ICON_RUG "fa-rug" +#define FA_ICON_RULER "fa-ruler" +#define FA_ICON_RULER_COMBINED "fa-ruler-combined" +#define FA_ICON_RULER_HORIZONTAL "fa-ruler-horizontal" +#define FA_ICON_RULER_VERTICAL "fa-ruler-vertical" +#define FA_ICON_RUNNING "fa-running" +#define FA_ICON_RUPEE "fa-rupee" +#define FA_ICON_RUPEE_SIGN "fa-rupee-sign" +#define FA_ICON_RUPIAH_SIGN "fa-rupiah-sign" +#define FA_ICON_S "fa-s" +#define FA_ICON_SACK_DOLLAR "fa-sack-dollar" +#define FA_ICON_SACK_XMARK "fa-sack-xmark" +#define FA_ICON_SAD_CRY "fa-sad-cry" +#define FA_ICON_SAD_TEAR "fa-sad-tear" +#define FA_ICON_SAILBOAT "fa-sailboat" +#define FA_ICON_SATELLITE "fa-satellite" +#define FA_ICON_SATELLITE_DISH "fa-satellite-dish" +#define FA_ICON_SAVE "fa-save" +#define FA_ICON_SCALE_BALANCED "fa-scale-balanced" +#define FA_ICON_SCALE_UNBALANCED "fa-scale-unbalanced" +#define FA_ICON_SCALE_UNBALANCED_FLIP "fa-scale-unbalanced-flip" +#define FA_ICON_SCHOOL "fa-school" +#define FA_ICON_SCHOOL_CIRCLE_CHECK "fa-school-circle-check" +#define FA_ICON_SCHOOL_CIRCLE_EXCLAMATION "fa-school-circle-exclamation" +#define FA_ICON_SCHOOL_CIRCLE_XMARK "fa-school-circle-xmark" +#define FA_ICON_SCHOOL_FLAG "fa-school-flag" +#define FA_ICON_SCHOOL_LOCK "fa-school-lock" +#define FA_ICON_SCISSORS "fa-scissors" +#define FA_ICON_SCREWDRIVER "fa-screwdriver" +#define FA_ICON_SCREWDRIVER_WRENCH "fa-screwdriver-wrench" +#define FA_ICON_SCROLL "fa-scroll" +#define FA_ICON_SCROLL_TORAH "fa-scroll-torah" +#define FA_ICON_SD_CARD "fa-sd-card" +#define FA_ICON_SEARCH "fa-search" +#define FA_ICON_SEARCH_DOLLAR "fa-search-dollar" +#define FA_ICON_SEARCH_LOCATION "fa-search-location" +#define FA_ICON_SEARCH_MINUS "fa-search-minus" +#define FA_ICON_SEARCH_PLUS "fa-search-plus" +#define FA_ICON_SECTION "fa-section" +#define FA_ICON_SEEDLING "fa-seedling" +#define FA_ICON_SERVER "fa-server" +#define FA_ICON_SHAPES "fa-shapes" +#define FA_ICON_SHARE "fa-share" +#define FA_ICON_SHARE_ALT "fa-share-alt" +#define FA_ICON_SHARE_ALT_SQUARE "fa-share-alt-square" +#define FA_ICON_SHARE_FROM_SQUARE "fa-share-from-square" +#define FA_ICON_SHARE_NODES "fa-share-nodes" +#define FA_ICON_SHARE_SQUARE "fa-share-square" +#define FA_ICON_SHEET_PLASTIC "fa-sheet-plastic" +#define FA_ICON_SHEKEL "fa-shekel" +#define FA_ICON_SHEKEL_SIGN "fa-shekel-sign" +#define FA_ICON_SHEQEL "fa-sheqel" +#define FA_ICON_SHEQEL_SIGN "fa-sheqel-sign" +#define FA_ICON_SHIELD "fa-shield" +#define FA_ICON_SHIELD_ALT "fa-shield-alt" +#define FA_ICON_SHIELD_BLANK "fa-shield-blank" +#define FA_ICON_SHIELD_CAT "fa-shield-cat" +#define FA_ICON_SHIELD_DOG "fa-shield-dog" +#define FA_ICON_SHIELD_HALVED "fa-shield-halved" +#define FA_ICON_SHIELD_HEART "fa-shield-heart" +#define FA_ICON_SHIELD_VIRUS "fa-shield-virus" +#define FA_ICON_SHIP "fa-ship" +#define FA_ICON_SHIPPING_FAST "fa-shipping-fast" +#define FA_ICON_SHIRT "fa-shirt" +#define FA_ICON_SHOE_PRINTS "fa-shoe-prints" +#define FA_ICON_SHOP "fa-shop" +#define FA_ICON_SHOP_LOCK "fa-shop-lock" +#define FA_ICON_SHOP_SLASH "fa-shop-slash" +#define FA_ICON_SHOPPING_BAG "fa-shopping-bag" +#define FA_ICON_SHOPPING_BASKET "fa-shopping-basket" +#define FA_ICON_SHOPPING_CART "fa-shopping-cart" +#define FA_ICON_SHOWER "fa-shower" +#define FA_ICON_SHRIMP "fa-shrimp" +#define FA_ICON_SHUFFLE "fa-shuffle" +#define FA_ICON_SHUTTLE_SPACE "fa-shuttle-space" +#define FA_ICON_SHUTTLE_VAN "fa-shuttle-van" +#define FA_ICON_SIGN "fa-sign" +#define FA_ICON_SIGN_HANGING "fa-sign-hanging" +#define FA_ICON_SIGN_IN "fa-sign-in" +#define FA_ICON_SIGN_IN_ALT "fa-sign-in-alt" +#define FA_ICON_SIGN_LANGUAGE "fa-sign-language" +#define FA_ICON_SIGN_OUT "fa-sign-out" +#define FA_ICON_SIGN_OUT_ALT "fa-sign-out-alt" +#define FA_ICON_SIGNAL "fa-signal" +#define FA_ICON_SIGNAL_5 "fa-signal-5" +#define FA_ICON_SIGNAL_PERFECT "fa-signal-perfect" +#define FA_ICON_SIGNATURE "fa-signature" +#define FA_ICON_SIGNING "fa-signing" +#define FA_ICON_SIGNS_POST "fa-signs-post" +#define FA_ICON_SIM_CARD "fa-sim-card" +#define FA_ICON_SINK "fa-sink" +#define FA_ICON_SITEMAP "fa-sitemap" +#define FA_ICON_SKATING "fa-skating" +#define FA_ICON_SKIING "fa-skiing" +#define FA_ICON_SKIING_NORDIC "fa-skiing-nordic" +#define FA_ICON_SKULL "fa-skull" +#define FA_ICON_SKULL_CROSSBONES "fa-skull-crossbones" +#define FA_ICON_SLASH "fa-slash" +#define FA_ICON_SLEIGH "fa-sleigh" +#define FA_ICON_SLIDERS "fa-sliders" +#define FA_ICON_SLIDERS_H "fa-sliders-h" +#define FA_ICON_SMILE "fa-smile" +#define FA_ICON_SMILE_BEAM "fa-smile-beam" +#define FA_ICON_SMILE_WINK "fa-smile-wink" +#define FA_ICON_SMOG "fa-smog" +#define FA_ICON_SMOKING "fa-smoking" +#define FA_ICON_SMOKING_BAN "fa-smoking-ban" +#define FA_ICON_SMS "fa-sms" +#define FA_ICON_SNOWBOARDING "fa-snowboarding" +#define FA_ICON_SNOWFLAKE "fa-snowflake" +#define FA_ICON_SNOWMAN "fa-snowman" +#define FA_ICON_SNOWPLOW "fa-snowplow" +#define FA_ICON_SOAP "fa-soap" +#define FA_ICON_SOCCER_BALL "fa-soccer-ball" +#define FA_ICON_SOCKS "fa-socks" +#define FA_ICON_SOLAR_PANEL "fa-solar-panel" +#define FA_ICON_SORT "fa-sort" +#define FA_ICON_SORT_ALPHA_ASC "fa-sort-alpha-asc" +#define FA_ICON_SORT_ALPHA_DESC "fa-sort-alpha-desc" +#define FA_ICON_SORT_ALPHA_DOWN "fa-sort-alpha-down" +#define FA_ICON_SORT_ALPHA_DOWN_ALT "fa-sort-alpha-down-alt" +#define FA_ICON_SORT_ALPHA_UP "fa-sort-alpha-up" +#define FA_ICON_SORT_ALPHA_UP_ALT "fa-sort-alpha-up-alt" +#define FA_ICON_SORT_AMOUNT_ASC "fa-sort-amount-asc" +#define FA_ICON_SORT_AMOUNT_DESC "fa-sort-amount-desc" +#define FA_ICON_SORT_AMOUNT_DOWN "fa-sort-amount-down" +#define FA_ICON_SORT_AMOUNT_DOWN_ALT "fa-sort-amount-down-alt" +#define FA_ICON_SORT_AMOUNT_UP "fa-sort-amount-up" +#define FA_ICON_SORT_AMOUNT_UP_ALT "fa-sort-amount-up-alt" +#define FA_ICON_SORT_ASC "fa-sort-asc" +#define FA_ICON_SORT_DESC "fa-sort-desc" +#define FA_ICON_SORT_DOWN "fa-sort-down" +#define FA_ICON_SORT_NUMERIC_ASC "fa-sort-numeric-asc" +#define FA_ICON_SORT_NUMERIC_DESC "fa-sort-numeric-desc" +#define FA_ICON_SORT_NUMERIC_DOWN "fa-sort-numeric-down" +#define FA_ICON_SORT_NUMERIC_DOWN_ALT "fa-sort-numeric-down-alt" +#define FA_ICON_SORT_NUMERIC_UP "fa-sort-numeric-up" +#define FA_ICON_SORT_NUMERIC_UP_ALT "fa-sort-numeric-up-alt" +#define FA_ICON_SORT_UP "fa-sort-up" +#define FA_ICON_SPA "fa-spa" +#define FA_ICON_SPACE_SHUTTLE "fa-space-shuttle" +#define FA_ICON_SPAGHETTI_MONSTER_FLYING "fa-spaghetti-monster-flying" +#define FA_ICON_SPELL_CHECK "fa-spell-check" +#define FA_ICON_SPIDER "fa-spider" +#define FA_ICON_SPINNER "fa-spinner" +#define FA_ICON_SPLOTCH "fa-splotch" +#define FA_ICON_SPOON "fa-spoon" +#define FA_ICON_SPRAY_CAN "fa-spray-can" +#define FA_ICON_SPRAY_CAN_SPARKLES "fa-spray-can-sparkles" +#define FA_ICON_SPROUT "fa-sprout" +#define FA_ICON_SQUARE "fa-square" +#define FA_ICON_SQUARE_ARROW_UP_RIGHT "fa-square-arrow-up-right" +#define FA_ICON_SQUARE_CARET_DOWN "fa-square-caret-down" +#define FA_ICON_SQUARE_CARET_LEFT "fa-square-caret-left" +#define FA_ICON_SQUARE_CARET_RIGHT "fa-square-caret-right" +#define FA_ICON_SQUARE_CARET_UP "fa-square-caret-up" +#define FA_ICON_SQUARE_CHECK "fa-square-check" +#define FA_ICON_SQUARE_ENVELOPE "fa-square-envelope" +#define FA_ICON_SQUARE_FULL "fa-square-full" +#define FA_ICON_SQUARE_H "fa-square-h" +#define FA_ICON_SQUARE_MINUS "fa-square-minus" +#define FA_ICON_SQUARE_NFI "fa-square-nfi" +#define FA_ICON_SQUARE_PARKING "fa-square-parking" +#define FA_ICON_SQUARE_PEN "fa-square-pen" +#define FA_ICON_SQUARE_PERSON_CONFINED "fa-square-person-confined" +#define FA_ICON_SQUARE_PHONE "fa-square-phone" +#define FA_ICON_SQUARE_PHONE_FLIP "fa-square-phone-flip" +#define FA_ICON_SQUARE_PLUS "fa-square-plus" +#define FA_ICON_SQUARE_POLL_HORIZONTAL "fa-square-poll-horizontal" +#define FA_ICON_SQUARE_POLL_VERTICAL "fa-square-poll-vertical" +#define FA_ICON_SQUARE_ROOT_ALT "fa-square-root-alt" +#define FA_ICON_SQUARE_ROOT_VARIABLE "fa-square-root-variable" +#define FA_ICON_SQUARE_RSS "fa-square-rss" +#define FA_ICON_SQUARE_SHARE_NODES "fa-square-share-nodes" +#define FA_ICON_SQUARE_UP_RIGHT "fa-square-up-right" +#define FA_ICON_SQUARE_VIRUS "fa-square-virus" +#define FA_ICON_SQUARE_XMARK "fa-square-xmark" +#define FA_ICON_SR_ONLY_FOCUSABLE "fa-sr-only-focusable" +#define FA_ICON_STAFF_AESCULAPIUS "fa-staff-aesculapius" +#define FA_ICON_STAFF_SNAKE "fa-staff-snake" +#define FA_ICON_STAIRS "fa-stairs" +#define FA_ICON_STAMP "fa-stamp" +#define FA_ICON_STAPLER "fa-stapler" +#define FA_ICON_STAR "fa-star" +#define FA_ICON_STAR_AND_CRESCENT "fa-star-and-crescent" +#define FA_ICON_STAR_HALF "fa-star-half" +#define FA_ICON_STAR_HALF_ALT "fa-star-half-alt" +#define FA_ICON_STAR_HALF_STROKE "fa-star-half-stroke" +#define FA_ICON_STAR_OF_DAVID "fa-star-of-david" +#define FA_ICON_STAR_OF_LIFE "fa-star-of-life" +#define FA_ICON_STEP_BACKWARD "fa-step-backward" +#define FA_ICON_STEP_FORWARD "fa-step-forward" +#define FA_ICON_STERLING_SIGN "fa-sterling-sign" +#define FA_ICON_STETHOSCOPE "fa-stethoscope" +#define FA_ICON_STICKY_NOTE "fa-sticky-note" +#define FA_ICON_STOP "fa-stop" +#define FA_ICON_STOP_CIRCLE "fa-stop-circle" +#define FA_ICON_STOPWATCH "fa-stopwatch" +#define FA_ICON_STOPWATCH_20 "fa-stopwatch-20" +#define FA_ICON_STORE "fa-store" +#define FA_ICON_STORE_ALT "fa-store-alt" +#define FA_ICON_STORE_ALT_SLASH "fa-store-alt-slash" +#define FA_ICON_STORE_SLASH "fa-store-slash" +#define FA_ICON_STREAM "fa-stream" +#define FA_ICON_STREET_VIEW "fa-street-view" +#define FA_ICON_STRIKETHROUGH "fa-strikethrough" +#define FA_ICON_STROOPWAFEL "fa-stroopwafel" +#define FA_ICON_SUBSCRIPT "fa-subscript" +#define FA_ICON_SUBTRACT "fa-subtract" +#define FA_ICON_SUBWAY "fa-subway" +#define FA_ICON_SUITCASE "fa-suitcase" +#define FA_ICON_SUITCASE_MEDICAL "fa-suitcase-medical" +#define FA_ICON_SUITCASE_ROLLING "fa-suitcase-rolling" +#define FA_ICON_SUN "fa-sun" +#define FA_ICON_SUN_PLANT_WILT "fa-sun-plant-wilt" +#define FA_ICON_SUPERSCRIPT "fa-superscript" +#define FA_ICON_SURPRISE "fa-surprise" +#define FA_ICON_SWATCHBOOK "fa-swatchbook" +#define FA_ICON_SWIMMER "fa-swimmer" +#define FA_ICON_SWIMMING_POOL "fa-swimming-pool" +#define FA_ICON_SYNAGOGUE "fa-synagogue" +#define FA_ICON_SYNC "fa-sync" +#define FA_ICON_SYNC_ALT "fa-sync-alt" +#define FA_ICON_SYRINGE "fa-syringe" +#define FA_ICON_T "fa-t" +#define FA_ICON_T_SHIRT "fa-t-shirt" +#define FA_ICON_TABLE "fa-table" +#define FA_ICON_TABLE_CELLS "fa-table-cells" +#define FA_ICON_TABLE_CELLS_LARGE "fa-table-cells-large" +#define FA_ICON_TABLE_COLUMNS "fa-table-columns" +#define FA_ICON_TABLE_LIST "fa-table-list" +#define FA_ICON_TABLE_TENNIS "fa-table-tennis" +#define FA_ICON_TABLE_TENNIS_PADDLE_BALL "fa-table-tennis-paddle-ball" +#define FA_ICON_TABLET "fa-tablet" +#define FA_ICON_TABLET_ALT "fa-tablet-alt" +#define FA_ICON_TABLET_ANDROID "fa-tablet-android" +#define FA_ICON_TABLET_BUTTON "fa-tablet-button" +#define FA_ICON_TABLET_SCREEN_BUTTON "fa-tablet-screen-button" +#define FA_ICON_TABLETS "fa-tablets" +#define FA_ICON_TACHOGRAPH_DIGITAL "fa-tachograph-digital" +#define FA_ICON_TACHOMETER "fa-tachometer" +#define FA_ICON_TACHOMETER_ALT "fa-tachometer-alt" +#define FA_ICON_TACHOMETER_ALT_AVERAGE "fa-tachometer-alt-average" +#define FA_ICON_TACHOMETER_ALT_FAST "fa-tachometer-alt-fast" +#define FA_ICON_TACHOMETER_AVERAGE "fa-tachometer-average" +#define FA_ICON_TACHOMETER_FAST "fa-tachometer-fast" +#define FA_ICON_TAG "fa-tag" +#define FA_ICON_TAGS "fa-tags" +#define FA_ICON_TANAKH "fa-tanakh" +#define FA_ICON_TAPE "fa-tape" +#define FA_ICON_TARP "fa-tarp" +#define FA_ICON_TARP_DROPLET "fa-tarp-droplet" +#define FA_ICON_TASKS "fa-tasks" +#define FA_ICON_TASKS_ALT "fa-tasks-alt" +#define FA_ICON_TAXI "fa-taxi" +#define FA_ICON_TEETH "fa-teeth" +#define FA_ICON_TEETH_OPEN "fa-teeth-open" +#define FA_ICON_TELETYPE "fa-teletype" +#define FA_ICON_TELEVISION "fa-television" +#define FA_ICON_TEMPERATURE_0 "fa-temperature-0" +#define FA_ICON_TEMPERATURE_1 "fa-temperature-1" +#define FA_ICON_TEMPERATURE_2 "fa-temperature-2" +#define FA_ICON_TEMPERATURE_3 "fa-temperature-3" +#define FA_ICON_TEMPERATURE_4 "fa-temperature-4" +#define FA_ICON_TEMPERATURE_ARROW_DOWN "fa-temperature-arrow-down" +#define FA_ICON_TEMPERATURE_ARROW_UP "fa-temperature-arrow-up" +#define FA_ICON_TEMPERATURE_DOWN "fa-temperature-down" +#define FA_ICON_TEMPERATURE_EMPTY "fa-temperature-empty" +#define FA_ICON_TEMPERATURE_FULL "fa-temperature-full" +#define FA_ICON_TEMPERATURE_HALF "fa-temperature-half" +#define FA_ICON_TEMPERATURE_HIGH "fa-temperature-high" +#define FA_ICON_TEMPERATURE_LOW "fa-temperature-low" +#define FA_ICON_TEMPERATURE_QUARTER "fa-temperature-quarter" +#define FA_ICON_TEMPERATURE_THREE_QUARTERS "fa-temperature-three-quarters" +#define FA_ICON_TEMPERATURE_UP "fa-temperature-up" +#define FA_ICON_TENGE "fa-tenge" +#define FA_ICON_TENGE_SIGN "fa-tenge-sign" +#define FA_ICON_TENT "fa-tent" +#define FA_ICON_TENT_ARROW_DOWN_TO_LINE "fa-tent-arrow-down-to-line" +#define FA_ICON_TENT_ARROW_LEFT_RIGHT "fa-tent-arrow-left-right" +#define FA_ICON_TENT_ARROW_TURN_LEFT "fa-tent-arrow-turn-left" +#define FA_ICON_TENT_ARROWS_DOWN "fa-tent-arrows-down" +#define FA_ICON_TENTS "fa-tents" +#define FA_ICON_TERMINAL "fa-terminal" +#define FA_ICON_TEXT_HEIGHT "fa-text-height" +#define FA_ICON_TEXT_SLASH "fa-text-slash" +#define FA_ICON_TEXT_WIDTH "fa-text-width" +#define FA_ICON_TH "fa-th" +#define FA_ICON_TH_LARGE "fa-th-large" +#define FA_ICON_TH_LIST "fa-th-list" +#define FA_ICON_THEATER_MASKS "fa-theater-masks" +#define FA_ICON_THERMOMETER "fa-thermometer" +#define FA_ICON_THERMOMETER_0 "fa-thermometer-0" +#define FA_ICON_THERMOMETER_1 "fa-thermometer-1" +#define FA_ICON_THERMOMETER_2 "fa-thermometer-2" +#define FA_ICON_THERMOMETER_3 "fa-thermometer-3" +#define FA_ICON_THERMOMETER_4 "fa-thermometer-4" +#define FA_ICON_THERMOMETER_EMPTY "fa-thermometer-empty" +#define FA_ICON_THERMOMETER_FULL "fa-thermometer-full" +#define FA_ICON_THERMOMETER_HALF "fa-thermometer-half" +#define FA_ICON_THERMOMETER_QUARTER "fa-thermometer-quarter" +#define FA_ICON_THERMOMETER_THREE_QUARTERS "fa-thermometer-three-quarters" +#define FA_ICON_THUMB_TACK "fa-thumb-tack" +#define FA_ICON_THUMBS_DOWN "fa-thumbs-down" +#define FA_ICON_THUMBS_UP "fa-thumbs-up" +#define FA_ICON_THUMBTACK "fa-thumbtack" +#define FA_ICON_THUNDERSTORM "fa-thunderstorm" +#define FA_ICON_TICKET "fa-ticket" +#define FA_ICON_TICKET_ALT "fa-ticket-alt" +#define FA_ICON_TICKET_SIMPLE "fa-ticket-simple" +#define FA_ICON_TIMELINE "fa-timeline" +#define FA_ICON_TIMES "fa-times" +#define FA_ICON_TIMES_CIRCLE "fa-times-circle" +#define FA_ICON_TIMES_RECTANGLE "fa-times-rectangle" +#define FA_ICON_TIMES_SQUARE "fa-times-square" +#define FA_ICON_TINT "fa-tint" +#define FA_ICON_TINT_SLASH "fa-tint-slash" +#define FA_ICON_TIRED "fa-tired" +#define FA_ICON_TOGGLE_OFF "fa-toggle-off" +#define FA_ICON_TOGGLE_ON "fa-toggle-on" +#define FA_ICON_TOILET "fa-toilet" +#define FA_ICON_TOILET_PAPER "fa-toilet-paper" +#define FA_ICON_TOILET_PAPER_SLASH "fa-toilet-paper-slash" +#define FA_ICON_TOILET_PORTABLE "fa-toilet-portable" +#define FA_ICON_TOILETS_PORTABLE "fa-toilets-portable" +#define FA_ICON_TOOLBOX "fa-toolbox" +#define FA_ICON_TOOLS "fa-tools" +#define FA_ICON_TOOTH "fa-tooth" +#define FA_ICON_TORAH "fa-torah" +#define FA_ICON_TORII_GATE "fa-torii-gate" +#define FA_ICON_TORNADO "fa-tornado" +#define FA_ICON_TOWER_BROADCAST "fa-tower-broadcast" +#define FA_ICON_TOWER_CELL "fa-tower-cell" +#define FA_ICON_TOWER_OBSERVATION "fa-tower-observation" +#define FA_ICON_TRACTOR "fa-tractor" +#define FA_ICON_TRADEMARK "fa-trademark" +#define FA_ICON_TRAFFIC_LIGHT "fa-traffic-light" +#define FA_ICON_TRAILER "fa-trailer" +#define FA_ICON_TRAIN "fa-train" +#define FA_ICON_TRAIN_SUBWAY "fa-train-subway" +#define FA_ICON_TRAIN_TRAM "fa-train-tram" +#define FA_ICON_TRAM "fa-tram" +#define FA_ICON_TRANSGENDER "fa-transgender" +#define FA_ICON_TRANSGENDER_ALT "fa-transgender-alt" +#define FA_ICON_TRASH "fa-trash" +#define FA_ICON_TRASH_ALT "fa-trash-alt" +#define FA_ICON_TRASH_ARROW_UP "fa-trash-arrow-up" +#define FA_ICON_TRASH_CAN "fa-trash-can" +#define FA_ICON_TRASH_CAN_ARROW_UP "fa-trash-can-arrow-up" +#define FA_ICON_TRASH_RESTORE "fa-trash-restore" +#define FA_ICON_TRASH_RESTORE_ALT "fa-trash-restore-alt" +#define FA_ICON_TREE "fa-tree" +#define FA_ICON_TREE_CITY "fa-tree-city" +#define FA_ICON_TRIANGLE_CIRCLE_SQUARE "fa-triangle-circle-square" +#define FA_ICON_TRIANGLE_EXCLAMATION "fa-triangle-exclamation" +#define FA_ICON_TROPHY "fa-trophy" +#define FA_ICON_TROWEL "fa-trowel" +#define FA_ICON_TROWEL_BRICKS "fa-trowel-bricks" +#define FA_ICON_TRUCK "fa-truck" +#define FA_ICON_TRUCK_ARROW_RIGHT "fa-truck-arrow-right" +#define FA_ICON_TRUCK_DROPLET "fa-truck-droplet" +#define FA_ICON_TRUCK_FAST "fa-truck-fast" +#define FA_ICON_TRUCK_FIELD "fa-truck-field" +#define FA_ICON_TRUCK_FIELD_UN "fa-truck-field-un" +#define FA_ICON_TRUCK_FRONT "fa-truck-front" +#define FA_ICON_TRUCK_LOADING "fa-truck-loading" +#define FA_ICON_TRUCK_MEDICAL "fa-truck-medical" +#define FA_ICON_TRUCK_MONSTER "fa-truck-monster" +#define FA_ICON_TRUCK_MOVING "fa-truck-moving" +#define FA_ICON_TRUCK_PICKUP "fa-truck-pickup" +#define FA_ICON_TRUCK_PLANE "fa-truck-plane" +#define FA_ICON_TRUCK_RAMP_BOX "fa-truck-ramp-box" +#define FA_ICON_TRY "fa-try" +#define FA_ICON_TSHIRT "fa-tshirt" +#define FA_ICON_TTY "fa-tty" +#define FA_ICON_TURKISH_LIRA "fa-turkish-lira" +#define FA_ICON_TURKISH_LIRA_SIGN "fa-turkish-lira-sign" +#define FA_ICON_TURN_DOWN "fa-turn-down" +#define FA_ICON_TURN_UP "fa-turn-up" +#define FA_ICON_TV "fa-tv" +#define FA_ICON_TV_ALT "fa-tv-alt" +#define FA_ICON_U "fa-u" +#define FA_ICON_UMBRELLA "fa-umbrella" +#define FA_ICON_UMBRELLA_BEACH "fa-umbrella-beach" +#define FA_ICON_UNDERLINE "fa-underline" +#define FA_ICON_UNDO "fa-undo" +#define FA_ICON_UNDO_ALT "fa-undo-alt" +#define FA_ICON_UNIVERSAL_ACCESS "fa-universal-access" +#define FA_ICON_UNIVERSITY "fa-university" +#define FA_ICON_UNLINK "fa-unlink" +#define FA_ICON_UNLOCK "fa-unlock" +#define FA_ICON_UNLOCK_ALT "fa-unlock-alt" +#define FA_ICON_UNLOCK_KEYHOLE "fa-unlock-keyhole" +#define FA_ICON_UNSORTED "fa-unsorted" +#define FA_ICON_UP_DOWN "fa-up-down" +#define FA_ICON_UP_DOWN_LEFT_RIGHT "fa-up-down-left-right" +#define FA_ICON_UP_LONG "fa-up-long" +#define FA_ICON_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER "fa-up-right-and-down-left-from-center" +#define FA_ICON_UP_RIGHT_FROM_SQUARE "fa-up-right-from-square" +#define FA_ICON_UPLOAD "fa-upload" +#define FA_ICON_USD "fa-usd" +#define FA_ICON_USER "fa-user" +#define FA_ICON_USER_ALT "fa-user-alt" +#define FA_ICON_USER_ALT_SLASH "fa-user-alt-slash" +#define FA_ICON_USER_ASTRONAUT "fa-user-astronaut" +#define FA_ICON_USER_CHECK "fa-user-check" +#define FA_ICON_USER_CIRCLE "fa-user-circle" +#define FA_ICON_USER_CLOCK "fa-user-clock" +#define FA_ICON_USER_COG "fa-user-cog" +#define FA_ICON_USER_DOCTOR "fa-user-doctor" +#define FA_ICON_USER_EDIT "fa-user-edit" +#define FA_ICON_USER_FRIENDS "fa-user-friends" +#define FA_ICON_USER_GEAR "fa-user-gear" +#define FA_ICON_USER_GRADUATE "fa-user-graduate" +#define FA_ICON_USER_GROUP "fa-user-group" +#define FA_ICON_USER_INJURED "fa-user-injured" +#define FA_ICON_USER_LARGE "fa-user-large" +#define FA_ICON_USER_LARGE_SLASH "fa-user-large-slash" +#define FA_ICON_USER_LOCK "fa-user-lock" +#define FA_ICON_USER_MD "fa-user-md" +#define FA_ICON_USER_MINUS "fa-user-minus" +#define FA_ICON_USER_NINJA "fa-user-ninja" +#define FA_ICON_USER_NURSE "fa-user-nurse" +#define FA_ICON_USER_PEN "fa-user-pen" +#define FA_ICON_USER_PLUS "fa-user-plus" +#define FA_ICON_USER_SECRET "fa-user-secret" +#define FA_ICON_USER_SHIELD "fa-user-shield" +#define FA_ICON_USER_SLASH "fa-user-slash" +#define FA_ICON_USER_TAG "fa-user-tag" +#define FA_ICON_USER_TIE "fa-user-tie" +#define FA_ICON_USER_TIMES "fa-user-times" +#define FA_ICON_USER_XMARK "fa-user-xmark" +#define FA_ICON_USERS "fa-users" +#define FA_ICON_USERS_BETWEEN_LINES "fa-users-between-lines" +#define FA_ICON_USERS_COG "fa-users-cog" +#define FA_ICON_USERS_GEAR "fa-users-gear" +#define FA_ICON_USERS_LINE "fa-users-line" +#define FA_ICON_USERS_RAYS "fa-users-rays" +#define FA_ICON_USERS_RECTANGLE "fa-users-rectangle" +#define FA_ICON_USERS_SLASH "fa-users-slash" +#define FA_ICON_USERS_VIEWFINDER "fa-users-viewfinder" +#define FA_ICON_UTENSIL_SPOON "fa-utensil-spoon" +#define FA_ICON_UTENSILS "fa-utensils" +#define FA_ICON_V "fa-v" +#define FA_ICON_VAN_SHUTTLE "fa-van-shuttle" +#define FA_ICON_VAULT "fa-vault" +#define FA_ICON_VCARD "fa-vcard" +#define FA_ICON_VECTOR_SQUARE "fa-vector-square" +#define FA_ICON_VENUS "fa-venus" +#define FA_ICON_VENUS_DOUBLE "fa-venus-double" +#define FA_ICON_VENUS_MARS "fa-venus-mars" +#define FA_ICON_VEST "fa-vest" +#define FA_ICON_VEST_PATCHES "fa-vest-patches" +#define FA_ICON_VIAL "fa-vial" +#define FA_ICON_VIAL_CIRCLE_CHECK "fa-vial-circle-check" +#define FA_ICON_VIAL_VIRUS "fa-vial-virus" +#define FA_ICON_VIALS "fa-vials" +#define FA_ICON_VIDEO "fa-video" +#define FA_ICON_VIDEO_CAMERA "fa-video-camera" +#define FA_ICON_VIDEO_SLASH "fa-video-slash" +#define FA_ICON_VIHARA "fa-vihara" +#define FA_ICON_VIRUS "fa-virus" +#define FA_ICON_VIRUS_COVID "fa-virus-covid" +#define FA_ICON_VIRUS_COVID_SLASH "fa-virus-covid-slash" +#define FA_ICON_VIRUS_SLASH "fa-virus-slash" +#define FA_ICON_VIRUSES "fa-viruses" +#define FA_ICON_VOICEMAIL "fa-voicemail" +#define FA_ICON_VOLCANO "fa-volcano" +#define FA_ICON_VOLLEYBALL "fa-volleyball" +#define FA_ICON_VOLLEYBALL_BALL "fa-volleyball-ball" +#define FA_ICON_VOLUME_CONTROL_PHONE "fa-volume-control-phone" +#define FA_ICON_VOLUME_DOWN "fa-volume-down" +#define FA_ICON_VOLUME_HIGH "fa-volume-high" +#define FA_ICON_VOLUME_LOW "fa-volume-low" +#define FA_ICON_VOLUME_MUTE "fa-volume-mute" +#define FA_ICON_VOLUME_OFF "fa-volume-off" +#define FA_ICON_VOLUME_TIMES "fa-volume-times" +#define FA_ICON_VOLUME_UP "fa-volume-up" +#define FA_ICON_VOLUME_XMARK "fa-volume-xmark" +#define FA_ICON_VOTE_YEA "fa-vote-yea" +#define FA_ICON_VR_CARDBOARD "fa-vr-cardboard" +#define FA_ICON_W "fa-w" +#define FA_ICON_WALKIE_TALKIE "fa-walkie-talkie" +#define FA_ICON_WALKING "fa-walking" +#define FA_ICON_WALLET "fa-wallet" +#define FA_ICON_WAND_MAGIC "fa-wand-magic" +#define FA_ICON_WAND_MAGIC_SPARKLES "fa-wand-magic-sparkles" +#define FA_ICON_WAND_SPARKLES "fa-wand-sparkles" +#define FA_ICON_WAREHOUSE "fa-warehouse" +#define FA_ICON_WARNING "fa-warning" +#define FA_ICON_WATER "fa-water" +#define FA_ICON_WATER_LADDER "fa-water-ladder" +#define FA_ICON_WAVE_SQUARE "fa-wave-square" +#define FA_ICON_WEIGHT "fa-weight" +#define FA_ICON_WEIGHT_HANGING "fa-weight-hanging" +#define FA_ICON_WEIGHT_SCALE "fa-weight-scale" +#define FA_ICON_WHEAT_ALT "fa-wheat-alt" +#define FA_ICON_WHEAT_AWN "fa-wheat-awn" +#define FA_ICON_WHEAT_AWN_CIRCLE_EXCLAMATION "fa-wheat-awn-circle-exclamation" +#define FA_ICON_WHEELCHAIR "fa-wheelchair" +#define FA_ICON_WHEELCHAIR_ALT "fa-wheelchair-alt" +#define FA_ICON_WHEELCHAIR_MOVE "fa-wheelchair-move" +#define FA_ICON_WHISKEY_GLASS "fa-whiskey-glass" +#define FA_ICON_WIFI "fa-wifi" +#define FA_ICON_WIFI_3 "fa-wifi-3" +#define FA_ICON_WIFI_STRONG "fa-wifi-strong" +#define FA_ICON_WIND "fa-wind" +#define FA_ICON_WINDOW_CLOSE "fa-window-close" +#define FA_ICON_WINDOW_MAXIMIZE "fa-window-maximize" +#define FA_ICON_WINDOW_MINIMIZE "fa-window-minimize" +#define FA_ICON_WINDOW_RESTORE "fa-window-restore" +#define FA_ICON_WINE_BOTTLE "fa-wine-bottle" +#define FA_ICON_WINE_GLASS "fa-wine-glass" +#define FA_ICON_WINE_GLASS_ALT "fa-wine-glass-alt" +#define FA_ICON_WINE_GLASS_EMPTY "fa-wine-glass-empty" +#define FA_ICON_WON "fa-won" +#define FA_ICON_WON_SIGN "fa-won-sign" +#define FA_ICON_WORM "fa-worm" +#define FA_ICON_WRENCH "fa-wrench" +#define FA_ICON_X "fa-x" +#define FA_ICON_X_RAY "fa-x-ray" +#define FA_ICON_XMARK "fa-xmark" +#define FA_ICON_XMARK_CIRCLE "fa-xmark-circle" +#define FA_ICON_XMARK_SQUARE "fa-xmark-square" +#define FA_ICON_XMARKS_LINES "fa-xmarks-lines" +#define FA_ICON_Y "fa-y" +#define FA_ICON_YEN "fa-yen" +#define FA_ICON_YEN_SIGN "fa-yen-sign" +#define FA_ICON_YIN_YANG "fa-yin-yang" +#define FA_ICON_Z "fa-z" +#define FA_ICON_ZAP "fa-zap" diff --git a/code/__defines/gamemode.dm b/code/__defines/gamemode.dm index cc6b81eb8a..7b2474e6f9 100644 --- a/code/__defines/gamemode.dm +++ b/code/__defines/gamemode.dm @@ -1,10 +1,3 @@ -// Ticker game states, turns out these are equivilent to runlevels1 -#define GAME_STATE_INIT 0 // RUNLEVEL_INIT -#define GAME_STATE_PREGAME 1 // RUNLEVEL_LOBBY -#define GAME_STATE_SETTING_UP 2 // RUNLEVEL_SETUP -#define GAME_STATE_PLAYING 3 // RUNLEVEL_GAME -#define GAME_STATE_FINISHED 4 // RUNLEVEL_POSTGAME - //End game state, to manage round end. #define END_GAME_NOT_OVER 1 // Still playing normally #define END_GAME_MODE_FINISHED 2 // Mode has finished but game has not, wait for game to end too. diff --git a/code/__defines/is_helpers.dm b/code/__defines/is_helpers.dm index 472c41a38c..f30c3754d2 100644 --- a/code/__defines/is_helpers.dm +++ b/code/__defines/is_helpers.dm @@ -90,3 +90,5 @@ GLOBAL_VAR_INIT(refid_filter, TYPEID(filter(type="angular_blur"))) #define isnum_safe(x) ( isnum((x)) && !isnan((x)) && !isinf((x)) ) #define ismopable(A) (A && (A.plane <= OBJ_PLANE)) //If something can be cleaned by floor-cleaning devices such as mops or clean bots + +#define isfloorturf(A) (istype(A, /turf/simulated/floor)) diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm index 029ae1bd37..592b7a74ba 100644 --- a/code/__defines/mobs.dm +++ b/code/__defines/mobs.dm @@ -1,3 +1,11 @@ +/*ALL MOB-RELATED DEFINES THAT DON'T BELONG IN ANOTHER FILE GO HERE*/ + +//Misc mob defines + +//Ready states at roundstart for mob/dead/new_player +#define PLAYER_NOT_READY 0 +#define PLAYER_READY_TO_PLAY 1 + // /mob/var/stat things. #define CONSCIOUS 0 #define UNCONSCIOUS 1 diff --git a/code/__defines/rust_g.dm b/code/__defines/rust_g.dm index 02f01bbb79..897b323046 100644 --- a/code/__defines/rust_g.dm +++ b/code/__defines/rust_g.dm @@ -168,6 +168,9 @@ */ #define rustg_dmi_inject_metadata(path, metadata) RUSTG_CALL(RUST_G, "dmi_inject_metadata")(path, metadata) +#define rustg_create_qr_code_png(path, data) RUSTG_CALL(RUST_G, "create_qr_code_png")(path, data) +#define rustg_create_qr_code_svg(data) RUSTG_CALL(RUST_G, "create_qr_code_svg")(data) + #define rustg_file_read(fname) RUSTG_CALL(RUST_G, "file_read")(fname) #define rustg_file_exists(fname) (RUSTG_CALL(RUST_G, "file_exists")(fname) == "true") #define rustg_file_write(text, fname) RUSTG_CALL(RUST_G, "file_write")(text, fname) diff --git a/code/__defines/subsystems.dm b/code/__defines/subsystems.dm index 7585816562..63a1c91bdc 100644 --- a/code/__defines/subsystems.dm +++ b/code/__defines/subsystems.dm @@ -84,24 +84,6 @@ }\ } -// SS runlevels - -#define RUNLEVEL_INIT 0 // "Initialize Only" - Used for subsystems that should never be fired (Should also have SS_NO_FIRE set) -#define RUNLEVEL_LOBBY 1 // Initial runlevel before setup. Returns to here if setup fails. -#define RUNLEVEL_SETUP 2 // While the gamemode setup is running. I.E gameticker.setup() -#define RUNLEVEL_GAME 4 // After successful game ticker setup, while the round is running. -#define RUNLEVEL_POSTGAME 8 // When round completes but before reboot - -#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) - -GLOBAL_LIST_INIT(runlevel_flags, list( - RUNLEVEL_LOBBY, - RUNLEVEL_SETUP, - RUNLEVEL_GAME, - RUNLEVEL_POSTGAME - )) -#define RUNLEVEL_FLAG_TO_INDEX(flag) (log(2, flag) + 1) // Convert from the runlevel bitfield constants to index in runlevel_flags list - //! ### SS initialization hints /** * Negative values indicate a failure or warning of some kind, positive are good. @@ -120,59 +102,8 @@ GLOBAL_LIST_INIT(runlevel_flags, list( /// If your system doesn't need to be initialized (by being disabled or something) #define SS_INIT_NO_NEED 3 -//! ### SS initialization load orders -// Subsystem init_order, from highest priority to lowest priority -// Subsystems shutdown in the reverse of the order they initialize in -// The numbers just define the ordering, they are meaningless otherwise. -#define INIT_ORDER_SERVER_MAINT 93 -#define INIT_ORDER_ADMIN_VERBS 84 // needs to be pretty high, admins can't do much without it -#define INIT_ORDER_LOBBY 82 -#define INIT_ORDER_WEBHOOKS 50 -#define INIT_ORDER_SQLITE 41 -#define INIT_ORDER_GARBAGE 40 -#define INIT_ORDER_DBCORE 39 -#define INIT_ORDER_MEDIA_TRACKS 38 // Gotta get that lobby music up, yo -#define INIT_ORDER_INPUT 37 -#define INIT_ORDER_CHEMISTRY 35 -#define INIT_ORDER_ROBOT_SPRITES 34 -#define INIT_ORDER_VIS 32 -#define INIT_ORDER_MAPPING 25 -#define INIT_ORDER_RESEARCH 24 -#define INIT_ORDER_SOUNDS 23 -#define INIT_ORDER_INSTRUMENTS 22 -#define INIT_ORDER_DECALS 20 -#define INIT_ORDER_PLANTS 19 // Must initialize before atoms. -#define INIT_ORDER_PLANETS 18 -#define INIT_ORDER_JOB 17 -#define INIT_ORDER_ALARM 16 // Must initialize before atoms. -#define INIT_ORDER_TRANSCORE 15 -#define INIT_ORDER_ATOMS 14 -#define INIT_ORDER_HOLOMAPS 13 -#define INIT_ORDER_POIS 12 -#define INIT_ORDER_MACHINES 10 -#define INIT_ORDER_STARMOVER 4 -#define INIT_ORDER_SHUTTLES 3 -#define INIT_ORDER_TIMER 1 -#define INIT_ORDER_DEFAULT 0 -#define INIT_ORDER_LIGHTING 0 -#define INIT_ORDER_AIR -1 -#define INIT_ORDER_ASSETS -5 -#define INIT_ORDER_NIGHTSHIFT -6 -#define INIT_ORDER_OVERLAY -7 -#define INIT_ORDER_XENOARCH -20 -#define INIT_ORDER_CIRCUIT -21 -#define INIT_ORDER_AI -22 -#define INIT_ORDER_AI_FAST -23 -#define INIT_ORDER_GAME_MASTER -24 -#define INIT_ORDER_PERSISTENCE -25 -#define INIT_ORDER_SKYBOX -30 //Visual only, irrelevant to gameplay, but needs to be late enough to have overmap populated fully -#define INIT_ORDER_TICKER -50 -#define INIT_ORDER_MAPRENAME -60 //Initiating after Ticker to ensure everything is loaded and everything we rely on us working -#define INIT_ORDER_WIKI -61 -#define INIT_ORDER_ATC -70 -#define INIT_ORDER_LOOT -80 -#define INIT_ORDER_STATPANELS -98 -#define INIT_ORDER_CHAT -100 //Should be last to ensure chat remains smooth during init. +/// Successfully initialized, BUT do not announce it to players (generally to hide game mechanics it would otherwise spoil) +#define SS_INIT_NO_MESSAGE 4 // Subsystem fire priority, from lowest to highest priority // If the subsystem isn't listed here it's either DEFAULT or PROCESS (if it's a processing subsystem child) @@ -193,6 +124,7 @@ GLOBAL_LIST_INIT(runlevel_flags, list( #define FIRE_PRIORITY_AI 10 #define FIRE_PRIORITY_STARMOVER 11 #define FIRE_PRIORITY_GARBAGE 15 +#define FIRE_PRIORITY_DATABASE 16 #define FIRE_PRIORITY_ASSETS 20 #define FIRE_PRIORITY_POIS 20 #define FIRE_PRIORITY_ALARM 20 @@ -217,6 +149,36 @@ GLOBAL_LIST_INIT(runlevel_flags, list( #define FIRE_PRIORITY_DELAYED_VERBS 950 #define FIRE_PRIORITY_INPUT 1000 // This must always always be the max highest priority. Player input must never be lost. + +// SS runlevels + +#define RUNLEVEL_LOBBY (1<<0) +#define RUNLEVEL_SETUP (1<<1) +#define RUNLEVEL_GAME (1<<2) +#define RUNLEVEL_POSTGAME (1<<3) + +#define RUNLEVELS_DEFAULT (RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME) + +//SSticker.current_state values +/// Game is loading +#define GAME_STATE_STARTUP 0 +/// Game is loaded and in pregame lobby +#define GAME_STATE_PREGAME 1 +/// Game is attempting to start the round +#define GAME_STATE_SETTING_UP 2 +/// Game has round in progress +#define GAME_STATE_PLAYING 3 +/// Game has round finished +#define GAME_STATE_FINISHED 4 + +// Used for SSticker.force_ending +/// Default, round is not being forced to end. +#define END_ROUND_AS_NORMAL 0 +/// End the round now as normal +#define FORCE_END_ROUND 1 +/// For admin forcing roundend, can be used to distinguish the two +#define ADMIN_FORCE_END_ROUND 2 + /** Create a new timer and add it to the queue. * Arguments: @@ -227,5 +189,8 @@ GLOBAL_LIST_INIT(runlevel_flags, list( */ #define addtimer(args...) _addtimer(args, file = __FILE__, line = __LINE__) +// The change in the world's time from the subsystem's last fire in seconds. +#define DELTA_WORLD_TIME(ss) ((world.time - ss.last_fire) * 0.1) + /// The timer key used to know how long subsystem initialization takes #define SS_INIT_TIMER_KEY "ss_init" diff --git a/code/__defines/time.dm b/code/__defines/time.dm index 857e2fa155..b580e01a1b 100644 --- a/code/__defines/time.dm +++ b/code/__defines/time.dm @@ -34,3 +34,101 @@ #define MS2DS(T) ((T) MILLISECONDS) #define DS2MS(T) ((T) * 100) + +/*Timezones*/ + +/// Line Islands Time +#define TIMEZONE_LINT 14 + +// Chatham Daylight Time +#define TIMEZONE_CHADT 13.75 + +/// Tokelau Time +#define TIMEZONE_TKT 13 + +/// Tonga Time +#define TIMEZONE_TOT 13 + +/// New Zealand Daylight Time +#define TIMEZONE_NZDT 13 + +/// New Zealand Standard Time +#define TIMEZONE_NZST 12 + +/// Norfolk Time +#define TIMEZONE_NFT 11 + +/// Lord Howe Standard Time +#define TIMEZONE_LHST 10.5 + +/// Australian Eastern Standard Time +#define TIMEZONE_AEST 10 + +/// Australian Central Standard Time +#define TIMEZONE_ACST 9.5 + +/// Australian Central Western Standard Time +#define TIMEZONE_ACWST 8.75 + +/// Australian Western Standard Time +#define TIMEZONE_AWST 8 + +/// Christmas Island Time +#define TIMEZONE_CXT 7 + +/// Cocos Islands Time +#define TIMEZONE_CCT 6.5 + +/// Central European Summer Time +#define TIMEZONE_CEST 2 + +/// Coordinated Universal Time +#define TIMEZONE_UTC 0 + +/// Eastern Daylight Time +#define TIMEZONE_EDT -4 + +/// Eastern Standard Time +#define TIMEZONE_EST -5 + +/// Central Daylight Time +#define TIMEZONE_CDT -5 + +/// Central Standard Time +#define TIMEZONE_CST -6 + +/// Mountain Daylight Time +#define TIMEZONE_MDT -6 + +/// Mountain Standard Time +#define TIMEZONE_MST -7 + +/// Pacific Daylight Time +#define TIMEZONE_PDT -7 + +/// Pacific Standard Time +#define TIMEZONE_PST -8 + +/// Alaska Daylight Time +#define TIMEZONE_AKDT -8 + +/// Alaska Standard Time +#define TIMEZONE_AKST -9 + +/// Hawaii-Aleutian Daylight Time +#define TIMEZONE_HDT -9 + +/// Hawaii Standard Time +#define TIMEZONE_HST -10 + +/// Cook Island Time +#define TIMEZONE_CKT -10 + +/// Niue Time +#define TIMEZONE_NUT -11 + +/// Anywhere on Earth +#define TIMEZONE_ANYWHERE_ON_EARTH -12 + +/// in the grim darkness of the thirteenth space station there is no timezones, since they break IC game times. Use this for all IC/round time values +#define NO_TIMEZONE 0 diff --git a/code/__defines/tracy.dm b/code/__defines/tracy.dm new file mode 100644 index 0000000000..0a9ab8d68e --- /dev/null +++ b/code/__defines/tracy.dm @@ -0,0 +1,5 @@ +/// File path used for the "enable tracy next round" functionality +#define TRACY_ENABLE_PATH "data/enable_tracy" + +/// The DLL path for byond-tracy. +#define TRACY_DLL_PATH (world.system_type == MS_WINDOWS ? "prof.dll" : "./libprof.so") diff --git a/code/__defines/traits.dm b/code/__defines/traits.dm index 9edc8ba505..e5fadf1e0e 100644 --- a/code/__defines/traits.dm +++ b/code/__defines/traits.dm @@ -28,6 +28,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai */ #define TRAIT_XENO_HOST "xeno_host" //Tracks whether we're gonna be a baby alien's mummy. #define TRAIT_MIMING "miming" //Tracks whether you're a mime or not. +/// "Magic" trait that blocks the mob from moving or interacting with anything. Used for transient stuff like mob transformations or incorporality in special cases. +/// Will block movement, `Life()` (!!!), and other stuff based on the mob. +#define TRAIT_NO_TRANSFORM "block_transformations" /* #define TRAIT_STUNIMMUNE "stun_immunity" #define TRAIT_STUNRESISTANCE "stun_resistance" diff --git a/code/__defines/turfs.dm b/code/__defines/turfs.dm index 57e1e37c45..40329df795 100644 --- a/code/__defines/turfs.dm +++ b/code/__defines/turfs.dm @@ -38,3 +38,6 @@ locate(max(CENTER.x-(H_RADIUS),1), max(CENTER.y-(V_RADIUS),1), CENTER.z), \ locate(min(CENTER.x+(H_RADIUS),world.maxx), min(CENTER.y+(V_RADIUS),world.maxy), CENTER.z) \ ) + +///Returns all turfs in a zlevel +#define Z_TURFS(ZLEVEL) block(1, 1, ZLEVEL, world.maxx, world.maxy, ZLEVEL) diff --git a/code/__defines/unit_tests.dm b/code/__defines/unit_tests.dm deleted file mode 100644 index 111bbe60bf..0000000000 --- a/code/__defines/unit_tests.dm +++ /dev/null @@ -1,5 +0,0 @@ -#define ASCII_ESC ascii2text(27) -#define ASCII_RED "[ASCII_ESC]\[31m" -#define ASCII_GREEN "[ASCII_ESC]\[32m" -#define ASCII_YELLOW "[ASCII_ESC]\[33m" -#define ASCII_RESET "[ASCII_ESC]\[0m" diff --git a/code/_global_vars/__unsorted.dm b/code/_global_vars/__unsorted.dm index b12fff4d1a..0a79909d95 100644 --- a/code/_global_vars/__unsorted.dm +++ b/code/_global_vars/__unsorted.dm @@ -83,3 +83,5 @@ GLOBAL_DATUM_INIT(buildmode_hud, /icon, icon('icons/misc/buildmode.dmi')) //Keyed list for caching icons so you don't need to make them for records, IDs, etc all separately. //Could be useful for AI impersonation or something at some point? GLOBAL_LIST_EMPTY(cached_character_icons) + +GLOBAL_VAR_INIT(triai, FALSE) diff --git a/code/_global_vars/lists/icons.dm b/code/_global_vars/lists/icons.dm new file mode 100644 index 0000000000..31efd7e468 --- /dev/null +++ b/code/_global_vars/lists/icons.dm @@ -0,0 +1,4 @@ +/// Cache of the states of icon files +GLOBAL_LIST_EMPTY(icon_states_cache) +/// Cache of the states of icon files, stored associatively with TRUE for lookup +GLOBAL_LIST_EMPTY(icon_states_cache_lookup) diff --git a/code/_global_vars/lists/mapping.dm b/code/_global_vars/lists/mapping.dm index df4d9c90ae..3a11711e66 100644 --- a/code/_global_vars/lists/mapping.dm +++ b/code/_global_vars/lists/mapping.dm @@ -1,8 +1,10 @@ +#define ALL_POSSIBLE_DIRS list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST) // Use this define as Subsystem PreInit comes after GLOB + GLOBAL_LIST_INIT(cardinal, list(NORTH, SOUTH, EAST, WEST)) GLOBAL_LIST_INIT(cardinalz, list(NORTH, SOUTH, EAST, WEST, UP, DOWN)) GLOBAL_LIST_INIT(cornerdirs, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) GLOBAL_LIST_INIT(cornerdirsz, list(NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST, NORTH|UP, EAST|UP, WEST|UP, SOUTH|UP, NORTH|DOWN, EAST|DOWN, WEST|DOWN, SOUTH|DOWN)) -GLOBAL_LIST_INIT(alldirs, list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST)) +GLOBAL_LIST_INIT(alldirs, ALL_POSSIBLE_DIRS) GLOBAL_LIST_INIT(reverse_dir, list( // reverse_dir[dir] = reverse of dir 2, 1, 3, 8, 10, 9, 11, 4, 6, 5, 7, 12, 14, 13, 15, 32, 34, 33, 35, 40, 42, 41, 43, 36, 38, 37, 39, 44, 46, 45, 47, diff --git a/code/_global_vars/lists/names.dm b/code/_global_vars/lists/names.dm new file mode 100644 index 0000000000..75aeab8cf2 --- /dev/null +++ b/code/_global_vars/lists/names.dm @@ -0,0 +1,16 @@ +GLOBAL_LIST_INIT(ai_names, world.file2list("strings/names/ai.txt")) +GLOBAL_LIST_INIT(wizard_first, world.file2list("strings/names/wizardfirst.txt")) +GLOBAL_LIST_INIT(wizard_second, world.file2list("strings/names/wizardsecond.txt")) +GLOBAL_LIST_INIT(ninja_titles, world.file2list("strings/names/ninjatitle.txt")) +GLOBAL_LIST_INIT(ninja_names, world.file2list("strings/names/ninjaname.txt")) +GLOBAL_LIST_INIT(commando_names, world.file2list("strings/names/death_commando.txt")) +GLOBAL_LIST_INIT(first_names, world.file2list("strings/names/first.txt")) +GLOBAL_LIST_INIT(first_names_male, world.file2list("strings/names/first_male.txt")) +GLOBAL_LIST_INIT(first_names_female, world.file2list("strings/names/first_female.txt")) +GLOBAL_LIST_INIT(last_names, world.file2list("strings/names/last.txt")) +GLOBAL_LIST_INIT(clown_names, world.file2list("strings/names/clown.txt")) + +GLOBAL_LIST_INIT(verbs, world.file2list("strings/names/verbs.txt")) +GLOBAL_LIST_INIT(adjectives, world.file2list("strings/names/adjectives.txt")) +//loaded on startup because of " +//would include in rsc if ' was used diff --git a/code/_global_vars/radio.dm b/code/_global_vars/radio.dm new file mode 100644 index 0000000000..956811207d --- /dev/null +++ b/code/_global_vars/radio.dm @@ -0,0 +1,184 @@ + +/* +Frequency range: 1200 to 1600 +Radiochat range: 1441 to 1489 (most devices refuse to be tune to other frequency, even during mapmaking) +Radio: +1459 - standard radio chat +1351 - Science +1353 - Command +1355 - Medical +1357 - Engineering +1359 - Security +1341 - deathsquad +1443 - Confession Intercom +1347 - Cargo techs +1349 - Service people +Devices: +1451 - tracking implant +1457 - RSD default +On the map: +1311 for prison shuttle console (in fact, it is not used) +1433 for engine components +1435 for status displays +1437 for atmospherics/fire alerts +1439 for air pumps, air scrubbers, atmo control +1441 for atmospherics - supply tanks +1443 for atmospherics - distribution loop/mixed air tank +1445 for bot nav beacons +1447 for mulebot, secbot and ed209 control +1449 for airlock controls, electropack, magnets +1451 for toxin lab access +1453 for engineering access +1455 for AI access +*/ + +var/const/RADIO_LOW_FREQ = 1200 +var/const/PUBLIC_LOW_FREQ = 1441 +var/const/PUBLIC_HIGH_FREQ = 1489 +var/const/RADIO_HIGH_FREQ = 1600 + +var/const/BOT_FREQ = 1447 +var/const/COMM_FREQ = 1353 +var/const/ERT_FREQ = 1345 +var/const/AI_FREQ = 1343 +var/const/DTH_FREQ = 1341 +var/const/SYND_FREQ = 1213 +var/const/RAID_FREQ = 1277 +var/const/ENT_FREQ = 1461 //entertainment frequency. This is not a diona exclusive frequency. + +// department channels +var/const/PUB_FREQ = 1459 +var/const/SEC_FREQ = 1359 +var/const/ENG_FREQ = 1357 +var/const/MED_FREQ = 1355 +var/const/SCI_FREQ = 1351 +var/const/SRV_FREQ = 1349 +var/const/SUP_FREQ = 1347 +var/const/EXP_FREQ = 1361 + +// internal department channels +var/const/MED_I_FREQ = 1485 +var/const/SEC_I_FREQ = 1475 + +var/const/TALON_FREQ = 1363 //VOREStation Add +var/const/CSN_FREQ = 1365 //VOREStation Add + +var/list/radiochannels = list( + CHANNEL_COMMON = PUB_FREQ, + CHANNEL_SCIENCE = SCI_FREQ, + CHANNEL_COMMAND = COMM_FREQ, + CHANNEL_MEDICAL = MED_FREQ, + CHANNEL_ENGINEERING = ENG_FREQ, + CHANNEL_SECURITY = SEC_FREQ, + CHANNEL_RESPONSE_TEAM = ERT_FREQ, + CHANNEL_SPECIAL_OPS = DTH_FREQ, + CHANNEL_MERCENARY = SYND_FREQ, + CHANNEL_RAIDER = RAID_FREQ, + CHANNEL_SUPPLY = SUP_FREQ, + CHANNEL_SERVICE = SRV_FREQ, + CHANNEL_EXPLORATION = EXP_FREQ, + CHANNEL_AI_PRIVATE = AI_FREQ, + CHANNEL_ENTERTAINMENT = ENT_FREQ, + CHANNEL_MEDICAL_1 = MED_I_FREQ, + CHANNEL_SECURITY_1 = SEC_I_FREQ, + CHANNEL_TALON = TALON_FREQ, //VOREStation Add + CHANNEL_CASINO = CSN_FREQ, +) + +// Hey, if anyone ever needs to update tgui/packages/tgui/constants.js with new radio channels +// I've kept this around just for you. +/* /client/verb/generate_tgui_radio_constants() + set name = "Generate TGUI Radio Constants" + set category = "Generate TGUI Radio Constants" + var/list/channel_info = list() + for(var/i in RADIO_LOW_FREQ to RADIO_HIGH_FREQ) + for(var/key in radiochannels) + if(i == radiochannels[key]) + channel_info.Add(list(list("name" = key, "freq" = i, "color" = frequency_span_class(i)))) + for(var/list/channel in channel_info) + switch(channel["color"]) + if("deadsay") channel["color"] = "#530FAD" + if("radio") channel["color"] = "#008000" + if("deptradio") channel["color"] = "#ff00ff" + if("newscaster") channel["color"] = "#750000" + if("comradio") channel["color"] = "#193A7A" + if("syndradio") channel["color"] = "#6D3F40" + if("centradio") channel["color"] = "#5C5C8A" + if("airadio") channel["color"] = "#FF00FF" + if("entradio") channel["color"] = "#339966" + if("secradio") channel["color"] = "#A30000" + if("engradio") channel["color"] = "#A66300" + if("medradio") channel["color"] = "#008160" + if("sciradio") channel["color"] = "#993399" + if("supradio") channel["color"] = "#5F4519" + if("srvradio") channel["color"] = "#6eaa2c" + if("expradio") channel["color"] = "#555555" + to_chat(src, json_encode(channel_info)) */ + + +// central command channels, i.e deathsquid & response teams +var/list/CENT_FREQS = list(ERT_FREQ, DTH_FREQ) + +// Antag channels, i.e. Syndicate +var/list/ANTAG_FREQS = list(SYND_FREQ, RAID_FREQ) + +//Department channels, arranged lexically +var/list/DEPT_FREQS = list(AI_FREQ, COMM_FREQ, ENG_FREQ, ENT_FREQ, MED_FREQ, SEC_FREQ, SCI_FREQ, SRV_FREQ, SUP_FREQ) + +var/list/OFFMAP_FREQS = list(TALON_FREQ, CSN_FREQ) //VOREStation Add + +/proc/frequency_span_class(var/frequency) + // Antags! + if (frequency in ANTAG_FREQS) + return "syndradio" + // CentCom channels (deathsquid and ert) + if(frequency in CENT_FREQS) + return "centradio" + // command channel + if(frequency == COMM_FREQ) + return "comradio" + // AI private channel + if(frequency == AI_FREQ) + return "airadio" + // department radio formatting (poorly optimized, ugh) + if(frequency == SEC_FREQ) + return "secradio" + if (frequency == ENG_FREQ) + return "engradio" + if(frequency == SCI_FREQ) + return "sciradio" + if(frequency == MED_FREQ) + return "medradio" + if(frequency == SUP_FREQ) // cargo + return "supradio" + if(frequency == SRV_FREQ) // service + return "srvradio" + if(frequency == EXP_FREQ) // explorer + return "expradio" + if(frequency == ENT_FREQ) // entertainment + return "entradio" + if(frequency in DEPT_FREQS) + return "deptradio" + //VOREStation Add + if(frequency in OFFMAP_FREQS) + return "expradio" + //VOREStation Add End + return "radio" + +/* filters */ +//When devices register with the radio controller, they might register under a certain filter. +//Other devices can then choose to send signals to only those devices that belong to a particular filter. +//This is done for performance, so we don't send signals to lots of machines unnecessarily. + +//This filter is special because devices belonging to default also recieve signals sent to any other filter. +var/const/RADIO_DEFAULT = "radio_default" + +var/const/RADIO_TO_AIRALARM = "radio_airalarm" //air alarms +var/const/RADIO_FROM_AIRALARM = "radio_airalarm_rcvr" //devices interested in recieving signals from air alarms +var/const/RADIO_CHAT = "radio_telecoms" +var/const/RADIO_ATMOSIA = "radio_atmos" +var/const/RADIO_NAVBEACONS = "radio_navbeacon" +var/const/RADIO_AIRLOCK = "radio_airlock" +var/const/RADIO_SECBOT = "radio_secbot" +var/const/RADIO_MULEBOT = "radio_mulebot" +var/const/RADIO_MAGNETS = "radio_magnet" diff --git a/code/_helpers/_lists.dm b/code/_helpers/_lists.dm index 754f1a065e..8e9e3adac8 100644 --- a/code/_helpers/_lists.dm +++ b/code/_helpers/_lists.dm @@ -1017,3 +1017,32 @@ GLOBAL_LIST_EMPTY(json_cache) ///uses sort_list() but uses the var's name specifically. This should probably be using mergeAtom() instead /proc/sort_names(list/list_to_sort, order=1) return sortTim(list_to_sort.Copy(), order >= 0 ? GLOBAL_PROC_REF(cmp_name_asc) : GLOBAL_PROC_REF(cmp_name_dsc)) + +/// Compares 2 lists, returns TRUE if they are the same +/proc/deep_compare_list(list/list_1, list/list_2) + if(list_1 == list_2) + return TRUE + + if(!islist(list_1) || !islist(list_2)) + return FALSE + + if(list_1.len != list_2.len) + return FALSE + + for(var/i in 1 to list_1.len) + var/key_1 = list_1[i] + var/key_2 = list_2[i] + if (islist(key_1) && islist(key_2)) + if(!deep_compare_list(key_1, key_2)) + return FALSE + else if(key_1 != key_2) + return FALSE + if(istext(key_1) || islist(key_1) || ispath(key_1) || isdatum(key_1) || key_1 == world) + var/value_1 = list_1[key_1] + var/value_2 = list_2[key_1] + if (islist(value_1) && islist(value_2)) + if(!deep_compare_list(value_1, value_2)) + return FALSE + else if(value_1 != value_2) + return FALSE + return TRUE diff --git a/code/_helpers/game.dm b/code/_helpers/game.dm index 072bcc006e..4b6c4a5715 100644 --- a/code/_helpers/game.dm +++ b/code/_helpers/game.dm @@ -647,10 +647,15 @@ /proc/SecondsToTicks(var/seconds) return seconds * 10 -/proc/window_flash(var/client_or_usr) - if (!client_or_usr) +///Flash the window of a player +/proc/window_flash(client/flashed_client, ignorepref = FALSE) + if(ismob(flashed_client)) + var/mob/player_mob = flashed_client + if(player_mob.client) + flashed_client = player_mob.client + if(!flashed_client || (!flashed_client.prefs.read_preference(/datum/preference/toggle/window_flashing) && !ignorepref)) return - winset(client_or_usr, "mainwindow", "flash=5") + winset(flashed_client, "mainwindow", "flash=5") /** * Get a bounding box of a list of atoms. diff --git a/code/_helpers/icons.dm b/code/_helpers/icons.dm index 52189e6b21..3f26fb903c 100644 --- a/code/_helpers/icons.dm +++ b/code/_helpers/icons.dm @@ -815,6 +815,26 @@ GLOBAL_LIST_EMPTY(cached_examine_icons) var/icon/I = getFlatIcon(thing, force_south = force_south) return icon2html(I, target, sourceonly = sourceonly) +/// Returns rustg-parsed metadata for an icon, universal icon, or DMI file, using cached values where possible +/// Returns null if passed object is not a filepath or icon with a valid DMI file +/proc/icon_metadata(file) + var/static/list/icon_metadata_cache = list() + if(istype(file, /datum/universal_icon)) + var/datum/universal_icon/u_icon = file + file = u_icon.icon_file + var/file_string = "[file]" + if(!istext(file) && !(isfile(file) && length(file_string))) + return null + var/list/cached_metadata = icon_metadata_cache[file_string] + if(islist(cached_metadata)) + return cached_metadata + var/list/metadata_result = rustg_dmi_read_metadata(file_string) + if(!islist(metadata_result) || !length(metadata_result)) + CRASH("Error while reading DMI metadata for path '[file_string]': [metadata_result]") + else + icon_metadata_cache[file_string] = metadata_result + return metadata_result + /// Checks whether a given icon state exists in a given icon file. If `file` and `state` both exist, /// this will return `TRUE` - otherwise, it will return `FALSE`. /// @@ -836,3 +856,25 @@ GLOBAL_LIST_EMPTY(cached_examine_icons) icon_states_cache[file][istate] = TRUE return !isnull(icon_states_cache[file][state]) + +/// Cached, rustg-based alternative to icon_states() +/proc/icon_states_fast(file) + if(isnull(file)) + return null + if(isnull(GLOB.icon_states_cache[file])) + compile_icon_states_cache(file) + return GLOB.icon_states_cache[file] + +/proc/compile_icon_states_cache(file) + GLOB.icon_states_cache[file] = list() + GLOB.icon_states_cache_lookup[file] = list() + // Try to use rustg first + var/list/metadata = icon_metadata(file) + if(islist(metadata) && islist(metadata["states"])) + for(var/list/state_data as anything in metadata["states"]) + GLOB.icon_states_cache[file] += state_data["name"] + GLOB.icon_states_cache_lookup[file][state_data["name"]] = TRUE + else // Otherwise, we have to use the slower BYOND proc + for(var/istate in icon_states(file)) + GLOB.icon_states_cache[file] += istate + GLOB.icon_states_cache_lookup[file][istate] = TRUE diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm index 7848162199..c410493a47 100644 --- a/code/_helpers/logging.dm +++ b/code/_helpers/logging.dm @@ -177,7 +177,7 @@ WRITE_LOG(GLOB.diary, "TOPIC: [text]") /proc/log_unit_test(text) - to_world_log("## UNIT_TEST: [text]") + to_world_log("## UNIT_TESTS: [text]") #ifdef REFERENCE_TRACKING_LOG #define log_reftracker(msg) WRITE_LOG(GLOB.diary, "## REF SEARCH [msg]") diff --git a/code/_helpers/mobs.dm b/code/_helpers/mobs.dm index 3c98dc1511..b8a4ecbb14 100644 --- a/code/_helpers/mobs.dm +++ b/code/_helpers/mobs.dm @@ -93,9 +93,9 @@ if(!current_species || current_species.name_language == null) if(gender==FEMALE) - return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) else - return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) else return current_species.get_random_name(gender) diff --git a/code/_helpers/names.dm b/code/_helpers/names.dm index ba23ca7ab9..da476e1888 100644 --- a/code/_helpers/names.dm +++ b/code/_helpers/names.dm @@ -195,9 +195,9 @@ GLOBAL_VAR(syndicate_code_response) //Code response for traitors. if(names.len&&prob(70)) code_phrase += pick(names) else - code_phrase += pick(pick(first_names_male,first_names_female)) + code_phrase += pick(pick(GLOB.first_names_male, GLOB.first_names_female)) code_phrase += " " - code_phrase += pick(last_names) + code_phrase += pick(GLOB.last_names) if(2) code_phrase += pick(GLOB.joblist)//Returns a job. safety -= 1 @@ -213,9 +213,9 @@ GLOBAL_VAR(syndicate_code_response) //Code response for traitors. if(1) code_phrase += pick(nouns) if(2) - code_phrase += pick(adjectives) + code_phrase += pick(GLOB.adjectives) if(3) - code_phrase += pick(verbs) + code_phrase += pick(GLOB.verbs) if(words==1) code_phrase += "." else diff --git a/code/_helpers/roundend.dm b/code/_helpers/roundend.dm new file mode 100644 index 0000000000..3bd043cd58 --- /dev/null +++ b/code/_helpers/roundend.dm @@ -0,0 +1,106 @@ +/datum/controller/subsystem/ticker/proc/declare_completion(was_forced = END_ROUND_AS_NORMAL) + set waitfor = FALSE + + for(var/datum/callback/roundend_callbacks as anything in round_end_events) + roundend_callbacks.InvokeAsync() + LAZYCLEARLIST(round_end_events) + + to_world(span_filter_system("


A round of [mode.name] has ended!

")) + for(var/mob/Player in GLOB.player_list) + if(Player.mind && !isnewplayer(Player)) + if(Player.stat != DEAD) + var/turf/playerTurf = get_turf(Player) + if(emergency_shuttle.departed && emergency_shuttle.evac) + if(isNotAdminLevel(playerTurf.z)) + to_chat(Player, span_filter_system(span_blue(span_bold("You survived the round, but remained on [station_name()] as [Player.real_name].")))) + else + to_chat(Player, span_filter_system(span_green(span_bold("You managed to survive the events on [station_name()] as [Player.real_name].")))) + else if(isAdminLevel(playerTurf.z)) + to_chat(Player, span_filter_system(span_green(span_bold("You successfully underwent crew transfer after events on [station_name()] as [Player.real_name].")))) + else if(issilicon(Player)) + to_chat(Player, span_filter_system(span_green(span_bold("You remain operational after the events on [station_name()] as [Player.real_name].")))) + else + to_chat(Player, span_filter_system(span_blue(span_bold("You missed the crew transfer after the events on [station_name()] as [Player.real_name].")))) + else + if(isobserver(Player)) + var/mob/observer/dead/O = Player + if(!O.started_as_observer) + to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]...")))) + else + to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]...")))) + to_world(span_filter_system("
")) + + for (var/mob/living/silicon/ai/aiPlayer in GLOB.mob_list) + if (aiPlayer.stat != 2) + to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws at the end of the round were:"))) // VOREStation edit + else + to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws when it was deactivated were:"))) // VOREStation edit + aiPlayer.show_laws(1) + + if (aiPlayer.connected_robots.len) + var/robolist = span_bold("The AI's loyal minions were:") + " " + for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots) + robolist += "[robo.name][robo.stat?" (Deactivated), ":", "]" // VOREStation edit + to_world(span_filter_system("[robolist]")) + + var/dronecount = 0 + + for (var/mob/living/silicon/robot/robo in GLOB.mob_list) + + if(istype(robo, /mob/living/silicon/robot/platform)) + var/mob/living/silicon/robot/platform/tank = robo + if(!tank.has_had_player) + continue + + if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm)) + dronecount++ + continue + + if (!robo.connected_ai) + if (robo.stat != 2) + to_world(span_filter_system(span_bold("[robo.name] survived as an AI-less stationbound synthetic! Its laws were:"))) // VOREStation edit + else + to_world(span_filter_system(span_bold("[robo.name] was unable to survive the rigors of being a stationbound synthetic without an AI. Its laws were:"))) // VOREStation edit + + if(robo) //How the hell do we lose robo between here and the world messages directly above this? + robo.laws.show_laws(world) + + if(dronecount) + to_world(span_filter_system(span_bold("There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round."))) + + mode.declare_completion()//To declare normal completion. + + //Ask the event manager to print round end information + SSevents.RoundEnd() + + //Print a list of antagonists to the server log + var/list/total_antagonists = list() + //Look into all mobs in world, dead or alive + for(var/datum/mind/Mind in minds) + var/temprole = Mind.special_role + if(temprole) //if they are an antagonist of some sort. + if(temprole in total_antagonists) //If the role exists already, add the name to it + total_antagonists[temprole] += ", [Mind.name]([Mind.key])" + else + total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob + total_antagonists[temprole] += ": [Mind.name]([Mind.key])" + + //Now print them all into the log! + log_game("Antagonists at round end were...") + for(var/i in total_antagonists) + log_game("[i]s[total_antagonists[i]].") + + SSdbcore.SetRoundEnd() + + sleep(5 SECONDS) + ready_for_reboot = TRUE + standard_reboot() + +/datum/controller/subsystem/ticker/proc/standard_reboot() + if(ready_for_reboot) + if(mode.station_was_nuked) + Reboot("Station destroyed by Nuclear Device.", "nuke") + else + Reboot("Round ended.", "proper completion") + else + CRASH("Attempted standard reboot without ticker roundend completion") diff --git a/code/_helpers/sorts/comparators.dm b/code/_helpers/sorts/comparators.dm index c2d3ffb506..8358330704 100644 --- a/code/_helpers/sorts/comparators.dm +++ b/code/_helpers/sorts/comparators.dm @@ -13,7 +13,10 @@ // Sorts subsystems by init_order /proc/cmp_subsystem_init(datum/controller/subsystem/a, datum/controller/subsystem/b) - return initial(b.init_order) - initial(a.init_order) //uses initial() so it can be used on types + return a.init_order - b.init_order + +/proc/cmp_subsystem_init_stage(datum/controller/subsystem/a, datum/controller/subsystem/b) + return initial(a.init_stage) - initial(b.init_stage) // Sorts subsystems by priority /proc/cmp_subsystem_priority(datum/controller/subsystem/a, datum/controller/subsystem/b) diff --git a/code/_helpers/time.dm b/code/_helpers/time.dm index b1d372f435..99c0e61409 100644 --- a/code/_helpers/time.dm +++ b/code/_helpers/time.dm @@ -121,24 +121,30 @@ GLOBAL_VAR_INIT(round_start_time, 0) rollovercheck_last_timeofday = world.timeofday return midnight_rollovers -//Increases delay as the server gets more overloaded, -//as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful +///Increases delay as the server gets more overloaded, as sleeps aren't cheap and sleeping only to wake up and sleep again is wasteful #define DELTA_CALC max(((max(TICK_USAGE, world.cpu) / 100) * max(Master.sleep_delta-1,1)), 1) -//returns the number of ticks slept +///returns the number of ticks slept /proc/stoplag(initial_delay) - if (!Master || !(Master.current_runlevel & RUNLEVELS_DEFAULT)) + if (!Master || Master.init_stage_completed < INITSTAGE_MAX) sleep(world.tick_lag) return 1 if (!initial_delay) initial_delay = world.tick_lag +// Unit tests are not the normal environemnt. The mc can get absolutely thigh crushed, and sleeping procs running for ages is much more common +// We don't want spurious hard deletes off this, so let's only sleep for the requested period of time here yeah? +#ifdef UNIT_TESTS + sleep(initial_delay) + return CEILING(DS2TICKS(initial_delay), 1) +#else . = 0 var/i = DS2TICKS(initial_delay) do - . += CEILING(i*DELTA_CALC, 1) - sleep(i*world.tick_lag*DELTA_CALC) + . += CEILING(i * DELTA_CALC, 1) + sleep(i * world.tick_lag * DELTA_CALC) i *= 2 while (TICK_USAGE > min(TICK_LIMIT_TO_RUN, Master.current_ticklimit)) +#endif #undef DELTA_CALC diff --git a/code/_onclick/click.dm b/code/_onclick/click.dm index dd9f2fa50a..6d82e50d67 100644 --- a/code/_onclick/click.dm +++ b/code/_onclick/click.dm @@ -194,7 +194,7 @@ if(is_incorporeal()) return 0 - if(!ticker) + if(!SSticker) to_chat(src, "You cannot attack people before the game has started.") return 0 diff --git a/code/_onclick/hud/fullscreen.dm b/code/_onclick/hud/fullscreen.dm index c6d8a916b7..2a0b8191d9 100644 --- a/code/_onclick/hud/fullscreen.dm +++ b/code/_onclick/hud/fullscreen.dm @@ -143,6 +143,15 @@ show_when_dead = TRUE plane = PLANE_PLAYER_HUD_ABOVE +/obj/screen/fullscreen/cinematic_backdrop + icon = 'icons/hud/screen_gen.dmi' + screen_loc = "WEST,SOUTH to EAST,NORTH" + icon_state = "flash" + plane = SPLASHSCREEN_PLANE + layer = CINEMATIC_LAYER + color = COLOR_BLACK + show_when_dead = TRUE + /obj/screen/fullscreen/lighting_backdrop icon = 'icons/mob/screen_gen.dmi' icon_state = "flash" diff --git a/code/controllers/admin.dm b/code/controllers/admin.dm index 69ec6de5cb..e74cfbed04 100644 --- a/code/controllers/admin.dm +++ b/code/controllers/admin.dm @@ -79,7 +79,7 @@ ADMIN_VERB(debug_controller, R_DEBUG, "Debug Controller", "Debug the various per //Goon PS stuff, and other yet-to-be-subsystem things. options["LEGACY: master_controller"] = master_controller options["LEGACY: job_master"] = job_master - options["LEGACY: radio_controller"] = radio_controller + options["LEGACY: SSradio"] = SSradio options["LEGACY: emergency_shuttle"] = emergency_shuttle options["LEGACY: paiController"] = paiController options["LEGACY: cameranet"] = cameranet diff --git a/code/controllers/communications.dm b/code/controllers/communications.dm deleted file mode 100644 index 3bccb4b944..0000000000 --- a/code/controllers/communications.dm +++ /dev/null @@ -1,394 +0,0 @@ -/* - HOW IT WORKS - - The radio_controller is a global object maintaining all radio transmissions, think about it as about "ether". - Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way. - procs: - - add_object(obj/device as obj, var/new_frequency as num, var/radio_filter as text|null = null) - Adds listening object. - parameters: - device - device receiving signals, must have proc receive_signal (see description below). - one device may listen several frequencies, but not same frequency twice. - new_frequency - see possibly frequencies below; - radio_filter - thing for optimization. Optional, but recommended. - All filters should be consolidated in this file, see defines later. - Device without listening filter will receive all signals (on specified frequency). - Device with filter will receive any signals sent without filter. - Device with filter will not receive any signals sent with different filter. - returns: - Reference to frequency object. - - remove_object (obj/device, old_frequency) - Obliviously, after calling this proc, device will not receive any signals on old_frequency. - Other frequencies will left unaffected. - - return_frequency(var/frequency as num) - returns: - Reference to frequency object. Use it if you need to send and do not need to listen. - - radio_frequency is a global object maintaining list of devices that listening specific frequency. - procs: - - post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter as text|null = null, var/range as num|null = null) - Sends signal to all devices that wants such signal. - parameters: - source - object, emitted signal. Usually, devices will not receive their own signals. - signal - see description below. - radio_filter - described above. - range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels. - - obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param) - Handler from received signals. By default does nothing. Define your own for your object. - Avoid of sending signals directly from this proc, use spawn(-1). DO NOT use sleep() here or call procs that sleep please. If you must, use spawn() - parameters: - signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return! - receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO. - TRANSMISSION_WIRE is currently unused. - receive_param - for TRANSMISSION_RADIO here comes frequency. - - datum/signal - vars: - source - an object that emitted signal. Used for debug and bearing. - data - list with transmitting data. Usual use pattern: - data["msg"] = "hello world" - encryption - Some number symbolizing "encryption key". - Note that game actually do not use any cryptography here. - If receiving object don't know right key, it must ignore encrypted signal in its receive_signal. - -*/ - -/* -Frequency range: 1200 to 1600 -Radiochat range: 1441 to 1489 (most devices refuse to be tune to other frequency, even during mapmaking) - -Radio: -1459 - standard radio chat -1351 - Science -1353 - Command -1355 - Medical -1357 - Engineering -1359 - Security -1341 - deathsquad -1443 - Confession Intercom -1347 - Cargo techs -1349 - Service people - -Devices: -1451 - tracking implant -1457 - RSD default - -On the map: -1311 for prison shuttle console (in fact, it is not used) -1433 for engine components -1435 for status displays -1437 for atmospherics/fire alerts -1439 for air pumps, air scrubbers, atmo control -1441 for atmospherics - supply tanks -1443 for atmospherics - distribution loop/mixed air tank -1445 for bot nav beacons -1447 for mulebot, secbot and ed209 control -1449 for airlock controls, electropack, magnets -1451 for toxin lab access -1453 for engineering access -1455 for AI access -*/ - -var/const/RADIO_LOW_FREQ = 1200 -var/const/PUBLIC_LOW_FREQ = 1441 -var/const/PUBLIC_HIGH_FREQ = 1489 -var/const/RADIO_HIGH_FREQ = 1600 - -var/const/BOT_FREQ = 1447 -var/const/COMM_FREQ = 1353 -var/const/ERT_FREQ = 1345 -var/const/AI_FREQ = 1343 -var/const/DTH_FREQ = 1341 -var/const/SYND_FREQ = 1213 -var/const/RAID_FREQ = 1277 -var/const/ENT_FREQ = 1461 //entertainment frequency. This is not a diona exclusive frequency. - -// department channels -var/const/PUB_FREQ = 1459 -var/const/SEC_FREQ = 1359 -var/const/ENG_FREQ = 1357 -var/const/MED_FREQ = 1355 -var/const/SCI_FREQ = 1351 -var/const/SRV_FREQ = 1349 -var/const/SUP_FREQ = 1347 -var/const/EXP_FREQ = 1361 - -// internal department channels -var/const/MED_I_FREQ = 1485 -var/const/SEC_I_FREQ = 1475 - -var/const/TALON_FREQ = 1363 //VOREStation Add -var/const/CSN_FREQ = 1365 //VOREStation Add - -var/list/radiochannels = list( - CHANNEL_COMMON = PUB_FREQ, - CHANNEL_SCIENCE = SCI_FREQ, - CHANNEL_COMMAND = COMM_FREQ, - CHANNEL_MEDICAL = MED_FREQ, - CHANNEL_ENGINEERING = ENG_FREQ, - CHANNEL_SECURITY = SEC_FREQ, - CHANNEL_RESPONSE_TEAM = ERT_FREQ, - CHANNEL_SPECIAL_OPS = DTH_FREQ, - CHANNEL_MERCENARY = SYND_FREQ, - CHANNEL_RAIDER = RAID_FREQ, - CHANNEL_SUPPLY = SUP_FREQ, - CHANNEL_SERVICE = SRV_FREQ, - CHANNEL_EXPLORATION = EXP_FREQ, - CHANNEL_AI_PRIVATE = AI_FREQ, - CHANNEL_ENTERTAINMENT = ENT_FREQ, - CHANNEL_MEDICAL_1 = MED_I_FREQ, - CHANNEL_SECURITY_1 = SEC_I_FREQ, - CHANNEL_TALON = TALON_FREQ, //VOREStation Add - CHANNEL_CASINO = CSN_FREQ, -) - -// Hey, if anyone ever needs to update tgui/packages/tgui/constants.js with new radio channels -// I've kept this around just for you. -/* /client/verb/generate_tgui_radio_constants() - set name = "Generate TGUI Radio Constants" - set category = "Generate TGUI Radio Constants" - - var/list/channel_info = list() - - for(var/i in RADIO_LOW_FREQ to RADIO_HIGH_FREQ) - for(var/key in radiochannels) - if(i == radiochannels[key]) - channel_info.Add(list(list("name" = key, "freq" = i, "color" = frequency_span_class(i)))) - - for(var/list/channel in channel_info) - switch(channel["color"]) - if("deadsay") channel["color"] = "#530FAD" - if("radio") channel["color"] = "#008000" - if("deptradio") channel["color"] = "#ff00ff" - if("newscaster") channel["color"] = "#750000" - if("comradio") channel["color"] = "#193A7A" - if("syndradio") channel["color"] = "#6D3F40" - if("centradio") channel["color"] = "#5C5C8A" - if("airadio") channel["color"] = "#FF00FF" - if("entradio") channel["color"] = "#339966" - if("secradio") channel["color"] = "#A30000" - if("engradio") channel["color"] = "#A66300" - if("medradio") channel["color"] = "#008160" - if("sciradio") channel["color"] = "#993399" - if("supradio") channel["color"] = "#5F4519" - if("srvradio") channel["color"] = "#6eaa2c" - if("expradio") channel["color"] = "#555555" - - to_chat(src, json_encode(channel_info)) */ - - -// central command channels, i.e deathsquid & response teams -var/list/CENT_FREQS = list(ERT_FREQ, DTH_FREQ) - -// Antag channels, i.e. Syndicate -var/list/ANTAG_FREQS = list(SYND_FREQ, RAID_FREQ) - -//Department channels, arranged lexically -var/list/DEPT_FREQS = list(AI_FREQ, COMM_FREQ, ENG_FREQ, ENT_FREQ, MED_FREQ, SEC_FREQ, SCI_FREQ, SRV_FREQ, SUP_FREQ) - -var/list/OFFMAP_FREQS = list(TALON_FREQ, CSN_FREQ) //VOREStation Add - -/proc/frequency_span_class(var/frequency) - // Antags! - if (frequency in ANTAG_FREQS) - return "syndradio" - // CentCom channels (deathsquid and ert) - if(frequency in CENT_FREQS) - return "centradio" - // command channel - if(frequency == COMM_FREQ) - return "comradio" - // AI private channel - if(frequency == AI_FREQ) - return "airadio" - // department radio formatting (poorly optimized, ugh) - if(frequency == SEC_FREQ) - return "secradio" - if (frequency == ENG_FREQ) - return "engradio" - if(frequency == SCI_FREQ) - return "sciradio" - if(frequency == MED_FREQ) - return "medradio" - if(frequency == SUP_FREQ) // cargo - return "supradio" - if(frequency == SRV_FREQ) // service - return "srvradio" - if(frequency == EXP_FREQ) // explorer - return "expradio" - if(frequency == ENT_FREQ) // entertainment - return "entradio" - if(frequency in DEPT_FREQS) - return "deptradio" - //VOREStation Add - if(frequency in OFFMAP_FREQS) - return "expradio" - //VOREStation Add End - return "radio" - -/* filters */ -//When devices register with the radio controller, they might register under a certain filter. -//Other devices can then choose to send signals to only those devices that belong to a particular filter. -//This is done for performance, so we don't send signals to lots of machines unnecessarily. - -//This filter is special because devices belonging to default also recieve signals sent to any other filter. -var/const/RADIO_DEFAULT = "radio_default" - -var/const/RADIO_TO_AIRALARM = "radio_airalarm" //air alarms -var/const/RADIO_FROM_AIRALARM = "radio_airalarm_rcvr" //devices interested in recieving signals from air alarms -var/const/RADIO_CHAT = "radio_telecoms" -var/const/RADIO_ATMOSIA = "radio_atmos" -var/const/RADIO_NAVBEACONS = "radio_navbeacon" -var/const/RADIO_AIRLOCK = "radio_airlock" -var/const/RADIO_SECBOT = "radio_secbot" -var/const/RADIO_MULEBOT = "radio_mulebot" -var/const/RADIO_MAGNETS = "radio_magnet" - -var/global/datum/controller/radio/radio_controller - -/hook/startup/proc/createRadioController() - radio_controller = new /datum/controller/radio() - return 1 - -//callback used by objects to react to incoming radio signals -/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param) - return null - -//The global radio controller -/datum/controller/radio - var/list/datum/radio_frequency/frequencies = list() - -/datum/controller/radio/proc/add_object(obj/device as obj, var/new_frequency as num, var/radio_filter = null as text|null) - var/f_text = num2text(new_frequency) - var/datum/radio_frequency/frequency = frequencies[f_text] - - if(!frequency) - frequency = new - frequency.frequency = new_frequency - frequencies[f_text] = frequency - - frequency.add_listener(device, radio_filter) - return frequency - -/datum/controller/radio/proc/remove_object(obj/device, old_frequency) - var/f_text = num2text(old_frequency) - var/datum/radio_frequency/frequency = frequencies[f_text] - - if(frequency) - frequency.remove_listener(device) - - if(frequency.devices.len == 0) - qdel(frequency) - frequencies -= f_text - - return 1 - -/datum/controller/radio/proc/return_frequency(var/new_frequency as num) - var/f_text = num2text(new_frequency) - var/datum/radio_frequency/frequency = frequencies[f_text] - - if(!frequency) - frequency = new - frequency.frequency = new_frequency - frequencies[f_text] = frequency - - return frequency - -/datum/radio_frequency - var/frequency as num - var/list/list/obj/devices = list() - -/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter = null as text|null, var/range = null as num|null) - var/turf/start_point - if(range) - start_point = get_turf(source) - if(!start_point) - qdel(signal) - return 0 - if (radio_filter) - send_to_filter(source, signal, radio_filter, start_point, range) - send_to_filter(source, signal, RADIO_DEFAULT, start_point, range) - else - //Broadcast the signal to everyone! - for (var/next_filter in devices) - send_to_filter(source, signal, next_filter, start_point, range) - -//Sends a signal to all machines belonging to a given filter. Should be called by post_signal() -/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/radio_filter, var/turf/start_point = null, var/range = null) - if (range && !start_point) - return - - for(var/obj/device in devices[radio_filter]) - if(device == source) - continue - if(range) - var/turf/end_point = get_turf(device) - if(!end_point) - continue - if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range) - continue - - device.receive_signal(signal, TRANSMISSION_RADIO, frequency) - -/datum/radio_frequency/proc/add_listener(obj/device as obj, var/radio_filter as text|null) - if (!radio_filter) - radio_filter = RADIO_DEFAULT - //log_admin("add_listener(device=[device],radio_filter=[radio_filter]) frequency=[frequency]") - var/list/obj/devices_line = devices[radio_filter] - if (!devices_line) - devices_line = new - devices[radio_filter] = devices_line - devices_line+=device -// var/list/obj/devices_line___ = devices[filter_str] -// var/l = devices_line___.len - //log_admin("DEBUG: devices_line.len=[devices_line.len]") - //log_admin("DEBUG: devices(filter_str).len=[l]") - -/datum/radio_frequency/proc/remove_listener(obj/device) - for (var/devices_filter in devices) - var/list/devices_line = devices[devices_filter] - devices_line-=device - while (null in devices_line) - devices_line -= null - if (devices_line.len==0) - devices -= devices_filter - -/datum/signal - var/obj/source - - var/transmission_method = 0 //unused at the moment - //0 = wire - //1 = radio transmission - //2 = subspace transmission - - var/list/data = list() - var/encryption - - var/frequency = 0 - -/datum/signal/proc/copy_from(datum/signal/model) - source = model.source - transmission_method = model.transmission_method - data = model.data - encryption = model.encryption - frequency = model.frequency - -/datum/signal/proc/debug_print() - if (source) - . = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n" - else - . = "signal = {source = '[source]' ()\n" - for (var/i in data) - . += "data\[\"[i]\"\] = \"[data[i]]\"\n" - if(islist(data[i])) - var/list/L = data[i] - for(var/t in L) - . += "data\[\"[i]\"\] list has: [t]" diff --git a/code/controllers/configuration/configuration.dm b/code/controllers/configuration/configuration.dm index 1cba676a0c..a2f5b2c9d1 100644 --- a/code/controllers/configuration/configuration.dm +++ b/code/controllers/configuration/configuration.dm @@ -24,6 +24,9 @@ /// If the configuration is loaded var/loaded = FALSE + /// If a reload is in progress + var/reload_in_progress = FALSE + /// A regex that matches words blocked IC var/static/regex/ic_filter_regex @@ -64,13 +67,23 @@ var/static/list/configuration_errors /datum/controller/configuration/proc/admin_reload() - if(IsAdminAdvancedProcCall()) + if(IsAdminAdvancedProcCall() || !PreConfigReload()) return log_admin("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") message_admins("[key_name_admin(usr)] has forcefully reloaded the configuration from disk.") full_wipe() Load(world.params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) +/datum/controller/configuration/proc/PreConfigReload() + if(reload_in_progress) + to_chat(usr, span_warning("Another user is already reloading the config!")) + return FALSE + + reload_in_progress = TRUE + world.TgsTriggerEvent("tg-PreConfigReload", wait_for_completion = TRUE) + reload_in_progress = FALSE + return TRUE + /datum/controller/configuration/proc/Load(_directory) if(IsAdminAdvancedProcCall()) //If admin proccall is detected down the line it will horribly break everything. return diff --git a/code/controllers/configuration/entries/game_options.dm b/code/controllers/configuration/entries/game_options.dm index 64e5058fdb..86f3c78790 100644 --- a/code/controllers/configuration/entries/game_options.dm +++ b/code/controllers/configuration/entries/game_options.dm @@ -34,6 +34,8 @@ /datum/config_entry/number/walk_speed default = 0 +/datum/config_entry/flag/force_random_names + ///Mob specific modifiers. NOTE: These will affect different mob types in different ways /datum/config_entry/number/human_delay default = 0 diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index ca15716dbe..7b9ae13596 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -1,6 +1,18 @@ /// server name (the name of the game window) /datum/config_entry/string/servername +/// Countdown between lobby and the round starting. +/datum/config_entry/number/lobby_countdown + default = 120 + integer = FALSE + min_val = 0 + +/// Post round murder death kill countdown. +/datum/config_entry/number/round_end_countdown + default = 25 + integer = FALSE + min_val = 0 + /// generate numeric suffix based on server port /datum/config_entry/flag/server_suffix @@ -222,9 +234,6 @@ // var/static/Tickcomp = 0 // FIXME: Unused -/// use socket_talk to communicate with other processes -/datum/config_entry/flag/socket_talk - // var/static/list/resource_urls = null // FIXME: Unused /// Ghosts can turn on Antagovision to see a HUD of who is the bad guys this round. @@ -787,6 +796,60 @@ /datum/config_entry/flag/discord_ahelps_all default = FALSE +/datum/config_entry/number/mc_tick_rate/base_mc_tick_rate + integer = FALSE + default = 1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_tick_rate + integer = FALSE + default = 1.1 + +/datum/config_entry/number/mc_tick_rate/high_pop_mc_mode_amount + default = 65 + +/datum/config_entry/number/mc_tick_rate/disable_high_pop_mc_mode_amount + default = 60 + +/datum/config_entry/number/mc_tick_rate + abstract_type = /datum/config_entry/number/mc_tick_rate + +/datum/config_entry/number/mc_tick_rate/ValidateAndSet(str_val) + . = ..() + if (.) + Master.UpdateTickRate() + +/datum/config_entry/flag/resume_after_initializations + +/datum/config_entry/flag/resume_after_initializations/ValidateAndSet(str_val) + . = ..() + if(. && MC_RUNNING()) + world.sleep_offline = !config_entry_value + /datum/config_entry/number/rounds_until_hard_restart default = -1 min_val = 0 + +/datum/config_entry/flag/auto_profile + +/datum/config_entry/number/profiler_interval + default = 300 SECONDS + +/datum/config_entry/number/drift_dump_threshold + default = 4 SECONDS + +/datum/config_entry/number/drift_profile_delay + default = 15 SECONDS + +/datum/config_entry/flag/forbid_all_profiling + +/datum/config_entry/flag/forbid_admin_profiling + +/datum/config_entry/flag/toast_notification_on_init + +/// If admins with +DEBUG can initialize byond-tracy midround. +/datum/config_entry/flag/allow_tracy_start + protection = CONFIG_ENTRY_LOCKED + +/// If admins with +DEBUG can queue byond-tracy to run the next round. +/datum/config_entry/flag/allow_tracy_queue + protection = CONFIG_ENTRY_LOCKED diff --git a/code/controllers/emergency_shuttle_controller.dm b/code/controllers/emergency_shuttle_controller.dm index 71e087d6a7..226557bf7e 100644 --- a/code/controllers/emergency_shuttle_controller.dm +++ b/code/controllers/emergency_shuttle_controller.dm @@ -161,8 +161,8 @@ /datum/emergency_shuttle_controller/proc/get_shuttle_prep_time() // During mutiny rounds, the shuttle takes twice as long. - if(ticker && ticker.mode) - return SHUTTLE_PREPTIME * ticker.mode.shuttle_delay + if(SSticker && SSticker.mode) + return SHUTTLE_PREPTIME * SSticker.mode.shuttle_delay return SHUTTLE_PREPTIME diff --git a/code/controllers/failsafe.dm b/code/controllers/failsafe.dm index e92e30079b..f645733804 100644 --- a/code/controllers/failsafe.dm +++ b/code/controllers/failsafe.dm @@ -24,6 +24,9 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe) var/running = TRUE /datum/controller/failsafe/New() + // Ensure usr is null, to prevent any potential weirdness resulting from the failsafe having a usr if it's manually restarted. + usr = null + // Highlander-style: there can only be one! Kill off the old and replace it with the new. if(Failsafe != src) if(istype(Failsafe)) @@ -149,7 +152,7 @@ GLOBAL_REAL(Failsafe, /datum/controller/failsafe) /proc/recover_all_SS_and_recreate_master() del(Master) var/list/subsytem_types = subtypesof(/datum/controller/subsystem) - sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init)) + sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init_stage)) for(var/I in subsytem_types) new I . = Recreate_MC() diff --git a/code/controllers/globals.dm b/code/controllers/globals.dm index bf9c7e1e9c..b809fb7bd5 100644 --- a/code/controllers/globals.dm +++ b/code/controllers/globals.dm @@ -1,9 +1,10 @@ +// See initialization order in /code/game/world.dm GLOBAL_REAL(GLOB, /datum/controller/global_vars) /datum/controller/global_vars name = "Global Variables" - var/list/gvars_datum_protected_varlist + var/static/list/gvars_datum_protected_varlist var/list/gvars_datum_in_built_vars var/list/gvars_datum_init_order @@ -13,30 +14,24 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars) GLOB = src var/datum/controller/exclude_these = new - gvars_datum_in_built_vars = exclude_these.vars + list("gvars_datum_protected_varlist", "gvars_datum_in_built_vars", "gvars_datum_init_order") + // I know this is dumb but the nested vars list hangs a ref to the datum. This fixes that + // I have an issue report open, lummox has not responded. It might be a FeaTuRE + // Sooo we gotta be dumb + var/list/controller_vars = exclude_these.vars.Copy() + controller_vars["vars"] = null + gvars_datum_in_built_vars = controller_vars + list(NAMEOF(src, gvars_datum_protected_varlist), NAMEOF(src, gvars_datum_in_built_vars), NAMEOF(src, gvars_datum_init_order)) - log_world("[vars.len - gvars_datum_in_built_vars.len] global variables") + QDEL_IN(exclude_these, 0) //signal logging isn't ready - Initialize(exclude_these) + Initialize() /datum/controller/global_vars/Destroy(force) - stack_trace("There was an attempt to qdel the global vars holder!") - if(!force) - return QDEL_HINT_LETMELIVE - - QDEL_NULL(statclick) - gvars_datum_protected_varlist.Cut() - gvars_datum_in_built_vars.Cut() - - GLOB = null - - return ..() + // This is done to prevent an exploit where admins can get around protected vars + SHOULD_CALL_PARENT(FALSE) + return QDEL_HINT_IWILLGC /datum/controller/global_vars/stat_entry(msg) - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - msg = "GLOB: [statclick.update("Edit")]" + msg = "Edit" return msg /datum/controller/global_vars/vv_edit_var(var_name, var_value) @@ -44,6 +39,14 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars) return FALSE return ..() +/* +/datum/controller/global_vars/vv_get_var(var_name) + switch(var_name) + if (NAMEOF(src, vars)) + return debug_variable(var_name, list(), 0, src) + return debug_variable(var_name, vars[var_name], 0, src, display_flags = VV_ALWAYS_CONTRACT_LIST) +*/ + /datum/controller/global_vars/Initialize() gvars_datum_init_order = list() gvars_datum_protected_varlist = list(NAMEOF(src, gvars_datum_protected_varlist) = TRUE) @@ -55,8 +58,8 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars) var/list/expected_global_procs = vars - gvars_datum_in_built_vars for(var/I in global_procs) expected_global_procs -= replacetext("[I]", "InitGlobal", "") - var/english_missing = expected_global_procs.Join(", ") - log_world("Missing procs: [english_missing]") + log_world("Missing procs: [expected_global_procs.Join(", ")]") + for(var/I in global_procs) var/start_tick = world.time call(src, I)() @@ -64,4 +67,5 @@ GLOBAL_REAL(GLOB, /datum/controller/global_vars) if(end_tick - start_tick) warning("Global [replacetext("[I]", "InitGlobal", "")] slept during initialization!") + // Someone make it so this call isn't necessary populate_legacy_globals() diff --git a/code/controllers/master.dm b/code/controllers/master.dm index 6a562eb319..8730d0c27f 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -8,7 +8,7 @@ **/ // See initialization order in /code/game/world.dm -GLOBAL_REAL(Master, /datum/controller/master) = new +GLOBAL_REAL(Master, /datum/controller/master) /datum/controller/master name = "Master" @@ -25,6 +25,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /// List of subsystems to process(). var/list/subsystems + ///Most recent init stage to complete init. + var/static/init_stage_completed + // Vars for keeping track of tick drift. var/init_timeofday var/init_time @@ -41,9 +44,9 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /// 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 @@ -53,11 +56,16 @@ GLOBAL_REAL(Master, /datum/controller/master) = new 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/sleep_offline_after_initializations = TRUE /// During initialization, will be the instanced subsytem that is currently initializing. /// Outside of initialization, returns null. var/current_initializing_subsystem = null + /// The last decisecond we force dumped profiling information + /// Used to avoid spamming profile reads since they can be expensive (string memes) + var/last_profiled = 0 + var/static/restart_clear = 0 var/static/restart_timeout = 0 var/static/restart_count = 0 @@ -66,13 +74,25 @@ GLOBAL_REAL(Master, /datum/controller/master) = new ///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 + var/static/current_ticklimit = TICK_LIMIT_RUNNING + + /// Whether the Overview UI will update as fast as possible for viewers. + var/overview_fast_update = FALSE + /// Enables rolling usage averaging + var/use_rolling_usage = FALSE + /// How long to run our rolling usage averaging + var/rolling_usage_length = 5 SECONDS /datum/controller/master/New() + // Ensure usr is null, to prevent any potential weirdness resulting from the MC having a usr if it's manually restarted. + usr = null + + if(!config) + config = new // Highlander-style: there can only be one! Kill off the old and replace it with the new. if(!random_seed) - #ifdef UNIT_TEST + #ifdef UNIT_TESTS random_seed = 29051994 // How about 22475? #else random_seed = rand(1, 1e9) @@ -82,15 +102,29 @@ 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 - var/list/subsytem_types = subtypesof(/datum/controller/subsystem) - sortTim(subsytem_types, GLOBAL_PROC_REF(cmp_subsystem_init)) - for(var/I in subsytem_types) - _subsystems += new I - Master = src + //Code used for first master on game boot or if existing master got deleted + Master = src + var/list/subsystem_types = subtypesof(/datum/controller/subsystem) + sortTim(subsystem_types, GLOBAL_PROC_REF(cmp_subsystem_init_stage)) + + //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 subsystem_types) + var/ss_idx = existing_subsystems.Find(I) + if (ss_idx) + _subsystems += existing_subsystems[ss_idx] + else + _subsystems += new I if(!GLOB) new /datum/controller/global_vars @@ -111,8 +145,109 @@ GLOBAL_REAL(Master, /datum/controller/master) = new ss.Shutdown() log_world("Shutdown complete") +ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "View the current states of the Subsystem Controllers.", ADMIN_CATEGORY_DEBUG) + Master.tgui_interact(user.mob) + +/datum/controller/master/tgui_status(mob/user, datum/tgui_state/state) + if(!user.client?.holder?.check_for_rights(R_SERVER|R_DEBUG)) + return STATUS_CLOSE + return STATUS_INTERACTIVE + +/datum/controller/master/tgui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(isnull(ui)) + ui = new /datum/tgui(user, src, "ControllerOverview") + ui.open() + use_rolling_usage = TRUE + +/datum/controller/master/tgui_close(mob/user) + var/valid_found = FALSE + for(var/datum/tgui/open_ui as anything in open_tguis) + if(open_ui.user == user) + continue + valid_found = TRUE + if(!valid_found) + use_rolling_usage = FALSE + return ..() + +/datum/controller/master/tgui_data(mob/user) + var/list/data = list() + + var/list/subsystem_data = list() + for(var/datum/controller/subsystem/subsystem as anything in subsystems) + var/list/rolling_usage = subsystem.rolling_usage + subsystem.prune_rolling_usage() + + // Then we sum + var/sum = 0 + for(var/i in 2 to length(rolling_usage) step 2) + sum += rolling_usage[i] + var/average = sum / DS2TICKS(rolling_usage_length) + + subsystem_data += list(list( + "name" = subsystem.name, + "ref" = REF(subsystem), + "init_order" = subsystem.init_order, + "last_fire" = subsystem.last_fire, + "next_fire" = subsystem.next_fire, + "can_fire" = subsystem.can_fire, + "doesnt_fire" = !!(subsystem.flags & SS_NO_FIRE), + "cost_ms" = subsystem.cost, + "tick_usage" = subsystem.tick_usage, + "usage_per_tick" = average, + "tick_overrun" = subsystem.tick_overrun, + "initialized" = subsystem.initialized, + "initialization_failure_message" = subsystem.initialization_failure_message, + )) + data["subsystems"] = subsystem_data + data["world_time"] = world.time + data["map_cpu"] = world.map_cpu + data["fast_update"] = overview_fast_update + data["rolling_length"] = rolling_usage_length + + return data + +/datum/controller/master/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) + if(..()) + return TRUE + + switch(action) + if("toggle_fast_update") + overview_fast_update = !overview_fast_update + return TRUE + + if("set_rolling_length") + var/length = text2num(params["rolling_length"]) + if(!length || length < 0) + return + rolling_usage_length = length SECONDS + return TRUE + + if("view_variables") + var/datum/controller/subsystem/subsystem = locate(params["ref"]) in subsystems + if(isnull(subsystem)) + to_chat(ui.user, span_warning("Failed to locate subsystem.")) + return + SSadmin_verbs.dynamic_invoke_verb(ui.user, /datum/admin_verb/debug_variables, subsystem) + return TRUE + +/datum/controller/master/proc/check_and_perform_fast_update() + PRIVATE_PROC(TRUE) + set waitfor = FALSE + + + if(!overview_fast_update) + return + + var/static/already_updating = FALSE + if(already_updating) + return + already_updating = TRUE + SStgui.update_uis(src) + already_updating = FALSE + // 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) @@ -123,7 +258,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 @@ -132,18 +268,23 @@ GLOBAL_REAL(Master, /datum/controller/master) = new /datum/controller/master/Recover() - var/msg = "## DEBUG: [time2text(world.timeofday)] MC restarted. Reports:\n" - for (var/varname in Master.vars) - switch (varname) - if("name", "tag", "bestF", "type", "parent_type", "vars", "statclick") // Built-in junk. - continue - else - var/varval = Master.vars[varname] - if (istype(varval, /datum)) // Check if it has a type var. - var/datum/D = varval - msg += "\t [varname] = [D]([D.type])\n" - else - msg += "\t [varname] = [varval]\n" + var/msg = "## DEBUG: [time2text(world.timeofday, "DDD MMM DD hh:mm:ss YYYY", TIMEZONE_UTC)] MC restarted. Reports:\n" + var/list/master_attributes = Master.vars + var/list/filtered_variables = list( + NAMEOF(src, name), + NAMEOF(src, parent_type), + NAMEOF(src, statclick), + NAMEOF(src, tag), + NAMEOF(src, type), + NAMEOF(src, vars), + ) + for (var/varname in master_attributes - filtered_variables) + var/varval = master_attributes[varname] + if (isdatum(varval)) // Check if it has a type var. + var/datum/D = varval + msg += "\t [varname] = [D]([D.type])\n" + else + msg += "\t [varname] = [varval]\n" log_world(msg) var/datum/controller/subsystem/BadBoy = Master.last_type_processed @@ -153,88 +294,183 @@ GLOBAL_REAL(Master, /datum/controller/master) = new LAZYINITLIST(BadBoy.failure_strikes) switch(++BadBoy.failure_strikes[BadBoy.type]) if(2) - msg = "MC Notice: The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again." + msg = "The [BadBoy.name] subsystem was the last to fire for 2 controller restarts. It will be recovered now and disabled if it happens again." FireHim = TRUE - BadBoy.fail() if(3) - msg = "MC Notice: The [BadBoy.name] subsystem seems to be destabilizing the MC and will be offlined." + msg = "The [BadBoy.name] subsystem seems to be destabilizing the MC and will be put offline." BadBoy.flags |= SS_NO_FIRE - BadBoy.critfail() if(msg) - log_game(msg) - message_admins(span_boldannounce("[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_world(span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING")) - Initialize(20, TRUE) - + to_chat(world, span_boldannounce("The Master Controller is having some issues, we will need to re-initialize EVERYTHING")) + Initialize(20, TRUE, FALSE) // 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 its Initialize() /datum/controller/master/Initialize(delay, init_sss, tgs_prime) set waitfor = 0 if(delay) sleep(delay) - if(tgs_prime) - world.TgsInitializationComplete() - if(init_sss) init_subtypes(/datum/controller/subsystem, subsystems) - to_chat(world, span_boldannounce("MC: Initializing subsystems...")) + init_stage_completed = 0 + var/mc_started = FALSE - // Sort subsystems by init_order, so they initialize in the correct order. - sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_init)) + to_chat(world, span_boldannounce("Initializing subsystems..."), MESSAGE_TYPE_DEBUG) + var/list/stage_sorted_subsystems = new(INITSTAGE_MAX) + for (var/i in 1 to INITSTAGE_MAX) + stage_sorted_subsystems[i] = list() + + var/list/type_to_subsystem = list() + for(var/datum/controller/subsystem/subsystem as anything in subsystems) + type_to_subsystem[subsystem.type] = subsystem + + // Allows subsystems to declare other subsystems that must initialize after them. + for(var/datum/controller/subsystem/subsystem as anything in subsystems) + for(var/dependent_type as anything in subsystem.dependents) + if(!ispath(dependent_type, /datum/controller/subsystem)) + stack_trace("ERROR: MC: subsystem `[subsystem.type]` has an invalid dependent: `[dependent_type]`. Skipping") + continue + var/datum/controller/subsystem/dependent = type_to_subsystem[dependent_type] + dependent.dependencies |= subsystem.type + subsystem.dependents = list() + + // Constructs a reverse-dependency graph. + for(var/datum/controller/subsystem/subsystem as anything in subsystems) + for(var/dependency_type as anything in subsystem.dependencies) + if(!ispath(dependency_type, /datum/controller/subsystem)) + stack_trace("ERROR: MC: subsystem `[subsystem.type]` has an invalid dependency: `[dependency_type]`. Skipping") + continue + var/datum/controller/subsystem/dependency = type_to_subsystem[dependency_type] + // Not a foolproof failsafe, likely to only prevent any immediate issues if this is only triggered once. + if(subsystem.init_stage < dependency.init_stage) + stack_trace("ERROR: MC: subsystem `[subsystem.type]` has an init_stage before one of its dependencies (Dependency: `[dependency.type]`, [subsystem.init_stage] < [dependency.init_stage])! Setting init_stage to [dependency.init_stage]") + subsystem.init_stage = dependency.init_stage + dependency.dependents += subsystem + + // Topological sorting algorithm + var/list/counts = new(subsystems.len) + var/list/unsorted_subsystems = list() + var/index = 1 + for(var/datum/controller/subsystem/subsystem as anything in subsystems) + counts[index] = length(subsystem.dependencies) + subsystem.ordering_id = index + if(counts[index] == 0) + unsorted_subsystems += subsystem + index += 1 + + var/list/sorted_subsystems = list() + while(length(unsorted_subsystems) > 0) + var/datum/controller/subsystem/sub = unsorted_subsystems[unsorted_subsystems.len] + unsorted_subsystems.len-- + sorted_subsystems += sub + for(var/datum/controller/subsystem/dependent as anything in sub.dependents) + counts[dependent.ordering_id] -= 1 + if(counts[dependent.ordering_id] == 0) + unsorted_subsystems += dependent + // Topological sorting algorithm end + + if(length(subsystems) != length(sorted_subsystems)) + var/list/circular_dependency = subsystems.Copy() - sorted_subsystems + var/list/debug_msg = list() + var/list/usr_msg = list() + for(var/datum/controller/subsystem/subsystem as anything in circular_dependency) + usr_msg += "[subsystem.name]" + + var/list/datum/controller/subsystem/nodes = list(circular_dependency[1]) + var/list/loop = list() + while(length(nodes) > 0) + var/datum/controller/subsystem/node = nodes[nodes.len] + nodes.len-- + if(node in loop) + loop += node + break + loop += node + for(var/datum/controller/subsystem/connected as anything in node.dependencies) + nodes += type_to_subsystem[connected] + + var/loop_position = 0 + for(var/datum/controller/subsystem/node in loop) + if(node == loop[loop.len]) + break + loop_position++ + if(loop_position != 0) + loop.Cut(1, loop_position + 1) + + for(var/datum/controller/subsystem/subsystem as anything in loop) + debug_msg += "[subsystem.name]" + + // Can't initialize them if they have circular dependencies, there's no real failsafe here. + stack_trace("ERROR: CRITICAL: MC: The following subsystems have circular dependencies: [jointext(debug_msg, " -> ")]") + to_chat(world, span_bolddanger("CRITICAL: Failed to initialize [jointext(usr_msg, ", ")]"), MESSAGE_TYPE_DEBUG) + + for (var/datum/controller/subsystem/subsystem as anything in sorted_subsystems) + var/subsystem_init_stage = subsystem.init_stage + if (!isnum(subsystem_init_stage) || subsystem_init_stage < 1 || subsystem_init_stage > INITSTAGE_MAX || round(subsystem_init_stage) != subsystem_init_stage) + stack_trace("ERROR: MC: subsystem `[subsystem.type]` has invalid init_stage: `[subsystem_init_stage]`. Setting to `[INITSTAGE_MAX]`") + subsystem_init_stage = subsystem.init_stage = INITSTAGE_MAX + stage_sorted_subsystems[subsystem_init_stage] += subsystem + + // Sort subsystems by display setting for easy access. + var/evaluated_order = 1 + sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_display)) var/start_timeofday = REALTIMEOFDAY - // 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) - continue - init_subsystem(SS) - CHECK_TICK - current_initializing_subsystem = null - current_ticklimit = TICK_LIMIT_RUNNING + for (var/current_init_stage in 1 to INITSTAGE_MAX) + + // Initialize subsystems. + for (var/datum/controller/subsystem/subsystem in stage_sorted_subsystems[current_init_stage]) + subsystem.init_order = evaluated_order + evaluated_order++ + init_subsystem(subsystem) + + CHECK_TICK + current_initializing_subsystem = null + init_stage_completed = current_init_stage + if (!mc_started) + mc_started = TRUE + if (!current_runlevel) + SetRunLevel(1) // Intentionally not using the defines here because the MC doesn't care about them + // Loop. + Master.StartProcessing(0) + var/time = (REALTIMEOFDAY - start_timeofday) / 10 - var/msg = "MC: Initializations complete within [time] second[time == 1 ? "" : "s"]!" - to_chat(world, span_boldannounce("[msg]")) + + + var/msg = "Initializations complete within [time] second[time == 1 ? "" : "s"]!" + to_chat(world, span_boldannounce("[msg]"), MESSAGE_TYPE_DEBUG) log_world(msg) - // FIXME: TGS <-> Discord communication; sending message to a TGS chat channel - // send2chat("Server Initialization completed! - Took [time] second[time == 1 ? "" : "s"].", "bot announce") + if(world.system_type == MS_WINDOWS && CONFIG_GET(flag/toast_notification_on_init) && !length(GLOB.clients)) + world.shelleo("start /min powershell -ExecutionPolicy Bypass -File tools/initToast/initToast.ps1 -name \"[world.name]\" -icon %CD%\\icons\\virgoicon_16.png -port [world.port]") - if (!current_runlevel) - SetRunLevel(RUNLEVEL_LOBBY) - - // GLOB.revdata = new // It can load revdata now, from tgs or .git or whatever - - // Sort subsystems by display setting for easy access. - sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_display)) // Set world options. - #ifdef UNIT_TEST - world.sleep_offline = 0 - #else - world.sleep_offline = 1 - #endif world.change_fps(CONFIG_GET(number/fps)) var/initialized_tod = REALTIMEOFDAY - sleep(1) + + if(tgs_prime) + world.TgsInitializationComplete() + + if(sleep_offline_after_initializations) + world.sleep_offline = TRUE + sleep(1 TICKS) + + if(sleep_offline_after_initializations && CONFIG_GET(flag/resume_after_initializations)) + world.sleep_offline = FALSE initializations_finished_with_no_players_logged_in = initialized_tod < REALTIMEOFDAY - 10 - // Loop. - Master.StartProcessing(0) /** * Initialize a given subsystem and handle the results. @@ -248,13 +484,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new SS_INIT_NONE, SS_INIT_SUCCESS, SS_INIT_NO_NEED, + SS_INIT_NO_MESSAGE, ) - if (subsystem.subsystem_initialized) //Don't init if they already are initialized - return - - if (subsystem.flags & SS_NO_INIT) - subsystem.subsystem_initialized = TRUE + if ((subsystem.flags & SS_NO_INIT) || subsystem.initialized) //Don't init SSs with the corresponding flag or if they already are initialized + subsystem.initialized = TRUE // set initialized to TRUE, because the value of initialized may still be checked on SS_NO_INIT subsystems as an "is this ready" check return current_initializing_subsystem = subsystem @@ -267,7 +501,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/seconds = round(time / 1000, 0.01) // Always update the blackbox tally regardless. - // SSblackbox.record_feedback("tally", "subsystem_initialize", time, subsystem.name) + // NOT IMPLEMENTED: SSblackbox.record_feedback("tally", "subsystem_initialize", time, subsystem.name) feedback_set_details("subsystem_initialize", "[time] [subsystem.name]") // Gave invalid return value. @@ -280,12 +514,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(result != SS_INIT_FAILURE) // Some form of success, implicit failure, or the SS in unused. - subsystem.subsystem_initialized = TRUE - + subsystem.initialized = TRUE SEND_SIGNAL(subsystem, COMSIG_SUBSYSTEM_POST_INITIALIZE) else // The subsystem officially reports that it failed to init and wishes to be treated as such. - subsystem.subsystem_initialized = FALSE + subsystem.initialized = FALSE subsystem.can_fire = FALSE // The rest of this proc is printing the world log and chat message. @@ -298,7 +531,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new if(SS_INIT_FAILURE) message_prefix = "Failed to initialize [subsystem.name] subsystem after" chat_warning = TRUE - if(SS_INIT_SUCCESS) + if(SS_INIT_SUCCESS, SS_INIT_NO_MESSAGE) message_prefix = "Initialized [subsystem.name] subsystem within" if(SS_INIT_NO_NEED) // This SS is disabled or is otherwise shy. @@ -311,14 +544,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/message = "[message_prefix] [seconds] second[seconds == 1 ? "" : "s"]!" var/chat_message = chat_warning ? span_boldwarning(message) : span_boldannounce(message) - to_chat(world, chat_message) + if(result != SS_INIT_NO_MESSAGE) + to_chat(world, chat_message, MESSAGE_TYPE_DEBUG) log_world(message) /datum/controller/master/proc/SetRunLevel(new_runlevel) - var/old_runlevel = isnull(current_runlevel) ? "NULL" : GLOB.runlevel_flags[current_runlevel] - testing("MC: Runlevel changed from [old_runlevel] to [new_runlevel]") - current_runlevel = RUNLEVEL_FLAG_TO_INDEX(new_runlevel) + var/old_runlevel = current_runlevel + + testing("MC: Runlevel changed from [isnull(old_runlevel) ? "NULL" : old_runlevel] to [new_runlevel]") + current_runlevel = log(2, new_runlevel) + 1 if(current_runlevel < 1) + current_runlevel = old_runlevel CRASH("Attempted to set invalid runlevel: [new_runlevel]") // Starts the mc, and sticks around to restart it if the loop ever ends. @@ -326,49 +562,56 @@ GLOBAL_REAL(Master, /datum/controller/master) = new set waitfor = 0 if(delay) sleep(delay) - var/rtn = Loop() - if (rtn > 0 || processing < 0) + testing("Master starting processing") + var/started_stage + var/rtn = -2 + do + started_stage = init_stage_completed + rtn = Loop(started_stage) + while (rtn == MC_LOOP_RTN_NEWSTAGES && processing > 0 && started_stage < init_stage_completed) + + if (rtn >= MC_LOOP_RTN_GRACEFUL_EXIT || processing < 0) return //this was suppose to happen. //loop ended, restart the mc - log_and_message_admins("MC Notice: MC crashed or runtimed, self-restarting (\ref[src])") + log_game("MC crashed or runtimed, restarting") + message_admins("MC crashed or runtimed, restarting") var/rtn2 = Recreate_MC() - switch(rtn2) - if(-1) - log_and_message_admins("MC Warning: Failed to self-recreate MC (Return code: [rtn2]), it's up to the failsafe now (\ref[src])") - Failsafe.defcon = 2 - if(0) - log_and_message_admins("MC Warning: Too soon for MC self-restart (Return code: [rtn2]), going to let failsafe handle it (\ref[src])") - Failsafe.defcon = 2 - if(1) - log_and_message_admins("MC Notice: MC self-recreated, old MC departing (Return code: [rtn2]) (\ref[src])") + if (rtn2 <= 0) + log_game("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") + message_admins("Failed to recreate MC (Error code: [rtn2]), it's up to the failsafe now") + Failsafe.defcon = 2 // Main loop. -/datum/controller/master/proc/Loop() +/datum/controller/master/proc/Loop(init_stage) . = -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/datum/controller/subsystem/SS as anything in subsystems) + for (var/thing in subsystems) + var/datum/controller/subsystem/SS = thing if (SS.flags & SS_NO_FIRE) continue + if (SS.init_stage > init_stage) + continue SS.queued_time = 0 SS.queue_next = null SS.queue_prev = null SS.state = SS_IDLE - if (SS.flags & SS_TICKER) + if ((SS.flags & (SS_TICKER|SS_BACKGROUND)) == SS_TICKER) tickersubsystems += SS - timer += world.tick_lag * rand(0,1) + // Timer subsystems aren't allowed to bunch up, so we offset them a bit + timer += world.tick_lag * rand(0, 1) SS.next_fire = timer continue var/ss_runlevels = SS.runlevels var/added_to_any = FALSE - for(var/I in 1 to GLOB.runlevel_flags.len) - if(ss_runlevels & GLOB.runlevel_flags[I]) + for(var/I in 1 to GLOB.bitflags.len) + if(ss_runlevels & GLOB.bitflags[I]) while(runlevel_sorted_subsystems.len < I) runlevel_sorted_subsystems += list(list()) runlevel_sorted_subsystems[I] += SS @@ -402,30 +645,41 @@ GLOBAL_REAL(Master, /datum/controller/master) = new 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/newdrift = ((REALTIMEOFDAY - init_timeofday) - (world.time - init_time)) / world.tick_lag + tickdrift = max(0, MC_AVERAGE_FAST(tickdrift, newdrift)) var/starting_tick_usage = TICK_USAGE + + if(newdrift - olddrift >= CONFIG_GET(number/drift_dump_threshold)) + AttemptProfileDump(CONFIG_GET(number/drift_profile_delay)) + olddrift = newdrift + + if (init_stage != init_stage_completed) + return MC_LOOP_RTN_NEWSTAGES if (processing <= 0) current_ticklimit = TICK_LIMIT_RUNNING - sleep(10) + sleep(1 SECONDS) continue //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) - 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 - sleep(world.tick_lag * (processing * sleep_delta)) - continue + if (init_stage == INITSTAGE_MAX) + //if there are multiple 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) + 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 + sleep(world.tick_lag * (processing * sleep_delta)) + continue - //Byond resumed us late. assume it might have to do the same next tick - if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time) - sleep_delta += 1 + //Byond resumed us late. assume it might have to do the same next tick + if (last_run + CEILING(world.tick_lag * (processing * sleep_delta), world.tick_lag) < world.time) + sleep_delta += 1 - sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta + sleep_delta = MC_AVERAGE_FAST(sleep_delta, 1) //decay sleep_delta - if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick - sleep_delta += 1 + if (starting_tick_usage > (TICK_LIMIT_MC*0.75)) //we ran 3/4 of the way into the tick + sleep_delta += 1 + else + sleep_delta = 1 //debug if (make_runtime) @@ -436,7 +690,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new new/datum/controller/failsafe() // (re)Start the failsafe. //now do the actual stuff - if (!queue_head || !(iteration % 3)) + if (!skip_ticks) var/checking_runlevel = current_runlevel if(cached_runlevel != checking_runlevel) //resechedule subsystems @@ -455,46 +709,73 @@ GLOBAL_REAL(Master, /datum/controller/master) = new else subsystems_to_check = tickersubsystems - if (CheckQueue(subsystems_to_check) <= 0) - log_world("MC: CheckQueue(subsystems_to_check) exited uncleanly, SoftReset (error_level=[error_level]") + if (CheckQueue(subsystems_to_check) <= 0) //error processing queue + stack_trace("MC: CheckQueue failed. Current error_level is [round(error_level, 0.25)]") if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) - log_world("MC: SoftReset() failed, crashing") - return - if (!error_level) + error_level++ + CRASH("MC: SoftReset() failed, exiting loop()") + + if (error_level < 2) //except for the first strike, stop incrmenting our iteration so failsafe enters defcon iteration++ - error_level++ + else + cached_runlevel = null //3 strikes, Lets reset the runlevel lists current_ticklimit = TICK_LIMIT_RUNNING - sleep(10) + sleep((1 SECONDS) * error_level) + error_level++ continue if (queue_head) - if (RunQueue() <= 0) - log_world("MC: RunQueue() exited uncleanly, running SoftReset (error_level=[error_level]") - if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) - log_world("MC: SoftReset() failed, crashing") - return - if (!error_level) - iteration++ + if (RunQueue() <= 0) //error running queue + stack_trace("MC: RunQueue failed. Current error_level is [round(error_level, 0.25)]") + if (error_level > 1) //skip the first error, + if (!SoftReset(tickersubsystems, runlevel_sorted_subsystems)) + error_level++ + CRASH("MC: SoftReset() failed, exiting loop()") + + if (error_level <= 2) //after 3 strikes stop incrmenting our iteration so failsafe enters defcon + iteration++ + else + cached_runlevel = null //3 strikes, Lets also reset the runlevel lists + current_ticklimit = TICK_LIMIT_RUNNING + sleep((1 SECONDS) * error_level) + error_level++ + continue error_level++ - current_ticklimit = TICK_LIMIT_RUNNING - sleep(10) - continue - error_level-- + if (error_level > 0) + error_level = max(MC_AVERAGE_SLOW(error_level-1, error_level), 0) if (!queue_head) //reset the counts if the queue is empty, in the off chance they get out of sync queue_priority_count = 0 queue_priority_count_bg = 0 iteration++ last_run = world.time + if (skip_ticks) + skip_ticks-- src.sleep_delta = MC_AVERAGE_FAST(src.sleep_delta, sleep_delta) - current_ticklimit = TICK_LIMIT_RUNNING - if (processing * sleep_delta <= world.tick_lag) - current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick + +// Force any verbs into overtime, to test how they perfrom under load +// For local ONLY +#ifdef VERB_STRESS_TEST + /// Target enough tick usage to only allow time for our maptick estimate and verb processing, and nothing else + var/overtime_target = TICK_LIMIT_RUNNING +// This will leave just enough cpu time for maptick, forcing verbs to run into overtime +// Use this for testing the worst case scenario, when maptick is spiking and usage is otherwise completely consumed +#ifdef FORCE_VERB_OVERTIME + overtime_target += TICK_BYOND_RESERVE +#endif + CONSUME_UNTIL(overtime_target) +#endif + + if (init_stage != INITSTAGE_MAX) + current_ticklimit = TICK_LIMIT_RUNNING * 2 + else + current_ticklimit = TICK_LIMIT_RUNNING + if (processing * sleep_delta <= world.tick_lag) + current_ticklimit -= (TICK_LIMIT_RUNNING * 0.25) //reserve the tail 1/4 of the next tick for the mc if we plan on running next tick + + check_and_perform_fast_update() sleep(world.tick_lag * (processing * sleep_delta)) - - - // This is what decides if something should run. /datum/controller/master/proc/CheckQueue(list/subsystemstocheck) . = 0 //so the mc knows if we runtimed @@ -519,11 +800,16 @@ 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 -// Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage +/// RunQueue - Run thru the queue of subsystems to run, running them while balancing out their allocated tick precentage +/// Returns 0 if runtimed, a negitive number for logic errors, and a positive number if the operation completed without errors /datum/controller/master/proc/RunQueue() . = 0 var/datum/controller/subsystem/queue_node @@ -535,12 +821,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 @@ -549,42 +834,42 @@ GLOBAL_REAL(Master, /datum/controller/master) = new while (queue_node) if (ran && TICK_USAGE > TICK_LIMIT_RUNNING) break - queue_node_flags = queue_node.flags queue_node_priority = queue_node.queued_priority - //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) - 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 (!(queue_node_flags & SS_TICKER) && skip_ticks) + queue_node = queue_node.queue_next + continue + + if ((queue_node_flags & SS_BACKGROUND)) + if (!bg_calc) + current_tick_budget = queue_priority_count_bg + bg_calc = TRUE + else if (bg_calc) + //error state, do sane fallback behavior + if (. == 0) + log_world("MC: Queue logic failure, non-background subsystem queued to run after a background subsystem: [queue_node] queue_prev:[queue_node.queue_prev]") + . = -1 + current_tick_budget = queue_priority_count //this won't even be right, but is the best we have. + bg_calc = FALSE - if ((queue_node_flags & SS_BACKGROUND) && !bg_calc) - current_tick_budget = queue_priority_count_bg - bg_calc = TRUE 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) + if (queue_node_priority >= 0 && current_tick_budget > 0 && current_tick_budget >= queue_node_priority) + //Give the subsystem a precentage of the remaining tick based on the remaining priority + tick_precentage = tick_remaining * (queue_node_priority / current_tick_budget) else - tick_precentage = tick_remaining + //error state + if (. == 0) + log_world("MC: tick_budget sync error. [json_encode(list(current_tick_budget, queue_priority_count, queue_priority_count_bg, bg_calc, queue_node, queue_node_priority))]") + . = -1 + tick_precentage = tick_remaining //just because we lost track of priority calculations doesn't mean we can't try to finish off the run, if the error state persists, we don't want to stop ticks from happening - // Reduce tick allocation for subsystems that overran on their last tick. tick_precentage = max(tick_precentage*0.5, tick_precentage-queue_node.tick_overrun) 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) @@ -592,10 +877,22 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node.state = SS_RUNNING + if(queue_node.profiler_focused) + world.Profile(PROFILE_START) + tick_usage = TICK_USAGE var/state = queue_node.ignite(queue_node_paused) tick_usage = TICK_USAGE - tick_usage + if(use_rolling_usage) + queue_node.prune_rolling_usage() + // Rolling usage is an unrolled list that we know the order off + // OPTIMIZATION POSTING + queue_node.rolling_usage += list(DS2TICKS(world.time), tick_usage) + + if(queue_node.profiler_focused) + world.Profile(PROFILE_STOP) + if (state == SS_RUNNING) state = SS_IDLE current_tick_budget -= queue_node_priority @@ -621,7 +918,7 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node.paused_ticks = 0 queue_node.paused_tick_usage = 0 - if (queue_node_flags & SS_BACKGROUND) //update our running total + if (bg_calc) //update our running total queue_priority_count_bg -= queue_node_priority else queue_priority_count -= queue_node_priority @@ -629,14 +926,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 @@ -645,21 +935,24 @@ GLOBAL_REAL(Master, /datum/controller/master) = new queue_node = queue_node.queue_next - . = 1 + if (. == 0) + . = 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.") + stack_trace("MC: SoftReset called, resetting MC queue state.") + if (!istype(subsystems) || !istype(ticker_SS) || !istype(runlevel_SS)) log_world("MC: SoftReset: Bad list contents: '[subsystems]' '[ticker_SS]' '[runlevel_SS]'") return - var/subsystemstocheck = subsystems + ticker_SS + var/subsystemstocheck = subsystems | ticker_SS for(var/I in runlevel_SS) subsystemstocheck |= I - for (var/datum/controller/subsystem/SS as anything in subsystemstocheck) + for (var/thing in subsystemstocheck) + var/datum/controller/subsystem/SS = thing if (!SS || !istype(SS)) //list(SS) is so if a list makes it in the subsystem list, we remove the list, not the contents subsystems -= list(SS) @@ -688,39 +981,51 @@ GLOBAL_REAL(Master, /datum/controller/master) = new log_world("MC: SoftReset: Finished.") . = 1 +/// Warns us that the end of tick byond map_update will be laggier then normal, so that we can just skip running subsystems this tick. +/datum/controller/master/proc/laggy_byond_map_update_incoming() + if (!skip_ticks) + skip_ticks = 1 /datum/controller/master/stat_entry(msg) - if(!statclick) - statclick = new/obj/effect/statclick/debug(null, "Initializing...", src) - - msg = "Byond: (FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%))" - msg += "Master Controller: [statclick.update("(TickRate:[Master.processing]) (Iteration:[Master.iteration])")]" - + msg = "(TickRate:[Master.processing]) (Iteration:[Master.iteration]) (TickLimit: [round(Master.current_ticklimit, 0.1)])" return msg -/datum/controller/master/StartLoadingMap(var/quiet = TRUE) - if(map_loading) - admin_notice(span_danger("Another map is attempting to be loaded before first map released lock. Delaying."), R_DEBUG) - else if(!quiet) - admin_notice(span_danger("Map is now being built. Locking."), R_DEBUG) +/datum/controller/master/StartLoadingMap() //disallow more than one map to load at once, multithreading it will just cause race conditions while(map_loading) stoplag() - for(var/datum/controller/subsystem/SS as anything in subsystems) + for(var/S in subsystems) + var/datum/controller/subsystem/SS = S SS.StartLoadingMap() - map_loading = TRUE -/datum/controller/master/StopLoadingMap(var/quiet = TRUE) - if(!quiet) - admin_notice(span_danger("Map is finished. Unlocking."), R_DEBUG) +/datum/controller/master/StopLoadingMap(bounds = null) map_loading = FALSE - for(var/datum/controller/subsystem/SS as anything in subsystems) + for(var/S in subsystems) + var/datum/controller/subsystem/SS = S SS.StopLoadingMap() + +/datum/controller/master/proc/UpdateTickRate() + if (!processing) + return + var/client_count = length(GLOB.clients) + if (client_count < CONFIG_GET(number/mc_tick_rate/disable_high_pop_mc_mode_amount)) + processing = CONFIG_GET(number/mc_tick_rate/base_mc_tick_rate) + else if (client_count > CONFIG_GET(number/mc_tick_rate/high_pop_mc_mode_amount)) + processing = CONFIG_GET(number/mc_tick_rate/high_pop_mc_tick_rate) + /datum/controller/master/proc/OnConfigLoad() for (var/thing in subsystems) var/datum/controller/subsystem/SS = thing SS.OnConfigLoad() + +/// Attempts to dump our current profile info into a file, triggered if the MC thinks shit is going down +/// Accepts a delay in deciseconds of how long ago our last dump can be, this saves causing performance problems ourselves +/datum/controller/master/proc/AttemptProfileDump(delay) + if(REALTIMEOFDAY - last_profiled <= delay) + return FALSE + last_profiled = REALTIMEOFDAY + SSprofiler.DumpFile(allow_yield = FALSE) diff --git a/code/controllers/master_controller.dm b/code/controllers/master_controller.dm index 4f8b17d4cf..6f157b7bcf 100644 --- a/code/controllers/master_controller.dm +++ b/code/controllers/master_controller.dm @@ -42,7 +42,7 @@ var/global/last_tick_duration = 0 transfer_controller = new admin_notice(span_danger("Initializations complete."), R_DEBUG) -// #if UNIT_TEST +// #if UNIT_TESTS // # define CHECK_SLEEP_MASTER // For unit tests we don't care about a smooth lobby screen experience. We care about speed. // #else // # define CHECK_SLEEP_MASTER if(++initialized_objects > 500) { initialized_objects=0;sleep(world.tick_lag); } diff --git a/code/controllers/subsystem.dm b/code/controllers/subsystem.dm index 3e5e269057..02f45a5415 100644 --- a/code/controllers/subsystem.dm +++ b/code/controllers/subsystem.dm @@ -13,27 +13,48 @@ /// Name of the subsystem - you must change this name = "fire coderbus" - /// Order of initialization. Higher numbers are initialized first, lower numbers later. Use or create defines such as [INIT_ORDER_DEFAULT] so we can see the order in one file. - var/init_order = INIT_ORDER_DEFAULT + /// Determines which subsystems this subsystem is dependant on to initialize. Will initialize after all specified subsystems. + /// If init_stage is earlier than a dependent subsystem, will throw an error and push the init stage forward to that subsystem. + /// Usage: Put the typepaths of the subsystems that need to init before this one in this list. + var/list/dependencies = list() + + /// The inverse of the dependencies. Can be set manually, but will also get evaluated at runtime. Turns into a list of instances at runtime. + /// Usage: Put the typepaths of the subsystems that need to init after this one in this list. + var/list/dependents + + /// ID of the subsystem. Set automatically when the dependency graph is evaluated. Used primarily in determining order. + var/ordering_id = 0 + + /// Do not modify. Automatically set when the dependency graph is evaluated. Similar to ordering_id, but evaluated after init_stage. + var/init_order = 0 /// Time to wait (in deciseconds) between each call to fire(). Must be a positive integer. var/wait = 20 - /// Priority Weight: When mutiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems. + /// Priority Weight: When multiple subsystems need to run in the same tick, higher priority subsystems will be given a higher share of the tick before MC_TICK_CHECK triggers a sleep, higher priority subsystems also run before lower priority subsystems var/priority = FIRE_PRIORITY_DEFAULT /// [Subsystem Flags][SS_NO_INIT] to control binary behavior. Flags must be set at compile time or before preinit finishes to take full effect. (You can also restart the mc to force them to process again) var/flags = NONE - /// This var is set to TRUE after the subsystem has been initialized. - var/subsystem_initialized = FALSE + /// Which stage does this subsystem init at. Earlier stages can fire while later stages init. + var/init_stage = INITSTAGE_MAIN + + /// This var is set to `INITIALIZATION_INNEW_REGULAR` after the subsystem has been initialized. + 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 var/can_fire = TRUE ///Bitmap of what game states can this subsystem fire at. See [RUNLEVELS_DEFAULT] for more details. - var/runlevels = RUNLEVELS_DEFAULT + var/runlevels = RUNLEVELS_DEFAULT //points of the game at which the SS can fire + + /** + * boolean set by admins. if TRUE then this subsystem will stop the world profiler after ignite() returns and start it again when called. + * used so that you can audit a specific subsystem or group of subsystems' synchronous call chain. + */ + var/profiler_focused = FALSE /* * The following variables are managed by the MC and should not be modified directly. @@ -54,6 +75,9 @@ /// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing var/tick_overrun = 0 + /// Flat list of usage and time, every odd index is a log time, every even index is a usage + var/list/rolling_usage = list() + /// How much of a tick (in percents of a tick) were we allocated last fire. var/tick_allocation_last = 0 @@ -78,6 +102,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 @@ -92,9 +119,13 @@ /// Previous subsystem in the queue of subsystems to run this tick var/datum/controller/subsystem/queue_prev + /// String to store an applicable error message for a subsystem crashing, used to help debug crashes in contexts such as Continuous Integration/Unit Tests + var/initialization_failure_message = null + //Do not blindly add vars here to the bottom, put it where it goes above //If your var only has two values, put it in as a flag. + //Do not override ///datum/controller/subsystem/New() @@ -107,7 +138,7 @@ ///This is used so the mc knows when the subsystem sleeps. do not override. /datum/controller/subsystem/proc/ignite(resumed = FALSE) SHOULD_NOT_OVERRIDE(TRUE) - set waitfor = 0 + set waitfor = FALSE . = SS_IDLE tick_allocation_last = Master.current_ticklimit-(TICK_USAGE) @@ -131,7 +162,7 @@ ///Sleeping in here prevents future fires until returned. /datum/controller/subsystem/proc/fire(resumed = FALSE) flags |= SS_NO_FIRE - throw EXCEPTION("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.") + CRASH("Subsystem [src]([type]) does not fire() but did not set the SS_NO_FIRE flag. Please add the SS_NO_FIRE flag to any subsystem that doesn't fire so it doesn't get added to the processing list and waste cpu.") /datum/controller/subsystem/Destroy() dequeue() @@ -141,6 +172,31 @@ 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) @@ -155,8 +211,8 @@ queue_node_priority = queue_node.queued_priority queue_node_flags = queue_node.flags - if (queue_node_flags & SS_TICKER) - if (!(SS_flags & SS_TICKER)) + if (queue_node_flags & (SS_TICKER|SS_BACKGROUND) == SS_TICKER) + if ((SS_flags & (SS_TICKER|SS_BACKGROUND)) != SS_TICKER) continue if (queue_node_priority < SS_priority) break @@ -207,9 +263,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) @@ -228,14 +284,13 @@ /datum/controller/subsystem/proc/OnConfigLoad() /** - * Used to initialize the subsystem. This is expected to be overriden by subtypes. + * Used to initialize the subsystem. This is expected to be overridden by subtypes. */ /datum/controller/subsystem/Initialize() return SS_INIT_NONE -//hook for printing stats to the "MC" statuspanel for admins to see performance and related stats etc. /datum/controller/subsystem/stat_entry(msg) - if(can_fire && !(SS_NO_FIRE & flags)) + if(can_fire && !(SS_NO_FIRE & flags) && init_stage <= Master.init_stage_completed) msg = "[round(cost,1)]ms|[round(tick_usage,1)]%([round(tick_overrun,1)]%)|[round(ticks,0.1)]\t[msg]" else msg = "OFFLINE\t[msg]" @@ -254,45 +309,30 @@ 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 + +/// Prunes out of date entries in our rolling usage list +/datum/controller/subsystem/proc/prune_rolling_usage() + var/list/rolling_usage = src.rolling_usage + var/cut_to = 0 + while(cut_to + 2 <= length(rolling_usage) && rolling_usage[cut_to + 1] < DS2TICKS(world.time - Master.rolling_usage_length)) + cut_to += 2 + if(cut_to) + rolling_usage.Cut(1, cut_to + 1) //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 /datum/controller/subsystem/Recover() -// Suspends this subsystem from being queued for running. If already in the queue, sleeps until idle. Returns FALSE if the subsystem was already suspended. -/datum/controller/subsystem/proc/suspend() - . = (can_fire > 0) // Return true if we were previously runnable, false if previously suspended. - can_fire = FALSE - // Safely sleep in a loop until the subsystem is idle, (or its been un-suspended somehow) - while(can_fire <= 0 && state != SS_IDLE) - stoplag() // Safely sleep in a loop until - -// Wakes a suspended subsystem. -/datum/controller/subsystem/proc/wake() - can_fire = TRUE - -// This subsystem has destabilized the game and is being put on warning. At this point there may be -// an opportunity to clean up the subsystem or check it for errors in ways that would otherwise be too slow. -// You should log the errors/cleanup results, so you can fix the problem rather than using this as a crutch. -/datum/controller/subsystem/proc/fail() - var/msg = "[name] subsystem being blamed for MC failure" - log_world(msg) - log_game(msg) - -// DO NOT ATTEMPT RECOVERY. Only log debugging info. You should leave the subsystem as it is. -// Attempting recovery here could make things worse, create hard recursions with the MC disabling it every run, etc. -/datum/controller/subsystem/proc/critfail() - var/msg = "[name] subsystem received final blame for MC failure" - log_world(msg) - log_game(msg) - /datum/controller/subsystem/vv_edit_var(var_name, var_value) switch (var_name) + 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) + update_nextfire(reset_time = TRUE) if (NAMEOF(src, queued_priority)) //editing this breaks things. return FALSE . = ..() diff --git a/code/controllers/subsystems/admin_verbs.dm b/code/controllers/subsystems/admin_verbs.dm index b4bfcf487c..fabb7eff7b 100644 --- a/code/controllers/subsystems/admin_verbs.dm +++ b/code/controllers/subsystems/admin_verbs.dm @@ -3,7 +3,7 @@ GENERAL_PROTECT_DATUM(/datum/controller/subsystem/admin_verbs) SUBSYSTEM_DEF(admin_verbs) name = "Admin Verbs" flags = SS_NO_FIRE - //init_stage = INITSTAGE_EARLY + init_stage = INITSTAGE_EARLY /// A list of all admin verbs indexed by their type. var/list/datum/admin_verb/admin_verbs_by_type = list() /// A list of all admin verbs indexed by their visibility flag. diff --git a/code/controllers/subsystems/ai.dm b/code/controllers/subsystems/ai.dm index 4e5256d949..090a405a52 100644 --- a/code/controllers/subsystems/ai.dm +++ b/code/controllers/subsystems/ai.dm @@ -1,11 +1,15 @@ SUBSYSTEM_DEF(ai) name = "AI" - init_order = INIT_ORDER_AI priority = FIRE_PRIORITY_AI wait = 2 SECONDS flags = SS_NO_INIT runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + dependencies = list( + /datum/controller/subsystem/air, + /datum/controller/subsystem/mobs + ) + var/list/processing = list() var/list/currentrun = list() diff --git a/code/controllers/subsystems/aifast.dm b/code/controllers/subsystems/aifast.dm index 2580c156ac..bd3294af0e 100644 --- a/code/controllers/subsystems/aifast.dm +++ b/code/controllers/subsystems/aifast.dm @@ -1,11 +1,14 @@ SUBSYSTEM_DEF(aifast) name = "AI (Fast)" - init_order = INIT_ORDER_AI_FAST priority = FIRE_PRIORITY_AI wait = 0.25 SECONDS // Every quarter second flags = SS_NO_INIT runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + dependencies = list( + /datum/controller/subsystem/ai + ) + var/list/processing = list() var/list/currentrun = list() diff --git a/code/controllers/subsystems/air.dm b/code/controllers/subsystems/air.dm index 3a342124fc..6fa1c5e616 100644 --- a/code/controllers/subsystems/air.dm +++ b/code/controllers/subsystems/air.dm @@ -65,7 +65,9 @@ Class Procs: SUBSYSTEM_DEF(air) name = "Air" - init_order = INIT_ORDER_AIR + dependencies = list( + /datum/controller/subsystem/atoms + ) priority = FIRE_PRIORITY_AIR wait = 2 SECONDS // seconds (We probably can speed this up actually) flags = SS_BACKGROUND // TODO - Should this really be background? It might be important. diff --git a/code/controllers/subsystems/air_traffic.dm b/code/controllers/subsystems/air_traffic.dm index 8ee8a354fd..3e78fa84ad 100644 --- a/code/controllers/subsystems/air_traffic.dm +++ b/code/controllers/subsystems/air_traffic.dm @@ -6,7 +6,7 @@ SUBSYSTEM_DEF(atc) priority = FIRE_PRIORITY_ATC runlevels = RUNLEVEL_GAME wait = 2 SECONDS - init_order = INIT_ORDER_ATC + init_stage = INITSTAGE_LAST flags = SS_BACKGROUND VAR_PRIVATE/next_tick = 0 diff --git a/code/controllers/subsystems/airflow.dm b/code/controllers/subsystems/airflow.dm index 30ad9f9ea0..0e87df8259 100644 --- a/code/controllers/subsystems/airflow.dm +++ b/code/controllers/subsystems/airflow.dm @@ -16,6 +16,11 @@ SUBSYSTEM_DEF(airflow) runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME priority = FIRE_PRIORITY_AIRFLOW + dependencies = list( + /datum/controller/subsystem/atoms, + /datum/controller/subsystem/air + ) + var/list/processing = list() var/list/currentrun = list() diff --git a/code/controllers/subsystems/alarm.dm b/code/controllers/subsystems/alarm.dm index 1fb94ac556..61af1f852d 100644 --- a/code/controllers/subsystems/alarm.dm +++ b/code/controllers/subsystems/alarm.dm @@ -2,7 +2,9 @@ SUBSYSTEM_DEF(alarm) name = "Alarm" wait = 2 SECONDS priority = FIRE_PRIORITY_ALARM - init_order = INIT_ORDER_ALARM + dependencies = list( + /datum/controller/subsystem/mapping + ) var/list/datum/alarm/all_handlers var/tmp/list/currentrun = null var/static/list/active_alarm_cache = list() diff --git a/code/controllers/subsystems/asset_loading.dm b/code/controllers/subsystems/asset_loading.dm index ecdf406894..642f9ca4b5 100644 --- a/code/controllers/subsystems/asset_loading.dm +++ b/code/controllers/subsystems/asset_loading.dm @@ -13,10 +13,11 @@ SUBSYSTEM_DEF(asset_loading) while(length(generate_queue)) var/datum/asset/to_load = generate_queue[generate_queue.len] - to_load.queued_generation() - if(MC_TICK_CHECK) return + + to_load.queued_generation() + last_queue_len = length(generate_queue) generate_queue.len-- // We just emptied the queue diff --git a/code/controllers/subsystems/assets.dm b/code/controllers/subsystems/assets.dm index d34d05ff55..32d8b4c390 100644 --- a/code/controllers/subsystems/assets.dm +++ b/code/controllers/subsystems/assets.dm @@ -1,6 +1,11 @@ SUBSYSTEM_DEF(assets) name = "Assets" - init_order = INIT_ORDER_ASSETS + dependencies = list( + /datum/controller/subsystem/holomaps, + /datum/controller/subsystem/robot_sprites + ///datum/controller/subsystem/persistent_paintings, + ///datum/controller/subsystem/greyscale_previews, + ) flags = SS_NO_FIRE var/list/datum/asset_cache_item/cache = list() var/list/preload = list() @@ -32,7 +37,7 @@ SUBSYSTEM_DEF(assets) transport.Initialize(cache) - subsystem_initialized = TRUE + initialized = TRUE return SS_INIT_SUCCESS /datum/controller/subsystem/assets/Recover() diff --git a/code/controllers/subsystems/atoms.dm b/code/controllers/subsystems/atoms.dm index e30803118c..f8d3cece62 100644 --- a/code/controllers/subsystems/atoms.dm +++ b/code/controllers/subsystems/atoms.dm @@ -1,6 +1,14 @@ SUBSYSTEM_DEF(atoms) name = "Atoms" - init_order = INIT_ORDER_ATOMS + dependencies = list( + /datum/controller/subsystem/garbage, + /datum/controller/subsystem/mapping, + /datum/controller/subsystem/planets, + /datum/controller/subsystem/transcore, + /datum/controller/subsystem/chemistry, + /datum/controller/subsystem/sounds, + /datum/controller/subsystem/job + ) flags = SS_NO_FIRE /// A stack of list(source, desired initialized state) @@ -8,7 +16,7 @@ SUBSYSTEM_DEF(atoms) var/list/initialized_state = list() var/base_initialized - var/initialized = INITIALIZATION_INSSATOMS + var/atom_initialized = INITIALIZATION_INSSATOMS var/list/late_loaders = list() var/list/BadInitializeCalls = list() @@ -25,19 +33,19 @@ SUBSYSTEM_DEF(atoms) var/list/mapload_init_times = list() #endif - initialized = INITIALIZATION_INSSATOMS + atom_initialized = INITIALIZATION_INSSATOMS /datum/controller/subsystem/atoms/Initialize() init_start_time = world.time - initialized = INITIALIZATION_INNEW_MAPLOAD + atom_initialized = INITIALIZATION_INNEW_MAPLOAD InitializeAtoms() - initialized = INITIALIZATION_INNEW_REGULAR + atom_initialized = INITIALIZATION_INNEW_REGULAR return SS_INIT_SUCCESS /datum/controller/subsystem/atoms/proc/InitializeAtoms(list/atoms, list/atoms_to_return) - if(initialized == INITIALIZATION_INSSATOMS) + if(atom_initialized == INITIALIZATION_INSSATOMS) return // Generate a unique mapload source for this run of InitializeAtoms @@ -142,9 +150,9 @@ SUBSYSTEM_DEF(atoms) /// Accepts a state and a source, the most recent state is used, sources exist to prevent overriding old values accidentally /datum/controller/subsystem/atoms/proc/set_tracked_initalized(state, source) if(!length(initialized_state)) - base_initialized = initialized + base_initialized = atom_initialized initialized_state += list(list(source, state)) - initialized = state + atom_initialized = state /datum/controller/subsystem/atoms/proc/clear_tracked_initalize(source) if(!length(initialized_state)) @@ -155,18 +163,18 @@ SUBSYSTEM_DEF(atoms) break if(!length(initialized_state)) - initialized = base_initialized + atom_initialized = base_initialized base_initialized = INITIALIZATION_INNEW_REGULAR return - initialized = initialized_state[length(initialized_state)][2] + atom_initialized = initialized_state[length(initialized_state)][2] /// Returns TRUE if anything is currently being initialized /datum/controller/subsystem/atoms/proc/initializing_something() return length(initialized_state) > 1 /datum/controller/subsystem/atoms/Recover() - initialized = SSatoms.initialized - if(initialized == INITIALIZATION_INNEW_MAPLOAD) + atom_initialized = SSatoms.atom_initialized + if(atom_initialized == INITIALIZATION_INNEW_MAPLOAD) InitializeAtoms() initialized_state = SSatoms.initialized_state BadInitializeCalls = SSatoms.BadInitializeCalls @@ -187,7 +195,7 @@ SUBSYSTEM_DEF(atoms) /// Prepares an atom to be deleted once the atoms SS is initialized. /datum/controller/subsystem/atoms/proc/prepare_deletion(atom/target) - if (initialized == INITIALIZATION_INNEW_REGULAR) + if (atom_initialized == INITIALIZATION_INNEW_REGULAR) // Atoms SS has already completed, just kill it now. qdel(target) else diff --git a/code/controllers/subsystems/character_setup.dm b/code/controllers/subsystems/character_setup.dm index 272cd0b8ab..7a26d70c76 100644 --- a/code/controllers/subsystems/character_setup.dm +++ b/code/controllers/subsystems/character_setup.dm @@ -1,6 +1,5 @@ SUBSYSTEM_DEF(character_setup) name = "Character Setup" - init_order = INIT_ORDER_DEFAULT priority = FIRE_PRIORITY_CHARSETUP flags = SS_BACKGROUND | SS_NO_INIT wait = 1 SECOND diff --git a/code/controllers/subsystems/chat.dm b/code/controllers/subsystems/chat.dm index b8b2917c8d..575a9fad31 100644 --- a/code/controllers/subsystems/chat.dm +++ b/code/controllers/subsystems/chat.dm @@ -8,7 +8,7 @@ SUBSYSTEM_DEF(chat) flags = SS_TICKER|SS_NO_INIT wait = 1 priority = FIRE_PRIORITY_CHAT - init_order = INIT_ORDER_CHAT + init_stage = INITSTAGE_LAST /// Assosciates a ckey with a list of messages to send to them. var/list/list/datum/chat_payload/client_to_payloads = list() diff --git a/code/controllers/subsystems/chemistry.dm b/code/controllers/subsystems/chemistry.dm index 0e9aae6f58..6de865b9fd 100644 --- a/code/controllers/subsystems/chemistry.dm +++ b/code/controllers/subsystems/chemistry.dm @@ -2,7 +2,9 @@ SUBSYSTEM_DEF(chemistry) name = "Chemistry" wait = 20 flags = SS_NO_FIRE - init_order = INIT_ORDER_CHEMISTRY + dependencies = list( + /datum/controller/subsystem/garbage + ) var/list/chemical_reactions = list() var/list/chemical_reactions_by_product = list() diff --git a/code/controllers/subsystems/circuits.dm b/code/controllers/subsystems/circuits.dm index 777b74f4d7..a1130bff25 100644 --- a/code/controllers/subsystems/circuits.dm +++ b/code/controllers/subsystems/circuits.dm @@ -4,8 +4,12 @@ // SUBSYSTEM_DEF(circuit) name = "Circuit" - init_order = INIT_ORDER_CIRCUIT flags = SS_NO_FIRE + + dependencies = list( + /datum/controller/subsystem/atoms + ) + var/list/all_components = list() // Associative list of [component_name]:[component_path] pairs var/list/cached_components = list() // Associative list of [component_path]:[component] pairs var/list/all_assemblies = list() // Associative list of [assembly_name]:[assembly_path] pairs diff --git a/code/controllers/subsystems/communications.dm b/code/controllers/subsystems/communications.dm new file mode 100644 index 0000000000..6c25eecd59 --- /dev/null +++ b/code/controllers/subsystems/communications.dm @@ -0,0 +1,207 @@ +/* + HOW IT WORKS + + The SSradio is a global object maintaining all radio transmissions, think about it as about "ether". + Note that walkie-talkie, intercoms and headsets handle transmission using nonstandard way. + procs: + + add_object(obj/device as obj, var/new_frequency as num, var/radio_filter as text|null = null) + Adds listening object. + parameters: + device - device receiving signals, must have proc receive_signal (see description below). + one device may listen several frequencies, but not same frequency twice. + new_frequency - see possibly frequencies below; + radio_filter - thing for optimization. Optional, but recommended. + All filters should be consolidated in this file, see defines later. + Device without listening filter will receive all signals (on specified frequency). + Device with filter will receive any signals sent without filter. + Device with filter will not receive any signals sent with different filter. + returns: + Reference to frequency object. + + remove_object (obj/device, old_frequency) + Obliviously, after calling this proc, device will not receive any signals on old_frequency. + Other frequencies will left unaffected. + + return_frequency(var/frequency as num) + returns: + Reference to frequency object. Use it if you need to send and do not need to listen. + + radio_frequency is a global object maintaining list of devices that listening specific frequency. + procs: + + post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter as text|null = null, var/range as num|null = null) + Sends signal to all devices that wants such signal. + parameters: + source - object, emitted signal. Usually, devices will not receive their own signals. + signal - see description below. + radio_filter - described above. + range - radius of regular byond's square circle on that z-level. null means everywhere, on all z-levels. + + obj/proc/receive_signal(datum/signal/signal, var/receive_method as num, var/receive_param) + Handler from received signals. By default does nothing. Define your own for your object. + Avoid of sending signals directly from this proc, use spawn(-1). DO NOT use sleep() here or call procs that sleep please. If you must, use spawn() + parameters: + signal - see description below. Extract all needed data from the signal before doing sleep(), spawn() or return! + receive_method - may be TRANSMISSION_WIRE or TRANSMISSION_RADIO. + TRANSMISSION_WIRE is currently unused. + receive_param - for TRANSMISSION_RADIO here comes frequency. + + datum/signal + vars: + source + an object that emitted signal. Used for debug and bearing. + data + list with transmitting data. Usual use pattern: + data["msg"] = "hello world" + encryption + Some number symbolizing "encryption key". + Note that game actually do not use any cryptography here. + If receiving object don't know right key, it must ignore encrypted signal in its receive_signal. + +*/ + +SUBSYSTEM_DEF(radio) + name = "Radio" + flags = SS_NO_FIRE + dependencies = list( + /datum/controller/subsystem/machines + ) + var/list/datum/radio_frequency/frequencies = list() + +/datum/controller/subsystem/radio/Initialize() + GLOB.autospeaker = new (null, FALSE, null, null, TRUE) //Set up Global Announcer + return SS_INIT_SUCCESS + +/datum/controller/subsystem/radio/proc/add_object(obj/device as obj, var/new_frequency as num, var/radio_filter = null as text|null) + var/f_text = num2text(new_frequency) + var/datum/radio_frequency/frequency = frequencies[f_text] + + if(!frequency) + frequency = new + frequency.frequency = new_frequency + frequencies[f_text] = frequency + + frequency.add_listener(device, radio_filter) + return frequency + +/datum/controller/subsystem/radio/proc/remove_object(obj/device, old_frequency) + var/f_text = num2text(old_frequency) + var/datum/radio_frequency/frequency = frequencies[f_text] + + if(frequency) + frequency.remove_listener(device) + + if(frequency.devices.len == 0) + qdel(frequency) + frequencies -= f_text + + return 1 + +/datum/controller/subsystem/radio/proc/return_frequency(var/new_frequency as num) + var/f_text = num2text(new_frequency) + var/datum/radio_frequency/frequency = frequencies[f_text] + + if(!frequency) + frequency = new + frequency.frequency = new_frequency + frequencies[f_text] = frequency + + return frequency + +//Frequency channels + +/datum/radio_frequency + var/frequency as num + var/list/list/obj/devices = list() + +/datum/radio_frequency/proc/post_signal(obj/source as obj|null, datum/signal/signal, var/radio_filter = null as text|null, var/range = null as num|null) + var/turf/start_point + if(range) + start_point = get_turf(source) + if(!start_point) + qdel(signal) + return 0 + if (radio_filter) + send_to_filter(source, signal, radio_filter, start_point, range) + send_to_filter(source, signal, RADIO_DEFAULT, start_point, range) + else + //Broadcast the signal to everyone! + for (var/next_filter in devices) + send_to_filter(source, signal, next_filter, start_point, range) + +//Sends a signal to all machines belonging to a given filter. Should be called by post_signal() +/datum/radio_frequency/proc/send_to_filter(obj/source, datum/signal/signal, var/radio_filter, var/turf/start_point = null, var/range = null) + if (range && !start_point) + return + + for(var/obj/device in devices[radio_filter]) + if(device == source) + continue + if(range) + var/turf/end_point = get_turf(device) + if(!end_point) + continue + if(start_point.z!=end_point.z || get_dist(start_point, end_point) > range) + continue + + device.receive_signal(signal, TRANSMISSION_RADIO, frequency) + +/datum/radio_frequency/proc/add_listener(obj/device as obj, var/radio_filter as text|null) + if (!radio_filter) + radio_filter = RADIO_DEFAULT + //log_admin("add_listener(device=[device],radio_filter=[radio_filter]) frequency=[frequency]") + var/list/obj/devices_line = devices[radio_filter] + if (!devices_line) + devices_line = new + devices[radio_filter] = devices_line + devices_line+=device +// var/list/obj/devices_line___ = devices[filter_str] +// var/l = devices_line___.len + //log_admin("DEBUG: devices_line.len=[devices_line.len]") + //log_admin("DEBUG: devices(filter_str).len=[l]") + +/datum/radio_frequency/proc/remove_listener(obj/device) + for (var/devices_filter in devices) + var/list/devices_line = devices[devices_filter] + devices_line-=device + while (null in devices_line) + devices_line -= null + if (devices_line.len==0) + devices -= devices_filter + +/datum/signal + var/obj/source + + var/transmission_method = 0 //unused at the moment + //0 = wire + //1 = radio transmission + //2 = subspace transmission + + var/list/data = list() + var/encryption + + var/frequency = 0 + +/datum/signal/proc/copy_from(datum/signal/model) + source = model.source + transmission_method = model.transmission_method + data = model.data + encryption = model.encryption + frequency = model.frequency + +/datum/signal/proc/debug_print() + if (source) + . = "signal = {source = '[source]' ([source:x],[source:y],[source:z])\n" + else + . = "signal = {source = '[source]' ()\n" + for (var/i in data) + . += "data\[\"[i]\"\] = \"[data[i]]\"\n" + if(islist(data[i])) + var/list/L = data[i] + for(var/t in L) + . += "data\[\"[i]\"\] list has: [t]" + +//callback used by objects to react to incoming radio signals +/obj/proc/receive_signal(datum/signal/signal, receive_method, receive_param) + return null diff --git a/code/controllers/subsystems/dbcore.dm b/code/controllers/subsystems/dbcore.dm index e800b4aaca..b8acc18c9b 100644 --- a/code/controllers/subsystems/dbcore.dm +++ b/code/controllers/subsystems/dbcore.dm @@ -1,8 +1,10 @@ SUBSYSTEM_DEF(dbcore) name = "Database" flags = SS_TICKER + init_stage = INITSTAGE_EARLY wait = 10 // Not seconds because we're running on SS_TICKER - init_order = INIT_ORDER_DBCORE + runlevels = RUNLEVEL_LOBBY|RUNLEVELS_DEFAULT + priority = FIRE_PRIORITY_DATABASE var/failed_connection_timeout = 0 @@ -41,6 +43,7 @@ SUBSYSTEM_DEF(dbcore) //var/db_daemon_started = FALSE /datum/controller/subsystem/dbcore/Initialize() + Connect() return SS_INIT_SUCCESS /datum/controller/subsystem/dbcore/stat_entry(msg) diff --git a/code/controllers/subsystems/events.dm b/code/controllers/subsystems/events.dm index 1a7ce0a4aa..ef229b58fe 100644 --- a/code/controllers/subsystems/events.dm +++ b/code/controllers/subsystems/events.dm @@ -1,6 +1,7 @@ SUBSYSTEM_DEF(events) name = "Events" // VOREStation Edit - This is still the main events subsystem for us. wait = 2 SECONDS + init_stage = INITSTAGE_LAST var/tmp/list/currentrun = null diff --git a/code/controllers/subsystems/events2.dm b/code/controllers/subsystems/events2.dm index a0706fadef..a30d2a970a 100644 --- a/code/controllers/subsystems/events2.dm +++ b/code/controllers/subsystems/events2.dm @@ -5,6 +5,9 @@ SUBSYSTEM_DEF(event_ticker) name = "Events (Ticker)" wait = 2 SECONDS runlevels = RUNLEVEL_GAME + dependencies = list( + /datum/controller/subsystem/events + ) // List of `/datum/event2/event`s that are currently active, and receiving process() ticks. var/list/active_events = list() diff --git a/code/controllers/subsystems/game_master.dm b/code/controllers/subsystems/game_master.dm index fb1a2e7c35..ec9ae6378b 100644 --- a/code/controllers/subsystems/game_master.dm +++ b/code/controllers/subsystems/game_master.dm @@ -96,7 +96,7 @@ SUBSYSTEM_DEF(game_master) // These are ran before committing to an event. // Returns TRUE if the system is allowed to procede, otherwise returns FALSE. /datum/controller/subsystem/game_master/proc/pre_event_checks(quiet = FALSE) - if(!ticker || ticker.current_state != GAME_STATE_PLAYING) + if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING) if(!quiet) log_game_master("Unable to start event: Ticker is nonexistent, or the game is not ongoing.") return FALSE diff --git a/code/controllers/subsystems/garbage.dm b/code/controllers/subsystems/garbage.dm index a4bc5ee17d..3780495812 100644 --- a/code/controllers/subsystems/garbage.dm +++ b/code/controllers/subsystems/garbage.dm @@ -27,8 +27,7 @@ SUBSYSTEM_DEF(garbage) wait = 2 SECONDS flags = SS_POST_FIRE_TIMING|SS_BACKGROUND|SS_NO_INIT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY - init_order = INIT_ORDER_GARBAGE -// init_stage = INITSTAGE_EARLY + init_stage = INITSTAGE_FIRST var/list/collection_timeout = list(GC_FILTER_QUEUE, GC_CHECK_QUEUE, GC_DEL_QUEUE) // deciseconds to wait before moving something up in the queue to the next level diff --git a/code/controllers/subsystems/holomaps.dm b/code/controllers/subsystems/holomaps.dm index 11270bc2a5..8ee708c721 100644 --- a/code/controllers/subsystems/holomaps.dm +++ b/code/controllers/subsystems/holomaps.dm @@ -4,8 +4,10 @@ // SUBSYSTEM_DEF(holomaps) name = "HoloMiniMaps" - init_order = INIT_ORDER_HOLOMAPS flags = SS_NO_FIRE + dependencies = list( + /datum/controller/subsystem/atoms + ) var/static/holomaps_initialized = FALSE var/static/list/holoMiniMaps = list() var/static/list/extraMiniMaps = list() diff --git a/code/controllers/subsystems/input.dm b/code/controllers/subsystems/input.dm index 70d1f9cda6..482b84de67 100644 --- a/code/controllers/subsystems/input.dm +++ b/code/controllers/subsystems/input.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(input) name = "Input" wait = 1 // SS_TICKER means this runs every tick - init_order = INIT_ORDER_INPUT + init_stage = INITSTAGE_EARLY flags = SS_TICKER | SS_NO_INIT priority = FIRE_PRIORITY_INPUT runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY diff --git a/code/controllers/subsystems/internal_wiki.dm b/code/controllers/subsystems/internal_wiki.dm index 86fc917982..1f40ca1ad8 100644 --- a/code/controllers/subsystems/internal_wiki.dm +++ b/code/controllers/subsystems/internal_wiki.dm @@ -6,7 +6,12 @@ SUBSYSTEM_DEF(internal_wiki) name = "Wiki" wait = 1 - init_order = INIT_ORDER_WIKI + //dependencies = list( + // /datum/controller/subsystem/chemistry, + // /datum/controller/subsystem/plants, + // /datum/controller/subsystem/supply + //) + init_stage = INITSTAGE_LAST flags = SS_NO_FIRE VAR_PRIVATE/list/pages = list() diff --git a/code/controllers/subsystems/job.dm b/code/controllers/subsystems/job.dm index c86d108415..7ee0918444 100644 --- a/code/controllers/subsystems/job.dm +++ b/code/controllers/subsystems/job.dm @@ -1,6 +1,8 @@ SUBSYSTEM_DEF(job) name = "Job" - init_order = INIT_ORDER_JOB + dependencies = list( + /datum/controller/subsystem/mapping, + ) flags = SS_NO_FIRE var/list/occupations = list() //List of all jobs diff --git a/code/controllers/subsystems/lighting.dm b/code/controllers/subsystems/lighting.dm index c0c0e7b377..1e861298f7 100644 --- a/code/controllers/subsystems/lighting.dm +++ b/code/controllers/subsystems/lighting.dm @@ -1,7 +1,9 @@ SUBSYSTEM_DEF(lighting) name = "Lighting" + dependencies = list( + /datum/controller/subsystem/machines + ) wait = 1 - init_order = INIT_ORDER_LIGHTING flags = SS_TICKER runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY // Do some work during lobby waiting period. May as well. var/sun_mult = 1.0 @@ -19,8 +21,8 @@ SUBSYSTEM_DEF(lighting) /datum/controller/subsystem/lighting/Initialize() - if(!subsystem_initialized) - subsystem_initialized = TRUE + if(!initialized) + initialized = TRUE create_all_lighting_objects() for(var/datum/planet/planet in SSplanets.planets) @@ -177,5 +179,5 @@ SUBSYSTEM_DEF(lighting) /datum/controller/subsystem/lighting /datum/controller/subsystem/lighting/Recover() - subsystem_initialized = SSlighting.subsystem_initialized + initialized = SSlighting.initialized ..() diff --git a/code/controllers/subsystems/lobby_monitor.dm b/code/controllers/subsystems/lobby_monitor.dm index 0a18f89f39..5f34a0a013 100644 --- a/code/controllers/subsystems/lobby_monitor.dm +++ b/code/controllers/subsystems/lobby_monitor.dm @@ -1,7 +1,6 @@ SUBSYSTEM_DEF(lobby_monitor) name = "Lobby Art" - init_order = INIT_ORDER_LOBBY - // init_stage = INITSTAGE_EARLY + init_stage = INITSTAGE_EARLY flags = SS_NO_INIT wait = 1 SECOND runlevels = ALL diff --git a/code/controllers/subsystems/machines.dm b/code/controllers/subsystems/machines.dm index a0edb971c9..f20d38d175 100644 --- a/code/controllers/subsystems/machines.dm +++ b/code/controllers/subsystems/machines.dm @@ -9,8 +9,10 @@ SUBSYSTEM_DEF(machines) name = "Machines" + dependencies = list( + /datum/controller/subsystem/points_of_interest + ) priority = FIRE_PRIORITY_MACHINES - init_order = INIT_ORDER_MACHINES flags = SS_KEEP_TIMING runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME diff --git a/code/controllers/subsystems/mapping.dm b/code/controllers/subsystems/mapping.dm index 958f65f35a..f49d004998 100644 --- a/code/controllers/subsystems/mapping.dm +++ b/code/controllers/subsystems/mapping.dm @@ -1,19 +1,32 @@ // Handles map-related tasks, mostly here to ensure it does so after the MC initializes. SUBSYSTEM_DEF(mapping) name = "Mapping" - init_order = INIT_ORDER_MAPPING + //dependencies = list( + // /datum/controller/subsystem/job, + ///datum/controller/subsystem/processing/station, + // /datum/controller/subsystem/chemistry + // ///datum/controller/subsystem/processing/reagents + //) + dependencies = list( + ///datum/controller/subsystem/garbage, + /datum/controller/subsystem/vis_overlays, + /datum/controller/subsystem/chemistry + ) flags = SS_NO_FIRE var/list/map_templates = list() var/obj/effect/landmark/engine_loader/engine_loader var/list/shelter_templates = list() + // TODO: Implement Later + var/datum/map/current_map + /datum/controller/subsystem/mapping/Recover() flags |= SS_NO_INIT // Make extra sure we don't initialize twice. shelter_templates = SSmapping.shelter_templates /datum/controller/subsystem/mapping/Initialize() - if(subsystem_initialized) + if(initialized) return world.max_z_changed() // This is to set up the player z-level list, maxz hasn't actually changed (probably) load_map_templates() @@ -24,6 +37,7 @@ SUBSYSTEM_DEF(mapping) // TODO - Other stuff related to maps and areas could be moved here too. Look at /tg // Lateload Code related to Expedition areas. if(using_map) // VOREStation Edit: Re-enable this. + current_map = using_map loadLateMaps() if(CONFIG_GET(flag/generate_map)) // VOREStation Edit: Re-order this. diff --git a/code/controllers/subsystems/media_tracks.dm b/code/controllers/subsystems/media_tracks.dm index 9034bdc665..0c567cf63c 100644 --- a/code/controllers/subsystems/media_tracks.dm +++ b/code/controllers/subsystems/media_tracks.dm @@ -1,7 +1,7 @@ SUBSYSTEM_DEF(media_tracks) name = "Media Tracks" flags = SS_NO_FIRE - init_order = INIT_ORDER_MEDIA_TRACKS + init_stage = INITSTAGE_EARLY /// Every track, including secret var/list/all_tracks = list() diff --git a/code/controllers/subsystems/mobs.dm b/code/controllers/subsystems/mobs.dm index 36043e9748..521d7cef4f 100644 --- a/code/controllers/subsystems/mobs.dm +++ b/code/controllers/subsystems/mobs.dm @@ -12,6 +12,12 @@ SUBSYSTEM_DEF(mobs) flags = SS_KEEP_TIMING|SS_NO_INIT runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + dependencies = list( + /datum/controller/subsystem/atoms, + /datum/controller/subsystem/points_of_interest, + /datum/controller/subsystem/shuttles + ) + var/list/currentrun = list() var/log_extensively = FALSE var/list/timelog = list() @@ -96,22 +102,14 @@ SUBSYSTEM_DEF(mobs) log_game(msg) log_world(msg) -/datum/controller/subsystem/mobs/fail() - ..() - log_recent() - -/datum/controller/subsystem/mobs/critfail() - ..() - log_recent() - /datum/controller/subsystem/mobs/proc/report_death(var/mob/living/L) if(!L) return if(!L.key || !L.mind) return - if(!ticker || !ticker.mode) + if(!SSticker || !SSticker.mode) return - ticker.mode.check_win() + SSticker.mode.check_win() // Don't bother with the rest if we've not got a DB to do anything with if(!CONFIG_GET(flag/enable_stat_tracking) || !CONFIG_GET(flag/sql_enabled)) diff --git a/code/controllers/subsystems/nightshift.dm b/code/controllers/subsystems/nightshift.dm index ecf7fa30e7..201a4794b9 100644 --- a/code/controllers/subsystems/nightshift.dm +++ b/code/controllers/subsystems/nightshift.dm @@ -1,6 +1,8 @@ SUBSYSTEM_DEF(nightshift) name = "Night Shift" - init_order = INIT_ORDER_NIGHTSHIFT + dependencies = list( + /datum/controller/subsystem/lighting + ) priority = FIRE_PRIORITY_NIGHTSHIFT wait = 60 SECONDS flags = SS_NO_TICK_CHECK diff --git a/code/controllers/subsystems/overlays.dm b/code/controllers/subsystems/overlays.dm index d1f1247081..236ee537f3 100644 --- a/code/controllers/subsystems/overlays.dm +++ b/code/controllers/subsystems/overlays.dm @@ -3,7 +3,9 @@ SUBSYSTEM_DEF(overlays) flags = SS_TICKER wait = 1 // SS_TICKER - Ticks priority = FIRE_PRIORITY_OVERLAYS - init_order = INIT_ORDER_OVERLAY + dependencies = list( + /datum/controller/subsystem/atoms + ) /// The queue of atoms that need overlay updates. var/static/tmp/list/queue = list() diff --git a/code/controllers/subsystems/overmap_renamer_vr.dm b/code/controllers/subsystems/overmap_renamer_vr.dm index 149ece296e..460e172f46 100644 --- a/code/controllers/subsystems/overmap_renamer_vr.dm +++ b/code/controllers/subsystems/overmap_renamer_vr.dm @@ -4,8 +4,12 @@ Readme at code\modules\awaymissions\overmap_renamer\readme.md SUBSYSTEM_DEF(overmap_renamer) name = "Overmap Renamer" - init_order = INIT_ORDER_MAPRENAME //Loaded very late in initializations. Must come before mapping and objs. Uses both as inputs. - runlevels = RUNLEVEL_INIT + //Loaded very late in initializations. Must come before mapping and objs. Uses both as inputs. + init_stage = INITSTAGE_LAST + dependencies = list( + /datum/controller/subsystem/skybox + ) + runlevels = RUNLEVEL_SETUP flags = SS_NO_FIRE diff --git a/code/controllers/subsystems/persistence.dm b/code/controllers/subsystems/persistence.dm index 2b1cef0e77..575e81dfe1 100644 --- a/code/controllers/subsystems/persistence.dm +++ b/code/controllers/subsystems/persistence.dm @@ -1,7 +1,12 @@ SUBSYSTEM_DEF(persistence) name = "Persistence" - init_order = INIT_ORDER_PERSISTENCE + dependencies = list( + /datum/controller/subsystem/mapping, + /datum/controller/subsystem/atoms, + /datum/controller/subsystem/points_of_interest + ) flags = SS_NO_FIRE + var/list/tracking_values = list() var/list/persistence_datums = list() diff --git a/code/controllers/subsystems/ping.dm b/code/controllers/subsystems/ping.dm index 8f4836290a..2ef44bc167 100644 --- a/code/controllers/subsystems/ping.dm +++ b/code/controllers/subsystems/ping.dm @@ -6,7 +6,7 @@ SUBSYSTEM_DEF(ping) name = "Ping" priority = FIRE_PRIORITY_PING - // init_stage = INITSTAGE_EARLY + init_stage = INITSTAGE_EARLY wait = 4 SECONDS flags = SS_NO_INIT runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT diff --git a/code/controllers/subsystems/planets.dm b/code/controllers/subsystems/planets.dm index 4ad613b4ce..b3d319bad4 100644 --- a/code/controllers/subsystems/planets.dm +++ b/code/controllers/subsystems/planets.dm @@ -1,10 +1,12 @@ SUBSYSTEM_DEF(planets) name = "Planets" - init_order = INIT_ORDER_PLANETS priority = FIRE_PRIORITY_PLANETS wait = 2 SECONDS flags = SS_BACKGROUND runlevels = RUNLEVEL_GAME | RUNLEVEL_POSTGAME + dependencies = list( + /datum/controller/subsystem/plants + ) var/static/list/planets = list() var/static/list/z_to_planet = list() @@ -75,7 +77,7 @@ SUBSYSTEM_DEF(planets) if(MC_TICK_CHECK) return - #ifndef UNIT_TEST // Don't be updating temperatures and such during unit tests + #ifndef UNIT_TESTS // Don't be updating temperatures and such during unit tests var/list/needs_temp_update = src.needs_temp_update while(needs_temp_update.len) var/datum/planet/P = needs_temp_update[needs_temp_update.len] diff --git a/code/controllers/subsystems/plants.dm b/code/controllers/subsystems/plants.dm index 922a2c42e5..9d266640cf 100644 --- a/code/controllers/subsystems/plants.dm +++ b/code/controllers/subsystems/plants.dm @@ -2,7 +2,9 @@ SUBSYSTEM_DEF(plants) name = "Plants" - init_order = INIT_ORDER_PLANTS + dependencies = list( + /datum/controller/subsystem/mapping + ) priority = FIRE_PRIORITY_PLANTS wait = PLANT_TICK_TIME diff --git a/code/controllers/subsystems/pois.dm b/code/controllers/subsystems/pois.dm index e5bfbbecf0..053e6abfc1 100644 --- a/code/controllers/subsystems/pois.dm +++ b/code/controllers/subsystems/pois.dm @@ -3,8 +3,10 @@ SUBSYSTEM_DEF(points_of_interest) name = "Points of Interest" wait = 1 SECONDS priority = FIRE_PRIORITY_POIS - init_order = INIT_ORDER_POIS runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT //POIs can be loaded mid-round. + dependencies = list( + /datum/controller/subsystem/holomaps + ) var/list/obj/effect/landmark/poi_loader/poi_queue = list() /datum/controller/subsystem/points_of_interest/Initialize() diff --git a/code/controllers/subsystems/processing/instruments.dm b/code/controllers/subsystems/processing/instruments.dm index e3a4802f11..af6bacc2fc 100644 --- a/code/controllers/subsystems/processing/instruments.dm +++ b/code/controllers/subsystems/processing/instruments.dm @@ -1,7 +1,6 @@ PROCESSING_SUBSYSTEM_DEF(instruments) name = "Instruments" wait = 0.5 - init_order = INIT_ORDER_INSTRUMENTS flags = SS_KEEP_TIMING priority = FIRE_PRIORITY_INSTRUMENTS /// List of all instrument data, associative id = datum diff --git a/code/controllers/subsystems/processing/processing.dm b/code/controllers/subsystems/processing/processing.dm index 51c1afe015..42ebfba08c 100644 --- a/code/controllers/subsystems/processing/processing.dm +++ b/code/controllers/subsystems/processing/processing.dm @@ -96,14 +96,6 @@ SUBSYSTEM_DEF(processing) log_game(msg) log_world(msg) -/datum/controller/subsystem/processing/fail() - ..() - log_recent() - -/datum/controller/subsystem/processing/critfail() - ..() - log_recent() - /datum/proc/DebugSubsystemProcess(var/wait, var/times_fired, var/datum/controller/subsystem/processing/subsystem) subsystem.debug_last_thing = src var/start_tick = world.time diff --git a/code/controllers/subsystems/profiler.dm b/code/controllers/subsystems/profiler.dm new file mode 100644 index 0000000000..578bb8bc08 --- /dev/null +++ b/code/controllers/subsystems/profiler.dm @@ -0,0 +1,69 @@ +SUBSYSTEM_DEF(profiler) + name = "Profiler" + init_stage = INITSTAGE_FIRST + runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY + wait = 300 SECONDS + var/fetch_cost = 0 + var/write_cost = 0 + +/datum/controller/subsystem/profiler/stat_entry(msg) + msg += "F:[round(fetch_cost,1)]ms" + msg += "|W:[round(write_cost,1)]ms" + return msg + +/datum/controller/subsystem/profiler/Initialize() + if(CONFIG_GET(flag/auto_profile)) + StartProfiling() + else + StopProfiling() //Stop the early start profiler + wait = CONFIG_GET(number/profiler_interval) + return SS_INIT_SUCCESS + +/datum/controller/subsystem/profiler/OnConfigLoad() + if(CONFIG_GET(flag/auto_profile)) + StartProfiling() + can_fire = TRUE + else + StopProfiling() + can_fire = FALSE + +/datum/controller/subsystem/profiler/fire() + DumpFile() + +/datum/controller/subsystem/profiler/Shutdown() + if(CONFIG_GET(flag/auto_profile)) + DumpFile(allow_yield = FALSE) + world.Profile(PROFILE_CLEAR, type = "sendmaps") + return ..() + +/datum/controller/subsystem/profiler/proc/StartProfiling() + world.Profile(PROFILE_START) + world.Profile(PROFILE_START, type = "sendmaps") + +/datum/controller/subsystem/profiler/proc/StopProfiling() + world.Profile(PROFILE_STOP) + world.Profile(PROFILE_STOP, type = "sendmaps") + +/datum/controller/subsystem/profiler/proc/DumpFile(allow_yield = TRUE) + var/timer = TICK_USAGE_REAL + var/current_profile_data = world.Profile(PROFILE_REFRESH, format = "json") + var/current_sendmaps_data = world.Profile(PROFILE_REFRESH, type = "sendmaps", format="json") + fetch_cost = MC_AVERAGE(fetch_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) + if(allow_yield) + CHECK_TICK + + if(!length(current_profile_data)) //Would be nice to have explicit proc to check this + stack_trace("Warning, profiling stopped manually before dump.") + var/prof_file = file("[GLOB.log_directory]/profiler/profiler-[round(world.time * 0.1, 10)].json") + if(fexists(prof_file)) + fdel(prof_file) + if(!length(current_sendmaps_data)) //Would be nice to have explicit proc to check this + stack_trace("Warning, sendmaps profiling stopped manually before dump.") + var/sendmaps_file = file("[GLOB.log_directory]/profiler/sendmaps-[round(world.time * 0.1, 10)].json") + if(fexists(sendmaps_file)) + fdel(sendmaps_file) + + timer = TICK_USAGE_REAL + WRITE_FILE(prof_file, current_profile_data) + WRITE_FILE(sendmaps_file, current_sendmaps_data) + write_cost = MC_AVERAGE(write_cost, TICK_DELTA_TO_MS(TICK_USAGE_REAL - timer)) diff --git a/code/controllers/subsystems/research.dm b/code/controllers/subsystems/research.dm index cc581440c4..467304bd78 100644 --- a/code/controllers/subsystems/research.dm +++ b/code/controllers/subsystems/research.dm @@ -2,11 +2,13 @@ SUBSYSTEM_DEF(research) name = "Research" // priority = FIRE_PRIORITY_RESEARCH - init_order = INIT_ORDER_RESEARCH wait = 10 // dependencies = list( // /datum/controller/subsystem/processing/station, // ) + dependencies = list( + /datum/controller/subsystem/mapping + ) //TECHWEB STATIC var/list/techweb_nodes = list() //associative id = node datum var/list/techweb_designs = list() //associative id = node datum diff --git a/code/controllers/subsystems/robot_sprites.dm b/code/controllers/subsystems/robot_sprites.dm index fbb1ab41c1..a7f283c0e3 100644 --- a/code/controllers/subsystems/robot_sprites.dm +++ b/code/controllers/subsystems/robot_sprites.dm @@ -1,6 +1,8 @@ SUBSYSTEM_DEF(robot_sprites) name = "Robot Sprites" - init_order = INIT_ORDER_ROBOT_SPRITES + dependencies = list( + /datum/controller/subsystem/garbage + ) flags = SS_NO_FIRE var/list/all_cyborg_sprites = list() var/list/cyborg_sprites_by_module = list() diff --git a/code/controllers/subsystems/server_maint.dm b/code/controllers/subsystems/server_maint.dm index a6ed6e471a..0db62b2606 100644 --- a/code/controllers/subsystems/server_maint.dm +++ b/code/controllers/subsystems/server_maint.dm @@ -5,8 +5,7 @@ SUBSYSTEM_DEF(server_maint) wait = 6 flags = SS_POST_FIRE_TIMING priority = FIRE_PRIORITY_SERVER_MAINT - init_order = INIT_ORDER_SERVER_MAINT - //init_stage = INITSTAGE_EARLY + init_stage = INITSTAGE_FIRST runlevels = RUNLEVEL_LOBBY | RUNLEVELS_DEFAULT var/list/currentrun ///Associated list of list names to lists to clear of nulls diff --git a/code/controllers/subsystems/shuttles.dm b/code/controllers/subsystems/shuttles.dm index ebd4cdcc5c..ac8825feb4 100644 --- a/code/controllers/subsystems/shuttles.dm +++ b/code/controllers/subsystems/shuttles.dm @@ -8,7 +8,11 @@ SUBSYSTEM_DEF(shuttles) name = "Shuttles" wait = 2 SECONDS priority = FIRE_PRIORITY_SHUTTLES - init_order = INIT_ORDER_SHUTTLES + dependencies = list( + /datum/controller/subsystem/machines, + /datum/controller/subsystem/atoms, + /datum/controller/subsystem/radio + ) flags = SS_KEEP_TIMING|SS_NO_TICK_CHECK runlevels = RUNLEVEL_GAME|RUNLEVEL_POSTGAME diff --git a/code/controllers/subsystems/skybox.dm b/code/controllers/subsystems/skybox.dm index 9692c55160..fc11fff590 100644 --- a/code/controllers/subsystems/skybox.dm +++ b/code/controllers/subsystems/skybox.dm @@ -2,7 +2,7 @@ //Exists to handle a few global variables that change enough to justify this. Technically a parallax, but it exhibits a skybox effect. SUBSYSTEM_DEF(skybox) name = "Space skybox" - init_order = INIT_ORDER_SKYBOX + init_stage = INITSTAGE_LAST flags = SS_NO_FIRE var/static/list/skybox_cache = list() @@ -64,7 +64,7 @@ SUBSYSTEM_DEF(skybox) speedspace_cache["EW_[i]"] = MA //Over-the-edge images - for (var/dir in GLOB.alldirs) + for (var/dir in ALL_POSSIBLE_DIRS) var/mutable_appearance/MA = new(normal_space) var/matrix/M = matrix() var/horizontal = (dir & (WEST|EAST)) @@ -93,7 +93,7 @@ SUBSYSTEM_DEF(skybox) return SS_INIT_SUCCESS /datum/controller/subsystem/skybox/proc/get_skybox(z) - if(!subsystem_initialized) + if(!initialized) return // WAIT if(!skybox_cache["[z]"]) skybox_cache["[z]"] = generate_skybox(z) diff --git a/code/controllers/subsystems/sounds.dm b/code/controllers/subsystems/sounds.dm index 2c55777473..e2840eace8 100644 --- a/code/controllers/subsystems/sounds.dm +++ b/code/controllers/subsystems/sounds.dm @@ -3,7 +3,9 @@ SUBSYSTEM_DEF(sounds) name = "Sounds" flags = SS_NO_FIRE - init_order = INIT_ORDER_SOUNDS + dependencies = list( + /datum/controller/subsystem/mapping + ) var/static/using_channels_max = CHANNEL_HIGHEST_AVAILABLE //BYOND max channels /// Amount of channels to reserve for random usage rather than reservations being allowed to reserve all channels. Also a nice safeguard for when someone screws up. var/static/random_channels_min = 50 diff --git a/code/controllers/subsystems/sqlite.dm b/code/controllers/subsystems/sqlite.dm index 725c4acf39..bb2da0b118 100644 --- a/code/controllers/subsystems/sqlite.dm +++ b/code/controllers/subsystems/sqlite.dm @@ -3,7 +3,9 @@ // however this makes it a lot easier to test, and it is natively supported by BYOND. SUBSYSTEM_DEF(sqlite) name = "SQLite" - init_order = INIT_ORDER_SQLITE + dependencies = list( + /datum/controller/subsystem/dbcore + ) flags = SS_NO_FIRE var/database/sqlite_db = null diff --git a/code/controllers/subsystems/starmover.dm b/code/controllers/subsystems/starmover.dm index f83059e4fc..36a7678a52 100644 --- a/code/controllers/subsystems/starmover.dm +++ b/code/controllers/subsystems/starmover.dm @@ -3,9 +3,11 @@ // SUBSYSTEM_DEF(starmover) name = "Shuttle Star Movement" - init_order = INIT_ORDER_STARMOVER priority = FIRE_PRIORITY_STARMOVER wait = 1 // This needs to be done pretty quickly + dependencies = list( + /datum/controller/subsystem/points_of_interest + ) var/list/zqueue = list() var/list/current_movement = null //list used to track which zlevels are being 'moved' by the proc below diff --git a/code/controllers/subsystems/statpanel.dm b/code/controllers/subsystems/statpanel.dm index 0639d31400..b3c5cd7d66 100644 --- a/code/controllers/subsystems/statpanel.dm +++ b/code/controllers/subsystems/statpanel.dm @@ -1,8 +1,6 @@ SUBSYSTEM_DEF(statpanels) name = "Stat Panels" wait = 4 - init_order = INIT_ORDER_STATPANELS - //init_stage = INITSTAGE_EARLY priority = FIRE_PRIORITY_STATPANEL runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY flags = SS_NO_INIT @@ -42,6 +40,15 @@ SUBSYSTEM_DEF(statpanels) var/ETA = emergency_shuttle.get_status_panel_eta() if(ETA) global_data += "[ETA]" + + if(SSticker.reboot_timer) + var/reboot_time = timeleft(SSticker.reboot_timer) + if(reboot_time) + global_data += "Reboot: [DisplayTimeText(reboot_time, 1)]" + // admin must have delayed round end + else if(SSticker.ready_for_reboot) + global_data += "Reboot: DELAYED" + src.currentrun = GLOB.clients.Copy() mc_data = null @@ -140,7 +147,7 @@ SUBSYSTEM_DEF(statpanels) var/coord_entry = COORD(eye_turf) if(!mc_data) generate_mc_data() - target.stat_panel.send_message("update_mc", list(mc_data = mc_data, coord_entry = coord_entry)) + target.stat_panel.send_message("update_mc", list("mc_data" = mc_data, "coord_entry" = coord_entry)) /datum/controller/subsystem/statpanels/proc/set_examine_tab(client/target) var/description_holders = target.description_holders @@ -206,13 +213,31 @@ SUBSYSTEM_DEF(statpanels) list("CPU:", world.cpu), list("Instances:", "[num2text(world.contents.len, 10)]"), list("World Time:", "[world.time]"), - list("Globals:", GLOB.stat_entry(), "\ref[GLOB]"), - list("[config]:", config.stat_entry(), "\ref[config]"), + list("Globals:", GLOB.stat_entry(), text_ref(GLOB)), + list("[config]:", config.stat_entry(), text_ref(config)), list("Byond:", "(FPS:[world.fps]) (TickCount:[world.time/world.tick_lag]) (TickDrift:[round(Master.tickdrift,1)]([round((Master.tickdrift/(world.time/world.tick_lag))*100,0.1)]%)) (Internal Tick Usage: [round(MAPTICK_LAST_INTERNAL_TICK_USAGE,0.1)]%)"), - list("Master Controller:", Master.stat_entry(), "\ref[Master]"), - list("Failsafe Controller:", Failsafe.stat_entry(), "\ref[Failsafe]"), + list("Master Controller:", Master.stat_entry(), text_ref(Master)), + list("Failsafe Controller:", Failsafe.stat_entry(), text_ref(Failsafe)), list("","") ) +#if defined(MC_TAB_TRACY_INFO) || defined(SPACEMAN_DMM) + var/static/tracy_dll + var/static/tracy_present + if(isnull(tracy_dll)) + tracy_dll = TRACY_DLL_PATH + tracy_present = fexists(tracy_dll) + if(tracy_present) + if(Tracy.enabled) + mc_data.Insert(2, list(list("byond-tracy:", "Active (reason: [Tracy.init_reason || "N/A"])"))) + else if(Tracy.error) + mc_data.Insert(2, list(list("byond-tracy:", "Errored ([Tracy.error])"))) + else if(fexists(TRACY_ENABLE_PATH)) + mc_data.Insert(2, list(list("byond-tracy:", "Queued for next round"))) + else + mc_data.Insert(2, list(list("byond-tracy:", "Inactive"))) + else + mc_data.Insert(2, list(list("byond-tracy:", "[tracy_dll] not present"))) +#endif for(var/datum/controller/subsystem/sub_system as anything in Master.subsystems) mc_data[++mc_data.len] = list("\[[sub_system.state_letter()]][sub_system.name]", sub_system.stat_entry(), "\ref[sub_system]") mc_data[++mc_data.len] = list("Camera Net", "Cameras: [global.cameranet.cameras.len] | Chunks: [global.cameranet.chunks.len]", "\ref[global.cameranet]") diff --git a/code/controllers/subsystems/ticker.dm b/code/controllers/subsystems/ticker.dm index 343cd44950..240d05f82b 100644 --- a/code/controllers/subsystems/ticker.dm +++ b/code/controllers/subsystems/ticker.dm @@ -1,59 +1,85 @@ -// -// Ticker controls the state of the game, being responsible for round start, game mode, and round end. -// SUBSYSTEM_DEF(ticker) - name = "Gameticker" - wait = 2 SECONDS - init_order = INIT_ORDER_TICKER + name = "Ticker" priority = FIRE_PRIORITY_TICKER - flags = SS_NO_TICK_CHECK | SS_KEEP_TIMING - runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME | RUNLEVEL_POSTGAME // Every runlevel! + flags = SS_KEEP_TIMING + runlevels = RUNLEVEL_LOBBY | RUNLEVEL_SETUP | RUNLEVEL_GAME - var/const/restart_timeout = 4 MINUTES // Default time to wait before rebooting in desiseconds. - var/current_state = GAME_STATE_INIT // We aren't even at pregame yet // TODO replace with CURRENT_GAME_STATE + /// state of current round (used by process()) Use the defines GAME_STATE_* ! + var/current_state = GAME_STATE_STARTUP + /// Boolean to track if round should be forcibly ended next ticker tick. + /// Set by admin intervention ([ADMIN_FORCE_END_ROUND]) + /// or a "round-ending" event, like summoning Nar'Sie, a blob victory, the nuke going off, etc. ([FORCE_END_ROUND]) + var/force_ending = END_ROUND_AS_NORMAL + /// If TRUE, there is no lobby phase, the game starts immediately. + var/start_immediately = FALSE + /// Boolean to track and check if our subsystem setup is done. + var/setup_done = FALSE - /* Relies upon the following globals (TODO move those in here) */ - // var/GLOB.master_mode = "extended" //The underlying game mode (so "secret" or the voted mode). - // Set by SSvote when VOTE_GAMEMODE finishes. - // var/round_progressing = 1 //Whether the lobby clock is ticking down. + var/hide_mode = FALSE + var/datum/game_mode/mode = null - var/pregame_timeleft = 0 // Time remaining until game starts in seconds. Set by config - var/start_immediately = FALSE // If true there is no lobby phase, the game starts immediately. + var/login_music //music played in pregame lobby + var/round_end_sound //music/jingle played when the world reboots + var/round_end_sound_sent = TRUE //If all clients have loaded it - var/hide_mode = FALSE // If the true game mode should be hidden (because we chose "secret") - var/datum/game_mode/mode = null // The actual gamemode, if selected. + var/list/datum/mind/minds = list() //The characters in the game. Used for objective tracking. - var/end_game_state = END_GAME_NOT_OVER // Track where we are ending game/round - var/restart_timeleft // Time remaining until restart in desiseconds - var/last_restart_notify // world.time of last restart warning. - var/delay_end = FALSE // If set, the round will not restart on its own. + var/delay_end = FALSE //if set true, the round will not restart on it's own + var/admin_delay_notice = "" //a message to display to anyone who tries to restart the world after a delay + var/ready_for_reboot = FALSE //all roundend preparation done with, all that's left is reboot - // var/login_music // music played in pregame lobby // VOREStation Edit - We do music differently + var/tipped = FALSE //Did we broadcast the tip of the day yet? + var/selected_tip // What will be the tip of the day? - var/list/datum/mind/minds = list() // The people in the game. Used for objective tracking. + var/timeLeft //pregame timer + var/start_at - var/random_players = FALSE // If set to nonzero, ALL players who latejoin or declare-ready join will have random appearances/genders + var/gametime_offset = 432000 //Deciseconds to add to world.time for station time. + var/station_time_rate_multiplier = 12 //factor of station time progressal vs real time. - // TODO - Should this go here or in the job subsystem? - var/triai = FALSE // Global flag for Triumvirate AI being enabled + /// Num of players, used for pregame stats on statpanel + var/totalPlayers = 0 + /// Num of ready players, used for pregame stats on statpanel (only viewable by admins) + var/totalPlayersReady = 0 + /// Num of ready admins, used for pregame stats on statpanel (only viewable by admins) + var/total_admins_ready = 0 - //station_explosion used to be a variable for every mob's hud. Which was a waste! - //Now we have a general cinematic centrally held within the gameticker....far more efficient! - var/obj/screen/cinematic = null + var/queue_delay = 0 + var/list/queued_players = list() //used for join queues when the server exceeds the hard population cap + + /// What is going to be reported to other stations at end of round? + var/news_report + + + var/roundend_check_paused = FALSE var/round_start_time = 0 var/list/round_start_events + var/list/round_end_events + var/mode_result = "undefined" + var/end_state = "undefined" + /// People who have been commended and will receive a heart + var/list/hearts + /// Why an emergency shuttle was called + var/emergency_reason -// This global variable exists for legacy support so we don't have to rename every 'ticker' to 'SSticker' yet. -var/global/datum/controller/subsystem/ticker/ticker -/datum/controller/subsystem/ticker/PreInit() - global.ticker = src // TODO - Remove this! Change everything to point at SSticker intead + /// ID of round reboot timer, if it exists + var/reboot_timer = null + + /// ### LEGACY VARS ### + /// Default time to wait before rebooting in desiseconds. + var/const/restart_timeout = 4 MINUTES + /// Track where we are ending game/round + var/end_game_state = END_GAME_NOT_OVER + /// Time remaining until restart in desiseconds + var/restart_timeleft + /// world.time of last restart warning. + var/last_restart_notify /datum/controller/subsystem/ticker/Initialize() - pregame_timeleft = CONFIG_GET(number/pregame_time) - send2mainirc("Server lobby is loaded and open at byond://[CONFIG_GET(string/serverurl) ? CONFIG_GET(string/serverurl) : (CONFIG_GET(string/server) ? CONFIG_GET(string/server) : "[world.address]:[world.port]")]") + start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10) SSwebhooks.send( WEBHOOK_ROUNDPREP, list( @@ -61,63 +87,185 @@ var/global/datum/controller/subsystem/ticker/ticker "url" = get_world_url() ) ) - GLOB.autospeaker = new (null, FALSE, null, null, TRUE) //Set up Global Announcer + return SS_INIT_SUCCESS /datum/controller/subsystem/ticker/fire(resumed = FALSE) switch(current_state) - if(GAME_STATE_INIT) - pregame_welcome() + if(GAME_STATE_STARTUP) + if(Master.initializations_finished_with_no_players_logged_in) + start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10) + for(var/client/C in GLOB.clients) + window_flash(C, ignorepref = TRUE) //let them know lobby has opened up. + to_chat(world, span_notice("Welcome to [station_name()]!")) + //for(var/channel_tag in CONFIG_GET(str_list/channel_announce_new_game)) + // send2chat(new /datum/tgs_message_content("New round starting on [SSmapping.current_map.map_name]!"), channel_tag) current_state = GAME_STATE_PREGAME + SEND_SIGNAL(src, COMSIG_TICKER_ENTER_PREGAME) + + fire() if(GAME_STATE_PREGAME) - pregame_tick() + //lobby stats for statpanels + if(isnull(timeLeft)) + timeLeft = max(0,start_at - world.time) + totalPlayers = LAZYLEN(GLOB.new_player_list) + totalPlayersReady = 0 + total_admins_ready = 0 + for(var/mob/new_player/player as anything in GLOB.new_player_list) + if(player.ready == PLAYER_READY_TO_PLAY) + ++totalPlayersReady + if(player.client?.holder) + ++total_admins_ready + + if(start_immediately) + timeLeft = 0 + + //countdown + if(timeLeft < 0) + return + timeLeft -= wait + + //if(timeLeft <= 300 && !tipped) + // send_tip_of_the_round(world, selected_tip) + // tipped = TRUE + + if(timeLeft <= 0) + SEND_SIGNAL(src, COMSIG_TICKER_ENTER_SETTING_UP) + current_state = GAME_STATE_SETTING_UP + Master.SetRunLevel(RUNLEVEL_SETUP) + if(start_immediately) + fire() + if(GAME_STATE_SETTING_UP) - setup_tick() + if(!setup()) + //setup failed + current_state = GAME_STATE_STARTUP + start_at = world.time + (CONFIG_GET(number/lobby_countdown) * 10) + timeLeft = null + Master.SetRunLevel(RUNLEVEL_LOBBY) + SEND_SIGNAL(src, COMSIG_TICKER_ERROR_SETTING_UP) + if(GAME_STATE_PLAYING) - playing_tick() + mode.process() // So THIS is where we run mode.process() huh? Okay + + if(mode.explosion_in_progress) + return // wait until explosion is done. + + if(force_ending) + current_state = GAME_STATE_FINISHED + declare_completion(force_ending) + Master.SetRunLevel(RUNLEVEL_POSTGAME) + else + // Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option) + var/game_finished = FALSE + var/mode_finished = FALSE + if (CONFIG_GET(flag/continuous_rounds)) // Game keeps going after mode ends. + game_finished = (emergency_shuttle.returned() || mode.station_was_nuked) + mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished. + else // Game ends when mode does + game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || GLOB.universe_has_ended + mode_finished = game_finished + + if(game_finished && mode_finished) + end_game_state = END_GAME_READY_TO_END + current_state = GAME_STATE_FINISHED + Master.SetRunLevel(RUNLEVEL_POSTGAME) + INVOKE_ASYNC(src, PROC_REF(declare_completion)) + else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED)) + end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once! + mode.cleanup() + //call a transfer shuttle vote + to_world(span_boldannounce("The round has ended!")) + SSvote.start_vote(new /datum/vote/crew_transfer) + + // FIXME: IMPROVE THIS LATER! if(GAME_STATE_FINISHED) post_game_tick() -/datum/controller/subsystem/ticker/proc/pregame_welcome() - to_world(span_boldannounce(span_notice("Welcome to the pregame lobby!"))) - to_world(span_boldannounce(span_notice("Please set up your character and select ready. The round will start in [pregame_timeleft] seconds."))) - world << sound('sound/misc/server-ready.ogg', volume = 100) + if (world.time - last_restart_notify >= 1 MINUTE && !delay_end) + to_world(span_boldannounce("Restarting in [round(restart_timeleft/600, 1)] minute\s.")) + last_restart_notify = world.time -// Called during GAME_STATE_PREGAME (RUNLEVEL_LOBBY) -/datum/controller/subsystem/ticker/proc/pregame_tick() - if(GLOB.round_progressing && last_fire) - pregame_timeleft -= (world.time - last_fire) / (1 SECOND) +/datum/controller/subsystem/ticker/proc/setup() + to_chat(world, span_boldannounce("Starting game...")) + var/init_start = world.timeofday - if(start_immediately) - pregame_timeleft = 0 - else if(SSvote.active_vote) - return // vote still going, wait for it. + CHECK_TICK + setup_choose_gamemode() + // TODO - // Time to start the game! - if(pregame_timeleft <= 0) - current_state = GAME_STATE_SETTING_UP - Master.SetRunLevel(RUNLEVEL_SETUP) - if(start_immediately) - fire() // Don't wait for next tick, do it now! - return + CHECK_TICK + setup_economy() + create_characters() //Create player characters + collect_minds() + equip_characters() - //if(pregame_timeleft <= config.vote_autogamemode_timeleft && !SSvote.gamemode_vote_called) - //.autogamemode() // Start the game mode vote (if we haven't had one already) + // data_core.manifest() -// Called during GAME_STATE_SETTING_UP (RUNLEVEL_SETUP) -/datum/controller/subsystem/ticker/proc/setup_tick(resumed = FALSE) - round_start_time = world.time // otherwise round_start_time would be 0 for the signals - if(!setup_choose_gamemode()) - // It failed, go back to lobby state and re-send the welcome message - pregame_timeleft = CONFIG_GET(number/pregame_time) - // SSvote.gamemode_vote_called = FALSE // Allow another autogamemode vote - current_state = GAME_STATE_PREGAME - Master.SetRunLevel(RUNLEVEL_LOBBY) - pregame_welcome() - return - // If we got this far we succeeded in picking a game mode. Punch it! - setup_startgame() - return + for(var/I in round_start_events) + var/datum/callback/cb = I + cb.InvokeAsync() + LAZYCLEARLIST(round_start_events) + + round_start_time = world.time //otherwise round_start_time would be 0 for the signals + SEND_SIGNAL(src, COMSIG_TICKER_ROUND_STARTING, world.time) + callHook("roundstart") + + log_world("Game start took [(world.timeofday - init_start)/10]s") + INVOKE_ASYNC(SSdbcore, TYPE_PROC_REF(/datum/controller/subsystem/dbcore,SetRoundStart)) + + to_chat(world, span_notice(span_bold("Welcome to [station_name()], enjoy your stay!"))) + world << sound('sound/AI/welcome.ogg') // Skie + //SEND_SOUND(world, sound(SSstation.announcer.get_rand_welcome_sound())) + + current_state = GAME_STATE_PLAYING + Master.SetRunLevel(RUNLEVEL_GAME) + + //Holiday Round-start stuff ~Carn + Holiday_Game_Start() + + // TODO END + + PostSetup() + + return TRUE + +/datum/controller/subsystem/ticker/proc/PostSetup() + set waitfor = FALSE + mode.post_setup() + // TODO + + var/list/adm = get_admin_counts() + var/list/allmins = adm["present"] + // TODO: IMPLEMENT: send2adminchat("Server", "Round [GLOB.round_id ? "#[GLOB.round_id]" : ""] has started[allmins.len ? ".":" with no active admins online!"]") + if(!allmins.len) + send2adminirc("A round has started with no admins online.") + + setup_done = TRUE + // TODO START + + // TODO END + for(var/obj/effect/landmark/start/S in GLOB.landmarks_list) + //Deleting Startpoints but we need the ai point to AI-ize people later + if (S.name != "AI") + qdel(S) + + if(CONFIG_GET(flag/sql_enabled)) + statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE + +//These callbacks will fire after roundstart key transfer +/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb) + if(!HasRoundStarted()) + LAZYADD(round_start_events, cb) + else + cb.InvokeAsync() + +//These callbacks will fire before roundend report +/datum/controller/subsystem/ticker/proc/OnRoundend(datum/callback/cb) + if(current_state >= GAME_STATE_FINISHED) + cb.InvokeAsync() + else + LAZYADD(round_end_events, cb) // Formerly the first half of setup() - The part that chooses the game mode. // Returns 0 if failed to pick a mode, otherwise 1 @@ -170,74 +318,6 @@ var/global/datum/controller/subsystem/ticker/ticker src.mode.announce() return 1 -// Formerly the second half of setup() - The part that actually initializes everything and starts the game. -/datum/controller/subsystem/ticker/proc/setup_startgame() - setup_economy() - create_characters() //Create player characters and transfer them. - collect_minds() - equip_characters() -// data_core.manifest() - - callHook("roundstart") - for(var/I in round_start_events) - var/datum/callback/cb = I - cb.InvokeAsync() - LAZYCLEARLIST(round_start_events) - - spawn(0)//Forking here so we dont have to wait for this to finish - mode.post_setup() - //Cleanup some stuff - for(var/obj/effect/landmark/start/S in GLOB.landmarks_list) - //Deleting Startpoints but we need the ai point to AI-ize people later - if (S.name != "AI") - qdel(S) - to_world(span_boldannounce(span_notice("Enjoy the game!"))) - world << sound('sound/AI/welcome.ogg') // Skie - //Holiday Round-start stuff ~Carn - Holiday_Game_Start() - - var/list/adm = get_admin_counts() - if(adm["total"] == 0) - send2adminirc("A round has started with no admins online.") - - current_state = GAME_STATE_PLAYING - Master.SetRunLevel(RUNLEVEL_GAME) - - if(CONFIG_GET(flag/sql_enabled)) - statistic_cycle() // Polls population totals regularly and stores them in an SQL DB -- TLE - - return 1 - - -// Called during GAME_STATE_PLAYING (RUNLEVEL_GAME) -/datum/controller/subsystem/ticker/proc/playing_tick(resumed = FALSE) - mode.process() // So THIS is where we run mode.process() huh? Okay - - if(mode.explosion_in_progress) - return // wait until explosion is done. - - // Calculate if game and/or mode are finished (Complicated by the continuous_rounds config option) - var/game_finished = FALSE - var/mode_finished = FALSE - if (CONFIG_GET(flag/continuous_rounds)) // Game keeps going after mode ends. - game_finished = (emergency_shuttle.returned() || mode.station_was_nuked) - mode_finished = ((end_game_state >= END_GAME_MODE_FINISHED) || mode.check_finished()) // Short circuit if already finished. - else // Game ends when mode does - game_finished = (mode.check_finished() || (emergency_shuttle.returned() && emergency_shuttle.evac == 1)) || GLOB.universe_has_ended - mode_finished = game_finished - - if(game_finished && mode_finished) - end_game_state = END_GAME_READY_TO_END - current_state = GAME_STATE_FINISHED - Master.SetRunLevel(RUNLEVEL_POSTGAME) - INVOKE_ASYNC(src, PROC_REF(declare_completion)) - else if (mode_finished && (end_game_state < END_GAME_MODE_FINISHED)) - end_game_state = END_GAME_MODE_FINISHED // Only do this cleanup once! - mode.cleanup() - //call a transfer shuttle vote - to_world(span_boldannounce("The round has ended!")) - SSvote.start_vote(new /datum/vote/crew_transfer) - // Called during GAME_STATE_FINISHED (RUNLEVEL_POSTGAME) /datum/controller/subsystem/ticker/proc/post_game_tick() switch(end_game_state) @@ -259,148 +339,6 @@ var/global/datum/controller/subsystem/ticker/ticker end_game_state = END_GAME_ENDING return - if(END_GAME_ENDING) - restart_timeleft -= (world.time - last_fire) - if(delay_end) - to_world(span_boldannounce("An admin has delayed the round end.")) - end_game_state = END_GAME_DELAYED - else if(restart_timeleft <= 0) - to_world(span_boldannounce("Restarting world!")) - sleep(5) - world.Reboot() - else if (world.time - last_restart_notify >= 1 MINUTE) - to_world(span_boldannounce("Restarting in [round(restart_timeleft/600, 1)] minute\s.")) - last_restart_notify = world.time - return - if(END_GAME_DELAYED) - restart_timeleft -= (world.time - last_fire) - if(!delay_end) - end_game_state = END_GAME_ENDING - else - log_error("Ticker arrived at round end in an unexpected endgame state '[end_game_state]'.") - end_game_state = END_GAME_READY_TO_END - - -// ---------------------------------------------------------------------- -// These two below are not used! But they could be - -// Use these preferentially to directly examining ticker.current_state to help prepare for transition to ticker as subsystem! - -/datum/controller/subsystem/ticker/proc/PreRoundStart() - return (current_state < GAME_STATE_PLAYING) - -/datum/controller/subsystem/ticker/proc/IsSettingUp() - return (current_state == GAME_STATE_SETTING_UP) - -/datum/controller/subsystem/ticker/proc/IsRoundInProgress() - return (current_state == GAME_STATE_PLAYING) - -/datum/controller/subsystem/ticker/proc/HasRoundStarted() - return (current_state >= GAME_STATE_PLAYING) - -// ------------------------------------------------------------------------ -// HELPER PROCS! -// ------------------------------------------------------------------------ - -//Plus it provides an easy way to make cinematics for other events. Just use this as a template :) -/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null) - if( cinematic ) return //already a cinematic in progress! - - //initialise our cinematic screen object - cinematic = new(src) - cinematic.icon = 'icons/effects/station_explosion.dmi' - cinematic.icon_state = "station_intact" - cinematic.layer = 100 - cinematic.plane = PLANE_PLAYER_HUD - cinematic.mouse_opacity = 0 - cinematic.screen_loc = "1,0" - - var/obj/structure/bed/temp_buckle = new(src) - //Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around - if(station_missed) - for(var/mob/living/M in GLOB.living_mob_list) - M.buckled = temp_buckle //buckles the mob so it can't do anything - if(M.client) - M.client.screen += cinematic //show every client the cinematic - else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived" - for(var/mob/living/M in GLOB.living_mob_list) - M.buckled = temp_buckle - if(M.client) - M.client.screen += cinematic - - switch(M.z) - if(0) //inside a crate or something - var/turf/T = get_turf(M) - if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and - M.health = 0 - M.set_stat(DEAD) - if(1) //on a z-level 1 turf. - M.health = 0 - M.set_stat(DEAD) - - //Now animate the cinematic - switch(station_missed) - if(1) //nuke was nearby but (mostly) missed - if( mode && !override ) - override = mode.name - switch( override ) - if("mercenary") //Nuke wasn't on station when it blew up - flick("intro_nuke",cinematic) - sleep(35) - world << sound('sound/effects/explosionfar.ogg') - flick("station_intact_fade_red",cinematic) - cinematic.icon_state = "summary_nukefail" - else - flick("intro_nuke",cinematic) - sleep(35) - world << sound('sound/effects/explosionfar.ogg') - //flick("end",cinematic) - - - if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation - sleep(50) - world << sound('sound/effects/explosionfar.ogg') - - - else //station was destroyed - if( mode && !override ) - override = mode.name - switch( override ) - if("mercenary") //Nuke Ops successfully bombed the station - flick("intro_nuke",cinematic) - sleep(35) - flick("station_explode_fade_red",cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_nukewin" - if("AI malfunction") //Malf (screen,explosion,summary) - flick("intro_malf",cinematic) - sleep(76) - flick("station_explode_fade_red",cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_malf" - if("blob") //Station nuked (nuke,explosion,summary) - flick("intro_nuke",cinematic) - sleep(35) - flick("station_explode_fade_red",cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_selfdes" - else //Station nuked (nuke,explosion,summary) - flick("intro_nuke",cinematic) - sleep(35) - flick("station_explode_fade_red", cinematic) - world << sound('sound/effects/explosionfar.ogg') - cinematic.icon_state = "summary_selfdes" - for(var/mob/living/M in GLOB.living_mob_list) - if(M.loc.z in using_map.station_levels) - M.death()//No mercy - //If its actually the end of the round, wait for it to end. - //Otherwise if its a verb it will continue on afterwards. - sleep(300) - - if(cinematic) qdel(cinematic) //end the cinematic - if(temp_buckle) qdel(temp_buckle) //release everybody - return - /datum/controller/subsystem/ticker/proc/create_characters() for(var/mob/new_player/player in GLOB.player_list) @@ -431,12 +369,13 @@ var/global/datum/controller/subsystem/ticker/ticker // If they're a carbon, they can get manifested if(J?.mob_type & JOB_CARBON) GLOB.data_core.manifest_inject(new_char) + CHECK_TICK /datum/controller/subsystem/ticker/proc/collect_minds() for(var/mob/living/player in GLOB.player_list) if(player.mind) minds += player.mind - + CHECK_TICK /datum/controller/subsystem/ticker/proc/equip_characters() var/captainless=1 @@ -457,147 +396,107 @@ var/global/datum/controller/subsystem/ticker/ticker if(imp.handle_implant(player,player.zone_sel.selecting)) imp.post_implant(player) //VOREStation Addition End + CHECK_TICK if(captainless) for(var/mob/M in GLOB.player_list) if(!isnewplayer(M)) to_chat(M, span_notice("Site Management is not forced on anyone.")) +///Whether the game has started, including roundend. +/datum/controller/subsystem/ticker/proc/HasRoundStarted() + return current_state >= GAME_STATE_PLAYING -/datum/controller/subsystem/ticker/proc/declare_completion() - to_world(span_filter_system("


A round of [mode.name] has ended!

")) - for(var/mob/Player in GLOB.player_list) - if(Player.mind && !isnewplayer(Player)) - if(Player.stat != DEAD) - var/turf/playerTurf = get_turf(Player) - if(emergency_shuttle.departed && emergency_shuttle.evac) - if(isNotAdminLevel(playerTurf.z)) - to_chat(Player, span_filter_system(span_blue(span_bold("You survived the round, but remained on [station_name()] as [Player.real_name].")))) - else - to_chat(Player, span_filter_system(span_green(span_bold("You managed to survive the events on [station_name()] as [Player.real_name].")))) - else if(isAdminLevel(playerTurf.z)) - to_chat(Player, span_filter_system(span_green(span_bold("You successfully underwent crew transfer after events on [station_name()] as [Player.real_name].")))) - else if(issilicon(Player)) - to_chat(Player, span_filter_system(span_green(span_bold("You remain operational after the events on [station_name()] as [Player.real_name].")))) - else - to_chat(Player, span_filter_system(span_blue(span_bold("You missed the crew transfer after the events on [station_name()] as [Player.real_name].")))) - else - if(isobserver(Player)) - var/mob/observer/dead/O = Player - if(!O.started_as_observer) - to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]...")))) - else - to_chat(Player, span_filter_system(span_red(span_bold("You did not survive the events on [station_name()]...")))) - to_world(span_filter_system("
")) +///Whether the game is currently in progress, excluding roundend +/datum/controller/subsystem/ticker/proc/IsRoundInProgress() + return current_state == GAME_STATE_PLAYING - for (var/mob/living/silicon/ai/aiPlayer in GLOB.mob_list) - if (aiPlayer.stat != 2) - to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws at the end of the round were:"))) // VOREStation edit - else - to_world(span_filter_system(span_bold("[aiPlayer.name]'s laws when it was deactivated were:"))) // VOREStation edit - aiPlayer.show_laws(1) - - if (aiPlayer.connected_robots.len) - var/robolist = span_bold("The AI's loyal minions were:") + " " - for(var/mob/living/silicon/robot/robo in aiPlayer.connected_robots) - robolist += "[robo.name][robo.stat?" (Deactivated), ":", "]" // VOREStation edit - to_world(span_filter_system("[robolist]")) - - var/dronecount = 0 - - for (var/mob/living/silicon/robot/robo in GLOB.mob_list) - - if(istype(robo, /mob/living/silicon/robot/platform)) - var/mob/living/silicon/robot/platform/tank = robo - if(!tank.has_had_player) - continue - - if(istype(robo,/mob/living/silicon/robot/drone) && !istype(robo,/mob/living/silicon/robot/drone/swarm)) - dronecount++ - continue - - if (!robo.connected_ai) - if (robo.stat != 2) - to_world(span_filter_system(span_bold("[robo.name] survived as an AI-less stationbound synthetic! Its laws were:"))) // VOREStation edit - else - to_world(span_filter_system(span_bold("[robo.name] was unable to survive the rigors of being a stationbound synthetic without an AI. Its laws were:"))) // VOREStation edit - - if(robo) //How the hell do we lose robo between here and the world messages directly above this? - robo.laws.show_laws(world) - - if(dronecount) - to_world(span_filter_system(span_bold("There [dronecount>1 ? "were" : "was"] [dronecount] industrious maintenance [dronecount>1 ? "drones" : "drone"] at the end of this round."))) - - mode.declare_completion()//To declare normal completion. - - //Ask the event manager to print round end information - SSevents.RoundEnd() - - //Print a list of antagonists to the server log - var/list/total_antagonists = list() - //Look into all mobs in world, dead or alive - for(var/datum/mind/Mind in minds) - var/temprole = Mind.special_role - if(temprole) //if they are an antagonist of some sort. - if(temprole in total_antagonists) //If the role exists already, add the name to it - total_antagonists[temprole] += ", [Mind.name]([Mind.key])" - else - total_antagonists.Add(temprole) //If the role doesnt exist in the list, create it and add the mob - total_antagonists[temprole] += ": [Mind.name]([Mind.key])" - - //Now print them all into the log! - log_game("Antagonists at round end were...") - for(var/i in total_antagonists) - log_game("[i]s[total_antagonists[i]].") - - SSdbcore.SetRoundEnd() - - return 1 - -/datum/controller/subsystem/ticker/stat_entry(msg) - switch(current_state) - if(GAME_STATE_INIT) - ..() - if(GAME_STATE_PREGAME) // RUNLEVEL_LOBBY - msg = "START [GLOB.round_progressing ? "[round(pregame_timeleft)]s" : "(PAUSED)"]" - if(GAME_STATE_SETTING_UP) // RUNLEVEL_SETUP - msg = "SETUP" - if(GAME_STATE_PLAYING) // RUNLEVEL_GAME - msg = "GAME" - if(GAME_STATE_FINISHED) // RUNLEVEL_POSTGAME - switch(end_game_state) - if(END_GAME_MODE_FINISHED) - msg = "MODE OVER, WAITING" - if(END_GAME_READY_TO_END) - msg = "ENDGAME PROCESSING" - if(END_GAME_ENDING) - msg = "END IN [round(restart_timeleft/10)]s" - if(END_GAME_DELAYED) - msg = "END PAUSED" - else - msg = "ENDGAME ERROR:[end_game_state]" - return ..() +///Whether the game is currently in progress, excluding roundend +/datum/controller/subsystem/ticker/proc/IsPostgame() + return current_state == GAME_STATE_FINISHED /datum/controller/subsystem/ticker/Recover() - flags |= SS_NO_INIT // Don't initialize again - current_state = SSticker.current_state - mode = SSticker.mode - pregame_timeleft = SSticker.pregame_timeleft + force_ending = SSticker.force_ending - end_game_state = SSticker.end_game_state - delay_end = SSticker.delay_end - restart_timeleft = SSticker.restart_timeleft + login_music = SSticker.login_music + round_end_sound = SSticker.round_end_sound minds = SSticker.minds - random_players = SSticker.random_players + delay_end = SSticker.delay_end + tipped = SSticker.tipped + selected_tip = SSticker.selected_tip + + timeLeft = SSticker.timeLeft + + totalPlayers = SSticker.totalPlayers + totalPlayersReady = SSticker.totalPlayersReady + total_admins_ready = SSticker.total_admins_ready + + queue_delay = SSticker.queue_delay + queued_players = SSticker.queued_players round_start_time = SSticker.round_start_time + queue_delay = SSticker.queue_delay + queued_players = SSticker.queued_players -//These callbacks will fire after roundstart key transfer -/datum/controller/subsystem/ticker/proc/OnRoundstart(datum/callback/cb) - if(!HasRoundStarted()) - LAZYADD(round_start_events, cb) - else - cb.InvokeAsync() + if (Master) //Set Masters run level if it exists + switch (current_state) + if(GAME_STATE_SETTING_UP) + Master.SetRunLevel(RUNLEVEL_SETUP) + if(GAME_STATE_PLAYING) + Master.SetRunLevel(RUNLEVEL_GAME) + if(GAME_STATE_FINISHED) + Master.SetRunLevel(RUNLEVEL_POSTGAME) + +/datum/controller/subsystem/ticker/proc/Reboot(reason, end_string, delay) + set waitfor = FALSE + if(usr && !check_rights(R_SERVER, TRUE)) + return + + if(!delay) + delay = CONFIG_GET(number/round_end_countdown) * 10 + + var/skip_delay = check_rights() + if(delay_end && !skip_delay) + to_chat(world, span_boldannounce("An admin has delayed the round end.")) + return + + to_chat(world, span_boldannounce("Rebooting World in [DisplayTimeText(delay)]. [reason]")) + + // We dont have those + //var/statspage = CONFIG_GET(string/roundstatsurl) + //var/gamelogloc = CONFIG_GET(string/gamelogurl) + //if(statspage) + // to_chat(world, span_info("Round statistics and logs can be viewed at this website!")) + //else if(gamelogloc) + // to_chat(world, span_info("Round logs can be located at this website!")) + + var/start_wait = world.time + UNTIL(round_end_sound_sent || (world.time - start_wait) > (delay * 2)) //don't wait forever + reboot_timer = addtimer(CALLBACK(src, PROC_REF(reboot_callback), reason, end_string), delay - (world.time - start_wait), TIMER_STOPPABLE) + + +/datum/controller/subsystem/ticker/proc/reboot_callback(reason, end_string) + if(end_string) + end_state = end_string + + log_game(span_boldannounce("Rebooting World. [reason]")) + + world.Reboot() + +/** + * Deletes the current reboot timer and nulls the var + * + * Arguments: + * * user - the user that cancelled the reboot, may be null + */ +/datum/controller/subsystem/ticker/proc/cancel_reboot(mob/user) + if(!reboot_timer) + to_chat(user, span_warning("There is no pending reboot!")) + return FALSE + to_chat(world, span_boldannounce("An admin has delayed the round end.")) + deltimer(reboot_timer) + reboot_timer = null + return TRUE diff --git a/code/controllers/subsystems/timer.dm b/code/controllers/subsystems/timer.dm index c314fa7d38..a90bcc334b 100644 --- a/code/controllers/subsystems/timer.dm +++ b/code/controllers/subsystems/timer.dm @@ -17,9 +17,11 @@ SUBSYSTEM_DEF(timer) name = "Timer" wait = 1 // SS_TICKER subsystem, so wait is in ticks - init_order = INIT_ORDER_TIMER priority = FIRE_PRIORITY_TIMER flags = SS_TICKER|SS_NO_INIT + dependencies = list( + /datum/controller/subsystem/machines + ) /// Queue used for storing timers that do not fit into the current buckets var/list/datum/timedevent/second_queue = list() diff --git a/code/controllers/subsystems/transcore_vr.dm b/code/controllers/subsystems/transcore_vr.dm index 52e10970b2..23948787ea 100644 --- a/code/controllers/subsystems/transcore_vr.dm +++ b/code/controllers/subsystems/transcore_vr.dm @@ -12,7 +12,9 @@ SUBSYSTEM_DEF(transcore) wait = 3 MINUTES flags = SS_BACKGROUND runlevels = RUNLEVEL_GAME - init_order = INIT_ORDER_TRANSCORE + dependencies = list( + /datum/controller/subsystem/mapping + ) // THINGS var/overdue_time = 6 MINUTES // Has to be a multiple of wait var, or else will just round up anyway. diff --git a/code/controllers/subsystems/verb_manager.dm b/code/controllers/subsystems/verb_manager.dm index 8e8c4cd3bd..f09c050964 100644 --- a/code/controllers/subsystems/verb_manager.dm +++ b/code/controllers/subsystems/verb_manager.dm @@ -78,7 +78,7 @@ SUBSYSTEM_DEF(verb_manager) //we want unit tests to be able to directly call verbs that attempt to queue, and since unit tests should test internal behavior, we want the queue //to happen as if it was actually from player input if its called on a mob. -#ifdef UNIT_TEST +#ifdef UNIT_TESTS if(QDELETED(usr) && ismob(incoming_callback.object)) incoming_callback.user = WEAKREF(incoming_callback.object) var/datum/callback/new_us = CALLBACK(arglist(list(GLOBAL_PROC, GLOBAL_PROC_REF(_queue_verb)) + args.Copy())) @@ -120,7 +120,7 @@ SUBSYSTEM_DEF(verb_manager) return TRUE if((usr.client?.holder && !can_queue_admin_verbs) \ - || (!subsystem_initialized && !(flags & SS_NO_INIT)) \ + || (!initialized && !(flags & SS_NO_INIT)) \ || FOR_ADMINS_IF_VERBS_FUCKED_immediately_execute_all_verbs \ || !(runlevels & Master.current_runlevel)) return FALSE diff --git a/code/controllers/subsystems/vis_overlays.dm b/code/controllers/subsystems/vis_overlays.dm index 61cb58170a..372dd4b306 100644 --- a/code/controllers/subsystems/vis_overlays.dm +++ b/code/controllers/subsystems/vis_overlays.dm @@ -2,7 +2,6 @@ SUBSYSTEM_DEF(vis_overlays) name = "Vis contents overlays" wait = 1 MINUTES priority = FIRE_PRIORITY_VIS - init_order = INIT_ORDER_VIS var/list/vis_overlay_cache var/list/currentrun diff --git a/code/controllers/subsystems/vote.dm b/code/controllers/subsystems/vote.dm index b593026cf2..d462073c80 100644 --- a/code/controllers/subsystems/vote.dm +++ b/code/controllers/subsystems/vote.dm @@ -24,7 +24,7 @@ SUBSYSTEM_DEF(vote) /datum/controller/subsystem/vote/fire(resumed) if(mode) time_remaining = round((started_time + duration - world.time)/10) - if(mode == VOTE_GAMEMODE && ticker.current_state >= GAME_STATE_SETTING_UP) + if(mode == VOTE_GAMEMODE && SSticker.current_state >= GAME_STATE_SETTING_UP) to_chat(world, span_bold("Gamemode vote aborted: Game has already started.")) reset() return @@ -151,7 +151,7 @@ SUBSYSTEM_DEF(vote) if(VOTE_GAMEMODE) if(GLOB.master_mode != .) world.save_mode(.) - if(ticker && ticker.mode) + if(SSticker && SSticker.mode) restart = 1 else GLOB.master_mode = . @@ -203,7 +203,7 @@ SUBSYSTEM_DEF(vote) if(VOTE_RESTART) choices.Add("Restart Round", "Continue Playing") if(VOTE_GAMEMODE) - if(ticker.current_state >= GAME_STATE_SETTING_UP) + if(SSticker.current_state >= GAME_STATE_SETTING_UP) return 0 choices.Add(config.votable_modes) for(var/F in choices) @@ -218,13 +218,13 @@ SUBSYSTEM_DEF(vote) if(get_security_level() == "red" || get_security_level() == "delta") to_chat(initiator_key, "The current alert status is too high to call for a crew transfer!") return 0 - if(ticker.current_state <= GAME_STATE_SETTING_UP) + if(SSticker.current_state <= GAME_STATE_SETTING_UP) to_chat(initiator_key, "The crew transfer button has been disabled!") return 0 question = "Your PDA beeps with a message from Central. Would you like an additional hour to finish ongoing projects?" //VOREStation Edit choices.Add("Initiate Crew Transfer", "Extend the Shift") //VOREStation Edit if(VOTE_ADD_ANTAGONIST) - if(!config.allow_extra_antags || ticker.current_state >= GAME_STATE_SETTING_UP) + if(!config.allow_extra_antags || SSticker.current_state >= GAME_STATE_SETTING_UP) return 0 for(var/antag_type in all_antag_types) var/datum/antagonist/antag = all_antag_types[antag_type] diff --git a/code/controllers/subsystems/webhooks.dm b/code/controllers/subsystems/webhooks.dm index 3a8031b4ea..17a12f6f77 100644 --- a/code/controllers/subsystems/webhooks.dm +++ b/code/controllers/subsystems/webhooks.dm @@ -1,6 +1,8 @@ SUBSYSTEM_DEF(webhooks) name = "Webhooks" - init_order = INIT_ORDER_WEBHOOKS + dependencies = list( + /datum/controller/subsystem/server_maint, + ) flags = SS_NO_FIRE var/list/webhook_decls = list() @@ -63,7 +65,7 @@ SUBSYSTEM_DEF(webhooks) if(!check_rights_for(src, R_HOLDER)) return - if(!SSwebhooks.subsystem_initialized) + if(!SSwebhooks.initialized) to_chat(usr, span_warning("Let the webhook subsystem initialize before trying to reload it.")) return diff --git a/code/controllers/subsystems/xenoarch.dm b/code/controllers/subsystems/xenoarch.dm index 1711af7407..fecc64906b 100644 --- a/code/controllers/subsystems/xenoarch.dm +++ b/code/controllers/subsystems/xenoarch.dm @@ -14,8 +14,8 @@ // SUBSYSTEM_DEF(xenoarch) name = "Xenoarch" - init_order = INIT_ORDER_XENOARCH flags = SS_NO_FIRE + init_stage = INITSTAGE_LAST var/list/artifact_spawning_turfs = list() var/list/digsite_spawning_turfs = list() diff --git a/code/datums/cinematics/_cinematic.dm b/code/datums/cinematics/_cinematic.dm new file mode 100644 index 0000000000..6a49548f1f --- /dev/null +++ b/code/datums/cinematics/_cinematic.dm @@ -0,0 +1,181 @@ +#define CINEMATIC_SOURCE "cinematic" + +/** + * Plays a cinematic, duh. Can be to a select few people, or everyone. + * + * cinematic_type - datum typepath to what cinematic you wish to play. + * watchers - a list of all mobs you are playing the cinematic to. If world, the cinematical will play globally to all players. + * special_callback - optional callback to be invoked mid-cinematic. + */ +/proc/play_cinematic(datum/cinematic/cinematic_type, watchers, datum/callback/special_callback) + if(!ispath(cinematic_type, /datum/cinematic)) + CRASH("play_cinematic called with a non-cinematic type. (Got: [cinematic_type])") + var/datum/cinematic/playing = new cinematic_type(watchers, special_callback) + + if(watchers == world) + watchers = GLOB.mob_list + + playing.start_cinematic(watchers) + + return playing + +/// The cinematic screen showed to everyone +/atom/movable/screen/cinematic + icon = 'icons/effects/station_explosion.dmi' + icon_state = "station_intact" + plane = SPLASHSCREEN_PLANE + mouse_opacity = MOUSE_OPACITY_TRANSPARENT + screen_loc = "BOTTOM,LEFT+50%" + appearance_flags = APPEARANCE_UI | TILE_BOUND + +/// Cinematic datum. Used to show an animation to everyone. +/datum/cinematic + /// A list of all clients watching the cinematic + var/list/client/watching = list() + /// A list of all mobs who have TRAIT_NO_TRANSFORM set while watching the cinematic + var/list/datum/weakref/locked = list() + /// Whether the cinematic is a global cinematic or not + var/is_global = FALSE + /// Refernce to the cinematic screen shown to everyohne + var/atom/movable/screen/cinematic/screen + /// Callbacks passed that occur during the animation + var/datum/callback/special_callback + /// How long for the final screen remains shown + var/cleanup_time = 30 SECONDS + /// Whether the cinematic turns off ooc when played globally. + var/stop_ooc = TRUE + +/datum/cinematic/New(watcher, datum/callback/special_callback) + screen = new(src) + if(watcher == world) + is_global = TRUE + + src.special_callback = special_callback + +/datum/cinematic/Destroy() + QDEL_NULL(screen) + special_callback = null + watching.Cut() + locked.Cut() + return ..() + +/// Actually goes through the process of showing the cinematic to the list of watchers. +/datum/cinematic/proc/start_cinematic(list/watchers) + if(SEND_GLOBAL_SIGNAL(COMSIG_GLOB_PLAY_CINEMATIC, src) & COMPONENT_GLOB_BLOCK_CINEMATIC) + return + + // Register a signal to handle what happens when a different cinematic tries to play over us. + RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, PROC_REF(handle_replacement_cinematics)) + + // Pause OOC + // NOT IMPLEMENTED + var/ooc_toggled = FALSE + //if(is_global && stop_ooc && GLOB.ooc_allowed) + // ooc_toggled = TRUE + // toggle_ooc(FALSE) + + // Place the /atom/movable/screen/cinematic into everyone's screens, and prevent movement. + for(var/mob/watching_mob in watchers) + //show_to(watching_mob, GET_CLIENT(watching_mob)) // NOT IMPLEMENTED (GET_CLIENT) + show_to(watching_mob, watching_mob.client) + RegisterSignal(watching_mob, COMSIG_MOB_CLIENT_LOGIN, PROC_REF(show_to)) + // Close watcher ui's, too, so they can watch it. + SStgui.close_user_uis(watching_mob) + + // Actually plays the animation. This will sleep, likely. + play_cinematic() + + // Cleans up after it's done playing. + addtimer(CALLBACK(src, PROC_REF(clean_up_cinematic), ooc_toggled), cleanup_time) + +/// Cleans up the cinematic after a set timer of it sticking on the end screen. +/datum/cinematic/proc/clean_up_cinematic(was_ooc_toggled = FALSE) + // NOT IMPLEMENTED + //if(was_ooc_toggled) + // toggle_ooc(TRUE) + + stop_cinematic() + +/// Whenever another cinematic starts to play over us, we have the chacne to block it. +/datum/cinematic/proc/handle_replacement_cinematics(datum/source, datum/cinematic/other) + SIGNAL_HANDLER + + // Stop our's and allow others to play if we're local and it's global + if(!is_global && other.is_global) + stop_cinematic() + return NONE + + return COMPONENT_GLOB_BLOCK_CINEMATIC + +/// Whenever a mob watching the cinematic logs in, show them the ongoing cinematic +/datum/cinematic/proc/show_to(mob/watching_mob, client/watching_client) + SIGNAL_HANDLER + + if(!HAS_TRAIT_FROM(watching_mob, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE)) + lock_mob(watching_mob) + + // Only show the actual cinematic to cliented mobs. + if(!watching_client || (watching_client in watching)) + return + + watching += watching_client + watching_mob.overlay_fullscreen("cinematic", /obj/screen/fullscreen/cinematic_backdrop) + watching_client.screen += screen + RegisterSignal(watching_client, COMSIG_PARENT_QDELETING, PROC_REF(remove_watcher)) + +/// Simple helper for playing sounds from the cinematic. +/datum/cinematic/proc/play_cinematic_sound(sound_to_play) + if(is_global) + SEND_SOUND(world, sound_to_play) + else + for(var/client/watching_client as anything in watching) + SEND_SOUND(watching_client, sound_to_play) + +/// Invoke any special callbacks for actual effects synchronized with animation. +/// (Such as a real nuke explosion happening midway) +/datum/cinematic/proc/invoke_special_callback() + special_callback?.Invoke() + +/// The actual cinematic occurs here. +/datum/cinematic/proc/play_cinematic() + return + +/// Stops the cinematic and removes it from all the viewers. +/datum/cinematic/proc/stop_cinematic() + for(var/client/viewing_client as anything in watching) + remove_watcher(viewing_client) + + for(var/datum/weakref/locked_ref as anything in locked) + unlock_mob(locked_ref) + + qdel(src) + +/// Locks a mob, preventing them from moving, being hurt, or acting +/datum/cinematic/proc/lock_mob(mob/to_lock) + locked += WEAKREF(to_lock) + ADD_TRAIT(to_lock, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE) + +/// Unlocks a previously locked weakref +/datum/cinematic/proc/unlock_mob(datum/weakref/mob_ref) + var/mob/locked_mob = mob_ref.resolve() + if(isnull(locked_mob)) + return + REMOVE_TRAIT(locked_mob, TRAIT_NO_TRANSFORM, CINEMATIC_SOURCE) + UnregisterSignal(locked_mob, COMSIG_MOB_CLIENT_LOGIN) + +/// Removes the passed client from our watching list. +/datum/cinematic/proc/remove_watcher(client/no_longer_watching) + SIGNAL_HANDLER + + if(!(no_longer_watching in watching)) + CRASH("cinematic remove_watcher was passed a client which wasn't watching.") + + UnregisterSignal(no_longer_watching, COMSIG_PARENT_QDELETING) + // We'll clear the cinematic if they have a mob which has one, + // but we won't remove TRAIT_NO_TRANSFORM. Wait for the cinematic end to do that. + no_longer_watching.mob?.clear_fullscreen("cinematic") + no_longer_watching.screen -= screen + + watching -= no_longer_watching + +#undef CINEMATIC_SOURCE diff --git a/code/datums/cinematics/malf_doomsday.dm b/code/datums/cinematics/malf_doomsday.dm new file mode 100644 index 0000000000..673d229f1d --- /dev/null +++ b/code/datums/cinematics/malf_doomsday.dm @@ -0,0 +1,10 @@ +/// A malfunctioning AI has activated the doomsday device and wiped the station! +/datum/cinematic/malf + +/datum/cinematic/malf/play_cinematic() + flick("intro_malf", screen) + stoplag(7.6 SECONDS) + flick("station_explode_fade_red", screen) + play_cinematic_sound(sound('sound/effects/explosionfar.ogg')) + special_callback?.Invoke() + screen.icon_state = "summary_malf" diff --git a/code/datums/cinematics/nuke_cinematics.dm b/code/datums/cinematics/nuke_cinematics.dm new file mode 100644 index 0000000000..a17b48180d --- /dev/null +++ b/code/datums/cinematics/nuke_cinematics.dm @@ -0,0 +1,160 @@ +/// Simple, base cinematic for all animations based around a nuke detonating. +/datum/cinematic/nuke + /// If set, this is the summary screen that pops up after the nuke is done. + var/after_nuke_summary_state + + +/datum/cinematic/nuke/play_cinematic() + flick("intro_nuke", screen) + stoplag(3.5 SECONDS) + play_nuke_effect() + if(special_callback) + special_callback.Invoke() + if(after_nuke_summary_state) + screen.icon_state = after_nuke_summary_state + +/// Specific effects for each type of cinematics goes here. +/datum/cinematic/nuke/proc/play_nuke_effect() + return + +/// The syndicate nuclear bomb was activated, and destroyed the station! +/datum/cinematic/nuke/ops_victory + after_nuke_summary_state = "summary_nukewin" + +/datum/cinematic/nuke/ops_victory/play_nuke_effect() + flick("station_explode_fade_red", screen) + play_cinematic_sound(sound('sound/effects/explosionfar.ogg')) + +/// The syndicate nuclear bomb was activated, but just barely missed the station! +/datum/cinematic/nuke/ops_miss + after_nuke_summary_state = "summary_nukefail" + +/datum/cinematic/nuke/ops_miss/play_nuke_effect() + flick("station_intact_fade_red", screen) + play_cinematic_sound(sound('sound/effects/explosionfar.ogg')) + +/// The self destruct, or another station-destroying entity like a blob, destroyed the station! +/datum/cinematic/nuke/self_destruct + after_nuke_summary_state = "summary_selfdes" + +/datum/cinematic/nuke/self_destruct/play_nuke_effect() + flick("station_explode_fade_red", screen) + play_cinematic_sound(sound('sound/effects/explosionfar.ogg')) + +/// The self destruct was activated, yet somehow avoided destroying the station! +/datum/cinematic/nuke/self_destruct_miss + after_nuke_summary_state = "station_intact" + +/datum/cinematic/nuke/self_destruct_miss/play_nuke_effect() + play_cinematic_sound(sound('sound/effects/explosionfar.ogg')) + special_callback?.Invoke() + +/// The syndicate nuclear bomb was activated, but just missed the station by a whole z-level! +/datum/cinematic/nuke/far_explosion + cleanup_time = 0 SECONDS + +/datum/cinematic/nuke/far_explosion/play_cinematic() + // This one has no intro sequence. + // It's actually just a global sound, which makes you wonder why it's a cinematic. + play_cinematic_sound(sound('sound/effects/explosionfar.ogg')) + special_callback?.Invoke() + +/* +/datum/controller/subsystem/ticker/proc/station_explosion_cinematic(var/station_missed=0, var/override = null) + if( cinematic ) return //already a cinematic in progress! + + //initialise our cinematic screen object + cinematic = new(src) + cinematic.icon = 'icons/effects/station_explosion.dmi' + cinematic.icon_state = "station_intact" + cinematic.layer = 100 + cinematic.plane = PLANE_PLAYER_HUD + cinematic.mouse_opacity = 0 + cinematic.screen_loc = "1,0" + + var/obj/structure/bed/temp_buckle = new(src) + //Incredibly hackish. It creates a bed within the gameticker (lol) to stop mobs running around + if(station_missed) + for(var/mob/living/M in living_mob_list) + M.buckled = temp_buckle //buckles the mob so it can't do anything + if(M.client) + M.client.screen += cinematic //show every client the cinematic + else //nuke kills everyone on z-level 1 to prevent "hurr-durr I survived" + for(var/mob/living/M in living_mob_list) + M.buckled = temp_buckle + if(M.client) + M.client.screen += cinematic + + switch(M.z) + if(0) //inside a crate or something + var/turf/T = get_turf(M) + if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and + M.health = 0 + M.set_stat(DEAD) + if(1) //on a z-level 1 turf. + M.health = 0 + M.set_stat(DEAD) + + //Now animate the cinematic + switch(station_missed) + if(1) //nuke was nearby but (mostly) missed + if( mode && !override ) + override = mode.name + switch( override ) + if("mercenary") //Nuke wasn't on station when it blew up + flick("intro_nuke",cinematic) + sleep(35) + world << sound('sound/effects/explosionfar.ogg') + flick("station_intact_fade_red",cinematic) + cinematic.icon_state = "summary_nukefail" + else + flick("intro_nuke",cinematic) + sleep(35) + world << sound('sound/effects/explosionfar.ogg') + //flick("end",cinematic) + + + if(2) //nuke was nowhere nearby //TODO: a really distant explosion animation + sleep(50) + world << sound('sound/effects/explosionfar.ogg') + + + else //station was destroyed + if( mode && !override ) + override = mode.name + switch( override ) + if("mercenary") //Nuke Ops successfully bombed the station + flick("intro_nuke",cinematic) + sleep(35) + flick("station_explode_fade_red",cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_nukewin" + if("AI malfunction") //Malf (screen,explosion,summary) + flick("intro_malf",cinematic) + sleep(76) + flick("station_explode_fade_red",cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_malf" + if("blob") //Station nuked (nuke,explosion,summary) + flick("intro_nuke",cinematic) + sleep(35) + flick("station_explode_fade_red",cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_selfdes" + else //Station nuked (nuke,explosion,summary) + flick("intro_nuke",cinematic) + sleep(35) + flick("station_explode_fade_red", cinematic) + world << sound('sound/effects/explosionfar.ogg') + cinematic.icon_state = "summary_selfdes" + for(var/mob/living/M in living_mob_list) + if(M.loc.z in using_map.station_levels) + M.death()//No mercy + //If its actually the end of the round, wait for it to end. + //Otherwise if its a verb it will continue on afterwards. + sleep(300) + + if(cinematic) qdel(cinematic) //end the cinematic + if(temp_buckle) qdel(temp_buckle) //release everybody + return +*/ diff --git a/code/datums/diseases/advance/advance.dm b/code/datums/diseases/advance/advance.dm index 0ecad3e8b9..7edcf5f089 100644 --- a/code/datums/diseases/advance/advance.dm +++ b/code/datums/diseases/advance/advance.dm @@ -489,8 +489,8 @@ GLOBAL_LIST_INIT(advance_cures, list( return main_symptom.name // Prefixes. These need a space right after. - var/list/prefixes = list("Spacer's ", "Space ", "Infectious ","Viral ", "The ", "[capitalize(prob(50) ? pick(first_names_male) : pick(first_names_female))]'s ", "[capitalize(pick(last_names))]'s ", "Acute ") - var/list/bodies = list(pick("[capitalize(prob(50) ? pick(first_names_male) : pick(first_names_female))]", "[pick(last_names)]"), "Space", "Disease", "Noun", "Cold", "Germ", "Virus") + var/list/prefixes = list("Spacer's ", "Space ", "Infectious ","Viral ", "The ", "[capitalize(prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female))]'s ", "[capitalize(pick(GLOB.last_names))]'s ", "Acute ") + var/list/bodies = list(pick("[capitalize(prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female))]", "[pick(GLOB.last_names)]"), "Space", "Disease", "Noun", "Cold", "Germ", "Virus") // These might need some space before the word, depends on what you want to add. var/list/suffixes = list("ism", "itis", "osis", "itosis", " #[rand(1,10000)]", "-[rand(1,100)]", "s", "y", " Virus", " Bug", " Infection", " Disease", " Complex", " Syndrome", " Sickness") diff --git a/code/datums/ghost_spawn_options.dm b/code/datums/ghost_spawn_options.dm index ed539a1909..dcf7255169 100644 --- a/code/datums/ghost_spawn_options.dm +++ b/code/datums/ghost_spawn_options.dm @@ -57,7 +57,7 @@ to_chat(host, span_info("You are now a mouse. Try to avoid interaction with players, and do not give hints away that you are more than a simple rodent.")) /datum/tgui_module/ghost_spawn_menu/proc/become_drone(mob/observer/dead/user, fabricator) - if(ticker.current_state < GAME_STATE_PLAYING) + if(SSticker.current_state < GAME_STATE_PLAYING) to_chat(user, span_danger("The game hasn't started yet!")) return diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm index 5234d3f5e7..563cd816ad 100644 --- a/code/datums/helper_datums/teleport.dm +++ b/code/datums/helper_datums/teleport.dm @@ -1,9 +1,10 @@ -var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding, -/obj/item/storage/bag/trash/holding, -/obj/item/storage/pouch/holding, -/obj/item/storage/belt/utility/holding, -/obj/item/storage/belt/medical/holding -) +GLOBAL_LIST_INIT(bluespace_item_types, list( + /obj/item/storage/backpack/holding, + /obj/item/storage/bag/trash/holding, + /obj/item/storage/pouch/holding, + /obj/item/storage/belt/utility/holding, + /obj/item/storage/belt/medical/holding +)) //wrapper /proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE, bohsafe=FALSE) @@ -167,7 +168,7 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding, var/list/bluespace_things = newlist() - for (var/item in bluespace_item_types) + for (var/item in GLOB.bluespace_item_types) if (istype(teleatom, item)) precision = rand(1, 100) bluespace_things |= teleatom.search_contents_for(item) @@ -176,7 +177,7 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding, var/mob/living/L = teleatom if(LAZYLEN(L.buckled_mobs)) for(var/mob/rider in L.buckled_mobs) - for (var/item in bluespace_item_types) + for (var/item in GLOB.bluespace_item_types) bluespace_things |= rider.search_contents_for(item) if(bluespace_things.len) diff --git a/code/datums/mind.dm b/code/datums/mind.dm index 4b4473a6dd..93ad33bf1b 100644 --- a/code/datums/mind.dm +++ b/code/datums/mind.dm @@ -123,7 +123,7 @@ popup.open() /datum/mind/proc/edit_memory() - if(!ticker || !ticker.mode) + if(!SSticker || !SSticker.mode) tgui_alert_async(usr, "Not before round-start!", "Alert") return @@ -239,7 +239,7 @@ var/objective_type = "[objective_type_capital][objective_type_text]"//Add them together into a text string. var/list/possible_targets = list("Free objective") - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if ((possible_target != src) && ishuman(possible_target.current)) possible_targets += possible_target.current @@ -509,8 +509,8 @@ else mind = new /datum/mind(key) mind.original = src - if(ticker) - ticker.minds += mind + if(SSticker) + SSticker.minds += mind else to_world_log("## DEBUG: mind_initialize(): No ticker ready yet! Please inform Carn") if(!mind.name) mind.name = real_name diff --git a/code/datums/verb_callbacks.dm b/code/datums/verb_callbacks.dm index 77f817017d..9e887f53e0 100644 --- a/code/datums/verb_callbacks.dm +++ b/code/datums/verb_callbacks.dm @@ -8,7 +8,7 @@ creation_time = DS2TICKS(world.time) . = ..() -#ifndef UNIT_TEST +#ifndef UNIT_TESTS /datum/callback/verb_callback/Invoke(...) var/mob/our_user = user?.resolve() if(QDELETED(our_user) || isnull(our_user.client)) diff --git a/code/defines/procs/announce.dm b/code/defines/procs/announce.dm index a686580557..b1df8246b9 100644 --- a/code/defines/procs/announce.dm +++ b/code/defines/procs/announce.dm @@ -142,7 +142,7 @@ command_announcement.Announce("It has come to our attention that \the [station_name()] passed through an ion storm. Please monitor all electronic equipment for malfunctions.", "Anomaly Alert") /proc/AnnounceArrival(var/mob/living/carbon/human/character, var/rank, var/join_message, var/channel = "Common", var/zlevel) - if (ticker.current_state == GAME_STATE_PLAYING) + if (SSticker.current_state == GAME_STATE_PLAYING) var/list/zlevels = zlevel ? using_map.get_map_levels(zlevel, TRUE, om_range = DEFAULT_OVERMAP_RANGE) : null if(character.mind.role_alt_title) rank = character.mind.role_alt_title diff --git a/code/defines/procs/radio.dm b/code/defines/procs/radio.dm index 1f8f03bc9a..a73de9d384 100644 --- a/code/defines/procs/radio.dm +++ b/code/defines/procs/radio.dm @@ -5,13 +5,13 @@ /proc/register_radio(source, old_frequency, new_frequency, radio_filter) if(old_frequency) - radio_controller.remove_object(source, old_frequency) + SSradio.remove_object(source, old_frequency) if(new_frequency) - return radio_controller.add_object(source, new_frequency, radio_filter) + return SSradio.add_object(source, new_frequency, radio_filter) /proc/unregister_radio(source, frequency) - if(radio_controller) - radio_controller.remove_object(source, frequency) + if(SSradio) + SSradio.remove_object(source, frequency) /proc/get_frequency_name(var/display_freq) var/freq_text diff --git a/code/game/antagonist/antagonist.dm b/code/game/antagonist/antagonist.dm index febff8fce8..493a127c14 100644 --- a/code/game/antagonist/antagonist.dm +++ b/code/game/antagonist/antagonist.dm @@ -107,7 +107,7 @@ // Prune restricted status. Broke it up for readability. // Note that this is done before jobs are handed out. - candidates = ticker.mode.get_players_for_role(role_type, id, ghosts_only) + candidates = SSticker.mode.get_players_for_role(role_type, id, ghosts_only) for(var/datum/mind/player in candidates) if(ghosts_only && !isobserver(player.current)) candidates -= player @@ -143,10 +143,10 @@ if(players && players.len) player = pick(players) if(!istype(player)) - message_admins("[uppertext(ticker.mode.name)]: Failed to find a candidate for [role_text].") + message_admins("[uppertext(SSticker.mode.name)]: Failed to find a candidate for [role_text].") return 0 to_chat(player.current, span_danger(span_italics("You have been selected this round as an antagonist!"))) - message_admins("[uppertext(ticker.mode.name)]: Selected [player] as a [role_text].") + message_admins("[uppertext(SSticker.mode.name)]: Selected [player] as a [role_text].") if(isobserver(player.current)) create_default(player.current) else diff --git a/code/game/antagonist/antagonist_update.dm b/code/game/antagonist/antagonist_update.dm index 04bf3a92f5..7d19f0de6a 100644 --- a/code/game/antagonist/antagonist_update.dm +++ b/code/game/antagonist/antagonist_update.dm @@ -78,11 +78,11 @@ /datum/antagonist/proc/update_current_antag_max() cur_max = hard_cap - if(ticker && ticker.mode) - if(ticker.mode.antag_tags && (id in ticker.mode.antag_tags)) + if(SSticker && SSticker.mode) + if(SSticker.mode.antag_tags && (id in SSticker.mode.antag_tags)) cur_max = hard_cap_round - if(ticker.mode.antag_scaling_coeff) + if(SSticker.mode.antag_scaling_coeff) var/count = 0 for(var/mob/living/M in GLOB.player_list) @@ -91,4 +91,4 @@ // Minimum: initial_spawn_target // Maximum: hard_cap or hard_cap_round - cur_max = max(initial_spawn_target,min(round(count/ticker.mode.antag_scaling_coeff),cur_max)) + cur_max = max(initial_spawn_target,min(round(count/SSticker.mode.antag_scaling_coeff),cur_max)) diff --git a/code/game/antagonist/outsider/deathsquad.dm b/code/game/antagonist/outsider/deathsquad.dm index 2ea908d23c..cc748bf016 100644 --- a/code/game/antagonist/outsider/deathsquad.dm +++ b/code/game/antagonist/outsider/deathsquad.dm @@ -68,7 +68,7 @@ var/datum/antagonist/deathsquad/deathsquad else syndicate_commando_rank = pick("Lieutenant", "Captain", "Major") - var/syndicate_commando_name = pick(last_names) + var/syndicate_commando_name = pick(GLOB.last_names) player.name = "[syndicate_commando_rank] [syndicate_commando_name]" player.current.name = player.name diff --git a/code/game/antagonist/outsider/ninja.dm b/code/game/antagonist/outsider/ninja.dm index b06ef042ae..3abf458521 100644 --- a/code/game/antagonist/outsider/ninja.dm +++ b/code/game/antagonist/outsider/ninja.dm @@ -84,8 +84,8 @@ var/datum/antagonist/ninja/ninjas /datum/antagonist/ninja/update_antag_mob(var/datum/mind/player) ..() - var/ninja_title = pick(ninja_titles) - var/ninja_name = pick(ninja_names) + var/ninja_title = pick(GLOB.ninja_titles) + var/ninja_name = pick(GLOB.ninja_names) var/mob/living/carbon/human/H = player.current if(istype(H)) H.real_name = "[ninja_title] [ninja_name]" diff --git a/code/game/antagonist/outsider/technomancer.dm b/code/game/antagonist/outsider/technomancer.dm index 976aa4f762..112ff3dd54 100644 --- a/code/game/antagonist/outsider/technomancer.dm +++ b/code/game/antagonist/outsider/technomancer.dm @@ -29,7 +29,7 @@ var/datum/antagonist/technomancer/technomancers /datum/antagonist/technomancer/update_antag_mob(var/datum/mind/technomancer) ..() technomancer.store_memory(span_bold("Remember:") + " Do not forget to purchase the functions and equipment you need.") - technomancer.current.real_name = "[pick(wizard_first)] [pick(wizard_second)]" + technomancer.current.real_name = "[pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)]" technomancer.current.name = technomancer.current.real_name /datum/antagonist/technomancer/equip(var/mob/living/carbon/human/technomancer_mob) diff --git a/code/game/antagonist/outsider/wizard.dm b/code/game/antagonist/outsider/wizard.dm index 033c45c1ab..0543ac718b 100644 --- a/code/game/antagonist/outsider/wizard.dm +++ b/code/game/antagonist/outsider/wizard.dm @@ -67,7 +67,7 @@ var/datum/antagonist/wizard/wizards /datum/antagonist/wizard/update_antag_mob(var/datum/mind/wizard) ..() wizard.store_memory(span_bold("Remember:") + " do not forget to prepare your spells.") - wizard.current.real_name = "[pick(wizard_first)] [pick(wizard_second)]" + wizard.current.real_name = "[pick(GLOB.wizard_first)] [pick(GLOB.wizard_second)]" wizard.current.name = wizard.current.real_name /datum/antagonist/wizard/equip(var/mob/living/carbon/human/wizard_mob) diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm index 85aeab1080..9d18fcd8a7 100644 --- a/code/game/area/areas.dm +++ b/code/game/area/areas.dm @@ -86,7 +86,7 @@ GLOBAL_LIST_EMPTY(areas_by_type) A.contents.Add(T) if(old_area) // Handle dynamic lighting update if - if(SSlighting.subsystem_initialized && T.dynamic_lighting && old_area.dynamic_lighting != A.dynamic_lighting) + if(SSlighting.initialized && T.dynamic_lighting && old_area.dynamic_lighting != A.dynamic_lighting) if(A.dynamic_lighting) T.lighting_build_overlay() else diff --git a/code/game/atom/README.md b/code/game/atom/README.md new file mode 100644 index 0000000000..3ce4f9b0cf --- /dev/null +++ b/code/game/atom/README.md @@ -0,0 +1,3 @@ +# Core /atom Systems + +This is a folder of basic systems that apply to all /atom types, split into relevant files. Keep the files named fittingly and don't put anything in `_atom.dm` if you can avoid it. diff --git a/code/game/atoms.dm b/code/game/atom/_atom.dm similarity index 98% rename from code/game/atoms.dm rename to code/game/atom/_atom.dm index 4d770c50b5..2a43441713 100644 --- a/code/game/atoms.dm +++ b/code/game/atom/_atom.dm @@ -1,3 +1,9 @@ +/** + * The base type for nearly all physical objects in SS13 + + * Lots and lots of functionality lives here, although in general we are striving to move + * as much as possible to the components/elements system + */ /atom layer = TURF_LAYER //This was here when I got here. Why though? var/level = 2 diff --git a/code/game/atoms_init.dm b/code/game/atom/atoms_initializing_EXPENSIVE.dm similarity index 99% rename from code/game/atoms_init.dm rename to code/game/atom/atoms_initializing_EXPENSIVE.dm index 666ff6312a..fd5d4961a0 100644 --- a/code/game/atoms_init.dm +++ b/code/game/atom/atoms_initializing_EXPENSIVE.dm @@ -67,7 +67,7 @@ if(GLOB.use_preloader && src.type == GLOB._preloader_path)//in case the instantiated atom is creating other atoms in New() world.preloader_load(src) - var/do_initialize = SSatoms.initialized + var/do_initialize = SSatoms.atom_initialized if(do_initialize != INITIALIZATION_INSSATOMS) args[1] = do_initialize == INITIALIZATION_INNEW_MAPLOAD if(SSatoms.InitAtom(src, FALSE, args)) diff --git a/code/game/atoms_movable.dm b/code/game/atoms_movable.dm index d314a661d1..c306e0c7f7 100644 --- a/code/game/atoms_movable.dm +++ b/code/game/atoms_movable.dm @@ -1,7 +1,8 @@ /atom/movable layer = OBJ_LAYER - appearance_flags = TILE_BOUND|PIXEL_SCALE|KEEP_TOGETHER|LONG_GLIDE glide_size = 8 + appearance_flags = TILE_BOUND|PIXEL_SCALE|KEEP_TOGETHER|LONG_GLIDE + var/last_move = null //The direction the atom last moved var/anchored = FALSE // var/elevation = 2 - not used anywhere @@ -512,8 +513,8 @@ new_y = TRANSITIONEDGE + 1 new_x = rand(TRANSITIONEDGE + 2, world.maxx - TRANSITIONEDGE - 2) - if(ticker && istype(ticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear - var/datum/game_mode/nuclear/G = ticker.mode + if(SSticker && istype(SSticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear + var/datum/game_mode/nuclear/G = SSticker.mode G.check_nuke_disks() var/turf/T = locate(new_x, new_y, new_z) diff --git a/code/game/dna/dna2.dm b/code/game/dna/dna2.dm index dcdc8f075b..49fa7554c0 100644 --- a/code/game/dna/dna2.dm +++ b/code/game/dna/dna2.dm @@ -14,7 +14,7 @@ GLOBAL_LIST_EMPTY_TYPED(dna_genes_bad, /datum/gene/trait) /proc/get_gene_from_trait(var/trait_path) // ALWAYS USE THIS RETURN_TYPE(/datum/gene/trait) var/G = GLOB.trait_to_dna_genes[trait_path] - #ifndef UNIT_TEST + #ifndef UNIT_TESTS if(!G) // This SHOULD NOT HAPPEN, be sure any viruses or injectors that give trait paths are actually traitgenes. stack_trace("[trait_path] was used as a traitgene, without being flagged as one.") #endif diff --git a/code/game/gamemodes/cult/runes.dm b/code/game/gamemodes/cult/runes.dm index cfebf37f34..2536f1cf02 100644 --- a/code/game/gamemodes/cult/runes.dm +++ b/code/game/gamemodes/cult/runes.dm @@ -665,7 +665,7 @@ var/list/sacrificed = list() if(lamb.species.rarity_value > 3) worth = 1 - if (ticker.mode.name == "cult") + if (SSticker.mode.name == "cult") if(H.mind == cult.sacrifice_target) if(cultsinrange.len >= 3) sacrificed += H.mind @@ -990,7 +990,7 @@ var/list/sacrificed = list() /obj/effect/rune/proc/bloodboil() //cultists need at least one DANGEROUS rune. Even if they're all stealthy. /* var/list/mob/living/carbon/cultists = new - for(var/datum/mind/H in ticker.mode.cult) + for(var/datum/mind/H in SSticker.mode.cult) if (istype(H.current,/mob/living/carbon)) cultists+=H.current */ diff --git a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm index 8ba1570153..923066ed7d 100644 --- a/code/game/gamemodes/endgame/supermatter_cascade/universe.dm +++ b/code/game/gamemodes/endgame/supermatter_cascade/universe.dm @@ -78,7 +78,19 @@ GLOBAL_VAR_INIT(universe_has_ended, 0) C.req_one_access = list() spawn(5 MINUTES) - ticker.station_explosion_cinematic(0,null) // TODO: Custom cinematic + play_cinematic(/datum/cinematic/nuke/self_destruct) // TODO: Custom cinematic + + // FIXME: Probably a better way + for(var/mob/living/M in GLOB.living_mob_list) + switch(M.z) + if(0) //inside a crate or something + var/turf/T = get_turf(M) + if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and + M.health = 0 + M.set_stat(DEAD) + if(1) //on a z-level 1 turf. + M.health = 0 + M.set_stat(DEAD) GLOB.universe_has_ended = 1 return diff --git a/code/game/gamemodes/epidemic/epidemic.dm b/code/game/gamemodes/epidemic/epidemic.dm index 6be1aeaac3..dd0a8ee2b6 100644 --- a/code/game/gamemodes/epidemic/epidemic.dm +++ b/code/game/gamemodes/epidemic/epidemic.dm @@ -132,7 +132,7 @@ checkwin_counter++ if(checkwin_counter >= 20) if(!finished) - ticker.mode.check_win() + SSticker.mode.check_win() checkwin_counter = 0 return 0 @@ -165,7 +165,7 @@ ///Handle crew failure(station explodes)/// /////////////////////////////////////////// /datum/game_mode/epidemic/proc/crew_lose() - ticker.mode:explosion_in_progress = 1 + SSticker.mode:explosion_in_progress = 1 for(var/mob/M in world) if(M.client) M << 'sound/machines/Alarm.ogg' @@ -177,9 +177,9 @@ enter_allowed = 0 if(ticker) ticker.station_explosion_cinematic(0,null) - if(ticker.mode) - ticker.mode:station_was_nuked = 1 - ticker.mode:explosion_in_progress = 0 + if(SSticker.mode) + SSticker.mode:station_was_nuked = 1 + SSticker.mode:explosion_in_progress = 0 finished = 2 return diff --git a/code/game/gamemodes/game_mode.dm b/code/game/gamemodes/game_mode.dm index df10f34056..18d3c9c8d2 100644 --- a/code/game/gamemodes/game_mode.dm +++ b/code/game/gamemodes/game_mode.dm @@ -104,9 +104,9 @@ GLOBAL_LIST_EMPTY(additional_antag_types) return var/datum/antagonist/antag = GLOB.all_antag_types[choice] if(antag) - if(!islist(ticker.mode.antag_templates)) - ticker.mode.antag_templates = list() - ticker.mode.antag_templates |= antag + if(!islist(SSticker.mode.antag_templates)) + SSticker.mode.antag_templates = list() + SSticker.mode.antag_templates |= antag message_admins("Admin [key_name_admin(usr)] added [antag.role_text] template to game mode.") // I am very sure there's a better way to do this, but I'm not sure what it might be. ~Z @@ -219,8 +219,8 @@ GLOBAL_LIST_EMPTY(additional_antag_types) feedback_set_details("round_start","[time2text(world.realtime)]") INVOKE_ASYNC(SSdbcore, TYPE_PROC_REF(/datum/controller/subsystem/dbcore, SetRoundStart)) - if(ticker && ticker.mode) - feedback_set_details("game_mode","[ticker.mode]") + if(SSticker && SSticker.mode) + feedback_set_details("game_mode","[SSticker.mode]") feedback_set_details("server_ip","[world.internet_address]:[world.port]") return 1 @@ -394,7 +394,7 @@ GLOBAL_LIST_EMPTY(additional_antag_types) return candidates // If this is being called post-roundstart then it doesn't care about ready status. - if(ticker && ticker.current_state == GAME_STATE_PLAYING) + if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) for(var/mob/player in GLOB.player_list) if(!player.client) continue @@ -549,16 +549,16 @@ GLOBAL_LIST_EMPTY(additional_antag_types) set name = "Check Round Info" set category = "OOC.Game" - if(!ticker || !ticker.mode) + if(!SSticker|| !SSticker.mode) to_chat(usr, span_warning("Something is terribly wrong; there is no gametype.")) return if(GLOB.master_mode != "secret") - to_chat(usr, span_boldnotice("The roundtype is [capitalize(ticker.mode.name)]")) - if(ticker.mode.round_description) - to_chat(usr, span_notice(span_italics("[ticker.mode.round_description]"))) - if(ticker.mode.extended_round_description) - to_chat(usr, span_notice("[ticker.mode.extended_round_description]")) + to_chat(usr, span_boldnotice("The roundtype is [capitalize(SSticker.mode.name)]")) + if(SSticker.mode.round_description) + to_chat(usr, span_notice(span_italics("[SSticker.mode.round_description]"))) + if(SSticker.mode.extended_round_description) + to_chat(usr, span_notice("[SSticker.mode.extended_round_description]")) else to_chat(usr, span_notice(span_italics("Shhhh") + ". It's a secret.")) return diff --git a/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm b/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm index 0a555f5612..f2bc0d6000 100644 --- a/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm +++ b/code/game/gamemodes/malfunction/newmalf_ability_trees/HARDWARE.dm @@ -105,7 +105,20 @@ radio.autosay("Self destructing now. Have a nice day.", "Self-Destruct Control") timer-- - if(ticker) - ticker.station_explosion_cinematic(0,null) - if(ticker.mode) - ticker.mode:station_was_nuked = 1 + if(SSticker) + play_cinematic(/datum/cinematic/malf) + + // FIXME: Probably a better way + for(var/mob/living/M in GLOB.living_mob_list) + switch(M.z) + if(0) //inside a crate or something + var/turf/T = get_turf(M) + if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and + M.health = 0 + M.set_stat(DEAD) + if(1) //on a z-level 1 turf. + M.health = 0 + M.set_stat(DEAD) + + if(SSticker.mode) + SSticker.mode:station_was_nuked = 1 diff --git a/code/game/gamemodes/newobjective.dm b/code/game/gamemodes/newobjective.dm index 59d43ee024..e9a3429e45 100644 --- a/code/game/gamemodes/newobjective.dm +++ b/code/game/gamemodes/newobjective.dm @@ -22,7 +22,7 @@ /proc/GenerateAssassinate(var/job,var/datum/mind/traitor) var/list/datum/objective/assassinate/missions = list() - for(var/datum/mind/target in ticker.minds) + for(var/datum/mind/target in SSticker.minds) if((target != traitor) && ishuman(target.current)) if(target && target.current) var/datum/objective/target_obj = new /datum/objective/assassinate(null,job,target) @@ -33,7 +33,7 @@ /proc/GenerateFrame(var/job,var/datum/mind/traitor) var/list/datum/objective/frame/missions = list() - for(var/datum/mind/target in ticker.minds) + for(var/datum/mind/target in SSticker.minds) if((target != traitor) && ishuman(target.current)) if(target && target.current) var/datum/objective/target_obj = new /datum/objective/frame(null,job,target) @@ -44,7 +44,7 @@ /proc/GenerateProtection(var/job,var/datum/mind/traitor) var/list/datum/objective/frame/missions = list() - for(var/datum/mind/target in ticker.minds) + for(var/datum/mind/target in SSticker.minds) if((target != traitor) && ishuman(target.current)) if(target && target.current) var/datum/objective/target_obj = new /datum/objective/protection(null,job,target) @@ -253,7 +253,7 @@ datum proc/get_weight(var/job) return INFINITY proc/find_target_by_role(role, role_type=0)//Option sets either to check assigned role or special role. Default to assigned. - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if((possible_target != owner) && ishuman(possible_target.current) && ((role_type ? possible_target.special_role : possible_target.assigned_role) == role) ) target = possible_target break @@ -387,7 +387,7 @@ datum return 0 find_target_by_role(var/role) - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if((possible_target != owner) && ishuman(possible_target.current) && (possible_target.assigned_role == role)) target = possible_target break @@ -403,7 +403,7 @@ datum proc/find_target() var/list/possible_targets = list() - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if((possible_target != owner) && ishuman(possible_target.current)) possible_targets += possible_target @@ -438,7 +438,7 @@ datum return 1 find_target_by_role(var/role) - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if((possible_target != owner) && ishuman(possible_target.current) && (possible_target.assigned_role == role)) target = possible_target break @@ -1287,13 +1287,13 @@ datum target_amount = rand (lowbound,highbound) if (ticker) var/n_p = 1 //autowin - if (ticker.current_state == GAME_STATE_SETTING_UP) + if (SSticker.current_state == GAME_STATE_SETTING_UP) for(var/mob/new_player/P in world) if(P.client && P.ready && P.mind!=owner) n_p ++ - else if (ticker.current_state == GAME_STATE_PLAYING) + else if (SSticker.current_state == GAME_STATE_PLAYING) for(var/mob/living/carbon/human/P in world) - if(P.client && !(P.mind in ticker.mode.changelings) && P.mind!=owner) + if(P.client && !(P.mind in SSticker.mode.changelings) && P.mind!=owner) n_p ++ target_amount = min(target_amount, n_p) diff --git a/code/game/gamemodes/objective.dm b/code/game/gamemodes/objective.dm index db507c5fc1..b894823eea 100644 --- a/code/game/gamemodes/objective.dm +++ b/code/game/gamemodes/objective.dm @@ -23,7 +23,7 @@ GLOBAL_LIST_EMPTY(all_objectives) /datum/objective/proc/find_target() var/list/possible_targets = list() - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != 2)) possible_targets += possible_target if(possible_targets.len > 0) @@ -31,7 +31,7 @@ GLOBAL_LIST_EMPTY(all_objectives) /datum/objective/proc/find_target_by_role(role, role_type=0)//Option sets either to check assigned role or special role. Default to assigned. - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if((possible_target != owner) && ishuman(possible_target.current) && ((role_type ? possible_target.special_role : possible_target.assigned_role) == role) ) target = possible_target break @@ -573,13 +573,13 @@ GLOBAL_LIST_EMPTY(all_objectives) /datum/objective/absorb/proc/gen_amount_goal(var/lowbound = 4, var/highbound = 6) target_amount = rand (lowbound,highbound) - if (ticker) + if (SSticker) var/n_p = 1 //autowin - if (ticker.current_state == GAME_STATE_SETTING_UP) + if (SSticker.current_state == GAME_STATE_SETTING_UP) for(var/mob/new_player/P in GLOB.player_list) if(P.client && P.ready && P.mind!=owner) n_p ++ - else if (ticker.current_state == GAME_STATE_PLAYING) + else if (SSticker.current_state == GAME_STATE_PLAYING) for(var/mob/living/carbon/human/P in GLOB.player_list) var/datum/component/antag/changeling/comp = P.GetComponent(/datum/component/antag/changeling) if(P.client && !(comp) && P.mind!=owner) @@ -612,7 +612,7 @@ GLOBAL_LIST_EMPTY(all_objectives) var/list/possible_targets = list() var/list/priority_targets = list() - for(var/datum/mind/possible_target in ticker.minds) + for(var/datum/mind/possible_target in SSticker.minds) if(possible_target != owner && ishuman(possible_target.current) && (possible_target.current.stat != 2) && (!possible_target.special_role)) possible_targets += possible_target for(var/role in roles) diff --git a/code/game/jobs/job_controller.dm b/code/game/jobs/job_controller.dm index b454050492..b52d7158be 100644 --- a/code/game/jobs/job_controller.dm +++ b/code/game/jobs/job_controller.dm @@ -228,7 +228,7 @@ var/global/datum/controller/occupations/job_master SetupOccupations() //Holder for Triumvirate is stored in the ticker, this just processes it - if(ticker && ticker.triai) + if(SSticker && GLOB.triai) for(var/datum/job/A in occupations) if(A.title == JOB_AI) A.spawn_positions = 3 @@ -273,7 +273,7 @@ var/global/datum/controller/occupations/job_master // Loop through all levels from high to low var/list/shuffledoccupations = shuffle(occupations) - // var/list/disabled_jobs = ticker.mode.disabled_jobs // So we can use .Find down below without a colon. + // var/list/disabled_jobs = SSticker.mode.disabled_jobs // So we can use .Find down below without a colon. for(var/level = 1 to 3) //Check the head jobs first each level CheckHeadPositions(level) @@ -283,7 +283,7 @@ var/global/datum/controller/occupations/job_master // Loop through all jobs for(var/datum/job/job in shuffledoccupations) // SHUFFLE ME BABY - if(!job || ticker.mode.disabled_jobs.Find(job.title) ) + if(!job || SSticker.mode.disabled_jobs.Find(job.title) ) continue if(jobban_isbanned(player, job.title)) @@ -346,7 +346,7 @@ var/global/datum/controller/occupations/job_master //For ones returning to lobby for(var/mob/new_player/player in unassigned) if(player.client.prefs.alternate_option == RETURN_TO_LOBBY) - player.ready = 0 + player.ready = PLAYER_NOT_READY unassigned -= player return 1 @@ -503,7 +503,7 @@ var/global/datum/controller/occupations/job_master // TWEET PEEP if(rank == JOB_SITE_MANAGER && announce) - var/sound/announce_sound = (ticker.current_state <= GAME_STATE_SETTING_UP) ? null : sound('sound/misc/boatswain.ogg', volume=20) + var/sound/announce_sound = (SSticker.current_state <= GAME_STATE_SETTING_UP) ? null : sound('sound/misc/boatswain.ogg', volume=20) captain_announcement.Announce("All hands, [alt_title ? alt_title : JOB_SITE_MANAGER] [H.real_name] on deck!", new_sound = announce_sound, zlevel = H.z) //Deferred item spawning. diff --git a/code/game/json.dm b/code/game/json.dm deleted file mode 100644 index 5a0e62b0aa..0000000000 --- a/code/game/json.dm +++ /dev/null @@ -1,100 +0,0 @@ - -var/jsonpath = "/home/bay12/public_html" -var/dmepath = "/home/bay12/git/baystation12.dme" -var/makejson = 1 //temp -/proc/makejson() - - if(!makejson) - return - fdel("[jsonpath]/info.json") - //to_chat(usr, "Error cant delete json") - //else - //to_chat(usr, "Deleted json in public html") - fdel("info.json") - //to_chat(usr, "error cant delete local json") - //else - //to_chat(usr, "Deleted local json") - var/F = file("info.json") - if(!isfile(F)) - return - var/mode - if(ticker) - if(ticker.current_state == 1) - mode = "Round Setup" - else if(ticker.hide_mode) - mode = "SECRET" - else - mode = GLOB.master_mode - var/playerscount = 0 - var/players = "" - var/admins = "no" - for(var/client/C) - playerscount++ - if(C.holder && C.holder.level >= 0) // make sure retired admins don't make nt think admins are on - if(!C.stealth) - admins = "yes" - players += "[C.key];" - else - players += "[C.fakekey];" - else - players += "[C.key];" - to_chat(F, "{\"mode\":\"[mode]\",\"players\" : \"[players]\",\"playercount\" : \"[playerscount]\",\"admin\" : \"[admins]\",\"time\" : \"[time2text(world.realtime,"MM/DD - hh:mm")]\"}") - fcopy("info.json","[jsonpath]/info.json") - -/proc/switchmap(newmap,newpath) - var/oldmap - var/obj/mapinfo/M = locate() - - if(M) - oldmap = M.mapname - - else - message_admins("Did not locate mapinfo object. Go bug the mapper to add a /obj/mapinfo to their map!\n For now, you can probably spawn one manually. If you do, be sure to set it's mapname var correctly, or else you'll just get an error again.") - return - - message_admins("Current map: [oldmap]") - var/text = file2text(dmepath) - var/path = "#include \"maps/[oldmap].dmm\"" - var/xpath = "#include \"maps/[newpath].dmm\"" - var/loc = findtext(text,path,1,0) - if(!loc) - path = "#include \"maps\\[oldmap].dmm\"" - xpath = "#include \"maps\\[newpath].dmm\"" - loc = findtext(text,path,1,0) - if(!loc) - message_admins("Could not find '#include \"maps\\[oldmap].dmm\"' or '\"maps/[oldmap].dmm\"' in the bs12.dme. The mapinfo probably has an incorrect mapname var. Alternatively, could not find the .dme itself, at [dmepath].") - return - - var/rest = copytext(text, loc + length(path)) - text = copytext(text,1,loc) - text += "\n[xpath]" - text += rest -/* for(var/A in lines) - if(findtext(A,path,1,0)) - lineloc = lines.Find(A,1,0) - lines[lineloc] = xpath - to_world("FOUND")*/ - fdel(dmepath) - var/file = file(dmepath) - file << text - message_admins("Compiling...") - shell("./recompile") - message_admins("Done") - world.Reboot("Switching to [newmap]") - -/obj/mapinfo - invisibility = INVISIBILITY_ABSTRACT - var/mapname = "thismap" - var/decks = 4 -/proc/GetMapInfo() -// var/obj/mapinfo/M = locate() -// Just removing these to try and fix the occasional JSON -> WORLD issue. -// to_world(M.name) -// to_world(M.mapname) -/client/proc/ChangeMap(var/X as text) - set name = "Change Map" - set category = "Admin" - switchmap(X,X) -/proc/send2adminirc(channel,msg) - world << channel << " "<< msg - shell("python nudge.py '[channel]' [msg]") diff --git a/code/game/machinery/air_alarm.dm b/code/game/machinery/air_alarm.dm index 0c85dfb797..9843274ad5 100644 --- a/code/game/machinery/air_alarm.dm +++ b/code/game/machinery/air_alarm.dm @@ -434,9 +434,9 @@ send_signal(id_tag, list("status")) /obj/machinery/alarm/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_TO_AIRALARM) + radio_connection = SSradio.add_object(src, frequency, RADIO_TO_AIRALARM) /obj/machinery/alarm/proc/send_signal(var/target, var/list/command)//sends signal 'command' to 'target'. Returns 0 if no radio connection, 1 otherwise if(!radio_connection) @@ -497,7 +497,7 @@ update_icon() /obj/machinery/alarm/proc/post_alert(alert_level) - var/datum/radio_frequency/frequency = radio_controller.return_frequency(alarm_frequency) + var/datum/radio_frequency/frequency = SSradio.return_frequency(alarm_frequency) if(!frequency) return diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm index fee07cde30..4fd35cf9e5 100644 --- a/code/game/machinery/atmo_control.dm +++ b/code/game/machinery/atmo_control.dm @@ -60,9 +60,9 @@ radio_connection.post_signal(src, signal, radio_filter = RADIO_ATMOSIA) /obj/machinery/air_sensor/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) /obj/machinery/air_sensor/Initialize(mapload) . = ..() @@ -70,8 +70,8 @@ set_frequency(frequency) /obj/machinery/air_sensor/Destroy() - if(radio_controller) - radio_controller.remove_object(src,frequency) + if(SSradio) + SSradio.remove_object(src,frequency) . = ..() /obj/machinery/computer/general_air_control @@ -86,8 +86,8 @@ circuit = /obj/item/circuitboard/air_management /obj/machinery/computer/general_air_control/Destroy() - if(radio_controller) - radio_controller.remove_object(src, frequency) + if(SSradio) + SSradio.remove_object(src, frequency) . = ..() /obj/machinery/computer/general_air_control/attack_hand(mob/user) @@ -126,9 +126,9 @@ return data /obj/machinery/computer/general_air_control/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, RADIO_ATMOSIA) /obj/machinery/computer/general_air_control/Initialize(mapload) . = ..() diff --git a/code/game/machinery/atmoalter/meter.dm b/code/game/machinery/atmoalter/meter.dm index 92c38482cd..62eb28e19a 100644 --- a/code/game/machinery/atmoalter/meter.dm +++ b/code/game/machinery/atmoalter/meter.dm @@ -61,7 +61,7 @@ icon_state = "meter4" if(frequency) - var/datum/radio_frequency/radio_connection = radio_controller.return_frequency(frequency) + var/datum/radio_frequency/radio_connection = SSradio.return_frequency(frequency) if(!radio_connection) return diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm index 23d3d8d703..9d0b7939e2 100644 --- a/code/game/machinery/camera/camera.dm +++ b/code/game/machinery/camera/camera.dm @@ -346,7 +346,7 @@ /atom/proc/auto_turn() //Automatically turns based on nearby walls. var/turf/simulated/wall/T = null - for(var/i = 1, i <= 8; i += i) + for(var/i = 1, i <= 8, i += i) T = get_ranged_target_turf(src, i, 1) if(istype(T)) //If someone knows a better way to do this, let me know. -Giacom diff --git a/code/game/machinery/computer/arcade.dm b/code/game/machinery/computer/arcade.dm index c376eab801..6e8fe1da9b 100644 --- a/code/game/machinery/computer/arcade.dm +++ b/code/game/machinery/computer/arcade.dm @@ -982,9 +982,9 @@ newcrew = specific else if(prob(50)) - newcrew = pick(first_names_male) + newcrew = pick(GLOB.first_names_male) else - newcrew = pick(first_names_female) + newcrew = pick(GLOB.first_names_female) if(newcrew) settlers += newcrew alive++ diff --git a/code/game/machinery/computer/medical.dm b/code/game/machinery/computer/medical.dm index dd0153c3b7..97b58e93e5 100644 --- a/code/game/machinery/computer/medical.dm +++ b/code/game/machinery/computer/medical.dm @@ -487,7 +487,7 @@ if(prob(10/severity)) switch(rand(1,6)) if(1) - R.fields["name"] = "[pick(pick(first_names_male), pick(first_names_female))] [pick(last_names)]" + R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" if(2) R.fields["sex"] = pick("Male", "Female") if(3) diff --git a/code/game/machinery/computer/prisonshuttle.dm b/code/game/machinery/computer/prisonshuttle.dm index ababe6e073..5e2c0e9399 100644 --- a/code/game/machinery/computer/prisonshuttle.dm +++ b/code/game/machinery/computer/prisonshuttle.dm @@ -117,7 +117,7 @@ GLOBAL_VAR_INIT(prison_shuttle_timeleft, 0) /obj/machinery/computer/prison_shuttle/proc/post_signal(var/command) - var/datum/radio_frequency/frequency = radio_controller.return_frequency(1311) + var/datum/radio_frequency/frequency = SSradio.return_frequency(1311) if(!frequency) return var/datum/signal/status_signal = new status_signal.source = src diff --git a/code/game/machinery/computer/security.dm b/code/game/machinery/computer/security.dm index eb6daa5865..04ae7ac3c0 100644 --- a/code/game/machinery/computer/security.dm +++ b/code/game/machinery/computer/security.dm @@ -498,7 +498,7 @@ if(prob(10/severity)) switch(rand(1,6)) if(1) - R.fields["name"] = "[pick(pick(first_names_male), pick(first_names_female))] [pick(last_names)]" + R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" if(2) R.fields["sex"] = pick("Male", "Female") if(3) diff --git a/code/game/machinery/computer/shuttle.dm b/code/game/machinery/computer/shuttle.dm index d79c4ae186..bd464438f7 100644 --- a/code/game/machinery/computer/shuttle.dm +++ b/code/game/machinery/computer/shuttle.dm @@ -10,7 +10,7 @@ /obj/machinery/computer/shuttle/attackby(var/obj/item/card/W as obj, var/mob/user as mob) if(stat & (BROKEN|NOPOWER)) return - if ((!( istype(W, /obj/item/card) ) || !( ticker ) || emergency_shuttle.location() || !( user ))) return + if ((!( istype(W, /obj/item/card) ) || !( SSticker ) || emergency_shuttle.location() || !( user ))) return if (istype(W, /obj/item/card/id)||istype(W, /obj/item/pda)) if (istype(W, /obj/item/pda)) var/obj/item/pda/pda = W diff --git a/code/game/machinery/computer/skills.dm b/code/game/machinery/computer/skills.dm index fce51ce7cb..0e959f8d5d 100644 --- a/code/game/machinery/computer/skills.dm +++ b/code/game/machinery/computer/skills.dm @@ -375,7 +375,7 @@ if(prob(10/severity)) switch(rand(1,6)) if(1) - R.fields["name"] = "[pick(pick(first_names_male), pick(first_names_female))] [pick(last_names)]" + R.fields["name"] = "[pick(pick(GLOB.first_names_male), pick(GLOB.first_names_female))] [pick(GLOB.last_names)]" if(2) R.fields["sex"] = pick("Male", "Female") if(3) diff --git a/code/game/machinery/computer/supply.dm b/code/game/machinery/computer/supply.dm index e9a6132c57..d610f6a187 100644 --- a/code/game/machinery/computer/supply.dm +++ b/code/game/machinery/computer/supply.dm @@ -488,7 +488,7 @@ add_fingerprint(ui.user) /obj/machinery/computer/supplycomp/proc/post_signal(var/command) - var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435) + var/datum/radio_frequency/frequency = SSradio.return_frequency(1435) if(!frequency) return diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index 5bd314c23b..47af6f3134 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -489,8 +489,8 @@ to_despawn.mind.special_role = null //else - //if(ticker.mode.name == "AutoTraitor") - //var/datum/game_mode/traitor/autotraitor/current_mode = ticker.mode + //if(SSticker.mode.name == "AutoTraitor") + //var/datum/game_mode/traitor/autotraitor/current_mode = SSticker.mode //current_mode.possible_traitors.Remove(to_despawn) // Delete them from datacore. diff --git a/code/game/machinery/doors/airlock_control.dm b/code/game/machinery/doors/airlock_control.dm index a66d2bc772..01a1038971 100644 --- a/code/game/machinery/doors/airlock_control.dm +++ b/code/game/machinery/doors/airlock_control.dm @@ -143,15 +143,15 @@ /obj/machinery/door/airlock/proc/set_frequency(new_frequency) radio_connection = null - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(new_frequency) - radio_connection = radio_controller.add_object(src, new_frequency, RADIO_AIRLOCK) + radio_connection = SSradio.add_object(src, new_frequency, RADIO_AIRLOCK) /obj/machinery/door/airlock/Destroy() - if(frequency && radio_controller) - radio_controller.remove_object(src,frequency) + if(frequency && SSradio) + SSradio.remove_object(src,frequency) return ..() /obj/machinery/airlock_sensor @@ -218,17 +218,17 @@ update_icon() /obj/machinery/airlock_sensor/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_AIRLOCK) + radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) /obj/machinery/airlock_sensor/Initialize(mapload) . = ..() set_frequency(frequency) /obj/machinery/airlock_sensor/Destroy() - if(radio_controller) - radio_controller.remove_object(src,frequency) + if(SSradio) + SSradio.remove_object(src,frequency) return ..() /obj/machinery/airlock_sensor/examine(mob/user, infix, suffix) @@ -363,9 +363,9 @@ /obj/machinery/access_button/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_AIRLOCK) + radio_connection = SSradio.add_object(src, frequency, RADIO_AIRLOCK) /obj/machinery/access_button/Initialize(mapload) @@ -373,8 +373,8 @@ set_frequency(frequency) /obj/machinery/access_button/Destroy() - if(radio_controller) - radio_controller.remove_object(src, frequency) + if(SSradio) + SSradio.remove_object(src, frequency) return ..() /obj/machinery/access_button/airlock_interior diff --git a/code/game/machinery/doors/alarmlock.dm b/code/game/machinery/doors/alarmlock.dm index 399d11b1fe..76950f5f9a 100644 --- a/code/game/machinery/doors/alarmlock.dm +++ b/code/game/machinery/doors/alarmlock.dm @@ -10,14 +10,14 @@ autoclose = 0 /obj/machinery/door/airlock/alarmlock/Destroy() - if(radio_controller) - radio_controller.remove_object(src,air_frequency) + if(SSradio) + SSradio.remove_object(src,air_frequency) . = ..() /obj/machinery/door/airlock/alarmlock/Initialize(mapload) . = ..() - radio_controller.remove_object(src, air_frequency) - air_connection = radio_controller.add_object(src, air_frequency, RADIO_TO_AIRALARM) + SSradio.remove_object(src, air_frequency) + air_connection = SSradio.add_object(src, air_frequency, RADIO_TO_AIRALARM) open() diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm index 597e138491..f1fcf8be38 100644 --- a/code/game/machinery/doors/door.dm +++ b/code/game/machinery/doors/door.dm @@ -99,12 +99,12 @@ START_MACHINE_PROCESSING(src) /obj/machinery/door/proc/can_open() - if(!density || operating || !ticker) + if(!density || operating || !SSticker) return 0 return 1 /obj/machinery/door/proc/can_close() - if(density || operating || !ticker) + if(density || operating || !SSticker) return 0 return 1 diff --git a/code/game/machinery/doors/windowdoor.dm b/code/game/machinery/doors/windowdoor.dm index 5880c713bd..db33596538 100644 --- a/code/game/machinery/doors/windowdoor.dm +++ b/code/game/machinery/doors/windowdoor.dm @@ -74,7 +74,7 @@ open() addtimer(CALLBACK(src, PROC_REF(close)), 50) return - if (!( ticker )) + if (!( SSticker )) return if (src.operating) return @@ -106,7 +106,7 @@ /obj/machinery/door/window/open() if (operating == 1 || !density) //doors can still open when emag-disabled return 0 - if (!ticker) + if (!SSticker) return 0 if (!operating) //in case of emag operating = 1 diff --git a/code/game/machinery/embedded_controller/embedded_controller_base.dm b/code/game/machinery/embedded_controller/embedded_controller_base.dm index f58515b19e..c2f3bba77e 100644 --- a/code/game/machinery/embedded_controller/embedded_controller_base.dm +++ b/code/game/machinery/embedded_controller/embedded_controller_base.dm @@ -89,8 +89,8 @@ . = ..() /obj/machinery/embedded_controller/radio/Destroy() - if(radio_controller) - radio_controller.remove_object(src,frequency) + if(SSradio) + SSradio.remove_object(src,frequency) . = ..() /obj/machinery/embedded_controller/radio/update_icon() @@ -111,6 +111,6 @@ qdel(signal) /obj/machinery/embedded_controller/radio/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, radio_filter) + radio_connection = SSradio.add_object(src, frequency, radio_filter) diff --git a/code/game/machinery/magnet.dm b/code/game/machinery/magnet.dm index 3743ca2b2c..cceb8e6177 100644 --- a/code/game/machinery/magnet.dm +++ b/code/game/machinery/magnet.dm @@ -33,8 +33,8 @@ hide(!T.is_plating()) center = T - if(radio_controller) - radio_controller.add_object(src, freq, RADIO_MAGNETS) + if(SSradio) + SSradio.add_object(src, freq, RADIO_MAGNETS) magnetic_process() @@ -182,8 +182,8 @@ pulling = 0 /obj/machinery/magnetic_module/Destroy() - if(radio_controller) - radio_controller.remove_object(src, freq) + if(SSradio) + SSradio.remove_object(src, freq) . = ..() /obj/machinery/magnetic_controller @@ -220,8 +220,8 @@ magnets.Add(M) - if(radio_controller) - radio_connection = radio_controller.add_object(src, frequency, RADIO_MAGNETS) + if(SSradio) + radio_connection = SSradio.add_object(src, frequency, RADIO_MAGNETS) if(path) // check for default path @@ -390,6 +390,6 @@ // there doesn't HAVE to be separators but it makes paths syntatically visible /obj/machinery/magnetic_controller/Destroy() - if(radio_controller) - radio_controller.remove_object(src, frequency) + if(SSradio) + SSradio.remove_object(src, frequency) . = ..() diff --git a/code/game/machinery/nuclear_bomb.dm b/code/game/machinery/nuclear_bomb.dm index b16ae0a156..5471070d9f 100644 --- a/code/game/machinery/nuclear_bomb.dm +++ b/code/game/machinery/nuclear_bomb.dm @@ -371,8 +371,8 @@ GLOBAL_VAR(bomb_set) if(!lighthack) icon_state = "nuclearbomb3" playsound(src,'sound/machines/Alarm.ogg',100,0,5) - if(ticker && ticker.mode) - ticker.mode.explosion_in_progress = 1 + if(SSticker && SSticker.mode) + SSticker.mode.explosion_in_progress = 1 sleep(100) var/off_station = 0 @@ -383,21 +383,47 @@ GLOBAL_VAR(bomb_set) else off_station = 2 - if(ticker) - if(ticker.mode && ticker.mode.name == "Mercenary") + if(SSticker) + if(SSticker.mode && SSticker.mode.name == "Mercenary") var/obj/machinery/computer/shuttle_control/multi/syndicate/syndie_location = locate(/obj/machinery/computer/shuttle_control/multi/syndicate) if(syndie_location) - ticker.mode:syndies_didnt_escape = (syndie_location.z > 1 ? 0 : 1) //muskets will make me change this, but it will do for now - ticker.mode:nuke_off_station = off_station - ticker.station_explosion_cinematic(off_station,null) - if(ticker.mode) - ticker.mode.explosion_in_progress = 0 + SSticker.mode:syndies_didnt_escape = (syndie_location.z > 1 ? 0 : 1) //muskets will make me change this, but it will do for now + SSticker.mode:nuke_off_station = off_station + + switch(off_station) + if(0) + if(SSticker.mode.name == "mercenary") + play_cinematic(/datum/cinematic/nuke/ops_victory) + else + play_cinematic(/datum/cinematic/nuke/self_destruct) + + // FIXME: Probably a better way + for(var/mob/living/M in GLOB.living_mob_list) + switch(M.z) + if(0) //inside a crate or something + var/turf/T = get_turf(M) + if(T && (T.z in using_map.station_levels)) //we don't use M.death(0) because it calls a for(/mob) loop and + M.health = 0 + M.set_stat(DEAD) + if(1) //on a z-level 1 turf. + M.health = 0 + M.set_stat(DEAD) + if(1) + if(SSticker.mode.name == "mercenary") + play_cinematic(/datum/cinematic/nuke/ops_miss) + else + play_cinematic(/datum/cinematic/nuke/self_destruct_miss) + if(2) + play_cinematic(/datum/cinematic/nuke/far_explosion) + + if(SSticker.mode) + SSticker.mode.explosion_in_progress = 0 to_world(span_boldannounce("The station was destoyed by the nuclear blast!")) - ticker.mode.station_was_nuked = (off_station<2) //offstation==1 is a draw. the station becomes irradiated and needs to be evacuated. + SSticker.mode.station_was_nuked = (off_station<2) //offstation==1 is a draw. the station becomes irradiated and needs to be evacuated. //kinda shit but I couldn't get permission to do what I wanted to do. - if(!ticker.mode.check_finished())//If the mode does not deal with the nuke going off so just reboot because everyone is stuck as is + if(!SSticker.mode.check_finished())//If the mode does not deal with the nuke going off so just reboot because everyone is stuck as is to_world(span_boldannounce("Resetting in 30 seconds!")) feedback_set_details("end_error","nuke - unhandled ending") diff --git a/code/game/machinery/status_display.dm b/code/game/machinery/status_display.dm index 706a2f4939..ec8ae422d8 100644 --- a/code/game/machinery/status_display.dm +++ b/code/game/machinery/status_display.dm @@ -53,8 +53,8 @@ var/seclevel = "green" /obj/machinery/status_display/Destroy() - if(radio_controller) - radio_controller.remove_object(src,frequency) + if(SSradio) + SSradio.remove_object(src,frequency) return ..() /obj/machinery/status_display/attackby(I as obj, user as mob) @@ -67,8 +67,8 @@ // register for radio system /obj/machinery/status_display/Initialize(mapload) . = ..() - if(radio_controller) - radio_controller.add_object(src, frequency) + if(SSradio) + SSradio.add_object(src, frequency) // timed process /obj/machinery/status_display/process() diff --git a/code/game/machinery/telecomms/broadcaster.dm b/code/game/machinery/telecomms/broadcaster.dm index 2330451a4c..015ef3a5bd 100644 --- a/code/game/machinery/telecomms/broadcaster.dm +++ b/code/game/machinery/telecomms/broadcaster.dm @@ -379,7 +379,7 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages else if(data == DATA_ANTAG) for(var/antag_freq in ANTAG_FREQS) - var/datum/radio_frequency/antag_connection = radio_controller.return_frequency(antag_freq) + var/datum/radio_frequency/antag_connection = SSradio.return_frequency(antag_freq) for (var/obj/item/radio/R in antag_connection.devices["[RADIO_CHAT]"]) if(R.receive_range(antag_freq, level) > -1) radios |= R @@ -564,7 +564,7 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages var/mob/living/carbon/human/H = new M = H - var/datum/radio_frequency/connection = radio_controller.return_frequency(frequency) + var/datum/radio_frequency/connection = SSradio.return_frequency(frequency) var/display_freq = connection.frequency @@ -598,7 +598,7 @@ GLOBAL_VAR_INIT(message_delay, 0) // To make sure restarting the recentmessages else if(data == DATA_ANTAG) for(var/freq in ANTAG_FREQS) - var/datum/radio_frequency/antag_connection = radio_controller.return_frequency(freq) + var/datum/radio_frequency/antag_connection = SSradio.return_frequency(freq) for (var/obj/item/radio/R in antag_connection.devices["[RADIO_CHAT]"]) var/turf/position = get_turf(R) if(position && position.z == level) diff --git a/code/game/mecha/mech_sensor.dm b/code/game/mecha/mech_sensor.dm index 1541e22ad8..77a6a64575 100644 --- a/code/game/mecha/mech_sensor.dm +++ b/code/game/mecha/mech_sensor.dm @@ -75,10 +75,10 @@ /obj/machinery/mech_sensor/proc/set_frequency(new_frequency) if(radio_connection) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency) + radio_connection = SSradio.add_object(src, frequency) /obj/machinery/mech_sensor/receive_signal(datum/signal/signal) if(stat & NOPOWER) diff --git a/code/game/objects/buckling.dm b/code/game/objects/buckling.dm index 0404e30235..9320b64701 100644 --- a/code/game/objects/buckling.dm +++ b/code/game/objects/buckling.dm @@ -116,7 +116,7 @@ //Wrapper procs that handle sanity and user feedback /atom/movable/proc/user_buckle_mob(mob/living/M, mob/user, var/forced = FALSE, var/silent = FALSE) - if(!ticker) + if(!SSticker) to_chat(user, span_warning("You can't buckle anyone in before the game starts.")) return FALSE // Is this really needed? if(!user.Adjacent(M) || user.restrained() || user.stat || ispAI(user)) diff --git a/code/game/objects/effects/landmarks.dm b/code/game/objects/effects/landmarks.dm index ee8495c602..3f3e41f5f0 100644 --- a/code/game/objects/effects/landmarks.dm +++ b/code/game/objects/effects/landmarks.dm @@ -268,3 +268,13 @@ . = ..() new /obj/item/clothing/mask/gas/sexymime(src.loc) new /obj/item/clothing/under/sexymime(src.loc) + +/// Marks the bottom left of the testing zone. +/// In landmarks.dm and not unit_test.dm so it is always active in the mapping tools. +/obj/effect/landmark/unit_test_bottom_left + name = "unit test zone bottom left" + +/// Marks the top right of the testing zone. +/// In landmarks.dm and not unit_test.dm so it is always active in the mapping tools. +/obj/effect/landmark/unit_test_top_right + name = "unit test zone top right" diff --git a/code/game/objects/effects/prop/snake.dm b/code/game/objects/effects/prop/snake.dm index 463c5dc4ca..057e3d5ba4 100644 --- a/code/game/objects/effects/prop/snake.dm +++ b/code/game/objects/effects/prop/snake.dm @@ -47,7 +47,7 @@ if(LAZYLEN(iterated_turfs) && iterated_turfs.len > total_turf_memory) iterated_turfs.Cut(total_turf_memory + 1) - for(var/direction in list(NORTH, SOUTH, EAST, WEST, NORTHEAST, NORTHWEST, SOUTHEAST, SOUTHWEST) - turn(src.dir,180)) + for(var/direction in GLOB.alldirs - turn(src.dir,180)) var/turf/T = get_step(src, direction) if(T in iterated_turfs) continue diff --git a/code/game/objects/items.dm b/code/game/objects/items.dm index ad72c91d4d..89c2e8aa8e 100644 --- a/code/game/objects/items.dm +++ b/code/game/objects/items.dm @@ -904,7 +904,7 @@ modules/mob/living/carbon/human/life.dm if you die, you will be zoomed out. // testing("[src] (\ref[src]) - Slot: [slot_name], Inhands: [inhands], Worn Icon:[icon2use], Worn State:[state2use], Worn Layer:[layer2use]") // Send icon data to unit test when it is running, hello old testing(). I'm like, your great great grandkid! THE FUTURE IS NOW OLD MAN! - #ifdef UNIT_TEST + #ifdef UNIT_TESTS var/mob/living/carbon/human/H = loc if(ishuman(H)) SEND_SIGNAL(H, COMSIG_UNITTEST_DATA, list("set_slot",slot_name,icon2use,state2use,inhands,type,H.species?.name)) diff --git a/code/game/objects/items/devices/communicator/messaging.dm b/code/game/objects/items/devices/communicator/messaging.dm index ba35b16549..370fd2d819 100644 --- a/code/game/objects/items/devices/communicator/messaging.dm +++ b/code/game/objects/items/devices/communicator/messaging.dm @@ -118,7 +118,7 @@ set name = "Text Communicator" set desc = "If there is a communicator available, send a text message to it." - if(ticker.current_state < GAME_STATE_PLAYING) + if(SSticker.current_state < GAME_STATE_PLAYING) to_chat(src, span_danger("The game hasn't started yet!")) return diff --git a/code/game/objects/items/devices/communicator/phone.dm b/code/game/objects/items/devices/communicator/phone.dm index 3d9c95ce12..ee28d8b20f 100644 --- a/code/game/objects/items/devices/communicator/phone.dm +++ b/code/game/objects/items/devices/communicator/phone.dm @@ -276,7 +276,7 @@ set name = "Call Communicator" set desc = "If there is a communicator available, send a request to speak through it. This will reset your respawn timer, if someone picks up." - if(ticker.current_state < GAME_STATE_PLAYING) + if(SSticker.current_state < GAME_STATE_PLAYING) to_chat(src, span_danger("The game hasn't started yet!")) return diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 3498e259c9..d8c8eaddd6 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -502,7 +502,7 @@ if(has_channel_access(card.pai, internal_chan)) channels += ch_name channels[ch_name] = 1 - secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT) + secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT) /obj/item/paicard/typeb name = "personal AI device" diff --git a/code/game/objects/items/devices/radio/electropack.dm b/code/game/objects/items/devices/radio/electropack.dm index 76500af122..ec099a8536 100644 --- a/code/game/objects/items/devices/radio/electropack.dm +++ b/code/game/objects/items/devices/radio/electropack.dm @@ -47,7 +47,7 @@ //..() if(usr.stat || usr.restrained()) return - if(((ishuman(usr) && ((!( ticker ) || (ticker && ticker.mode != "monkey")) && usr.contents.Find(src))) || (usr.contents.Find(master) || (in_range(src, usr) && istype(loc, /turf))))) + if(((ishuman(usr) && ((!( SSticker ) || (SSticker && SSticker.mode != "monkey")) && usr.contents.Find(src))) || (usr.contents.Find(master) || (in_range(src, usr) && istype(loc, /turf))))) usr.set_machine(src) if(href_list["freq"]) var/new_frequency = sanitize_frequency(frequency + text2num(href_list["freq"])) diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index d8bbc60462..7995048000 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -95,7 +95,7 @@ for(var/ch_name in channels) - radio_controller.remove_object(src, radiochannels[ch_name]) + SSradio.remove_object(src, radiochannels[ch_name]) secure_radio_connections[ch_name] = null @@ -183,15 +183,15 @@ /obj/item/radio/headset/proc/handle_finalize_recalculatechannels(var/setDescription = FALSE, var/initial_run = FALSE) PRIVATE_PROC(TRUE) SHOULD_NOT_OVERRIDE(TRUE) - if(!radio_controller && initial_run) + if(!SSradio && initial_run) addtimer(CALLBACK(src,PROC_REF(handle_finalize_recalculatechannels),setDescription, FALSE),3 SECONDS) return - if(!radio_controller && !initial_run) + if(!SSradio && !initial_run) name = "broken radio headset" return for (var/ch_name in channels) - secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT) + secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT) if(setDescription) setupRadioDescription() diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm index 20aeb06a96..74746d6b56 100644 --- a/code/game/objects/items/devices/radio/radio.dm +++ b/code/game/objects/items/devices/radio/radio.dm @@ -45,9 +45,9 @@ var/list/datum/radio_frequency/secure_radio_connections /obj/item/radio/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) + radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) /obj/item/radio/Initialize(mapload) . = ..() @@ -57,7 +57,7 @@ set_frequency(frequency) for (var/ch_name in channels) - secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT) + secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT) wires = new(src) internal_channels = GLOB.default_internal_channels.Copy() @@ -106,10 +106,10 @@ qdel(wires) wires = null GLOB.listening_objects -= src - if(radio_controller) - radio_controller.remove_object(src, frequency) + if(SSradio) + SSradio.remove_object(src, frequency) for (var/ch_name in channels) - radio_controller.remove_object(src, radiochannels[ch_name]) + SSradio.remove_object(src, radiochannels[ch_name]) return ..() /obj/item/radio/proc/recalculateChannels() @@ -649,7 +649,7 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer) for(var/ch_name in channels) - radio_controller.remove_object(src, radiochannels[ch_name]) + SSradio.remove_object(src, radiochannels[ch_name]) secure_radio_connections[ch_name] = null @@ -707,24 +707,24 @@ GLOBAL_DATUM(autospeaker, /mob/living/silicon/ai/announcer) /obj/item/radio/borg/proc/controller_check(var/initial_run = FALSE) PRIVATE_PROC(TRUE) SHOULD_NOT_OVERRIDE(TRUE) - if(!radio_controller && initial_run) + if(!SSradio && initial_run) addtimer(CALLBACK(src,PROC_REF(controller_check), FALSE),3 SECONDS) return - if(!radio_controller && !initial_run) + if(!SSradio && !initial_run) name = "broken radio headset" return for (var/ch_name in channels) - secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT) + secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT) /obj/item/radio/proc/config(op) - if(radio_controller) + if(SSradio) for (var/ch_name in channels) - radio_controller.remove_object(src, radiochannels[ch_name]) + SSradio.remove_object(src, radiochannels[ch_name]) secure_radio_connections = new channels = op - if(radio_controller) + if(SSradio) for (var/ch_name in op) - secure_radio_connections[ch_name] = radio_controller.add_object(src, radiochannels[ch_name], RADIO_CHAT) + secure_radio_connections[ch_name] = SSradio.add_object(src, radiochannels[ch_name], RADIO_CHAT) return /obj/item/radio/off diff --git a/code/game/objects/structures/window_spawner.dm b/code/game/objects/structures/window_spawner.dm index 98b239f54a..463d413ba3 100644 --- a/code/game/objects/structures/window_spawner.dm +++ b/code/game/objects/structures/window_spawner.dm @@ -27,7 +27,7 @@ return FALSE /obj/effect/wingrille_spawn/Initialize(mapload) - if(win_path && ticker && ticker.current_state < GAME_STATE_PLAYING) + if(win_path && SSticker && SSticker.current_state < GAME_STATE_PLAYING) activate() ..() return INITIALIZE_HINT_QDEL diff --git a/code/game/objects/unit_testing.dm b/code/game/objects/unit_testing.dm new file mode 100644 index 0000000000..f3b7a303d9 --- /dev/null +++ b/code/game/objects/unit_testing.dm @@ -0,0 +1,38 @@ +/// Code (OBJECTS) in here gets used by the unit tests, but also regular code to test specific things. +/// But because unit_tests are rather late in the loading order, we have to put objects that get shared in here. +/// Else OpenDream gets very angry. + +// Used to test distillations without hacking the other machinery's code up +/obj/distilling_tester + icon = 'icons/obj/weapons.dmi' + icon_state = "cartridge" + var/datum/gas_mixture/GM = new() + var/current_temp = 0 + +/obj/distilling_tester/Initialize(mapload) + create_reagents(5000,/datum/reagents/distilling) + . = ..() + +/obj/distilling_tester/return_air() + return GM + +/obj/distilling_tester/proc/test_distilling(var/decl/chemical_reaction/distilling/D, var/temp_prog) + qdel_swap(GM,new()) + if(D.require_xgm_gas) + GM.gas[D.require_xgm_gas] = 100 + else + if(D.rejects_xgm_gas == GAS_N2) + GM.gas[GAS_O2] = 100 + else + GM.gas[GAS_N2] = 100 + if(D.minimum_xgm_pressure) + GM.temperature = (D.minimum_xgm_pressure * CELL_VOLUME) / (GM.gas[D.require_xgm_gas] * R_IDEAL_GAS_EQUATION) + + // Try this 10 times, We need to know if something is blocking at multiple temps. + // If it passes unit test, it might still be awful to make though, gotta find the right gas mix! + current_temp = LERP( D.temp_range[1], D.temp_range[2], temp_prog) + reagents.handle_reactions() + +/obj/distilling_tester/Destroy(force, ...) + qdel_null(GM) + . = ..() diff --git a/code/game/periodic_news.dm b/code/game/periodic_news.dm index f14f6b4742..15a6ff3e35 100644 --- a/code/game/periodic_news.dm +++ b/code/game/periodic_news.dm @@ -102,7 +102,7 @@ round_time = 60 * 60 /proc/process_newscaster() - check_for_newscaster_updates(ticker.mode.newscaster_announcements) + check_for_newscaster_updates(SSticker.mode.newscaster_announcements) var/global/tmp/announced_news_types = list() /proc/check_for_newscaster_updates(type) diff --git a/code/game/response_team.dm b/code/game/response_team.dm deleted file mode 100644 index e46649b7bb..0000000000 --- a/code/game/response_team.dm +++ /dev/null @@ -1,137 +0,0 @@ -//STRIKE TEAMS -//Thanks to Kilakk for the admin-button portion of this code. - -GLOBAL_VAR_INIT(send_emergency_team, 0) // Used for automagic response teams; 'admin_emergency_team' for admin-spawned response teams - -GLOBAL_VAR_INIT(ert_base_chance, 10) // Default base chance. Will be incremented by increment ERT chance. -GLOBAL_VAR(can_call_ert) -GLOBAL_VAR_INIT(silent_ert, 0) - -/client/proc/response_team() - set name = "Dispatch Emergency Response Team" - set category = "Fun.Event Kit" - set desc = "Send an emergency response team to the station" - - if(!check_rights_for(src, R_HOLDER)) - to_chat(usr, span_danger("Only administrators may use this command.")) - return - if(!ticker) - to_chat(usr, span_danger("The game hasn't started yet!")) - return - if(ticker.current_state == 1) - to_chat(usr, span_danger("The round hasn't started yet!")) - return - if(GLOB.send_emergency_team) - to_chat(usr, span_danger("[using_map.boss_name] has already dispatched an emergency response team!")) - return - if(tgui_alert(usr, "Do you want to dispatch an Emergency Response Team?","ERT",list("Yes","No")) != "Yes") - return - if(tgui_alert(usr, "Do you want this Response Team to be announced?","ERT",list("Yes","No")) != "Yes") - GLOB.silent_ert = 1 - if(get_security_level() != "red") // Allow admins to reconsider if the alert level isn't Red - if(tgui_alert(usr, "The station is not in red alert. Do you still want to dispatch a response team?","ERT",list("Yes","No")) != "Yes") - return - if(GLOB.send_emergency_team) - to_chat(usr, span_danger("Looks like somebody beat you to it!")) - return - - message_admins("[key_name_admin(usr)] is dispatching an Emergency Response Team.", 1) - admin_chat_message(message = "[key_name(usr)] is dispatching an Emergency Response Team", color = "#CC2222") //VOREStation Add - log_admin("[key_name(usr)] used Dispatch Response Team.") - trigger_armed_response_team(1) - -/client/verb/JoinResponseTeam() - - set name = "Join Response Team" - set category = "IC.Event" - - if(!MayRespawn(1)) - to_chat(usr, span_warning("You cannot join the response team at this time.")) - return - - if(isobserver(usr) || isnewplayer(usr)) - if(!GLOB.send_emergency_team) - to_chat(usr, "No emergency response team is currently being sent.") - return - if(jobban_isbanned(usr, JOB_SYNDICATE) || jobban_isbanned(usr, JOB_EMERGENCY_RESPONSE_TEAM) || jobban_isbanned(usr, JOB_SECURITY_OFFICER)) - to_chat(usr, span_danger("You are jobbanned from the emergency reponse team!")) - return - if(ert.current_antagonists.len >= ert.hard_cap) - to_chat(usr, "The emergency response team is already full!") - return - ert.create_default(usr) - else - to_chat(usr, "You need to be an observer or new player to use this.") - -// returns a number of dead players in % -/proc/percentage_dead() - var/total = 0 - var/deadcount = 0 - for(var/mob/living/carbon/human/H in GLOB.mob_list) - if(H.client) // Monkeys and mice don't have a client, amirite? - if(H.stat == 2) deadcount++ - total++ - - if(total == 0) return 0 - else return round(100 * deadcount / total) - -// counts the number of antagonists in % -/proc/percentage_antagonists() - var/total = 0 - var/antagonists = 0 - for(var/mob/living/carbon/human/H in GLOB.mob_list) - if(is_special_character(H) >= 1) - antagonists++ - total++ - - if(total == 0) return 0 - else return round(100 * antagonists / total) - -// Increments the ERT chance automatically, so that the later it is in the round, -// the more likely an ERT is to be able to be called. -/proc/increment_ert_chance() - while(GLOB.send_emergency_team == 0) // There is no ERT at the time. - if(get_security_level() == "green") - GLOB.ert_base_chance += 1 - if(get_security_level() == "yellow") - GLOB.ert_base_chance += 1 - if(get_security_level() == "violet") - GLOB.ert_base_chance += 2 - if(get_security_level() == "orange") - GLOB.ert_base_chance += 2 - if(get_security_level() == "blue") - GLOB.ert_base_chance += 2 - if(get_security_level() == "red") - GLOB.ert_base_chance += 3 - if(get_security_level() == "delta") - GLOB.ert_base_chance += 10 // Need those big guns - sleep(600 * 3) // Minute * Number of Minutes - - -/proc/trigger_armed_response_team(var/force = 0) - if(!GLOB.can_call_ert && !force) - return - if(GLOB.send_emergency_team) - return - - var/send_team_chance = GLOB.ert_base_chance // Is incremented by increment_ert_chance. - send_team_chance += 2*percentage_dead() // the more people are dead, the higher the chance - send_team_chance += percentage_antagonists() // the more antagonists, the higher the chance - send_team_chance = min(send_team_chance, 100) - - if(force) send_team_chance = 100 - - // there's only a certain chance a team will be sent - if(!prob(send_team_chance)) - command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. Unfortunately, we were unable to send one at this time.", "[using_map.boss_name]") - GLOB.can_call_ert = 0 // Only one call per round, ladies. - return - if(GLOB.silent_ert == 0) - command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. We will prepare and send one as soon as possible.", "[using_map.boss_name]") - - GLOB.can_call_ert = 0 // Only one call per round, gentleman. - GLOB.send_emergency_team = 1 - consider_ert_load() //VOREStation Add - - sleep(600 * 5) - GLOB.send_emergency_team = 0 // Can no longer join the ERT. diff --git a/code/game/response_team_vr.dm b/code/game/response_team_vr.dm deleted file mode 100644 index 3d686cd723..0000000000 --- a/code/game/response_team_vr.dm +++ /dev/null @@ -1,11 +0,0 @@ -GLOBAL_VAR(ert_loaded) - -/proc/consider_ert_load() - if(!GLOB.ert_loaded) - GLOB.ert_loaded = TRUE - var/datum/map_template/MT = SSmapping.map_templates["Special Area - ERT"] - if(!istype(MT)) - error("ERT Area is not a valid map template!") - else - MT.load_new_z(centered = TRUE) - log_and_message_admins("Loaded the ERT shuttle just now.") diff --git a/code/game/socket_talk.dm b/code/game/socket_talk.dm deleted file mode 100644 index 6cad496937..0000000000 --- a/code/game/socket_talk.dm +++ /dev/null @@ -1,26 +0,0 @@ -// Module used for fast interprocess communication between BYOND and other processes - -/datum/socket_talk - var - enabled = 0 - New() - ..() - src.enabled = config.socket_talk - - if(enabled) - LIBCALL("DLLSocket.so","establish_connection")("127.0.0.1","8019") - - proc - send_raw(message) - if(enabled) - return LIBCALL("DLLSocket.so","send_message")(message) - receive_raw() - if(enabled) - return LIBCALL("DLLSocket.so","recv_message")() - send_log(var/log, var/message) - return send_raw("type=log&log=[log]&message=[message]") - send_keepalive() - return send_raw("type=keepalive") - - -var/global/datum/socket_talk/socket_talk diff --git a/code/game/sound.dm b/code/game/sound.dm index 19214b639d..124fed98b1 100644 --- a/code/game/sound.dm +++ b/code/game/sound.dm @@ -136,7 +136,7 @@ return rand(32000, 55000) //Frequency stuff only works with 45kbps oggs. /client/proc/playtitlemusic() - if(!ticker || !SSmedia_tracks.lobby_tracks.len || !media) return + if(!SSticker || !SSmedia_tracks.lobby_tracks.len || !media) return if(prefs?.read_preference(/datum/preference/toggle/play_lobby_music)) var/datum/track/T = pick(SSmedia_tracks.lobby_tracks) media.push_music(T.url, world.time, 0.35) diff --git a/code/game/supplyshuttle.dm b/code/game/supplyshuttle.dm deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/code/game/trader_visit_vr.dm b/code/game/trader_visit_vr.dm deleted file mode 100644 index b9589eb2a5..0000000000 --- a/code/game/trader_visit_vr.dm +++ /dev/null @@ -1,11 +0,0 @@ -GLOBAL_VAR(trader_loaded) - -/proc/consider_trader_load() - if(!GLOB.trader_loaded) - GLOB.trader_loaded = TRUE - var/datum/map_template/MT = SSmapping.map_templates["Special Area - Salamander Trader"] //was: "Special Area - Trader" - if(!istype(MT)) - error("Trader is not a valid map template!") - else - MT.load_new_z(centered = TRUE) - log_and_message_admins("Loaded the trade shuttle just now.") diff --git a/code/game/turfs/space/space.dm b/code/game/turfs/space/space.dm index b87ef31ff4..c3d8e95c45 100644 --- a/code/game/turfs/space/space.dm +++ b/code/game/turfs/space/space.dm @@ -137,7 +137,7 @@ /turf/space/Entered(var/atom/movable/A) . = ..() - if(edge && ticker?.mode && !density) // !density so 'fake' space turfs don't fling ghosts everywhere + if(edge && SSticker?.mode && !density) // !density so 'fake' space turfs don't fling ghosts everywhere if(isliving(A)) var/mob/living/L = A if(L.pulling) diff --git a/code/game/turfs/turf_changing.dm b/code/game/turfs/turf_changing.dm index 20fb5e6205..4939573623 100644 --- a/code/game/turfs/turf_changing.dm +++ b/code/game/turfs/turf_changing.dm @@ -117,7 +117,7 @@ dynamic_lumcount = old_dynamic_lumcount - if(SSlighting.subsystem_initialized) + if(SSlighting.initialized) lighting_object = old_lighting_object directional_opacity = old_directional_opacity diff --git a/code/game/world.dm b/code/game/world.dm index 462cc92dfd..014c95c8ec 100644 --- a/code/game/world.dm +++ b/code/game/world.dm @@ -1,14 +1,138 @@ #define RESTART_COUNTER_PATH "data/round_counter.txt" +/// Load byond-tracy. If USE_BYOND_TRACY is defined, then this is ignored and byond-tracy is always loaded. +#define USE_TRACY_PARAMETER "tracy" +/// Force the log directory to be something specific in the data/logs folder +#define OVERRIDE_LOG_DIRECTORY_PARAMETER "log-directory" +/// Prevent the master controller from starting automatically +#define NO_INIT_PARAMETER "no-init" GLOBAL_VAR(restart_counter) -#define RECOMMENDED_VERSION 513 +/** + * WORLD INITIALIZATION + * THIS IS THE INIT ORDER: + * + * BYOND => + * - (secret init native) => + * - world.Genesis() => + * - world.init_byond_tracy() + * - (Start native profiling) + * - world.init_debugger() + * - Master => + * - config *unloaded + * - (all subsystems) PreInit() + * - GLOB => + * - make_datum_reference_lists() + * - (/static variable inits, reverse declaration order) + * - (all pre-mapped atoms) /atom/New() + * - world.New() => + * - config.Load() + * - world.InitTgs() => + * - TgsNew() *may sleep + * - GLOB.rev_data.load_tgs_info() + * - world.ConfigLoaded() => + * - SSdbcore.InitializeRound() + * - world.SetupLogs() + * - load_admins() + * - ... + * - Master.Initialize() => + * - (all subsystems) Initialize() + * - Master.StartProcessing() => + * - Master.Loop() => + * - Failsafe + * - world.RunUnattendedFunctions() + * + * Now listen up because I want to make something clear: + * If something is not in this list it should almost definitely be handled by a subsystem Initialize()ing + * If whatever it is that needs doing doesn't fit in a subsystem you probably aren't trying hard enough tbhfam + * + * GOT IT MEMORIZED? + * - Dominion/Cyberboss + * + * Where to put init shit quick guide: + * If you need it to happen before the mc is created: world/Genesis. + * If you need it to happen last: world/New(), + * Otherwise, in a subsystem preinit or init. Subsystems can set an init priority. + */ + +/** + * THIS !!!SINGLE!!! PROC IS WHERE ANY FORM OF INIITIALIZATION THAT CAN'T BE PERFORMED IN SUBSYSTEMS OR WORLD/NEW IS DONE + * NOWHERE THE FUCK ELSE + * I DON'T CARE HOW MANY LAYERS OF DEBUG/PROFILE/TRACE WE HAVE, YOU JUST HAVE TO DEAL WITH THIS PROC EXISTING + * I'M NOT EVEN GOING TO TELL YOU WHERE IT'S CALLED FROM BECAUSE I'M DECLARING THAT FORBIDDEN KNOWLEDGE + * SO HELP ME GOD IF I FIND ABSTRACTION LAYERS OVER THIS! + */ +/world/proc/Genesis(tracy_initialized = FALSE) + RETURN_TYPE(/datum/controller/master) + + if(!tracy_initialized) + Tracy = new +#ifdef USE_BYOND_TRACY + if(Tracy.enable("USE_BYOND_TRACY defined")) + Genesis(tracy_initialized = TRUE) + return +#else + var/tracy_enable_reason + if(USE_TRACY_PARAMETER in params) + tracy_enable_reason = "world.params" + if(fexists(TRACY_ENABLE_PATH)) + tracy_enable_reason ||= "enabled for round" + SEND_TEXT(world.log, "[TRACY_ENABLE_PATH] exists, initializing byond-tracy!") + fdel(TRACY_ENABLE_PATH) + if(!isnull(tracy_enable_reason) && Tracy.enable(tracy_enable_reason)) + Genesis(tracy_initialized = TRUE) + return +#endif + + Profile(PROFILE_RESTART) + Profile(PROFILE_RESTART, type = "sendmaps") + + // Init the debugger first so we can debug Master + Debugger = new + + // THAT'S IT, WE'RE DONE, THE. FUCKING. END. + Master = new + +/** + * World creation + * + * Here is where a round itself is actually begun and setup. + * * db connection setup + * * config loaded from files + * * loads admins + * * Sets up the dynamic menu system + * * and most importantly, calls initialize on the master subsystem, starting the game loop that causes the rest of the game to begin processing and setting up + * + * + * Nothing happens until something moves. ~Albert Einstein + * + * For clarity, this proc gets triggered later in the initialization pipeline, it is not the first thing to happen, as it might seem. + * + * Initialization Pipeline: + * Global vars are new()'ed, (including config, glob, and the master controller will also new and preinit all subsystems when it gets new()ed) + * Compiled in maps are loaded (mainly centcom). all areas/turfs/objs/mobs(ATOMs) in these maps will be new()ed + * world/New() (You are here) + * Once world/New() returns, client's can connect. + * 1 second sleep + * Master Controller initialization. + * Subsystem initialization. + * Non-compiled-in maps are maploaded, all atoms are new()ed + * All atoms in both compiled and uncompiled maps are initialized() + */ /world/New() + log_world("World loaded at [time_stamp()]!") + world_startup_time = world.timeofday rollover_safety_date = world.realtime - world.timeofday // 00:00 today (ish, since floating point error with world.realtime) of today to_world_log("Map Loading Complete") //logs - GLOB.log_directory += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss") + //VOREStation Edit Start + + var/override_dir = params[OVERRIDE_LOG_DIRECTORY_PARAMETER] + if(override_dir) + GLOB.log_directory = "data/logs/[override_dir]" + else + GLOB.log_directory += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss") GLOB.diary = start_log("[GLOB.log_directory].log") GLOB.href_logfile = start_log("[GLOB.log_directory]-hrefs.htm") GLOB.error_log = start_log("[GLOB.log_directory]-error.log") @@ -20,16 +144,16 @@ GLOBAL_VAR(restart_counter) GLOB.changelog_hash = fexists(latest_changelog) ? md5(latest_changelog) : "" //for telling if the changelog has changed recently to_world_log("Changelog Hash: '[GLOB.changelog_hash]' ([latest_changelog])") - if(byond_version < RECOMMENDED_VERSION) - to_world_log("Your server's byond version does not meet the recommended requirements for this server. Please update BYOND") - + // First possible sleep() InitTgs() config.Load(params[OVERRIDE_CONFIG_DIRECTORY_PARAMETER]) - load_admins() - ConfigLoaded() + + if(NO_INIT_PARAMETER in params) + return + makeDatumRefLists() var servername = CONFIG_GET(string/servername) @@ -52,14 +176,7 @@ GLOBAL_VAR(restart_counter) src.update_status() setup_season() //VOREStation Addition - var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") - if (debug_server) - call_ext(debug_server, "auxtools_init")() - enable_debugging() - - . = ..() - -#ifdef UNIT_TEST +#ifdef UNIT_TESTS log_unit_test("Unit Tests Enabled. This will destroy the world when testing is complete.") log_unit_test("If you did not intend to enable this please check code/__defines/unit_testing.dm") #endif @@ -81,16 +198,13 @@ GLOBAL_VAR(restart_counter) spawn(1) master_controller.setup() -#ifdef UNIT_TEST - initialize_unit_tests() -#endif + + RunUnattendedFunctions() spawn(3000) //so we aren't adding to the round-start lag if(CONFIG_GET(flag/ToRban)) ToRban_autoupdate() -#undef RECOMMENDED_VERSION - return /// Initializes TGS and loads the returned revising info into GLOB.revdata @@ -107,6 +221,8 @@ GLOBAL_VAR(restart_counter) // Try to set round ID SSdbcore.InitializeRound() + load_admins(initial = TRUE) + //apply a default value to config.python_path, if needed if (!CONFIG_GET(string/python_path)) if(world.system_type == UNIX) @@ -118,6 +234,38 @@ GLOBAL_VAR(restart_counter) GLOB.restart_counter = text2num(trim(file2text(RESTART_COUNTER_PATH))) fdel(RESTART_COUNTER_PATH) +/// Runs after the call to Master.Initialize, but before the delay kicks in. Used to turn the world execution into some single function then exit +/world/proc/RunUnattendedFunctions() + #ifdef UNIT_TESTS + HandleTestRun() + #endif + + #ifdef AUTOWIKI + setup_autowiki() + #endif + +/world/proc/HandleTestRun() + //trigger things to run the whole process + Master.sleep_offline_after_initializations = FALSE + SSticker.start_immediately = TRUE + CONFIG_SET(number/round_end_countdown, 0) + var/datum/callback/cb +#ifdef UNIT_TESTS + cb = CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(RunUnitTests)) +#else + cb = VARSET_CALLBACK(SSticker, force_ending, ADMIN_FORCE_END_ROUND) +#endif + SSticker.OnRoundstart(CALLBACK(GLOBAL_PROC, GLOBAL_PROC_REF(_addtimer), cb, 10 SECONDS)) + +/// Returns a list of data about the world state, don't clutter +/world/proc/get_world_state_for_logging() + var/data = list() + data["tick_usage"] = world.tick_usage + data["tick_lag"] = world.tick_lag + data["time"] = world.time + data["timestamp"] = rustg_unix_timestamp() + return data + var/world_topic_spam_protect_ip = "0.0.0.0" var/world_topic_spam_protect_time = world.timeofday @@ -338,17 +486,33 @@ var/world_topic_spam_protect_time = world.timeofday text2file("[++GLOB.restart_counter]", RESTART_COUNTER_PATH) return FALSE -/world/Reboot(reason = 0, fast_track = FALSE) - /*spawn(0) - world << sound(pick('sound/AI/newroundsexy.ogg','sound/misc/apcdestroyed.ogg','sound/misc/bangindonk.ogg')) // random end sounds!! - LastyBatsy - */ +/world/proc/FinishTestRun() + set waitfor = FALSE + var/list/fail_reasons + if(GLOB) + if(GLOB.total_runtimes != 0) + fail_reasons = list("Total runtimes: [GLOB.total_runtimes]") +#ifdef UNIT_TESTS + if(GLOB.failed_any_test) + LAZYADD(fail_reasons, "Unit Tests failed!") +#endif + if(!GLOB.log_directory) + LAZYADD(fail_reasons, "Missing GLOB.log_directory!") + else + fail_reasons = list("Missing GLOB!") + if(!fail_reasons) + text2file("Success!", "[GLOB.log_directory]/clean_run.lk") + else + log_world("Test run failed!\n[fail_reasons.Join("\n")]") + sleep(0) //yes, 0, this'll let Reboot finish and prevent byond memes + qdel(src) //shut it down +/world/Reboot(reason = 0, fast_track = FALSE) if (reason || fast_track) //special reboot, do none of the normal stuff if (usr) log_admin("[key_name(usr)] Has requested an immediate world restart via client side debugging tools") message_admins("[key_name_admin(usr)] Has requested an immediate world restart via client side debugging tools") to_world(span_boldannounce("[key_name_admin(usr)] has requested an immediate world restart via client side debugging tools")) - else to_world(span_boldannounce("Rebooting world immediately due to host request")) else @@ -357,6 +521,10 @@ var/world_topic_spam_protect_time = world.timeofday if(CONFIG_GET(string/server)) //if you set a server location in config.txt, it sends you there instead of trying to reconnect to the same world address. -- NeoFite C << link("byond://[CONFIG_GET(string/server)]") + #ifdef UNIT_TESTS + FinishTestRun() + return + #else if(check_hard_reboot()) log_world("World hard rebooted at [time_stamp()]") //shutdown_logging() // See comment below. @@ -367,7 +535,19 @@ var/world_topic_spam_protect_time = world.timeofday TgsReboot() log_world("World rebooted at [time_stamp()]") + + QDEL_NULL(Tracy) + QDEL_NULL(Debugger) + + TgsReboot() + ..() + #endif + +/world/Del() + QDEL_NULL(Tracy) + QDEL_NULL(Debugger) + . = ..() /hook/startup/proc/loadMode() world.load_mode() @@ -430,7 +610,7 @@ var/world_topic_spam_protect_time = world.timeofday var/list/features = list() - if(ticker) + if(SSticker) if(GLOB.master_mode) features += GLOB.master_mode else @@ -595,10 +775,11 @@ var/failed_old_db_connections = 0 /proc/enable_debugging(mode, port) CRASH("auxtools not loaded") -/world/Del() - var/debug_server = world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") - if (debug_server) - call_ext(debug_server, "auxtools_shutdown")() - . = ..() +/world/Profile(command, type, format) + if((command & PROFILE_STOP) || !global.config?.loaded || !CONFIG_GET(flag/forbid_all_profiling)) + . = ..() +#undef NO_INIT_PARAMETER +#undef OVERRIDE_LOG_DIRECTORY_PARAMETER +#undef USE_TRACY_PARAMETER #undef RESTART_COUNTER_PATH diff --git a/code/genesis_call.dme b/code/genesis_call.dme new file mode 100644 index 0000000000..2466af786c --- /dev/null +++ b/code/genesis_call.dme @@ -0,0 +1,52 @@ +/* + + You look around. + + There is nothing but naught about you. + + You've come to the end of the world. + + You get a feeling that you really shouldn't be here. + + Ever. + + But with all ends come beginnings. + + As you turn to leave, you spot it out of the corner of your eye. + + Your eye widen in wonder as you look upon the the legendary treasure. + + After all these years of pouring through shitcode + your endevours have brought you to... + +*/ + +/** + * THE GENESIS CALL + * + * THE VERY FIRST LINE OF DM CODE TO EXECUTE + * Ong this must be done before !!!EVERYTHING!!! else + * NO IFS ANDS OR BUTS + * it's a hack, not an example of any sort, and DEFINITELY should NOT be emulated + * IT JUST HAS TO BE FIRST!!!!!! + * If you want to do something in the initialization pipeline + * FIRST RTFM IN /code/game/world.dm + * AND THEN NEVER RETURN TO THIS PLACE + * + * + * + * If you're still here, here's an explanation: + * BYOND loves to tell you about its loving spouse /global + * But it's actually having a sexy an affair with /static + * Specifically statics in procs + * Priority is given to these lines of code in REVERSE order of declaration in the .dme + * Which is why this file has a funky name + * So this is what we use to call world.Genesis() + * It's a nameless, no-op function, because it does absolutely nothing + * It exists to hold a static var which is initialized to null + * It's on /world to hide it from reflection + * Painful right? Good, now you share my suffering + * Please lock the door on your way out + */ +/world/proc/_() + var/static/_ = world.Genesis() diff --git a/code/global_init.dm b/code/global_init.dm index 1cb0610f81..f23bd8bf6d 100644 --- a/code/global_init.dm +++ b/code/global_init.dm @@ -14,15 +14,6 @@ var/global/datum/global_init/init = new () Pre-map initialization stuff should go here. */ /datum/global_init/New() -/* VOREStation Removal - Ours is even earlier, in world/New() - //logs - GLOB.log_directory += time2text(world.realtime, "YYYY/MM-Month/DD-Day/round-hh-mm-ss") - diary = file("[GLOB.log_directory].log") - GLOB.href_logfile = file("[GLOB.log_directory]-hrefs.htm") - GLOB.error_log = file("[GLOB.log_directory]-error.log") - GLOB.debug_log = file("[GLOB.log_directory]-debug.log") - GLOB.debug_log << "[log_end]\n[log_end]\nStarting up. [time_stamp()][log_end]\n---------------------[log_end]" -*/ //VOREStation Removal End decls_repository = new() initialize_integrated_circuits_list() diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm index ec53d53514..781a414d6e 100644 --- a/code/modules/admin/admin.dm +++ b/code/modules/admin/admin.dm @@ -625,29 +625,69 @@ ADMIN_VERB_ONLY_CONTEXT_MENU(show_player_panel, R_HOLDER, "Show Player Panel", m /////////////////////////////////////////////////////////////////////////////////////////////////admins2.dm merge //i.e. buttons/verbs +#define REGULAR_RESTART "Regular Restart" +#define REGULAR_RESTART_DELAYED "Regular Restart (with delay)" +#define HARD_RESTART "Hard Restart (No Delay/Feedback Reason)" +#define HARDEST_RESTART "Hardest Restart (No actions, just reboot)" +#define TGS_RESTART "Server Restart (Kill and restart DD)" +ADMIN_VERB(restart, R_SERVER, "Reboot World", "Restarts the world immediately.", "Server.Game") + var/list/options = list(REGULAR_RESTART, REGULAR_RESTART_DELAYED, HARD_RESTART) -/datum/admins/proc/restart() - set category = "Server.Game" - set name = "Restart" - set desc="Restarts the world" - if (!check_rights_for(usr.client, R_HOLDER)) + // this option runs a codepath that can leak db connections because it skips subsystem (specifically SSdbcore) shutdown + if(!SSdbcore.IsConnected()) + options += HARDEST_RESTART + + if(world.TgsAvailable()) + options += TGS_RESTART; + + if(SSticker.admin_delay_notice) + if(alert(user, "Are you sure? An admin has already delayed the round end for the following reason: [SSticker.admin_delay_notice]", "Confirmation", "Yes", "No") != "Yes") + return FALSE + + var/result = input(user, "Select reboot method", "World Reboot", options[1]) as null|anything in options + if(isnull(result)) return - var/confirm = alert(usr, "Restart the game world?", "Restart", "Yes", "Cancel") // Not tgui_alert for safety - if(!confirm || confirm == "Cancel") + + feedback_add_details("admin_verb","R") + if(blackbox) + blackbox.save_all_data_to_sql() + + var/init_by = "Initiated by [user.holder.fakekey ? "Admin" : user.key]." + switch(result) + if(REGULAR_RESTART) + if(!user.is_localhost()) + if(alert(user, "Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart") + return FALSE + SSticker.Reboot(init_by, "admin reboot - by [user.key] [user.holder.fakekey ? "(stealth)" : ""]", 10) + if(REGULAR_RESTART_DELAYED) + var/delay = input("What delay should the restart have (in seconds)?", "Restart Delay", 5) as num|null + if(!delay) + return FALSE + if(!user.is_localhost()) + if(alert(user,"Are you sure you want to restart the server?","This server is live", "Restart", "Cancel") != "Restart") + return FALSE + SSticker.Reboot(init_by, "admin reboot - by [user.key] [user.holder.fakekey ? "(stealth)" : ""]", delay * 10) + if(HARD_RESTART) + to_chat(world, "World reboot - [init_by]") + world.Reboot() + if(HARDEST_RESTART) + to_chat(world, "Hard world reboot - [init_by]") + world.Reboot(fast_track = TRUE) + if(TGS_RESTART) + to_chat(world, "Server restart - [init_by]") + world.TgsEndProcess() + +#undef REGULAR_RESTART +#undef REGULAR_RESTART_DELAYED +#undef HARD_RESTART +#undef HARDEST_RESTART +#undef TGS_RESTART + +ADMIN_VERB(cancel_reboot, R_SERVER, "Cancel Reboot", "Cancels a pending world reboot.", "Server.Game") + if(!SSticker.cancel_reboot(user)) return - if(confirm == "Yes") - to_world(span_danger("Restarting world!" ) + span_notice("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]!")) - log_admin("[key_name(usr)] initiated a reboot.") - - feedback_set_details("end_error","admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]") - feedback_add_details("admin_verb","R") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - if(blackbox) - blackbox.save_all_data_to_sql() - - sleep(50) - world.Reboot() - + log_admin("[key_name(user)] cancelled the pending world reboot.") + message_admins("[key_name_admin(user)] cancelled the pending world reboot.") /datum/admins/proc/announce() set category = "Admin.Chat" @@ -880,7 +920,7 @@ var/datum/announcement/minor/admin_min_announcer = new if(!SSticker.start_immediately) SSticker.start_immediately = TRUE var/msg = "" - if(SSticker.current_state == GAME_STATE_INIT) + if(SSticker.current_state == GAME_STATE_STARTUP) msg = " (The server is still setting up, but the round will be started as soon as possible.)" log_admin("[key_name(usr)] has started the game.[msg]") message_admins(span_notice("[key_name_admin(usr)] has started the game.[msg]")) @@ -1017,24 +1057,6 @@ var/datum/announcement/minor/admin_min_announcer = new message_admins(span_blue("Toggled reviving to [CONFIG_GET(flag/allow_admin_rev)].")) feedback_add_details("admin_verb","TAR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -/datum/admins/proc/immreboot() - set category = "Server.Game" - set desc="Reboots the server post haste" - set name="Immediate Reboot" - if(!check_rights_for(usr.client, R_HOLDER)) return - if(alert(usr, "Reboot server?","Reboot!","Yes","No") != "Yes") // Not tgui_alert for safety - return - to_world(span_filter_system("[span_red(span_bold("Rebooting world!"))] [span_blue("Initiated by [usr.client.holder.fakekey ? "Admin" : usr.key]!")]")) - log_admin("[key_name(usr)] initiated an immediate reboot.") - - feedback_set_details("end_error","immediate admin reboot - by [usr.key] [usr.client.holder.fakekey ? "(stealth)" : ""]") - feedback_add_details("admin_verb","IR") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! - - if(blackbox) - blackbox.save_all_data_to_sql() - - world.Reboot() - /datum/admins/proc/unprison(var/mob/M in GLOB.mob_list) set category = "Admin.Moderation" set name = "Unprison" @@ -1052,7 +1074,7 @@ var/datum/announcement/minor/admin_min_announcer = new ////////////////////////////////////////////////////////////////////////////////////////////////ADMIN HELPER PROCS /proc/is_special_character(var/character) // returns 1 for special characters and 2 for heroes of gamemode - if(!ticker || !ticker.mode) + if(!SSticker|| !SSticker.mode) return 0 var/datum/mind/M if (ismob(character)) @@ -1062,8 +1084,8 @@ var/datum/announcement/minor/admin_min_announcer = new M = character if(M) - if(ticker.mode.antag_templates && ticker.mode.antag_templates.len) - for(var/datum/antagonist/antag in ticker.mode.antag_templates) + if(SSticker.mode.antag_templates && SSticker.mode.antag_templates.len) + for(var/datum/antagonist/antag in SSticker.mode.antag_templates) if(antag.is_antagonist(M)) return 2 if(M.special_role) @@ -1197,70 +1219,70 @@ var/datum/announcement/minor/admin_min_announcer = new set desc = "Show the current round configuration." set name = "Show Game Mode" - if(!ticker || !ticker.mode) + if(!SSticker|| !SSticker.mode) tgui_alert_async(usr, "Not before roundstart!", "Alert") return - var/out = span_large(span_bold("Current mode: [ticker.mode.name] ([ticker.mode.config_tag])")) + "
" + var/out = span_large(span_bold("Current mode: [SSticker.mode.name] ([SSticker.mode.config_tag])")) + "
" out += "
" - if(ticker.mode.ert_disabled) - out += span_bold("Emergency Response Teams:") + "disabled" + if(SSticker.mode.ert_disabled) + out += span_bold("Emergency Response Teams:") + "disabled" else - out += span_bold("Emergency Response Teams:") + "enabled" + out += span_bold("Emergency Response Teams:") + "enabled" out += "
" - if(ticker.mode.deny_respawn) - out += span_bold("Respawning:") + "disallowed" + if(SSticker.mode.deny_respawn) + out += span_bold("Respawning:") + "disallowed" else - out += span_bold("Respawning:") + "allowed" + out += span_bold("Respawning:") + "allowed" out += "
" - out += span_bold("Shuttle delay multiplier:") + " [ticker.mode.shuttle_delay]
" + out += span_bold("Shuttle delay multiplier:") + " [SSticker.mode.shuttle_delay]
" - if(ticker.mode.auto_recall_shuttle) - out += span_bold("Shuttle auto-recall:") + " enabled" + if(SSticker.mode.auto_recall_shuttle) + out += span_bold("Shuttle auto-recall:") + " enabled" else - out += span_bold("Shuttle auto-recall:") + " disabled" + out += span_bold("Shuttle auto-recall:") + " disabled" out += "

" - if(ticker.mode.event_delay_mod_moderate) - out += span_bold("Moderate event time modifier:") + " [ticker.mode.event_delay_mod_moderate]
" + if(SSticker.mode.event_delay_mod_moderate) + out += span_bold("Moderate event time modifier:") + " [SSticker.mode.event_delay_mod_moderate]
" else - out += span_bold("Moderate event time modifier:") + " unset
" + out += span_bold("Moderate event time modifier:") + " unset
" - if(ticker.mode.event_delay_mod_major) - out += span_bold("Major event time modifier:") + " [ticker.mode.event_delay_mod_major]
" + if(SSticker.mode.event_delay_mod_major) + out += span_bold("Major event time modifier:") + " [SSticker.mode.event_delay_mod_major]
" else - out += span_bold("Major event time modifier:") + " unset
" + out += span_bold("Major event time modifier:") + " unset
" out += "
" - if(ticker.mode.antag_tags && ticker.mode.antag_tags.len) + if(SSticker.mode.antag_tags && SSticker.mode.antag_tags.len) out += span_bold("Core antag templates:") + "
" - for(var/antag_tag in ticker.mode.antag_tags) - out += "[antag_tag].
" + for(var/antag_tag in SSticker.mode.antag_tags) + out += "[antag_tag].
" - if(ticker.mode.round_autoantag) - out += span_bold("Autotraitor enabled.") - if(ticker.mode.antag_scaling_coeff > 0) - out += " (scaling with [ticker.mode.antag_scaling_coeff])" + if(SSticker.mode.round_autoantag) + out += span_bold("Autotraitor enabled.") + if(SSticker.mode.antag_scaling_coeff > 0) + out += " (scaling with [SSticker.mode.antag_scaling_coeff])" else - out += " (not currently scaling, set a coefficient)" + out += " (not currently scaling, set a coefficient)" out += "
" else - out += span_bold("Autotraitor disabled.") + "
" + out += span_bold("Autotraitor disabled.") + "
" out += span_bold("All antag ids:") - if(ticker.mode.antag_templates && ticker.mode.antag_templates.len) - for(var/datum/antagonist/antag in ticker.mode.antag_templates) + if(SSticker.mode.antag_templates && SSticker.mode.antag_templates.len) + for(var/datum/antagonist/antag in SSticker.mode.antag_templates) antag.update_current_antag_max() - out += " [antag.id]" + out += " [antag.id]" out += " ([antag.get_antag_count()]/[antag.cur_max]) " - out += " \[-\]
" + out += " \[-\]
" else out += " None." - out += " \[+\]
" + out += " \[+\]
" var/datum/browser/popup = new(owner, "edit_mode[src]", "Edit Game Mode") popup.set_content(out) @@ -1404,7 +1426,7 @@ var/datum/announcement/minor/admin_min_announcer = new to_chat(usr, "Error: you are not an admin!") return - if(!ticker || !ticker.mode) + if(!SSticker|| !SSticker.mode) to_chat(usr, "Mode has not started.") return @@ -1428,12 +1450,12 @@ var/datum/announcement/minor/admin_min_announcer = new to_chat(usr, "Error: you are not an admin!") return - if(!ticker || !ticker.mode) + if(!SSticker|| !SSticker.mode) to_chat(usr, "Mode has not started.") return log_and_message_admins("attempting to force mode autospawn.") - ticker.mode.try_latespawn() + SSticker.mode.try_latespawn() /datum/admins/proc/paralyze_mob(mob/living/H as mob) set category = "Admin.Events" diff --git a/code/modules/admin/admin_ranks.dm b/code/modules/admin/admin_ranks.dm index 2995552033..65cf7949f0 100644 --- a/code/modules/admin/admin_ranks.dm +++ b/code/modules/admin/admin_ranks.dm @@ -213,7 +213,11 @@ GLOBAL_PROTECT(protected_ranks) /// (Re)Loads the admin list. /// returns TRUE if database admins had to be loaded from the backup json -/proc/load_admins(no_update) +/proc/load_admins(no_update, initial = FALSE) + if(!initial) + if(!global.config.PreConfigReload()) + return + var/dbfail if(!CONFIG_GET(flag/admin_legacy_system) && !SSdbcore.Connect()) message_admins("Failed to connect to database while loading admins. Loading from backup.") diff --git a/code/modules/admin/admin_verb_lists_vr.dm b/code/modules/admin/admin_verb_lists_vr.dm index e1e0334f2a..2ae6e2169a 100644 --- a/code/modules/admin/admin_verb_lists_vr.dm +++ b/code/modules/admin/admin_verb_lists_vr.dm @@ -18,7 +18,6 @@ var/list/admin_verbs_admin = list( /client/proc/player_panel, /client/proc/hide_verbs, //hides all our adminverbs, /client/proc/hide_most_verbs, //hides all our hideable adminverbs, - /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify, /client/proc/cmd_check_new_players, //allows us to see every new player, /client/proc/toggle_view_range, //changes how far we can see, /client/proc/cmd_admin_pm_context, //right-click adminPM interface, @@ -103,7 +102,6 @@ var/list/admin_verbs_fun = list( /datum/admins/proc/cmd_admin_dress, /client/proc/drop_bomb, /client/proc/everyone_random, - /client/proc/cinematic, /datum/admins/proc/toggle_aliens, /datum/admins/proc/toggle_space_ninja, /client/proc/cmd_admin_add_freeform_ai_law, @@ -152,12 +150,10 @@ var/list/admin_verbs_server = list( /client/proc/Set_Holiday, /client/proc/ToRban, /datum/admins/proc/startnow, - /datum/admins/proc/restart, /datum/admins/proc/delay, /datum/admins/proc/toggleaban, /datum/admins/proc/togglepersistence, /client/proc/toggle_log_hrefs, - /datum/admins/proc/immreboot, /client/proc/everyone_random, /datum/admins/proc/toggleAI, /client/proc/cmd_admin_delete, //delete an instance/object/mob/etc, @@ -194,7 +190,6 @@ var/list/admin_verbs_debug = list( /client/proc/air_report, /client/proc/reload_admins, /client/proc/reload_eventMs, - /datum/admins/proc/restart, /client/proc/print_random_map, /client/proc/create_random_map, /client/proc/apply_random_map, @@ -211,7 +206,6 @@ var/list/admin_verbs_debug = list( /client/proc/player_panel, /client/proc/hide_verbs, //hides all our adminverbs, /client/proc/hide_most_verbs, //hides all our hideable adminverbs, - /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify, /client/proc/cmd_check_new_players, //allows us to see every new player, /datum/admins/proc/view_runtimes, // /client/proc/show_gm_status, //We don't use SSgame_master yet. @@ -254,7 +248,6 @@ var/list/admin_verbs_hideable = list( /client/proc/object_talk, /datum/admins/proc/cmd_admin_dress, /client/proc/drop_bomb, - /client/proc/cinematic, /datum/admins/proc/toggle_aliens, /datum/admins/proc/toggle_space_ninja, /client/proc/cmd_admin_add_freeform_ai_law, @@ -266,11 +259,9 @@ var/list/admin_verbs_hideable = list( /client/proc/Set_Holiday, /client/proc/ToRban, /datum/admins/proc/startnow, - /datum/admins/proc/restart, /datum/admins/proc/delay, /datum/admins/proc/toggleaban, /client/proc/toggle_log_hrefs, - /datum/admins/proc/immreboot, /client/proc/everyone_random, /datum/admins/proc/toggleAI, /datum/admins/proc/adrev, @@ -299,14 +290,12 @@ var/list/admin_verbs_hideable = list( var/list/admin_verbs_mod = list( /client/proc/cmd_admin_pm_context, //right-click adminPM interface, /client/proc/cmd_admin_pm_panel, //admin-pm list, - /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game., /datum/admins/proc/PlayerNotes, /client/proc/admin_ghost, //allows us to ghost/reenter body at will, /client/proc/player_panel_new, //shows an interface for all players, with links to various panels, /client/proc/player_panel, /client/proc/hide_verbs, //hides all our adminverbs, /client/proc/hide_most_verbs, //hides all our hideable adminverbs, - /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify, /client/proc/cmd_check_new_players, //allows us to see every new player, /datum/admins/proc/show_player_info, /datum/admins/proc/show_traitor_panel, @@ -332,12 +321,10 @@ var/list/admin_verbs_event_manager = list( /client/proc/player_panel, /client/proc/hide_verbs, //hides all our adminverbs, /client/proc/hide_most_verbs, //hides all our hideable adminverbs, - /client/proc/debug_variables, //allows us to -see- the variables of any instance in the game. +VAREDIT needed to modify, /client/proc/cmd_check_new_players, //allows us to see every new player, /datum/admins/proc/show_player_info, /client/proc/dsay, /client/proc/cmd_admin_subtle_message, - /client/proc/debug_variables, /client/proc/check_antagonists, /client/proc/aooc, /datum/admins/proc/paralyze_mob, @@ -439,9 +426,7 @@ var/list/admin_verbs_event_manager = list( /datum/admins/proc/capture_map, /client/proc/Set_Holiday, /datum/admins/proc/startnow, - /datum/admins/proc/restart, /datum/admins/proc/delay, - /datum/admins/proc/immreboot, /client/proc/everyone_random, /client/proc/cmd_admin_delete, //delete an instance/object/mob/etc, /client/proc/cmd_debug_del_all, diff --git a/code/modules/admin/modify_robot.dm b/code/modules/admin/modify_robot.dm index a7732c644d..8d69612bb5 100644 --- a/code/modules/admin/modify_robot.dm +++ b/code/modules/admin/modify_robot.dm @@ -390,7 +390,7 @@ target.radio.syndie = 1 target.module.channels += list("[selected_radio_channel]" = 1) target.radio.channels[selected_radio_channel] = target.module.channels[selected_radio_channel] - target.radio.secure_radio_connections[selected_radio_channel] = radio_controller.add_object(target.radio, radiochannels[selected_radio_channel], RADIO_CHAT) + target.radio.secure_radio_connections[selected_radio_channel] = SSradio.add_object(target.radio, radiochannels[selected_radio_channel], RADIO_CHAT) return TRUE if("rem_channel") var/selected_radio_channel = params["channel"] @@ -404,7 +404,7 @@ target.radio.channels = list() for(var/n_chan in target.module.channels) target.radio.channels[n_chan] = target.module.channels[n_chan] - radio_controller.remove_object(target.radio, radiochannels[selected_radio_channel]) + SSradio.remove_object(target.radio, radiochannels[selected_radio_channel]) target.radio.secure_radio_connections -= selected_radio_channel return TRUE if("add_component") diff --git a/code/modules/admin/player_panel.dm b/code/modules/admin/player_panel.dm index b10ea3ada6..0f2efe3283 100644 --- a/code/modules/admin/player_panel.dm +++ b/code/modules/admin/player_panel.dm @@ -390,9 +390,9 @@ /datum/admins/proc/check_antagonists() - if (ticker && ticker.current_state >= GAME_STATE_PLAYING) + if (SSticker && SSticker.current_state >= GAME_STATE_PLAYING) var/dat = "Round Status

" + span_bold("Round Status") + "

" - dat += "Current Game Mode: " + span_bold("[ticker.mode.name]") + "
" + dat += "Current Game Mode: " + span_bold("[SSticker.mode.name]") + "
" dat += "Round Duration: " + span_bold("[roundduration2text()]") + "
" dat += span_bold("Emergency shuttle") + "
" if (!emergency_shuttle.online()) @@ -410,7 +410,7 @@ if (emergency_shuttle.shuttle.moving_status == SHUTTLE_WARMUP) dat += "Launching now..." - dat += "[ticker.delay_end ? "End Round Normally" : "Delay Round End"]
" + dat += "[SSticker.delay_end ? "End Round Normally" : "Delay Round End"]
" dat += "
" for(var/antag_type in GLOB.all_antag_types) var/datum/antagonist/A = GLOB.all_antag_types[antag_type] diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm index 92b19d150d..8298b96a32 100644 --- a/code/modules/admin/topic.dm +++ b/code/modules/admin/topic.dm @@ -25,7 +25,7 @@ if(!CheckAdminHref(href, href_list)) return - if(ticker.mode && ticker.mode.check_antagonists_topic(href, href_list)) + if(SSticker.mode && SSticker.mode.check_antagonists_topic(href, href_list)) check_antagonists() return @@ -66,13 +66,13 @@ else if(href_list["call_shuttle"]) if(!check_rights(R_ADMIN|R_EVENT)) return - if( ticker.mode.name == "blob" ) + if( SSticker.mode.name == "blob" ) tgui_alert_async(usr, "You can't call the shuttle during blob!") return switch(href_list["call_shuttle"]) if("1") - if ((!( ticker ) || !emergency_shuttle.location())) + if ((!( SSticker ) || !emergency_shuttle.location())) return if (emergency_shuttle.can_call()) emergency_shuttle.call_evac() @@ -80,7 +80,7 @@ message_admins(span_blue("[key_name_admin(usr)] called the Emergency Shuttle to the station."), 1) if("2") - if (!( ticker ) || !emergency_shuttle.location()) + if (!( SSticker ) || !emergency_shuttle.location()) return if (emergency_shuttle.can_call()) emergency_shuttle.call_evac() @@ -119,9 +119,9 @@ else if(href_list["delay_round_end"]) if(!check_rights(R_SERVER)) return - ticker.delay_end = !ticker.delay_end - log_admin("[key_name(usr)] [ticker.delay_end ? "delayed the round end" : "has made the round end normally"].") - message_admins(span_blue("[key_name(usr)] [ticker.delay_end ? "delayed the round end" : "has made the round end normally"]."), 1) + SSticker.delay_end = !SSticker.delay_end + log_admin("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"].") + message_admins(span_blue("[key_name(usr)] [SSticker.delay_end ? "delayed the round end" : "has made the round end normally"]."), 1) href_list["secretsadmin"] = "check_antagonist" else if(href_list["simplemake"]) @@ -832,7 +832,7 @@ else if(href_list["c_mode"]) if(!check_rights(R_ADMIN|R_EVENT)) return - if(ticker && ticker.mode) + if(SSticker && SSticker.mode) return tgui_alert_async(usr, "The game has already started.") var/dat = {"What mode do you wish to play?
"} for(var/mode in config.modes) @@ -845,7 +845,7 @@ else if(href_list["f_secret"]) if(!check_rights(R_ADMIN|R_EVENT)) return - if(ticker && ticker.mode) + if(SSticker && SSticker.mode) return tgui_alert_async(usr, "The game has already started.") if(GLOB.master_mode != "secret") return tgui_alert_async(usr, "The game mode has to be secret!") @@ -859,7 +859,7 @@ else if(href_list["c_mode2"]) if(!check_rights(R_ADMIN|R_SERVER|R_EVENT)) return - if (ticker && ticker.mode) + if (SSticker && SSticker.mode) return tgui_alert_async(usr, "The game has already started.") GLOB.master_mode = href_list["c_mode2"] log_admin("[key_name(usr)] set the mode as [config.mode_names[GLOB.master_mode]].") @@ -872,7 +872,7 @@ else if(href_list["f_secret2"]) if(!check_rights(R_ADMIN|R_SERVER|R_EVENT)) return - if(ticker && ticker.mode) + if(SSticker && SSticker.mode) return tgui_alert_async(usr, "The game has already started.") if(GLOB.master_mode != "secret") return tgui_alert_async(usr, "The game mode has to be secret!") @@ -1495,7 +1495,7 @@ else if(href_list["traitor"]) if(!check_rights(R_ADMIN|R_MOD|R_EVENT)) return - if(!ticker || !ticker.mode) + if(!SSticker|| !SSticker.mode) tgui_alert_async(usr, "The game hasn't started yet!") return diff --git a/code/modules/admin/verbs/cinematic.dm b/code/modules/admin/verbs/cinematic.dm index db6e44be73..c0a5095f55 100644 --- a/code/modules/admin/verbs/cinematic.dm +++ b/code/modules/admin/verbs/cinematic.dm @@ -1,28 +1,10 @@ -/client/proc/cinematic(var/cinematic as anything in list("explosion",null)) - set name = "Cinematic" - set category = "Fun.Do Not" - set desc = "Shows a cinematic." // Intended for testing but I thought it might be nice for events on the rare occasion Feel free to comment it out if it's not wanted. - - if(!check_rights(R_FUN)) +ADMIN_VERB(cinematic, R_FUN, "Cinematic", "Show a cinematic to all players.", ADMIN_CATEGORY_FUN) + var/datum/cinematic/choice = tgui_input_list( + user, + "Chose a cinematic to play to everyone in the server.", + "Choose Cinematic", + sortList(subtypesof(/datum/cinematic), GLOBAL_PROC_REF(cmp_typepaths_asc)), + ) + if(!choice || !ispath(choice, /datum/cinematic)) return - - if(tgui_alert(usr, "Are you sure you want to run [cinematic]?","Confirmation",list("Yes","No")) != "Yes") return - if(!ticker) return - switch(cinematic) - if("explosion") - var/input = tgui_alert(usr, "The game will be over. Are you really sure?", "Confirmation", list("Continue","Cancel")) - if(!input || input == "Cancel") - return - var/parameter = tgui_input_number(src,"station_missed = ?","Enter Parameter",0,1,0) - var/override - switch(parameter) - if(1) - override = tgui_input_list(src,"mode = ?","Enter Parameter", list("mercenary","no override")) - if(0) - override = tgui_input_list(src,"mode = ?","Enter Parameter", list("blob","mercenary","AI malfunction","no override")) - ticker.station_explosion_cinematic(parameter,override) - - log_admin("[key_name(src)] launched cinematic \"[cinematic]\"") - message_admins("[key_name_admin(src)] launched cinematic \"[cinematic]\"", 1) - - return + play_cinematic(choice, world) diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm index 6db2128153..45b5b52d2a 100644 --- a/code/modules/admin/verbs/debug.dm +++ b/code/modules/admin/verbs/debug.dm @@ -97,7 +97,7 @@ set category = "Fun.Event Kit" set name = "Make Robot" - if(!ticker) + if(!SSticker) tgui_alert_async(usr, "Wait until the game starts") return if(ishuman(M)) @@ -112,7 +112,7 @@ set category = "Fun.Event Kit" set name = "Make Simple Animal" - if(!ticker) + if(!SSticker) tgui_alert_async(usr, "Wait until the game starts") return @@ -164,7 +164,7 @@ set category = "Fun.Event Kit" set name = "Make Alien" - if(!ticker) + if(!SSticker) tgui_alert_async(usr, "Wait until the game starts") return if(ishuman(M)) @@ -277,7 +277,7 @@ set category = "Admin.Events" set name = "Grant Full Access" - if (!ticker) + if (!SSticker) tgui_alert_async(usr, "Wait until the game starts") return if (ishuman(M)) @@ -610,7 +610,7 @@ ADMIN_VERB(cmd_assume_direct_control, (R_DEBUG|R_ADMIN|R_EVENT), "Assume Direct // DNA2 - Admin Hax /client/proc/cmd_admin_toggle_block(var/mob/M,var/block) - if(!ticker) + if(!SSticker) tgui_alert_async(usr, "Wait until the game starts") return if(istype(M, /mob/living/carbon)) diff --git a/code/modules/admin/verbs/diagnostics.dm b/code/modules/admin/verbs/diagnostics.dm index 44f369422d..3573d274c1 100644 --- a/code/modules/admin/verbs/diagnostics.dm +++ b/code/modules/admin/verbs/diagnostics.dm @@ -79,9 +79,9 @@ set name = "Radio report" var/output = "Radio Report
" - for (var/fq in radio_controller.frequencies) + for (var/fq in SSradio.frequencies) output += "Freq: [fq]
" - var/datum/radio_frequency/fqs = radio_controller.frequencies[fq] + var/datum/radio_frequency/fqs = SSradio.frequencies[fq] if (!fqs) output += "  ERROR
" continue diff --git a/code/modules/admin/verbs/randomverbs.dm b/code/modules/admin/verbs/randomverbs.dm index 83326dbff2..8db00ad4ed 100644 --- a/code/modules/admin/verbs/randomverbs.dm +++ b/code/modules/admin/verbs/randomverbs.dm @@ -867,7 +867,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp set category = "Admin.Events" set name = "Call Shuttle" - if ((!( ticker ) || !emergency_shuttle.location())) + if ((!( SSticker ) || !emergency_shuttle.location())) return if(!check_rights(R_ADMIN)) return @@ -876,7 +876,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp if(confirm != "Yes") return var/choice - if(ticker.mode.auto_recall_shuttle) + if(SSticker.mode.auto_recall_shuttle) choice = tgui_input_list(usr, "The shuttle will just return if you call it. Call anyway?", "Shuttle Call", list("Confirm", "Cancel")) if(choice == "Confirm") emergency_shuttle.auto_recall = 1 //enable auto-recall @@ -903,7 +903,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp if(tgui_alert(src, "You sure?", "Confirm", list("Yes", "No")) != "Yes") return - if(!ticker || !emergency_shuttle.can_recall()) + if(!SSticker || !emergency_shuttle.can_recall()) return emergency_shuttle.recall() @@ -917,7 +917,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp set category = "Admin.Events" set name = "Toggle Deny Shuttle" - if (!ticker) + if (!SSticker) return if(!check_rights(R_ADMIN)) return @@ -944,12 +944,12 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp if(!check_rights(R_FUN)) return - if (ticker && ticker.mode) + if (SSticker && SSticker.mode) to_chat(usr, "Nope you can't do this, the game's already started. This only works before rounds!") return - if(ticker.random_players) - ticker.random_players = 0 + if(CONFIG_GET(flag/force_random_names)) + CONFIG_SET(flag/force_random_names, FALSE) message_admins("Admin [key_name_admin(usr)] has disabled \"Everyone is Special\" mode.", 1) to_chat(usr, "Disabled.") return @@ -967,7 +967,7 @@ ADMIN_VERB(respawn_character, (R_ADMIN|R_REJUVINATE), "Spawn Character", "(Re)Sp to_chat(usr, "Remember: you can always disable the randomness by using the verb again, assuming the round hasn't started yet.") - ticker.random_players = 1 + CONFIG_SET(flag/force_random_names, TRUE) feedback_add_details("admin_verb","MER") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! diff --git a/code/modules/admin/verbs/secrets.dm b/code/modules/admin/verbs/secrets.dm index 329c89f1bf..1b12176fe3 100644 --- a/code/modules/admin/verbs/secrets.dm +++ b/code/modules/admin/verbs/secrets.dm @@ -79,7 +79,7 @@ ADMIN_VERB(secrets, R_HOLDER, "Secrets", "Abuse harder than you ever have before if("show_traitors_and_objectives") // Not implemented in the UI holder.holder.check_antagonists() if("show_game_mode") - if (ticker.mode) tgui_alert_async(holder, "The game mode is [ticker.mode.name]") + if (SSticker.mode) tgui_alert_async(holder, "The game mode is [SSticker.mode.name]") else tgui_alert_async(holder, "For some reason there's a ticker, but not a game mode") //Buttons for debug. diff --git a/code/modules/admin/verbs/striketeam.dm b/code/modules/admin/verbs/striketeam.dm index a0eadc712d..3d2511ddfe 100644 --- a/code/modules/admin/verbs/striketeam.dm +++ b/code/modules/admin/verbs/striketeam.dm @@ -10,7 +10,7 @@ var/const/commandos_possible = 6 //if more Commandos are needed in the future to_chat(src, "Only administrators may use this command.") return - if(!ticker) + if(!SSticker) to_chat(usr, span_red("The game hasn't started yet!")) return @@ -54,3 +54,153 @@ var/const/commandos_possible = 6 //if more Commandos are needed in the future return team.attempt_random_spawn() + +//STRIKE TEAMS +//Thanks to Kilakk for the admin-button portion of this code. + +GLOBAL_VAR_INIT(send_emergency_team, 0) // Used for automagic response teams; 'admin_emergency_team' for admin-spawned response teams + +GLOBAL_VAR_INIT(ert_base_chance, 10) // Default base chance. Will be incremented by increment ERT chance. +GLOBAL_VAR(can_call_ert) +GLOBAL_VAR_INIT(silent_ert, 0) + +/client/proc/response_team() + set name = "Dispatch Emergency Response Team" + set category = "Fun.Event Kit" + set desc = "Send an emergency response team to the station" + + if(!check_rights_for(src, R_HOLDER)) + to_chat(usr, span_danger("Only administrators may use this command.")) + return + if(!SSticker) + to_chat(usr, span_danger("The game hasn't started yet!")) + return + if(SSticker.current_state == 1) + to_chat(usr, span_danger("The round hasn't started yet!")) + return + if(GLOB.send_emergency_team) + to_chat(usr, span_danger("[using_map.boss_name] has already dispatched an emergency response team!")) + return + if(tgui_alert(usr, "Do you want to dispatch an Emergency Response Team?","ERT",list("Yes","No")) != "Yes") + return + if(tgui_alert(usr, "Do you want this Response Team to be announced?","ERT",list("Yes","No")) != "Yes") + GLOB.silent_ert = 1 + if(get_security_level() != "red") // Allow admins to reconsider if the alert level isn't Red + if(tgui_alert(usr, "The station is not in red alert. Do you still want to dispatch a response team?","ERT",list("Yes","No")) != "Yes") + return + if(GLOB.send_emergency_team) + to_chat(usr, span_danger("Looks like somebody beat you to it!")) + return + + message_admins("[key_name_admin(usr)] is dispatching an Emergency Response Team.", 1) + admin_chat_message(message = "[key_name(usr)] is dispatching an Emergency Response Team", color = "#CC2222") //VOREStation Add + log_admin("[key_name(usr)] used Dispatch Response Team.") + trigger_armed_response_team(1) + +/client/verb/JoinResponseTeam() + + set name = "Join Response Team" + set category = "IC.Event" + + if(!MayRespawn(1)) + to_chat(usr, span_warning("You cannot join the response team at this time.")) + return + + if(isobserver(usr) || isnewplayer(usr)) + if(!GLOB.send_emergency_team) + to_chat(usr, "No emergency response team is currently being sent.") + return + if(jobban_isbanned(usr, JOB_SYNDICATE) || jobban_isbanned(usr, JOB_EMERGENCY_RESPONSE_TEAM) || jobban_isbanned(usr, JOB_SECURITY_OFFICER)) + to_chat(usr, span_danger("You are jobbanned from the emergency reponse team!")) + return + if(ert.current_antagonists.len >= ert.hard_cap) + to_chat(usr, "The emergency response team is already full!") + return + ert.create_default(usr) + else + to_chat(usr, "You need to be an observer or new player to use this.") + +// returns a number of dead players in % +/proc/percentage_dead() + var/total = 0 + var/deadcount = 0 + for(var/mob/living/carbon/human/H in GLOB.mob_list) + if(H.client) // Monkeys and mice don't have a client, amirite? + if(H.stat == 2) deadcount++ + total++ + + if(total == 0) return 0 + else return round(100 * deadcount / total) + +// counts the number of antagonists in % +/proc/percentage_antagonists() + var/total = 0 + var/antagonists = 0 + for(var/mob/living/carbon/human/H in GLOB.mob_list) + if(is_special_character(H) >= 1) + antagonists++ + total++ + + if(total == 0) return 0 + else return round(100 * antagonists / total) + +// Increments the ERT chance automatically, so that the later it is in the round, +// the more likely an ERT is to be able to be called. +/proc/increment_ert_chance() + while(GLOB.send_emergency_team == 0) // There is no ERT at the time. + if(get_security_level() == "green") + GLOB.ert_base_chance += 1 + if(get_security_level() == "yellow") + GLOB.ert_base_chance += 1 + if(get_security_level() == "violet") + GLOB.ert_base_chance += 2 + if(get_security_level() == "orange") + GLOB.ert_base_chance += 2 + if(get_security_level() == "blue") + GLOB.ert_base_chance += 2 + if(get_security_level() == "red") + GLOB.ert_base_chance += 3 + if(get_security_level() == "delta") + GLOB.ert_base_chance += 10 // Need those big guns + sleep(600 * 3) // Minute * Number of Minutes + + +/proc/trigger_armed_response_team(var/force = 0) + if(!GLOB.can_call_ert && !force) + return + if(GLOB.send_emergency_team) + return + + var/send_team_chance = GLOB.ert_base_chance // Is incremented by increment_ert_chance. + send_team_chance += 2*percentage_dead() // the more people are dead, the higher the chance + send_team_chance += percentage_antagonists() // the more antagonists, the higher the chance + send_team_chance = min(send_team_chance, 100) + + if(force) send_team_chance = 100 + + // there's only a certain chance a team will be sent + if(!prob(send_team_chance)) + command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. Unfortunately, we were unable to send one at this time.", "[using_map.boss_name]") + GLOB.can_call_ert = 0 // Only one call per round, ladies. + return + if(GLOB.silent_ert == 0) + command_announcement.Announce("It would appear that an emergency response team was requested for [station_name()]. We will prepare and send one as soon as possible.", "[using_map.boss_name]") + + GLOB.can_call_ert = 0 // Only one call per round, gentleman. + GLOB.send_emergency_team = 1 + consider_ert_load() //VOREStation Add + + sleep(600 * 5) + GLOB.send_emergency_team = 0 // Can no longer join the ERT. + +GLOBAL_VAR(ert_loaded) + +/proc/consider_ert_load() + if(!GLOB.ert_loaded) + GLOB.ert_loaded = TRUE + var/datum/map_template/MT = SSmapping.map_templates["Special Area - ERT"] + if(!istype(MT)) + error("ERT Area is not a valid map template!") + else + MT.load_new_z(centered = TRUE) + log_and_message_admins("Loaded the ERT shuttle just now.") diff --git a/code/game/trader_visit.dm b/code/modules/admin/verbs/trader.dm similarity index 82% rename from code/game/trader_visit.dm rename to code/modules/admin/verbs/trader.dm index 3fbd8b250a..08a63b4bd8 100644 --- a/code/game/trader_visit.dm +++ b/code/modules/admin/verbs/trader.dm @@ -11,10 +11,10 @@ GLOBAL_VAR_INIT(can_call_traders, 1) if(!holder) to_chat(usr, span_danger("Only administrators may use this command.")) return - if(!ticker) + if(!SSticker) to_chat(usr, span_danger("The game hasn't started yet!")) return - if(ticker.current_state == 1) + if(SSticker.current_state == 1) to_chat(usr, span_danger("The round hasn't started yet!")) return if(GLOB.send_beruang) @@ -67,3 +67,15 @@ GLOBAL_VAR_INIT(can_call_traders, 1) sleep(600 * 5) GLOB.send_beruang = 0 // Can no longer join the traders. + +GLOBAL_VAR(trader_loaded) + +/proc/consider_trader_load() + if(!GLOB.trader_loaded) + GLOB.trader_loaded = TRUE + var/datum/map_template/MT = SSmapping.map_templates["Special Area - Salamander Trader"] //was: "Special Area - Trader" + if(!istype(MT)) + error("Trader is not a valid map template!") + else + MT.load_new_z(centered = TRUE) + log_and_message_admins("Loaded the trade shuttle just now.") diff --git a/code/modules/admin/verbs/tripAI.dm b/code/modules/admin/verbs/tripAI.dm index 2e31e7418b..7a9f0d31d0 100644 --- a/code/modules/admin/verbs/tripAI.dm +++ b/code/modules/admin/verbs/tripAI.dm @@ -2,21 +2,21 @@ set category = "Fun.Event Kit" set name = "Create AI Triumvirate" - if(ticker.current_state > GAME_STATE_PREGAME) + if(SSticker.current_state > GAME_STATE_PREGAME) to_chat(usr, "This option is currently only usable during pregame. This may change at a later date.") return - if(job_master && ticker) + if(job_master && SSticker) var/datum/job/job = job_master.GetJob(JOB_AI) if(!job) to_chat(usr, "Unable to locate the AI job") return - if(ticker.triai) - ticker.triai = 0 + if(GLOB.triai) + GLOB.triai = 0 to_chat(usr, "Only one AI will be spawned at round start.") message_admins(span_blue("[key_name_admin(usr)] has toggled off triple AIs at round start."), 1) else - ticker.triai = 1 + GLOB.triai = 1 to_chat(usr, "There will be an AI Triumvirate at round start.") message_admins(span_blue("[key_name_admin(usr)] has toggled on triple AIs at round start."), 1) return diff --git a/code/modules/ai/__readme.dm b/code/modules/ai/README.md similarity index 98% rename from code/modules/ai/__readme.dm rename to code/modules/ai/README.md index dc0346e58a..d78ef03f41 100644 --- a/code/modules/ai/__readme.dm +++ b/code/modules/ai/README.md @@ -1,5 +1,4 @@ -/* -[Summary] +## Summary This module contains an AI implementation designed to be (at the base level) mobtype-agnostic, by being held inside a datum instead of being written into the mob directly. More specialized @@ -11,7 +10,7 @@ When designing a new mob, all that is needed to give a mob an AI is to set its 'ai_holder_type' variable to the path of the AI that is desired. -[Seperation] +## Seperation In previous iterations of AI systems, the AI is generally written into the mob's code directly, which has some advantages, but often makes the code rigid, and also tied the speed of the AI @@ -46,7 +45,7 @@ in the future. delay, as the ai_holder might not exist yet. -[Flow of Processing] +## Flow of Processing Terrible visual representation here; AI Subsystem -> Every 0.5s -> /datum/ai_holder/handle_tactics() -> switch(stance)... @@ -92,7 +91,7 @@ with each other, as opposed to having individual tick counters inside all of the ai_holder instances. It should be noted that handle_tactics() is always called first, before handle_strategicals() every two seconds. -[Process Skipping] +## Process Skipping An ai_holder object can choose to enter a 'busy' state, or a 'sleep' state, in order to avoid processing. @@ -117,7 +116,7 @@ from processing the other ai_holders until the sleep() finishes. Delays on the mob typically have set waitfor = FALSE, or spawn() is used. -[Stances] +## Stances The AI has a large number of states that it can be in, called stances. The AI will act in a specific way depending on which stance it is in, @@ -138,7 +137,7 @@ module folder and are mostly self contained, however some files instead deal with general things that other stances may require, such as targeting or movement. -[Interfaces] +## Interfaces Interfaces are a concept that is used to help bridge the gap between the ai_holder, and its mob. Because the (base) ai_holder is explicitly @@ -168,7 +167,7 @@ ranged attack. For simple_mobs, they can if a ranged projectile type was set, where as for a human mob, it could check if a gun is in a hand. For a borg, it could check if a gun is inside their current module. -[Say List] +## Say List A /datum/say_list is a very light datum that holds a list of strings for the AI to have their mob say based on certain conditions, such as when threatening @@ -181,7 +180,7 @@ mercenaries and fake piloted mecha mobs. The say_list datum is applied to the mob itself and not held inside the AI datum. -[Subtypes] +## Subtypes Some subtypes of ai_holder are more specialized, but remain compatible with most mob types. There are many different subtypes that make the AI act different @@ -196,6 +195,3 @@ To use a specific subtype on a mob, all that is needed is setting the mob's ai_holder_type to the subtype desired, and it will create that subtype when the mob is initialize()d. Switching to a subtype 'live' will require additional effort on the coder. - - -*/ diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm index b3bef467db..916858e340 100644 --- a/code/modules/assembly/signaler.dm +++ b/code/modules/assembly/signaler.dm @@ -122,14 +122,14 @@ /obj/item/assembly/signaler/proc/set_frequency(new_frequency) if(!frequency) return - if(!radio_controller) + if(!SSradio) addtimer(CALLBACK(src, PROC_REF(radio_checkup), new_frequency), 2 SECONDS) return set_radio(new_frequency) /obj/item/assembly/signaler/proc/radio_checkup(new_frequency) PROTECTED_PROC(TRUE) - if(!radio_controller) + if(!SSradio) return set_radio(new_frequency) @@ -137,12 +137,12 @@ /obj/item/assembly/signaler/proc/set_radio(new_frequency) PROTECTED_PROC(TRUE) SHOULD_NOT_OVERRIDE(TRUE) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) + radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) /obj/item/assembly/signaler/Destroy() - if(radio_controller) - radio_controller.remove_object(src,frequency) + if(SSradio) + SSradio.remove_object(src,frequency) frequency = 0 . = ..() diff --git a/code/modules/asset_cache/assets/spritesheets/robot_icons.dm b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm index 7297883337..d7b34163aa 100644 --- a/code/modules/asset_cache/assets/spritesheets/robot_icons.dm +++ b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm @@ -1,107 +1,90 @@ GLOBAL_LIST_EMPTY(robot_sprite_sheets) /datum/asset/spritesheet_batched/robot_icons + _abstract = /datum/asset/spritesheet_batched/robot_icons name = "robot_icons" - fully_generated = TRUE var/module_type /datum/asset/spritesheet_batched/robot_icons/standard name = "robot_icons_standard" - fully_generated = FALSE module_type = "Standard" /datum/asset/spritesheet_batched/robot_icons/service name = "robot_icons_service" - fully_generated = FALSE module_type = "Service" /datum/asset/spritesheet_batched/robot_icons/clerical name = "robot_icons_clerical" - fully_generated = FALSE module_type = "Clerical" /datum/asset/spritesheet_batched/robot_icons/clown name = "robot_icons_clown" - fully_generated = FALSE module_type = "Clown" /datum/asset/spritesheet_batched/robot_icons/command name = "robot_icons_command" - fully_generated = FALSE module_type = "Command" /datum/asset/spritesheet_batched/robot_icons/research name = "robot_icons_research" - fully_generated = FALSE module_type = "Research" /datum/asset/spritesheet_batched/robot_icons/miner name = "robot_icons_miner" - fully_generated = FALSE module_type = "Miner" /datum/asset/spritesheet_batched/robot_icons/crisis name = "robot_icons_crisis" - fully_generated = FALSE module_type = "Crisis" +/* Modul not in use /datum/asset/spritesheet_batched/robot_icons/surgeon name = "robot_icons_surgeon" module_type = "Surgeon" +*/ /datum/asset/spritesheet_batched/robot_icons/security name = "robot_icons_security" - fully_generated = FALSE module_type = "Security" /datum/asset/spritesheet_batched/robot_icons/combat name = "robot_icons_combat" - fully_generated = FALSE module_type = "Combat" /datum/asset/spritesheet_batched/robot_icons/exploration name = "robot_icons_exploration" - fully_generated = FALSE module_type = "Exploration" /datum/asset/spritesheet_batched/robot_icons/engineering name = "robot_icons_engineering" - fully_generated = FALSE module_type = "Engineering" /datum/asset/spritesheet_batched/robot_icons/janitor name = "robot_icons_janitor" - fully_generated = FALSE module_type = "Janitor" /datum/asset/spritesheet_batched/robot_icons/gravekeeper name = "robot_icons_gravekeeper" - fully_generated = FALSE module_type = "Gravekeeper" /datum/asset/spritesheet_batched/robot_icons/lost name = "robot_icons_lost" - fully_generated = FALSE module_type = "Lost" /datum/asset/spritesheet_batched/robot_icons/protector name = "robot_icons_protector" - fully_generated = FALSE module_type = "Protector" /datum/asset/spritesheet_batched/robot_icons/mechanist name = "robot_icons_mechanist" - fully_generated = FALSE module_type = "Mechanist" /datum/asset/spritesheet_batched/robot_icons/combat_medic name = "robot_icons_combat_medic" - fully_generated = FALSE module_type = "Combat Medic" /datum/asset/spritesheet_batched/robot_icons/ninja name = "robot_icons_ninja" - fully_generated = FALSE module_type = "Ninja" /datum/asset/spritesheet_batched/robot_icons/create_spritesheets() diff --git a/code/modules/asset_cache/iconforge/batched_spritesheet.dm b/code/modules/asset_cache/iconforge/batched_spritesheet.dm index db4a0fd5cf..f50370fb22 100644 --- a/code/modules/asset_cache/iconforge/batched_spritesheet.dm +++ b/code/modules/asset_cache/iconforge/batched_spritesheet.dm @@ -28,8 +28,6 @@ var/load_immediately = FALSE /// If we should avoid propogating 'invalid dir' errors from rust-g. Because sometimes, you just don't know what dirs are valid. var/ignore_dir_errors = FALSE - /// Avoid propogating 'Could not find associated icon state' errors because we know our input data fucking sucks - var/ignore_associated_icon_state_errors = FALSE /// Forces use of the smart cache. This is for unit tests, please respect the config <3 var/force_cache = FALSE @@ -48,6 +46,7 @@ var/cache_dmi_hashes_json = null /// Used to prevent async cache refresh jobs from looping on failure. var/cache_result = null + var/getting_genned = FALSE /datum/asset/spritesheet_batched/proc/should_load_immediately() #ifdef DO_NOT_DEFER_ASSETS @@ -78,7 +77,7 @@ if(cached_rustg_version != rustg_version) log_asset("Invalidated cache for spritesheet_[name] due to rustg updating from [cached_rustg_version] to [rustg_version].") return CACHE_INVALID - // Invalidate cache if the DM version changes + // Invalidate cache if the DM version changes var/cached_dm_version = cache_json["dm_version"] if(isnull(cached_dm_version)) log_asset("Cache for spritesheet_[name] did not contain a dm_version!") @@ -108,8 +107,8 @@ if (data_out == RUSTG_JOB_ERROR) CRASH("Spritesheet [name] cache JOB PANIC") else if(!findtext(data_out, "{", 1, 2)) - rustg_file_write(cache_data, "[GLOB.log_directory]-spritesheet_cache_debug.[name].json") - rustg_file_write(entries_json, "[GLOB.log_directory]-spritesheet_debug_[name].json") + rustg_file_write(cache_data, "[GLOB.log_directory]/spritesheet_cache_debug.[name].json") + rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json") CRASH("Spritesheet [name] cache check UNKNOWN ERROR: [data_out]") var/result = json_decode(data_out) var/fail = result["fail_reason"] @@ -127,6 +126,8 @@ /datum/asset/spritesheet_batched/proc/insert_icon(sprite_name, datum/universal_icon/entry) if(!istext(sprite_name) || !length(sprite_name)) CRASH("Invalid sprite_name \"[sprite_name]\" given to insert_icon()! Providing non-strings will break icon generation.") + if(!istype(entry)) + CRASH("Invalid type provided to insert_icon()! Value: [entry] (type: [entry?.type])") entries[sprite_name] = entry.to_list() /datum/asset/spritesheet_batched/register() @@ -148,7 +149,6 @@ /// Call insert_icon or insert_all_icons here, building a spritesheet! /datum/asset/spritesheet_batched/proc/create_spritesheets() - SHOULD_CALL_PARENT(FALSE) CRASH("create_spritesheets() not implemented for [type]!") /datum/asset/spritesheet_batched/proc/insert_all_icons(prefix, icon/I, list/directions, prefix_with_dirs = TRUE) @@ -159,8 +159,6 @@ directions = list(SOUTH) for (var/icon_state_name in icon_states(I)) - if(icon_state_name == "") - icon_state_name = "byond-default" for (var/direction in directions) var/prefix2 = (directions.len > 1 && prefix_with_dirs) ? "[dir2text(direction)]-" : "" insert_icon("[prefix][prefix2][icon_state_name]", uni_icon(I, icon_state_name, direction)) @@ -171,6 +169,8 @@ if(!length(entries)) CRASH("Spritesheet [name] ([type]) is empty! What are you doing?") + if(getting_genned) + stack_trace("Spritesheet batching has been called twice. This is illegal!") if(isnull(entries_json)) entries_json = json_encode(entries) @@ -192,15 +192,16 @@ var/data_out if(yield || !isnull(job_id)) if(isnull(job_id)) + getting_genned = TRUE job_id = rustg_iconforge_generate_async("data/spritesheets/", name, entries_json, do_cache, FALSE, TRUE) UNTIL((data_out = rustg_iconforge_check(job_id)) != RUSTG_JOB_NO_RESULTS_YET) + getting_genned = FALSE else - //rustg_file_write(entries_json, "fuckoff.json") data_out = rustg_iconforge_generate("data/spritesheets/", name, entries_json, do_cache, FALSE, TRUE) if (data_out == RUSTG_JOB_ERROR) CRASH("Spritesheet [name] JOB PANIC") else if(!findtext(data_out, "{", 1, 2)) - rustg_file_write(entries_json, "[GLOB.log_directory]-spritesheet_debug_[name].json") + rustg_file_write(entries_json, "[GLOB.log_directory]/spritesheet_debug_[name].json") CRASH("Spritesheet [name] UNKNOWN ERROR: [data_out]") var/data = json_decode(data_out) sizes = data["sizes"] @@ -209,30 +210,31 @@ var/dmi_hashes = data["dmi_hashes"] // this only contains values if do_cache is TRUE. for(var/size_id in sizes) - var/file_path = "data/spritesheets/[name]_[size_id].png" - var/file_hash = rustg_hash_file("md5", file_path) - SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(file_path), file_hash) - var/res_name = "spritesheet_[name].css" - var/fname = "data/spritesheets/[res_name]" + var/png_name = "[name]_[size_id].png" + var/file_directory = "data/spritesheets/[png_name]" + var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_directory) + SSassets.transport.register_asset(png_name, fcopy_rsc(file_directory), file_hash) + if(CONFIG_GET(flag/save_spritesheets)) + save_to_logs(file_name = png_name, file_location = file_directory) + var/css_name = "spritesheet_[name].css" + var/file_directory = "data/spritesheets/[css_name]" - fdel(fname) + fdel(file_directory) var/css = generate_css() - rustg_file_write(css, fname) - var/css_hash = rustg_hash_string("md5", css) - SSassets.transport.register_asset(res_name, fcopy_rsc(fname), file_hash=css_hash) + rustg_file_write(css, file_directory) + var/css_hash = rustg_hash_string(RUSTG_HASH_MD5, css) + SSassets.transport.register_asset(css_name, fcopy_rsc(file_directory), file_hash=css_hash) + + if(CONFIG_GET(flag/save_spritesheets)) + save_to_logs(file_name = css_name, file_location = file_directory) if (do_cache) write_cache_meta(input_hash, dmi_hashes) fully_generated = TRUE // If we were ever in there, remove ourselves SSasset_loading.dequeue_asset(src) - if(data["error"]) - var/err = data["error"] - if(ignore_dir_errors && findtext(err, "is not in the set of valid dirs")) - return - if(ignore_associated_icon_state_errors && findtext(err, "Could not find associated icon state")) - return - CRASH("Error during spritesheet generation for [name]: [err]") + if(data["error"] && !(ignore_dir_errors && findtext(data["error"], "is not in the set of valid dirs"))) + CRASH("Error during spritesheet generation for [name]: [data["error"]]") /datum/asset/spritesheet_batched/queued_generation() realize_spritesheets(yield = TRUE) @@ -266,7 +268,7 @@ var/size_split = splittext(size_id, "x") var/width = text2num(size_split[1]) var/height = text2num(size_split[2]) - out += ".[name][size_id]{display:inline-block;width:[width]px;height:[height]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat: no-repeat;}" + out += ".[name][size_id]{display:inline-block;width:[width]px;height:[height]px;background-image:url('[get_background_url("[name]_[size_id].png")]');background-repeat:no-repeat;}" for (var/sprite_id in sprites) var/sprite = sprites[sprite_id] @@ -286,7 +288,8 @@ if(!CONFIG_GET(flag/smart_cache_assets) && !force_cache) return FALSE // this is already guaranteed to exist. - var/css_fname = "data/spritesheets/spritesheet_[name].css" + var/css_name = "spritesheet_[name].css" + var/css_file_directory = "data/spritesheets/[css_name]" // sizes gets filled during should_refresh() for(var/size_id in sizes) @@ -294,13 +297,16 @@ if(!fexists(fname)) return FALSE - var/css_hash = rustg_hash_file("md5", css_fname) - SSassets.transport.register_asset("spritesheet_[name].css", fcopy_rsc(css_fname), file_hash=css_hash) + var/css_hash = rustg_hash_file(RUSTG_HASH_MD5, css_file_directory) + SSassets.transport.register_asset(css_name, fcopy_rsc(css_file_directory), file_hash=css_hash) for(var/size_id in sizes) var/fname = "data/spritesheets/[name]_[size_id].png" - var/hash = rustg_hash_file("md5", fname) + var/hash = rustg_hash_file(RUSTG_HASH_MD5, fname) SSassets.transport.register_asset("[name]_[size_id].png", fcopy_rsc(fname), file_hash=hash) + if(CONFIG_GET(flag/save_spritesheets)) + save_to_logs(file_name = css_name, file_location = css_file_directory) + return TRUE /// Returns the URL to put in the background:url of the CSS asset diff --git a/code/modules/asset_cache/iconforge/universal_icon.dm b/code/modules/asset_cache/iconforge/universal_icon.dm index dda41928d8..1052c105e3 100644 --- a/code/modules/asset_cache/iconforge/universal_icon.dm +++ b/code/modules/asset_cache/iconforge/universal_icon.dm @@ -11,9 +11,9 @@ /// Don't instantiate these yourself, use uni_icon. /datum/universal_icon/New(icon/icon_file, icon_state="", dir=null, frame=null, datum/icon_transformer/transform=null, color=null) - #ifdef UNIT_TEST + #ifdef UNIT_TESTS // This check is kinda slow and shouldn't fail unless a developer makes a mistake. So it'll get caught in unit tests. - if(!isicon(icon_file) || !isfile(icon_file) || "[icon_file]" == "/icon") + if(!isicon(icon_file) || !isfile(icon_file) || "[icon_file]" == "/icon" || !length("[icon_file]")) // bad! use 'icons/path_to_dmi.dmi' format only CRASH("FATAL: universal_icon was provided icon_file: [icon_file] - icons provided to batched spritesheets MUST be DMI files, they cannot be /image, /icon, or other runtime generated icons.") #endif @@ -44,10 +44,10 @@ transform.blend_color(color, blend_mode) return src -/datum/universal_icon/proc/blend_icon(datum/universal_icon/icon_object, blend_mode) +/datum/universal_icon/proc/blend_icon(datum/universal_icon/icon_object, blend_mode, x=1, y=1) if(!transform) transform = new - transform.blend_icon(icon_object, blend_mode) + transform.blend_icon(icon_object, blend_mode, x, y) return src /datum/universal_icon/proc/scale(width, height) @@ -62,14 +62,116 @@ transform.crop(x1, y1, x2, y2) return src -/// Internally performs a crop. -/datum/universal_icon/proc/shift(dir, amount, icon_width, icon_height) +/datum/universal_icon/proc/flip(dir) if(!transform) transform = new - var/list/offsets = dir2offset(dir) - var/shift_x = -offsets[1] * amount - var/shift_y = -offsets[2] * amount - transform.crop(1 + shift_x, 1 + shift_y, icon_width + shift_x, icon_height + shift_y) + transform.flip(dir) + return src + +/datum/universal_icon/proc/rotate(angle) + if(!transform) + transform = new + transform.rotate(angle) + return src + +/datum/universal_icon/proc/shift(dir, offset, wrap=0) + if(!transform) + transform = new + transform.shift(dir, offset, wrap) + return src + +/datum/universal_icon/proc/swap_color(src_color, dst_color) + if(!transform) + transform = new + transform.swap_color(src_color, dst_color) + return src + +/datum/universal_icon/proc/draw_box(color, x1, y1, x2=x1, y2=y1) + if(!transform) + transform = new + transform.draw_box(color, x1, y1, x2, y2) + return src + +/datum/universal_icon/proc/map_colors_inferred(list/color_args) + var/num_args = length(color_args) + if(num_args <= 20 || num_args >= 16) + src.map_colors_rgba(arglist(color_args)) + else if(num_args <= 12 || num_args >= 9) + src.map_colors_rgb(arglist(color_args)) + else if(num_args == 5) + src.map_colors_rgba_hex(arglist(color_args)) + else if(num_args == 4) + // is there alpha in the hex? + if(length(color_args[3]) == 7 || length(color_args[3]) == 4) + src.map_colors_rgb_hex(arglist(color_args)) + else + src.map_colors_rgba_hex(arglist(color_args)) + else if(num_args == 3) + src.map_colors_rgb_hex(arglist(color_args)) + +/datum/universal_icon/proc/map_colors_rgba(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0=0, g0=0, b0=0, a0=0) + if(!transform) + transform = new + transform.map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0, g0, b0, a0) + return src + +/datum/universal_icon/proc/map_colors_rgb(rr, rg, rb, gr, gg, gb, br, bg, bb, r0=0, g0=0, b0=0) + if(!transform) + transform = new + transform.map_colors(rr, rg, rb, 0, gr, gg, gb, 0, br, bg, bb, 0, 0, 0, 0, 1, r0, g0, b0, 0) + return src + +/datum/universal_icon/proc/map_colors_rgb_hex(r_rgb, g_rgb, b_rgb, rgb0=rgb(0,0,0)) + if(!transform) + transform = new + var/rr = hex2num(copytext(r_rgb, 2, 4)) / 255 + var/rg = hex2num(copytext(r_rgb, 4, 6)) / 255 + var/rb = hex2num(copytext(r_rgb, 6, 8)) / 255 + + var/gr = hex2num(copytext(g_rgb, 2, 4)) / 255 + var/gg = hex2num(copytext(g_rgb, 4, 6)) / 255 + var/gb = hex2num(copytext(g_rgb, 6, 8)) / 255 + + var/br = hex2num(copytext(b_rgb, 2, 4)) / 255 + var/bg = hex2num(copytext(b_rgb, 4, 6)) / 255 + var/bb = hex2num(copytext(b_rgb, 6, 8)) / 255 + + var/r0 = hex2num(copytext(rgb0, 2, 4)) / 255 + var/b0 = hex2num(copytext(rgb0, 4, 6)) / 255 + var/g0 = hex2num(copytext(rgb0, 6, 8)) / 255 + + transform.map_colors(rr, rg, rb, 0, gr, gg, gb, 0, br, bg, bb, 0, 0, 0, 0, 1, r0, b0, g0, 0) + return src + +/datum/universal_icon/proc/map_colors_rgba_hex(r_rgba, g_rgba, b_rgba, a_rgba, rgba0="#00000000") + if(!transform) + transform = new + var/rr = hex2num(copytext(r_rgba, 2, 4)) / 255 + var/rg = hex2num(copytext(r_rgba, 4, 6)) / 255 + var/rb = hex2num(copytext(r_rgba, 6, 8)) / 255 + var/ra = hex2num(copytext(r_rgba, 8, 10)) / 255 + + var/gr = hex2num(copytext(g_rgba, 2, 4)) / 255 + var/gg = hex2num(copytext(g_rgba, 4, 6)) / 255 + var/gb = hex2num(copytext(g_rgba, 6, 8)) / 255 + var/ga = hex2num(copytext(g_rgba, 8, 10)) / 255 + + var/br = hex2num(copytext(b_rgba, 2, 4)) / 255 + var/bg = hex2num(copytext(b_rgba, 4, 6)) / 255 + var/bb = hex2num(copytext(b_rgba, 6, 8)) / 255 + var/ba = hex2num(copytext(b_rgba, 8, 10)) / 255 + + var/ar = hex2num(copytext(a_rgba, 2, 4)) / 255 + var/ag = hex2num(copytext(a_rgba, 4, 6)) / 255 + var/ab = hex2num(copytext(a_rgba, 6, 8)) / 255 + var/aa = hex2num(copytext(a_rgba, 8, 10)) / 255 + + var/r0 = hex2num(copytext(rgba0, 2, 4)) / 255 + var/b0 = hex2num(copytext(rgba0, 4, 6)) / 255 + var/g0 = hex2num(copytext(rgba0, 6, 8)) / 255 + var/a0 = hex2num(copytext(rgba0, 8, 10)) / 255 + + transform.map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0, b0, g0, a0) return src /// Internally performs a color blend. @@ -118,11 +220,29 @@ if(!istype(icon_object)) stack_trace("Invalid icon found in icon transformer during apply()! [icon_object]") continue - target.Blend(icon_object.to_icon(), transform["blend_mode"]) + target.Blend(icon_object.to_icon(), transform["blend_mode"], transform["x"], transform["y"]) if(RUSTG_ICONFORGE_SCALE) target.Scale(transform["width"], transform["height"]) if(RUSTG_ICONFORGE_CROP) target.Crop(transform["x1"], transform["y1"], transform["x2"], transform["y2"]) + if(RUSTG_ICONFORGE_MAP_COLORS) + target.MapColors( + transform["rr"], transform["rg"], transform["rb"], transform["ra"], + transform["gr"], transform["gg"], transform["gb"], transform["ga"], + transform["br"], transform["bg"], transform["bb"], transform["ba"], + transform["ar"], transform["ag"], transform["ab"], transform["aa"], + transform["r0"], transform["g0"], transform["b0"], transform["a0"], + ) + if(RUSTG_ICONFORGE_FLIP) + target.Flip(transform["dir"]) + if(RUSTG_ICONFORGE_TURN) + target.Turn(transform["angle"]) + if(RUSTG_ICONFORGE_SHIFT) + target.Shift(transform["dir"], transform["offset"], transform["wrap"]) + if(RUSTG_ICONFORGE_SWAP_COLOR) + target.SwapColor(transform["src_color"], transform["dst_color"]) + if(RUSTG_ICONFORGE_DRAW_BOX) + target.DrawBox(transform["color"], transform["x1"], transform["y1"], transform["x2"], transform["y2"]) return target /datum/icon_transformer/proc/copy() @@ -134,7 +254,7 @@ return new_transformer /datum/icon_transformer/proc/blend_color(color, blend_mode) - #ifdef UNIT_TEST + #ifdef UNIT_TESTS if(!istext(color)) CRASH("Invalid color provided to blend_color: [color]") if(!isnum(blend_mode)) @@ -142,28 +262,77 @@ #endif transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_COLOR, "color" = color, "blend_mode" = blend_mode)) -/datum/icon_transformer/proc/blend_icon(datum/universal_icon/icon_object, blend_mode) - #ifdef UNIT_TEST +/datum/icon_transformer/proc/blend_icon(datum/universal_icon/icon_object, blend_mode, x=1, y=1) + #ifdef UNIT_TESTS // icon_object's type is checked later in to_list if(!isnum(blend_mode)) CRASH("Invalid blend_mode provided to blend_icon: [blend_mode]") + if(!isnum(x)) + CRASH("Invalid x offset provided to blend_icon: [x]") + if(!isnum(y)) + CRASH("Invalid y offset provided to blend_icon: [y]") #endif - transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode)) + transforms += list(list("type" = RUSTG_ICONFORGE_BLEND_ICON, "icon" = icon_object, "blend_mode" = blend_mode, "x" = x, "y" = y)) /datum/icon_transformer/proc/scale(width, height) - #ifdef UNIT_TEST + #ifdef UNIT_TESTS if(!isnum(width) || !isnum(height)) CRASH("Invalid arguments provided to scale: [width],[height]") #endif transforms += list(list("type" = RUSTG_ICONFORGE_SCALE, "width" = width, "height" = height)) /datum/icon_transformer/proc/crop(x1, y1, x2, y2) - #ifdef UNIT_TEST + #ifdef UNIT_TESTS if(!isnum(x1) || !isnum(y1) || !isnum(x2) || !isnum(y2)) CRASH("Invalid arguments provided to crop: [x1],[y1],[x2],[y2]") #endif transforms += list(list("type" = RUSTG_ICONFORGE_CROP, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2)) +/datum/icon_transformer/proc/flip(dir) + #ifdef UNIT_TESTS + if(!isnum(dir)) + CRASH("Invalid arguments provided to flip: [dir]") + #endif + transforms += list(list("type" = RUSTG_ICONFORGE_FLIP, "dir" = dir)) + +/datum/icon_transformer/proc/rotate(angle) + #ifdef UNIT_TESTS + if(!isnum(angle)) + CRASH("Invalid arguments provided to rotate: [angle]") + #endif + transforms += list(list("type" = RUSTG_ICONFORGE_TURN, "angle" = angle)) + +/datum/icon_transformer/proc/shift(dir, offset, wrap=FALSE) + #ifdef UNIT_TESTS + if(!isnum(dir) || !isnum(offset) || (wrap != FALSE && wrap != TRUE)) + CRASH("Invalid arguments provided to shift: [dir],[offset],[wrap]") + #endif + transforms += list(list("type" = RUSTG_ICONFORGE_SHIFT, "dir" = dir, "offset" = offset, "wrap" = wrap)) + +/datum/icon_transformer/proc/swap_color(src_color, dst_color) + #ifdef UNIT_TESTS + if(!istext(src_color) || !istext(dst_color)) + CRASH("Invalid arguments provided to swap_color: [src_color],[dst_color]") + #endif + transforms += list(list("type" = RUSTG_ICONFORGE_SWAP_COLOR, "src_color" = src_color, "dst_color" = dst_color)) + +/datum/icon_transformer/proc/draw_box(color, x1, y1, x2=x1, y2=y1) + #ifdef UNIT_TESTS + if(!istext(color) || !isnum(x1) || !isnum(y1) || !isnum(x2) || !isnum(y2)) + CRASH("Invalid arguments provided to draw_box: [color],[x1],[y1],[x2],[y2]") + #endif + transforms += list(list("type" = RUSTG_ICONFORGE_DRAW_BOX, "color" = color, "x1" = x1, "y1" = y1, "x2" = x2, "y2" = y2)) + +/datum/icon_transformer/proc/map_colors(rr, rg, rb, ra, gr, gg, gb, ga, br, bg, bb, ba, ar, ag, ab, aa, r0=0, g0=0, b0=0, a0=0) + transforms += list(list( + "type" = RUSTG_ICONFORGE_MAP_COLORS, + "rr" = rr, "rg" = rg, "rb" = rb, "ra" = ra, + "gr" = gr, "gg" = gg, "gb" = gb, "ga" = ga, + "br" = br, "bg" = bg, "bb" = bb, "ba" = ba, + "ar" = ar, "ag" = ag, "ab" = ab, "aa" = aa, + "r0" = r0, "g0" = g0, "b0" = b0, "a0" = a0, + )) + /// Recursively converts all contained [/datum/universal_icon]s and their associated [/datum/icon_transformer]s into list form so the transforms can be JSON encoded. /datum/icon_transformer/proc/to_list() RETURN_TYPE(/list) @@ -204,26 +373,240 @@ return transform /// Converts a GAGS item to a universal icon by generating blend operations. -// /proc/gags_to_universal_icon(obj/item/path) -// RETURN_TYPE(/datum/universal_icon) -// if(!ispath(path, /obj/item) || !initial(path.greyscale_config) || !initial(path.greyscale_colors)) -// CRASH("gags_to_universal_icon() received an invalid path!") -// var/datum/greyscale_config/config = initial(path.greyscale_config) -// var/colors = initial(path.greyscale_colors) -// var/datum/universal_icon/entry = SSgreyscale.GetColoredIconEntryByType(config, colors, initial(path.icon_state)) -// return entry +/* +/proc/gags_to_universal_icon(atom/path) + RETURN_TYPE(/datum/universal_icon) + if(!ispath(path, /atom) || !initial(path.greyscale_config) || !initial(path.greyscale_colors)) + CRASH("gags_to_universal_icon() received an invalid path of \"[path]\"!") + var/datum/greyscale_config/config = initial(path.greyscale_config) + var/colors = initial(path.greyscale_colors) + var/datum/universal_icon/entry = SSgreyscale.GetColoredIconByTypeUniversalIcon(config, colors, path::post_init_icon_state || path::icon_state) + return entry +*/ /// Gets the relevant universal icon for an atom, when displayed in TGUI. (see: icon_state_preview) /// Supports GAGS items and colored items. -/proc/get_display_icon_for(atom/A) - if (!ispath(A, /atom)) +/proc/get_display_icon_for(atom/atom_path) + if (!ispath(atom_path, /atom)) return FALSE - var/icon_file = initial(A.icon) - var/icon_state = initial(A.icon_state) - // if(ispath(A, /obj/item)) - // var/obj/item/I = A - // if(initial(I.icon_state_preview)) - // icon_state = initial(I.icon_state_preview) - // if(initial(I.greyscale_config) && initial(I.greyscale_colors)) - // return gags_to_universal_icon(I) - return uni_icon(icon_file, icon_state, color=initial(A.color)) + var/icon_file = atom_path::icon + var/icon_state = atom_path::icon_state + /* + if(atom_path::greyscale_config && atom_path::greyscale_colors) + return gags_to_universal_icon(atom_path) + if(ispath(atom_path, /obj)) + var/obj/obj_path = atom_path + if(obj_path::icon_state_preview) + icon_state = obj_path::icon_state_preview + */ + return uni_icon(icon_file, icon_state, color=atom_path::color) + +/// getFlatIcon for [/datum/universal_icon]s +/// Still fairly slow for complex appearances due to filesystem operations. Try to avoid using it +/proc/get_flat_uni_icon(image/appearance, defdir, deficon, defstate, defblend, start = TRUE, parentcolor) + // Loop through the underlays, then overlays, sorting them into the layers list + #define PROCESS_OVERLAYS_OR_UNDERLAYS(flat, process, base_layer) \ + for (var/i in 1 to process.len) { \ + var/image/current = process[i]; \ + if (!current) { \ + continue; \ + } \ + if (current.plane != FLOAT_PLANE && current.plane != appearance.plane) { \ + continue; \ + } \ + var/current_layer = current.layer; \ + if (current_layer < 0) { \ + if (current_layer <= -1000) { \ + return flat; \ + } \ + current_layer = base_layer + appearance.layer + current_layer / 1000; \ + } \ + /* If we are using topdown rendering, chop that part off so things layer together as expected */ \ + if((current_layer >= TOPDOWN_LAYER && current_layer < EFFECTS_LAYER) || current_layer > TOPDOWN_LAYER + EFFECTS_LAYER) { \ + current_layer -= TOPDOWN_LAYER; \ + } \ + for (var/index_to_compare_to in 1 to layers.len) { \ + var/compare_to = layers[index_to_compare_to]; \ + if (current_layer < layers[compare_to]) { \ + layers.Insert(index_to_compare_to, current); \ + break; \ + } \ + } \ + layers[current] = current_layer; \ + } + + var/datum/universal_icon/flat = uni_icon('icons/blanks/32x32.dmi', "nothing") + + if(!appearance || appearance.alpha <= 0) + return flat + + if(start) + if(!deficon) + deficon = appearance.icon + if(!defstate) + defstate = appearance.icon_state + if(!defblend) + defblend = appearance.blend_mode + + var/should_display = TRUE + var/curicon = appearance.icon || deficon + var/string_curicon = "[curicon]" + var/curstate = appearance.icon_state || defstate + // Filter out 'runtime' icons (server-generated RSC cache icons) + // Write the icon to the filesystem so it can be used by iconforge + if(!isfile(curicon) || !length(string_curicon)) + var/file_path_tmp = "tmp/uni_icon-tmp-[rand(1, 999)].dmi" // this filename is temporary. + fcopy(curicon, file_path_tmp) + var/file_hash = rustg_hash_file(RUSTG_HASH_MD5, file_path_tmp) + // Use the hash as its new filename - this allows the uni_icon to be smart cached, because the filename will be consistent between runs if the content is the same + var/file_path = "tmp/uni_icon-[file_hash].dmi" + fcopy(file_path_tmp, file_path) + fdel(file_path_tmp) // delete the old one + curicon = file(file_path) + + if(!icon_exists(curicon, curstate)) + if("" in icon_states_fast(curicon)) // BYOND defaulting functionality + curstate = "" + else + should_display = FALSE + + var/curdir = (!appearance.dir || appearance.dir == SOUTH) ? defdir : appearance.dir + var/base_icon_dir //We'll use this to get the icon state to display if not null BUT NOT pass it to overlays as the dir we have + + if(should_display) + //Determines if there're directionals. + if (curdir != SOUTH) + // icon states either have 1, 4 or 8 dirs. We only have to check + // one of NORTH, EAST or WEST to know that this isn't a 1-dir icon_state since they just have SOUTH. + var/list/metadata = icon_metadata(curicon) + if(islist(metadata)) + for(var/list/state_data as anything in metadata["states"]) + var/name = state_data["name"] + if(name != curstate) + continue + var/dir_count = state_data["dirs"] + if(dir_count == 1) + base_icon_dir = SOUTH + else if(!length(icon_states(icon(curicon, curstate, NORTH)))) + base_icon_dir = SOUTH + + var/list/icon_dimensions = get_icon_dimensions(curicon) + var/icon_width = icon_dimensions["width"] + var/icon_height = icon_dimensions["height"] + if(icon_width != 32 || icon_height != 32) + flat.scale(icon_width, icon_height) + + if(!base_icon_dir) + base_icon_dir = curdir + + var/curblend = appearance.blend_mode || defblend + + + if(appearance.overlays.len || appearance.underlays.len) + // Layers will be a sorted list of icons/overlays, based on the order in which they are displayed + var/list/layers = list() + var/image/copy + if(should_display) + // Add the atom's icon itself, without pixel_x/y offsets. + copy = image(icon=curicon, icon_state=curstate, layer=appearance.layer, dir=base_icon_dir) + copy.color = appearance.color + copy.alpha = appearance.alpha + copy.blend_mode = curblend + layers[copy] = appearance.layer + + PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.underlays, 0) + PROCESS_OVERLAYS_OR_UNDERLAYS(flat, appearance.overlays, 1) + + var/datum/universal_icon/add // Icon of overlay being added + + var/list/flat_dimensions = get_icon_dimensions(flat) + var/flatX1 = 1 + var/flatX2 = flat_dimensions["width"] + var/flatY1 = 1 + var/flatY2 = flat_dimensions["height"] + + var/addX1 = 0 + var/addX2 = 0 + var/addY1 = 0 + var/addY2 = 0 + + if(appearance.color) + if(islist(appearance.color)) + flat.map_colors_inferred(appearance.color) + else + flat.blend_color(appearance.color, ICON_MULTIPLY) + + if(parentcolor && !(appearance.appearance_flags & RESET_COLOR)) + if(islist(parentcolor)) + flat.map_colors_inferred(parentcolor) + else + flat.blend_color(parentcolor, ICON_MULTIPLY) + + var/next_parentcolor = appearance.color || parentcolor + + for(var/image/layer_image as anything in layers) + if(layer_image.alpha == 0) + continue + + if(layer_image == copy && length("[layer_image.icon]")) // 'layer_image' is an /image based on the object being flattened, and isn't a 'runtime' icon. + curblend = BLEND_OVERLAY + add = uni_icon(layer_image.icon, layer_image.icon_state, base_icon_dir) + if(appearance.color) + if(islist(appearance.color)) + add.map_colors_inferred(appearance.color) + else + add.blend_color(appearance.color, ICON_MULTIPLY) + else // 'layer_image' is an appearance object. + add = get_flat_uni_icon(layer_image, curdir, curicon, curstate, curblend, FALSE, next_parentcolor) + if(!add || !length(add.icon_file)) + continue + + // Find the new dimensions of the flat icon to fit the added overlay + var/list/add_dimensions = get_icon_dimensions(add) + addX1 = min(flatX1, layer_image.pixel_x + layer_image.pixel_w + 1) + addX2 = max(flatX2, layer_image.pixel_x + layer_image.pixel_w + add_dimensions["width"]) // assuming 32x32 + addY1 = min(flatY1, layer_image.pixel_y + layer_image.pixel_z + 1) + addY2 = max(flatY2, layer_image.pixel_y + layer_image.pixel_z + add_dimensions["height"]) + + if ( + addX1 != flatX1 \ + && addX2 != flatX2 \ + && addY1 != flatY1 \ + && addY2 != flatY2 \ + ) + // Resize the flattened icon so the new icon fits + flat.crop( + addX1 - flatX1 + 1, + addY1 - flatY1 + 1, + addX2 - flatX1 + 1, + addY2 - flatY1 + 1 + ) + + flatX1 = addX1 + flatX2 = addY1 + flatY1 = addX2 + flatY2 = addY2 + + // Blend the overlay into the flattened icon + flat.blend_icon(add, blendMode2iconMode(curblend), layer_image.pixel_x + layer_image.pixel_w + 2 - flatX1, layer_image.pixel_y + layer_image.pixel_z + 2 - flatY1) + + if(appearance.alpha < 255) + flat.blend_color(rgb(255, 255, 255, appearance.alpha), ICON_MULTIPLY) + + return flat + + else if(should_display) // There's no overlays. + var/datum/universal_icon/final_icon = uni_icon(curicon, curstate, base_icon_dir) + + if (appearance.alpha < 255) + final_icon.blend_color(rgb(255,255,255, appearance.alpha), ICON_MULTIPLY) + + if (appearance.color) + if (islist(appearance.color)) + final_icon.map_colors_inferred(appearance.color) + else + final_icon.blend_color(appearance.color, ICON_MULTIPLY) + + return final_icon + + #undef PROCESS_OVERLAYS_OR_UNDERLAYS diff --git a/code/modules/asset_cache/transports/asset_transport.dm b/code/modules/asset_cache/transports/asset_transport.dm index e320742235..77daee2a60 100644 --- a/code/modules/asset_cache/transports/asset_transport.dm +++ b/code/modules/asset_cache/transports/asset_transport.dm @@ -85,7 +85,7 @@ /// asset_list - A list of asset filenames to be sent to the client. Can optionally be assoicated with the asset's asset_cache_item datum. /// Returns TRUE if any assets were sent. /datum/asset_transport/proc/send_assets(client/client, list/asset_list) -#if defined(UNIT_TEST) +#if defined(UNIT_TESTS) return #endif if (!istype(client)) diff --git a/code/modules/awaymissions/zlevel.dm b/code/modules/awaymissions/zlevel.dm index 1dc4bf6e8d..75e76c2923 100644 --- a/code/modules/awaymissions/zlevel.dm +++ b/code/modules/awaymissions/zlevel.dm @@ -1,5 +1,5 @@ /proc/createRandomZlevel() - #ifdef UNIT_TEST + #ifdef UNIT_TESTS return #endif if(GLOB.awaydestinations.len) //crude, but it saves another var! //VOREStation Edit - No loading away missions during CI testing diff --git a/code/modules/client/client procs.dm b/code/modules/client/client procs.dm index 801dc4c672..07c2275f16 100644 --- a/code/modules/client/client procs.dm +++ b/code/modules/client/client procs.dm @@ -206,6 +206,14 @@ /client/proc/_Topic(datum/hsrc, href, list/href_list) return hsrc.Topic(href, href_list) +/client/proc/is_localhost() + var/static/localhost_addresses = list( + "127.0.0.1", + "::1", + null, + ) + return address in localhost_addresses + //This stops files larger than UPLOAD_LIMIT being sent from client to server via input(), client.Import() etc. /client/AllowUpload(filename, filelength) if(filelength > UPLOAD_LIMIT) diff --git a/code/modules/client/preference_setup/general/01_basic.dm b/code/modules/client/preference_setup/general/01_basic.dm index 899946f2d3..a57a147f59 100644 --- a/code/modules/client/preference_setup/general/01_basic.dm +++ b/code/modules/client/preference_setup/general/01_basic.dm @@ -49,9 +49,9 @@ var/firstspace = findtext(pref.real_name, " ") var/name_length = length(pref.real_name) if(!firstspace) //we need a surname - pref.real_name += " [pick(last_names)]" + pref.real_name += " [pick(GLOB.last_names)]" else if(firstspace == name_length) - pref.real_name += "[pick(last_names)]" + pref.real_name += "[pick(GLOB.last_names)]" character.real_name = pref.real_name character.name = character.real_name diff --git a/code/modules/client/preferences.dm b/code/modules/client/preferences.dm index b04b0d816f..1ba6b76bdc 100644 --- a/code/modules/client/preferences.dm +++ b/code/modules/client/preferences.dm @@ -428,9 +428,9 @@ var/list/preferences_datums = list() var/firstspace = findtext(real_name, " ") var/name_length = length(real_name) if(!firstspace) //we need a surname - real_name += " [pick(last_names)]" + real_name += " [pick(GLOB.last_names)]" else if(firstspace == name_length) - real_name += "[pick(last_names)]" + real_name += "[pick(GLOB.last_names)]" character.real_name = real_name character.name = character.real_name if(character.dna) diff --git a/code/modules/client/preferences/types/game/ui.dm b/code/modules/client/preferences/types/game/ui.dm index 81c24e3f91..27c140b537 100644 --- a/code/modules/client/preferences/types/game/ui.dm +++ b/code/modules/client/preferences/types/game/ui.dm @@ -127,3 +127,9 @@ INVOKE_ASYNC(client, TYPE_VERB_REF(/client, refresh_tgui)) client.tgui_say?.load() + +/// Enables flashing the window in your task tray for important events +/datum/preference/toggle/window_flashing + category = PREFERENCE_CATEGORY_GAME_PREFERENCES + savefile_key = "windowflashing" + savefile_identifier = PREFERENCE_PLAYER diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm index 4e2585a1aa..c5154e023a 100644 --- a/code/modules/client/verbs/suicide.dm +++ b/code/modules/client/verbs/suicide.dm @@ -7,7 +7,7 @@ to_chat(src, "You're already dead!") return - if (!ticker) + if (!SSticker) to_chat(src, "You can't commit suicide before the game starts!") return @@ -21,7 +21,7 @@ to_chat(src, "You're already dead!") return - if (!ticker) + if (!SSticker) to_chat(src, "You can't commit suicide before the game starts!") return diff --git a/code/modules/clothing/accessories/accessory_vr.dm b/code/modules/clothing/accessories/accessory_vr.dm index 158a3c6f6e..4c87c7fbc2 100644 --- a/code/modules/clothing/accessories/accessory_vr.dm +++ b/code/modules/clothing/accessories/accessory_vr.dm @@ -136,17 +136,17 @@ /obj/item/clothing/accessory/collar/shock/Initialize(mapload) . = ..() - radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) // Makes it so you don't need to change the frequency off of default for it to work. + radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) // Makes it so you don't need to change the frequency off of default for it to work. /obj/item/clothing/accessory/collar/shock/Destroy() //Clean up your toys when you're done. - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) radio_connection = null //Don't delete this, this is a shared object. return ..() /obj/item/clothing/accessory/collar/shock/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) + radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) /obj/item/clothing/accessory/collar/shock/attack_self(mob/user as mob, flag1) if(!ishuman(user)) diff --git a/code/modules/debugging/debugger.dm b/code/modules/debugging/debugger.dm new file mode 100644 index 0000000000..6ae3939959 --- /dev/null +++ b/code/modules/debugging/debugger.dm @@ -0,0 +1,54 @@ +/// The debugger instance. +/// This is a GLOBAL_REAL because it initializes before the MC or GLOB. +/// Really only used to check to see if the debugger is enabled or not, +/// and to separate debugger-related code into its own thing. +GLOBAL_REAL(Debugger, /datum/debugger) + +/datum/debugger + /// Is the debugger enabled? + VAR_FINAL/enabled = FALSE + /// The error text, if initializing the debugger errored. + VAR_FINAL/error + /// The path to the auxtools debug DLL, if it sets. + /// Defaults to the environmental variable AUXTOOLS_DEBUG_DLL. + VAR_FINAL/dll_path + +/datum/debugger/New(dll_path) + if(!isnull(Debugger)) + CRASH("Attempted to initialize /datum/debugger when global.Debugger is already set!") + Debugger = src +#ifndef OPENDREAM_REAL + src.dll_path = dll_path || world.GetConfig("env", "AUXTOOLS_DEBUG_DLL") + enable() +#endif + +/datum/debugger/Destroy() +#ifndef OPENDREAM_REAL + if(enabled) + call_ext(dll_path, "auxtools_shutdown")() +#endif + return ..() + +/// Attempt to enable the debugger. +/datum/debugger/proc/enable() +#ifndef OPENDREAM_REAL + if(enabled) + CRASH("Attempted to enable debugger while its already enabled, somehow.") + if(!dll_path) + return FALSE + var/result = call_ext(dll_path, "auxtools_init")() + if(result != "SUCCESS") + error = result + return FALSE + enable_debugging() + enabled = TRUE + return TRUE +#else + return FALSE +#endif + +/datum/debugger/vv_edit_var(var_name, var_value) + return FALSE // no. + +/datum/debugger/CanProcCall(procname) + return FALSE // double no. diff --git a/code/modules/debugging/tracy.dm b/code/modules/debugging/tracy.dm new file mode 100644 index 0000000000..ac661466e9 --- /dev/null +++ b/code/modules/debugging/tracy.dm @@ -0,0 +1,65 @@ +/// The byond-tracy instance. +/// This is a GLOBAL_REAL because it is the VERY FIRST THING to initialize, even before the MC or GLOB. +GLOBAL_REAL(Tracy, /datum/tracy) + +/datum/tracy + /// Is byond-tracy enabled and running? + VAR_FINAL/enabled = FALSE + /// The error text, if initializing byond-tracy errored. + VAR_FINAL/error + /// A description of what / who enabled byond-tracy. + VAR_FINAL/init_reason + /// A path to the file containing the output trace, if any. + VAR_FINAL/trace_path + +/datum/tracy/New() + if(!isnull(Tracy)) + CRASH("Attempted to initialize /datum/tracy when global.Tracy is already set!") + Tracy = src + +/datum/tracy/Destroy() +#ifndef OPENDREAM_REAL + if(enabled) + call_ext(TRACY_DLL_PATH, "destroy")() +#endif + return ..() + +/// Tries to initialize byond-tracy. +/datum/tracy/proc/enable(init_reason) +#ifndef OPENDREAM_REAL + if(enabled) + return TRUE + src.init_reason = init_reason + if(!fexists(TRACY_DLL_PATH)) + error = "[TRACY_DLL_PATH] not found" + SEND_TEXT(world.log, "Error initializing byond-tracy: [error]") + return FALSE + + var/init_result = call_ext(TRACY_DLL_PATH, "init")("block") + if(length(init_result) != 0 && init_result[1] == ".") // if first character is ., then it returned the output filename + SEND_TEXT(world.log, "byond-tracy initialized (logfile: [init_result])") + enabled = TRUE + trace_path = init_result + return TRUE + else if(init_result == "already initialized") // not gonna question it. + enabled = TRUE + SEND_TEXT(world.log, "byond-tracy already initialized ([trace_path ? "logfile: [trace_path]" : "no logfile"])") + return TRUE + else if(init_result != "0") + error = init_result + SEND_TEXT(world.log, "Error initializing byond-tracy: [init_result]") + return FALSE + else + enabled = TRUE + SEND_TEXT(world.log, "byond-tracy initialized (no logfile)") + return TRUE +#else + error = "OpenDream not supported" + return FALSE +#endif + +/datum/tracy/vv_edit_var(var_name, var_value) + return FALSE // no. + +/datum/tracy/CanProcCall(procname) + return FALSE // double no. diff --git a/code/modules/events/money_spam.dm b/code/modules/events/money_spam.dm index c499a80547..cb05dfcfc9 100644 --- a/code/modules/events/money_spam.dm +++ b/code/modules/events/money_spam.dm @@ -60,7 +60,7 @@ if(2) sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides") message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\ - "If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\ + "If you will write to me on my email [pick(GLOB.first_names_female)]@[pick(GLOB.last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\ "I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\ "You have (1) new message!",\ "You have (2) new profile views!") diff --git a/code/modules/events/supply_demand_vr.dm b/code/modules/events/supply_demand_vr.dm index d5ccca4fe9..291ab67f8d 100644 --- a/code/modules/events/supply_demand_vr.dm +++ b/code/modules/events/supply_demand_vr.dm @@ -75,7 +75,7 @@ // Success! SSsupply.points += 100 * severity var/msg = "Great work! With those items you delivered our inventory levels all match up. " - msg += "[capitalize(pick(first_names_female))] from accounting will have nothing to complain about. " + msg += "[capitalize(pick(GLOB.first_names_female))] from accounting will have nothing to complain about. " msg += "I think you'll find a little something in your supply account." command_announcement.Announce(msg, my_department) else diff --git a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm index b3df2bde62..65186bffb6 100644 --- a/code/modules/gamemaster/event2/events/everyone/pda_spam.dm +++ b/code/modules/gamemaster/event2/events/everyone/pda_spam.dm @@ -88,7 +88,7 @@ if(2) sender = pick(300;"QuickDatingSystem",200;"Find your russian bride",50;"Tajaran beauties are waiting",50;"Find your secret skrell crush",50;"Beautiful unathi brides") message = pick("Your profile caught my attention and I wanted to write and say hello (QuickDating).",\ - "If you will write to me on my email [pick(first_names_female)]@[pick(last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\ + "If you will write to me on my email [pick(GLOB.first_names_female)]@[pick(GLOB.last_names)].[pick("ru","ck","tj","ur","nt")] I shall necessarily send you a photo (QuickDating).",\ "I want that we write each other and I hope, that you will like my profile and you will answer me (QuickDating).",\ "You have (1) new message!",\ "You have (2) new profile views!") diff --git a/code/modules/gamemaster/event2/events/security/stowaway.dm b/code/modules/gamemaster/event2/events/security/stowaway.dm index cdc7344c0f..fdbcb70e5a 100644 --- a/code/modules/gamemaster/event2/events/security/stowaway.dm +++ b/code/modules/gamemaster/event2/events/security/stowaway.dm @@ -23,7 +23,7 @@ event_type = /datum/event2/event/ghost_pod_spawner/stowaway/infiltrator /datum/event2/meta/stowaway/get_weight() - if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended) + if(istype(SSticker.mode, /datum/game_mode/extended) && !safe_for_extended) return 0 var/security = GLOB.metric.count_people_in_department(DEPARTMENT_SECURITY) diff --git a/code/modules/gamemaster/event2/events/security/swarm_boarder.dm b/code/modules/gamemaster/event2/events/security/swarm_boarder.dm index a1e07e21b7..68dd152151 100644 --- a/code/modules/gamemaster/event2/events/security/swarm_boarder.dm +++ b/code/modules/gamemaster/event2/events/security/swarm_boarder.dm @@ -9,7 +9,7 @@ var/safe_for_extended = FALSE /datum/event2/meta/swarm_boarder/get_weight() - if(istype(ticker.mode, /datum/game_mode/extended) && !safe_for_extended) + if(istype(SSticker.mode, /datum/game_mode/extended) && !safe_for_extended) return 0 var/security = GLOB.metric.count_people_in_department(DEPARTMENT_SECURITY) diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm index 3d6ade30dc..4efca25cf8 100644 --- a/code/modules/integrated_electronics/subtypes/input.dm +++ b/code/modules/integrated_electronics/subtypes/input.dm @@ -424,8 +424,8 @@ addtimer(CALLBACK(src, PROC_REF(set_frequency), frequency), 40) /obj/item/integrated_circuit/input/signaler/Destroy() - if(radio_controller) - radio_controller.remove_object(src,frequency) + if(SSradio) + SSradio.remove_object(src,frequency) frequency = 0 . = ..() @@ -452,13 +452,13 @@ /obj/item/integrated_circuit/input/signaler/proc/set_frequency(new_frequency) if(!frequency) return - if(!radio_controller) + if(!SSradio) sleep(20) - if(!radio_controller) + if(!SSradio) return - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency, RADIO_CHAT) + radio_connection = SSradio.add_object(src, frequency, RADIO_CHAT) /obj/item/integrated_circuit/input/signaler/receive_signal(datum/signal/signal) var/new_code = get_pin_data(IC_INPUT, 2) diff --git a/code/modules/lighting/lighting_overlay.dm b/code/modules/lighting/lighting_overlay.dm index 7ba7029a0d..63ec549f6c 100644 --- a/code/modules/lighting/lighting_overlay.dm +++ b/code/modules/lighting/lighting_overlay.dm @@ -11,7 +11,7 @@ var/turf/affected_turf /datum/lighting_object/New(turf/source) - if(!SSlighting.subsystem_initialized) + if(!SSlighting.initialized) stack_trace("lighting_object created before SSlighting up!") return if(!isturf(source)) diff --git a/code/modules/lighting/lighting_turf.dm b/code/modules/lighting/lighting_turf.dm index 3e1219f617..8a2e060023 100644 --- a/code/modules/lighting/lighting_turf.dm +++ b/code/modules/lighting/lighting_turf.dm @@ -127,7 +127,7 @@ /turf/proc/change_area(area/old_area, area/new_area) - if(SSlighting.subsystem_initialized) + if(SSlighting.initialized) if (new_area.dynamic_lighting != old_area.dynamic_lighting) if (new_area.dynamic_lighting) lighting_build_overlay() diff --git a/code/modules/maps/tg/map_template_vr.dm b/code/modules/maps/tg/map_template_vr.dm index 67bcd852b6..92acae407f 100644 --- a/code/modules/maps/tg/map_template_vr.dm +++ b/code/modules/maps/tg/map_template_vr.dm @@ -10,11 +10,11 @@ GLOBAL_LIST_EMPTY(map_templates_loaded) /datum/map_template/proc/on_map_loaded(z) //We missed air init! - if(SSair.subsystem_initialized) + if(SSair.initialized) for(var/turf/simulated/T in block(locate(1,1,z), locate(world.maxx, world.maxy, z))) T.update_air_properties() //We missed sslighting init! - if(SSlighting.subsystem_initialized) + if(SSlighting.initialized) for(var/Trf in block(locate(1,1,z), locate(world.maxx, world.maxy, z))) var/turf/T = Trf //faster than implicit istype with typed for loop T.lighting_build_overlay() diff --git a/code/modules/mining/mine_turfs.dm b/code/modules/mining/mine_turfs.dm index 3357d9a035..f4f54277d6 100644 --- a/code/modules/mining/mine_turfs.dm +++ b/code/modules/mining/mine_turfs.dm @@ -170,7 +170,7 @@ var/list/mining_overlay_cache = list() /turf/simulated/mineral/proc/update_general() recalculate_directional_opacity() - if(ticker && ticker.current_state == GAME_STATE_PLAYING) + if(SSticker && SSticker.current_state == GAME_STATE_PLAYING) reconsider_lights() if(SSair) SSair.mark_for_update(src) diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm index 66f63a6fca..96c4b2240a 100644 --- a/code/modules/mob/dead/observer/observer.dm +++ b/code/modules/mob/dead/observer/observer.dm @@ -74,9 +74,9 @@ name = M.real_name else if(gender == MALE) - name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names)) + name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) else - name = capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names)) + name = capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) mind = M.mind //we don't transfer the mind but we keep a reference to it. @@ -96,7 +96,7 @@ to_chat(src, span_danger("Could not locate an observer spawn point. Use the Teleport verb to jump to the station map.")) if(!name) //To prevent nameless ghosts - name = capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names)) + name = capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) real_name = name animate(src, pixel_y = 2, time = 10, loop = -1) animate(pixel_y = default_pixel_y, time = 10, loop = -1) @@ -683,7 +683,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp return 0 //something is terribly wrong var/ghosts_can_write - if(ticker.mode.name == "cult") + if(SSticker.mode.name == "cult") if(cult.current_antagonists.len > CONFIG_GET(number/cult_ghostwriter_req_cultists)) ghosts_can_write = 1 diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm index 43b4e00d64..6ba8bbc4c2 100644 --- a/code/modules/mob/death.dm +++ b/code/modules/mob/death.dm @@ -116,8 +116,8 @@ handle_regular_hud_updates() handle_vision() - if(ticker && ticker.mode) - ticker.mode.check_win() + if(SSticker && SSticker.mode) + SSticker.mode.check_win() return 1 diff --git a/code/modules/mob/freelook/ai/update_triggers.dm b/code/modules/mob/freelook/ai/update_triggers.dm index 99a8a83734..343abe9feb 100644 --- a/code/modules/mob/freelook/ai/update_triggers.dm +++ b/code/modules/mob/freelook/ai/update_triggers.dm @@ -46,7 +46,7 @@ /obj/machinery/camera/Initialize(mapload) . = ..() //Camera must be added to global list of all cameras no matter what... - if(cameranet.cameras_unsorted || !ticker) + if(cameranet.cameras_unsorted || !SSticker) cameranet.cameras += src cameranet.cameras_unsorted = 1 else diff --git a/code/modules/mob/freelook/update_triggers.dm b/code/modules/mob/freelook/update_triggers.dm index f73e3b8d13..aa4cfba60e 100644 --- a/code/modules/mob/freelook/update_triggers.dm +++ b/code/modules/mob/freelook/update_triggers.dm @@ -3,7 +3,7 @@ // TURFS /proc/updateVisibility(atom/A, var/opacity_check = 1) - if(ticker) + if(SSticker) for(var/datum/visualnet/VN in visual_nets) VN.updateVisibility(A, opacity_check) diff --git a/code/modules/mob/freelook/visualnet.dm b/code/modules/mob/freelook/visualnet.dm index 10f0a6c809..d7b0d39c33 100644 --- a/code/modules/mob/freelook/visualnet.dm +++ b/code/modules/mob/freelook/visualnet.dm @@ -92,7 +92,7 @@ /datum/visualnet/proc/updateVisibility(atom/A, var/opacity_check = 1) - if(!ticker || (opacity_check && !A.opacity)) + if(!SSticker || (opacity_check && !A.opacity)) return majorChunkChange(A, 2) diff --git a/code/modules/mob/language/language.dm b/code/modules/mob/language/language.dm index afbb6380f7..7aa900d9df 100644 --- a/code/modules/mob/language/language.dm +++ b/code/modules/mob/language/language.dm @@ -26,9 +26,9 @@ /datum/language/proc/get_random_name(var/gender, name_count=2, syllable_count=4, syllable_divisor=2) if(!syllables || !syllables.len) if(gender==FEMALE) - return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) else - return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) var/full_name = "" var/new_name = "" diff --git a/code/modules/mob/language/station.dm b/code/modules/mob/language/station.dm index 575799bfab..685b08463b 100644 --- a/code/modules/mob/language/station.dm +++ b/code/modules/mob/language/station.dm @@ -127,8 +127,8 @@ syllables = list("qr","qrr","xuq","qil","quum","xuqm","vol","xrim","zaoo","qu-uu","qix","qoo","zix") /datum/language/skrell/get_random_name(var/gender) - var/list/first_names = file2list('config/names/first_name_skrell.txt') - var/list/last_names = file2list('config/names/last_name_skrell.txt') + var/list/first_names = file2list('strings/names/first_name_skrell.txt') + var/list/last_names = file2list('strings/names/last_name_skrell.txt') return "[pick(first_names)] [pick(last_names)]" /datum/language/human @@ -152,9 +152,9 @@ /datum/language/human/get_random_name(var/gender) if (prob(80)) if(gender==FEMALE) - return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) else - return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) else return ..() @@ -174,7 +174,7 @@ if(prob(70)) return "[pick(list("PBU","HIU","SINA","ARMA","OSI"))]-[rand(100, 999)]" else - return pick(ai_names) + return pick(GLOB.ai_names) /datum/language/teshari name = LANGUAGE_SCHECHI diff --git a/code/modules/mob/living/carbon/human/species/species_getters.dm b/code/modules/mob/living/carbon/human/species/species_getters.dm index 03d0575307..3ec5e9bc5b 100644 --- a/code/modules/mob/living/carbon/human/species/species_getters.dm +++ b/code/modules/mob/living/carbon/human/species/species_getters.dm @@ -117,11 +117,11 @@ /datum/species/proc/get_random_name(var/gender) if(!name_language) if(gender == FEMALE) - return capitalize(pick(first_names_female)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) else if(gender == MALE) - return capitalize(pick(first_names_male)) + " " + capitalize(pick(last_names)) + return capitalize(pick(GLOB.first_names_male)) + " " + capitalize(pick(GLOB.last_names)) else - return capitalize(prob(50) ? pick(first_names_male) : pick(first_names_female)) + " " + capitalize(pick(last_names)) + return capitalize(prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)) + " " + capitalize(pick(GLOB.last_names)) var/datum/language/species_language = GLOB.all_languages[name_language] if(!species_language) diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm index a901fe24a7..0cdabcfaca 100644 --- a/code/modules/mob/living/living.dm +++ b/code/modules/mob/living/living.dm @@ -932,8 +932,8 @@ //check for nuke disks if(client && stat != DEAD) //if they are clientless and dead don't bother, the parent will treat them as any other container - if(ticker && istype(ticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear - var/datum/game_mode/nuclear/G = ticker.mode + if(SSticker && istype(SSticker.mode, /datum/game_mode/nuclear)) //only really care if the game mode is nuclear + var/datum/game_mode/nuclear/G = SSticker.mode if(G.check_mob(src)) if(x <= TRANSITIONEDGE) inertia_dir = 4 @@ -1429,7 +1429,7 @@ "}) /mob/living/update_gravity(has_gravity) - if(!ticker) + if(!SSticker) return if(has_gravity) clear_alert("weightless") diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index 1047b13e83..646bcfc4ad 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -113,11 +113,11 @@ var/list/ai_verbs_default = list( announcement.announcement_type = "A.I. Announcement" announcement.newscast = 1 - var/list/possibleNames = ai_names + var/list/possibleNames = GLOB.ai_names var/pickedName = null while(!pickedName) - pickedName = pick(ai_names) + pickedName = pick(GLOB.ai_names) for (var/mob/living/silicon/ai/A in GLOB.mob_list) if (A.real_name == pickedName && possibleNames.len > 1) //fixing the theoretically possible infinite loop possibleNames -= pickedName diff --git a/code/modules/mob/living/silicon/ai/latejoin.dm b/code/modules/mob/living/silicon/ai/latejoin.dm index 4524a58e4e..654178a1cb 100644 --- a/code/modules/mob/living/silicon/ai/latejoin.dm +++ b/code/modules/mob/living/silicon/ai/latejoin.dm @@ -15,7 +15,7 @@ GLOBAL_LIST_EMPTY(empty_playable_ai_cores) set category = "OOC.Game" set desc = "Enter intelligence storage. This is functionally equivalent to cryo or robotic storage, freeing up your job slot." - if(ticker && ticker.mode && ticker.mode.name == "AI malfunction") + if(SSticker && SSticker.mode && SSticker.mode.name == "AI malfunction") to_chat(src, span_danger("You cannot use this verb in malfunction. If you need to leave, please adminhelp.")) return diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm index f2f23f299c..1a6603c78a 100644 --- a/code/modules/mob/living/silicon/pai/recruit.dm +++ b/code/modules/mob/living/silicon/pai/recruit.dm @@ -40,7 +40,7 @@ var/datum/paiController/paiController // Global handler for pAI candidates GLOB.paikeys |= pai.ckey card.setPersonality(pai) if(!candidate.name) - pai.SetName(pick(ninja_names)) + pai.SetName(pick(GLOB.ninja_names)) else pai.SetName(candidate.name) if(candidate.description) diff --git a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm index 201d67492b..bbf1f124cb 100644 --- a/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm +++ b/code/modules/mob/living/silicon/robot/drone/drone_manufacturer.dm @@ -50,7 +50,7 @@ /obj/machinery/drone_fabricator/process() - if(ticker.current_state < GAME_STATE_PLAYING) + if(SSticker.current_state < GAME_STATE_PLAYING) return if(stat & NOPOWER || !produce_drones) diff --git a/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm b/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm index 4319b9a7fc..1e941602c4 100644 --- a/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm +++ b/code/modules/mob/living/silicon/robot/subtypes/thinktank/thinktank_interactions.dm @@ -34,11 +34,11 @@ /mob/living/silicon/robot/platform/attack_ghost(mob/observer/dead/user) - if(client || key || stat == DEAD || !ticker || !ticker.mode) + if(client || key || stat == DEAD || !SSticker || !SSticker.mode) return ..() var/confirm = tgui_alert(user, "Do you wish to take control of \the [src]?", "Platform Control", list("No", "Yes")) - if(confirm != "Yes" || QDELETED(src) || client || key || stat == DEAD || !ticker || !ticker.mode) + if(confirm != "Yes" || QDELETED(src) || client || key || stat == DEAD || !SSticker || !SSticker.mode) return ..() if(jobban_isbanned(user, "Robot")) diff --git a/code/modules/mob/logout.dm b/code/modules/mob/logout.dm index 5b801c5056..c1a1a9ca81 100644 --- a/code/modules/mob/logout.dm +++ b/code/modules/mob/logout.dm @@ -9,7 +9,7 @@ var/datum/admins/is_admin = GLOB.admin_datums[src.ckey] if(is_admin && is_admin.check_for_rights(R_HOLDER)) message_admins("Staff logout: [key_name(src)]") // Staff logout notice displays no matter what - if (ticker && ticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. + if (SSticker && SSticker.current_state == GAME_STATE_PLAYING) //Only report this stuff if we are currently playing. var/admins_number = GLOB.admins.len if(admins_number == 0) //Apparently the admin logging out is no longer an admin at this point, so we have to check this towards 0 and not towards 1. Awell. diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm index 7dc2adb6d7..ce27989b5f 100644 --- a/code/modules/mob/mob.dm +++ b/code/modules/mob/mob.dm @@ -340,7 +340,7 @@ // Try to figure out what time to use // Special cases, can never respawn - if(ticker?.mode?.deny_respawn) + if(SSticker?.mode?.deny_respawn) time = -1 else if(!CONFIG_GET(flag/abandon_allowed)) time = -1 @@ -348,7 +348,7 @@ time = -1 // Special case for observing before game start - else if(ticker?.current_state <= GAME_STATE_SETTING_UP) + else if(SSticker?.current_state <= GAME_STATE_SETTING_UP) time = 1 MINUTE // Wasn't given a time, use the config time @@ -377,7 +377,7 @@ to_chat(src, span_boldnotice("You are already in the lobby!")) return - if(stat != DEAD || !ticker) + if(stat != DEAD || !SSticker) to_chat(src, span_boldnotice("You must be dead to use this!")) return diff --git a/code/modules/mob/mob_movement.dm b/code/modules/mob/mob_movement.dm index bf4f372c97..954738d9a6 100644 --- a/code/modules/mob/mob_movement.dm +++ b/code/modules/mob/mob_movement.dm @@ -146,6 +146,9 @@ // Used many times below, faster reference. var/atom/loc = my_mob.loc + if(HAS_TRAIT(my_mob, TRAIT_NO_TRANSFORM)) + return FALSE //This is sorta the goto stop mobs from moving trait + // We're controlling an object which is when admins possess an object. if(my_mob.control_object) Move_object(direct) diff --git a/code/modules/mob/new_player/lobby_browser.dm b/code/modules/mob/new_player/lobby_browser.dm index 40aeb4d61b..003443c8d2 100644 --- a/code/modules/mob/new_player/lobby_browser.dm +++ b/code/modules/mob/new_player/lobby_browser.dm @@ -43,7 +43,7 @@ data["server_name"] = displayed_name data["map"] = using_map.full_name data["station_time"] = stationtime2text() - data["display_loading"] = SSticker.current_state == GAME_STATE_INIT + data["display_loading"] = SSticker.current_state == GAME_STATE_STARTUP data["round_start"] = !SSticker.mode || SSticker.current_state <= GAME_STATE_PREGAME data["round_time"] = roundduration2text() data["ready"] = ready @@ -82,7 +82,7 @@ ViewManifest() return TRUE if("late_join") - if(!ticker || ticker.current_state != GAME_STATE_PLAYING) + if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING) to_chat(usr, span_red("The round is either not ready, or has already finished...")) return TRUE @@ -97,7 +97,7 @@ if("observe") if(QDELETED(src)) return FALSE - if(!SSticker || SSticker.current_state == GAME_STATE_INIT) + if(!SSticker || SSticker.current_state == GAME_STATE_STARTUP) to_chat(src, span_warning("The game is still setting up, please try again later.")) return TRUE if(tgui_alert(src,"Are you sure you wish to observe? If you do, make sure to not use any knowledge gained from observing if you decide to join later.","Observe Round?",list("Yes","No")) == "Yes") @@ -160,7 +160,7 @@ client.changes() return TRUE if("keyboard") - if(!SSsounds.subsystem_initialized) + if(!SSsounds.initialized) return playsound_local(ui.user, get_sfx("keyboard"), vol = 20) diff --git a/code/modules/mob/new_player/new_player.dm b/code/modules/mob/new_player/new_player.dm index df64e99b91..96fb89c228 100644 --- a/code/modules/mob/new_player/new_player.dm +++ b/code/modules/mob/new_player/new_player.dm @@ -42,11 +42,11 @@ // if(SSvote.mode) // . += "Vote: [capitalize(SSvote.mode)] Time Left: [SSvote.time_remaining] s" - if(SSticker.current_state == GAME_STATE_INIT) + if(SSticker.current_state == GAME_STATE_STARTUP) . += "Time To Start: Server Initializing" else if(SSticker.current_state == GAME_STATE_PREGAME) - . += "Time To Start: [round(SSticker.pregame_timeleft,1)][GLOB.round_progressing ? "" : " (DELAYED)"]" + . += "Time To Start: [round(SSticker.timeLeft, 1)][GLOB.round_progressing ? "" : " (DELAYED)"]" . += "Players: [totalPlayers]" . += "Players Ready: [totalPlayersReady]" totalPlayers = 0 @@ -223,7 +223,7 @@ /mob/new_player/proc/AttemptLateSpawn(rank,var/spawning_at) if (src != usr) return 0 - if(!ticker || ticker.current_state != GAME_STATE_PLAYING) + if(!SSticker || SSticker.current_state != GAME_STATE_PLAYING) to_chat(src, span_red("The round is either not ready, or has already finished...")) return 0 if(!CONFIG_GET(flag/enter_allowed)) @@ -297,7 +297,7 @@ character = character.AIize(move = FALSE) // Dupe of code in /datum/controller/subsystem/ticker/proc/create_characters() for non-latespawn, unify? AnnounceCyborg(character, rank, "has been transferred to the empty core in \the [character.loc.loc]") - ticker.mode.latespawn(character) + SSticker.mode.latespawn(character) qdel(C) //Deletes empty core (really?) qdel(src) //Deletes new_player @@ -308,22 +308,22 @@ character.buckled.loc = character.loc character.buckled.set_dir(character.dir) - ticker.mode.latespawn(character) + SSticker.mode.latespawn(character) if(rank == JOB_OUTSIDER) log_and_message_admins("has joined the round as non-crew. (JMP)",character) if(!(J.mob_type & JOB_SILICON)) - ticker.minds += character.mind + SSticker.minds += character.mind else if(rank == JOB_ANOMALY) log_and_message_admins("has joined the round as anomaly. (JMP)",character) if(!(J.mob_type & JOB_SILICON)) - ticker.minds += character.mind + SSticker.minds += character.mind else if(J.mob_type & JOB_SILICON) AnnounceCyborg(character, rank, join_message, announce_channel, character.z) else AnnounceArrival(character, rank, join_message, announce_channel, character.z) GLOB.data_core.manifest_inject(character) - ticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn + SSticker.minds += character.mind//Cyborgs and AIs handle this in the transform proc. //TODO!!!!! ~Carn if(ishuman(character)) if(character.client.prefs.auto_backup_implant) var/obj/item/implant/backup/imp = new(src) @@ -370,7 +370,7 @@ qdel(src) // Delete new_player mob /mob/new_player/proc/AnnounceCyborg(var/mob/living/character, var/rank, var/join_message, var/channel, var/zlevel) - if (ticker.current_state == GAME_STATE_PLAYING) + if (SSticker.current_state == GAME_STATE_PLAYING) var/list/zlevels = zlevel ? using_map.get_map_levels(zlevel, TRUE, om_range = DEFAULT_OVERMAP_RANGE) : null if(character.mind.role_alt_title) rank = character.mind.role_alt_title @@ -402,7 +402,7 @@ if(!new_character) new_character = new(T) - if(ticker.random_players) + if(CONFIG_GET(flag/force_random_names)) new_character.gender = pick(MALE, FEMALE) client.prefs.real_name = random_name(new_character.gender) else diff --git a/code/modules/overmap/README.dm b/code/modules/overmap/README.md similarity index 77% rename from code/modules/overmap/README.dm rename to code/modules/overmap/README.md index 7681891059..7929103462 100644 --- a/code/modules/overmap/README.dm +++ b/code/modules/overmap/README.md @@ -1,13 +1,11 @@ -/* The overmap system allows adding new maps to the big 'galaxy' map. Idea is that new sectors can be added by just ticking in new maps and recompiling. Not real hot-plugging, but still pretty modular. It uses the fact that all ticked in .dme maps are melded together into one as different zlevels. Metaobjects are used to make it not affected by map order in .dme and carry some additional info. -************************************************************* -Metaobject -************************************************************* +## Metaobject + /obj/effect/mapinfo, sectors.dm Used to build overmap in beginning, has basic information needed to create overmap objects and make shuttles work. Its name and icon (if non-standard) vars will be applied to resulting overmap object. @@ -19,9 +17,9 @@ Has two important vars: Object could be placed anywhere on zlevel. Should only be placed on zlevel that should appear on overmap as a separate entitety. Right after creation it sends itself to nullspace and creates an overmap object, corresponding to this zlevel. -************************************************************* -Overmap object -************************************************************* + +## Overmap object + /obj/effect/map, sectors.dm Represents a zlevel on the overmap. Spawned by metaobjects at the startup. var/area/shuttle/shuttle_landing - keeps a reference to the area of where inbound shuttles should land @@ -33,9 +31,9 @@ Remember to call ..() in children, it updates ship's current sector. subtype /ship of this object represents spacefaring vessels. It has 'current_sector' var that keeps refernce to, well, sector ship currently in. -************************************************************* -Helm console -************************************************************* + +## Helm console + /obj/machinery/computer/helm, helm.dm On creation console seeks a ship overmap object corresponding to this zlevel and links it. Clicking with empty hand on it starts steering, Cancel-Camera-View stops it. @@ -43,9 +41,9 @@ Helm console relays movement of mob to the linked overmap object. Helm console currently has no interface. All travel happens instanceously too. Sector shuttles are not supported currently, only ship shuttles. -************************************************************* -Exploration shuttle terminal -************************************************************* + +## Exploration shuttle terminal + A generic shuttle controller. Has a var landing_type defining type of area shuttle should be landing at. On initalizing, checks for a shuttle corresponding to this zlevel, and creates one if it's not there. @@ -53,16 +51,16 @@ Changes desitnation area depending on current sector ship is in. Currently updating is called in attack_hand(), until a better place is found. Currently no modifications were made to interface to display availability of landing area in sector. -************************************************************* -Landable Ships -************************************************************* + +## Landable Ships + Ship - Vessel that can move around on the overmap. It's entire z-level(s) "move" conceptually. Shuttles - Vessel that can jump to shuttle landmarks. Its areas move by transition_turfs. Landable Ship - Vessel that can do both. Sits at a special shuttle landmark for overmap movement mode. -************************************************************* -Guide to how make new sector -************************************************************* + +## Guide to how make new sector + 0.Map Remember to define shuttle areas if you want sector be accessible via shuttles. Currently there are no other ways to reach sectors from ships. @@ -73,38 +71,37 @@ Ships need engines to move. Currently there are only thermal engines. Thermal engines are just a unary atmopheric machine, like a vent. They need high-pressure gas input to produce more thrust. -1.Metaobject +1. Metaobject All vars needed for it to work could be set directly in map editor, so in most cases you won't have to define new in code. Remember to set landing_area var for sectors. -2.Overmap object +2. Overmap object If you need custom behaviour on entering/leaving this sector, or restricting access to it, you can define your custom map object. Remember to put this new type into spawn_type var of metaobject. -3.Shuttle console +3. Shuttle console Remember to place one on the actual shuttle too, or it won't be able to return from sector without ship-side recall. Remember to set landing_type var to ship-side shuttle area type. shuttle_tag can be set to custom name (it shows up in console interface) -5.Engines +5. Engines Actual engines could be any type of machinery, as long as it creates a ship_engine datum for itself. -6.Tick map in and compile. +6. Tick map in and compile. Sector should appear on overmap (in random place if you didn't set mapx,mapy) -TODO: -shuttle console: - checking occupied pad or not with docking controllers - ?landing pad size detection -non-zlevel overmap objects - field generator - meteor fields - speed-based chance for a rock in the ship - debris fields - speed-based chance of - debirs in the ship - a drone - EMP - nebulaes -*/ +TODO: +shuttle console: + checking occupied pad or not with docking controllers + ?landing pad size detection +non-zlevel overmap objects + field generator + meteor fields + speed-based chance for a rock in the ship + debris fields + speed-based chance of + debirs in the ship + a drone + EMP + nebulaes diff --git a/code/modules/pda/cart.dm b/code/modules/pda/cart.dm index 4054717d1f..3a5277df6d 100644 --- a/code/modules/pda/cart.dm +++ b/code/modules/pda/cart.dm @@ -279,7 +279,7 @@ var/list/civilian_cartridges = list( /obj/item/cartridge/proc/post_status(var/command, var/data1, var/data2) - var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435) + var/datum/radio_frequency/frequency = SSradio.return_frequency(1435) if(!frequency) return var/datum/signal/status_signal = new diff --git a/code/modules/pda/cart_apps.dm b/code/modules/pda/cart_apps.dm index 946fa7e671..1d6da01edb 100644 --- a/code/modules/pda/cart_apps.dm +++ b/code/modules/pda/cart_apps.dm @@ -31,7 +31,7 @@ return TRUE /datum/data/pda/app/status_display/proc/post_status(var/command, var/data1, var/data2) - var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435) + var/datum/radio_frequency/frequency = SSradio.return_frequency(1435) if(!frequency) return diff --git a/code/modules/pda/radio.dm b/code/modules/pda/radio.dm index fab19f180b..2db4492475 100644 --- a/code/modules/pda/radio.dm +++ b/code/modules/pda/radio.dm @@ -28,15 +28,15 @@ add_to_radio(bot_filter) /obj/item/radio/integrated/Destroy() - if(radio_controller) - radio_controller.remove_object(src, control_freq) + if(SSradio) + SSradio.remove_object(src, control_freq) hostpda = null return ..() /obj/item/radio/integrated/proc/post_signal(var/freq, var/key, var/value, var/key2, var/value2, var/key3, var/value3, s_filter) //to_world("Post: [freq]: [key]=[value], [key2]=[value2]") - var/datum/radio_frequency/frequency = radio_controller.return_frequency(freq) + var/datum/radio_frequency/frequency = SSradio.return_frequency(freq) if(!frequency) return @@ -90,8 +90,8 @@ botstatus = b.Copy() /obj/item/radio/integrated/proc/add_to_radio(bot_filter) //Master filter control for bots. Must be placed in the bot's local Initialize(mapload) to support map spawned bots. - if(radio_controller) - radio_controller.add_object(src, control_freq, radio_filter = bot_filter) + if(SSradio) + SSradio.add_object(src, control_freq, radio_filter = bot_filter) /* * Radio Cartridge, essentially a signaler. @@ -101,22 +101,22 @@ var/code = 30.0 /obj/item/radio/integrated/signal/Destroy() - if(radio_controller) - radio_controller.remove_object(src, frequency) + if(SSradio) + SSradio.remove_object(src, frequency) radio_connection = null return ..() /obj/item/radio/integrated/signal/Initialize(mapload) . = ..() - if(radio_controller) + if(SSradio) if(src.frequency < PUBLIC_LOW_FREQ || src.frequency > PUBLIC_HIGH_FREQ) src.frequency = sanitize_frequency(src.frequency) set_frequency(frequency) /obj/item/radio/integrated/signal/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency - radio_connection = radio_controller.add_object(src, frequency) + radio_connection = SSradio.add_object(src, frequency) /obj/item/radio/integrated/signal/proc/send_signal(message="ACTIVATE") if(last_transmission && world.time < (last_transmission + 5)) diff --git a/code/modules/power/apc.dm b/code/modules/power/apc.dm index cc386e4846..f0d35f4f63 100644 --- a/code/modules/power/apc.dm +++ b/code/modules/power/apc.dm @@ -482,7 +482,7 @@ GLOBAL_LIST_EMPTY(apcs) span_warning("[user.name] has broken the charred power control board inside [name]!"),\ span_notice("You broke the charred power control board and remove the remains."), "You hear a crack!") - //ticker.mode:apcs-- //XSI said no and I agreed. -rastaf0 + //SSticker.mode:apcs-- //XSI said no and I agreed. -rastaf0 else user.visible_message(\ span_warning("[user.name] has removed the power control board from [name]!"),\ diff --git a/code/modules/projectiles/projectile/change.dm b/code/modules/projectiles/projectile/change.dm index 5395da0d92..a0103b9883 100644 --- a/code/modules/projectiles/projectile/change.dm +++ b/code/modules/projectiles/projectile/change.dm @@ -66,15 +66,15 @@ if(M.gender == MALE) H.gender = MALE - H.name = pick(first_names_male) + H.name = pick(GLOB.first_names_male) else if(M.gender == FEMALE) H.gender = FEMALE - H.name = pick(first_names_female) + H.name = pick(GLOB.first_names_female) else H.gender = NEUTER - H.name = pick(first_names_female|first_names_male) + H.name = pick(GLOB.first_names_female|GLOB.first_names_male) - H.name += " [pick(last_names)]" + H.name += " [pick(GLOB.last_names)]" H.real_name = H.name H.set_species(randomize) diff --git a/code/modules/reagents/holder/holder.dm b/code/modules/reagents/holder/holder.dm index acffe00a5a..cc6cffc741 100644 --- a/code/modules/reagents/holder/holder.dm +++ b/code/modules/reagents/holder/holder.dm @@ -101,7 +101,7 @@ while(reaction_occurred) for(var/decl/chemical_reaction/C as anything in effect_reactions) C.post_reaction(src) - #ifdef UNIT_TEST + #ifdef UNIT_TESTS SEND_SIGNAL(src, COMSIG_UNITTEST_DATA, list(C)) #endif update_total() diff --git a/code/modules/reagents/reactions/_reactions.dm b/code/modules/reagents/reactions/_reactions.dm index 604340bd86..5e0a06c028 100644 --- a/code/modules/reagents/reactions/_reactions.dm +++ b/code/modules/reagents/reactions/_reactions.dm @@ -103,8 +103,8 @@ var/amt_produced = result_amount * reaction_progress if(result) holder.add_reagent(result, amt_produced, data, safety = 1, was_from_belly = belly_reagent) - // #ifdef UNIT_TEST - // log_unit_test("[name] - Reagent reaction result: [result] [amt_produced]") // Uncomment for UNIT_TEST debug assistance + // #ifdef UNIT_TESTS + // log_unit_test("[name] - Reagent reaction result: [result] [amt_produced]") // Uncomment for UNIT_TESTS debug assistance // #endif on_reaction(holder, amt_produced) diff --git a/code/modules/reagents/reactions/instant/instant.dm b/code/modules/reagents/reactions/instant/instant.dm index ce330c132d..c8108b1816 100644 --- a/code/modules/reagents/reactions/instant/instant.dm +++ b/code/modules/reagents/reactions/instant/instant.dm @@ -825,8 +825,8 @@ result_amount = 2 log_is_important = 1 -#ifndef UNIT_TEST -// If it becomes possible to make this without exploding and clearing reagents, remove the UNIT_TEST wrapper +#ifndef UNIT_TESTS +// If it becomes possible to make this without exploding and clearing reagents, remove the UNIT_TESTS wrapper /decl/chemical_reaction/instant/nitroglycerin/on_reaction(var/datum/reagents/holder, var/created_volume) var/datum/effect/effect/system/reagents_explosion/e = new() e.set_up(round (created_volume/2, 1), holder.my_atom, 0, 0) diff --git a/code/modules/rogueminer_vr/zonemaster.dm b/code/modules/rogueminer_vr/zonemaster.dm index 0c1a3fc774..64b09ca05b 100644 --- a/code/modules/rogueminer_vr/zonemaster.dm +++ b/code/modules/rogueminer_vr/zonemaster.dm @@ -70,8 +70,8 @@ //Add the core to the asteroid's map rm_controller.dbg("ZM(ga): Starting core generation for [A.coresize] size core..") - for(var/x = 1; x <= A.coresize, x++) - for(var/y = 1; y <= A.coresize, y++) + for(var/x = 1, x <= A.coresize, x++) + for(var/y = 1, y <= A.coresize, y++) rm_controller.dbg("ZM(ga): Doing core-relative [x],[y] at [A.coresize+x],[A.coresize+y], [A.type_wall].") A.spot_add(A.coresize+x, A.coresize+y, A.type_wall) diff --git a/code/modules/scripting/Implementations/Telecomms.dm b/code/modules/scripting/Implementations/Telecomms.dm index c9c99dafaf..be93c2705f 100644 --- a/code/modules/scripting/Implementations/Telecomms.dm +++ b/code/modules/scripting/Implementations/Telecomms.dm @@ -265,7 +265,7 @@ freq = text2num(freq) newsign.frequency = freq - var/datum/radio_frequency/connection = radio_controller.return_frequency(freq) + var/datum/radio_frequency/connection = SSradio.return_frequency(freq) newsign.data["connection"] = connection diff --git a/code/modules/security levels/keycard authentication.dm b/code/modules/security levels/keycard authentication.dm index f1502cc6b4..0c2a113cd4 100644 --- a/code/modules/security levels/keycard authentication.dm +++ b/code/modules/security levels/keycard authentication.dm @@ -183,7 +183,7 @@ /obj/machinery/keycard_auth/proc/is_ert_blocked() if(CONFIG_GET(flag/ert_admin_call_only)) return 1 - return ticker.mode && ticker.mode.ert_disabled + return SSticker.mode && SSticker.mode.ert_disabled var/global/maint_all_access = 0 diff --git a/code/modules/shuttles/shuttle_emergency.dm b/code/modules/shuttles/shuttle_emergency.dm index af1998c2f7..15bb884f1f 100644 --- a/code/modules/shuttles/shuttle_emergency.dm +++ b/code/modules/shuttles/shuttle_emergency.dm @@ -6,7 +6,7 @@ /datum/shuttle/autodock/ferry/emergency/New() ..() - radio_connection = radio_controller.add_object(src, frequency, null) + radio_connection = SSradio.add_object(src, frequency, null) if(emergency_shuttle.shuttle) CRASH("An emergency shuttle has already been defined.") emergency_shuttle.shuttle = src diff --git a/code/modules/shuttles/shuttle_supply.dm b/code/modules/shuttles/shuttle_supply.dm index e030f336cf..616f904377 100644 --- a/code/modules/shuttles/shuttle_supply.dm +++ b/code/modules/shuttles/shuttle_supply.dm @@ -17,7 +17,7 @@ var/datum/signal/S = new() S.source = src S.data = list("command" = "supply") - var/datum/radio_frequency/F = radio_controller.return_frequency(1435) + var/datum/radio_frequency/F = SSradio.return_frequency(1435) F.post_signal(src, S) //it would be cool to play a sound here diff --git a/code/modules/spells/targeted/targeted.dm b/code/modules/spells/targeted/targeted.dm index e36d6477e7..847d4cbfa5 100644 --- a/code/modules/spells/targeted/targeted.dm +++ b/code/modules/spells/targeted/targeted.dm @@ -86,7 +86,7 @@ Targeted spells have two useful flags: INCLUDEUSER and SELECTABLE. These are exp possible_targets += target if(spell_flags & SELECTABLE) - for(var/i = 1; i<=max_targets, i++) + for(var/i = 1, i<=max_targets, i++) if(!possible_targets.len) break var/mob/M = tgui_input_list(user, "Choose the target for the spell.", "Targeting", possible_targets) diff --git a/code/modules/stockmarket/articles.dm b/code/modules/stockmarket/articles.dm index c9c5bc1127..1ed89d786f 100644 --- a/code/modules/stockmarket/articles.dm +++ b/code/modules/stockmarket/articles.dm @@ -94,11 +94,11 @@ GLOBAL_LIST_EMPTY(FrozenAccounts) /datum/article/proc/generateAuthorName() switch(rand(1,3)) if (1) - return "[consonant()]. [pick(last_names)]" + return "[consonant()]. [pick(GLOB.last_names)]" if (2) - return "[prob(50) ? pick(first_names_male) : pick(first_names_female)] [consonant()].[prob(50) ? "[consonant()]. " : null] [pick(last_names)]" + return "[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)] [consonant()].[prob(50) ? "[consonant()]. " : null] [pick(GLOB.last_names)]" if (3) - return "[prob(50) ? pick(first_names_male) : pick(first_names_female)] \"[prob(50) ? pick(first_names_male) : pick(first_names_female)]\" [pick(last_names)]" + return "[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)] \"[prob(50) ? pick(GLOB.first_names_male) : pick(GLOB.first_names_female)]\" [pick(GLOB.last_names)]" /datum/article/proc/formatSpacetime() var/ticksc = round(ticks/100) diff --git a/code/modules/tables/tables.dm b/code/modules/tables/tables.dm index d07c5d24a3..e53a435543 100644 --- a/code/modules/tables/tables.dm +++ b/code/modules/tables/tables.dm @@ -72,7 +72,7 @@ var/list/table_icon_cache = list() // reset color/alpha, since they're set for nice map previews color = "#ffffff" alpha = 255 - update_connections(ticker && ticker.current_state == GAME_STATE_PLAYING) + update_connections(SSticker && SSticker.current_state == GAME_STATE_PLAYING) update_icon() update_desc() update_material() diff --git a/code/modules/tgchat/to_chat.dm b/code/modules/tgchat/to_chat.dm index 7b2da90066..6b054ba796 100644 --- a/code/modules/tgchat/to_chat.dm +++ b/code/modules/tgchat/to_chat.dm @@ -76,7 +76,7 @@ confidential = FALSE ) //if(isnull(Master) || !SSchat?.initialized || !MC_RUNNING(SSchat.init_stage)) - if(isnull(Master) || !SSchat?.subsystem_initialized) + if(isnull(Master) || !SSchat?.initialized) to_chat_immediate(target, html, type, text, avoid_highlighting) return diff --git a/code/modules/tgui/modules/communications.dm b/code/modules/tgui/modules/communications.dm index 0379910257..45bd3390db 100644 --- a/code/modules/tgui/modules/communications.dm +++ b/code/modules/tgui/modules/communications.dm @@ -175,7 +175,7 @@ return global_message_listener /proc/post_status(atom/source, command, data1, data2, mob/user = null) - var/datum/radio_frequency/frequency = radio_controller.return_frequency(1435) + var/datum/radio_frequency/frequency = SSradio.return_frequency(1435) if(!frequency) return @@ -381,7 +381,7 @@ PS.allowedtocall = !(PS.allowedtocall) /proc/call_shuttle_proc(var/mob/user) - if ((!( ticker ) || !emergency_shuttle.location())) + if ((!( SSticker ) || !emergency_shuttle.location())) return if(!GLOB.universe.OnShuttleCall(user)) @@ -408,7 +408,7 @@ to_chat(user, "The emergency shuttle is already on its way.") return - if(ticker.mode.name == "blob") + if(SSticker.mode.name == "blob") to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.") return @@ -420,7 +420,7 @@ return /proc/init_shift_change(var/mob/user, var/force = 0) - if ((!( ticker ) || !emergency_shuttle.location())) + if ((!( SSticker ) || !emergency_shuttle.location())) return if(emergency_shuttle.going_to_centcom()) @@ -445,11 +445,11 @@ to_chat(user, "The shuttle is refueling. Please wait another [round((54000-world.time)/60)] minutes before trying again.") return - if(ticker.mode.auto_recall_shuttle) + if(SSticker.mode.auto_recall_shuttle) //New version pretends to call the shuttle but cause the shuttle to return after a random duration. emergency_shuttle.auto_recall = 1 - if(ticker.mode.name == "blob" || ticker.mode.name == "epidemic") + if(SSticker.mode.name == "blob" || SSticker.mode.name == "epidemic") to_chat(user, "Under directive 7-10, [station_name()] is quarantined until further notice.") return @@ -467,9 +467,9 @@ return /proc/cancel_call_proc(var/mob/user) - if (!( ticker ) || !emergency_shuttle.can_recall()) + if (!( SSticker ) || !emergency_shuttle.can_recall()) return - if((ticker.mode.name == "blob")||(ticker.mode.name == "Meteor")) + if((SSticker.mode.name == "blob")||(SSticker.mode.name == "Meteor")) return if(!emergency_shuttle.going_to_centcom()) //check that shuttle isn't already heading to CentCom diff --git a/code/modules/tgui/modules/late_choices.dm b/code/modules/tgui/modules/late_choices.dm index 15d9b6ba80..14751393a7 100644 --- a/code/modules/tgui/modules/late_choices.dm +++ b/code/modules/tgui/modules/late_choices.dm @@ -121,7 +121,7 @@ if(!CONFIG_GET(flag/enter_allowed)) to_chat(new_user, span_notice("There is an administrative lock on entering the game!")) return - else if(ticker && ticker.mode && ticker.mode.explosion_in_progress) + else if(SSticker && SSticker.mode && SSticker.mode.explosion_in_progress) to_chat(new_user, span_danger("The station is currently exploding. Joining would go poorly.")) return diff --git a/code/modules/unit_tests/README.md b/code/modules/unit_tests/README.md new file mode 100644 index 0000000000..1f8bc271b3 --- /dev/null +++ b/code/modules/unit_tests/README.md @@ -0,0 +1,83 @@ +# Unit Tests + +## What is unit testing? + +Unit tests are automated code to verify that parts of the game work exactly as they should. For example, [a test to make sure that the amputation surgery actually amputates the limb](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/surgeries.dm#L1-L13). These are ran every time a PR is made, and thus are very helpful for preventing bugs from cropping up in your code that would've otherwise gone unnoticed. For example, would you have thought to check [that beach boys would still work the same after editing pizza](https://github.com/tgstation/tgstation/pull/53641#issuecomment-691384934)? If you value your time, probably not. + +On their most basic level, when `UNIT_TESTS` is defined, all subtypes of `/datum/unit_test` will have their `Run` proc executed. From here, if `Fail` is called at any point, then the tests will report as failed. + +## How do I write one? + +1. Find a relevant file. + +All unit test related code is in `code/modules/unit_tests`. If you are adding a new test for a surgery, for example, then you'd open `surgeries.dm`. If a relevant file does not exist, simply create one in this folder, then `#include` it in `_unit_tests.dm`. + +2. Create the unit test. + +To make a new unit test, you simply need to define a `/datum/unit_test`. + +For example, let's suppose that we are creating a test to make sure a proc `square` correctly raises inputs to the power of two. We'd start with first: + +``` +/datum/unit_test/square/Run() +``` + +This defines our new unit test, `/datum/unit_test/square`. Inside this function, we're then going to run through whatever we want to check. Tests provide a few assertion functions to make this easy. For now, we're going to use `TEST_ASSERT_EQUAL`. + +``` +/datum/unit_test/square/Run() + TEST_ASSERT_EQUAL(square(3), 9, "square(3) did not return 9") + TEST_ASSERT_EQUAL(square(4), 16, "square(4) did not return 16") +``` + +As you can hopefully tell, we're simply checking if the output of `square` matches the output we are expecting. If the test fails, it'll report the error message given as well as whatever the actual output was. + +3. Run the unit test + +Open `code/_compile_options.dm` and uncomment the following line. + +``` +//#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 +``` + +There are 3 ways to run unit tests + +- Run tgstation.dmb in Dream Daemon. Don't bother trying to connect, you won't need to. You'll be able to see the outputs of all the tests. You'll get to see which tests failed and for what reason. If they all pass, you're set! + +- Launch game from VS Code. Launch the game as normal & you will see the output of your unit tests in your fancy chat window. This is preferred as you can use the debugger to step through each line of your unit test & can use the games inbuilt debugging tools to further aid in testing + +- Use VS Code Tgstation Test Explorer Extension. This allows you to run tests without launching the game & can also run focused tests(either a single or a selected group) + +## How to think about tests + +Unit tests exist to prevent bugs that would happen in a real game. Thus, they should attempt to emulate the game world wherever possible. For example, the [quick swap sanity test](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/quick_swap_sanity.dm) emulates a _real_ scenario of the bug it fixed occurring by creating a character and giving it real items. The unrecommended alternative would be to create special test-only items. This isn't a hard rule, the [reagent method exposure tests](https://github.com/tgstation/tgstation/blob/e416283f162b86345a8623125ab866839b1ac40d/code/modules/unit_tests/reagent_mod_expose.dm) create a test-only reagent for example, but do keep it in mind. + +Unit tests should also be just that--testing _units_ of code. For example, instead of having one massive test for reagents, there are instead several smaller tests for testing exposure, metabolization, etc. + +## The unit testing API + +You can find more information about all of these from their respective doc comments, but for a brief overview: + +`/datum/unit_test` - The base for all tests to be ran. Subtypes must override `Run()`. `New()` and `Destroy()` can be used for setup and teardown. To fail, use `TEST_FAIL(reason)`. + +`/datum/unit_test/proc/allocate(type, ...)` - Allocates an instance of the provided type with the given arguments. Is automatically destroyed when the test is over. Commonly seen in the form of `var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent)`. + +`TEST_FAIL(reason)` - Marks a failure at this location, but does not stop the test. + +`TEST_ASSERT(assertion, reason)` - Stops the unit test and fails if the assertion is not met. For example: `TEST_ASSERT(powered(), "Machine is not powered")`. + +`TEST_ASSERT_NOTNULL(a, message)` - Same as `TEST_ASSERT`, but checks if `!isnull(a)`. For example: `TEST_ASSERT_NOTNULL(myatom, "My atom was never set!")`. + +`TEST_ASSERT_NULL(a, message)` - Same as `TEST_ASSERT`, but checks if `isnull(a)`. If not, gives a helpful message showing what `a` was. For example: `TEST_ASSERT_NULL(delme, "Delme was never cleaned up!")`. + +`TEST_ASSERT_EQUAL(a, b, message)` - Same as `TEST_ASSERT`, but checks if `a == b`. If not, gives a helpful message showing what both `a` and `b` were. For example: `TEST_ASSERT_EQUAL(2 + 2, 4, "The universe is falling apart before our eyes!")`. + +`TEST_ASSERT_NOTEQUAL(a, b, message)` - Same as `TEST_ASSERT_EQUAL`, but reversed. + +`TEST_FOCUS(test_path)` - _Only_ run the test provided within the parameters. Useful for reducing noise. For example, if we only want to run our example square test, we can add `TEST_FOCUS(/datum/unit_test/square)`. Should _never_ be pushed in a pull request--you will be laughed at. + +## Final Notes + +- Writing tests before you attempt to fix the bug can actually speed up development a lot! It means you don't have to go in game and folllow the same exact steps manually every time. This process is known as "TDD" (test driven development). Write the test first, make sure it fails, _then_ start work on the fix/feature, and you'll know you're done when your tests pass. If you do try this, do make sure to confirm in a non-testing environment just to double check. +- Make sure that your tests don't accidentally call RNG functions like `prob`. Since RNG is seeded during tests, you may not realize you have until someone else makes a PR and the tests fail! +- Do your best not to change the behavior of non-testing code during tests. While it may sometimes be necessary in the case of situations such as the above, it is still a slippery slope that can lead to the code you're testing being too different from the production environment to be useful. diff --git a/code/modules/unit_tests/_unit_tests.dm b/code/modules/unit_tests/_unit_tests.dm new file mode 100644 index 0000000000..b582dd8973 --- /dev/null +++ b/code/modules/unit_tests/_unit_tests.dm @@ -0,0 +1,129 @@ +//include unit test files in this module in this ifdef +//Keep this sorted alphabetically + +#if defined(UNIT_TESTS) || defined(SPACEMAN_DMM) + +/// For advanced cases, fail unconditionally but don't return (so a test can return multiple results) +#define TEST_FAIL(reason) (Fail(reason || "No reason", __FILE__, __LINE__)) + +/// 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"]", __FILE__, __LINE__) } + +/// Asserts that a parameter is not null +#define TEST_ASSERT_NOTNULL(a, reason) if (isnull(a)) { return Fail("Expected non-null value: [reason || "No reason"]", __FILE__, __LINE__) } + +/// Asserts that a parameter is null +#define TEST_ASSERT_NULL(a, reason) if (!isnull(a)) { return Fail("Expected null value but received [a]: [reason || "No reason"]", __FILE__, __LINE__) } + +/// 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) do { \ + var/lhs = ##a; \ + var/rhs = ##b; \ + if (lhs != rhs) { \ + return Fail("Expected [isnull(lhs) ? "null" : lhs] to be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \ + } \ +} while (FALSE) + +/// Asserts that the two parameters passed are not equal, fails otherwise +/// Optionally allows an additional message in the case of a failure +#define TEST_ASSERT_NOTEQUAL(a, b, message) do { \ + var/lhs = ##a; \ + var/rhs = ##b; \ + if (lhs == rhs) { \ + return Fail("Expected [isnull(lhs) ? "null" : lhs] to not be equal to [isnull(rhs) ? "null" : rhs].[message ? " [message]" : ""]", __FILE__, __LINE__); \ + } \ +} while (FALSE) + +/// *Only* run the test provided within the parentheses +/// This is useful for debugging when you want to reduce noise, but should never be pushed +/// Intended to be used in the manner of `TEST_FOCUS(/datum/unit_test/math)` +#define TEST_FOCUS(test_path) ##test_path { focus = TRUE; } + +/// Logs a noticable message on GitHub, but will not mark as an error. +/// Use this when something shouldn't happen and is of note, but shouldn't block CI. +/// Does not mark the test as failed. +#define TEST_NOTICE(source, message) source.log_for_test((##message), "notice", __FILE__, __LINE__) + +/// Constants indicating unit test completion status +#define UNIT_TEST_PASSED 0 +#define UNIT_TEST_FAILED 1 +#define UNIT_TEST_SKIPPED 2 + +#define TEST_PRE 0 +#define TEST_DEFAULT 1 +/// After most test steps, used for tests that run long so shorter issues can be noticed faster +#define TEST_LONGER 10 +/// This must be the one of last tests to run due to the inherent nature of the test iterating every single tangible atom in the game and qdeleting all of them (while taking long sleeps to make sure the garbage collector fires properly) taking a large amount of time. +#define TEST_CREATE_AND_DESTROY 9001 +/** + * For tests that rely on create and destroy having iterated through every (tangible) atom so they don't have to do something similar. + * Keep in mind tho that create and destroy will absolutely break the test platform, anything that relies on its shape cannot come after it. + */ +#define TEST_AFTER_CREATE_AND_DESTROY INFINITY + +/// Change color to red on ANSI terminal output, if enabled with -DANSICOLORS. +#ifdef ANSICOLORS +#define TEST_OUTPUT_RED(text) "\x1B\x5B1;31m[text]\x1B\x5B0m" +#else +#define TEST_OUTPUT_RED(text) (text) +#endif +/// Change color to green on ANSI terminal output, if enabled with -DANSICOLORS. +#ifdef ANSICOLORS +#define TEST_OUTPUT_GREEN(text) "\x1B\x5B1;32m[text]\x1B\x5B0m" +#else +#define TEST_OUTPUT_GREEN(text) (text) +#endif +/// Change color to yellow on ANSI terminal output, if enabled with -DANSICOLORS. +#ifdef ANSICOLORS +#define TEST_OUTPUT_YELLOW(text) "\x1B\x5B1;33m[text]\x1B\x5B0m" +#else +#define TEST_OUTPUT_YELLOW(text) (text) +#endif +/// A trait source when adding traits through unit tests +#define TRAIT_SOURCE_UNIT_TESTS "unit_tests" +/// Helper to allocate a new object with the implied type (the type of the variable it's assigned to) in the corner of the test room +#define EASY_ALLOCATE(arguments...) allocate(__IMPLIED_TYPE__, run_loc_floor_bottom_left, ##arguments) + +// BEGIN_INCLUDE +#include "asset_smart_cache.dm" +//#include "clothing_tests.dm" // FIXME +#include "component_tests.dm" +#include "cosmetic_tests.dm" +#include "dcs_check_list_arguments.dm" +#include "dcs_get_id_from_elements.dm" +#include "decl_tests.dm" +#include "disease_tests.dm" +#include "focus_only_tests.dm" +#include "font_awesome_icons.dm" +#include "genetics_tests.dm" +#include "language_tests.dm" +#include "loadout_tests.dm" +#include "map_tests.dm" +#include "material_tests.dm" +// #include "nuke_cinematic.dm" // TODO: This is probably fixed later on +#include "poster_tests.dm" +// #include "preferences.dm" // This unit test is missing some other stuff +#include "reagent_tests.dm" +#include "recipe_tests.dm" +#include "recycler_vendor_tests.dm" +#include "robot_tests.dm" +#include "spritesheets.dm" +#include "sqlite_tests.dm" +#include "subsystem_init.dm" +#include "tgui_create_message.dm" +#include "timer_sanity.dm" +#include "trait_tests.dm" +#include "unit_test.dm" +// #include "vore_tests.dm" // FIXME: REWRITE OR FIX THIS +// END_INCLUDE +#ifdef REFERENCE_TRACKING_DEBUG //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 +//#undef TEST_FOCUS - This define is used by vscode unit test extension to pick specific unit tests to run and appended later so needs to be used out of scope here +#endif diff --git a/code/modules/unit_tests/asset_smart_cache.dm b/code/modules/unit_tests/asset_smart_cache.dm new file mode 100644 index 0000000000..48d1506b10 --- /dev/null +++ b/code/modules/unit_tests/asset_smart_cache.dm @@ -0,0 +1,63 @@ +/datum/asset/spritesheet_batched/test + name = "test" + load_immediately = TRUE + force_cache = TRUE + // Don't let the asset subsystem load this. This is how we trick it. + _abstract = /datum/asset/spritesheet_batched/test + var/static/list/items = list(/obj/item/binoculars, /obj/item/camera, /obj/item/clothing/under/color/blue, /obj/item/clothing/under/color/black) + +/datum/asset/spritesheet_batched/test/create_spritesheets() + for(var/atom/item as anything in items) + if (!ispath(item, /atom)) + return FALSE + var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-") + insert_icon(imgid, get_display_icon_for(item)) + // Get some coverage on each operation. + var/datum/universal_icon/I = uni_icon('icons/effects/effects.dmi', "nothing") + I.blend_icon(uni_icon('icons/effects/effects.dmi', "sparks"), ICON_OVERLAY) + I.blend_color("#ff0000", ICON_MULTIPLY) + I.scale(64, 64) + I.crop(1, 1, 128, 64) // we'll test for the scale later. + insert_icon("test", I) + +/datum/asset/spritesheet_batched/test/unregister() + SSassets.transport.unregister_asset("spritesheet_[name].css") + if(length(sizes)) + for(var/size_id in sizes) + SSassets.transport.unregister_asset("[name]_[size_id].png") + +/datum/unit_test/test_asset_smart_cache/Run() + fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json") + fdel("data/spritesheets/spritesheet_test.css") + var/datum/asset/spritesheet_batched/test/sheet = new() + TEST_ASSERT(sheet.fully_generated, "Spritesheet not generated!") + // Cache should be invalid initially. + TEST_ASSERT(sheet.cache_result, "Spritesheet smart cache was VALID when it should be INVALID!") + for(var/item in sheet.items) + var/imgid = replacetext(replacetext("[item]", "/obj/item/", ""), "/", "-") + // All items should be in sprites list. + TEST_ASSERT(imgid in sheet.sprites, "Item [item] not present in spritesheet result!") + TEST_ASSERT("test" in sheet.sprites, "Item test not present in spritesheet result!") + TEST_ASSERT("128x64" in sheet.sizes, "Test icon was not output as 128x64!") + // cache wrote properly + TEST_ASSERT(fexists("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json"), "Smart cache entry did not write!") + // Clear it out and get ready to do it again, this time loading from cache + sheet.unregister() + sheet.entries = list() + sheet.sprites = list() + sheet.sizes = list() + sheet.job_id = null + sheet.cache_result = null + sheet.cache_data = null + sheet.cache_job_id = null + sheet.fully_generated = FALSE + + sheet.register() + TEST_ASSERT(sheet.fully_generated, "Spritesheet did not load from smart cache properly!") + // Check for CACHE_VALID + TEST_ASSERT(!sheet.cache_result, "Spritesheet did not load from smart cache, it was invalid despite having the same input data!") + // Cleanup files. + fdel("[ASSET_CROSS_ROUND_SMART_CACHE_DIRECTORY]/spritesheet_cache.test.json") + fdel("data/spritesheets/spritesheet_test.css") + for(var/size in sheet.sizes) + fdel("data/spritesheets/test_[size].png") diff --git a/code/unit_tests/clothing_tests.dm b/code/modules/unit_tests/clothing_tests.dm similarity index 68% rename from code/unit_tests/clothing_tests.dm rename to code/modules/unit_tests/clothing_tests.dm index f905d9072c..258caecb85 100644 --- a/code/unit_tests/clothing_tests.dm +++ b/code/modules/unit_tests/clothing_tests.dm @@ -1,8 +1,12 @@ +/// converted unit test, maybe should be fully refactored +/// MIGHT REQUIRE BIGGER REWORK + +/// Test that checks if all clothing is valid /datum/unit_test/all_clothing_shall_be_valid - name = "CLOTHING: All clothing shall be valid" var/signal_failed = FALSE -/datum/unit_test/all_clothing_shall_be_valid/start_test() + +/datum/unit_test/all_clothing_shall_be_valid/Run() var/failed = 0 var/obj/storage = new() @@ -22,8 +26,8 @@ failed += test_clothing(C) if(i > tenths * a_tenth) - log_unit_test("Clothing - Progress [tenths * 10]% - [i]/[scan.len]") - log_unit_test("---------------------------------------------------") + //TEST_NOTICE("Clothing - Progress [tenths * 10]% - [i]/[scan.len]") + //TEST_NOTICE("---------------------------------------------------") tenths++ if(istype(C,/obj/item/clothing/suit/storage/hooded)) @@ -36,10 +40,7 @@ qdel(storage) if(failed) - fail("One or more /obj/item/clothing items had invalid flags or icons") - else - pass("All /obj/item/clothing are valid.") - return 1 + TEST_FAIL("One or more /obj/item/clothing items had invalid flags or icons") /datum/unit_test/all_clothing_shall_be_valid/proc/test_clothing(var/obj/item/clothing/C,var/obj/storage) var/failed = FALSE @@ -49,20 +50,15 @@ return FALSE // ID - if(!C.name) - log_unit_test("[C.type]: Clothing - Missing name.") - failed = TRUE - - if(C.name == "") - log_unit_test("[C.type]: Clothing - Empty name.") - failed = TRUE + TEST_ASSERT(C.name, "[C.type]: Clothing - Missing name.") + TEST_ASSERT(C.name != "", "[C.type]: Clothing - Empty name.") // Icons if(!("[C.icon_state]" in cached_icon_states(C.icon))) if(C.icon == initial(C.icon) && C.icon_state == initial(C.icon_state)) - log_unit_test("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon].") + TEST_NOTICE("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon].") else - log_unit_test("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon]. This icon/state was changed by init. Initial icon \"[initial(C.icon)]\". initial icon_state \"[initial(C.icon_state)]\". Check code.") + TEST_NOTICE("[C.type]: Clothing - Icon_state \"[C.icon_state]\" is not present in [C.icon]. This icon/state was changed by init. Initial icon \"[initial(C.icon)]\". initial icon_state \"[initial(C.icon_state)]\". Check code.") failed = TRUE // Disabled, as currently not working in a presentable way, spams the CI hard, do not enable unless fixed @@ -104,47 +100,45 @@ #endif // Temps - if(C.min_cold_protection_temperature < 0) - log_unit_test("[C.type]: Clothing - Cold protection was lower than 0.") - failed = TRUE + TEST_ASSERT(C.min_cold_protection_temperature > 0, "[C.type]: Clothing - Cold protection was lower than 0.") if(C.max_heat_protection_temperature && C.min_cold_protection_temperature && C.max_heat_protection_temperature < C.min_cold_protection_temperature) - log_unit_test("[C.type]: Clothing - Maximum heat protection was greater than minimum cold protection.") + TEST_NOTICE("[C.type]: Clothing - Maximum heat protection was greater than minimum cold protection.") failed = TRUE //var/valid_range = HEAD|UPPER_TORSO|LOWER_TORSO|LEGS|FEET|ARMS|HANDS if(C.cold_protection) if(islist(C.cold_protection)) - log_unit_test("[C.type]: Clothing - cold_protection was defined as a list, when it is a bitflag.") + TEST_NOTICE("[C.type]: Clothing - cold_protection was defined as a list, when it is a bitflag.") failed = TRUE else if(!isnum(C.cold_protection)) - log_unit_test("[C.type]: Clothing - cold_protection was defined as something other than a number, when it is a bitflag.") + TEST_NOTICE("[C.type]: Clothing - cold_protection was defined as something other than a number, when it is a bitflag.") failed = TRUE else if(C.cold_protection && C.cold_protection != FULL_BODY) // Check flags that should be unused if(C.cold_protection & FACE) - log_unit_test("[C.type]: Clothing - cold_protection uses FACE bitflag, this provides no protection, use HEAD.") + TEST_NOTICE("[C.type]: Clothing - cold_protection uses FACE bitflag, this provides no protection, use HEAD.") failed = TRUE if(C.cold_protection & EYES) - log_unit_test("[C.type]: Clothing - cold_protection uses EYES bitflag, this provides no protection, use HEAD.") + TEST_NOTICE("[C.type]: Clothing - cold_protection uses EYES bitflag, this provides no protection, use HEAD.") failed = TRUE if(C.heat_protection) if(islist(C.heat_protection)) - log_unit_test("[C.type]: Clothing - heat_protection was defined as a list, when it is a bitflag.") + TEST_NOTICE("[C.type]: Clothing - heat_protection was defined as a list, when it is a bitflag.") failed = TRUE else if(!isnum(C.heat_protection)) - log_unit_test("[C.type]: Clothing - heat_protection was defined as something other than a number, when it is a bitflag.") + TEST_NOTICE("[C.type]: Clothing - heat_protection was defined as something other than a number, when it is a bitflag.") failed = TRUE else if(C.heat_protection && C.heat_protection != FULL_BODY) // Check flags that should be unused if(C.heat_protection & FACE) - log_unit_test("[C.type]: Clothing - heat_protection uses FACE bitflag, this provides no protection, use HEAD.") + TEST_NOTICE("[C.type]: Clothing - heat_protection uses FACE bitflag, this provides no protection, use HEAD.") failed = TRUE if(C.heat_protection & EYES) - log_unit_test("[C.type]: Clothing - heat_protection uses EYES bitflag, this provides no protection, use HEAD.") + TEST_NOTICE("[C.type]: Clothing - heat_protection uses EYES bitflag, this provides no protection, use HEAD.") failed = TRUE return failed @@ -172,6 +166,6 @@ // All that matters if(!("[set_state]" in cached_icon_states(set_icon))) - log_unit_test("[item_path]: Clothing - Testing \"[species]\" state \"[set_state]\" for slot \"[slot_name]\", but it was not in dmi \"[set_icon]\"") + TEST_NOTICE("[item_path]: Clothing - Testing \"[species]\" state \"[set_state]\" for slot \"[slot_name]\", but it was not in dmi \"[set_icon]\"") signal_failed = TRUE return diff --git a/code/modules/unit_tests/component_tests.dm b/code/modules/unit_tests/component_tests.dm new file mode 100644 index 0000000000..1fae398251 --- /dev/null +++ b/code/modules/unit_tests/component_tests.dm @@ -0,0 +1,7 @@ +/datum/unit_test/component_duping/Run() + var/list/bad_dms = list() + for(var/t in typesof(/datum/component)) + var/datum/component/comp = t + if(!isnum(initial(comp.dupe_mode))) + bad_dms += t + TEST_ASSERT(!length(bad_dms), "Components with invalid dupe modes: ([bad_dms.Join(",")])") diff --git a/code/modules/unit_tests/cosmetic_tests.dm b/code/modules/unit_tests/cosmetic_tests.dm new file mode 100644 index 0000000000..ef60de0217 --- /dev/null +++ b/code/modules/unit_tests/cosmetic_tests.dm @@ -0,0 +1,60 @@ +/// converted unit test, maybe should be fully refactored +/// MIGHT REQUIRE BIGGER REWORK + +/// Test that tests that all cosmetics have unique name entries +/datum/unit_test/sprite_accessories_shall_be_unique + +/datum/unit_test/sprite_accessories_shall_be_unique/Run() + validate_accessory_list(/datum/sprite_accessory/ears) + validate_accessory_list(/datum/sprite_accessory/facial_hair) + validate_accessory_list(/datum/sprite_accessory/hair) + validate_accessory_list(/datum/sprite_accessory/hair_accessory) + validate_accessory_list(/datum/sprite_accessory/marking) + validate_accessory_list(/datum/sprite_accessory/tail) + validate_accessory_list(/datum/sprite_accessory/wing) + +/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_accessory_list(var/path) + var/list/collection = list() + for(var/SP in subtypesof(path)) + var/datum/sprite_accessory/A = new SP() + TEST_ASSERT(A, "[SP]: Cosmetic - Path resolved to null in list.") + if(!A) + continue + + TEST_ASSERT(A.name, "[A] - [A.type]: Cosmetic - Missing name.") + + if(A.name == DEVELOPER_WARNING_NAME) + continue + + TEST_ASSERT(!collection[A.name], "[A] - [A.type]: Cosmetic - Name defined twice. Original def [collection[A.name]]") + if(!collection[A.name]) + collection[A.name] = A.type + + if(istype(A,text2path("[path]/invisible"))) + TEST_ASSERT(!A.icon_state, "[A] - [A.type]: Cosmetic - Invisible subtype has icon_state.") + else if(!A.icon_state) + TEST_ASSERT(A.icon_state, "[A] - [A.type]: Cosmetic - Has no icon_state.") + else + // Check if valid icon + validate_icons(A) + + qdel(A) + +/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_icons(datum/sprite_accessory/A) + var/actual_icon_state = A.icon_state + + if(istype(A,/datum/sprite_accessory/hair)) + actual_icon_state = "[A.icon_state]_s" + TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].") + + if(istype(A,/datum/sprite_accessory/facial_hair)) + actual_icon_state = "[A.icon_state]_s" + TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].") + + if(istype(A,/datum/sprite_accessory/marking)) + var/datum/sprite_accessory/marking/MA = A + for(var/BP in MA.body_parts) + TEST_ASSERT(BP in BP_ALL, "[A] - [A.type]: Cosmetic - Has an illegal bodypart \"[BP]\". ONLY use parts listed in BP_ALL.") + + actual_icon_state = "[A.icon_state]-[BP]" + TEST_ASSERT(actual_icon_state in cached_icon_states(A.icon), "[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].") diff --git a/code/modules/unit_tests/dcs_check_list_arguments.dm b/code/modules/unit_tests/dcs_check_list_arguments.dm new file mode 100644 index 0000000000..769574cf95 --- /dev/null +++ b/code/modules/unit_tests/dcs_check_list_arguments.dm @@ -0,0 +1,55 @@ +/** + * list arguments for bespoke elements are treated as a text ref in the ID, like any other datum. + * Which means that, unless cached, using lists as arguments will lead to multiple instance of the same element + * being created over and over. + * + * Because of how it works, this unit test checks that these list datum args + * do not share similar contents (when rearranged in descending alpha-numerical order), to ensure that + * the least necessary amount of elements is created. So, using static lists may not be enough, + * for example, in the case of two different critters using the death_drops element to drop ectoplasm on death, since, + * despite being static lists, the two are different instances assigned to different mob types. + * + * Most of the time, you won't encounter two different static lists with similar contents used as element args, + * meaning using static lists is accepted. However, should that happen, it's advised to replace the instances + * with either string_list(), string_assoc_list(), string_assoc_nested_list() or string_numbers_list(), depending on the contents of the list. + * + * In the case of an element where the position of the contents of each datum list argument is important, + * ELEMENT_DONT_SORT_LIST_ARGS should be added to its flags, to prevent such issues where the contents are similar + * when sorted, but the element instances are not. + * + * In the off-chance the element is not compatible with this unit test (such as for connect_loc et simila), + * you can also use ELEMENT_NO_LIST_UNIT_TEST so that they won't be processed by this unit test at all. + */ +/datum/unit_test/dcs_check_list_arguments + /** + * This unit test requires every (unless ignored) atom to have been created at least once + * for a more accurate search, which is why it's run after create_and_destroy is done running. + */ + priority = TEST_AFTER_CREATE_AND_DESTROY + +/datum/unit_test/dcs_check_list_arguments/Run() + var/we_failed = FALSE + for(var/element_type in SSdcs.arguments_that_are_lists_by_element) + // Keeps track of the lists that shouldn't be compared with again. + var/list/to_ignore = list() + var/list/superlist = SSdcs.arguments_that_are_lists_by_element[element_type] + for(var/list/current as anything in superlist) + to_ignore[current] = TRUE + var/list/bad_lists + for(var/list/compare as anything in superlist) + if(to_ignore[compare]) + continue + if(deep_compare_list(current, compare)) + if(!bad_lists) + bad_lists = list(list(current)) + bad_lists += list(compare) + to_ignore[compare] = TRUE + if(bad_lists) + we_failed = TRUE + //Include the original, unsorted list in the report. It should be easier to find by the contributor. + var/list/unsorted_list = superlist[current] + TEST_FAIL("Found [length(bad_lists)] datum list arguments with similar contents for [element_type]. Contents: [json_encode(unsorted_list)].") + ///Let's avoid sending the same instructions over and over, as it's just going to clutter the CI and confuse someone. + if(we_failed) + TEST_FAIL("Ensure that each list is static or cached. string_list() (as well as similar procs) is your friend here.\n\ + Check the documentation from dcs_check_list_arguments.dm for more information!") diff --git a/code/modules/unit_tests/dcs_get_id_from_elements.dm b/code/modules/unit_tests/dcs_get_id_from_elements.dm new file mode 100644 index 0000000000..5e6c7b7eaa --- /dev/null +++ b/code/modules/unit_tests/dcs_get_id_from_elements.dm @@ -0,0 +1,46 @@ +/// Tests that DCS' GetIdFromArguments works as expected with standard and odd cases +/datum/unit_test/dcs_get_id_from_arguments + +/datum/unit_test/dcs_get_id_from_arguments/Run() + assert_equal(list(1), list(1)) + assert_equal(list(1, 2), list(1, 2)) + assert_equal(list(src), list(src)) + + assert_equal( + list(a = "x", b = "y", c = "z"), + list(b = "y", a = "x", c = "z"), + list(c = "z", a = "x", b = "y"), + ) + + TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, 2)), get_id_from_arguments(list(2, 1)), "Swapped arguments should not return the same id") + TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, a = "x")), get_id_from_arguments(list(1)), "Named arguments were ignored when creating ids") + TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(1, a = "x")), get_id_from_arguments(list(a = "x")), "Unnamed arguments were ignored when creating ids") + TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list(src)), get_id_from_arguments(list(world)), "References to different datums should not return the same id") + + TEST_ASSERT_NOTEQUAL(get_id_from_arguments(list()), SSdcs.GetIdFromArguments(list(/datum/element/dcs_get_id_from_arguments_mock_element2)), "Different elements should not match the same id") + +/datum/unit_test/dcs_get_id_from_arguments/proc/assert_equal(reference, ...) + var/result = get_id_from_arguments(reference) + + // Start at 1 so the 2nd argument is 2 + var/index = 1 + + for (var/other_case in args) + index += 1 + + var/other_result = get_id_from_arguments(other_case) + + if (other_result == result) + continue + + TEST_FAIL("Case #[index] produces a different GetIdFromArguments result from the first. [other_result] != [result]") + +/datum/unit_test/dcs_get_id_from_arguments/proc/get_id_from_arguments(list/arguments) + return SSdcs.GetIdFromArguments(list(/datum/element/dcs_get_id_from_arguments_mock_element) + arguments) + +// Necessary because GetIdFromArguments uses argument_hash_start_idx from an element type +/datum/element/dcs_get_id_from_arguments_mock_element + argument_hash_start_idx = 2 + +/datum/element/dcs_get_id_from_arguments_mock_element2 + argument_hash_start_idx = 2 diff --git a/code/unit_tests/decl_tests.dm b/code/modules/unit_tests/decl_tests.dm similarity index 54% rename from code/unit_tests/decl_tests.dm rename to code/modules/unit_tests/decl_tests.dm index 41eb7d48e1..8883f0f0dd 100644 --- a/code/unit_tests/decl_tests.dm +++ b/code/modules/unit_tests/decl_tests.dm @@ -1,8 +1,6 @@ -/datum/unit_test/emotes_shall_have_unique_keys - name = "DECLS: Emotes Shall Have Unique Keys" - -/datum/unit_test/emotes_shall_have_unique_keys/start_test() +/// converted unit test, maybe should be fully refactored +/datum/unit_test/emotes_shall_have_unique_keys/Run() var/list/keys = list() var/list/duplicates = list() @@ -18,9 +16,4 @@ else keys += emote.key - if(length(duplicates)) - fail("[length(duplicates)] emote\s had overlapping keys: [english_list(duplicates)].") - else - pass("All emotes had unique keys.") - - return TRUE + TEST_ASSERT(!length(duplicates), "[length(duplicates)] emote\s had overlapping keys: [english_list(duplicates)].") diff --git a/code/modules/unit_tests/disease_tests.dm b/code/modules/unit_tests/disease_tests.dm new file mode 100644 index 0000000000..3d4ad4936f --- /dev/null +++ b/code/modules/unit_tests/disease_tests.dm @@ -0,0 +1,17 @@ +/// converted unit test, maybe should be fully refactored + +/datum/unit_test/disease_tests/Run() + var/list/used_ids = list() + + for(var/datum/disease/D as anything in subtypesof(/datum/disease)) + if(initial(D.name) == DEVELOPER_WARNING_NAME) + continue + + TEST_ASSERT(!(initial(D.medical_name) in used_ids), "[D]: Disease - Had a reused medical name, this is used as an ID and must be unique.") + used_ids.Add(initial(D.medical_name)) + + TEST_ASSERT_NOTNULL(initial(D.name), "[D]: Disease - Lacks a name.") + TEST_ASSERT_NOTEQUAL(initial(D.name), "", "[D]: Disease - Lacks a name.") + + TEST_ASSERT_NOTNULL(initial(D.desc), "[D]: Disease - Lacks a description.") + TEST_ASSERT_NOTEQUAL(initial(D.desc), "", "[D]: Disease - Lacks a description.") 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..4bc6445f02 --- /dev/null +++ b/code/modules/unit_tests/find_reference_sanity.dm @@ -0,0 +1,152 @@ +///Used to test the completeness of the reference finder proc. +/datum/unit_test/find_reference_sanity + +/atom/movable/ref_holder + var/static/atom/movable/ref_test/static_test + var/atom/movable/ref_test/test + var/list/test_list = list() + var/list/test_assoc_list = list() + +/atom/movable/ref_holder/Destroy() + test = null + static_test = null + test_list.Cut() + test_assoc_list.Cut() + return ..() + +/atom/movable/ref_test + // Gotta make sure we do a full check + references_to_clear = INFINITY + 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 + var/refcount = refcount(victim) + TEST_ASSERT_EQUAL(refcount, 3, "Should be: test references: 0 + baseline references: 3 (victim var,loc,allocated list)") + victim.DoSearchVar(testbed, "Sanity Check") //We increment search time to get around an optimization + + TEST_ASSERT(!LAZYLEN(victim.found_refs), "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 + + var/refcount = refcount(victim) + TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)") + victim.DoSearchVar(testbed, "First Run") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, "test"), "The ref-tracking tool failed to find a regular value") + TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.test_list), "The ref-tracking tool failed to find a list entry") + TEST_ASSERT(LAZYACCESS(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 + + var/refcount = refcount(victim) + TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)") + victim.DoSearchVar(testbed, "Second Run") + + //This is another sanity check + TEST_ASSERT(!LAZYACCESS(victim.found_refs, testbed.overlays), "The ref-tracking tool found an overlays entry? That shouldn't be possible") + TEST_ASSERT(LAZYACCESS(victim.found_refs, testbed.vis_contents), "The ref-tracking tool failed to find a vis_contents entry") + TEST_ASSERT(LAZYACCESS(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 + + var/refcount = refcount(victim) + TEST_ASSERT_EQUAL(refcount, 6, "Should be: test references: 3 + baseline references: 3 (victim var,loc,allocated list)") + victim.DoSearchVar(victim, "Third Run Self") + victim.DoSearchVar(testbed, "Third Run Testbed") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, "self_ref"), "The ref-tracking tool failed to find a self reference") + TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find), "The ref-tracking tool failed to find a nested list entry") + TEST_ASSERT(LAZYACCESS(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) + var/refcount = refcount(victim) + TEST_ASSERT_EQUAL(refcount, 4, "Should be: test references: 1 + baseline references: 3 (victim var,loc,allocated list)") + victim.DoSearchVar(testbed, "Fourth Run") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, 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 + + var/refcount = refcount(victim) + TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)") + victim.DoSearchVar(testbed, "Fifth Run") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, to_find_in_key), "The ref-tracking tool failed to find a nested assoc list key") + TEST_ASSERT(LAZYACCESS(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 + +/datum/unit_test/find_reference_static_investigation/Run() + var/atom/movable/ref_test/victim = allocate(/atom/movable/ref_test) + var/atom/movable/ref_holder/testbed = allocate(/atom/movable/ref_holder) + pass(testbed) + SSgarbage.should_save_refs = TRUE + + //Lets check static vars now, since those can be a real headache + testbed.static_test = victim + + //Yes we do actually need to do this. The searcher refuses to read weird lists + //And global.vars is a really weird list + var/global_vars = list() + for(var/key in global.vars) + global_vars[key] = global.vars[key] + + var/refcount = refcount(victim) + TEST_ASSERT_EQUAL(refcount, 5, "Should be: test references: 2 + baseline references: 3 (victim var,loc,allocated list)") + victim.DoSearchVar(global_vars, "Sixth Run") + + TEST_ASSERT(LAZYACCESS(victim.found_refs, global_vars), "The ref-tracking tool failed to find a natively global variable") + SSgarbage.should_save_refs = FALSE diff --git a/code/modules/unit_tests/focus_only_tests.dm b/code/modules/unit_tests/focus_only_tests.dm new file mode 100644 index 0000000000..bd2596e16d --- /dev/null +++ b/code/modules/unit_tests/focus_only_tests.dm @@ -0,0 +1,61 @@ +/// These tests perform no behavior of their own, and have their tests offloaded onto other procs. +/// This is useful in cases like in build_appearance_list where we want to know if any fail, +/// but is not useful to right a test for. +/// This file exists so that you can change any of these to TEST_FOCUS and only check for that test. +/// For example, change /datum/unit_test/focus_only/invalid_overlays to TEST_FOCUS(/datum/unit_test/focus_only/invalid_overlays), +/// and you will only test the check for invalid overlays in appearance building. +/datum/unit_test/focus_only + +/// Checks that every created emissive has a valid icon_state +/datum/unit_test/focus_only/invalid_emissives + +/// Checks that every overlay passed into build_appearance_list exists in the icon +/datum/unit_test/focus_only/invalid_overlays + +/// Checks that every icon sent to the research_designs spritesheet is valid +/datum/unit_test/focus_only/invalid_research_designs + +/// Checks that every icon sent to vending machines is valid +/datum/unit_test/focus_only/invalid_vending_machine_icon_states + +/// Checks that space does not initialize multiple times +/datum/unit_test/focus_only/multiple_space_initialization + +/// Checks that smoothing_groups and canSmoothWith are properly sorted in /atom/Initialize +/datum/unit_test/focus_only/sorted_smoothing_groups + +/// Checks that floor tiles are properly mapped to broken/burnt +/datum/unit_test/focus_only/valid_turf_states + +/// Checks that nightvision eyes have a full set of color lists +/datum/unit_test/focus_only/nightvision_color_cutoffs + +/// Checks that no light shares a tile/pixel offsets with another +/datum/unit_test/focus_only/stacked_lights + +/// Checks for bad icon / icon state setups in cooking crafting menu +/datum/unit_test/focus_only/bad_cooking_crafting_icons + +/// Ensures openspace never spawns on the bottom of a z stack +/datum/unit_test/focus_only/openspace_clear + +/// Checks to ensure that variables expected to exist in a job datum (for config reasons) actually exist +/datum/unit_test/focus_only/missing_job_datum_variables + +/// Checks that the contents of the fish_counts list are also present in fish_table +/datum/unit_test/focus_only/fish_sources_tables + +/// Checks that maploaded mobs with either the `atmos_requirements` or `body_temp_sensitive` +/datum/unit_test/focus_only/atmos_and_temp_requirements + +/// Ensures only whitelisted planes can have TOPDOWN_LAYERing, and vis versa +/datum/unit_test/focus_only/topdown_filtering + +/// Catches any invalid footstep types set for humans +/datum/unit_test/focus_only/humanstep_validity + +/// Checks icon states generated at runtime are valid +/datum/unit_test/focus_only/runtime_icon_states + +/// Checks that foodtypes are the same for food whether it's spawned or crafted (with the exact required types) +/datum/unit_test/focus_only/check_foodtypes diff --git a/code/modules/unit_tests/font_awesome_icons.dm b/code/modules/unit_tests/font_awesome_icons.dm new file mode 100644 index 0000000000..6ee4372dc6 --- /dev/null +++ b/code/modules/unit_tests/font_awesome_icons.dm @@ -0,0 +1,99 @@ +/** + * This unit test verifies that all Font Awesome icons are present in code, and that all quirk icons are valid. + */ +/datum/unit_test/font_awesome_icons + var/font_awesome_css + var/list/allowed_icons + +/datum/unit_test/font_awesome_icons/Run() + var/font_awesome_file = file('html/font-awesome/css/all.min.css') + if(isnull(font_awesome_file)) + TEST_FAIL("Font Awesome CSS file could not be found!") + return + + font_awesome_css = file2text(font_awesome_file) + if(isnull(font_awesome_css)) + TEST_NOTICE(src, "Font Awesome CSS file could not be loaded.") + return + + load_parse_verify() + //verify_quirk_icons() + generate_helper_dm_file() + +/** + * Loads the Font Awesome CSS file, parses it into a list of icon names, and compares it to the list of icons in code. + * If there are any differences, note them. + */ +/datum/unit_test/font_awesome_icons/proc/load_parse_verify() + //log_test("CSS Actual: [length(font_awesome_css)]") + log_unit_test("CSS Actual: [length(font_awesome_css)]") + allowed_icons = parse_fa_css_into_icon_list(font_awesome_css) + +/** + * Verifies that all quirk icons are valid. + */ +/* NOT IMPLEMENTED +/datum/unit_test/font_awesome_icons/proc/verify_quirk_icons() + for(var/datum/quirk/quirk as anything in subtypesof(/datum/quirk)) + if(quirk == initial(quirk.abstract_parent_type)) + continue + + var/quirk_icon = initial(quirk.icon) + if(findtext(quirk_icon, "tg-") == 1) // TODO: Validate these as well + continue + + if(findtext(quirk_icon, " ")) + var/list/split = splittext(quirk_icon, " ") + quirk_icon = split[length(split)] // respect modifier classes + + if(!(quirk_icon in allowed_icons)) + TEST_FAIL("Quirk [initial(quirk.name)]([quirk]) has invalid icon: [quirk_icon]") +*/ + +/// Parses the given Font Awesome CSS file into a list of icon names. +/datum/unit_test/font_awesome_icons/proc/parse_fa_css_into_icon_list(css) + css = replacetext(css, "\n", "") + var/list/css_entries = splittext(css, "}") + var/list/icons = list() + for(var/entry in css_entries) + entry = replacetext(entry, "\t", "") + if(!length(entry)) + continue + + var/entry_contents = splittext(entry, "{") + var/list/entry_names = splittext(entry_contents[1], ",") + for(var/entry_name in entry_names) + entry_names -= entry_name + + if(!findtext(entry_name, ":")) + continue + + entry_name = splittext(entry_name, ":")[1] + if(!findtext(entry_name, ".fa-")) + continue + + entry_name = replacetext(entry_name, ".fa-", "fa-") + entry_names |= entry_name + icons |= entry_names + + return sortList(icons) + +/datum/unit_test/font_awesome_icons/proc/generate_helper_dm_file() + var/list/output = list() + output += "/* This file is automatically generated by the unit test. Do not edit it manually." + output += " * Generating this file is done by running the unit test locally, see the fail message for more details." + output += " * All valid font awesome icons should be here." + output += " */" + output += "" + + for(var/icon in allowed_icons) + var/icon_name = replacetext(icon, "fa-", "") + output += "#define FA_ICON_[uppertext(replacetext(icon_name, "-", "_"))] \"[icon]\"" // #undef FA_ICON_ // we have this here to avoid define_sanity throwing a fit + + var/output_file = "[output.Join("\n")]\n" + rustg_file_write(output_file, "data/font_awesome_icons.dm") + var/current = file2text('code/__DEFINES/font_awesome_icons.dm') + if(current == output_file) + return + + TEST_FAIL("Font Awesome helper file is out of date. Run locally by enabling unit tests, (see _compile_options.dm) and copy 'data/font_awesome_icons.dm' to 'code/__DEFINES/font_awesome_icons.dm'") diff --git a/code/modules/unit_tests/genetics_tests.dm b/code/modules/unit_tests/genetics_tests.dm new file mode 100644 index 0000000000..2c39d20ea9 --- /dev/null +++ b/code/modules/unit_tests/genetics_tests.dm @@ -0,0 +1,69 @@ +/// converted unit test, maybe should be fully refactored + +/// Test that there are enough free gene slots available +/datum/unit_test/enough_free_gene_slots_must_be_available + +/datum/unit_test/enough_free_gene_slots_must_be_available/Run() + // Based off of traitgenes scanned on startup + TEST_ASSERT(!(GLOB.dna_genes.len > (DNA_SE_LENGTH - 10)), "Too few geneslots are empty, minimum 10. Increase DNA_SE_LENGTH.") + +/// Test that there is at least one positive gene +/datum/unit_test/enough_positive_genes_must_exist + +/datum/unit_test/enough_positive_genes_must_exist/Run() + // Based off of traitgenes scanned on startup + TEST_ASSERT(!(GLOB.dna_genes_good.len < 1), "Must have at least one positive gene.") + +/// Test that there is at least one neutral gene +/datum/unit_test/enough_neutral_genes_must_exist + +/datum/unit_test/enough_neutral_genes_must_exist/Run() + // Based off of traitgenes scanned on startup + TEST_ASSERT(!(GLOB.dna_genes_neutral.len < 1), "Must have at least one neutral gene.") + +/// Test that there is at least one bad gene +/datum/unit_test/enough_bad_genes_must_exist + +/datum/unit_test/enough_bad_genes_must_exist/Run() + // Based off of traitgenes scanned on startup + TEST_ASSERT(!(GLOB.dna_genes_bad.len < 1), "Must have at least one bad gene.") + +/// Test that all dna injectors are valid +/datum/unit_test/all_dna_injectors_must_be_valid + +/datum/unit_test/all_dna_injectors_must_be_valid/Run() + for(var/injector_path in subtypesof(/obj/item/dnainjector/set_trait)) + var/obj/item/dnainjector/D = new injector_path() + TEST_ASSERT(D.block, "[injector_path]: Genetics - Injector could not resolve geneblock for trait. Missing traitgene?") + qdel(D) + +/// Test that all genes have unique names +/datum/unit_test/all_genes_shall_have_unique_name + +/datum/unit_test/all_genes_shall_have_unique_name/Run() + var/collection = list() + for(var/datum/gene/G in GLOB.dna_genes) + TEST_ASSERT(!collection[G.name], "[G.name]: Genetics - Gene name was already in use.") + collection[G.name] = G.name + +/// Test that all genes should have valid activation bounds +/datum/unit_test/genetraits_should_have_valid_dna_bounds + +/datum/unit_test/genetraits_should_have_valid_dna_bounds/Run() + for(var/datum/gene/trait/G in GLOB.trait_to_dna_genes) + TEST_ASSERT(G.linked_trait, "[G.name]: Genetics - Has missing linked trait.") + TEST_ASSERT(G.linked_trait.activity_bounds, "[G.name]: Genetics - Has no activation bounds.") + TEST_ASSERT(G.linked_trait.activity_bounds.len, "[G.name]: Genetics - Has empty activation bounds.") + + // DNA activation bounds. Usually they are in a list as follows: + // [1]DNA_OFF_LOWERBOUND = 1, begining of the threshold where a gene turns off. + // [2]DNA_OFF_UPPERBOUND = a number above 1, end of the treshold where a gene turns off. + // [3]DNA_ON_LOWERBOUND = a number above DNA_OFF_UPPERBOUND(even if just by 1), threshold where a gene turns on. + // [4]DNA_ON_UPPERBOUND = 4095, end of the threshold where a gene turns on. + + var/list/bounds = G.linked_trait.activity_bounds + TEST_ASSERT(bounds[1] > 1, "[G.name]: Genetics - DNA_OFF_LOWERBOUND, was smaller than 1.") // lowest value a gene can be to turn off + TEST_ASSERT(bounds[2] > bounds[1], "[G.name]: Genetics - DNA_OFF_UPPERBOUND must be larger than DNA_OFF_LOWERBOUND, and never equal.") + TEST_ASSERT(bounds[2] <= bounds[3], "[G.name]: Genetics - DNA_OFF_UPPERBOUND must be smaller than DNA_ON_LOWERBOUND, and never equal.") + TEST_ASSERT(bounds[3] < bounds[4], "[G.name]: Genetics - DNA_ON_LOWERBOUND must be smaller than DNA_ON_UPPERBOUND, and never equal.") + TEST_ASSERT(bounds[4] < 4095, "[G.name]: Genetics - DNA_ON_UPPERBOUND, was larger than 4095.") // highest value a gene can be to turn on diff --git a/code/unit_tests/language_tests.dm b/code/modules/unit_tests/language_tests.dm similarity index 60% rename from code/unit_tests/language_tests.dm rename to code/modules/unit_tests/language_tests.dm index a45add0342..6d22e66e5b 100644 --- a/code/unit_tests/language_tests.dm +++ b/code/modules/unit_tests/language_tests.dm @@ -1,29 +1,25 @@ -/datum/unit_test/language_test_shall_have_distinct_names - name = "LANGUAGES: Entries shall have distinct names" +/// converted unit test, maybe should be fully refactored -/datum/unit_test/language_test_shall_have_distinct_names/start_test() +/// Test that language entries have distinct names +/datum/unit_test/language_test_shall_have_distinct_names + +/datum/unit_test/language_test_shall_have_distinct_names/Run() if(length(GLOB.language_name_conflicts) != 0) var/list/name_conflict_log = list() for(var/conflicted_name in GLOB.language_name_conflicts) name_conflict_log += "+[length(GLOB.language_name_conflicts[conflicted_name])] languages with name \"[conflicted_name]\"!" for(var/datum/language/L in GLOB.language_name_conflicts[conflicted_name]) name_conflict_log += "+-+[L.type]" - fail("Some names are used by more than one language:\n" + name_conflict_log.Join("\n")) - else - pass("All languages have distinct names") - return 1 + TEST_FAIL("Some names are used by more than one language:\n" + name_conflict_log.Join("\n")) +/// Test that language entries have distinct keys /datum/unit_test/language_test_shall_have_distinct_keys - name = "LANGUAGES: Entries shall have distinct keys" -/datum/unit_test/language_test_shall_have_distinct_keys/start_test() +/datum/unit_test/language_test_shall_have_distinct_keys/Run() if(length(GLOB.language_key_conflicts) != 0) var/list/key_conflict_log = list() for(var/conflicted_key in GLOB.language_key_conflicts) key_conflict_log += "+[length(GLOB.language_key_conflicts[conflicted_key])] languages with key \"[conflicted_key]\"!" for(var/datum/language/L in GLOB.language_key_conflicts[conflicted_key]) key_conflict_log += "+-+[L]([L.type])" - fail("Some keys are used by more than one language:\n" + key_conflict_log.Join("\n")) - else - pass("All languages in GLOB.all_languages have distinct keys") - return 1 + TEST_FAIL("Some keys are used by more than one language:\n" + key_conflict_log.Join("\n")) diff --git a/code/modules/unit_tests/loadout_tests.dm b/code/modules/unit_tests/loadout_tests.dm new file mode 100644 index 0000000000..04c8e60280 --- /dev/null +++ b/code/modules/unit_tests/loadout_tests.dm @@ -0,0 +1,7 @@ +/// converted unit test, maybe should be fully refactored + +/datum/unit_test/loadout_tests/Run() + for(var/datum/gear/G as anything in subtypesof(/datum/gear)) + TEST_ASSERT(initial(G.display_name), "[G]: Loadout - Missing display name.") + TEST_ASSERT_NOTNULL(initial(G.cost), "[G]: Loadout - Missing cost.") + TEST_ASSERT(initial(G.path), "[G]: Loadout - Missing path definition.") diff --git a/code/unit_tests/map_tests.dm b/code/modules/unit_tests/map_tests.dm similarity index 60% rename from code/unit_tests/map_tests.dm rename to code/modules/unit_tests/map_tests.dm index be51e6f9f4..b4ca7ef251 100644 --- a/code/unit_tests/map_tests.dm +++ b/code/modules/unit_tests/map_tests.dm @@ -1,9 +1,10 @@ -/datum/unit_test/apc_area_test - name = "MAP: Area Test APC / Scrubbers / Vents (Defined Z-Levels)" +/// converted unit test, maybe should be fully refactored +/// MIGHT REQUIRE BIGGER REWORK -/datum/unit_test/apc_area_test/start_test() - var/list/bad_areas = list() - var/area_test_count = 0 +/// Test that tests the apcs, scrubbers and vents of the defined z-levels +/datum/unit_test/apc_area_test + +/datum/unit_test/apc_area_test/Run() var/list/exempt_areas = typesof(/area/space, /area/syndicate_station, /area/skipjack_station, @@ -14,7 +15,8 @@ /area/mine, /area/vacant/vacant_shop, /area/turbolift, - /area/submap ) + /area/submap + ) var/list/exempt_from_atmos = typesof(/area/maintenance, /area/storage, @@ -43,58 +45,38 @@ for(var/area/A in world) if((A.z in zs_to_test) && !(A.type in exempt_areas)) - area_test_count++ - var/area_good = 1 var/bad_msg = "--------------- [A.name]([A.type])" // Scan for areas with extra APCs if(!(A.type in exempt_from_apc)) - if(isnull(A.apc)) - log_unit_test("[bad_msg] lacks an APC. (X[A.x]|Y[A.y]) - Z[A.z])") - area_good = 0 - else + TEST_ASSERT_NOTNULL(A.apc, "[bad_msg] lacks an APC. (X[A.x]|Y[A.y]) - Z[A.z])") + + if(!isnull(A.apc)) var/list/apc_list = list() for(var/turf/T in get_current_area_turfs(A)) for(var/atom/S in T.contents) if(istype(S,/obj/machinery/power/apc)) apc_list.Add(S) if(apc_list.len > 1) - area_good = 0 for(var/obj/machinery/power/P in apc_list) - log_unit_test("[bad_msg] has too many APCs. (X[P.x]|Y[P.y]) - Z[P.z])") + TEST_FAIL("[bad_msg] has too many APCs. (X[P.x]|Y[P.y]) - Z[P.z])") - if(!A.air_scrub_info.len && !(A.type in exempt_from_atmos)) - log_unit_test("[bad_msg] lacks an Air scrubber. (X[A.x]|Y[A.y]) - (Z[A.z])") - area_good = 0 - - if(!A.air_vent_info.len && !(A.type in exempt_from_atmos)) - log_unit_test("[bad_msg] lacks an Air vent. (X[A.x]|Y[A.y]) - (Z[A.z])") - area_good = 0 - - if(!area_good) - bad_areas.Add(A) - - if(bad_areas.len) - fail("\[[bad_areas.len]/[area_test_count]\]Some areas lacked APCs, Air Scrubbers, or Air vents.") - else - pass("All \[[area_test_count]\] areas contained APCs, Air scrubbers, and Air vents.") - - return 1 + TEST_ASSERT(!(!A.air_scrub_info.len && !(A.type in exempt_from_atmos)), "[bad_msg] lacks an Air scrubber. (X[A.x]|Y[A.y]) - (Z[A.z])") + TEST_ASSERT(!(!A.air_vent_info.len && !(A.type in exempt_from_atmos)), "[bad_msg] lacks an Air vent. (X[A.x]|Y[A.y]) - (Z[A.z])") +/// Test that tests cables on defined z-levels /datum/unit_test/wire_test - name = "MAP: Cable Test (Defined Z-Levels)" - -/datum/unit_test/wire_test/start_test() - set background=1 - var/wire_test_count = 0 - var/bad_tests = 0 var/turf/T = null var/obj/structure/cable/C = null var/list/cable_turfs = list() var/list/dirs_checked = list() var/list/exempt_from_wires = list() + +/datum/unit_test/wire_test/Run() + set background = 1 + exempt_from_wires += using_map.unit_test_exempt_from_wires.Copy() var/list/zs_to_test = using_map.unit_test_z_levels || list(1) //Either you set it, or you just get z1 @@ -117,31 +99,16 @@ for(C in T) wire_test_count++ var/combined_dir = "[C.d1]-[C.d2]" - if(combined_dir in dirs_checked) - bad_tests++ - log_unit_test("[bad_msg] Contains multiple wires with same direction on top of each other.") - if(C.dir != SOUTH) - bad_tests++ - log_unit_test("[bad_msg] Contains wire with dir set, wires MUST face south, use icon_states.") + TEST_ASSERT(!(combined_dir in dirs_checked), "[bad_msg] Contains multiple wires with same direction on top of each other.") + TEST_ASSERT(C.dir == SOUTH, "[bad_msg] Contains wire with dir set, wires MUST face south, use icon_states.") dirs_checked.Add(combined_dir) - log_unit_test("[color] wires checked.") - - if(bad_tests) - fail("\[[bad_tests] / [wire_test_count]\] Some turfs had overlapping wires going the same direction.") - else - pass("All \[[wire_test_count]\] wires had no overlapping cables going the same direction.") - - return 1 - +/// Test template no-ops on all maps /datum/unit_test/template_noops - name = "MAP: Template no-ops (all maps)" - -/datum/unit_test/template_noops/start_test() - var/list/log = list() - var/turf_noop_count = 0 + +/datum/unit_test/template_noops/Run() for(var/turf/template_noop/T in world) turf_noop_count++ log += "+-- Template Turf @ [T.x], [T.y], [T.z] ([T.loc])" @@ -152,19 +119,15 @@ log += "+-- Template Area" if(turf_noop_count || area_noop_count) - fail("Map contained [turf_noop_count] template turfs and [area_noop_count] template areas at round-start.\n" + log.Join("\n")) - else - pass("No template turfs or areas.") - - return 1 + TEST_FAIL("Map contained [turf_noop_count] template turfs and [area_noop_count] template areas at round-start.\n" + log.Join("\n")) +/// Test active edges on all maps /datum/unit_test/active_edges - name = "MAP: Active edges (all maps)" - -/datum/unit_test/active_edges/start_test() +/datum/unit_test/active_edges/Run() var/active_edges = SSair.active_edges.len var/list/edge_log = list() + if(active_edges) for(var/connection_edge/E in SSair.active_edges) var/a_temp = E.A.air.temperature @@ -205,38 +168,22 @@ edge_log += "+--- Connecting Turf [T] ([T.type]) @ [T.x], [T.y], [T.z] ([T.loc])" if(active_edges) - fail("Maps contained [active_edges] active edges at round-start.\n" + edge_log.Join("\n")) - else - pass("No active edges.") - - return 1 + TEST_FAIL("Maps contained [active_edges] active edges at round-start.\n" + edge_log.Join("\n")) +/// Test the ladders on the maps /datum/unit_test/ladder_test - name = "MAP: Ladder Test" - -/datum/unit_test/ladder_test/start_test() var/failed = FALSE +/datum/unit_test/ladder_test/Run() for(var/obj/structure/ladder/L in world) var/turf/T = get_turf(L) + TEST_ASSERT(T, "[L.x].[L.y].[L.z]: Map - Ladder on invalid turf") if(!T) - log_unit_test("[L.x].[L.y].[L.z]: Map - Ladder on invalid turf") - failed = TRUE continue - if(L.allowed_directions & UP) - if(!L.target_up) - log_unit_test("[T.x].[T.y].[T.z]: Map - Ladder allows upward movement, but had no ladder above it") - failed = TRUE - if(L.allowed_directions & DOWN) - if(!L.target_down) - log_unit_test("[T.x].[T.y].[T.z]: Map - Ladder allows downward movement, but had no ladder beneath it") - failed = TRUE - if(T.density) - log_unit_test("[L.x].[L.y].[L.z]: Map - Ladder is inside a wall") - failed = TRUE - if(failed) - fail("Ladders were incorrectly placed, or missing connections.") - else - pass("All ladders were correctly placed and had connections.") - return failed + if(L.allowed_directions & UP) + TEST_ASSERT(L.target_up, "[T.x].[T.y].[T.z]: Map - Ladder allows upward movement, but had no ladder above it") + if(L.allowed_directions & DOWN) + TEST_ASSERT(L.target_down, "[T.x].[T.y].[T.z]: Map - Ladder allows downward movement, but had no ladder beneath it") + + TEST_ASSERT(!T.density, "[L.x].[L.y].[L.z]: Map - Ladder is inside a wall") diff --git a/code/unit_tests/material_tests.dm b/code/modules/unit_tests/material_tests.dm similarity index 60% rename from code/unit_tests/material_tests.dm rename to code/modules/unit_tests/material_tests.dm index e3c80b6cd3..d276f2ff07 100644 --- a/code/unit_tests/material_tests.dm +++ b/code/modules/unit_tests/material_tests.dm @@ -1,7 +1,9 @@ -/datum/unit_test/materials_shall_have_names - name = "MATERIALS: Materials Shall Have All Names" +/// converted unit test, maybe should be fully refactored -/datum/unit_test/materials_shall_have_names/start_test() +/// Test that a material should have all the name variables set +/datum/unit_test/materials_shall_have_names + +/datum/unit_test/materials_shall_have_names/Run() var/list/failures = list() populate_material_list() for(var/name in global.name_to_material) @@ -12,8 +14,4 @@ failures[name] = mat.type if(length(failures)) - fail("[length(failures)] material\s had missing name strings: [english_list(failures)].") - else - pass("All materials had all their name strings.") - - return TRUE + TEST_FAIL("[length(failures)] material\s had missing name strings: [english_list(failures)].") diff --git a/code/modules/unit_tests/nuke_cinematic.dm b/code/modules/unit_tests/nuke_cinematic.dm new file mode 100644 index 0000000000..067a11dccc --- /dev/null +++ b/code/modules/unit_tests/nuke_cinematic.dm @@ -0,0 +1,68 @@ +// Some defines for tracking if the correct cinematic / animation is playing. +#define PLAYING_CORRECT_ANIMATION 2 +#define PLAYING_INCORRECT_NUKE_ANIMATION 1 +#define NOT_PLAYING_ANIMATION 0 + +/** + * Unit tests that a nuke going off plays a cinematic, + * and that it actually kills people. + */ +/datum/unit_test/nuke_cinematic + /// Used to track via signal if the correct cinematic / animation is playing. + var/cinematic_playing = NOT_PLAYING_ANIMATION + /// Tracks what typepath of cinematic is being played. + var/cinematic_playing_type + +/datum/unit_test/nuke_cinematic/Run() + var/obj/machinery/nuclearbomb/syndicate/nuke = allocate(/obj/machinery/nuclearbomb/syndicate) + var/mob/living/carbon/human/nuked = allocate(/mob/living/carbon/human/consistent) + var/datum/client_interface/mock_client = new + nuked.mock_client = mock_client + mock_client.mob = nuked + + var/obj/effect/landmark/observer_start/observer_point = locate(/obj/effect/landmark/observer_start) in landmarks_list + TEST_ASSERT_NOTNULL(observer_point, "Nuke cinematic test couldn't find observer spawn to place the nuke.") + + var/turf/turf_on_station = get_turf(observer_point) + TEST_ASSERT(is_station_level(turf_on_station.z), "Nuke cinematic test didn't get a turf which was located on the station.") + + nuke.forceMove(turf_on_station) + nuked.forceMove(turf_on_station) + + // Pause the check so we don't, y'know, end the round + SSticker.roundend_check_paused = TRUE + RegisterSignal(SSdcs, COMSIG_GLOB_PLAY_CINEMATIC, PROC_REF(check_cinematic)) + // actually_explode calls really_actually_explode which sleeps, so this will take a moment. + var/nuke_result = nuke.actually_explode() + + TEST_ASSERT_EQUAL(nuke_result, DETONATION_HIT_STATION, "A nuke went off on station, but didn't return DETONATION_HIT_STATION (4). (Got: [nuke_result])") + TEST_ASSERT(GLOB.station_was_nuked, "A nuke went off on station, but didn't set station_was_nuked.") + // Reset the nuke var back so we don't end the round + GLOB.station_was_nuked = FALSE + SSticker.roundend_check_paused = FALSE + + switch(cinematic_playing) + if(NOT_PLAYING_ANIMATION) + TEST_FAIL("No nuke cinematic was played when a nuke was detonated.") + + if(PLAYING_INCORRECT_NUKE_ANIMATION) + TEST_FAIL("An incorrect cinematic was played on nuke detonation. (Expected: /datum/cinematic/nuke/self_destruct, Got: [cinematic_playing_type])") + + TEST_ASSERT(QDELETED(nuked), "The nuke victim next to the nuke wasn't gibbed by the nuke.") + TEST_ASSERT(QDELETED(nuke), "The nuke itself was not deleted after successfully exploding.") + mock_client.mob = null + +/// Used to track whenever a cinematic starts playing, so we can check if it's the right one. +/datum/unit_test/nuke_cinematic/proc/check_cinematic(datum/source, datum/cinematic/playing) + SIGNAL_HANDLER + + cinematic_playing_type = playing.type + if(istype(playing, /datum/cinematic/nuke/self_destruct)) + cinematic_playing = PLAYING_CORRECT_ANIMATION + + else if(istype(playing, /datum/cinematic/nuke)) + cinematic_playing = PLAYING_INCORRECT_NUKE_ANIMATION + +#undef PLAYING_CORRECT_ANIMATION +#undef PLAYING_INCORRECT_NUKE_ANIMATION +#undef NOT_PLAYING_ANIMATION diff --git a/code/modules/unit_tests/poster_tests.dm b/code/modules/unit_tests/poster_tests.dm new file mode 100644 index 0000000000..64e1ca88d6 --- /dev/null +++ b/code/modules/unit_tests/poster_tests.dm @@ -0,0 +1,13 @@ +/// converted unit test, maybe should be fully refactored + +/datum/unit_test/posters_shall_have_legal_states/Run() + var/list/all_posters = decls_repository.get_decls_of_type(/decl/poster) + all_posters -= decls_repository.get_decl(/decl/poster/lewd) // Dumb exclusion for now. This really needs to become a valid poster instead of an illegaly made base type + + for(var/path in all_posters) + var/decl/poster/D = all_posters[path] + var/obj/structure/sign/poster/P = /obj/structure/sign/poster // The base poster shows ALL subtypes except /lewd, so all posters should function here regardless! + var/icon/I = initial(P.icon) + if(D.icon_override) + I = D.icon_override + TEST_ASSERT(D.icon_state in cached_icon_states(I), "[D.type]: Poster - missing icon_state \"[D.icon_state]\" in \"[I]\", as [D.icon_override ? "override" : "base"] dmi.") diff --git a/code/modules/unit_tests/preferences.dm b/code/modules/unit_tests/preferences.dm new file mode 100644 index 0000000000..32a8542be2 --- /dev/null +++ b/code/modules/unit_tests/preferences.dm @@ -0,0 +1,77 @@ +/// Requires all preferences to implement required methods. +/datum/unit_test/preferences_implement_everything + +/datum/unit_test/preferences_implement_everything/Run() + var/datum/preferences/preferences = new(new /datum/client_interface) + var/mob/living/carbon/human/human = allocate(/mob/living/carbon/human/consistent) + + for (var/preference_type in GLOB.preference_entries) + var/datum/preference/preference = GLOB.preference_entries[preference_type] + if (preference.savefile_identifier == PREFERENCE_CHARACTER) + preference.apply_to_human(human, preference.create_informed_default_value(preferences)) + + if (istype(preference, /datum/preference/choiced)) + var/datum/preference/choiced/choiced_preference = preference + choiced_preference.init_possible_values() + + // Smoke-test is_valid + preference.is_valid(TRUE) + preference.is_valid("string") + preference.is_valid(100) + preference.is_valid(list(1, 2, 3)) + +/// Requires all preferences to have a valid, unique savefile_identifier. +/datum/unit_test/preferences_valid_savefile_key + +/datum/unit_test/preferences_valid_savefile_key/Run() + var/list/known_savefile_keys = list() + + for (var/preference_type in GLOB.preference_entries) + var/datum/preference/preference = GLOB.preference_entries[preference_type] + if (!istext(preference.savefile_key)) + TEST_FAIL("[preference_type] has an invalid savefile_key.") + + if (preference.savefile_key in known_savefile_keys) + TEST_FAIL("[preference_type] has a non-unique savefile_key `[preference.savefile_key]`!") + + known_savefile_keys += preference.savefile_key + +/// Requires all main features have a main_feature_name +/datum/unit_test/preferences_valid_main_feature_name + +/datum/unit_test/preferences_valid_main_feature_name/Run() + for (var/preference_type in GLOB.preference_entries) + var/datum/preference/choiced/preference = GLOB.preference_entries[preference_type] + if (!istype(preference)) + continue + + if (preference.category != PREFERENCE_CATEGORY_FEATURES && preference.category != PREFERENCE_CATEGORY_CLOTHING) + continue + + TEST_ASSERT(!isnull(preference.main_feature_name), "Preference [preference_type] does not have a main_feature_name set!") + +/// Validates that every choiced preference with should_generate_icons implements icon_for, +/// and that every one that doesn't, doesn't. +/datum/unit_test/preferences_should_generate_icons_sanity + +/datum/unit_test/preferences_should_generate_icons_sanity/Run() + for (var/preference_type in GLOB.preference_entries) + var/datum/preference/choiced/choiced_preference = GLOB.preference_entries[preference_type] + if (!istype(choiced_preference) || choiced_preference.abstract_type == preference_type) + continue + + var/list/values = choiced_preference.get_choices() + + if (choiced_preference.should_generate_icons) + for (var/value in values) + var/icon = choiced_preference.icon_for(value) + TEST_ASSERT(istype(icon, /datum/universal_icon) || ispath(icon), "[preference_type] gave [icon] as an icon for [value], which is not a valid value") + else + var/errored = FALSE + + try + choiced_preference.icon_for(values[1]) + catch + errored = TRUE + + TEST_ASSERT(errored, "[preference_type] implemented icon_for, but does not have should_generate_icons = TRUE") diff --git a/code/modules/unit_tests/reagent_tests.dm b/code/modules/unit_tests/reagent_tests.dm new file mode 100644 index 0000000000..e1e8a64cad --- /dev/null +++ b/code/modules/unit_tests/reagent_tests.dm @@ -0,0 +1,251 @@ +/// converted unit test, maybe should be fully refactored +/// MIGHT REQUIRE BIGGER REWORK + +/// Test that makes sure that reagent ids and names are unique +/datum/unit_test/reagent_shall_have_unique_name_and_id + +/datum/unit_test/reagent_shall_have_unique_name_and_id/Run() + var/collection_name = list() + var/collection_id = list() + + for(var/Rpath in subtypesof(/datum/reagent)) + var/datum/reagent/R = new Rpath() + + if(R.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden + continue + + TEST_ASSERT(R.name != "", "[Rpath]: Reagents - reagent name blank.") + TEST_ASSERT_NOTEQUAL(R.id, REAGENT_ID_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent ID not set.") + TEST_ASSERT_NOTEQUAL(R.description, REAGENT_DESC_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent description unset.") + + TEST_ASSERT(R.id != "", "[Rpath]: Reagents - reagent ID blank.") + TEST_ASSERT_EQUAL(R.id, lowertext(R.id), "[Rpath]: Reagents - Reagent ID must be all lowercase.") + + if(!(R.wiki_flag & WIKI_SPOILER)) // If wiki hidden then don't conflict test it against name, used for intentionally copied names like beer2's + TEST_ASSERT(!collection_name[R.name], "[Rpath]: Reagents - reagent name \"[R.name]\" is not unique, used first in [collection_name[R.name]].") + collection_name[R.name] = R.type + + TEST_ASSERT(!collection_id[R.id], "[Rpath]: Reagents - reagent ID \"[R.id]\" is not unique, used first in [collection_id[R.id]].") + collection_id[R.id] = R.type + + TEST_ASSERT(R.supply_conversion_value, "[Rpath]: Reagents - reagent ID \"[R.id]\" does not have supply_conversion_value set.") + TEST_ASSERT(R.industrial_use && R.industrial_use != "", "[Rpath]: Reagents - reagent ID \"[R.id]\" does not have industrial_use set.") + TEST_ASSERT_NOTEQUAL(R.description, REAGENT_DESC_DEVELOPER_WARNING, "[Rpath]: Reagents - reagent description unset.") + + qdel(R) + +/// Test that makes sure that chemical reactions use and produce valid reagents +/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents + +/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents/Run() + var/list/collection_id = list() + + var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction) + for(var/rtype in all_reactions) + var/decl/chemical_reaction/CR = all_reactions[rtype] + if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden + continue + + TEST_ASSERT_NOTNULL(CR.name, "[CR.type]: Reagents - chemical reaction had null name.") + TEST_ASSERT(CR.name != "", "[CR.type]: Reagents - chemical reaction had blank name.") + TEST_ASSERT(CR.id, "[CR.type]: Reagents - chemical reaction had invalid ID.") + TEST_ASSERT_EQUAL(CR.id, lowertext(CR.id), "[CR.type]: Reagents - chemical reaction ID must be all lowercase.") + TEST_ASSERT(!(CR.id in collection_id), "[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" is not unique, used first in [collection_id[CR.id]].") + + + if(!(CR.id in collection_id)) + collection_id[CR.id] = CR.type + + TEST_ASSERT(CR.result_amount >= 0, "[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" had less than 0 as as result_amount?") + + if(CR.required_reagents && CR.required_reagents.len) + for(var/RR in CR.required_reagents) + TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".") + TEST_ASSERT(CR.required_reagents[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid required reagent amount or in invalid format \"[CR.required_reagents[RR]]\".") + + if(CR.catalysts && CR.catalysts.len) + for(var/RR in CR.catalysts) + TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".") + TEST_ASSERT(CR.catalysts[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid catalysts amount or in invalid format \"[CR.catalysts[RR]]\".") + + if(CR.inhibitors && CR.inhibitors.len) + for(var/RR in CR.inhibitors) + TEST_ASSERT(SSchemistry.chemical_reagents[RR], "[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".") + TEST_ASSERT(CR.inhibitors[RR] > 0, "[CR.type]: Reagents - chemical reaction had invalid inhibitors amount or in invalid format \"[CR.inhibitors[RR]]\".") + + if(CR.result) + TEST_ASSERT(SSchemistry.chemical_reagents[CR.result], "[CR.type]: Reagents - chemical reaction had invalid result reagent ID \"[CR.result]\".") + +/// Test that makes sure that prefilled reagent containers have valid reagents +/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents + +/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents/Run() + var/obj/container = new /obj + for(var/RC in subtypesof(/obj/item/reagent_containers/glass)) + var/obj/item/reagent_containers/glass/R = new RC(container) + + if(R.prefill && R.prefill.len) + for(var/ID in R.prefill) + TEST_ASSERT(SSchemistry.chemical_reagents[ID], "[RC]: Reagents - reagent prefill had invalid reagent ID \"[ID]\".") + + qdel(R) + + for(var/DC in subtypesof(/obj/item/reagent_containers/chem_disp_cartridge)) + var/obj/item/reagent_containers/chem_disp_cartridge/D = new DC(container) + + if(D.spawn_reagent) + TEST_ASSERT(SSchemistry.chemical_reagents[D.spawn_reagent], "[DC]: Reagents - chemical dispenser cartridge had invalid reagent ID \"[D.spawn_reagent]\".") + + qdel(D) + + qdel(container) + +/// Test that makes sure that chemical reactions do not conflict +/datum/unit_test/chemical_reactions_shall_not_conflict + var/obj/fake_beaker = null + var/list/result_reactions = list() + +/datum/unit_test/chemical_reactions_shall_not_conflict/Run() + var/failed = FALSE + + #ifdef UNIT_TEST + var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction) + for(var/rtype in all_reactions) + var/decl/chemical_reaction/CR = all_reactions[rtype] + + if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden + continue + if(!CR.name || CR.name == "" || !CR.id || CR.id == "") + continue + if(CR.result_amount <= 0) //Makes nothing anyway, or maybe an effect/explosion! + continue + if(!CR.result) // Cannot check for this + continue + + if(istype(CR, /decl/chemical_reaction/instant/slime)) + // slime time + var/decl/chemical_reaction/instant/slime/SR = CR + if(!SR.required) + continue + var/obj/item/slime_extract/E = new SR.required() + qdel_swap(fake_beaker, E) + fake_beaker.reagents.maximum_volume = 5000 + else if(istype(CR, /decl/chemical_reaction/distilling)) + // distilling + var/obj/distilling_tester/D = new() + qdel_swap(fake_beaker, D) + fake_beaker.reagents.maximum_volume = 5000 + else + // regular beaker + qdel_swap(fake_beaker, new /obj/item/reagent_containers/glass/beaker()) + fake_beaker.reagents.maximum_volume = 5000 + + // Perform test! If it fails once, it will perform a deeper check trying to use the inhibitors of anything in the beaker + RegisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA, PROC_REF(get_signal_data)) + + // Check if we failed the test with inhibitors in use, if so we absolutely couldn't make it... + // Uncomment the UNIT_TEST section in code\modules\reagents\reactions\_reactions.dm if you require more info + TEST_ASSERT(!perform_reaction(CR), "[CR.type]: Reagents - chemical reaction did not produce \"[CR.result]\". CONTAINS: \"[fake_beaker.reagents.get_reagents()]\"") + UnregisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA) + qdel_null(fake_beaker) + #endif + + if(failed) + TEST_FAIL("One or more /decl/chemical_reaction subtypes conflict with another reaction.") + +/datum/unit_test/chemical_reactions_shall_not_conflict/proc/perform_reaction(var/decl/chemical_reaction/CR, var/list/inhib = list()) + var/scale = 1 + if(CR.result_amount < 1) + scale = 1 / CR.result_amount // Create at least 1 unit + + // Weird loop here, but this is used to test both instant and distilling reactions + // Instants will meet the while() condition on the first loop and go to the next stuff + // but distilling will repeat over and over until the temperature test is finished! + var/temp_test = 0 + do + // clear for inhibitor searches + fake_beaker.reagents.clear_reagents() + result_reactions.Cut() + + if(inhib.len) // taken from argument and not reaction! Put in FIRST! + for(var/RR in inhib) + fake_beaker.reagents.add_reagent(RR, inhib[RR] * scale) + if(CR.catalysts) // Required for reaction + for(var/RR in CR.catalysts) + fake_beaker.reagents.add_reagent(RR, CR.catalysts[RR] * scale) + if(CR.required_reagents) + for(var/RR in CR.required_reagents) + fake_beaker.reagents.add_reagent(RR, CR.required_reagents[RR] * scale) + + if(!istype(CR, /decl/chemical_reaction/distilling)) + break // Skip the next section if we're not distilling + + // Check distillation at 10 points along its temperature range! + // This is so multiple reactions with the same requirements, but different temps, can be tested. + temp_test += 0.1 + var/obj/distilling_tester/DD = fake_beaker + DD.test_distilling(CR,temp_test) + + if(fake_beaker.reagents.has_reagent(CR.result)) + return FALSE // Distilling success + + while(temp_test > 1) + + // Check beaker to see if we reached our goal! + if(fake_beaker.reagents.has_reagent(CR.result)) + return FALSE // INSTANT SUCCESS! + + if(inhib.len) + // We've checked with inhibitors, so we're already in inhibitor checking phase. + // So we've absolutely failed this time. There is no way to make this... + return TRUE + + if(!result_reactions.len) + // Nothing to check for inhibitors... + for(var/decl/chemical_reaction/test_react in result_reactions) + TEST_FAIL("[CR.type]: Reagents - Used [test_react] but failed.") + return TRUE + + // Otherwise we check the resulting reagents and use their inhibitor this time! + for(var/decl/chemical_reaction/test_react in result_reactions) + if(!test_react) + continue + if(!test_react.inhibitors.len) + continue + // Test one by one + for(var/each in test_react.inhibitors) + if(!perform_reaction(CR, list("[each]" = test_react.inhibitors["[each]"]))) + return FALSE // SUCCESS using an inhibitor! + // Test all at once + if(!perform_reaction(CR, test_react.inhibitors)) + return FALSE // SUCCESS using all inhibitors! + + // No inhibiting reagent worked... + for(var/decl/chemical_reaction/test_react in result_reactions) + TEST_FAIL("[CR.type]: Reagents - Used [test_react] but failed.") + return TRUE + +/datum/unit_test/chemical_reactions_shall_not_conflict/proc/get_signal_data(atom/source, list/data = list()) + result_reactions.Add(data[1]) // Append the reactions that happened, then use that to check their inhibitors + +/// Test that makes sure that chemical grinding has valid results +/datum/unit_test/chemical_grinding_must_produce_valid_results + +/datum/unit_test/chemical_grinding_must_produce_valid_results/Run() + for(var/grind in GLOB.sheet_reagents + GLOB.ore_reagents) + var/list/results = GLOB.sheet_reagents[grind] + + if(!results) + results = GLOB.ore_reagents[grind] + + // Cursed test + TEST_ASSERT(!(!results || !islist(results)), "[grind]: Reagents - Grinding result had invalid list.") + if(!results || !islist(results)) + continue + + TEST_ASSERT(results.len, "[grind]: Reagents - Grinding result had empty.") + if(!results.len) + continue + + for(var/reg_id in results) + TEST_ASSERT(SSchemistry.chemical_reagents[reg_id], "[grind]: Reagents - Grinding result had invalid reagent id \"[reg_id]\".") diff --git a/code/modules/unit_tests/recipe_tests.dm b/code/modules/unit_tests/recipe_tests.dm new file mode 100644 index 0000000000..8dd4bb58a1 --- /dev/null +++ b/code/modules/unit_tests/recipe_tests.dm @@ -0,0 +1,9 @@ +/// converted unit test, maybe should be fully refactored + +/datum/unit_test/recipe_test/Run() + for(var/datum/recipe/R in subtypesof(/datum/recipe)) + TEST_ASSERT_NOTNULL(initial(R.result), "[R]: Recipes - Missing result.") + TEST_ASSERT(ispath(initial(R.result), /atom/movable), "[R]: Recipes - Improper result; [initial(R.result)] is not an obj or mob.") + TEST_ASSERT_NOTNULL(initial(R.result_quantity), "[R]: Recipes - result_quantity must be set.") + TEST_ASSERT(initial(R.result_quantity) <= 0, "[R]: Recipes - result_quantity must be greater than zero.") + TEST_ASSERT(ISINTEGER(initial(R.result_quantity)), "[R]: Recipes - result_quantity must be an integer.") diff --git a/code/modules/unit_tests/recycler_vendor_tests.dm b/code/modules/unit_tests/recycler_vendor_tests.dm new file mode 100644 index 0000000000..854e3b6166 --- /dev/null +++ b/code/modules/unit_tests/recycler_vendor_tests.dm @@ -0,0 +1,7 @@ +/// converted unit test, maybe should be fully refactored + +/datum/unit_test/recycler_vendor_tests/Run() + for(var/datum/maint_recycler_vendor_entry/R in subtypesof(/datum/maint_recycler_vendor_entry)) + TEST_ASSERT(!initial(R.object_type_to_spawn) && !initial(R.is_scam), "[R] : Vendor Entry - Missing Object Type on non-scam entry") + TEST_ASSERT(initial(R.item_cost) > 0, "[R] : Vendor Entry - Negative Cost") + TEST_ASSERT((initial(R.item_cost) == 0) && ((initial(R.per_round_cap) < 0) && (initial(R.per_person_cap) < 0)), "[R] : Vendor Entry - Infinite Item Spawning due to no individual or global item cap") diff --git a/code/modules/unit_tests/robot_tests.dm b/code/modules/unit_tests/robot_tests.dm new file mode 100644 index 0000000000..c7f4a4776e --- /dev/null +++ b/code/modules/unit_tests/robot_tests.dm @@ -0,0 +1,169 @@ +/// converted unit test, maybe should be fully refactored + +/// Test that all robot sprites are valid +/datum/unit_test/all_robot_sprites_must_be_valid + var/signal_failed = FALSE + var/failed = 0 + +/datum/unit_test/all_robot_sprites_must_be_valid/Run() + for(var/sprite in subtypesof(/datum/robot_sprite)) + var/datum/robot_sprite/RS = new sprite() + if(!RS.name) // Parent type, ignore me + continue + + TEST_ASSERT(RS.sprite_icon, "[RS.type]: Robots - Robot sprite \"[RS.name]\", missing sprite_icon.") + if(!RS.sprite_icon) + continue + + var/list/checks = list( + "[ROBOT_HAS_SPEED_SPRITE]" = "-roll", + "[ROBOT_HAS_SHIELD_SPRITE]" = "-shield", + "[ROBOT_HAS_SHIELD_SPEED_SPRITE]" = "-speed_shield", + "[ROBOT_HAS_MELEE_SPRITE]" = "-melee", + "[ROBOT_HAS_DAGGER_SPRITE]" = "-dagger", + "[ROBOT_HAS_BLADE_SPRITE]" = "-blade", + "[ROBOT_HAS_GUN_SPRITE]" = "-gun", + "[ROBOT_HAS_LASER_SPRITE]" = "-laser", + "[ROBOT_HAS_TASER_SPRITE]" = "-taser", + "[ROBOT_HAS_DISABLER_SPRITE]" = "-disabler" + ) + for(var/C in checks) + if(RS.sprite_flag_check(text2num(C))) + check_state(RS,checks[C]) + // eyes, lights, markings + if(RS.has_eye_sprites) + check_state(RS,"-eyes") + if(RS.has_eye_light_sprites) + check_state(RS,"-lights") + if(LAZYLEN(RS.sprite_decals)) + for(var/decal in RS.sprite_decals) + check_state(RS,"-[decal]") + if(LAZYLEN(RS.sprite_animations)) + for(var/animation in RS.sprite_animations) + check_state(RS,"-[animation]") + // Control panel + if(RS.has_custom_open_sprites) + check_state(RS,"-openpanel_nc") + check_state(RS,"-openpanel_c") + check_state(RS,"-openpanel_w") + // Glow State + if(RS.has_glow_sprites) + check_state(RS,"-glow") + // Bellies + if(RS.has_vore_belly_sprites && !RS.belly_capacity_list) + if(RS.has_sleeper_light_indicator) + // belly r/g light + check_state(RS,"-sleeper-r") + check_state(RS,"-sleeper-g") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-sleeper-r-[rest_style]") + check_state(RS,"-sleeper-g-[rest_style]") + // struggling + if(RS.has_vore_struggle_sprite) + check_state(RS,"-sleeper-r-struggle") + check_state(RS,"-sleeper-g-struggle") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-sleeper-r-[rest_style]-struggle") + check_state(RS,"-sleeper-g-[rest_style]-struggle") + else + // belly + check_state(RS,"-sleeper") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-sleeper-[rest_style]") + // struggling + if(RS.has_vore_struggle_sprite) + check_state(RS,"-sleeper-struggle") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-sleeper-[rest_style]-struggle") + else if (RS.belly_capacity_list) + for(var/belly in RS.belly_capacity_list) + for(var/num = 1 to RS.belly_capacity_list[belly]) + // big belly + check_state(RS,"-[belly]-[num]") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-[belly]-[num]-[rest_style]") + // struggling + if(RS.has_vore_struggle_sprite) + check_state(RS,"-[belly]-[num]-struggle") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-[belly]-[num]-[rest_style]-struggle") + if(RS.belly_light_list) + for(var/belly in RS.belly_light_list) + for(var/num = 1 to RS.belly_light_list[belly]) + // multi belly r/g light + check_state(RS,"-[belly]-[num]-r") + check_state(RS,"-[belly]-[num]-g") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-[belly]-[num]-r-[rest_style]") + check_state(RS,"-[belly]-[num]-g-[rest_style]") + // struggling + if(RS.has_vore_struggle_sprite) + check_state(RS,"-[belly]-[num]-r-struggle") + check_state(RS,"-[belly]-[num]-g-struggle") + if(RS.has_vore_belly_resting_sprites) + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-[belly]-[num]-r-[rest_style]-struggle") + check_state(RS,"-[belly]-[num]-g-[rest_style]-struggle") + // reseting + for(var/rest_style in RS.rest_sprite_options) + rest_style = lowertext(rest_style) + if(rest_style == "default") + rest_style = "rest" + check_state(RS,"-[rest_style]") + if(RS.has_glow_sprites) + check_state(RS,"-[rest_style]-glow") + if(RS.has_rest_lights_sprites) + check_state(RS,"-[rest_style]-lights") + if(RS.has_rest_eyes_sprites) + check_state(RS,"-[rest_style]-eyes") + // death + if(RS.has_dead_sprite) + check_state(RS,"-wreck") + if(RS.has_dead_sprite_overlay) // Only one per dmi + TEST_ASSERT("wreck-overlay" in cached_icon_states(RS.sprite_icon), "[RS.type]: Robots - Robot sprite \"[RS.name]\", missing icon_state wreck-overlay, in dmi \"[RS.sprite_icon]\".") + // offset + var/icon/I = new(RS.sprite_icon) + TEST_ASSERT_EQUAL(RS.icon_x, I.Width(), "[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_x \"[RS.icon_x]\" did not match dmi configured width \"[I.Width()]\"") + TEST_ASSERT_EQUAL(RS.icon_y, I.Height(), "[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_y \"[RS.icon_y]\" did not match dmi configured height \"[I.Height()]\"") + TEST_ASSERT_EQUAL(RS.icon_y, RS.vis_height, "[RS.type]: Robots - Robot sprite \"[RS.name]\", vis_height \"[RS.vis_height]\" did not match icon_y \"[RS.icon_y]\"") + + var/legal_offset = (I.Width() - world.icon_size) / 2 + TEST_ASSERT_EQUAL(RS.pixel_x, -legal_offset, "[RS.type]: Robots - Robot sprite \"[RS.name]\", pixel_x \"[RS.pixel_x]\" did not have correct offset, should be \"[-legal_offset]\"") + + qdel(I) + qdel(RS) + +/datum/unit_test/all_robot_sprites_must_be_valid/proc/check_state(datum/robot_sprite/RS, append) + var/check_state = "[RS.sprite_icon_state][append]" + TEST_ASSERT(check_state in cached_icon_states(RS.sprite_icon), "[RS.type]: Robots - Robot sprite \"[RS.name]\", enabled but missing icon_state \"[check_state]\", in dmi \"[RS.sprite_icon]\".") diff --git a/code/modules/unit_tests/spritesheets.dm b/code/modules/unit_tests/spritesheets.dm new file mode 100644 index 0000000000..16666dac17 --- /dev/null +++ b/code/modules/unit_tests/spritesheets.dm @@ -0,0 +1,24 @@ +///Checks if spritesheet assets contain icon states with invalid names +/datum/unit_test/spritesheets + +/datum/unit_test/spritesheets/Run() + for(var/datum/asset/spritesheet/sheet as anything in subtypesof(/datum/asset/spritesheet)) + if(!initial(sheet.name)) //Ignore abstract types + continue + if (sheet == initial(sheet._abstract)) + continue + sheet = get_asset_datum(sheet) + for(var/sprite_name in sheet.sprites) + if(!sprite_name) + TEST_FAIL("Spritesheet [sheet.type] has a nameless icon state.") + + // Test IconForge generated sheets as well + for(var/datum/asset/spritesheet_batched/sheet as anything in subtypesof(/datum/asset/spritesheet_batched)) + if(!initial(sheet.name)) //Ignore abstract types + continue + if (sheet == initial(sheet._abstract)) + continue + sheet = get_asset_datum(sheet) + for(var/sprite_name in sheet.sprites) + if(!sprite_name) + TEST_FAIL("Spritesheet [sheet.type] has a nameless icon state.") diff --git a/code/modules/unit_tests/sqlite_tests.dm b/code/modules/unit_tests/sqlite_tests.dm new file mode 100644 index 0000000000..60f4340949 --- /dev/null +++ b/code/modules/unit_tests/sqlite_tests.dm @@ -0,0 +1,50 @@ +/// converted unit test, maybe should be fully refactored + +/// Test that inserts and retrieves data from an sqlite database +/datum/unit_test/sqlite_tests_insert + +/datum/unit_test/sqlite_tests_insert/Run() + // Arrange. + fdel("data/sqlite/testing_sqlite_tests_insert.db") // In case any remain from a previous local test, so we can have a clean new database. + var/database/stub_sqlite_db = new("data/sqlite/testing_sqlite_tests_insert.db") // Unfortunately, byond doesn't like having working sqlite stuff w/o a file existing. + SSsqlite.init_schema(stub_sqlite_db) + + var/test_author = "alice" + var/test_topic = "Test" + var/test_content = "Bob is lame." + + // Act. + SSsqlite.insert_feedback(author = test_author, topic = test_topic, content = test_content, sqlite_object = stub_sqlite_db) + var/database/query/Q = new("SELECT * FROM [SQLITE_TABLE_FEEDBACK]") + Q.Execute(stub_sqlite_db) + SSsqlite.sqlite_check_for_errors(Q, "Sqlite Insert Unit Test") + Q.NextRow() + + // Assert. + var/list/row_data = Q.GetRowData() + if(!(row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR] == test_author && row_data[SQLITE_FEEDBACK_COLUMN_TOPIC] == test_topic && row_data[SQLITE_FEEDBACK_COLUMN_CONTENT] == test_content)) + TEST_FAIL("Data insert and loading failed to have matching information.") + +/// Test that does a cooldown in a sqlite database +/datum/unit_test/sqlite_tests_cooldown + +/datum/unit_test/sqlite_tests_cooldown/Run() + // Arrange. + fdel("data/sqlite/testing_sqlite_tests_cooldown.db") // In case any remain from a previous local test, so we can have a clean new database. + var/database/stub_sqlite_db = new("data/sqlite/testing_sqlite_tests_cooldown.db") // Unfortunately, byond doesn't like having working sqlite stuff w/o a file existing. + SSsqlite.init_schema(stub_sqlite_db) + + var/days_to_wait = 1 + + // Act. + SSsqlite.insert_feedback(author = "Alice", topic = "Testing", content = "This is a test.", sqlite_object = stub_sqlite_db) + + var/alice_cooldown_block = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db) + var/bob_cooldown = SSsqlite.get_feedback_cooldown("Bob", days_to_wait, stub_sqlite_db) + days_to_wait = 0 + var/alice_cooldown_allow = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db) + + // Assert. + TEST_ASSERT(alice_cooldown_block > 0, "User 'Alice' did not receive a cooldown, when they were supposed to.") + TEST_ASSERT(bob_cooldown <= 0, "User 'Bob' did receive a cooldown, when they did not do anything.") + TEST_ASSERT(alice_cooldown_allow <= 0, "User 'Alice' did receive a cooldown, when no cooldown is supposed to be enforced.") diff --git a/code/modules/unit_tests/subsystem_init.dm b/code/modules/unit_tests/subsystem_init.dm new file mode 100644 index 0000000000..b9c0a027e3 --- /dev/null +++ b/code/modules/unit_tests/subsystem_init.dm @@ -0,0 +1,22 @@ +/// Tests that all subsystems that need to properly initialize. +/datum/unit_test/subsystem_init + +/datum/unit_test/subsystem_init/Run() + for(var/datum/controller/subsystem/subsystem as anything in Master.subsystems) + if(subsystem.flags & SS_NO_INIT) + continue + if(subsystem.initialized) + continue + + var/should_fail = !(subsystem.flags & SS_OK_TO_FAIL_INIT) + var/list/message_strings = list("[subsystem] ([subsystem.type]) is a subsystem meant to initialize but could not get initialized.") + + if(!isnull(subsystem.initialization_failure_message)) + message_strings += "The subsystem reported the following: [subsystem.initialization_failure_message]" + + if(should_fail) + TEST_FAIL(jointext(message_strings, "\n")) + continue + + message_strings += "This subsystem is marked as SS_OK_TO_FAIL_INIT. This is still a bug, but it is non-blocking." + TEST_NOTICE(src, jointext(message_strings, "\n")) 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/timer_sanity.dm b/code/modules/unit_tests/timer_sanity.dm new file mode 100644 index 0000000000..dbdf3f6d8e --- /dev/null +++ b/code/modules/unit_tests/timer_sanity.dm @@ -0,0 +1,3 @@ +/datum/unit_test/timer_sanity/Run() + TEST_ASSERT(SStimer.bucket_count >= 0, + "SStimer is going into negative bucket count from something") diff --git a/code/modules/unit_tests/trait_tests.dm b/code/modules/unit_tests/trait_tests.dm new file mode 100644 index 0000000000..18d650e14a --- /dev/null +++ b/code/modules/unit_tests/trait_tests.dm @@ -0,0 +1,33 @@ +/// Test that all traits have unique names +/datum/unit_test/all_traits_unique_names + +/datum/unit_test/all_traits_unique_names/Run() + var/list/used_named = list() + for(var/traitpath in GLOB.all_traits) + var/datum/trait/T = GLOB.all_traits[traitpath] + TEST_ASSERT(!(T.name in used_named), "[T.type]: Trait - The name \"[T.name]\" is already in use.") + used_named.Add(T.name) + +/// Test that autohiss traits shall be excluse +/datum/unit_test/autohiss_shall_be_exclusive + +/datum/unit_test/autohiss_shall_be_exclusive/Run() + var/list/hiss_list = list() + for(var/traitpath in GLOB.all_traits) + var/datum/trait/T = GLOB.all_traits[traitpath] + if(!T.var_changes) + continue + if(!islist(T.var_changes["autohiss_basic_map"])) + continue + hiss_list += T + + for(var/datum/trait/T in hiss_list) + TEST_ASSERT(!(T.type in T.excludes), "[T.type]: Trait - Autohiss excludes itself.") + TEST_ASSERT(T.excludes, "[T.type]: Trait - Autohiss missing exclusion list.") + + if(!T.excludes) + continue + + var/list/exempt_list = hiss_list.Copy() - T // MUST exclude all others except itself + for(var/datum/trait/EX in exempt_list) + TEST_ASSERT(EX.type in T.excludes, "[T.type]: Trait - Autohiss missing exclusion for [EX].") diff --git a/code/modules/unit_tests/unit_test.dm b/code/modules/unit_tests/unit_test.dm new file mode 100644 index 0000000000..681661167c --- /dev/null +++ b/code/modules/unit_tests/unit_test.dm @@ -0,0 +1,402 @@ +/* + +Usage: +Override /Run() to run your test code + +Call TEST_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_floor_bottom_left and run_loc_floor_top_right to get turfs for testing + +*/ + +GLOBAL_DATUM(current_test, /datum/unit_test) +GLOBAL_VAR_INIT(failed_any_test, FALSE) +/// When unit testing, all logs sent to log_mapping are stored here and retrieved in log_mapping unit test. +GLOBAL_LIST_EMPTY(unit_test_mapping_logs) +/// Global assoc list of required mapping items, [item typepath] to [required item datum]. +GLOBAL_LIST_EMPTY(required_map_items) + +/// A list of every test that is currently focused. +/// Use the PERFORM_ALL_TESTS macro instead. +GLOBAL_VAR_INIT(focused_tests, focused_tests()) + +/proc/focused_tests() + var/list/focused_tests = list() + for (var/datum/unit_test/unit_test as anything in subtypesof(/datum/unit_test)) + if (initial(unit_test.focus)) + focused_tests += unit_test + + return focused_tests.len > 0 ? focused_tests : null + +/datum/unit_test + //Bit of metadata for the future maybe + var/list/procs_tested + + /// 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 + + /// Do not instantiate if type matches this + abstract_type = /datum/unit_test + + /// List of atoms that we don't want to ever initialize in an agnostic context, like for Create and Destroy. Stored on the base datum for usability in other relevant tests that need this data. + var/static/list/uncreatables = null + + // NOT IMPLEMENTED YET: var/static/datum/space_level/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() + // NOT IMPLEMENTED YET: if (isnull(reservation)) + // NOT IMPLEMENTED YET: var/datum/map_template/unit_tests/template = new + // NOT IMPLEMENTED YET: reservation = template.load_new_z() + + if (isnull(uncreatables)) + uncreatables = build_list_of_uncreatables() + + allocated = new + run_loc_floor_bottom_left = get_turf(locate(/obj/effect/landmark/unit_test_bottom_left) in GLOB.landmarks_list) + run_loc_floor_top_right = get_turf(locate(/obj/effect/landmark/unit_test_top_right) in GLOB.landmarks_list) + + // NOT IMPLENTED YET, SEE THE BEGINNING OF THIS PROC + //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() + QDEL_LIST(allocated) + // clear the test area + // NOT IMPLEMENTED YET, SEE NEW() PROC + //for (var/turf/turf in Z_TURFS(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() + TEST_FAIL("[type]/Run() called parent or not implemented") + +/datum/unit_test/proc/Fail(reason = "No reason", file = "OUTDATED_TEST", line = 1) + succeeded = FALSE + + if(!istext(reason)) + reason = "FORMATTED: [reason != null ? reason : "NULL"]" + + LAZYADD(fail_reasons, list(list(reason, file, line))) + +/// 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(ispath(type, /atom)) + if (!arguments.len) + arguments = list(run_loc_floor_bottom_left) + else if (arguments[1] == null) + arguments[1] = run_loc_floor_bottom_left + var/instance + // Byond will throw an index out of bounds if arguments is empty in that arglist call. Sigh + if(length(arguments)) + instance = new type(arglist(arguments)) + else + instance = new type() + allocated += instance + return instance + +/// Resets the air of our testing room to its default +/datum/unit_test/proc/restore_atmos() + // NOT IMPLEMENTED YET, SEE NEW() PROC + //var/area/working_area = run_loc_floor_bottom_left.loc + //var/list/turf/to_restore = working_area.get_turfs_from_all_zlevels() + //for(var/turf/simulated/restore in to_restore) + // var/datum/gas_mixture/GM = SSair.parse_gas_string(restore.initial_gas_mix, /datum/gas_mixture/turf) + // restore.copy_air(GM) + // restore.temperature = initial(restore.temperature) + // restore.air_update_turf(update = FALSE, remove = FALSE) + +/datum/unit_test/proc/test_screenshot(name, icon/icon) + if (!istype(icon)) + TEST_FAIL("[icon] is not an icon.") + return + + var/path_prefix = replacetext(replacetext("[type]", "/datum/unit_test/", ""), "/", "_") + name = replacetext(name, "/", "_") + + var/filename = "code/modules/unit_tests/screenshots/[path_prefix]_[name].png" + + if (fexists(filename)) + var/data_filename = "data/screenshots/[path_prefix]_[name].png" + fcopy(icon, data_filename) + //log_test("\t[path_prefix]_[name] was found, putting in data/screenshots") + log_unit_test("\t[path_prefix]_[name] was found, putting in data/screenshots") + else +#ifdef CIBUILDING + // We are runing in real CI, so just pretend it worked and move on + fcopy(icon, "data/screenshots_new/[path_prefix]_[name].png") + + //log_test("\t[path_prefix]_[name] was put in data/screenshots_new") + log_unit_test("\t[path_prefix]_[name] was put in data/screenshots_new") +#else + // We are probably running in a local build + fcopy(icon, filename) + TEST_FAIL("Screenshot for [name] did not exist. One has been created.") +#endif + + +/// Helper for screenshot tests to take an image of an atom from all directions and insert it into one icon +/datum/unit_test/proc/get_flat_icon_for_all_directions(atom/thing, no_anim = TRUE) + var/icon/output = icon('icons/effects/effects.dmi', "nothing") + + for (var/direction in GLOB.cardinal) + var/icon/partial = getFlatIcon(thing, defdir = direction, no_anim = no_anim) + output.Insert(partial, dir = direction) + + return output + +/// Logs a test message. Will use GitHub action syntax found at https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions +/datum/unit_test/proc/log_for_test(text, priority, file, line) + var/map_name = SSmapping.current_map.name + + // Need to escape the text to properly support newlines. + var/annotation_text = replacetext(text, "%", "%25") + annotation_text = replacetext(annotation_text, "\n", "%0A") + + log_world("::[priority] file=[file],line=[line],title=[map_name]: [type]::[annotation_text]") + +/** + * Helper to perform a click + * + * * clicker: The mob that will be clicking + * * clicked_on: The atom that will be clicked + * * passed_params: A list of parameters to pass to the click + */ +/datum/unit_test/proc/click_wrapper(mob/living/clicker, atom/clicked_on, list/passed_params = list(LEFT_CLICK = 1, BUTTON = LEFT_CLICK)) + clicker.next_click = -1 + clicker.next_move = -1 + clicker.ClickOn(clicked_on, list2params(passed_params)) + +/proc/RunUnitTest(datum/unit_test/test_path, list/test_results) + if(ispath(test_path, /datum/unit_test/focus_only)) + return + + if(initial(test_path.abstract_type) == test_path) + return + + var/datum/unit_test/test = new test_path + + GLOB.current_test = test + var/duration = REALTIMEOFDAY + var/skip_test = (test_path in SSmapping.current_map.skipped_tests) + var/test_output_desc = "[test_path]" + var/message = "" + + log_world("::group::[test_path]") + + if(skip_test) + log_world("[TEST_OUTPUT_YELLOW("SKIPPED")] Skipped run on map [SSmapping.current_map.name].") + + else + + test.Run() + test.restore_atmos() + + duration = REALTIMEOFDAY - duration + GLOB.current_test = null + GLOB.failed_any_test |= !test.succeeded + + var/list/log_entry = list() + var/list/fail_reasons = test.fail_reasons + + for(var/reasonID in 1 to LAZYLEN(fail_reasons)) + var/text = fail_reasons[reasonID][1] + var/file = fail_reasons[reasonID][2] + var/line = fail_reasons[reasonID][3] + + test.log_for_test(text, "error", file, line) + + // Normal log message + log_entry += "\tFAILURE #[reasonID]: [text] at [file]:[line]" + + if(length(log_entry)) + message = log_entry.Join("\n") + //log_test(message) + log_unit_test(message) + + test_output_desc += " [duration / 10]s" + if (test.succeeded) + log_world("[TEST_OUTPUT_GREEN("PASS")] [test_output_desc]") + + log_world("::endgroup::") + + if (!test.succeeded && !skip_test) + log_world("::error::[TEST_OUTPUT_RED("FAIL")] [test_output_desc]") + + var/final_status = skip_test ? UNIT_TEST_SKIPPED : (test.succeeded ? UNIT_TEST_PASSED : UNIT_TEST_FAILED) + test_results[test_path] = list("status" = final_status, "message" = message, "name" = test_path) + + qdel(test) + +/// Builds (and returns) a list of atoms that we shouldn't initialize in generic testing, like Create and Destroy. +/// It is appreciated to add the reason why the atom shouldn't be initialized if you add it to this list. +/datum/unit_test/proc/build_list_of_uncreatables() + RETURN_TYPE(/list) + var/list/returnable_list = list() + // The following are just generic, singular types. + returnable_list = list( + //Never meant to be created, errors out the ass for mobcode reasons + /mob/living/carbon, + //And another + // NOT IMPLEMENTED: /obj/item/slimecross/recurring, + //This should be obvious + // NOT IMPLEMENTED: /obj/machinery/doomsday_device, + //Yet more templates + // NOT IMPLEMENTED: /obj/machinery/restaurant_portal, + //Template type + /obj/machinery/power/turbine, + //Template type + // NOT IMPLEMENTED: /obj/effect/mob_spawn, + //Template type + // NOT IMPLEMENTED: /obj/structure/holosign/robot_seat, + //Singleton + /mob/dview, + //Template type + // NOT IMPLEMENTED: /obj/item/bodypart, + //This is meant to fail extremely loud every single time it occurs in any environment in any context, and it falsely alarms when this unit test iterates it. Let's not spawn it in. + /obj/merge_conflict_marker, + //briefcase launchpads erroring + // NOT IMPLEMENTED: /obj/machinery/launchpad/briefcase, + //Wings abstract path + // NOT IMPLEMENTED: /obj/item/organ/wings, + //Not meant to spawn without the machine wand + // NOT IMPLEMENTED: /obj/effect/bug_moving, + //The abstract grown item expects a seed, but doesn't have one + // NOT IMPLEMENTED: /obj/item/food/grown, + ///Single use case holder atom requiring a user + // NOT IMPLEMENTED: /atom/movable/looking_holder, + ) + + // Everything that follows is a typesof() check. + + //Say it with me now, type template + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/mapping_helpers) + //This turf existing is an error in and of itself + // NOT IMPLEMENTED: returnable_list += typesof(/turf/baseturf_skipover) + // NOT IMPLEMENTED: returnable_list += typesof(/turf/baseturf_bottom) + //This demands a borg, so we'll let if off easy + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/modular_computer/pda/silicon) + //This one demands a computer, ditto + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/modular_computer/processor) + //Very finiky, blacklisting to make things easier + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/poster/wanted) + //Needs clients / mobs to observe it to exist. Also includes hallucinations. + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/client_image_holder) + //Same to above. Needs a client / mob / hallucination to observe it to exist. + // NOT IMPLEMENTED: returnable_list += typesof(/obj/projectile/hallucination) + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/hallucinated) + //We don't have a pod + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/pod_landingzone_effect) + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/pod_landingzone) + //We have a baseturf limit of 10, adding more than 10 baseturf helpers will kill CI, so here's a future edge case to fix. + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/baseturf_helper) + //No tauma to pass in + // NOT IMPLEMENTED: returnable_list += typesof(/mob/eye/imaginary_friend) + //No heart to give + // NOT IMPLEMENTED: returnable_list += typesof(/obj/structure/ethereal_crystal) + //No linked console + // NOT IMPLEMENTED: returnable_list += typesof(/mob/eye/camera/remote/base_construction) + //See above + // NOT IMPLEMENTED: returnable_list += typesof(/mob/eye/camera/remote/shuttle_docker) + //Hangs a ref post invoke async, which we don't support. Could put a qdeleted check but it feels hacky + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/anomaly/grav/high) + //See above + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/timestop) + //Sparks can ignite a number of things, causing a fire to burn the floor away. Only you can prevent CI fires + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/particle_effect/sparks) + //See above - These are one of those things. + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/decal/cleanable/fuel_pool) + //Invoke async in init, skippppp + // NOT IMPLEMENTED: returnable_list += typesof(/mob/living/silicon/robot/model) + //This lad also sleeps + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/hilbertshotel) + //this boi spawns turf changing stuff, and it stacks and causes pain. Let's just not + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/sliding_puzzle) + //these can explode and cause the turf to be destroyed at unexpected moments + returnable_list += typesof(/obj/effect/mine) + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/spawner/random/contraband/landmine) + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/minespawner) + //Stacks baseturfs, can't be tested here + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/temp_visual/lava_warning) + //Stacks baseturfs, can't be tested here + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/landmark/ctf) + //Our system doesn't support it without warning spam from unregister calls on things that never registered + // NOT IMPLEMENTED: returnable_list += typesof(/obj/docking_port) + //Asks for a shuttle that may not exist, let's leave it alone + returnable_list += typesof(/obj/item/pinpointer/shuttle) + //This spawns beams as a part of init, which can sleep past an async proc. This hangs a ref, and fucks us. It's only a problem here because the beam sleeps with CHECK_TICK + // NOT IMPLEMENTED: returnable_list += typesof(/obj/structure/alien/resin/flower_bud) + //Needs a linked mecha + // NOT IMPLEMENTED: returnable_list += typesof(/obj/effect/skyfall_landingzone) + //Expects a mob to holderize, we have nothing to give + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/clothing/head/mob_holder) + //Needs cards passed into the initilazation args + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/toy/cards/cardhand) + //Needs a holodeck area linked to it which is not guarenteed to exist and technically is supposed to have a 1:1 relationship with computer anyway. + // NOT IMPLEMENTED: returnable_list += typesof(/obj/machinery/computer/holodeck) + //runtimes if not paired with a landmark + // NOT IMPLEMENTED: returnable_list += typesof(/obj/structure/transport/linear) + // Runtimes if the associated machinery does not exist, but not the base type + // NOT IMPLEMENTED: returnable_list += subtypesof(/obj/machinery/airlock_controller) + // Always ought to have an associated escape menu. Any references it could possibly hold would need one regardless. + // NOT IMPLEMENTED: returnable_list += subtypesof(/atom/movable/screen/escape_menu) + // Can't spawn openspace above nothing, it'll get pissy at me + // NOT IMPLEMENTED: returnable_list += typesof(/turf/open/space/openspace) + // NOT IMPLEMENTED: returnable_list += typesof(/turf/open/openspace) + // NOT IMPLEMENTED: returnable_list += typesof(/obj/item/robot_model) // These should never be spawned outside of a robot. + + return returnable_list + +/proc/RunUnitTests() + CHECK_TICK + + var/list/tests_to_run = subtypesof(/datum/unit_test) + var/list/focused_tests = list() + 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)) + focused_tests += test_to_run + if(length(focused_tests)) + tests_to_run = focused_tests + + sortTim(tests_to_run, GLOBAL_PROC_REF(cmp_unit_test_priority)) + + var/list/test_results = list() + + //Hell code, we're bound to end the round somehow so let's stop if from ending while we work + SSticker.delay_end = TRUE + 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) + SSticker.delay_end = FALSE + + var/file_name = "data/unit_tests.json" + fdel(file_name) + file(file_name) << json_encode(test_results) + + SSticker.force_ending = ADMIN_FORCE_END_ROUND + //We have to call this manually because del_text can preceed us, and SSticker doesn't fire in the post game + SSticker.declare_completion() + +/datum/map_template/unit_tests + name = "Unit Tests Zone" + mappath = "maps/templates/unit_tests.dmm" diff --git a/code/unit_tests/vore_tests_vr.dm b/code/modules/unit_tests/vore_tests.dm similarity index 83% rename from code/unit_tests/vore_tests_vr.dm rename to code/modules/unit_tests/vore_tests.dm index b14844b55c..ba1fde8f56 100644 --- a/code/unit_tests/vore_tests_vr.dm +++ b/code/modules/unit_tests/vore_tests.dm @@ -1,13 +1,41 @@ +/// converted unit test, maybe should be fully refactored + +// FIXME: THIS SHOULD BE REPLACED WITH ALLOCATE IN THE END +// SEE unit_test.dm NEW() WHY THIS ISNT IMPLEMENTED YET +/datum/unit_test + var/static/default_mobloc = null + +// FIXME: THIS SHOULD BE REPLACED WITH ALLOCATE IN THE END +// SEE unit_test.dm NEW() WHY THIS ISNT IMPLEMENTED YET +/datum/unit_test/proc/create_test_mob(var/turf/mobloc = null, var/mobtype = /mob/living/carbon/human, var/with_mind = FALSE) + if(isnull(mobloc)) + if(!default_mobloc) + for(var/turf/simulated/floor/tiled/T in world) + var/pressure = T.zone.air.return_pressure() + if(90 < pressure && pressure < 120) // Find a turf between 90 and 120 + default_mobloc = T + break + mobloc = default_mobloc + if(!mobloc) + fail("Unable to find a location to create test mob") + return 0 + + var/mob/living/carbon/human/H = new mobtype(mobloc) + + if(with_mind) + H.mind_initialize("TestKey[rand(0,10000)]") + + return H + +/// Test that a human mob does not suffocate in a belly /datum/unit_test/belly_nonsuffocation - name = "MOB: human mob does not suffocate in a belly" var/startLifeTick var/startOxyloss var/endOxyloss var/mob/living/carbon/human/pred var/mob/living/carbon/human/prey - async = 1 -/datum/unit_test/belly_nonsuffocation/start_test() +/datum/unit_test/belly_nonsuffocation/Run() pred = create_test_mob() if(!istype(pred)) return 0 diff --git a/code/modules/vore/eating/vorepanel_vr.dm b/code/modules/vore/eating/vorepanel_vr.dm index fa69f91405..1cfb871a49 100644 --- a/code/modules/vore/eating/vorepanel_vr.dm +++ b/code/modules/vore/eating/vorepanel_vr.dm @@ -15,7 +15,7 @@ set name = "Vore Panel" set category = "IC.Vore" - if(SSticker.current_state == GAME_STATE_INIT) + if(SSticker.current_state == GAME_STATE_STARTUP) return if(!isliving(src)) diff --git a/code/names.dm b/code/names.dm deleted file mode 100644 index 4f26e7ed5c..0000000000 --- a/code/names.dm +++ /dev/null @@ -1,16 +0,0 @@ -var/list/ai_names = file2list("config/names/ai.txt") -var/list/wizard_first = file2list("config/names/wizardfirst.txt") -var/list/wizard_second = file2list("config/names/wizardsecond.txt") -var/list/ninja_titles = file2list("config/names/ninjatitle.txt") -var/list/ninja_names = file2list("config/names/ninjaname.txt") -var/list/commando_names = file2list("config/names/death_commando.txt") -var/list/first_names_male = file2list("config/names/first_male.txt") -var/list/first_names_female = file2list("config/names/first_female.txt") -var/list/last_names = file2list("config/names/last.txt") -var/list/clown_names = file2list("config/names/clown.txt") - - -var/list/verbs = file2list("config/names/verbs.txt") -var/list/adjectives = file2list("config/names/adjectives.txt") -//loaded on startup because of " -//would include in rsc if ' was used diff --git a/code/unit_tests/cosmetic_tests.dm b/code/unit_tests/cosmetic_tests.dm deleted file mode 100644 index a5b54d835d..0000000000 --- a/code/unit_tests/cosmetic_tests.dm +++ /dev/null @@ -1,91 +0,0 @@ -/datum/unit_test/sprite_accessories_shall_be_unique - name = "COSMETICS: Entries shall have unique name." - -/datum/unit_test/sprite_accessories_shall_be_unique/start_test() - var/failed = 0 - - failed += validate_accessory_list( /datum/sprite_accessory/ears) - failed += validate_accessory_list( /datum/sprite_accessory/facial_hair) - failed += validate_accessory_list( /datum/sprite_accessory/hair) - failed += validate_accessory_list( /datum/sprite_accessory/hair_accessory) - failed += validate_accessory_list( /datum/sprite_accessory/marking) - failed += validate_accessory_list( /datum/sprite_accessory/tail) - failed += validate_accessory_list( /datum/sprite_accessory/wing) - - if(failed) - fail("One or more /datum/sprite_accessory definitions had invalid names, icon_states, or names were reused definitions") - else - pass("All /datum/sprite_accessory definitions had correct settings.") - return 1 - -/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_accessory_list(var/path) - var/failed = 0 - var/total_good = 0 - var/total_all = 0 - - var/list/collection = list() - for(var/SP in subtypesof(path)) - total_all++ - var/datum/sprite_accessory/A = new SP() - if(!A) - log_unit_test("[SP]: Cosmetic - Path resolved to null in list.") - continue - - if(!A.name) - log_unit_test("[A] - [A.type]: Cosmetic - Missing name.") - failed = 1 - - if(A.name == DEVELOPER_WARNING_NAME) - continue - - if(collection[A.name]) - log_unit_test("[A] - [A.type]: Cosmetic - Name defined twice. Original def [collection[A.name]]") - failed = 1 - else - collection[A.name] = A.type - - if(istype(A,text2path("[path]/invisible"))) - if(A.icon_state) - log_unit_test("[A] - [A.type]: Cosmetic - Invisible subtype has icon_state.") - failed = 1 - else if(!A.icon_state) - log_unit_test("[A] - [A.type]: Cosmetic - Has no icon_state.") - failed = 1 - else - // Check if valid icon - failed += validate_icons(A) - - total_good++ - qdel(A) - - log_unit_test("[path]: Cosmetic - Total valid count: [total_good]/[total_all].") - return failed - -/datum/unit_test/sprite_accessories_shall_be_unique/proc/validate_icons(var/datum/sprite_accessory/A) - var/failed = 0 - var/actual_icon_state = A.icon_state - if(istype(A,/datum/sprite_accessory/hair)) - actual_icon_state = "[A.icon_state]_s" - if(!(actual_icon_state in cached_icon_states(A.icon))) - log_unit_test("[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].") - failed = 1 - - if(istype(A,/datum/sprite_accessory/facial_hair)) - actual_icon_state = "[A.icon_state]_s" - if(!(actual_icon_state in cached_icon_states(A.icon))) - log_unit_test("[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].") - failed = 1 - - if(istype(A,/datum/sprite_accessory/marking)) - var/datum/sprite_accessory/marking/MA = A - for(var/BP in MA.body_parts) - if(!(BP in BP_ALL)) - log_unit_test("[A] - [A.type]: Cosmetic - Has an illegal bodypart \"[BP]\". ONLY use parts listed in BP_ALL.") - failed = 1 - - actual_icon_state = "[A.icon_state]-[BP]" - if(!(actual_icon_state in cached_icon_states(A.icon))) - log_unit_test("[A] - [A.type]: Cosmetic - Icon_state \"[actual_icon_state]\" is not present in [A.icon].") - failed = 1 - - return failed diff --git a/code/unit_tests/disease_tests.dm b/code/unit_tests/disease_tests.dm deleted file mode 100644 index 5327b5fdcc..0000000000 --- a/code/unit_tests/disease_tests.dm +++ /dev/null @@ -1,32 +0,0 @@ -/datum/unit_test/disease_must_be_valid - name = "DISEASE: All diseases must have valid data" - -/datum/unit_test/disease_must_be_valid/start_test() - var/failed = FALSE - var/list/used_ids = list() - - var/count = 0 - for(var/datum/disease/D as anything in subtypesof(/datum/disease)) - if(initial(D.name) == DEVELOPER_WARNING_NAME) - continue - - count++ - if(initial(D.medical_name) in used_ids) - log_unit_test("[D]: Disease - Had a reused medical name, this is used as an ID and must be unique.") - failed = TRUE - else - used_ids.Add(initial(D.medical_name)) - - if(!initial(D.name) || initial(D.name) == "") - log_unit_test("[D]: Disease - Lacks a name.") - failed = TRUE - - if(!initial(D.desc) || initial(D.desc) == "") - log_unit_test("[D]: Disease - Lacks a description.") - failed = TRUE - - if(failed) - fail("All diseases must have valid data.") - else - pass("All [count] diseases have proper data.") - return failed diff --git a/code/unit_tests/genetics_tests.dm b/code/unit_tests/genetics_tests.dm deleted file mode 100644 index f5da724d54..0000000000 --- a/code/unit_tests/genetics_tests.dm +++ /dev/null @@ -1,160 +0,0 @@ -/datum/unit_test/enough_free_gene_slots_must_be_available - name = "GENETICS: Enough free gene slots must be available." - -/datum/unit_test/enough_free_gene_slots_must_be_available/start_test() - var/failed = FALSE - - if(GLOB.dna_genes.len > (DNA_SE_LENGTH - 10)) // Based off of traitgenes scanned on startup - failed = TRUE - - if(failed) - fail("Too few geneslots are empty, minimum 10. Increase DNA_SE_LENGTH.") - else - pass("DNA_SE_LENGTH has enough free space remaining.") - return failed - - -/datum/unit_test/enough_positive_genes_must_exist - name = "GENETICS: Must be at least one positive gene." - -/datum/unit_test/enough_positive_genes_must_exist/start_test() - var/failed = FALSE - - if(GLOB.dna_genes_good.len < 1) // Based off of traitgenes scanned on startup - failed = TRUE - - if(failed) - fail("Must have at least one positive gene.") - else - pass("Has at least one positive gene.") - return failed - - -/datum/unit_test/enough_neutral_genes_must_exist - name = "GENETICS: Must be at least one neutral gene." - -/datum/unit_test/enough_neutral_genes_must_exist/start_test() - var/failed = FALSE - - if(GLOB.dna_genes_neutral.len < 1) // Based off of traitgenes scanned on startup - failed = TRUE - - if(failed) - fail("Must have at least one neutral gene.") - else - pass("Has at least one neutral gene.") - return failed - - -/datum/unit_test/enough_bad_genes_must_exist - name = "GENETICS: Must be at least one bad gene." - -/datum/unit_test/enough_bad_genes_must_exist/start_test() - var/failed = FALSE - - if(GLOB.dna_genes_bad.len < 1) // Based off of traitgenes scanned on startup - failed = TRUE - - if(failed) - fail("Must have at least one bad gene.") - else - pass("Has at least one bad gene.") - return failed - - -/datum/unit_test/all_dna_injectors_must_be_valid - name = "GENETICS: All dna injectors must be valid." - -/datum/unit_test/all_dna_injectors_must_be_valid/start_test() - var/failed = FALSE - - for(var/injector_path in subtypesof(/obj/item/dnainjector/set_trait)) - var/obj/item/dnainjector/D = new injector_path() - if(!D.block) - log_unit_test("[injector_path]: Genetics - Injector could not resolve geneblock for trait. Missing traitgene?") - failed = TRUE - qdel(D) - - if(failed) - fail("Dna injectors have traits that are not genetraits or are missing.") - else - pass("No invalid dna injectors.") - return failed - - -/datum/unit_test/all_genes_shall_have_unique_name - name = "GENETICS: All genes shall be init with unique names." - -/datum/unit_test/all_genes_shall_have_unique_name/start_test() - var/failed = FALSE - - var/collection = list() - for(var/datum/gene/G in GLOB.dna_genes) - if(collection[G.name]) - log_unit_test("[G.name]: Genetics - Gene name was already in use.") - failed = TRUE - else - collection[G.name] = G.name - - if(failed) - fail("Genes shared names. This should not be possible on init, all genes should have their blocknumber attached to them to ensure unique names.") - else - pass("All genes have unique names to use as list ids.") - return failed - - - -/datum/unit_test/genetraits_should_have_valid_dna_bounds - name = "GENETICS: All genes should have valid activation bounds." - -/datum/unit_test/genetraits_should_have_valid_dna_bounds/start_test() - var/failed = FALSE - - for(var/datum/gene/trait/G in GLOB.trait_to_dna_genes) - if(!G.linked_trait) - log_unit_test("[G.name]: Genetics - Has missing linked trait.") - failed = TRUE - continue - - if(!G.linked_trait.activity_bounds) - log_unit_test("[G.name]: Genetics - Has no activation bounds.") - failed = TRUE - continue - - if(!G.linked_trait.activity_bounds.len) - log_unit_test("[G.name]: Genetics - Has empty activation bounds.") - failed = TRUE - continue - - // DNA activation bounds. Usually they are in a list as follows: - // [1]DNA_OFF_LOWERBOUND = 1, begining of the threshold where a gene turns off. - // [2]DNA_OFF_UPPERBOUND = a number above 1, end of the treshold where a gene turns off. - // [3]DNA_ON_LOWERBOUND = a number above DNA_OFF_UPPERBOUND(even if just by 1), threshold where a gene turns on. - // [4]DNA_ON_UPPERBOUND = 4095, end of the threshold where a gene turns on. - - var/list/bounds = G.linked_trait.activity_bounds - if(bounds[1] < 1) // lowest value a gene can be to turn off - log_unit_test("[G.name]: Genetics - DNA_OFF_LOWERBOUND, was smaller than 1.") - failed = TRUE - - if(bounds[2] < bounds[1]) - log_unit_test("[G.name]: Genetics - DNA_OFF_UPPERBOUND must be larger than DNA_OFF_LOWERBOUND, and never equal.") - failed = TRUE - - if(bounds[2] >= bounds[3]) - log_unit_test("[G.name]: Genetics - DNA_OFF_UPPERBOUND must be smaller than DNA_ON_LOWERBOUND, and never equal.") - failed = TRUE - - if(bounds[3] > bounds[4]) - log_unit_test("[G.name]: Genetics - DNA_ON_LOWERBOUND must be smaller than DNA_ON_UPPERBOUND, and never equal.") - failed = TRUE - - if(bounds[4] > 4095) // highest value a gene can be to turn on - log_unit_test("[G.name]: Genetics - DNA_ON_UPPERBOUND, was larger than 4095.") - failed = TRUE - - if(failed) - fail("Invalid activity bounds for one or more traitgenes") - else - pass("All traitgenes have activity bounds, and activity bounds are legal.") - return failed diff --git a/code/unit_tests/loadout_tests.dm b/code/unit_tests/loadout_tests.dm deleted file mode 100644 index f6bfd19d5a..0000000000 --- a/code/unit_tests/loadout_tests.dm +++ /dev/null @@ -1,22 +0,0 @@ -/datum/unit_test/loadout_test_shall_have_name_cost_path - name = "LOADOUT: Entries shall have name, cost, and path definitions" - -/datum/unit_test/loadout_test_shall_have_name_cost_path/start_test() - var/failed = 0 - for(var/datum/gear/G as anything in subtypesof(/datum/gear)) - - if(!initial(G.display_name)) - log_unit_test("[G]: Loadout - Missing display name.") - failed = 1 - else if(isnull(initial(G.cost))) - log_unit_test("[G]: Loadout - Missing cost.") - failed = 1 - else if(!initial(G.path)) - log_unit_test("[G]: Loadout - Missing path definition.") - failed = 1 - - if(failed) - fail("One or more /datum/gear definitions had invalid display names, costs, or path definitions") - else - pass("All /datum/gear definitions had correct settings.") - return 1 diff --git a/code/unit_tests/poster_tests.dm b/code/unit_tests/poster_tests.dm deleted file mode 100644 index b7dc36cd23..0000000000 --- a/code/unit_tests/poster_tests.dm +++ /dev/null @@ -1,24 +0,0 @@ -/datum/unit_test/posters_shall_have_legal_states - name = "POSTERS: All poster decls shall have valid icon and icon overrides" - -/datum/unit_test/posters_shall_have_legal_states/start_test() - var/failed = 0 - var/list/all_posters = decls_repository.get_decls_of_type(/decl/poster) - all_posters -= decls_repository.get_decl(/decl/poster/lewd) // Dumb exclusion for now. This really needs to become a valid poster instead of an illegaly made base type - - for(var/path in all_posters) - var/decl/poster/D = all_posters[path] - var/obj/structure/sign/poster/P = /obj/structure/sign/poster // The base poster shows ALL subtypes except /lewd, so all posters should function here regardless! - var/icon/I = initial(P.icon) - if(D.icon_override) - I = D.icon_override - if(!(D.icon_state in cached_icon_states(I))) - failed += 1 - log_unit_test("[D.type]: Poster - missing icon_state \"[D.icon_state]\" in \"[I]\", as [D.icon_override ? "override" : "base"] dmi.") - - if(failed) - fail("[failed] posters had missing icon_states or bad icon overrides.") - else - pass("All [all_posters.len] posters have their icon_states and overrides set correctly.") - - return TRUE diff --git a/code/unit_tests/reagent_tests.dm b/code/unit_tests/reagent_tests.dm deleted file mode 100644 index b7552c6a61..0000000000 --- a/code/unit_tests/reagent_tests.dm +++ /dev/null @@ -1,378 +0,0 @@ -/datum/unit_test/reagent_shall_have_unique_name_and_id - name = "REAGENTS: Reagent IDs and names shall be unique" - -/datum/unit_test/reagent_shall_have_unique_name_and_id/start_test() - var/failed = FALSE - var/collection_name = list() - var/collection_id = list() - - for(var/Rpath in subtypesof(/datum/reagent)) - var/datum/reagent/R = new Rpath() - - if(R.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden - continue - - if(R.name == "") - log_unit_test("[Rpath]: Reagents - reagent name blank.") - failed = TRUE - - if(R.id == REAGENT_ID_DEVELOPER_WARNING) - log_unit_test("[Rpath]: Reagents - reagent ID not set.") - failed = TRUE - - if(R.id == "") - log_unit_test("[Rpath]: Reagents - reagent ID blank.") - failed = TRUE - - if(R.id != lowertext(R.id)) - log_unit_test("[Rpath]: Reagents - Reagent ID must be all lowercase.") - failed = TRUE - - if(!(R.wiki_flag & WIKI_SPOILER)) // If wiki hidden then don't conflict test it against name, used for intentionally copied names like beer2's - if(collection_name[R.name]) - log_unit_test("[Rpath]: Reagents - reagent name \"[R.name]\" is not unique, used first in [collection_name[R.name]].") - failed = TRUE - collection_name[R.name] = R.type - - if(collection_id[R.id]) - log_unit_test("[Rpath]: Reagents - reagent ID \"[R.id]\" is not unique, used first in [collection_id[R.id]].") - failed = TRUE - collection_id[R.id] = R.type - - if(!R.supply_conversion_value) - log_unit_test("[Rpath]: Reagents - reagent ID \"[R.id]\" does not have supply_conversion_value set.") - failed = TRUE - - if(!R.industrial_use || !R.industrial_use == "") - log_unit_test("[Rpath]: Reagents - reagent ID \"[R.id]\" does not have industrial_use set.") - failed = TRUE - - if(R.description == REAGENT_DESC_DEVELOPER_WARNING) - log_unit_test("[Rpath]: Reagents - reagent description unset.") - failed = TRUE - - qdel(R) - - if(failed) - fail("One or more /datum/reagent subtypes had invalid definitions.") - else - pass("All /datum/reagent subtypes had correct settings.") - return TRUE - - - -/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents - name = "REAGENTS: Chemical Reactions shall use and produce valid reagents" - -/datum/unit_test/chemical_reactions_shall_use_and_produce_valid_reagents/start_test() - var/failed = FALSE - var/list/collection_id = list() - - var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction) - for(var/rtype in all_reactions) - var/decl/chemical_reaction/CR = all_reactions[rtype] - if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden - continue - - if(!CR.name) - log_unit_test("[CR.type]: Reagents - chemical reaction had null name.") - failed = TRUE - - if(CR.name == "") - log_unit_test("[CR.type]: Reagents - chemical reaction had blank name.") - failed = TRUE - - if(!CR.id) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid ID.") - failed = TRUE - - if(CR.id != lowertext(CR.id)) - log_unit_test("[CR.type]: Reagents - chemical reaction ID must be all lowercase.") - failed = TRUE - - if(CR.id in collection_id) - log_unit_test("[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" is not unique, used first in [collection_id[CR.id]].") - failed = TRUE - else - collection_id[CR.id] = CR.type - - if(CR.result_amount < 0) - log_unit_test("[CR.type]: Reagents - chemical reaction ID \"[CR.name]\" had less than 0 as as result_amount?") - failed = TRUE - - if(CR.required_reagents && CR.required_reagents.len) - for(var/RR in CR.required_reagents) - if(!SSchemistry.chemical_reagents[RR]) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".") - failed = TRUE - if(CR.required_reagents[RR] <= 0) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent amount or in invalid format \"[CR.required_reagents[RR]]\".") - failed = TRUE - - - if(CR.catalysts && CR.catalysts.len) - for(var/RR in CR.catalysts) - if(!SSchemistry.chemical_reagents[RR]) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".") - failed = TRUE - if(CR.catalysts[RR] <= 0) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid catalysts amount or in invalid format \"[CR.catalysts[RR]]\".") - failed = TRUE - - if(CR.inhibitors && CR.inhibitors.len) - for(var/RR in CR.inhibitors) - if(!SSchemistry.chemical_reagents[RR]) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid required reagent ID \"[RR]\".") - failed = TRUE - if(CR.inhibitors[RR] <= 0) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid inhibitors amount or in invalid format \"[CR.inhibitors[RR]]\".") - failed = TRUE - - if(CR.result) - if(!SSchemistry.chemical_reagents[CR.result]) - log_unit_test("[CR.type]: Reagents - chemical reaction had invalid result reagent ID \"[CR.result]\".") - failed = TRUE - - if(failed) - fail("One or more /decl/chemical_reaction subtypes had invalid results or components.") - else - pass("All /decl/chemical_reaction subtypes had correct settings.") - return TRUE - - - -/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents - name = "REAGENTS: Prefilled reagent containers shall have valid reagents" - -/datum/unit_test/prefilled_reagent_containers_shall_have_valid_reagents/start_test() - var/failed = FALSE - - var/obj/container = new /obj - for(var/RC in subtypesof(/obj/item/reagent_containers/glass)) - var/obj/item/reagent_containers/glass/R = new RC(container) - - if(R.prefill && R.prefill.len) - for(var/ID in R.prefill) - if(!SSchemistry.chemical_reagents[ID]) - log_unit_test("[RC]: Reagents - reagent prefill had invalid reagent ID \"[ID]\".") - failed = TRUE - - qdel(R) - - for(var/DC in subtypesof(/obj/item/reagent_containers/chem_disp_cartridge)) - var/obj/item/reagent_containers/chem_disp_cartridge/D = new DC(container) - - if(D.spawn_reagent) - if(!SSchemistry.chemical_reagents[D.spawn_reagent]) - log_unit_test("[DC]: Reagents - chemical dispenser cartridge had invalid reagent ID \"[D.spawn_reagent]\".") - failed = TRUE - - qdel(D) - - qdel(container) - - if(failed) - fail("One or more /obj/item/reagent_containers had an invalid prefill reagent.") - else - pass("All /obj/item/reagent_containers had valid prefill reagents.") - return TRUE - - -/datum/unit_test/chemical_reactions_shall_not_conflict - name = "REAGENTS: Chemical Reactions shall not conflict" - var/obj/fake_beaker = null - var/list/result_reactions = list() - -/datum/unit_test/chemical_reactions_shall_not_conflict/start_test() - var/failed = FALSE - - #ifdef UNIT_TEST - var/list/all_reactions = decls_repository.get_decls_of_subtype(/decl/chemical_reaction) - for(var/rtype in all_reactions) - var/decl/chemical_reaction/CR = all_reactions[rtype] - - if(CR.name == REAGENT_DEVELOPER_WARNING) // Ignore these types as they are meant to be overridden - continue - if(!CR.name || CR.name == "" || !CR.id || CR.id == "") - continue - if(CR.result_amount <= 0) //Makes nothing anyway, or maybe an effect/explosion! - continue - if(!CR.result) // Cannot check for this - continue - - if(istype(CR, /decl/chemical_reaction/instant/slime)) - // slime time - var/decl/chemical_reaction/instant/slime/SR = CR - if(!SR.required) - continue - var/obj/item/slime_extract/E = new SR.required() - qdel_swap(fake_beaker, E) - fake_beaker.reagents.maximum_volume = 5000 - else if(istype(CR, /decl/chemical_reaction/distilling)) - // distilling - var/obj/distilling_tester/D = new() - qdel_swap(fake_beaker, D) - fake_beaker.reagents.maximum_volume = 5000 - else - // regular beaker - qdel_swap(fake_beaker, new /obj/item/reagent_containers/glass/beaker()) - fake_beaker.reagents.maximum_volume = 5000 - - // Perform test! If it fails once, it will perform a deeper check trying to use the inhibitors of anything in the beaker - RegisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA, PROC_REF(get_signal_data)) - if(perform_reaction(CR)) - // Check if we failed the test with inhibitors in use, if so we absolutely couldn't make it... - // Uncomment the UNIT_TEST section in code\modules\reagents\reactions\_reactions.dm if you require more info - log_unit_test("[CR.type]: Reagents - chemical reaction did not produce \"[CR.result]\". CONTAINS: \"[fake_beaker.reagents.get_reagents()]\"") - failed = TRUE - UnregisterSignal(fake_beaker.reagents, COMSIG_UNITTEST_DATA) - qdel_null(fake_beaker) - #endif - - if(failed) - fail("One or more /decl/chemical_reaction subtypes conflict with another reaction.") - else - pass("All /decl/chemical_reaction subtypes had no conflicts.") - return TRUE - -/datum/unit_test/chemical_reactions_shall_not_conflict/proc/perform_reaction(var/decl/chemical_reaction/CR, var/list/inhib = list()) - var/scale = 1 - if(CR.result_amount < 1) - scale = 1 / CR.result_amount // Create at least 1 unit - - // Weird loop here, but this is used to test both instant and distilling reactions - // Instants will meet the while() condition on the first loop and go to the next stuff - // but distilling will repeat over and over until the temperature test is finished! - var/temp_test = 0 - do - // clear for inhibitor searches - fake_beaker.reagents.clear_reagents() - result_reactions.Cut() - - if(inhib.len) // taken from argument and not reaction! Put in FIRST! - for(var/RR in inhib) - fake_beaker.reagents.add_reagent(RR, inhib[RR] * scale) - if(CR.catalysts) // Required for reaction - for(var/RR in CR.catalysts) - fake_beaker.reagents.add_reagent(RR, CR.catalysts[RR] * scale) - if(CR.required_reagents) - for(var/RR in CR.required_reagents) - fake_beaker.reagents.add_reagent(RR, CR.required_reagents[RR] * scale) - - if(!istype(CR, /decl/chemical_reaction/distilling)) - break // Skip the next section if we're not distilling - - // Check distillation at 10 points along its temperature range! - // This is so multiple reactions with the same requirements, but different temps, can be tested. - temp_test += 0.1 - var/obj/distilling_tester/DD = fake_beaker - DD.test_distilling(CR,temp_test) - - if(fake_beaker.reagents.has_reagent(CR.result)) - return FALSE // Distilling success - - while(temp_test > 1) - - // Check beaker to see if we reached our goal! - if(fake_beaker.reagents.has_reagent(CR.result)) - return FALSE // INSTANT SUCCESS! - - if(inhib.len) - // We've checked with inhibitors, so we're already in inhibitor checking phase. - // So we've absolutely failed this time. There is no way to make this... - return TRUE - - if(!result_reactions.len) - // Nothing to check for inhibitors... - for(var/decl/chemical_reaction/test_react in result_reactions) - log_unit_test("[CR.type]: Reagents - Used [test_react] but failed.") - return TRUE - - // Otherwise we check the resulting reagents and use their inhibitor this time! - for(var/decl/chemical_reaction/test_react in result_reactions) - if(!test_react) - continue - if(!test_react.inhibitors.len) - continue - // Test one by one - for(var/each in test_react.inhibitors) - if(!perform_reaction(CR, list("[each]" = test_react.inhibitors["[each]"]))) - return FALSE // SUCCESS using an inhibitor! - // Test all at once - if(!perform_reaction(CR, test_react.inhibitors)) - return FALSE // SUCCESS using all inhibitors! - - // No inhibiting reagent worked... - for(var/decl/chemical_reaction/test_react in result_reactions) - log_unit_test("[CR.type]: Reagents - Used [test_react] but failed.") - return TRUE - -/datum/unit_test/chemical_reactions_shall_not_conflict/get_signal_data(atom/source, list/data = list()) - result_reactions.Add(data[1]) // Append the reactions that happened, then use that to check their inhibitors - - -/datum/unit_test/chemical_grinding_must_produce_valid_results - name = "REAGENTS: Chemical Grinding Must Have Valid Results" - -/datum/unit_test/chemical_grinding_must_produce_valid_results/start_test() - var/failed = FALSE - - for(var/grind in GLOB.sheet_reagents + GLOB.ore_reagents) - var/list/results = GLOB.sheet_reagents[grind] - if(!results) - results = GLOB.ore_reagents[grind] - if(!results || !islist(results)) - log_unit_test("[grind]: Reagents - Grinding result had invalid list.") - failed = TRUE - continue - if(!results.len) - log_unit_test("[grind]: Reagents - Grinding result had empty.") - failed = TRUE - continue - for(var/reg_id in results) - if(!SSchemistry.chemical_reagents[reg_id]) - log_unit_test("[grind]: Reagents - Grinding result had invalid reagent id \"[reg_id]\".") - failed = TRUE - - if(failed) - fail("One or more grindable sheet or ore entries had invalid reagents or lists.") - else - pass("All grindable sheet or ore entries had valid lists and reagents.") - return TRUE - - - -// Used to test distillations without hacking the other machinery's code up -/obj/distilling_tester - icon = 'icons/obj/weapons.dmi' - icon_state = "cartridge" - var/datum/gas_mixture/GM = new() - var/current_temp = 0 - -/obj/distilling_tester/Initialize(mapload) - create_reagents(5000,/datum/reagents/distilling) - . = ..() - -/obj/distilling_tester/return_air() - return GM - -/obj/distilling_tester/proc/test_distilling(var/decl/chemical_reaction/distilling/D, var/temp_prog) - qdel_swap(GM,new()) - if(D.require_xgm_gas) - GM.gas[D.require_xgm_gas] = 100 - else - if(D.rejects_xgm_gas == GAS_N2) - GM.gas[GAS_O2] = 100 - else - GM.gas[GAS_N2] = 100 - if(D.minimum_xgm_pressure) - GM.temperature = (D.minimum_xgm_pressure * CELL_VOLUME) / (GM.gas[D.require_xgm_gas] * R_IDEAL_GAS_EQUATION) - - // Try this 10 times, We need to know if something is blocking at multiple temps. - // If it passes unit test, it might still be awful to make though, gotta find the right gas mix! - current_temp = LERP( D.temp_range[1], D.temp_range[2], temp_prog) - reagents.handle_reactions() - -/obj/distilling_tester/Destroy(force, ...) - qdel_null(GM) - . = ..() diff --git a/code/unit_tests/recipe_tests.dm b/code/unit_tests/recipe_tests.dm deleted file mode 100644 index a2d7594d0d..0000000000 --- a/code/unit_tests/recipe_tests.dm +++ /dev/null @@ -1,29 +0,0 @@ -/datum/unit_test/recipe_test_shall_have_valid_result_and_quantity - name = "RECIPES: Recipes shall have valid result and result_quantity definitions" - -/datum/unit_test/recipe_test_shall_have_valid_result_and_quantity/start_test() - var/failed = FALSE - for(var/datum/recipe/R in subtypesof(/datum/recipe)) - var/our_result = initial(R.result) - var/our_amount = initial(R.result_quantity) - if(!our_result) - log_unit_test("[R]: Recipes - Missing result.") - failed = TRUE - else if(!ispath(our_result, /atom/movable)) - log_unit_test("[R]: Recipes - Improper result; [our_result] is not an obj or mob.") - failed = TRUE - if(isnull(our_amount)) - log_unit_test("[R]: Recipes - result_quantity must be set.") - failed = TRUE - if(our_amount <= 0) - log_unit_test("[R]: Recipes - result_quantity must be greater than zero.") - failed = TRUE - else if(!ISINTEGER(our_amount)) - log_unit_test("[R]: Recipes - result_quantity must be an integer.") - failed = TRUE - - if(failed) - fail("One or more /datum/recipe subtypes had invalid results or result_quantity definitions.") - else - pass("All /datum/recipe subtypes had correct settings.") - return TRUE diff --git a/code/unit_tests/recycler_vendor_tests.dm b/code/unit_tests/recycler_vendor_tests.dm deleted file mode 100644 index 2a3ace9144..0000000000 --- a/code/unit_tests/recycler_vendor_tests.dm +++ /dev/null @@ -1,30 +0,0 @@ -/datum/unit_test/recycler_vendor_entry_invalid - name = "RECYCLER: Recycler Vendor Entry shall have valid definitions" - -/datum/unit_test/recycler_vendor_entry_invalid/start_test() - var/failed = FALSE - - - - for(var/datum/maint_recycler_vendor_entry/R in subtypesof(/datum/maint_recycler_vendor_entry)) - var/item_to_spawn = initial(R.object_type_to_spawn) - var/item_cost = initial(R.item_cost) - var/global_item_cap = initial(R.per_round_cap) - var/individual_item_cap = initial(R.per_person_cap) - var/is_scam = initial(R.is_scam) - if(!item_to_spawn && !is_scam) - log_unit_test("[R] : Vendor Entry - Missing Object Type on non-scam entry") - failed = TRUE - if(item_cost < 0) - log_unit_test("[R] : Vendor Entry - Negative Cost") - failed = TRUE - if((item_cost == 0) && ((global_item_cap < 0)&&(individual_item_cap < 0))) //nobody SHOULD set the cost to 0 but it's possible i guess. maybe for future scams - log_unit_test("[R] : Vendor Entry - Infinite Item Spawning due to no individual or global item cap") - failed = TRUE - - if(failed) - fail("One or more /datum/maint_recycler_vendor_entry had invalid results or definitions.") - else - pass("All /datum/maint_recycler_vendor_entry subtypes had correct settings.") - - return TRUE diff --git a/code/unit_tests/robot_tests.dm b/code/unit_tests/robot_tests.dm deleted file mode 100644 index fb8eb92975..0000000000 --- a/code/unit_tests/robot_tests.dm +++ /dev/null @@ -1,224 +0,0 @@ -/datum/unit_test/all_robot_sprites_must_be_valid - name = "ROBOTS: Robot sprites must be valid" - var/signal_failed = FALSE - -/datum/unit_test/all_robot_sprites_must_be_valid/start_test() - var/failed = 0 - - for(var/sprite in subtypesof(/datum/robot_sprite)) - var/datum/robot_sprite/RS = new sprite() - if(!RS.name) // Parent type, ignore me - continue - - if(!RS.sprite_icon) - log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", missing sprite_icon.") - failed = TRUE - continue - - var/list/checks = list( - "[ROBOT_HAS_SPEED_SPRITE]" = "-roll", - "[ROBOT_HAS_SHIELD_SPRITE]" = "-shield", - "[ROBOT_HAS_SHIELD_SPEED_SPRITE]" = "-speed_shield", - "[ROBOT_HAS_MELEE_SPRITE]" = "-melee", - "[ROBOT_HAS_DAGGER_SPRITE]" = "-dagger", - "[ROBOT_HAS_BLADE_SPRITE]" = "-blade", - "[ROBOT_HAS_GUN_SPRITE]" = "-gun", - "[ROBOT_HAS_LASER_SPRITE]" = "-laser", - "[ROBOT_HAS_TASER_SPRITE]" = "-taser", - "[ROBOT_HAS_DISABLER_SPRITE]" = "-disabler" - ) - for(var/C in checks) - if(RS.sprite_flag_check(text2num(C))) - if(check_state(RS,checks[C])) - failed = TRUE - // eyes, lights, markings - if(RS.has_eye_sprites) - if(check_state(RS,"-eyes")) - failed = TRUE - if(RS.has_eye_light_sprites) - if(check_state(RS,"-lights")) - failed = TRUE - if(LAZYLEN(RS.sprite_decals)) - for(var/decal in RS.sprite_decals) - if(check_state(RS,"-[decal]")) - failed = TRUE - if(LAZYLEN(RS.sprite_animations)) - for(var/animation in RS.sprite_animations) - if(check_state(RS,"-[animation]")) - failed = TRUE - // Control panel - if(RS.has_custom_open_sprites) - if(check_state(RS,"-openpanel_nc")) - failed = TRUE - if(check_state(RS,"-openpanel_c")) - failed = TRUE - if(check_state(RS,"-openpanel_w")) - failed = TRUE - // Glow State - if(RS.has_glow_sprites) - if(check_state(RS,"-glow")) - failed = TRUE - // Bellies - if(RS.has_vore_belly_sprites && !RS.belly_capacity_list) - if(RS.has_sleeper_light_indicator) - // belly r/g light - if(check_state(RS,"-sleeper-r")) - failed = TRUE - if(check_state(RS,"-sleeper-g")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-sleeper-r-[rest_style]")) - failed = TRUE - if(check_state(RS,"-sleeper-g-[rest_style]")) - failed = TRUE - // struggling - if(RS.has_vore_struggle_sprite) - if(check_state(RS,"-sleeper-r-struggle")) - failed = TRUE - if(check_state(RS,"-sleeper-g-struggle")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-sleeper-r-[rest_style]-struggle")) - failed = TRUE - if(check_state(RS,"-sleeper-g-[rest_style]-struggle")) - failed = TRUE - else - // belly - if(check_state(RS,"-sleeper")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-sleeper-[rest_style]")) - failed = TRUE - // struggling - if(RS.has_vore_struggle_sprite) - if(check_state(RS,"-sleeper-struggle")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-sleeper-[rest_style]-struggle")) - failed = TRUE - else if (RS.belly_capacity_list) - for(var/belly in RS.belly_capacity_list) - for(var/num = 1 to RS.belly_capacity_list[belly]) - // big belly - if(check_state(RS,"-[belly]-[num]")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-[belly]-[num]-[rest_style]")) - failed = TRUE - // struggling - if(RS.has_vore_struggle_sprite) - if(check_state(RS,"-[belly]-[num]-struggle")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-[belly]-[num]-[rest_style]-struggle")) - failed = TRUE - if(RS.belly_light_list) - for(var/belly in RS.belly_light_list) - for(var/num = 1 to RS.belly_light_list[belly]) - // multi belly r/g light - if(check_state(RS,"-[belly]-[num]-r")) - failed = TRUE - if(check_state(RS,"-[belly]-[num]-g")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-[belly]-[num]-r-[rest_style]")) - failed = TRUE - if(check_state(RS,"-[belly]-[num]-g-[rest_style]")) - failed = TRUE - // struggling - if(RS.has_vore_struggle_sprite) - if(check_state(RS,"-[belly]-[num]-r-struggle")) - failed = TRUE - if(check_state(RS,"-[belly]-[num]-g-struggle")) - failed = TRUE - if(RS.has_vore_belly_resting_sprites) - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-[belly]-[num]-r-[rest_style]-struggle")) - failed = TRUE - if(check_state(RS,"-[belly]-[num]-g-[rest_style]-struggle")) - failed = TRUE - // reseting - for(var/rest_style in RS.rest_sprite_options) - rest_style = lowertext(rest_style) - if(rest_style == "default") - rest_style = "rest" - if(check_state(RS,"-[rest_style]")) - failed = TRUE - if(RS.has_glow_sprites) - if(check_state(RS,"-[rest_style]-glow")) - failed = TRUE - if(RS.has_rest_lights_sprites) - if(check_state(RS,"-[rest_style]-lights")) - failed = TRUE - if(RS.has_rest_eyes_sprites) - if(check_state(RS,"-[rest_style]-eyes")) - failed = TRUE - // death - if(RS.has_dead_sprite) - if(check_state(RS,"-wreck")) - failed = TRUE - if(RS.has_dead_sprite_overlay) // Only one per dmi - if(!("wreck-overlay" in cached_icon_states(RS.sprite_icon))) - log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", missing icon_state wreck-overlay, in dmi \"[RS.sprite_icon]\".") - failed = TRUE - // offset - var/icon/I = new(RS.sprite_icon) - if(RS.icon_x != I.Width()) - log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_x \"[RS.icon_x]\" did not match dmi configured width \"[I.Width()]\"") - failed = TRUE - if(RS.icon_y != I.Height()) - log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", icon_y \"[RS.icon_y]\" did not match dmi configured height \"[I.Height()]\"") - failed = TRUE - if(RS.icon_y != RS.vis_height) - log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", vis_height \"[RS.vis_height]\" did not match icon_y \"[RS.icon_y]\"") - failed = TRUE - var/legal_offset = (I.Width() - world.icon_size) / 2 - if(RS.pixel_x != -legal_offset) - log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", pixel_x \"[RS.pixel_x]\" did not have correct offset, should be \"[-legal_offset]\"") - failed = TRUE - qdel(I) - qdel(RS) - - if(failed) - fail("One or more /datum/robot_sprite items had invalid flags or icons") - else - pass("All /datum/robot_sprite are valid.") - return 1 - -/datum/unit_test/all_robot_sprites_must_be_valid/proc/check_state(var/datum/robot_sprite/RS,var/append) - var/check_state = "[RS.sprite_icon_state][append]" - if(!(check_state in cached_icon_states(RS.sprite_icon))) - log_unit_test("[RS.type]: Robots - Robot sprite \"[RS.name]\", enabled but missing icon_state \"[check_state]\", in dmi \"[RS.sprite_icon]\".") - return TRUE - return FALSE diff --git a/code/unit_tests/sqlite_tests.dm b/code/unit_tests/sqlite_tests.dm deleted file mode 100644 index e8c19c1a7f..0000000000 --- a/code/unit_tests/sqlite_tests.dm +++ /dev/null @@ -1,69 +0,0 @@ -/datum/unit_test/sqlite - name = "SQLite template" // Template has to be in the name or this test will be ran, and fail. - var/database/stub_sqlite_db = null - -/datum/unit_test/sqlite/proc/setup_stub_db() - fdel("data/sqlite/testing_[name].db") // In case any remain from a previous local test, so we can have a clean new database. - stub_sqlite_db = new("data/sqlite/testing_[name].db") // Unfortunately, byond doesn't like having working sqlite stuff w/o a file existing. - SSsqlite.init_schema(stub_sqlite_db) - -// Feedback table tests. -/datum/unit_test/sqlite/feedback/insert - name = "SQLITE FEEDBACK: Insert and Retrieve Data" - -/datum/unit_test/sqlite/feedback/insert/start_test() - // Arrange. - setup_stub_db() - var/test_author = "alice" - var/test_topic = "Test" - var/test_content = "Bob is lame." - - // Act. - SSsqlite.insert_feedback(author = test_author, topic = test_topic, content = test_content, sqlite_object = stub_sqlite_db) - var/database/query/Q = new("SELECT * FROM [SQLITE_TABLE_FEEDBACK]") - Q.Execute(stub_sqlite_db) - SSsqlite.sqlite_check_for_errors(Q, "Sqlite Insert Unit Test") - Q.NextRow() - - // Assert. - var/list/row_data = Q.GetRowData() - if(row_data[SQLITE_FEEDBACK_COLUMN_AUTHOR] == test_author && row_data[SQLITE_FEEDBACK_COLUMN_TOPIC] == test_topic && row_data[SQLITE_FEEDBACK_COLUMN_CONTENT] == test_content) - pass("No issues found.") - else - fail("Data insert and loading failed to have matching information.") - return TRUE - - -/datum/unit_test/sqlite/feedback/cooldown - name = "SQLITE FEEDBACK: Cooldown" - -/datum/unit_test/sqlite/feedback/cooldown/start_test() - // Arrange. - setup_stub_db() - var/days_to_wait = 1 - var/issues = 0 - - // Act. - SSsqlite.insert_feedback(author = "Alice", topic = "Testing", content = "This is a test.", sqlite_object = stub_sqlite_db) - - var/alice_cooldown_block = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db) - var/bob_cooldown = SSsqlite.get_feedback_cooldown("Bob", days_to_wait, stub_sqlite_db) - days_to_wait = 0 - var/alice_cooldown_allow = SSsqlite.get_feedback_cooldown("Alice", days_to_wait, stub_sqlite_db) - - // Assert. - if(alice_cooldown_block <= 0) - issues++ - log_unit_test("User 'Alice' did not receive a cooldown, when they were supposed to.") - if(bob_cooldown > 0) - issues++ - log_unit_test("User 'Bob' did receive a cooldown, when they did not do anything.") - if(alice_cooldown_allow > 0) - issues++ - log_unit_test("User 'Alice' did receive a cooldown, when no cooldown is supposed to be enforced.") - - if(issues) - fail("[issues] issues were found.") - else - pass("No issues found.") - return TRUE diff --git a/code/unit_tests/subsystem_tests.dm b/code/unit_tests/subsystem_tests.dm deleted file mode 100644 index 4ad9a1989b..0000000000 --- a/code/unit_tests/subsystem_tests.dm +++ /dev/null @@ -1,46 +0,0 @@ -/datum/unit_test/subsystem_shall_be_initialized - name = "SUBSYSTEM - INIT: Subsystems shall be initalized" - -/datum/unit_test/subsystem_shall_be_initialized/start_test() - var/list/bad_subsystems = list() - for(var/datum/controller/subsystem/SS in Master.subsystems) - if (SS.flags & SS_NO_INIT) - continue - if(!SS.subsystem_initialized) - bad_subsystems += SS.type - - if(bad_subsystems.len) - fail("Found improperly initialized subsystems: [english_list(bad_subsystems)]") - else - pass("All susbsystems have initialized properly") - - return 1 - -/* Uncomment when all atoms init properly -/datum/unit_test/subsystem_atom_shall_have_no_bad_init_calls - name = "SUBSYSTEM - ATOMS: Shall have no bad init calls" - -/datum/unit_test/subsystem_atom_shall_have_no_bad_init_calls/start_test() - if(SSatoms.BadInitializeCalls.len) - log_bad(jointext(SSatoms.InitLog(), null)) - fail("[SSatoms] had bad initialization calls.") - else - pass("[SSatoms] had no bad initialization calls.") - return 1 - -/datum/unit_test/all_atoms_shall_be_initialized - name = "SUBSYSTEM - ATOMS: All atoms shall be initialized." - -/datum/unit_test/all_atoms_shall_be_initialized/start_test() - set background = TRUE // avoid infinite loop warning; SS will still wait for us. - var/fail = FALSE - for(var/atom/atom in world) - if(!(atom.initialized)) - log_bad("Uninitialized atom: [atom.log_info_line()]") - fail = TRUE - if(fail) - fail("There were uninitialized atoms.") - else - pass("All atoms were initialized") - return 1 -*/ diff --git a/code/unit_tests/trait_tests.dm b/code/unit_tests/trait_tests.dm deleted file mode 100644 index 411e61a4f5..0000000000 --- a/code/unit_tests/trait_tests.dm +++ /dev/null @@ -1,58 +0,0 @@ -/datum/unit_test/all_traits_unique_names - name = "TRAITS: All traits shall have unique names" - -/datum/unit_test/all_traits_unique_names/start_test() - var/failed = FALSE - - var/list/used_named = list() - for(var/traitpath in GLOB.all_traits) - var/datum/trait/T = GLOB.all_traits[traitpath] - if(T.name in used_named) - log_unit_test("[T.type]: Trait - The name \"[T.name]\" is already in use.") - failed = TRUE - else - used_named.Add(T.name) - - if(failed) - fail("One or more traits shared a name.") - else - pass("All [GLOB.all_traits.len] have unique names.") - return failed - - -/datum/unit_test/autohiss_shall_be_exclusive - name = "TRAITS: Autohiss traits shall be exclusive" - -/datum/unit_test/autohiss_shall_be_exclusive/start_test() - var/failed = FALSE - - var/list/hiss_list = list() - for(var/traitpath in GLOB.all_traits) - var/datum/trait/T = GLOB.all_traits[traitpath] - if(!T.var_changes) - continue - if(!islist(T.var_changes["autohiss_basic_map"])) - continue - hiss_list += T - - for(var/datum/trait/T in hiss_list) - if(T.type in T.excludes) - log_unit_test("[T.type]: Trait - Autohiss excludes itself.") - failed = TRUE - - if(!T.excludes) - log_unit_test("[T.type]: Trait - Autohiss missing exclusion list.") - failed = TRUE - continue - - var/list/exempt_list = hiss_list.Copy() - T // MUST exclude all others except itself - for(var/datum/trait/EX in exempt_list) - if(!(EX.type in T.excludes)) - log_unit_test("[T.type]: Trait - Autohiss missing exclusion for [EX].") - failed = TRUE - - if(failed) - fail("One or more autohiss traits allow another autohiss to be chosen with it.") - else - pass("All [hiss_list.len] autohiss traits are properly exclusive.") - return failed diff --git a/code/unit_tests/unit_test.dm b/code/unit_tests/unit_test.dm deleted file mode 100644 index 416d505033..0000000000 --- a/code/unit_tests/unit_test.dm +++ /dev/null @@ -1,114 +0,0 @@ -var/all_unit_tests_passed = 1 -var/failed_unit_tests = 0 -var/total_unit_tests = 0 - -/datum/unit_test - var/name = "template" - var/disabled = "" - var/async = 0 - var/reported = 0 - -/datum/unit_test/proc/fail(var/message) - all_unit_tests_passed = 0 - failed_unit_tests++ - reported = 1 - log_unit_test("[ASCII_RED]!! FAILURE !! \[[name]\]: [message][ASCII_RESET]") - -/datum/unit_test/proc/pass(var/message) - reported = 1 - log_unit_test("[ASCII_GREEN]** SUCCESS ** \[[name]\]: [message][ASCII_RESET]") - -/datum/unit_test/proc/start_test() - fail("No test proc.") - -/datum/unit_test/proc/check_result() - fail("No check results proc.") - return 1 - - -/proc/initialize_unit_tests() - log_unit_test("Initializing Unit Testing") - - if(!ticker) - stack_trace("No Ticker") - world.Del() - - var/said_msg = 0 - while(!Master.current_runlevel) // Make sure the initial startup is complete. - if(!said_msg) - said_msg = 1 - log_unit_test("Waiting for subystems initilization to finish.") - stoplag(10) - - world.save_mode("extended") - - sleep(1) - - ticker.current_state = GAME_STATE_SETTING_UP - Master.SetRunLevel(RUNLEVEL_SETUP) - - log_unit_test("Round has been started. Waiting 10 seconds to start tests.") - sleep(100) - - var/list/test_datums = typesof(/datum/unit_test) - - var/list/async_test = list() - var/list/started_tests = list() - - log_unit_test("Testing Started.") - - for(var/test in test_datums) - var/datum/unit_test/d = new test() - - if(d.disabled) - d.pass("[ASCII_RED]Check Disabled: [d.disabled][ASCII_RESET]") - continue - - if(findtext(d.name, "template")) - continue - - if(isnull(d.start_test())) // Start the test. - d.fail("Test Runtimed") - - if(d.async) // If it's async then we'll need to check back on it later. - async_test.Add(d) - total_unit_tests++ - - // - // Check the async tests to see if they are finished. - // - - while(async_test.len) - for(var/datum/unit_test/test in async_test) - if(test.check_result()) - async_test.Remove(test) - sleep(1) - - // - // Make sure all Unit Tests reported a result - // - - for(var/datum/unit_test/test in started_tests) - if(!test.reported) - test.fail("Test failed to report a result.") - - if(all_unit_tests_passed) - log_unit_test("[ASCII_GREEN]*** All Unit Tests Passed \[[total_unit_tests]\] ***[ASCII_RESET]") - else - log_unit_test("[ASCII_RED]!!! \[[failed_unit_tests]\\[total_unit_tests]\] Unit Tests Failed !!![ASCII_RESET]") - log_unit_test("Caught [GLOB.total_runtimes] Runtime\s.") - world.Del() - -/datum/unit_test/proc/get_standard_turf() - return locate(20,20,1) - -/datum/unit_test/proc/log_bad(var/message) - log_unit_test("[ASCII_RED]\[[name]\]: [message][ASCII_RESET]") - -/datum/unit_test/proc/log_debug(var/message) - log_unit_test("[ASCII_YELLOW]--- DEBUG --- \[[name]\]: [message][ASCII_RESET]") - -// This signal should never be possible to call if the game is not compiled for unit tests -/datum/unit_test/proc/get_signal_data(atom/source, list/data = list()) - SIGNAL_HANDLER - return diff --git a/code/unit_tests/unit_test_vr.dm b/code/unit_tests/unit_test_vr.dm deleted file mode 100644 index ae3d871b15..0000000000 --- a/code/unit_tests/unit_test_vr.dm +++ /dev/null @@ -1,27 +0,0 @@ -// -// VOREStation extensions to unit test framework. -// - -/datum/unit_test - var/static/default_mobloc = null - -/datum/unit_test/proc/create_test_mob(var/turf/mobloc = null, var/mobtype = /mob/living/carbon/human, var/with_mind = FALSE) - if(isnull(mobloc)) - if(!default_mobloc) - for(var/turf/simulated/floor/tiled/T in world) - var/pressure = T.zone.air.return_pressure() - if(90 < pressure && pressure < 120) // Find a turf between 90 and 120 - default_mobloc = T - break - mobloc = default_mobloc - if(!mobloc) - fail("Unable to find a location to create test mob") - return 0 - - var/mob/living/carbon/human/H = new mobtype(mobloc) - H.init_vore() - - if(with_mind) - H.mind_initialize("TestKey[rand(0,10000)]") - - return H diff --git a/code/unit_tests/zz_tests.dm b/code/unit_tests/zz_tests.dm deleted file mode 100644 index 39f904ce03..0000000000 --- a/code/unit_tests/zz_tests.dm +++ /dev/null @@ -1,30 +0,0 @@ -///NOTE: LEAVE THIS FILE UNTICKED AND THE SEGMENTS OF CODE COMMENTED OUT UNLESS YOU NEED TO DO SOMETHING SPECIFIC / TRACK SOMETHING SPECIFIC. - - -/* -/// Testing if DNA & Species datums get destoryed or not. Use this as a basis to test for memory leaks. -/// This happens if DNA is copied from somewhere but not qdel_swap()'d. WARNING: Not every time it's copied should be qdel_swap()'d. -/// But if you are doing anything DNA copy related, turn this on and make sure it's not infintely storing a datum somewhere. -GLOBAL_VAR(total_dna_counters) -GLOBAL_VAR(total_species_counters) - -/datum/dna/New() - . = ..() - GLOB.total_dna_counters += 1 - -/datum/dna/Destroy() - . = ..() - GLOB.total_dna_counters -= 1 - -/datum/species/New() - . = ..() - GLOB.total_species_counters += 1 - -/datum/species/Destroy() - . = ..() - GLOB.total_species_counters -= 1 - -/proc/delete_all_monkeys() - for(var/mob/living/carbon/human/monkey/monkeys in world) - qdel(monkeys) -*/ diff --git a/config/example/config.txt b/config/example/config.txt index e110289d92..c51746668f 100644 --- a/config/example/config.txt +++ b/config/example/config.txt @@ -17,6 +17,12 @@ $include sqlite.txt ## Server name: This appears at the top of the screen in-game. In this case it will read "tgstation: station_name" where station_name is the randomly generated name of the station for the round. Remove the # infront of SERVERNAME and replace 'tgstation' with the name of your choice # SERVERNAME spacestation13 +## Lobby time: This is the amount of time between rounds that players have to setup their characters and be ready. +LOBBY_COUNTDOWN 120 + +## Round End Time: This is the amount of time after the round ends that players have to murder death kill each other. +ROUND_END_COUNTDOWN 90 + ## Comment this out if you want to use the SQL based admin system, the legacy system uses admins.txt. ## You need to set up your database to use the SQL based system. ## This flag is automatically enabled if SQL_ENABLED isn't @@ -285,9 +291,6 @@ TICKLAG 0.25 ## FIXME: Unused config. Only uncomment if changed in code. # TICKCOMP 1 -## Whether the server will talk to other processes through socket_talk -SOCKET_TALK 0 - ## Uncomment this to ban use of ToR #TOR_BAN @@ -600,5 +603,56 @@ JUKEBOX_TRACK_FILES config/jukebox.json #Turn this on if you want all admin-PMs to go to be sent to discord, and not only the first message of a ticket. #DISCORD_AHELPS_ALL +###Master Controller High Pop Mode### + +##The Master Controller(MC) is the primary system controlling timed tasks and events in SS13 (lobby timer, game checks, lighting updates, atmos, etc) +##Default base MC tick rate (1 = process every "byond tick" (see: tick_lag/fps config settings), 2 = process every 2 byond ticks, etc) +## Setting this to 0 will prevent the Master Controller from ticking +BASE_MC_TICK_RATE 1 + +##High population MC tick rate +## Byond rounds timer values UP, but the tick rate is modified with heuristics during lag spites so setting this to something like 2 +## will make it run every 2 byond ticks, but will also double the effect of anti-lag heuristics. You can instead set it to something like +## 1.1 to make it run every 2 byond ticks, but only increase the effect of anti-lag heuristics by 10%. or 1.5 for 50%. +## (As an aside, you could in theory also reduce the effect of anti-lag heuristics in the base tick rate by setting it to something like 0.5) +HIGH_POP_MC_TICK_RATE 1.1 + +##Engage high pop mode if player count raises above this (Player in this context means any connected user. Lobby, ghost or in-game all count) +HIGH_POP_MC_MODE_AMOUNT 65 + +##Disengage high pop mode if player count drops below this +DISABLE_HIGH_POP_MC_MODE_AMOUNT 60 + +## Uncomment to prevent the world from sleeping while no players are connected after initializations +#RESUME_AFTER_INITIALIZATIONS + ## Uncomment to set the number of /world/Reboot()s before the DreamDaemon restarts itself. 0 means restart every round. Requires tgstation server tools. #ROUNDS_UNTIL_HARD_RESTART 10 + +## Enable automatic profiling - Byond 513.1506 and newer only. +#AUTO_PROFILE + +## Determines the interval between each saved profiler snapshot (in deciseconds). +#PROFILER_INTERVAL 3000 + +## Threshold (in deciseconds) for real time between ticks before we start dumping profiles +DRIFT_DUMP_THRESHOLD 40 + +## How long to wait (in deciseconds) after a profile dump before logging another tickdrift sourced one +DRIFT_PROFILE_DELAY 150 + +## Uncomment to block all attempts to profile, for performance reasons +#FORBID_ALL_PROFILING + +## Uncomment to block granting profiling privileges to users with R_DEBUG, for performance purposes +#FORBID_ADMIN_PROFILING + +## Comment to disable sending a toast notification on the host server when initializations complete. +## Even if this is enabled, a notification will only be sent if there are no clients connected. +TOAST_NOTIFICATION_ON_INIT + +## Uncomment to allow admins with +DEBUG to start the byond-tracy profiler during the round. +#ALLOW_TRACY_START + +## Uncomment to allow admins with +DEBUG to queue the next round to run the byond-tracy profiler. +#ALLOW_TRACY_QUEUE diff --git a/config/example/game_options.txt b/config/example/game_options.txt index 6fe8697bc2..28423f5534 100644 --- a/config/example/game_options.txt +++ b/config/example/game_options.txt @@ -44,6 +44,9 @@ REVIVAL_BRAIN_LIFE -1 RUN_SPEED 3 WALK_SPEED 9 +## Force Random Names. +## I think this also randomizes bodies for some reason. +# FORCE_RANDOM_NAMES ## The variables below affect the movement of specific mob types. HUMAN_DELAY 0 diff --git a/config/names/clown.txt b/config/names/clown.txt deleted file mode 100644 index c29fca74ba..0000000000 --- a/config/names/clown.txt +++ /dev/null @@ -1,37 +0,0 @@ -Gigglesworth -Honkel the III -Goose McSunny -Mr. Shoe -Toodles Sharperton -Dinky Doodle -Honkerbelle -Bo Bo Sassy -Baby Cakes -Ladybug Honks -Ziggy Yoyo -Razzle Dazzle -Buster Frown -Pepinpop -Silly Willy -Jo Jo Bobo Bo -Pocket -Patches -Checkers -Freckle -Honker -Bonker -Skiddle -Scootaloo -Sprinkledinkle -Ronnie Pace -Miss Stockings -Slippy Joe -Redshirt McBeat -Flop O'Honker -Speckles -Bubble -Button -Sparkle -Giggles -Jingle -Candy \ No newline at end of file diff --git a/config/names/wizardfirst.txt b/config/names/wizardfirst.txt deleted file mode 100644 index 18806ca74b..0000000000 --- a/config/names/wizardfirst.txt +++ /dev/null @@ -1,36 +0,0 @@ -Jim -Gulstaff -Gandalf -Grimm -Mordenkainen -Elminister -Saruman -Vaarsuvius -Yoda -Zul -Nihilus -Vecna -Mogan -Circe -Prospero -Raistlin -Rasputin -Tzeentch -Khelben -Dumbledor -Houdini -Terefi -Urza -Tenser -Zagyg -Mystryl -Boccob -Merlin -Archchancellor -Radagast -Kreol -Kaschei -Lina -Morgan -Alatar -Palando \ No newline at end of file diff --git a/config/names/wizardsecond.txt b/config/names/wizardsecond.txt deleted file mode 100644 index 1fe60bdb27..0000000000 --- a/config/names/wizardsecond.txt +++ /dev/null @@ -1,39 +0,0 @@ -the Powerful -the Great -the Magician -the Wise -the Seething -the Amazing -the Spiral King -Darkmagic -the White -the Gray -Shado -the Sorcelator -the Raven -the Emperor -the Brown -Weatherwax -the Destroyer -the Deathless -Yagg -the Remorseful -the Weeping -the Unending -the All Knowing -Dark -Smith -the Conquerer -the Unstoppable -Gray -of Void -Unseen -Darko -Honko -the Bandit Killer -the Dragon Spooker -Inverse -le Fay -the Blue -the Red -the Benevolent \ No newline at end of file diff --git a/icons/blanks/32x32.dmi b/icons/blanks/32x32.dmi new file mode 100644 index 0000000000..6c4f2b33e0 Binary files /dev/null and b/icons/blanks/32x32.dmi differ diff --git a/icons/virgoicon_16.png b/icons/virgoicon_16.png new file mode 100644 index 0000000000..5889e20fa9 Binary files /dev/null and b/icons/virgoicon_16.png differ diff --git a/lib/DLLSocket/DLLSocket.cbp b/lib/DLLSocket/DLLSocket.cbp deleted file mode 100644 index 6415a5ebd6..0000000000 --- a/lib/DLLSocket/DLLSocket.cbp +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - diff --git a/lib/DLLSocket/compile.sh b/lib/DLLSocket/compile.sh deleted file mode 100644 index 0ff309bdb9..0000000000 --- a/lib/DLLSocket/compile.sh +++ /dev/null @@ -1 +0,0 @@ -g++ -static -shared -O3 -fPIC main.cpp -o DLLSocket.so diff --git a/lib/DLLSocket/main.cpp b/lib/DLLSocket/main.cpp deleted file mode 100644 index e3aafb8791..0000000000 --- a/lib/DLLSocket/main.cpp +++ /dev/null @@ -1,133 +0,0 @@ -// OS-specific networking includes -// ------------------------------- -#ifdef __WIN32 - #include - typedef int socklen_t; -#else - extern "C" { - #include - #include - #include - #include - #include - #include - #include - #include - } - - typedef int SOCKET; - typedef sockaddr_in SOCKADDR_IN; - typedef sockaddr SOCKADDR; - #define SOCKET_ERROR -1 -#endif - -// Socket used for all communications -SOCKET sock; - -// Address of the remote server -SOCKADDR_IN addr; - -// Buffer used to return dynamic strings to the caller -#define BUFFER_SIZE 1024 -char return_buffer[BUFFER_SIZE]; - -// exposed functions -// ------------------------------ - -const char* SUCCESS = "1\0"; // string representing success - -#ifdef __WIN32 - #define DLL_EXPORT __declspec(dllexport) -#else - #define DLL_EXPORT __attribute__ ((visibility ("default"))) -#endif - -// arg1: ip(in the xx.xx.xx.xx format) -// arg2: port(a short) -// return: NULL on failure, SUCCESS otherwise -extern "C" DLL_EXPORT const char* establish_connection(int n, char *v[]) -{ - // extract args - // ------------ - if(n < 2) return 0; - const char* ip = v[0]; - const char* port_s = v[1]; - unsigned short port = atoi(port_s); - - // set up network stuff - // -------------------- - #ifdef __WIN32 - WSADATA wsa; - WSAStartup(MAKEWORD(2,0),&wsa); - #endif - sock = socket(AF_INET,SOCK_DGRAM,0); - - // make the socket non-blocking - // ---------------------------- - #ifdef __WIN32 - unsigned long iMode=1; - ioctlsocket(sock,FIONBIO,&iMode); - #else - fcntl(sock, F_SETFL, O_NONBLOCK); - #endif - - // establish a connection to the server - // ------------------------------------ - memset(&addr,0,sizeof(SOCKADDR_IN)); - addr.sin_family=AF_INET; - addr.sin_port=htons(port); - - // convert the string representation of the ip to a byte representation - addr.sin_addr.s_addr=inet_addr(ip); - - return SUCCESS; -} - -// arg1: string message to send -// return: NULL on failure, SUCCESS otherwise -extern "C" DLL_EXPORT const char* send_message(int n, char *v[]) -{ - // extract the args - if(n < 1) return 0; - const char* msg = v[0]; - - // send the message - int rc = sendto(sock,msg,strlen(msg),0,(SOCKADDR*)&addr,sizeof(SOCKADDR)); - - // check for errors - if (rc != -1) { - return SUCCESS; - } - else { - return 0; - } -} - -// no args -// return: message if any received, NULL otherwise -extern "C" DLL_EXPORT const char* recv_message(int n, char *v[]) -{ - SOCKADDR_IN sender; // we will store the sender address here - - socklen_t sender_byte_length = sizeof(sender); - - // Try receiving messages until we receive one that's valid, or there are no more messages - while(1) { - int rc = recvfrom(sock, return_buffer, BUFFER_SIZE,0,(SOCKADDR*) &sender,&sender_byte_length); - if(rc > 0) { - // we could read something - - if(sender.sin_addr.s_addr != addr.sin_addr.s_addr) { - continue; // not our connection, ignore and try again - } else { - return_buffer[rc] = 0; // 0-terminate the string - return return_buffer; - } - } - else { - break; // no more messages, stop trying to receive - } - } - - return 0; -} diff --git a/lib/DLLSocket/server_controller.py b/lib/DLLSocket/server_controller.py deleted file mode 100644 index 943f37f677..0000000000 --- a/lib/DLLSocket/server_controller.py +++ /dev/null @@ -1,51 +0,0 @@ -import subprocess -import socket -import urlparse - -UDP_IP="127.0.0.1" -UDP_PORT=8019 - -sock = socket.socket( socket.AF_INET, # Internet - socket.SOCK_DGRAM ) # UDP -sock.bind( (UDP_IP,UDP_PORT) ) - -last_ticker_state = None - -def handle_message(data, addr): - global last_ticker_state - - params = urlparse.parse_qs(data) - print(data) - - try: - if params["type"][0] == "log" and str(params["log"][0]) and str(params["message"][0]): - open(params["log"][0],"a+").write(params["message"][0]+"\n") - except IOError: - pass - except KeyError: - pass - - try: - if params["type"][0] == "ticker_state" and str(params["message"][0]): - last_ticker_state = str(params["message"][0]) - except KeyError: - pass - - try: - if params["type"][0] == "startup" and last_ticker_state: - open("crashlog.txt","a+").write("Server exited, last ticker state was: "+last_ticker_state+"\n") - except KeyError: - pass - -sock.settimeout(60*6) # 10 minute timeout -while True: - try: - data, addr = sock.recvfrom( 1024 ) # buffer size is 1024 bytes - handle_message(data,addr) - except socket.timeout: - # try to start the server again - print("Server timed out.. attempting restart.") - if last_ticker_state: - open("crashmsg.txt","a+").write("Server crashed, trying to reboot. last ticker state: "+last_ticker_state+"\n") - subprocess.call("killall -9 DreamDaemon") - subprocess.call("./start") \ No newline at end of file diff --git a/maps/common/common_defines.dm b/maps/common/common_defines.dm index 1607e4ac09..1bd3a99f13 100644 --- a/maps/common/common_defines.dm +++ b/maps/common/common_defines.dm @@ -181,6 +181,16 @@ skybox_pixel_x = 450 skybox_pixel_y = 200 +GLOBAL_LIST_EMPTY(all_stellar_delights) + +/obj/effect/overmap/visitable/ship/stellar_delight/Initialize(mapload) + . = ..() + GLOB.all_stellar_delights += src + +/obj/effect/overmap/visitable/ship/stellar_delight/Destroy() + GLOB.all_stellar_delights -= src + . = ..() + /obj/effect/overmap/visitable/sector/virgo2 name = "Virgo 2" desc = "Includes the Remmi Aerostat and associated ground mining complexes." diff --git a/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm b/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm index e256fb3204..840a654c49 100644 --- a/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm +++ b/maps/expedition_vr/aerostat/_aerostat_science_outpost.dm @@ -5,9 +5,13 @@ initial_generic_waypoints = list("aerostat_n_w", "aerostat_n_n","aerostat_n_e","aerostat_s_w","aerostat_s_s","aerostat_s_e","aerostat_west","aerostat_east") /obj/effect/overmap/visitable/sector/virgo2/Initialize(mapload) - for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in world) - docking_codes = sd.docking_codes . = ..() + return INITIALIZE_HINT_LATELOAD + +/obj/effect/overmap/visitable/sector/virgo2/LateInitialize() + for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in GLOB.all_stellar_delights) + docking_codes = sd.docking_codes + return // -- Datums -- // diff --git a/maps/expedition_vr/space/_debrisfield.dm b/maps/expedition_vr/space/_debrisfield.dm index 8c021ce59f..e61e125305 100644 --- a/maps/expedition_vr/space/_debrisfield.dm +++ b/maps/expedition_vr/space/_debrisfield.dm @@ -175,7 +175,7 @@ rename_areas(newname) /obj/effect/overmap/visitable/ship/landable/luxury_boat/proc/rename_areas(newname) - if(!SSshuttles.subsystem_initialized) + if(!SSshuttles.initialized) spawn(300) rename_areas(newname) return @@ -266,7 +266,7 @@ rename_areas(newname) /obj/effect/overmap/visitable/ship/landable/tinycarrier/proc/rename_areas(newname) - if(!SSshuttles.subsystem_initialized) + if(!SSshuttles.initialized) spawn(300) rename_areas(newname) return diff --git a/maps/gateway_vr/wildwest.dm b/maps/gateway_vr/wildwest.dm index e503f43dd8..757f0939eb 100644 --- a/maps/gateway_vr/wildwest.dm +++ b/maps/gateway_vr/wildwest.dm @@ -79,7 +79,7 @@ if("To Kill") to_chat(user, span_boldwarning("Your wish is granted, but at a terrible cost...")) to_chat(user, span_warning("The Wish Granter punishes you for your wickedness, claiming your soul and warping your body to match the darkness in your heart.")) - ticker.mode.traitors += user.mind + SSticker.mode.traitors += user.mind user.mind.special_role = "traitor" var/datum/objective/hijack/hijack = new hijack.owner = user.mind diff --git a/maps/stellar_delight/stellar_delight_defines.dm b/maps/stellar_delight/stellar_delight_defines.dm index 612baf025a..7aab63553d 100644 --- a/maps/stellar_delight/stellar_delight_defines.dm +++ b/maps/stellar_delight/stellar_delight_defines.dm @@ -356,9 +356,11 @@ /////SD Starts at V3b to pick up crew refuel and repair (And to make sure it doesn't spawn on hazards) /obj/effect/overmap/visitable/sector/virgo3b/Initialize(mapload) . = ..() - for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in world) + return INITIALIZE_HINT_LATELOAD + +/obj/effect/overmap/visitable/sector/virgo3b/LateInitialize() + for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in GLOB.all_stellar_delights) sd.forceMove(loc, SOUTH) - return /obj/effect/overmap/visitable/sector/virgo3b/Crossed(var/atom/movable/AM) . = ..() diff --git a/maps/stellar_delight/stellar_delight_shuttle_defs.dm b/maps/stellar_delight/stellar_delight_shuttle_defs.dm index 7e55444eb5..16d6bbb53a 100644 --- a/maps/stellar_delight/stellar_delight_shuttle_defs.dm +++ b/maps/stellar_delight/stellar_delight_shuttle_defs.dm @@ -136,11 +136,13 @@ mob_announce_cooldown = 0 /////SD Starts at V3b to pick up crew refuel and repair (And to make sure it doesn't spawn on hazards) -/obj/effect/overmap/visitable/sector/virgo3b/Initialize() +/obj/effect/overmap/visitable/sector/virgo3b/Initialize(mapload) . = ..() - for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in world) + return INITIALIZE_HINT_LATELOAD + +/obj/effect/overmap/visitable/sector/virgo3b/LateInitialize() + for(var/obj/effect/overmap/visitable/ship/stellar_delight/sd in GLOB.all_stellar_delights) sd.forceMove(loc, SOUTH) - return /obj/effect/overmap/visitable/sector/virgo3b/Crossed(var/atom/movable/AM) . = ..() diff --git a/maps/submaps/admin_use_vr/event_autonomous_drone.dm b/maps/submaps/admin_use_vr/event_autonomous_drone.dm index 860f2a468b..51b572b2bf 100644 --- a/maps/submaps/admin_use_vr/event_autonomous_drone.dm +++ b/maps/submaps/admin_use_vr/event_autonomous_drone.dm @@ -52,7 +52,7 @@ rename_areas(newname) /obj/effect/overmap/visitable/ship/landable/event_autonomous_drone/proc/rename_areas(newname) - if(!SSshuttles.subsystem_initialized) + if(!SSshuttles.initialized) spawn(300) rename_areas(newname) return diff --git a/maps/templates/unit_tests.dmm b/maps/templates/unit_tests.dmm new file mode 100644 index 0000000000..a3b03e27e5 --- /dev/null +++ b/maps/templates/unit_tests.dmm @@ -0,0 +1,79 @@ +//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE +"a" = ( +/turf/closed/indestructible, +/area/misc/testroom) +"m" = ( +/turf/simulated/floor/tiled/steel, +/area/misc/testroom) +"r" = ( +/obj/effect/landmark/unit_test_top_right, +/turf/simulated/floor/tiled/steel, +/area/misc/testroom) +"L" = ( +/obj/effect/landmark/unit_test_bottom_left, +/turf/simulated/floor/tiled/steel, +/area/misc/testroom) + +(1,1,1) = {" +a +a +a +a +a +a +a +"} +(2,1,1) = {" +a +m +m +m +m +L +a +"} +(3,1,1) = {" +a +m +m +m +m +m +a +"} +(4,1,1) = {" +a +m +m +m +m +m +a +"} +(5,1,1) = {" +a +m +m +m +m +m +a +"} +(6,1,1) = {" +a +r +m +m +m +m +a +"} +(7,1,1) = {" +a +a +a +a +a +a +a +"} diff --git a/maps/tether/tether_phoronlock.dm b/maps/tether/tether_phoronlock.dm index 7b7ce6a321..929e045910 100644 --- a/maps/tether/tether_phoronlock.dm +++ b/maps/tether/tether_phoronlock.dm @@ -56,10 +56,10 @@ set_frequency(frequency) /obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/proc/set_frequency(new_frequency) - radio_controller.remove_object(src, frequency) + SSradio.remove_object(src, frequency) frequency = new_frequency if(frequency) - radio_connection = radio_controller.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) + radio_connection = SSradio.add_object(src, frequency, radio_filter = RADIO_ATMOSIA) /obj/machinery/portable_atmospherics/powered/scrubber/huge/stationary/receive_signal(datum/signal/signal) if(!signal.data["tag"] || (signal.data["tag"] != scrub_id) || (signal.data["sigtype"] != "command")) diff --git a/maps/~map_system/maps.dm b/maps/~map_system/maps.dm index 6c455b2f64..ebb1c3c9e5 100644 --- a/maps/~map_system/maps.dm +++ b/maps/~map_system/maps.dm @@ -139,6 +139,8 @@ var/list/all_maps = list() var/list/planet_datums_to_make = list() // Types of `/datum/planet`s that will be instantiated by SSPlanets. + var/list/skipped_tests = list() // /datum/unit_test's to skip + /datum/map/New() ..() if(zlevel_datum_type) diff --git a/config/names/adjectives.txt b/strings/names/adjectives.txt similarity index 100% rename from config/names/adjectives.txt rename to strings/names/adjectives.txt diff --git a/config/names/ai.txt b/strings/names/ai.txt similarity index 98% rename from config/names/ai.txt rename to strings/names/ai.txt index 52ef743132..4ff0a3dd28 100644 --- a/config/names/ai.txt +++ b/strings/names/ai.txt @@ -52,4 +52,4 @@ VER-T G0 Wisdom XOR Y2K -ZE-M3 \ No newline at end of file +ZE-M3 diff --git a/strings/names/clown.txt b/strings/names/clown.txt new file mode 100644 index 0000000000..1a5f8740ae --- /dev/null +++ b/strings/names/clown.txt @@ -0,0 +1,124 @@ +Aluminium Dave +Baba +Baby Cakes +Bananium LXIX +Bimbim +Bingo +Birdman From Birdland +Bizarre Bottle +Bizarro +Bo Bo Sassy +Bonker +Bubble +Buster Frown +Butters The Bean +Buttery Muffin +Button +Candy +Carphunter +Checkers +Chuckles +Circe The Great +Clownpiece +Congo Bongo +Cool Cooper +Crusty +Deedum Dedah +Delicious Dan +Dinkster +Dinky Doodle +Doctor Greenthumb +Doctor Rockso +Doink +Early Worm +Eggy +Emotional Oatmeal +Fat Fingers +Fishbreath +Flop O'Honker +Freckle +Fretworks +Galton +Giggles +Gigglesworth +Goose McSunny +Grabby +Grandma +Grebble +Greedo Shotfurst +Happy Slappy +Harvey Harvey Harvey +Hedgerow Harry +Hefty Hempo +Homey +Honkel the III +Honker +Honkerbelle +Implausible Fun +Impro +Jazzle +Jim From Accounting +Jingle +Jo Jo Bobo Bo +Johnny Bigshoes +King of Klowns +Ladybug Honks +Laughing Man +Loopy Lazarus +Mime +Misery Marvin +Miss Stockings +Mister Ostprussen +Mister Redherring +Mister Roboto +Mister Safety Hazard +Moinen +Mr Shoe +Mr Weird +Ningelo +Oranges +Patches +Pepinpop +Pocket +Pogo +Polite Pablo +Post Nose +Razzle Dazzle +Red April +Red Dead Redemption Two +Redshirt McBeat +Rinso The Soapy +Roboflop +Ronnie Pace +Rude Robert +Sandwich Sam +Scootaloo +Shinturner +Shithead The Irritator +Shoe Giving Gerald +Silly Willy +Sir Honks-a-Lot +Skiddle +Slippo Supreme +Slippy Joe +Slipsy Dipsy +Sparkle +Speckles +Sprinkledinkle +The Amazing Farto +The Best +The Fun Uncle +The Mediocre +The Unfun Uncle +The Worst +Toodles Sharperton +Tree Talker +Twisty +Uncool Ulrich +Unimaginable Nut +Valid Vincent +Weather Report +Widderwise +Yesterdays Beef +Yobbo +Ziggy Yoyo diff --git a/config/names/death_commando.txt b/strings/names/death_commando.txt similarity index 71% rename from config/names/death_commando.txt rename to strings/names/death_commando.txt index f882ea581e..efae21fe3b 100644 --- a/config/names/death_commando.txt +++ b/strings/names/death_commando.txt @@ -1,69 +1,90 @@ +A whole bunch of spiders in a MODsuit +Al "Otta" Gore +AMERICA +Beat Punchbeef +Beef Manmuscle +Beef Slab +Benny Bloodmulch +Bicep McTricep +Billy Herrington +Blast Hardcheese +Blast Thickneck +Bob Johnson +Bold Bigflank +Bolt Vanderhuge +Brawn Hugeneck +Brick Hardmeat +Buck Plankchest +Buff Drinklots +Buff Hardback +Buff Slamchest +Butch Deadlift +Chombo Cromagnum +Crud Bonemeal +Crunch Buttsteak +Crush McStompbones +Diego Diefist +Dirk Hardpeck +Duke Killington +Evil Bob Marley +Evil Martin Luther King +Fist Rockbone +Flint Ironstag +Ford Spinetwist +Fridge Largemeat +George Melons +Gibbs McLargehuge +GORE Vidal +Gristle McThornBody +Hank Chesthair +Hans Testosteroneson +Jet Handblood Killiam Shakespeare -Stabby McGee -Sgt. Slaughter +Killing McKillingalot +Lance Killiam +Leonardo Da Viking +Lump Beefrock +Lunge Crushheel +Max Pain +Maximilian Murderface Maxx Power +Noam Bombsky +Norb Chrometaint +Oats Squatzon +Pack Blowfist +Punch Rockgroin +Punch Sideiron +Punt Speedchunk +Reef Blastbody +Rex Dudekiller VII +Rip Sidecheek +Rip Steakface +Rock Vangranite +Roid Swolebod +Roll Fizzlebeef +Ronnie Roidready +Sarah Pain +Seamus McTosterone +Sgt Slaughter +Shred Gluteflex Sir Killaslot Slab Bulkhead -Fridge Largemeat -Punt Speedchunk -Butch Deadlift -Bold Bigflank -Splint Chesthair -Flint Ironstag -Bolt Vanderhuge -Thick McRunfast -Blast Hardcheese -Buff Drinklots -Trunk Slamchest -Fist Rockbone -Stump Beefgnaw -Smash Lampjaw -Punch Rockgroin -Buck Plankchest -Stump Chunkman -Dirk Hardpeck -Rip Steakface -Slate Slabrock -Crud Bonemeal -Brick Hardmeat -Rip Sidecheek -Punch Sideiron -Gristle McThornBody -Slake Fistcrunch -Buff Hardback -Blast Thickneck -Crunch Buttsteak Slab Squatthrust -Lump Beefrock -Touch Rustrod -Reef Blastbody +Slake Fistcrunch +Slate Slabrock +Smash Lampjaw Smoke Manmuscle -Beat Punchbeef -Pack Blowfist -Roll Fizzlebeef -Lance Killiam -George Melons -Maximilian Murderface -Bob Johnson -Crush McStompbones -Hank Chesthair -Killing McKillingalot -Mancrush McBrorape -Rex Dudekiller VII -Seamus McTosterone -Hans Testosteroneson -Max Pain +Splint Chesthair +Stabby McGee +Stump Beefgnaw +Stump Chunkman Theodore Pain -Sarah Pain -GORE Vidal -Leonardo Da Viking -Noam Bombsky -Al "Otta" Gore -Gibbs McLargehuge -Evil Martin Luther King -Evil Bob Marley -Duke Killington -AMERICA +Thick McRunfast +Thrust Vanderhuge Toolboxl Rose +Touch Rustrod +Trooper Initforbenefits +Trent Tightshirt +Trunk Slamchest +Van Darkholme Zombie Gandhi -A whole bunch of spiders in a SWAT suit \ No newline at end of file diff --git a/config/names/first.txt b/strings/names/first.txt similarity index 100% rename from config/names/first.txt rename to strings/names/first.txt diff --git a/config/names/first_female.txt b/strings/names/first_female.txt similarity index 100% rename from config/names/first_female.txt rename to strings/names/first_female.txt diff --git a/config/names/first_male.txt b/strings/names/first_male.txt similarity index 100% rename from config/names/first_male.txt rename to strings/names/first_male.txt diff --git a/config/names/first_name_skrell.txt b/strings/names/first_name_skrell.txt similarity index 100% rename from config/names/first_name_skrell.txt rename to strings/names/first_name_skrell.txt diff --git a/config/names/last.txt b/strings/names/last.txt similarity index 100% rename from config/names/last.txt rename to strings/names/last.txt diff --git a/config/names/last_name_skrell.txt b/strings/names/last_name_skrell.txt similarity index 100% rename from config/names/last_name_skrell.txt rename to strings/names/last_name_skrell.txt diff --git a/config/names/ninjaname.txt b/strings/names/ninjaname.txt similarity index 100% rename from config/names/ninjaname.txt rename to strings/names/ninjaname.txt diff --git a/config/names/ninjatitle.txt b/strings/names/ninjatitle.txt similarity index 100% rename from config/names/ninjatitle.txt rename to strings/names/ninjatitle.txt diff --git a/config/names/verbs.txt b/strings/names/verbs.txt similarity index 100% rename from config/names/verbs.txt rename to strings/names/verbs.txt diff --git a/strings/names/wizardfirst.txt b/strings/names/wizardfirst.txt new file mode 100644 index 0000000000..f4822a2d79 --- /dev/null +++ b/strings/names/wizardfirst.txt @@ -0,0 +1,98 @@ +Aberforth +Ad +Aganemnom +Alatar +Aluneth +Arbiter +Arcano +Archchancellor +Asriel +Aurora +Baldric +Barachiel +Bigby +Blasto +Bob +Boccob +Boldwort +Bona +Carl +Channeler +Circe +Curley +Dickel +Edison +Ehran +Elminister +Eric +Fenty +Fernando +Fizban +Fizzy +Fumblemoar +Gabriel +Gandalf +Grimm +Gulstaff +Houdini +Jegudiel +Jim +Judge +Kaschei +Kazuo +Khelben +Koschei +Kreol +Larry +Ley +Lina +Magier +Merlin +Micheal +Mitarbeiter +Mo +Mogan +Mordenkainen +Morgan +Mortimer +Munstrum +Mystryl +Nihilus +Nikola +Odysseus +Othello +Palando +Pixel +Ponder +Prospero +Radagast +Raistlin +Raphael +Rasputin +Rihanna +Rincewind +Roduct +Saruman +Selaphiel +Sigmund +Supreme +Tabula +Temporus +Tenser +Terefi +Tim +Tom +Tzeentch +Ulysses +Uriel +Urza +Vaarsuvius +Vecna +Vlad +Willow +Wizardman +Yoda +Zagyg +Zatanna +Zifnab +Zul diff --git a/strings/names/wizardsecond.txt b/strings/names/wizardsecond.txt new file mode 100644 index 0000000000..1b51849e48 --- /dev/null +++ b/strings/names/wizardsecond.txt @@ -0,0 +1,99 @@ +Among-many +Azurepelt +Borealis +Dark +Darkmagic +Darko +Deathbringer +Doomancer +Evergreen +Evilton +Fide +Gray +Honko +Infinitum +Inverse +Lifereaper +Meatsmith +Midnight +Mother-of-dragons +Mountain-Mother +Namekeeper +Nauseam +Rasa +Runespear +Sequitur +Shado +Shadowdancer +Shorekeeper +Smith +Spellstone +Starfall +Starlightking +Starlightqueen +Stormcaller +Strange +Suneater +Truenamer +Unseen +Voidfall +Voidstabber +Volente +Weatherwax +Who-was-blood +Who-was-light +Who-was-stone +Windshearer +Worldshaker +Yagg +le Fay +of Void +the Alchemist +the All Knowing +the Amazing +the Annoyed +the Annoying +the Bandit Killer +the Benevolent +the Blue +the Brown +the Colourless +the Conquerer +the Dappled +the Deathless +the Destroyer +the Dragon Spooker +the Dragoncaller +the Drunk +the Emperor +the Empty +the Ethereal +the Excoherent +the Gray +the Great +the Herb +the Impaler +the Incoherent +the Jaunter +the Magician +the Manual +the Moldywarp +the Necromancer +the Obscure +the Powerful +the Raven +the Red +the Remorseful +the Seething +the Sorcelator +the Spiral King +the Unending +the Unseen +the Unstoppable +the Wandsmith +the Waxmancer +the Weeping +the White +the Wife +the Wise +the Wizzard diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/OverviewSection.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/OverviewSection.tsx new file mode 100644 index 0000000000..631b2525b4 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ControllerOverview/OverviewSection.tsx @@ -0,0 +1,75 @@ +import { Button, LabeledList, Section, Stack } from 'tgui-core/components'; + +import { useBackend } from '../../backend'; +import { type ControllerData } from './types'; + +export function OverviewSection(props) { + const { act, data } = useBackend(); + const { + fast_update, + rolling_length, + map_cpu, + subsystems = [], + world_time, + } = data; + + let avgUsage = 0; + let overallOverrun = 0; + for (let i = 0; i < subsystems.length; i++) { + avgUsage += subsystems[i].usage_per_tick; + overallOverrun += subsystems[i].tick_overrun; + } + + return ( +
+ + { + act('set_rolling_length', { + rolling_length: value, + }); + }} + /> + + } + > + + + + + {world_time.toFixed(1)} + + + {map_cpu.toFixed(2)}% + + + + + + + {avgUsage.toFixed(2)}% + + + {overallOverrun.toFixed(2)}% + + + + +
+ ); +} diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemDialog.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemDialog.tsx new file mode 100644 index 0000000000..6f7cae9c4b --- /dev/null +++ b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemDialog.tsx @@ -0,0 +1,76 @@ +import { + Box, + Button, + Divider, + LabeledList, + Modal, + Stack, +} from 'tgui-core/components'; + +import { type SubsystemData } from './types'; + +type Props = { + subsystem: SubsystemData; + onClose: () => void; +}; + +export function SubsystemDialog(props: Props) { + const { subsystem, onClose } = props; + const { + cost_ms, + init_order, + initialization_failure_message, + last_fire, + name, + next_fire, + tick_overrun, + tick_usage, + usage_per_tick, + } = subsystem; + + return ( + + + + {name} + + + + + + + ); +} diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemRow.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemRow.tsx new file mode 100644 index 0000000000..1eecb6891f --- /dev/null +++ b/tgui/packages/tgui/interfaces/ControllerOverview/SubsystemRow.tsx @@ -0,0 +1,107 @@ +import { type Dispatch } from 'react'; +import { + Button, + Icon, + ProgressBar, + Stack, + Table, + Tooltip, +} from 'tgui-core/components'; + +import { useBackend } from '../../backend'; +import { SORTING_TYPES } from './constants'; +import { SortType, type SubsystemData } from './types'; + +type Props = { + max: number; + setSelected: Dispatch; + showBars: boolean; + sortType: SortType; + subsystem: SubsystemData; +}; + +export function SubsystemRow(props: Props) { + const { act } = useBackend(); + const { max, setSelected, showBars, sortType, subsystem } = props; + const { can_fire, doesnt_fire, initialized, name, ref } = subsystem; + + const { propName } = SORTING_TYPES[sortType]; + const value = subsystem[propName]; + + let icon = 'play'; + let color = 'good'; + let tooltip = 'Operational'; + if (!initialized) { + icon = 'circle-exclamation'; + color = 'darkgreen'; + tooltip = 'Not initialized'; + } else if (doesnt_fire) { + icon = 'check'; + color = 'grey'; + tooltip = 'Does not fire'; + } else if (!can_fire) { + icon = 'pause'; + color = 'grey'; + tooltip = 'Paused'; + } + + let valueDisplay = ''; + let rangeDisplay = {}; + if (showBars) { + if (sortType === SortType.Cost) { + valueDisplay = value.toFixed(2) + 'ms'; + rangeDisplay = { + average: [75, 124.99], + bad: [125, Infinity], + }; + } else { + valueDisplay = value.toFixed(2) + '%'; + rangeDisplay = { + average: [10, 24.99], + bad: [25, Infinity], + }; + } + } else { + valueDisplay = value; + } + + return ( + + + + + + + setSelected(subsystem)}> + {showBars ? ( + + {name} {valueDisplay} + + ) : ( + + )} + + + + + + } + > + + {sorted.map((subsystem) => ( + + ))} +
+ + ); +} diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts b/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts new file mode 100644 index 0000000000..62953cc7dd --- /dev/null +++ b/tgui/packages/tgui/interfaces/ControllerOverview/constants.ts @@ -0,0 +1,48 @@ +type SortType = { + label: string; + propName: string; + inDeciseconds: boolean; +}; + +export const SORTING_TYPES: readonly SortType[] = [ + { + label: 'Alphabetical', + propName: 'name', + inDeciseconds: false, + }, + { + label: 'Cost', + propName: 'cost_ms', + inDeciseconds: true, + }, + { + label: 'Init Order', + propName: 'init_order', + inDeciseconds: false, + }, + { + label: 'Last Fire', + propName: 'last_fire', + inDeciseconds: false, + }, + { + label: 'Next Fire', + propName: 'next_fire', + inDeciseconds: false, + }, + { + label: 'Tick Usage', + propName: 'tick_usage', + inDeciseconds: true, + }, + { + label: 'Avg Usage Per Tick', + propName: 'usage_per_tick', + inDeciseconds: true, + }, + { + label: 'Tick Overrun', + propName: 'tick_overrun', + inDeciseconds: true, + }, +]; diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/filters.ts b/tgui/packages/tgui/interfaces/ControllerOverview/filters.ts new file mode 100644 index 0000000000..4df6f242dd --- /dev/null +++ b/tgui/packages/tgui/interfaces/ControllerOverview/filters.ts @@ -0,0 +1,45 @@ +import { type SortType } from './types'; + +export type FilterState = { + ascending: boolean; + inactive: boolean; + query: string; + smallValues: boolean; + sortType: SortType; +}; + +export enum FilterAction { + Ascending = 'SET_SORT_ASCENDING', + Inactive = 'SET_FILTER_INACTIVE', + SmallValues = 'SET_FILTER_SMALL_VALUES', + SortType = 'SET_SORT_TYPE', + Query = 'SET_FILTER_QUERY', + Update = 'UPDATE_FILTER', +} + +type Action = + | { type: FilterAction.Ascending; payload: boolean } + | { type: FilterAction.Inactive; payload: boolean } + | { type: FilterAction.SmallValues; payload: boolean } + | { type: FilterAction.SortType; payload: SortType } + | { type: FilterAction.Query; payload: string } + | { type: FilterAction.Update; payload: Partial }; + +export function filterReducer(state: FilterState, action: Action): FilterState { + switch (action.type) { + case FilterAction.Inactive: + return { ...state, inactive: action.payload }; + case FilterAction.SmallValues: + return { ...state, smallValues: action.payload }; + case FilterAction.Ascending: + return { ...state, ascending: action.payload }; + case FilterAction.SortType: + return { ...state, sortType: action.payload }; + case FilterAction.Query: + return { ...state, query: action.payload }; + case FilterAction.Update: + return { ...state, ...action.payload }; + default: + return state; + } +} diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/index.tsx b/tgui/packages/tgui/interfaces/ControllerOverview/index.tsx new file mode 100644 index 0000000000..3d5250df06 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ControllerOverview/index.tsx @@ -0,0 +1,152 @@ +import { useReducer, useState } from 'react'; +import { Button, Dropdown, Input, Section, Stack } from 'tgui-core/components'; + +import { Window } from '../../layouts'; +import { SORTING_TYPES } from './constants'; +import { FilterAction, filterReducer, type FilterState } from './filters'; +import { OverviewSection } from './OverviewSection'; +import { SubsystemDialog } from './SubsystemDialog'; +import { SubsystemViews } from './SubsystemViews'; +import { SortType, type SubsystemData } from './types'; + +export function ControllerOverview(props) { + return ( + + + + + + ); +} + +export function ControllerContent(props) { + const [state, dispatch] = useReducer(filterReducer, { + ascending: true, + inactive: true, + query: '', + smallValues: false, + sortType: SortType.Name, + }); + + const [selected, setSelected] = useState(); + + const { label, inDeciseconds } = + SORTING_TYPES?.[state.sortType] || SORTING_TYPES[0]; + + function onSelectionHandler(value: string) { + const updates: Partial = { + sortType: SORTING_TYPES.findIndex((type) => type.label === value), + }; + + if (updates.sortType === undefined) return; + + const { inDeciseconds } = SORTING_TYPES[updates.sortType]; + + updates.ascending = !inDeciseconds; + updates.smallValues = inDeciseconds; + + dispatch({ type: FilterAction.Update, payload: updates }); + } + + return ( + + {selected && ( + setSelected(undefined)} + subsystem={selected} + /> + )} + + + + +
+ + + + + + dispatch({ type: FilterAction.Query, payload: value }) + } + placeholder="By name" + value={state.query} + width="85%" + /> + + + + + + + + + + + type.label)} + selected={label} + displayText={label} + onSelected={onSelectionHandler} + /> + + + + + + + + +
+
+ + + +
+ ); +} diff --git a/tgui/packages/tgui/interfaces/ControllerOverview/types.ts b/tgui/packages/tgui/interfaces/ControllerOverview/types.ts new file mode 100644 index 0000000000..4dac0552d3 --- /dev/null +++ b/tgui/packages/tgui/interfaces/ControllerOverview/types.ts @@ -0,0 +1,35 @@ +import { type BooleanLike } from 'tgui-core/react'; + +export type SubsystemData = { + can_fire: BooleanLike; + cost_ms: number; + doesnt_fire: BooleanLike; + init_order: number; + initialization_failure_message: string | undefined; + initialized: BooleanLike; + last_fire: number; + name: string; + next_fire: number; + ref: string; + tick_overrun: number; + tick_usage: number; + usage_per_tick: number; +}; + +export type ControllerData = { + world_time: number; + fast_update: BooleanLike; + rolling_length: number; + map_cpu: number; + subsystems: SubsystemData[]; +}; + +export enum SortType { + Name, + Cost, + InitOrder, + LastFire, + NextFire, + TickUsage, + TickOverrun, +} diff --git a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx index 51d97e51e1..bb82dfc575 100644 --- a/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx +++ b/tgui/packages/tgui/interfaces/PreferencesMenu/preferences/features/game_preferences/ui.tsx @@ -96,3 +96,13 @@ export const tgui_say_width: FeatureNumeric = { description: 'The width to show in the tgui say input.', component: FeatureSliderInput, }; + +export const windowflashing: FeatureToggle = { + name: 'Enable window flashing', + category: 'UI', + description: ` + When toggled, some important events will make your game icon flash on your + task tray. + `, + component: CheckboxInput, +}; diff --git a/tools/ci/annotate_dm.sh b/tools/ci/annotate_dm.sh new file mode 100644 index 0000000000..e43f930ba1 --- /dev/null +++ b/tools/ci/annotate_dm.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +set -euo pipefail +tools/bootstrap/python -m dm_annotator "$@" diff --git a/tools/ci/annotate_od.sh b/tools/ci/annotate_od.sh new file mode 100644 index 0000000000..1239090807 --- /dev/null +++ b/tools/ci/annotate_od.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +set -euo pipefail +tools/bootstrap/python -m od_annotator "$@" diff --git a/tools/ci/check_changelogs.sh b/tools/ci/check_changelogs.sh new file mode 100644 index 0000000000..112a843f52 --- /dev/null +++ b/tools/ci/check_changelogs.sh @@ -0,0 +1,5 @@ +#!/bin/bash +set -euo pipefail + +md5sum -c - <<< "0c56937110d88f750a32d9075ddaab8b *html/changelogs/example.yml" +python3 tools/ss13_genchangelog.py html/changelogs diff --git a/tools/ci/check_genesis.sh b/tools/ci/check_genesis.sh new file mode 100644 index 0000000000..08d064af17 --- /dev/null +++ b/tools/ci/check_genesis.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -euo pipefail + +#ANSI Escape Codes for colors to increase contrast of errors +RED="\033[0;31m" +GREEN="\033[0;32m" +BLUE="\033[0;34m" +NC="\033[0m" # No Color + +if sha256sum -c tools/ci/genesis_call.dme.sha256sum ; then + echo -e "${GREEN}code/genesis_call.dme is unchanged.${NC}" +else + echo -e "${RED}code/genesis_call.dme has been changed!${NC}" + echo -e "${BLUE}This is likely not intentional, please revert any changes made to it.${NC}" + echo -e "${BLUE}On the unlikely occurance that you ARE intentionally modifying that file, replace the contents of tools/ci/genesis_call.dme.sha256sum with the following:${NC}" + + sha256sum tools/ci/genesis_call.dme.sha256sum + + exit 1 +fi diff --git a/tools/ci/validate_files.sh b/tools/ci/check_grep.sh old mode 100755 new mode 100644 similarity index 95% rename from tools/ci/validate_files.sh rename to tools/ci/check_grep.sh index 217896c1de..c259074071 --- a/tools/ci/validate_files.sh +++ b/tools/ci/check_grep.sh @@ -1,16 +1,16 @@ #!/bin/bash set -euo pipefail -# nb: must be bash to support shopt globstar +#nb: must be bash to support shopt globstar shopt -s globstar extglob source dependencies.sh -# ANSI Colors -RED='\033[0;31m' +#ANSI Escape Codes for colors to increase contrast of errors +RED="\033[0;31m" GREEN="\033[0;32m" BLUE="\033[0;34m" -NC='\033[0m' # No color +NC="\033[0m" # No Color FAILED=0 @@ -136,15 +136,6 @@ if $grep -i 'var/list/static/.*' $code_files; then st=1 fi; -part "changelog" -#Checking for a change to html/changelogs/example.yml -md5sum -c - <<< "0c56937110d88f750a32d9075ddaab8b *html/changelogs/example.yml" -retVal=$? -if [ $retVal -ne 0 ]; then - echo -e "${RED}Do not modify the example.yml changelog file.${NC}" - FAILED=1 -fi; - part "color macros" #Checking for color macros (num=`$grep -n '\\\\(red|blue|green|black|b|i[^mnc])' $code_files | wc -l`; echo "$num escapes (expecting ${MACRO_COUNT} or less)"; [ $num -le ${MACRO_COUNT} ]) diff --git a/tools/ci/check_misc.sh b/tools/ci/check_misc.sh new file mode 100644 index 0000000000..4f22294d29 --- /dev/null +++ b/tools/ci/check_misc.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -euo pipefail + +# This could be useful for misc files soon diff --git a/tools/ci/compile_and_run.sh b/tools/ci/compile_and_run.sh index 25e60bc5ac..3b449621ab 100755 --- a/tools/ci/compile_and_run.sh +++ b/tools/ci/compile_and_run.sh @@ -24,10 +24,10 @@ if [ $exitVal -gt 0 ]; then exit 1 fi -# If we're running, run +# This variable is set in the CI scripts depending if we need to run the codebase or not. +# Extra map tests for example have it set to false if [ $RUN -eq 1 ]; then - DreamDaemon $BASENAME.dmb -close -trusted -invisible -verbose -core 2>&1 | tee log.txt; - grep "All Unit Tests Passed" log.txt || exit 1 - grep "Caught 0 Runtimes" log.txt || exit 1 + DreamDaemon vorestation.dmb -close -invisible -trusted -verbose -params "log-directory=ci" + cat data/logs/ci/clean_run.lk fi diff --git a/tools/ci/genesis_call.dme.sha256sum b/tools/ci/genesis_call.dme.sha256sum new file mode 100644 index 0000000000..7238dacd38 --- /dev/null +++ b/tools/ci/genesis_call.dme.sha256sum @@ -0,0 +1 @@ +393cbb0404a017c61777b178fe5612e033a396550e8633a51fd902cc9912b8b6 code/genesis_call.dme diff --git a/tools/dm_annotator/__main__.py b/tools/dm_annotator/__main__.py new file mode 100644 index 0000000000..4948fd0865 --- /dev/null +++ b/tools/dm_annotator/__main__.py @@ -0,0 +1,51 @@ +import sys +import re +import os.path as path + +# Usage: tools/bootstrap/python -m dm_annotator [filename] +# If filename is not provided, stdin is checked instead + +def red(text): + return "\033[31m" + str(text) + "\033[0m" + +def green(text): + return "\033[32m" + str(text) + "\033[0m" + +def yellow(text): + return "\033[33m" + str(text) + "\033[0m" + +def annotate(raw_output): + # Remove ANSI escape codes + raw_output = re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]', '', raw_output) + + print("::group::DreamChecker Output") + print(raw_output) + print("::endgroup::") + + annotation_regex = r'(?P.*?), line (?P\d+), column (?P\d+):\s{1,2}(?Perror|warning): (?P.*)' + has_issues = False + + print("DM Code Annotations:") + for annotation in re.finditer(annotation_regex, raw_output): + print(f"::{annotation['type']} file={annotation['filename']},line={annotation['line']},col={annotation['column']}::{annotation['message']}") + has_issues = True + + if not has_issues: + print(green("No DM issues found")) + +def main(): + if len(sys.argv) > 1: + if not path.exists(sys.argv[1]): + print(red(f"Error: Annotations file '{sys.argv[1]}' does not exist")) + sys.exit(1) + with open(sys.argv[1], 'r') as f: + annotate(f.read()) + elif not sys.stdin.isatty(): + annotate(sys.stdin.read()) + else: + print(red("Error: No input provided")) + print("Usage: tools/bootstrap/python -m dm_annotator [filename]") + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/tools/initToast/initToast.ps1 b/tools/initToast/initToast.ps1 new file mode 100644 index 0000000000..9b7714f3a2 --- /dev/null +++ b/tools/initToast/initToast.ps1 @@ -0,0 +1,32 @@ +param([string]$name, [string]$icon, [Int32]$port=80) + +$hkcu = [Microsoft.Win32.RegistryKey]::OpenBaseKey('CurrentUser','default') + +$amuid_hkey = $hkcu.CreateSubKey('SOFTWARE\Classes\AppUserModelId\Tgstation.Tgstation', $true, [Microsoft.Win32.RegistryOptions]::Volatile) + +if ($name) { $amuid_hkey.SetValue('DisplayName', $name) } +if ($icon) { $amuid_hkey.SetValue('IconUri', $icon) } + +[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null +$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Tgstation.Tgstation') + +[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] > $null +$xml = New-Object Windows.Data.Xml.Dom.XmlDocument + +$xml.LoadXml(@" + + + + Initialization Complete + + + + + + +"@) + +[Windows.UI.Notifications.ToastNotification, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null +$toast = New-Object Windows.UI.Notifications.ToastNotification $xml + +$notifier.Show($toast) diff --git a/tools/od_annotator/__main__.py b/tools/od_annotator/__main__.py new file mode 100644 index 0000000000..357adccfe9 --- /dev/null +++ b/tools/od_annotator/__main__.py @@ -0,0 +1,50 @@ +import sys +import re + +def green(text): + return "\033[32m" + str(text) + "\033[0m" + +def red(text): + return "\033[31m" + str(text) + "\033[0m" + +def annotate(raw_output): + # Remove ANSI escape codes + raw_output = re.sub(r'(\x9B|\x1B\[)[0-?]*[ -\/]*[@-~]', '', raw_output) + + print("::group::OpenDream Output") + print(raw_output) + print("::endgroup::") + + annotation_regex = r'((?PError|Warning) (?POD(?P\d{4})) at (?P(?P.+):(?P\d+):(?P\d+)|): (?P.+))' + failures_detected = False + expected_failure_case_detected = False # this is just here so this script breaks if we forget to set it to True when we expect a failure. remove this when we have handled the expected failure + + print("OpenDream Code Annotations:") + for annotation in re.finditer(annotation_regex, raw_output): + message = annotation['message'] + if message == "Unimplemented proc & var warnings are currently suppressed": # this happens every single run, it's important to know about it but we don't need to throw an error + message += " (This is expected and can be ignored)" # also there's no location for it to annotate to since it's an failure. + expected_failure_case_detected = True + + if annotation['type'] == "Error": + failures_detected = True + + error_string = f"{annotation['errorcode']}: {message}" + + if annotation['location'] == "": + print(f"::{annotation['type']} file=,line=,col=::{error_string}") + else: + print(f"::{annotation['type']} file={annotation['filename']},line={annotation['line']},col={annotation['column']}::{error_string}") + + if failures_detected: + sys.exit(1) + return + + if not expected_failure_case_detected: + print(red("Failed to detect the expected failure case! If you have recently changed how we work with OpenDream Pragmas, please fix the od_annotator script!")) + sys.exit(1) + return + + print(green("No OpenDream issues found!")) + +annotate(sys.stdin.read()) diff --git a/tools/ticked_file_enforcement/schemas/unit_tests.json b/tools/ticked_file_enforcement/schemas/unit_tests.json new file mode 100644 index 0000000000..fad62267b7 --- /dev/null +++ b/tools/ticked_file_enforcement/schemas/unit_tests.json @@ -0,0 +1,7 @@ +{ + "file": "code/modules/unit_tests/_unit_tests.dm", + "scannable_directory": "code/modules/unit_tests/", + "subdirectories": false, + "excluded_files": ["find_reference_sanity.dm"], + "forbidden_includes": [] +} diff --git a/tools/ticked_file_enforcement/schemas/vorestation_dme.json b/tools/ticked_file_enforcement/schemas/vorestation_dme.json new file mode 100644 index 0000000000..f381e6056d --- /dev/null +++ b/tools/ticked_file_enforcement/schemas/vorestation_dme.json @@ -0,0 +1,10 @@ +{ + "file": "vorestation.dme", + "scannable_directory": "code/", + "subdirectories": true, + "excluded_files": [], + "forbidden_includes": [ + "code/modules/tgs/**/*.dm", + "code/modules/unit_tests/[!_]*.dm" + ] +} diff --git a/tools/ticked_file_enforcement/ticked_file_enforcement.py b/tools/ticked_file_enforcement/ticked_file_enforcement.py new file mode 100644 index 0000000000..86c399c735 --- /dev/null +++ b/tools/ticked_file_enforcement/ticked_file_enforcement.py @@ -0,0 +1,156 @@ +import codecs +import fnmatch +import functools +import glob +import json +import os +import sys + +# simple way to check if we're running on github actions, or on a local machine +on_github = os.getenv("GITHUB_ACTIONS") == "true" + +def green(text): + return "\033[32m" + str(text) + "\033[0m" + +def red(text): + return "\033[31m" + str(text) + "\033[0m" + +def blue(text): + return "\033[34m" + str(text) + "\033[0m" + +schema = json.load(sys.stdin) +file_reference = schema["file"] +file_reference_basename = os.path.basename(file_reference) +scannable_directory = schema["scannable_directory"] +subdirectories = schema["subdirectories"] +FORBIDDEN_INCLUDES = schema["forbidden_includes"] +excluded_files = schema["excluded_files"] + +def post_error(string): + print(red(f"Ticked File Enforcement [{file_reference}]: " + string)) + if on_github: + print(f"::error file={file_reference},line=1,title=Ticked File Enforcement::{string}") + +for excluded_file in excluded_files: + full_file_path = scannable_directory + excluded_file + if not os.path.isfile(full_file_path): + post_error(f"Excluded file {full_file_path} does not exist, please remove it!") + sys.exit(1) + +file_extensions = ("dm", "dmf") + +reading = False +lines = [] +total = 0 + +with open(file_reference, 'r') as file: + for line in file: + total += 1 + line = line.strip() + + if line == "// BEGIN_INCLUDE": + reading = True + continue + elif line == "// END_INCLUDE": + break + elif not reading: + continue + + lines.append(line) + +offset = total - len(lines) +print(blue(f"Ticked File Enforcement: {offset} lines were ignored in output for [{file_reference}].")) +fail_no_include = False + +scannable_files = [] +for file_extension in file_extensions: + compiled_directory = f"{scannable_directory}/**/*.{file_extension}" + scannable_files += glob.glob(compiled_directory, recursive=True) + +if len(scannable_files) == 0: + post_error(f"No files were found in {scannable_directory}. Ticked File Enforcement has failed!") + sys.exit(1) + +for code_file in scannable_files: + dm_path = "" + + if subdirectories is True: + dm_path = code_file.replace('/', '\\') + else: + dm_path = os.path.basename(code_file) + + included = f"#include \"{dm_path}\"" in lines + + forbid_include = False + for forbidable in FORBIDDEN_INCLUDES: + if not fnmatch.fnmatch(code_file, forbidable): + continue + + forbid_include = True + + if included: + post_error(f"{dm_path} should NOT be included.") + fail_no_include = True + + if forbid_include: + continue + + if not included: + if(dm_path == file_reference_basename): + continue + + if(dm_path in excluded_files): + continue + + post_error(f"Missing include for {dm_path}.") + fail_no_include = True + +if fail_no_include: + sys.exit(1) + +def compare_lines(a, b): + # Remove initial include as well as the final quotation mark + a = a[len("#include \""):-1].lower() + b = b[len("#include \""):-1].lower() + + split_by_period = a.split('.') + a_suffix = "" + if len(split_by_period) >= 2: + a_suffix = split_by_period[len(split_by_period) - 1] + split_by_period = b.split('.') + b_suffix = "" + if len(split_by_period) >= 2: + b_suffix = split_by_period[len(split_by_period) - 1] + + a_segments = a.split('\\') + b_segments = b.split('\\') + + for (a_segment, b_segment) in zip(a_segments, b_segments): + a_is_file = a_segment.endswith(file_extensions) + b_is_file = b_segment.endswith(file_extensions) + + # code\something.dm will ALWAYS come before code\directory\something.dm + if a_is_file and not b_is_file: + return -1 + + if b_is_file and not a_is_file: + return 1 + + # interface\something.dm will ALWAYS come after code\something.dm + if a_segment != b_segment: + # if we're at the end of a compare, then this is about the file name + # files with longer suffixes come after ones with shorter ones + if a_suffix != b_suffix: + return (a_suffix > b_suffix) - (a_suffix < b_suffix) + return (a_segment > b_segment) - (a_segment < b_segment) + + print(f"Two lines were exactly the same ({a} vs. {b})") + sys.exit(1) + +sorted_lines = sorted(lines, key = functools.cmp_to_key(compare_lines)) +for (index, line) in enumerate(lines): + if sorted_lines[index] != line: + post_error(f"The include at line {index + offset} is out of order ({line}, expected {sorted_lines[index]})") + sys.exit(1) + +print(green(f"Ticked File Enforcement: [{file_reference}] All includes (for {len(scannable_files)} scanned files) are in order!")) diff --git a/vorestation.dme b/vorestation.dme index 6e2a5a9da9..334a9ac1eb 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -1,14 +1,22 @@ -// DM Environment file for baystation12.dme. +// DM Environment file for tgstation.dme. // All manual changes should be made outside the BEGIN_ and END_ blocks. - // New source code should be placed in .dm files: choose File/New --> Code File. +// New source code should be placed in .dm files: choose File/New --> Code File. + +// This should always be the VERY FIRST THING. No exceptions. Read the file for more info, but basically, this needs to be here so that we can ensure specific code is always the very first thing to execute. +#include "code\genesis_call.dme" +// Again, DO NOT MOVE IT OR ALLOW ANY OTHER #include TO PRECEDE THE ONE ABOVE. + // BEGIN_INTERNALS // END_INTERNALS + // BEGIN_FILE_DIR #define FILE_DIR . // END_FILE_DIR + // BEGIN_PREFERENCES #define DEBUG // END_PREFERENCES + // BEGIN_INCLUDE #include "code\__byond_version_compat.dm" #include "code\__odlint.dm" @@ -16,7 +24,6 @@ #include "code\_macros.dm" #include "code\coalesce.dm" #include "code\global_init.dm" -#include "code\names.dm" #include "code\world.dm" #include "code\__defines\__globals.dm" #include "code\__defines\_atoms.dm" @@ -76,6 +83,7 @@ #include "code\__defines\experisci.dm" #include "code\__defines\faction.dm" #include "code\__defines\flags.dm" +#include "code\__defines\font_awesome_icons.dm" #include "code\__defines\fonts.dm" #include "code\__defines\footsteps.dm" #include "code\__defines\gamemode.dm" @@ -171,10 +179,10 @@ #include "code\__defines\tgui.dm" #include "code\__defines\time.dm" #include "code\__defines\tools.dm" +#include "code\__defines\tracy.dm" #include "code\__defines\traits.dm" #include "code\__defines\turfs.dm" #include "code\__defines\typeids.dm" -#include "code\__defines\unit_tests.dm" #include "code\__defines\update_icons.dm" #include "code\__defines\var_copy.dm" #include "code\__defines\vchatlog.dm" @@ -220,15 +228,18 @@ #include "code\_global_vars\logging.dm" #include "code\_global_vars\misc.dm" #include "code\_global_vars\mobs.dm" +#include "code\_global_vars\radio.dm" #include "code\_global_vars\silo.dm" #include "code\_global_vars\tgui.dm" #include "code\_global_vars\time_vars.dm" #include "code\_global_vars\typecache.dm" #include "code\_global_vars\lists\flavor_misc.dm" +#include "code\_global_vars\lists\icons.dm" #include "code\_global_vars\lists\logging.dm" #include "code\_global_vars\lists\mapping.dm" #include "code\_global_vars\lists\misc.dm" #include "code\_global_vars\lists\mob.dm" +#include "code\_global_vars\lists\names.dm" #include "code\_global_vars\lists\species.dm" #include "code\_global_vars\traits\_traits.dm" #include "code\_helpers\_global_objects.dm" @@ -257,6 +268,7 @@ #include "code\_helpers\nameof.dm" #include "code\_helpers\names.dm" #include "code\_helpers\refinery.dm" +#include "code\_helpers\roundend.dm" #include "code\_helpers\sanitize_values.dm" #include "code\_helpers\screen_objs.dm" #include "code\_helpers\shell.dm" @@ -374,7 +386,6 @@ #include "code\ATMOSPHERICS\pipes\vent.dm" #include "code\controllers\admin.dm" #include "code\controllers\autotransfer.dm" -#include "code\controllers\communications.dm" #include "code\controllers\controller.dm" #include "code\controllers\emergency_shuttle_controller.dm" #include "code\controllers\failsafe.dm" @@ -408,6 +419,7 @@ #include "code\controllers\subsystems\chat.dm" #include "code\controllers\subsystems\chemistry.dm" #include "code\controllers\subsystems\circuits.dm" +#include "code\controllers\subsystems\communications.dm" #include "code\controllers\subsystems\dbcore.dm" #include "code\controllers\subsystems\dcs.dm" #include "code\controllers\subsystems\events.dm" @@ -435,6 +447,7 @@ #include "code\controllers\subsystems\plants.dm" #include "code\controllers\subsystems\player_tips.dm" #include "code\controllers\subsystems\pois.dm" +#include "code\controllers\subsystems\profiler.dm" #include "code\controllers\subsystems\radiation.dm" #include "code\controllers\subsystems\research.dm" #include "code\controllers\subsystems\robot_sprites.dm" @@ -524,6 +537,9 @@ #include "code\datums\autolathe\tools.dm" #include "code\datums\autolathe\tools_vr.dm" #include "code\datums\changelog\changelog.dm" +#include "code\datums\cinematics\_cinematic.dm" +#include "code\datums\cinematics\malf_doomsday.dm" +#include "code\datums\cinematics\nuke_cinematics.dm" #include "code\datums\components\_component.dm" #include "code\datums\components\connect_loc_behalf.dm" #include "code\datums\components\connect_mob_behalf.dm" @@ -836,19 +852,13 @@ #include "code\defines\procs\AStar.dm" #include "code\defines\procs\radio.dm" #include "code\defines\procs\statistics.dm" -#include "code\game\atoms.dm" -#include "code\game\atoms_init.dm" #include "code\game\atoms_movable.dm" #include "code\game\base_turf.dm" #include "code\game\birthday.dm" #include "code\game\periodic_news.dm" -#include "code\game\response_team.dm" -#include "code\game\response_team_vr.dm" #include "code\game\shuttle_engines.dm" #include "code\game\skincmd.dm" #include "code\game\sound.dm" -#include "code\game\trader_visit.dm" -#include "code\game\trader_visit_vr.dm" #include "code\game\world.dm" #include "code\game\antagonist\_antagonist_setup.dm" #include "code\game\antagonist\antagonist.dm" @@ -896,7 +906,9 @@ #include "code\game\area\Space Station 13 areas.dm" #include "code\game\area\Space Station 13 areas_vr.dm" #include "code\game\area\ss13_deprecated_areas.dm" +#include "code\game\atom\_atom.dm" #include "code\game\atom\atom_vv.dm" +#include "code\game\atom\atoms_initializing_EXPENSIVE.dm" #include "code\game\dna\dna2.dm" #include "code\game\dna\dna2_domutcheck.dm" #include "code\game\dna\dna2_helpers.dm" @@ -1351,6 +1363,7 @@ #include "code\game\objects\structures.dm" #include "code\game\objects\stumble_into_vr.dm" #include "code\game\objects\trash_eating.dm" +#include "code\game\objects\unit_testing.dm" #include "code\game\objects\weapons.dm" #include "code\game\objects\effects\bump_teleporter.dm" #include "code\game\objects\effects\client_only_image.dm" @@ -2025,6 +2038,7 @@ #include "code\modules\admin\verbs\smite.dm" #include "code\modules\admin\verbs\special_verbs.dm" #include "code\modules\admin\verbs\striketeam.dm" +#include "code\modules\admin\verbs\trader.dm" #include "code\modules\admin\verbs\tgui_verbs.dm" #include "code\modules\admin\verbs\tripAI.dm" #include "code\modules\admin\verbs\SDQL2\SDQL_2.dm" @@ -2471,6 +2485,8 @@ #include "code\modules\compass\compass_waypoint.dm" #include "code\modules\compass\~compass.dm" #include "code\modules\customitems\item_spawning.dm" +#include "code\modules\debugging\debugger.dm" +#include "code\modules\debugging\tracy.dm" #include "code\modules\detectivework\footprints.dm" #include "code\modules\detectivework\forensics.dm" #include "code\modules\detectivework\microscope\dnascanner.dm" @@ -4481,6 +4497,7 @@ #include "code\modules\turbolift\turbolift_floor.dm" #include "code\modules\turbolift\turbolift_map.dm" #include "code\modules\turbolift\turbolift_turfs.dm" +#include "code\modules\unit_tests\_unit_tests.dm" #include "code\modules\vehicles\bike.dm" #include "code\modules\vehicles\boat.dm" #include "code\modules\vehicles\cargo_train.dm" @@ -4632,33 +4649,6 @@ #include "code\modules\xenobio\machinery\processor.dm" #include "code\modules\xgm\xgm_gas_data.dm" #include "code\modules\xgm\xgm_gas_mixture.dm" -#include "code\unit_tests\clothing_tests.dm" -#include "code\unit_tests\cosmetic_tests.dm" -#include "code\unit_tests\decl_tests.dm" -#include "code\unit_tests\disease_tests.dm" -#include "code\unit_tests\genetics_tests.dm" -#include "code\unit_tests\language_tests.dm" -#include "code\unit_tests\loadout_tests.dm" -#include "code\unit_tests\map_tests.dm" -#include "code\unit_tests\material_tests.dm" -#include "code\unit_tests\mob_tests.dm" -#include "code\unit_tests\poster_tests.dm" -#include "code\unit_tests\reagent_tests.dm" -#include "code\unit_tests\recipe_tests.dm" -#include "code\unit_tests\recycler_vendor_tests.dm" -#include "code\unit_tests\robot_tests.dm" -#include "code\unit_tests\sqlite_tests.dm" -#include "code\unit_tests\subsystem_tests.dm" -#include "code\unit_tests\trait_tests.dm" -#include "code\unit_tests\unit_test.dm" -#include "code\unit_tests\unit_test_vr.dm" -#include "code\unit_tests\vore_tests_vr.dm" -#include "code\unit_tests\zas_tests.dm" -#include "code\unit_tests\integrated_circuits\arithmetic.dm" -#include "code\unit_tests\integrated_circuits\circuits.dm" -#include "code\unit_tests\integrated_circuits\converter.dm" -#include "code\unit_tests\integrated_circuits\logic.dm" -#include "code\unit_tests\integrated_circuits\trig.dm" #include "code\ZAS\Airflow.dm" #include "code\ZAS\Atom.dm" #include "code\ZAS\Connection.dm"